gdata-2.0.18/0000750036466300116100000000000012156625015012732 5ustar afshareng00000000000000gdata-2.0.18/RELEASE_NOTES.txt0000640036466300116100000013142412156623513015532 0ustar afshareng00000000000000=== 2.0.18 === 1173[tip] 93c436da2d19 2013-06-14 07:21 -0700 afshar Remove unused script. 1172 e692050ec194 2013-06-14 07:20 -0700 afshar Updated docs. 1171 159874713088 2013-06-14 07:20 -0700 afshar Bumped version. 1170 7b4e3c22e83c 2013-06-07 15:52 +0200 burcud Adding missing unit attributes for unit picing elements. 1169 ad8ee900dda5 2013-06-06 19:01 +0200 burcud Fixing identifier_exists attribute for product items. 1168 d6ce8dc4970a 2013-06-06 17:53 +0200 burcud Fixing multipack attribute. 1167 b87779918c90 2013-06-06 15:10 +0200 burcud Adding new product spec attributes. 1166 624d33ad26f2 2013-02-11 12:47 -0800 rkubiak Update Sites Python API to allow page creation from a template 1165 ecb1d49b5fbe 2013-01-07 11:29 -0800 dhermes Adding an OAuth2Token subclass which can interact with google-api-python-client. 1164 f76c53eaf151 2012-11-20 16:09 +0100 burcud Modifying Content API for Shopping client to use schema projection. 1163 a8c25010b8b7 2012-08-27 13:50 -0700 dhermes Adding in ability to change Content for Shopping base URI via a kwarg for client constructor. (Fixes 6479060). 1162 c5d57eff0ef4 2012-08-15 08:31 -0700 dhermes Adding paid clicks to Content API performance datapoint. (Issue 6443130) 1161 71971b013563 2012-08-06 15:20 -0700 dhermes Adding support for adwords_accounts settings element in managedaccounts feed of Content API. (6443092) 1160 cf0208e89433 2012-07-30 09:53 -0700 dhermes Changing OAuth2 Authorization Header lead in from OAuth to Bearer (per http://goo.gl/QDiLZ). (issue 6455060) 1159 3b021605570f 2012-07-12 18:14 -0700 dhermes Adding missing name XML attribute to gdata.contentforshopping.data.Group. 1158 524fc2b2e821 2012-06-19 08:43 -0700 dhermes Renaming classes for inventory feed of Content API. (Issue 6296085) 1157 32c0cb313b2e 2012-06-14 15:36 -0700 dhermes Updating offline URI for OAuth2 and explicity specifying approval_prompt as a kw arg. (Issue 6296072) 1156 f7593ae5d035 2012-06-14 15:35 -0700 dhermes Added individual get for ManagedAccounts, functionality for paging and changed to correct endpoint. (issue 6304076) 1155 723e577ada7b 2012-06-14 13:53 -0700 dhermes Adding support for Local Products feed in Content API. (Issue 6305091) 1154 75ee2830ca74 2012-06-13 12:48 -0700 dhermes Adding support for wrong open search version hack on other ContentAPI Feed classes. (Issue 6296071) 1153 f783c64b953f 2012-06-13 10:07 -0700 dhermes Adding support for Data Quality Feed of Content API for Shopping. (Issue 6295074) 1152 4cc916619658 2012-06-13 09:07 -0700 dhermes Adding back accidentally removed code from commit c92bc870e3a4363bed2732d50d782189407af7ac. 1151 c92bc870e3a4 2012-06-13 09:01 -0700 dhermes Adding support for Content API Users Feed. (Issue 6295071) 1150 0dcb1f3aff81 2012-06-12 17:25 -0700 dhermes Adding custom id parser for Content API errors. (issue 6306073) 1149 8f11de681f03 2012-06-12 17:21 -0700 dhermes Adding support for product status elements in app:control for content for shopping. (Fixes 6299076) === 2.0.17 === 1143 3a0447b78b27 2012-04-20 13:25 -0400 jeffy The gdata.youtube.service class now explicitly sets the GData-Version: 1 header, and is updated to use the correct ClientLogin URL. 1142 1e302ccfd1db 2012-04-19 14:09 -0700 dhermes Adding support for OAuth2 revoke endpoint. 1141 7dbf78073e2c 2012-04-18 09:51 -0700 alainv Make Resource and ResourceFeed "Batch" compatible. 1140 cde9a812ff75 2012-04-02 22:25 -0700 dhermes Forgot to add camelcase alias for batch in issue 5970062. 1139 f3db7c388325 2012-04-02 20:37 -0700 dhermes Adding support for performance data in Content API for Shopping (fixes Issue 5976061). 1138 f5d8bd1a5d58 2012-04-02 20:30 -0700 dhermes Added support for custom attributes in Content API for Shopping (fixes Issue 5970070). 1137 df2263245009 2012-04-02 08:18 -0700 dhermes Changed dest attrs in Content API to iterable, added validate_dest and fixed qname on app:control (fixes Issue 5976046). 1136 6fa84c09df8e 2012-04-02 07:46 -0700 dhermes Added camel case names to content API client public methods (fixes Issue 5970062). 1135 f5d03167e6de 2012-04-02 07:38 -0700 dhermes Added support for Content for API errors on single and batch requests (fixes Issue 5971061). 1134 906bb1d9c899 2012-04-02 07:31 -0700 dhermes Added support for start-token in Content API for Shopping (closes Issue 5980044). 1133 f98fff494fb8 2012-03-30 11:36 -0700 alainv Change gdata.docs.client.DocsClient.DeleteResource to use the entry instead of the link. 1132 e3594e4ea683 2012-03-30 10:56 -0700 alainv Allow for random kwargs in atom.client.AtomClient.request method. 1131 57db9d630ee7 2012-03-29 15:44 -0700 dhermes Changing Shipping to list element in Content API for Shopping. 1130 ab61823f2562 2012-03-29 15:34 -0700 dhermes Fixing behavior in atom.http_core.Uri._get_query_string for query parameters with no parameter value (closes Issue 5938047). 1129 583a2f25c123 2012-03-27 15:21 -0700 dhermes Fixed batch function issues in content api for shopping client. 1128 900e9a0c3b3a 2012-03-03 18:08 -0800 ccherubino Updated GetFeed methods in the Email Settings API to return typed feeds 1127 c21438385488 2012-02-29 09:18 -0800 dhermes Namespace for channel element (in contentforshopping) was promoted from scp to sc. 1126 5e7914da3fc5 2012-02-13 10:29 -0500 afshar Fixed release date year. 1125 78f1044f8338 2012-02-09 16:48 -0800 ccherubino Fixing bug in recent change to the Email Settings API client library (rev. 194d0fd21fdd) + some typos 1123 194d0fd21fdd 2012-02-09 11:29 -0800 ccherubino Updating the Email Settings API to return instances of the correct classes when retrieving settings 1122:1120 5d90af058646 2012-02-09 17:14 +0530 shraddhag Added a sample to create a group and update its settings using the Groups Provisioning and Groups Settings APIs. 1121 632df99f18bb 2012-02-09 14:30 -0500 afshar Added docs:description element to docs resources. 1120 b916755be737 2012-02-02 09:49 -0500 afshar Fixed error in docs sample. 1119 19b1a2901d7e 2012-02-01 12:20 +0530 gunjansharma Added scopes for all Admin APIs in apps tag 1118 0e47dfadd4f6 2012-02-01 12:18 +0530 gunjansharma Added a sample for Email Audit API demonstrating CRUD operations on email monitors 1117 ce66eae4ca6f 2012-01-31 17:02 -0800 dhermes Fixed small doc errors, made tax a list element 1116 9676d3b1059b 2012-01-30 08:57 -0800 alainv Fix issues 590/591: Contact's gender XML element was wrong. 1115 32d0f6e650f5 2012-01-30 15:54 +0530 gunjansharma Added a sample marketplace application 1114 3a91fa3c66d1 2012-01-30 14:50 +0530 shraddhag Sample to delete obsolete suspended users 1113 1b7323bc42e7 2012-01-30 14:49 +0530 shraddhag Sample to retrieve user's profile and contacts using 2LO 1112 3265462309de 2012-01-25 22:42 -0500 vicfryzel Fixing issue 587. Wrong URL used for batch ACL changes. 1111 15e17565f0ed 2012-01-24 12:04 +0530 shraddhag Added a sample to list all the members of a group 1108:1106 d762873e72e8 2012-01-06 14:35 -0800 alainv Adding access_type=offline to OAuth 2.0 authorize URL. 1107 9941fe8ba765 2012-01-20 19:30 +0000 afshar Check document list entries have a content before trying to download them. 1106 090c7ee66c93 2012-01-05 20:43 +0000 afshar Added tag 2.0.16 for changeset c67f3c6398ba 1105[2.0.16] c67f3c6398ba 2012-01-05 20:39 +0000 afshar Updated hgignore 1104 e3c66713a107 2012-01-05 20:34 +0000 afshar Updated MANIFEST.in 1103 20c9ab58878a 2012-01-05 20:24 +0000 afshar Use a generated MANIFEST from MANIFEST.in 1102 b395b9e31ee9 2012-01-05 20:15 +0000 afshar Updated release notes. 1101 012305c4dac1 2012-01-05 19:43 +0000 afshar Fixed recording test, sometimes outside the US, google.com tries to redirect. 1100 e93eb1a3fe7c 2012-01-05 19:08 +0000 afshar Updated pydocs 1099 85cc6c57a719 2012-01-05 19:08 +0000 afshar Bumped version string 1098 afd40075d6bd 2012-01-05 18:42 +0000 afshar Fixed doc generation script for my environment. 1096 c6b6a36a98be 2012-01-16 22:21 +0530 shraddhag Added a sample to search users with a given pattern and and move them to an organization === 2.0.16 === 5 Jan 2012 - Add sample for Docs List API to delete ACL. - Added support for label deletion in the Email Settings API client library - Added a sample for Email Settings create label and filters with exponential back-off - Added sample for enabling pop settings using Email Settings API - Fixed typo in analytics sample. - Fixed ACL batching URI in Docs. - Updated RetrieveAllUsers and RetrieveAllAliases to return all pages instead of one. - Removing force=True from some ACL requests in DocsClient - Added RetrieveGroups method and updated RetrieveAllGroups and RetrieveAllMembers method - Added a quick start sample for user provisioning - Added a client for organization unit provisioning - Added client architecture for group provisioning - Added a new sample for multidonain provisioning. Demonstrates all the functions available. - Added test for RetrieveAllUserAliases and changed OAuth scopes for Apps - Added client architecture for provisioning api and tests for client - Added support for batch requests to update spreadsheet cells. - Added GetAcl alias for get_acl in DocsClient - Added new ACL and get by ID methods to DocsClient. - Added some conditional imports for tlslite. - Updates to category handling for Documents list API - Added a function to retrieve all the aliases for a given user email. - Fixed #553 === 2.0.15 === 18 Oct 2011 - New Documents List API client - Added support for MDM Provisioning API - Added Batch support for Documents List API ACL changes - Added category handling for Documents List API client - Added commenter role to Documents List API client - Added support for gContact:status - Added subdomain support for Email Settings API - Added support for Apparel attributes in Content API for Shopping client - Added Documents List sample - Added Sample for Provisioning API with OAuth2 - Added Sample for Email Settings API with OAuth2 - Added Sample for Provisioning API with Sites and Profiles API - Fixed Youtube client to use SSL - Fixed Sites API sample to use SSL - Removed Google Base Data API support - Issues closed: #190, #206, #215, #230, #268, #270, #279, #281, #288, #296, #303, #312, #346, #363, #399, #424, #435, #483, #492, #494, #498, #508, #512, #516, #526, #532, #533, #534, #541, #547 === 2.0.14 === 7 Mar 2011 - Bug Fixes: * 491. 302 redirect on some gsessionids * 501. Remove support for deprecated Maps Data API. * 462. Bug in samples/analytics/data_feed_demo.py (using segments) * 482. String exception in service.py * 414. http proxy url containing properly formatted user-name and password not accepted - Turn on SSL for Codesearch, Photos, Project Hosting, Analytics, Contacts, Marketplace Licensing, and Docs. - Add batch default URL to the Contacts API and clean the sample code. (issue4000058) - Added support for Content API for Shopping - Added Calendar V2 API support - Add Google Marketplace Licensing API to the Gdata Python Client - Added patch from Alexandre Vivien that fixes calendar resource email support that somehow went missing in a previous release. === 2.0.13 === 16 Nov 2010 (revision 902:73f3fbb5ea88) - Bug Fixes: * Issue 315: Correctly constructs queries based on categories for Blogger APIs. * Issue 323: YouTubeUserQuery constructor passes incorrect overridden parameter to base class. - Accept proxy username and password in http(s)_proxy environment variables. - Adding support for specifying URI to v3 docs client. - Adding tests for revisions for arbitrary file types in Documents List API. - Google Base, added bucket node to attributes. - Adding ContainsSampledData XML Element to Analytics Data Feed and updating samples === 2.0.12 === 17 Sep 2010 (revision 892:e949a7cf9a31) - Bug Fixes: * Issue 437: PATCH request generates incorrect Content-Type * Issue 428: get_blog_name() does not correctly handle blog URLs containing hyphens * Issue 443: Packages should contain gdata.apps.audit in setup.py * Issue 430: Lots of memory usage when uploading big video files * Issue 423: gdata.contacts.client.ContactsClient.get_profiles_feed() broken - Turn on SSL be default for the spreadsheet API - Added support for new Analytics feeds - All V3 code and development has been out of this project and into http://code.google.com/p/google-api-python-client/ - Added partial support for the YouTube API in v2 === 2.0.11 === Jul 29, 2010 (revision fa49231cc8) - Added the following new APIs: * Apps Audit APIs from Jay Lee * Apps Organization from Alexandre Vivien * Added Email Settings API client library v2.0 from Claudio Cherubino - Fixes to file permissions for samples. - Fixing a broken calendar resource client from r983, removing a superfluous call to MakeResourceFeedUri(), adding regression tests for edit links. - Abstracting out AppsProperty so that it's usable by other libs. Thanks to Claudio Cherubino for this change. - Added optional change_password parameter to AppsService.CreateUser - Fixing minor bugs in analytics samples - http://codereview.appspot.com/1677050 Switch default connection type back to non-SSL connections. - Switch the repository from subversion to mercurial - Added OAuth test cases thanks Samuel Cyprian - Many APIs now use SSL, and some require it, so the following APIs now default to using HTTPS: * Spreadsheets * Sites * Calendar Resource === 2.0.10 === May 12, 2010 (revision 980) - Add check for liveness of proxy in live tests. Also add in upload-diffs.py to make using codereview.appspot.com easier. - Patch from Alexandre Vivien adds the ResourceEmail attribute to CalendarResourceEntry - Adding new single page methods to apps.groups.service. Thanks Jeremy Selier! - Cleanup in service GetWithRetries. Thanks Will Charles! - Adding much more test coverage for calendar_resource.client. - Fixing Calendar Resource get_resource to use correct feed URI generator. Thanks to Alexandre Vivien for reporting. - Adds support for Blogger Pages. Thanks Wiktor Gworek! === 2.0.9 === March 5, 2010 (revision 964) - Fixed URL related bug in gdata.calendar.service. You can now edit and delete against https URLs. - Removed hard coded Python version from gdata.contacts. Resolves issue 347. === 2.0.8 === February 26, 2010 (revision 960) - Added support for resumable uploads with a sample that shows it's use with Google Docs. Thanks Eric! - Added the publish element and copy() method to the DocsClient. Thanks Eric! - Added support for the JSON-C data format recently released for the YouTube API. - Fixed import bug when using cryptomath with Python2.4 and lower. - Modified gdata.gauth to be usable under Python2.3. - Fixed errors which affect RetrieveAllEmailLists RetrieveEmailLists RetrieveAllRecipients RetrieveAllNicknames RetrieveNicknames RetrieveAllUsers in gdata.apps.service. Resolves issue 340 . Thanks Aprotim! - Fixed issues with contacts client returning a raw get and not a ContactEntry and a few auth_tokens are not being passed into the HTTP request. Resolves issue 332 . Thanks vinces! - Fixed issue parsing worksheet feed, entry was the wrong type, corrected to WorksheetEntry. Resolves issue 343. Thanks Igor! - Fixed typo in gdata.spreadsheets.client (auth_service). Resolves issue 342. Thanks to Igor once again! - Fixed bug in Google Apps APIs UpdateSSOSettings. - The gdata.data.LinkFinder now looks in both the link and feed_link members for an ACL link. Resolves issue 339. - Removed client side check on video type in YouTube service. Rely on the server to provide an error if the format is not supported. Resolves issue 338. - Improved documentation for SpreadsheetsService's UpdateRow method to explain the use case it addresses and recomment alternatives. - Changed projecthosting to use the host member with relative URLs. - Added a get_headers function to be used in Python2.2 and 2.3 since httplib.HTTPResponse does not have getheaders in those versions of Python. Resolves issue 335. - Added a client smoke test to check version and auth related class members which all gdata.x.client's should have. - Updated the docs service_test.py to run as part of run_all_tests.py. === 2.0.7 === January 26, 2010 (revision 937) - Added gdata.calendar_resource.client to support the management of calendar resources (like conference rooms) for Google Apps domains. Thanks Vic! - Updated the sites API client to use version 1.1 of the sites API. - Added support for 2 Legged OAuth to the GDClient and included a sample app. Thanks Eric! - Added a revoke_token method to GDClient to invalidate AuthSub or OAuth tokens. - Added a get_record method to the Spreadsheets client to fetch a single record entry. - Added includeSuspendedUsers parameter to the apps groups API. Thanks Will Charles! - Fixed a client login bug present in the Maps API client. Thanks Roman! - The client_id property in the YouTube service object is now optional. - Added optional 'feed' parameter to YouTube service's Query classes. Resolves issue 323. - Updated tlslite's use of sha1 to use hashlib if using python2.5 or higher. Resolves issue 290 . Thanks Jay Lee! - Added a deadline parameter to gdata.alt.appengine to allow the urlfetch timeout to be modified to as high as 10 seconds. - Added GetGeneratorFromLinkFinder to GDataService which simplifies pagination across the entries in a feed. Resolves issue 325 . Thanks Aprotim! - Removed the default value for the test domain option. - Moved auth scopes to gdata.gauth from gdata.service. === 2.0.6 === December 17, 2009 (revision 914) - updated the Google Analytics Data Export API to v2. Thanks Nick! - added support for batch operations on contacts and profiles. Thanks Julian! - added a DocsQuery class for the Documents List v3 API. - removed now obsolete ACL classes from gdata.docs.data in favor of using classes in gdata.acl.data. Thanks Eric! - fixed bug in querying group membership by email. Thanks Will Charles! - removed import which are not present in Python2.2 from gdata.service to maintain support for Python2.2. - added additional debug information to MockHttpClient. === 2.0.5 === November 24, 2009 (revision 900) - added new gdata.contacts.client (and data) which uses version 3.0 of the contacts API. Thanks Vince! - added new gdata.docs.client which uses version 3.0 of the Documents List API. Thanks Eric! - added new v2 modules for the Analytics API (gdata.analytics.client). Thanks Nick! - added conditional get using etags on get_entry. Thanks Eric! - fixed bug in how Calendar redirect headers are handled when using this library in an App Engine app. - added new Calendar XML elements to the gdata.calendar module. Thanks Michael Ballbach! - added v2 data model classes for finance, notebook, webmastertools, ACLs, opensearch and youtube. - fixed issues with contacts.service when editing profiles. Thanks Julian! - fixed bug in GDClient's get_next method. - improved usability in Python2.3, though tests do not run the src modules should be Python2.3 compatible. === 2.0.4 === October 15, 2009 (revision 864) - Added support for the project hosting issue tracker API. Thanks Joe LaPenna! - Added a get-with-retries method to the v1 service for use with the Google Apps API to automatically retry. Thanks Takashi Matsuo! - Revised Google Sites client to use text input when creating a page. Thanks Eric! - Changed demo calendar in the Google Calendar API example. Thanks Trevor! === 2.0.3 === October 9, 2009 (revision 856) - Added support for Google Sites Data API. Thanks Eric! - Added support for Google Contacts profiles. Thanks Julian Toledo and Pedro Morais! - Added support for the Spreadsheets v3 API Tables feed and Records feed. - Added support for the Google Apps Admin Settings API. Thanks Jay Lee! - New sample which illustrates use of the Blogger v1 API on App Engine using OAuth. Thanks Wiktor Gworek! - Added a new MediaSource for use with v2 data model classes. Thanks Eric! - The v2 client classes can now force all requests to be made over SSL. Thanks Eric! - Revised the test config system to allow test settings to be specified as command line arguments or prompt the user to enter them interactively. - Fixed UTF encoding issue in unittest on big endian architectures. - Fixed XML parsing bug in Spreadsheets data class. Thanks ppr.vitaly! - Fixed bug in webmaster tools service verification method arguments. Thanks eyepulp! - Fixed download URL recognition in Document List Data API. - Some progress made in re-adding support for Python2.3, not sure yet if full Python2.3 support is completely feasible. Python 2.4-2.6 should work. === 2.0.2 === August 20, 2009 (revision 823) - Added support for the Google Maps Data API. Thanks Roman! - Added data model classes for the v3 Spreadsheets API. This API is not yet fully supported by the library and the gdata.spreadsheets package should be considered experimental until the next release. The v1 gdata.spreadsheet package is unchanged and should still work. - Auth token lookups when running in App Engine are now memcached which should improve efficiency. Thanks Marc! - Fixed a bug with the v2 HTTP proxy client. If behind a proxy, the full URL is now sent as the request selector. Thanks Dody! - Removed the gdata.client.GDataClient class which had been deprecated. === 2.0.1 === July 23, 2009 (revision 805) - Added support for Secure AuthSub, OAuth with HMAC and with RSA to the v2 auth code. - Added v2 data model classes for all XML elements in the gd namespace. - Usability improvements in the gdata.docs.service module, thanks Eric! - Made the dependency on elementtree only required if you are using Python 2.4 and lower. Thanks brosner! - Setting the ssl member in atom.service.AtomService will now override a URL which starts with http:. Many thanks to Michael Ballbach for the patch. - Added a proxy HTTP client which will use proxy environment variables to send all requests through the desired proxy server if present. - Added user agent string indicating that this is from the gdata python v2 library. - Removed unneeded title parameter from the Blogger client's add_comment method. - Added an all_tests_local script which runs all tests except those which would make HTTP requests to remote servers. - Added the birthday element to contacts entry, the reminder element to calendar events, and the method attribute to calendar's reminder class. All with great thanks to Marc! - The analytics GetData method no longer requires the dimensions argument. Thanks Jim! === 2.0.0 === June 29, 2009 (revision 777) - Released support for version 2 of the Google Data APIs protocol. To use the new version-aware code, use the gdata.client, atom.data, gdata.data modules and classes that derive from them. For example, use gdata.blogger.client.BloggerClient instead of gdata.blogger.service.BloggerService. High level, service specific classes are not available yet for other services, so feel free to migrate as they become available. The v1 service classes are still included so your existing code should be unaffected. - Included new Blogger samples to illustrate v2 support, a command line demo and an app for App Engine. - Added OAuth sample app which uses App Engine. - Added the gCal:sequence element to CalendarEventEntry (thanks Anton). - Added two decorators to diaply warnings when deprecated methods are used. - Added a login utility for command line samples to share to standardize the auth process on in a sample program. - Fixed a bug in v2 XML parsing which prevented multiversion XML classes from correctly parsing and generating v2 code. - Fixed v1 HTTP Host header settings, not default ports should now be included in the Host header. - Fixed pickle error when an OAuth-RSA token is saved to the App Engine datastore. - Fixed missing data in the Document List API unit tests. - Improved backwards compatibility of v2 data model classes be adding aliases to v1 functions. === 1.3.3 === June 5, 2009 (revision 724) - Added support for the Google Finance API. Thanks Swee Heng! - The Google Data Service classes now support version 1.0a of the OAuth protocol (Thanks Eric!). - Fixed a naming bug in unit tests for atom.service which appeared when using Python 2.6.x - Fixed URL parameter propogation when calling GDataService.Delete. The URL parameters are now preserved. - Fixed incorrect return type for gdata.FeedLinkFromString. - Added data model classes for the Atom and AtomPub XML elements which support version 2 of the Google Data APIs (by means of dynamic AtomPub namespace switching). The version 2 XML classes are much more backwards compatible with v1 classes than in previous releases. === 1.3.2 === May 22, 2009 (revision 711) - Added support for the Google Analytics API. Thanks Sal Uryasev! - Added support for the Google Book Search API. Thanks James Sams! - Improved support for 2 legged OAuth and added a sample app. Thanks Eric Bidelman! - Simplified the way an XML response from the server is parsed into the desired class for the version 2.0 API client (gdata.client.GDClient). === 1.3.1 === April 23, 2009 (revision 695) - Fixed issues with setting the developer key in the YouTubeService constructor. - For the Document List API (thanks Eric!): - Added writersCanInvite element - Fixed a small errors in Document Entry - Added category label when creating different types of docs - Fixed DownLoad helpers which shouldn't write a file if server returns error. Fixes issue 240. - Added DocumentListEntryFromString converter and using DocumentListEntry class instead of GDataEntry class to create entries. - URLs in HTTP requests can now be unicode strings. Resolves issue 233. - Improvements for the upcoming version 2.0.0 release: - The v2 auth code now support AuthSub and has been tested on App Engine. - Unit tests for the v2 client code will default to not use the local file cache but will make live requests unless cached responses is set to True in the test configuration module. - Older v1 unit tests can now use the v2 testing framework, migrated contacts API tests as a proof of concept. This will come in handy when testing backwards compatibility for the 2.0 release. - Improved support for unicode and other character encodings in the v2 XML core module. === 1.3.0 === Mar 20, 2009 (revision 665) - Added support for the Google Health API. (Thanks Eric Bidelman!) - Added support for the groups management in the Google Apps Provisioning API. (Thanks to Tony Chen and Oskar Casquero!) - Added the following new features for the Google Documents List API: (Thanks again to Eric Bidelman for adding all of these!) - Folder create, move docs/folders in and out of other folders. (Thanks Nizam Sayeed!) - Suppport for modifications to Access Control Lists for documents. - Export functionality for documents, spreadsheets, and presentations. - Updated the docs_example.py sample app. - New new XML elements: resourceID, lastViewed, and lastModifiedBy. - Added ability to update web clip settings in the Google Apps email settigns API. (Thanks Takashi Matsuo.) - Fixed a bug in calendarExample that would cause execution to fail when an event attendee does not have any attendeeStatus data. (Thanks Trevor Johns!) - Fixed AuthSub request URLs which should be https. (Yay Eric!) - Fixed logic when changing email settings when using the Google Apps API to only send properties which have been specified. (Thanks Jay Lee!) - Includes a new and experimental client class (gdata.client.GDClient) which can be used with version two of the Google Data API protocol. This class may change in backwards incompatible ways before the 2.0.0 release, so use at your own risk. === 1.2.4 === Jan 22, 2009 (revision 603) - Added a new AtomPubClient class (and supporting classes) which begins a foundation on which support for version two of the Google Data protocol will be built. - OAuth methods can now specify the desired OAuth server with the default being the Google Accounts end point (thanks Dag Brattli!). - Improved support for unicode strings in XML element class attributes and text nodes (thanks again to Dag). - Fixed constructors for Service classes which inherit from GDataService to ensure that all parameters are passed up to the superclass constructor (thanks Guillaume Ryder!). - Added a 'contact_list' property to ContactsService to simplify API usage for shared contacts (thanks Guillaume once again). - For Google Contacts, added a GetFeedUri method to help users generating feed URIs (Guillaume for a hat-trick). - New unit tests to ensure that the ordering of entry objects within a feed is preserved when converting to and from XML. === 1.2.3 === Dec 3, 2008 (revision 585) - Added support for OAuth (thanks to Kunal Shah!). Your client can now obtain an authorization token using the OAuth protocol. - Added support for Secure AuthSub (thanks Eric Bidelman!). Your client can digitally sign requests using RSA allowing Google service to verify that the request came from your application. - Added a new module for parsing XML which will be used in future versions to support version of the Google Data APIs protocol. This new library handles versioning of XML schemas. - The Google Contacts API sample now pages through results. - Added phone number rel types using in the Google Contacts API. - The YouTube service module will use cElementTree if it is available. Improves XML parsing speed. - Fixed typo in gdata.geo, changed longtitude to longitude but kept an alias for backwards compatibility. - Fixed Blogger's GetBlogId regular expressions to extract the ID from multiple kinds of entries. - Fixed type check in atom.http to allow unicode URL strings. - Added webmastertools test to the packaged download which fixed failures when running all data tests. - Improved compatibility of unit tests with Python2.3. - Added copies of tlslite and dependencies to support secure AuthSub and OAuth. - Changed the default host for Google Apps API requests to apps-apis.google.com. === 1.2.2 === Oct 15, 2008 (revision 556) - Added support for the following APIs: Google Apps Email Migration API Google Apps Email Settings API Google Webmaster Tools Data API Some modules for the above are not yet fully tested, so please file an issue if you notice something is not working as expected. - Restored support for gdata.http_request_handler when using App Engine to preserve backwards compatibility. - Simplified auth token management by adding a current_token member to service classes. Also added settings to control when the token_store is updated when using SetXToken() methods. The token_store will only be queried if there is no current_token. - Fixed issue with requests to HTTPS URLs in which an AuthSub token was seen as invalid because the request contained the default port number (443). The library no longer includes the port in the Host header if it is using the default. - Resolved issues with YouTube token scopes. - Fixed issue which appeared when the Calendar API issues a redirect to a PUT request. The library now correctly retries with a PUT (instead of a POST). - Added workaround for differences in how the App Engine SDK handles redirects. - Fixed typo in gdata.EntryLink declaration. - Fixed invalid host errors seen when using some HTTP proxies. === 1.2.1 === Sep 15, 2008 (revision 529) - The gdata.alt.appengine module now replaces a Service object's token_store with an AppEngineTokenStore which automatically associates auth tokens with the current user and stores the auth tokens in the datastore. - Streamlined the gdata.auth module. There are now six recommended functions for using Google auth mechanisms. GDataService now uses these six functions. - Added an override_token in Service objects (AtomService, GDataService) which bypasses the token_store. This member is helpful when using a single use AuthSub token which is only valid for one request and so would not be reused. - gdata.alt.appengin.run_on_appengine will now return the modified Service object. This allows statements like: client = gdata.alt.appengin.run_on_appengine( gdata.blogger.service.BloggerService()) - Fixed content length header issue in gdata.alt.appengine which caused errors when running on App Engine when HTTP headers are set to non-string values. - Fixed function naming issue in gdata.alt.appengine, a "private" function was not accessible in some import styles. - Fixed issue in atom.http which surfaces when passing in a string for the port number. Ports are now cast to ints. - Updated pydocs. === 1.2.0 === Sep 9, 2008 (revision 522) - Refactored HTTP request mechanisms and the way that Authorization headers are created and applied to requests. These changes make it easier to swap out the HTTP layer to allow the library to be used in different environments (like Google App Engine). The changes in Auth token logic should make it much easier to add support for secure AuthSub and OAuth. For more details on this change, see the following wiki page: http://code.google.com/p/gdata-python-client/wiki/HTTPRequestRefactoring - Fixed issues with token string modification which caused certain AuthSub token values to become invalid. - Created a new module for parsing and constructing URLs (atom.url). - Created a module for token storage which will lookup the correct auth token to be used for a particular URL. - Auth tokens are now represented as objects and contain information about the scopes (URLs) for which they can be used. The token object is responsible for adding the Authorization header to the request. - Added new functions to gdata.auth for extracting the raw token string from a response to an AuthSub token upgrade request. - Added support for the location query parameter in YouTube queries. - Added groups functionality to the Contacts API library. - Batch request support added for Contacts API operations. - Added default behavior to text_db GetTables: when no worksheet name or ID is specified, all tables are retrieved. - Fixed client login for YouTube. - Fixed issue in YouTube service when setting the developer key. - Fixed bug in YouTube service which raised an exception when inserting a video using a file-like object. - Added a method to Feed and Entry classes to find the "previous" link. - A failure when attempting to upgrade a single use AuthSub token to a session token will now raise an exception. - AppsForYourDomainException now uses Python2.5 style exception construction. - Moved the logic for using this library on Google App Engine into a new module: gdata.alt.appengine. === 1.1.1 === June 13, 2008 (revision 421) - Added support for new feeds available in the Google Contacts API. Adding contact groups, querying for contacts in a group, specifying extended properties, and setting the contact's photo are now supported. - The ExtendedProperty class can now contain a blob of XML instead of just an attribute value. This is used in the Google Contacts Data API. - The YouTube service now has methods for updating playlists and removing a video from a playlist. - Added geo-location to the YouTube service class. - When using ClientLogin with Blogger, the default account type is now set to 'GOOGLE'. This prevents the rare problem of a user having a Google Apps account which conflicts with his or her Google Account used for Blogger. - Added support for the in-reply-to element which appears in Blogger comments. === 1.1.0 === June 6, 2008 (revision 403) - Added modules for YouTube. - Added modules for Blogger to simplify using the Blogger API. - Updated pydocs for all modules. - New member in service object to allow the ClientLogin server to be changed. Required to be able to use ClientLogin with YouTube. - Iterating over Google Apps Feeds can now be accomplished using a generator. - New unit tests for the Google Apps Provisioning API. - Bug fixes to the mock_service module used in unit tests. - Fixed a bug in Query string generation when using category queries. - Improved ease of use for Calendar query construction, URL components are now escaped automatically by default. - Bug fix, added timezone element to CalendarEventFeed. - Improved docstrings to describe token access methods (specifically GetAuthSubToken). - Moved ExtendedProperty class into gdata module from gdata.calendar since it is now also used in the Google Contacts Data API. === 1.0.13 === May 8, 2008 (revision 357) - Added sample code illustrating the use of the Contacts API modules. - Added a mock_service module which will allow for faster test execution by simulating a server and replaying recordings instead of making HTTP requests to a remote server. - Fixed a but in the gdata.auth module which could cause certain characters at the start of the token to be removed incorrectly. - Fixed a bug in the gdata.apps.service module which caused an import to fail when loading ElementTree in some cases. - Service unit tests should run correctly when using run_service_tests and run_all_tests now that the subtests have been made into packages. === 1.0.12.1 === April 10, 2008 (revision 341) - Added new import statements so that xml.etree.ElementTree can be used. Needed for the library to run on Google App Engine. === 1.0.12 === April 8, 2008 (revision 334) - Added support for the Google Contacts API. - Added a gdata.urlfetch module which allows this library to be used in Google App Engine. This module uses urlfetch instead of httplib to perform HTTP requests. - Refactored atom.service and gdata.service to make it easier to swap out the HTTP communication layer. - Fixed a bug in the Push method of gdata.spreadsheet.text_db. - Sped up InsertRow method in gdata.spreadsheet.service. - Improved XML parsing efficiency in gdata.calendar.service. - Added new attribuges for the Google Apps login element. - Improved UpdatePhotoBlob in gdata.photo.service. - Added pydocs for Google Contacts API and updates in other modules. === 1.0.11.1 === March 13, 2008 (revision 314) - Added text_db module to gdata.spreadsheet which makes using the Spreadsheets API feel more like using a simple database. This module streamlines use of the list feed. - Compatibility fixes for geo and photos modules to run on Python 2.2 and 2.3. - Added the orderby parameter to gdata.service's Query class. - Changed the blogger sample to use orderby=updated so that queries on updated min or max will work. - Fix to string encoding in the atom core when converting text members to XML. === 1.0.11 === February 11, 2008 (revision 290) - Modified the gdata.service.GDataService to include a handler member to which all HTTP requests are delegated. This will allow the underlying HTTP client to be swapped out and pave the way for use of a mock server in unit tests. - Fixed character encoding problems when parsing non ASCII XML. - Added pydocs for gdata's geo, exif, and media packages. === 1.0.10.1 === December 20, 2007 (revision 265) - Photos packages for working with the Picasa Web Albums Data API. - New modules for handling media, geo, and exif XML elements. - Packages for the Google Code Seach API. - New PyDoc HTML pages generated from the source code. - Extracted authentication code into a seperate module which can be used in other applications. - The core XML parser should now fully support UTF-8 strings, there are also options to change the default encoding. - Bugfixes in Atom service's proxy support when authenticating with the proxy. - Spreadsheets UpdateCell can now take integers for the row and column parameters. - Added INSTALL.txt to explain the installation process. The content was copied from the getting started article here: http://code.google.com/support/bin/answer.py?answer=75582 - You can now create update and delete worksheets within a Google Spreadsheet. - Added convenience methods to service object to ease the process of iterating through a feed. - For Document List queries, added the ability to construct schema-qualified named folder queries. === 1.0.9 === October 15, 2007 (revision 213) - Added support for batch requests for the Google Spreadsheets cells feed, Google Calendar's events feed, and Google Base's items feed. - Authentication requests can now be sent to a different server by modifying a module variable in gdata.service. - Fixed the birthdaySample mashup. - User Agent headers now fit the pattern used in other Google data client libraries. - Made it easier to access custom elements for Google Base items. === 1.0.8 === August 31, 2007 (revision 192) - Major refactoring of the data model classes. Improved XML parsing performance. - Created a script to run all of the tests, one to run the data model tests, and one to run all service tests. - Changes to MediaSource to handle uploading files on Windows. - All of the sample code should now work in Python 2.2 and higher. I removed 2.4 dependancies. - Minor change to the Blogger sample; it now uses entry.GetSelfLink(). - Added fix to prevent socket.sslerror and redirect failures when using a proxy. - Google Calendar event entries can now contain a reference to the original event. The original event element is present when describing a recurrance exception. - Added timezone parameter to Google Calendar queries. This allows the client to request that event times be expressed in a specific timezone. === 1.0.7 === August 7, 2007 - Added new test for the Documents List data API's service module. - Updated setup.py to include the docs package in the installation. === 1.0.6 === Aug 2, 2007 - Support for Documents List API added along with sample code. === 1.0.5 === July 31, 2007 - XML parsing can now use cElementTree if it is available, this should speed up XML parsing. - Redirects now include all params in the original HTTP request. - Added support for WebContent and QuickAdd features of Google Calendar data API. - Proxy support added. - Binary MIME uploads now work for Windows files when performing media uploads. - New calendar functionality for add/edit/delete calendar and add/edit/delete subscriptions. Also, added new functionality for Calendar Gadgets. === 1.0.4 === June 23, 2007 - Added data and service modules for Google Apps. - XML parsing can now be configured in gdata module service calls to avoid multiparsing to convert to the desired type. - Fixed UTF-8 support in the Atom modules. - The spreadsheets example now works with UTF-8 column names. - Fixed a type in the Google Base dry-run sample. - Calendar's Who element no longer requires a name when converting to XML. === 1.0.3 === May 30, 2007 - Added a mashup sample using the spreadsheets and calendar services. - Reduced the number of unnecessary namespace declarations in the XML produced by objects. - Changed type of custom elements in a spreadsheets list entry. - Fixed bugs in spreadsheetsExample.py. - Spreadsheet entries now inherit from GDataEntry and have gdata specific convenience methods. === 1.0.2 === May 4, 2007 - Added support for media. Services can now upload images, the tests for this service use the Picasa Web Albums data API. - Added example code illustrating Blogger operations. - Fixed host name processing for URLs which have -'s in the host name. - Added a debug setting for service classes. - Added user agent header. - Made finding links of a specific type easier when dealing with Atom elements. Atom Entries now have GetXLink methods. - Simplified finding license links in Atom Entries. === 1.0.1 === April 20, 2007 - Rearranged package structure, tests are in their own directory and Google data API extension classes are nested under the gdata package. - Simplified accessing extension elements by adding FindExtesions. - Provided a setup.py file for distutils based installation. - Added support for the app:draft element. - Imports ElementTree from XML libraries in Python 2.5 - Fixed _EntryLinkFromElementTree in the calendar data module. - Fixed various _TakeChildFromElementTree methods in calendar data module. - Fixed delete and update operations in the spreadsheetExample script. - Fixed setting sort order and order by in calendar's service module. - Added HTTP BasicAuth for AtomService. === 1.0 === March 26, 2007 Initial release gdata-2.0.18/samples/0000750036466300116100000000000012156625015014376 5ustar afshareng00000000000000gdata-2.0.18/samples/spreadsheets/0000750036466300116100000000000012156625015017070 5ustar afshareng00000000000000gdata-2.0.18/samples/spreadsheets/spreadsheetExample.py0000750036466300116100000001444112156622362023276 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright (C) 2007 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. __author__ = 'api.laurabeth@gmail.com (Laura Beth Lincoln)' try: from xml.etree import ElementTree except ImportError: from elementtree import ElementTree import gdata.spreadsheet.service import gdata.service import atom.service import gdata.spreadsheet import atom import getopt import sys import string class SimpleCRUD: def __init__(self, email, password): self.gd_client = gdata.spreadsheet.service.SpreadsheetsService() self.gd_client.email = email self.gd_client.password = password self.gd_client.source = 'Spreadsheets GData Sample' self.gd_client.ProgrammaticLogin() self.curr_key = '' self.curr_wksht_id = '' self.list_feed = None def _PromptForSpreadsheet(self): # Get the list of spreadsheets feed = self.gd_client.GetSpreadsheetsFeed() self._PrintFeed(feed) input = raw_input('\nSelection: ') id_parts = feed.entry[string.atoi(input)].id.text.split('/') self.curr_key = id_parts[len(id_parts) - 1] def _PromptForWorksheet(self): # Get the list of worksheets feed = self.gd_client.GetWorksheetsFeed(self.curr_key) self._PrintFeed(feed) input = raw_input('\nSelection: ') id_parts = feed.entry[string.atoi(input)].id.text.split('/') self.curr_wksht_id = id_parts[len(id_parts) - 1] def _PromptForCellsAction(self): print ('dump\n' 'update {row} {col} {input_value}\n' '\n') input = raw_input('Command: ') command = input.split(' ', 1) if command[0] == 'dump': self._CellsGetAction() elif command[0] == 'update': parsed = command[1].split(' ', 2) if len(parsed) == 3: self._CellsUpdateAction(parsed[0], parsed[1], parsed[2]) else: self._CellsUpdateAction(parsed[0], parsed[1], '') else: self._InvalidCommandError(input) def _PromptForListAction(self): print ('dump\n' 'insert {row_data} (example: insert label=content)\n' 'update {row_index} {row_data}\n' 'delete {row_index}\n' 'Note: No uppercase letters in column names!\n' '\n') input = raw_input('Command: ') command = input.split(' ' , 1) if command[0] == 'dump': self._ListGetAction() elif command[0] == 'insert': self._ListInsertAction(command[1]) elif command[0] == 'update': parsed = command[1].split(' ', 1) self._ListUpdateAction(parsed[0], parsed[1]) elif command[0] == 'delete': self._ListDeleteAction(command[1]) else: self._InvalidCommandError(input) def _CellsGetAction(self): # Get the feed of cells feed = self.gd_client.GetCellsFeed(self.curr_key, self.curr_wksht_id) self._PrintFeed(feed) def _CellsUpdateAction(self, row, col, inputValue): entry = self.gd_client.UpdateCell(row=row, col=col, inputValue=inputValue, key=self.curr_key, wksht_id=self.curr_wksht_id) if isinstance(entry, gdata.spreadsheet.SpreadsheetsCell): print 'Updated!' def _ListGetAction(self): # Get the list feed self.list_feed = self.gd_client.GetListFeed(self.curr_key, self.curr_wksht_id) self._PrintFeed(self.list_feed) def _ListInsertAction(self, row_data): entry = self.gd_client.InsertRow(self._StringToDictionary(row_data), self.curr_key, self.curr_wksht_id) if isinstance(entry, gdata.spreadsheet.SpreadsheetsList): print 'Inserted!' def _ListUpdateAction(self, index, row_data): self.list_feed = self.gd_client.GetListFeed(self.curr_key, self.curr_wksht_id) entry = self.gd_client.UpdateRow( self.list_feed.entry[string.atoi(index)], self._StringToDictionary(row_data)) if isinstance(entry, gdata.spreadsheet.SpreadsheetsList): print 'Updated!' def _ListDeleteAction(self, index): self.list_feed = self.gd_client.GetListFeed(self.curr_key, self.curr_wksht_id) self.gd_client.DeleteRow(self.list_feed.entry[string.atoi(index)]) print 'Deleted!' def _StringToDictionary(self, row_data): dict = {} for param in row_data.split(): temp = param.split('=') dict[temp[0]] = temp[1] return dict def _PrintFeed(self, feed): for i, entry in enumerate(feed.entry): if isinstance(feed, gdata.spreadsheet.SpreadsheetsCellsFeed): print '%s %s\n' % (entry.title.text, entry.content.text) elif isinstance(feed, gdata.spreadsheet.SpreadsheetsListFeed): print '%s %s %s' % (i, entry.title.text, entry.content.text) # Print this row's value for each column (the custom dictionary is # built using the gsx: elements in the entry.) print 'Contents:' for key in entry.custom: print ' %s: %s' % (key, entry.custom[key].text) print '\n', else: print '%s %s\n' % (i, entry.title.text) def _InvalidCommandError(self, input): print 'Invalid input: %s\n' % (input) def Run(self): self._PromptForSpreadsheet() self._PromptForWorksheet() input = raw_input('cells or list? ') if input == 'cells': while True: self._PromptForCellsAction() elif input == 'list': while True: self._PromptForListAction() def main(): # parse command line options try: opts, args = getopt.getopt(sys.argv[1:], "", ["user=", "pw="]) except getopt.error, msg: print 'python spreadsheetExample.py --user [username] --pw [password] ' sys.exit(2) user = '' pw = '' key = '' # Process options for o, a in opts: if o == "--user": user = a elif o == "--pw": pw = a if user == '' or pw == '': print 'python spreadsheetExample.py --user [username] --pw [password] ' sys.exit(2) sample = SimpleCRUD(user, pw) sample.Run() if __name__ == '__main__': main() gdata-2.0.18/samples/blogger/0000750036466300116100000000000012156625015016017 5ustar afshareng00000000000000gdata-2.0.18/samples/blogger/oauth-appengine/0000750036466300116100000000000012156625015021103 5ustar afshareng00000000000000gdata-2.0.18/samples/blogger/oauth-appengine/app.yaml0000640036466300116100000000024712156622362022555 0ustar afshareng00000000000000application: blogger-oauth-example version: 1 runtime: python api_version: 1 handlers: - url: /css static_dir: css - url: /.* script: main.py login: required gdata-2.0.18/samples/blogger/oauth-appengine/index.html0000640036466300116100000000425612156622362023112 0ustar afshareng00000000000000 Blogger OAuth example {% if logged %}

Your blogs:

{% for blog in blogs %}
{{ blog.title }}
created: {{ blog.published }}
last updated: {{ blog.updated }}
Write a post
{% endfor %} {% else %} I could do automatic redirect but we all love welcome screens :).
Ok, allow this app to access Blogger. {% endif %} gdata-2.0.18/samples/blogger/oauth-appengine/index.yaml0000640036466300116100000000072712156622362023107 0ustar afshareng00000000000000indexes: # AUTOGENERATED # This index.yaml is automatically updated whenever the dev_appserver # detects that a new type of query is run. If you want to manage the # index.yaml file manually, remove the above marker line (the line # saying "# AUTOGENERATED"). If you want to manage some indexes # manually, move them above the marker line. The index.yaml file is # automatically uploaded to the admin console when you next deploy # your application using appcfg.py. gdata-2.0.18/samples/blogger/oauth-appengine/main.py0000750036466300116100000000334512156622362022413 0ustar afshareng00000000000000__author__ = 'wiktorgworek@google.com (Wiktor Gworek)' import wsgiref.handlers import atom import os import cgi import gdata.blogger.service from oauth import OAuthDanceHandler, OAuthHandler, requiresOAuth from google.appengine.ext import webapp from google.appengine.ext.webapp import template class MainHandler(OAuthHandler): """Main handler. If user is not logged in via OAuth it will display welcome page. In other case user's blogs on Blogger will be displayed.""" def get(self): try: template_values = {'logged': self.client.has_access_token()} if template_values['logged']: feed = self.client.blogger.GetBlogFeed() blogs = [] for entry in feed.entry: blogs.append({ 'id': entry.GetBlogId(), 'title': entry.title.text, 'link': entry.GetHtmlLink().href, 'published': entry.published.text, 'updated': entry.updated.text }) template_values['blogs'] = blogs except gdata.service.RequestError, error: template_values['logged'] = False path = os.path.join(os.path.dirname(__file__), 'index.html') self.response.out.write(template.render(path, template_values)) class NewPostHandler(OAuthHandler): """Handles AJAX POST request to create a new post on a blog.""" @requiresOAuth def post(self): entry = atom.Entry(content=atom.Content(text=self.request.get('body'))) self.client.blogger.AddPost(entry, blog_id=self.request.get('id')) def main(): application = webapp.WSGIApplication([ (r'/oauth/(.*)', OAuthDanceHandler), ('/new_post', NewPostHandler), ('/', MainHandler), ], debug=True) wsgiref.handlers.CGIHandler().run(application) if __name__ == '__main__': main() gdata-2.0.18/samples/blogger/oauth-appengine/css/0000750036466300116100000000000012156625015021673 5ustar afshareng00000000000000gdata-2.0.18/samples/blogger/oauth-appengine/css/index.css0000640036466300116100000000166412156622362023526 0ustar afshareng00000000000000* { font-family: "HelveticaNeue-Light", "Helvetica Neue Light", "Helvetica Neue", sans-serif; } body { padding-left:15px; padding-right:15px; } a { color:#6699ff; text-decoration:none; } a:hover { color:#666666; text-decoration:underline; } #header { color:#666666; padding-bottom:10px; margin-bottom:1em; border-bottom:1px solid #666666; height:25px; text-align: center; } .left { float: left; } .right { float: right; } div#status span.text { background-color: #FFEEAA; text-align: center; padding: 5px; font-size: 0.8em; font-weight: bold; -moz-border-radius: 6px; -webkit-border-radius: 6px; color: black; } div.blog { padding:10px 10px 10px 20px; border-bottom:1px solid #eee; float: left; width: 90%; } div.blog:hover { background-color:#fffccc; } div.blog span.date { font-size: 0.7em; } .hidden { display: none; } .post-editor-wrapper { text-align: right; } gdata-2.0.18/samples/blogger/oauth-appengine/oauth.py0000750036466300116100000001465512156622362022615 0ustar afshareng00000000000000"""Provides OAuth authorization. Main components are: * OAuthClient - provides logic for 3-legged OAuth protocol, * OAuthDanceHandler - wrapper for OAuthClient for handling OAuth requests, * OAuthHandler - from this handler should inherit all other handlers that want to be authenticated and have access to BloggerService. Be sure that you added @requiredOAuth on top of your request method (i.e. post, get). Request tokens are stored in OAuthRequestToken (explicite) and access tokens are stored in TokenCollection (implicit) provided by gdata.alt.appengine. Heavily used resources and ideas from: * http://github.com/tav/tweetapp, * Examples of OAuth from GData Python Client written by Eric Bidelman. """ __author__ = ('wiktorgworek (Wiktor Gworek), ' 'e.bidelman (Eric Bidelman)') import os import gdata.auth import gdata.client import gdata.alt.appengine import gdata.blogger.service from google.appengine.api import users from google.appengine.ext import db from google.appengine.ext import webapp from google.appengine.ext.webapp import template SETTINGS = { 'APP_NAME': 'YOUR_APPLICATION_NAME', 'CONSUMER_KEY': 'YOUR_CONSUMER_KEY', 'CONSUMER_SECRET': 'YOUR_CONSUMER_SECRET', 'SIG_METHOD': gdata.auth.OAuthSignatureMethod.HMAC_SHA1, 'SCOPES': gdata.service.CLIENT_LOGIN_SCOPES['blogger'] } # ------------------------------------------------------------------------------ # Data store models. # ------------------------------------------------------------------------------ class OAuthRequestToken(db.Model): """Stores OAuth request token.""" token_key = db.StringProperty(required=True) token_secret = db.StringProperty(required=True) created = db.DateTimeProperty(auto_now_add=True) # ------------------------------------------------------------------------------ # OAuth client. # ------------------------------------------------------------------------------ class OAuthClient(object): __public__ = ('request_token', 'callback', 'revoke_token') def __init__(self, handler): self.handler = handler self.blogger = gdata.blogger.service.BloggerService( source=SETTINGS['APP_NAME']) self.blogger.SetOAuthInputParameters(SETTINGS['SIG_METHOD'], SETTINGS['CONSUMER_KEY'], consumer_secret=SETTINGS['CONSUMER_SECRET']) gdata.alt.appengine.run_on_appengine(self.blogger) def has_access_token(self): """Checks if there is an access token in token store.""" access_token = self.blogger.token_store.find_token( '%20'.join(SETTINGS['SCOPES'])) return isinstance(access_token, gdata.auth.OAuthToken) def request_token(self): """Fetches a request token and redirects the user to the approval page.""" if users.get_current_user(): # 1.) REQUEST TOKEN STEP. Provide the data scope(s) and the page we'll # be redirected back to after the user grants access on the approval page. req_token = self.blogger.FetchOAuthRequestToken( scopes=SETTINGS['SCOPES'], oauth_callback=self.handler.request.uri.replace( 'request_token', 'callback')) # When using HMAC, persist the token secret in order to re-create an # OAuthToken object coming back from the approval page. db_token = OAuthRequestToken(token_key = req_token.key, token_secret=req_token.secret) db_token.put() # 2.) APPROVAL STEP. Redirect to user to Google's OAuth approval page. self.handler.redirect(self.blogger.GenerateOAuthAuthorizationURL()) def callback(self): """Invoked after we're redirected back from the approval page.""" oauth_token = gdata.auth.OAuthTokenFromUrl(self.handler.request.uri) if oauth_token: # Find request token saved by put() method. db_token = OAuthRequestToken.all().filter( 'token_key =', oauth_token.key).fetch(1)[0] oauth_token.secret = db_token.token_secret oauth_token.oauth_input_params = self.blogger.GetOAuthInputParameters() self.blogger.SetOAuthToken(oauth_token) # 3.) Exchange the authorized request token for an access token oauth_verifier = self.handler.request.get( 'oauth_verifier', default_value='') access_token = self.blogger.UpgradeToOAuthAccessToken( oauth_verifier=oauth_verifier) # Remember the access token in the current user's token store if access_token and users.get_current_user(): self.blogger.token_store.add_token(access_token) elif access_token: self.blogger.current_token = access_token self.blogger.SetOAuthToken(access_token) self.handler.redirect('/') def revoke_token(self): """Revokes the current user's OAuth access token.""" try: self.blogger.RevokeOAuthToken() except gdata.service.RevokingOAuthTokenFailed: pass except gdata.service.NonOAuthToken: pass self.blogger.token_store.remove_all_tokens() self.handler.redirect('/') # ------------------------------------------------------------------------------ # Request handlers. # ------------------------------------------------------------------------------ class OAuthDanceHandler(webapp.RequestHandler): """Handler for the 3 legged OAuth dance. This handler is responsible for fetching an initial OAuth request token, redirecting the user to the approval page. When the user grants access, they will be redirected back to this GET handler and their authorized request token will be exchanged for a long-lived access token.""" def __init__(self): super(OAuthDanceHandler, self).__init__() self.client = OAuthClient(self) def get(self, action=''): if action in self.client.__public__: self.response.out.write(getattr(self.client, action)()) else: self.response.out.write(self.client.request_token()) class OAuthHandler(webapp.RequestHandler): """All handlers requiring OAuth should inherit from this class.""" def __init__(self): super(OAuthHandler, self).__init__() self.client = OAuthClient(self) def requiresOAuth(fun): """Decorator for request handlers to gain authentication via OAuth. Must be used in a handler that inherits from OAuthHandler.""" def decorate(self, *args, **kwargs): if self.client.has_access_token(): try: fun(self, *args, **kwargs) except gdata.service.RequestError, error: if error.code in [401, 403]: self.redirect('/oauth/request_token') else: raise else: self.redirect('/oauth/request_token') return decorate gdata-2.0.18/samples/blogger/app/0000750036466300116100000000000012156625015016577 5ustar afshareng00000000000000gdata-2.0.18/samples/blogger/app/app.yaml0000640036466300116100000000030412156622362020243 0ustar afshareng00000000000000application: your-app-id-here version: 1 runtime: python api_version: 1 handlers: - url: / static_files: welcome.html upload: welcome.html - url: /.* login: required script: blogapp.py gdata-2.0.18/samples/blogger/app/welcome.html0000640036466300116100000000051012156622362021117 0ustar afshareng00000000000000 Simple Blogger API sample.

This simple example illustrates how to build an App Engine app which uses the Blogger API to post on a user's blog.

To begin, view the list of your available blogs.

gdata-2.0.18/samples/blogger/app/blogapp.py0000750036466300116100000001153712156622362020611 0ustar afshareng00000000000000# Copyright (C) 2009 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. __author__ = 'j.s@google.com (Jeff Scudder)' import os import wsgiref.handlers from google.appengine.api import users from google.appengine.ext import webapp from google.appengine.ext.webapp import template from google.appengine.ext.webapp.util import run_wsgi_app import gdata.gauth import gdata.data import gdata.blogger.client def get_auth_token(request): """Retrieves the AuthSub token for the current user. Will first check the request URL for a token request parameter indicating that the user has been sent to this page after authorizing the app. Auto-upgrades to a session token. If the token was not in the URL, which will usually be the case, looks for the token in the datastore. Returns: The token object if one was found for the current user. If there is no current user, it returns False, if there is a current user but no AuthSub token, it returns None. """ current_user = users.get_current_user() if current_user is None or current_user.user_id() is None: return False # Look for the token string in the current page's URL. token_string, token_scopes = gdata.gauth.auth_sub_string_from_url( request.url) if token_string is None: # Try to find a previously obtained session token. return gdata.gauth.ae_load('blogger' + current_user.user_id()) # If there was a new token in the current page's URL, convert it to # to a long lived session token and persist it to be used in future # requests. single_use_token = gdata.gauth.AuthSubToken(token_string, token_scopes) # Create a client to make the HTTP request to upgrade the single use token # to a long lived session token. client = gdata.client.GDClient() session_token = client.upgrade_token(single_use_token) gdata.gauth.ae_save(session_token, 'blogger' + current_user.user_id()) return session_token class ListBlogs(webapp.RequestHandler): """Requests the list of the user's blogs from the Blogger API.""" def get(self): template_values = { 'sign_out': users.create_logout_url('/') } # See if we have an auth token for this user. token = get_auth_token(self.request) if token is None: template_values['auth_url'] = gdata.gauth.generate_auth_sub_url( self.request.url, ['http://www.blogger.com/feeds/']) path = os.path.join(os.path.dirname(__file__), 'auth_required.html') self.response.out.write(template.render(path, template_values)) return elif token == False: self.response.out.write( 'You must sign in first' '' % users.create_login_url('/blogs')) return client = gdata.blogger.client.BloggerClient() feed = client.get_blogs(auth_token=token) template_values['feed'] = feed path = os.path.join(os.path.dirname(__file__), 'list_blogs.html') self.response.out.write(template.render(path, template_values)) class WritePost(webapp.RequestHandler): def get(self): template_values = { 'sign_out': users.create_logout_url('/'), 'blog_id': self.request.get('id') } # We should have an auth token for this user. token = get_auth_token(self.request) if not token: self.redirect('/blogs') return path = os.path.join(os.path.dirname(__file__), 'post_editor.html') self.response.out.write(template.render(path, template_values)) def post(self): token = get_auth_token(self.request) if not token: self.redirect('/blogs') return draft = False if self.request.get('draft') == 'true': draft = True client = gdata.blogger.client.BloggerClient() new_post = client.add_post( self.request.get('blog_id'), self.request.get('title'), self.request.get('body'), draft=draft, auth_token=token) if not draft: self.response.out.write( 'See your new post here.' % ( new_post.find_alternate_link())) else: self.response.out.write( 'This was a draft blog post, visit ' 'blogger.com to publish') def main(): application = webapp.WSGIApplication([('/blogs', ListBlogs), ('/write_post', WritePost)], debug=True) wsgiref.handlers.CGIHandler().run(application) if __name__ == '__main__': main() gdata-2.0.18/samples/blogger/app/auth_required.html0000640036466300116100000000066112156622362022334 0ustar afshareng00000000000000 Authorization Required

Before this application can see your available blogs or post on a blog, you must allow this app to access your Blogger data. Please click here to authorize this application.

If you don't want to grant permission to this app, please sign out.

gdata-2.0.18/samples/blogger/app/list_blogs.html0000640036466300116100000000070112156622362021627 0ustar afshareng00000000000000 List of Your Blogs

Here are the blogs you can post on. Click a link to bring up a blog post form to add a new post to the blog.

Done? Sign Out

gdata-2.0.18/samples/blogger/app/post_editor.html0000640036466300116100000000107712156622362022030 0ustar afshareng00000000000000 List of Your Blogs

Write a new post for your blog



Draft:

Done? Sign Out

gdata-2.0.18/samples/blogger/BloggerExample.py0000750036466300116100000002264412156622362021303 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright (C) 2009 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. # This file demonstrates how to use the Google Data API's Python client library # to interface with the Blogger service. There are examples for the following # operations: # # * Retrieving the list of all the user's blogs # * Retrieving all posts on a single blog # * Performing a date-range query for posts on a blog # * Creating draft posts and publishing posts # * Updating posts # * Retrieving comments # * Creating comments # * Deleting comments # * Deleting posts __author__ = 'lkeppler@google.com (Luke Keppler)' import gdata.blogger.client import gdata.client import gdata.sample_util import gdata.data import atom.data class BloggerExample: def __init__(self): """Creates a GDataService and provides ClientLogin auth details to it. The email and password are required arguments for ClientLogin. The 'source' defined below is an arbitrary string, but should be used to reference your name or the name of your organization, the app name and version, with '-' between each of the three values.""" # Authenticate using ClientLogin, AuthSub, or OAuth. self.client = gdata.blogger.client.BloggerClient() gdata.sample_util.authorize_client( self.client, service='blogger', source='Blogger_Python_Sample-2.0', scopes=['http://www.blogger.com/feeds/']) # Get the blog ID for the first blog. feed = self.client.get_blogs() self.blog_id = feed.entry[0].get_blog_id() def PrintUserBlogTitles(self): """Prints a list of all the user's blogs.""" # Request the feed. feed = self.client.get_blogs() # Print the results. print feed.title.text for entry in feed.entry: print "\t" + entry.title.text print def CreatePost(self, title, content, is_draft): """This method creates a new post on a blog. The new post can be stored as a draft or published based on the value of the is_draft parameter. The method creates an GDataEntry for the new post using the title, content, author_name and is_draft parameters. With is_draft, True saves the post as a draft, while False publishes the post. Then it uses the given GDataService to insert the new post. If the insertion is successful, the added post (GDataEntry) will be returned. """ return self.client.add_post(self.blog_id, title, content, draft=is_draft) def PrintAllPosts(self): """This method displays the titles of all the posts in a blog. First it requests the posts feed for the blogs and then it prints the results. """ # Request the feed. feed = self.client.get_posts(self.blog_id) # Print the results. print feed.title.text for entry in feed.entry: if not entry.title.text: print "\tNo Title" else: print "\t" + entry.title.text.encode('utf-8') print def PrintPostsInDateRange(self, start_time, end_time): """This method displays the title and modification time for any posts that have been created or updated in the period between the start_time and end_time parameters. The method creates the query, submits it to the GDataService, and then displays the results. Note that while the start_time is inclusive, the end_time is exclusive, so specifying an end_time of '2007-07-01' will include those posts up until 2007-6-30 11:59:59PM. The start_time specifies the beginning of the search period (inclusive), while end_time specifies the end of the search period (exclusive). """ # Create query and submit a request. query = gdata.blogger.client.Query(updated_min=start_time, updated_max=end_time, order_by='updated') print query.updated_min print query.order_by feed = self.client.get_posts(self.blog_id, query=query) # Print the results. print feed.title.text + " posts between " + start_time + " and " + end_time print feed.title.text for entry in feed.entry: if not entry.title.text: print "\tNo Title" else: print "\t" + entry.title.text print def UpdatePostTitle(self, entry_to_update, new_title): """This method updates the title of the given post. The GDataEntry object is updated with the new title, then a request is sent to the GDataService. If the insertion is successful, the updated post will be returned. Note that other characteristics of the post can also be modified by updating the values of the entry object before submitting the request. The entry_to_update is a GDatEntry containing the post to update. The new_title is the text to use for the post's new title. Returns: a GDataEntry containing the newly-updated post. """ # Set the new title in the Entry object entry_to_update.title = atom.data.Title(type='xhtml', text=new_title) return self.client.update(entry_to_update) def CreateComment(self, post_id, comment_text): """This method adds a comment to the specified post. First the comment feed's URI is built using the given post ID. Then a GDataEntry is created for the comment and submitted to the GDataService. The post_id is the ID of the post on which to post comments. The comment_text is the text of the comment to store. Returns: an entry containing the newly-created comment NOTE: This functionality is not officially supported yet. """ return self.client.add_comment(self.blog_id, post_id, comment_text) def PrintAllComments(self, post_id): """This method displays all the comments for the given post. First the comment feed's URI is built using the given post ID. Then the method requests the comments feed and displays the results. Takes the post_id of the post on which to view comments. """ feed = self.client.get_post_comments(self.blog_id, post_id) # Display the results print feed.title.text for entry in feed.entry: print "\t" + entry.title.text print "\t" + entry.updated.text print def DeleteComment(self, comment_entry): """This method removes the comment specified by the given edit_link_href, the URI for editing the comment. """ self.client.delete(comment_entry) def DeletePost(self, post_entry): """This method removes the post specified by the given edit_link_href, the URI for editing the post. """ self.client.delete(post_entry) def run(self): """Runs each of the example methods defined above, demonstrating how to interface with the Blogger service. """ # Demonstrate retrieving a list of the user's blogs. self.PrintUserBlogTitles() # Demonstrate how to create a draft post. draft_post = self.CreatePost('Snorkling in Aruba', '

We had so much fun snorkling in Aruba

', True) print 'Successfully created draft post: "' + draft_post.title.text + '".\n' # Delete the draft blog post. self.client.delete(draft_post) # Demonstrate how to publish a public post. public_post = self.CreatePost("Back from vacation", "

I didn't want to leave Aruba, but I ran out of money :(

", False) print "Successfully created public post: \"" + public_post.title.text + "\".\n" # Demonstrate various feed queries. print "Now listing all posts." self.PrintAllPosts() print "Now listing all posts between 2007-04-04 and 2007-04-23." self.PrintPostsInDateRange("2007-04-04", "2007-04-23") # Demonstrate updating a post's title. print "Now updating the title of the post we just created:" public_post = self.UpdatePostTitle(public_post, "The party's over") print "Successfully changed the post's title to \"" + public_post.title.text + "\".\n" # Demonstrate how to retrieve the comments for a post. # Get the post ID and build the comments feed URI for the specified post post_id = public_post.get_post_id() print "Now posting a comment on the post titled: \"" + public_post.title.text + "\"." comment = self.CreateComment(post_id, "Did you see any sharks?") print "Successfully posted \"" + comment.content.text + "\" on the post titled: \"" + public_post.title.text + "\".\n" comment_id = comment.GetCommentId() print "Now printing all comments" self.PrintAllComments(post_id) # Delete the comment we just posted print "Now deleting the comment we just posted" self.DeleteComment(comment) print "Successfully deleted comment." self.PrintAllComments(post_id) # Demonstrate deleting posts. print "Now deleting the post titled: \"" + public_post.title.text + "\"." self.DeletePost(public_post) print "Successfully deleted post." self.PrintAllPosts() def main(): """The main function runs the BloggerExample application. NOTE: It is recommended that you run this sample using a test account. """ sample = BloggerExample() sample.run() if __name__ == '__main__': main() gdata-2.0.18/samples/blogger/BloggerExampleV1.py0000750036466300116100000002633512156622362021513 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright (C) 2007 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. # This file demonstrates how to use the Google Data API's Python client library # to interface with the Blogger service. There are examples for the following # operations: # # * Retrieving the list of all the user's blogs # * Retrieving all posts on a single blog # * Performing a date-range query for posts on a blog # * Creating draft posts and publishing posts # * Updating posts # * Retrieving comments # * Creating comments # * Deleting comments # * Deleting posts __author__ = 'lkeppler@google.com (Luke Keppler)' from gdata import service import gdata import atom import getopt import sys class BloggerExample: def __init__(self, email, password): """Creates a GDataService and provides ClientLogin auth details to it. The email and password are required arguments for ClientLogin. The 'source' defined below is an arbitrary string, but should be used to reference your name or the name of your organization, the app name and version, with '-' between each of the three values.""" # Authenticate using ClientLogin. self.service = service.GDataService(email, password) self.service.source = 'Blogger_Python_Sample-1.0' self.service.service = 'blogger' self.service.server = 'www.blogger.com' self.service.ProgrammaticLogin() # Get the blog ID for the first blog. feed = self.service.Get('/feeds/default/blogs') self_link = feed.entry[0].GetSelfLink() if self_link: self.blog_id = self_link.href.split('/')[-1] def PrintUserBlogTitles(self): """Prints a list of all the user's blogs.""" # Request the feed. query = service.Query() query.feed = '/feeds/default/blogs' feed = self.service.Get(query.ToUri()) # Print the results. print feed.title.text for entry in feed.entry: print "\t" + entry.title.text print def CreatePost(self, title, content, author_name, is_draft): """This method creates a new post on a blog. The new post can be stored as a draft or published based on the value of the is_draft parameter. The method creates an GDataEntry for the new post using the title, content, author_name and is_draft parameters. With is_draft, True saves the post as a draft, while False publishes the post. Then it uses the given GDataService to insert the new post. If the insertion is successful, the added post (GDataEntry) will be returned. """ # Create the entry to insert. entry = gdata.GDataEntry() entry.author.append(atom.Author(atom.Name(text=author_name))) entry.title = atom.Title(title_type='xhtml', text=title) entry.content = atom.Content(content_type='html', text=content) if is_draft: control = atom.Control() control.draft = atom.Draft(text='yes') entry.control = control # Ask the service to insert the new entry. return self.service.Post(entry, '/feeds/' + self.blog_id + '/posts/default') def PrintAllPosts(self): """This method displays the titles of all the posts in a blog. First it requests the posts feed for the blogs and then it prints the results. """ # Request the feed. feed = self.service.GetFeed('/feeds/' + self.blog_id + '/posts/default') # Print the results. print feed.title.text for entry in feed.entry: if not entry.title.text: print "\tNo Title" else: print "\t" + entry.title.text print def PrintPostsInDateRange(self, start_time, end_time): """This method displays the title and modification time for any posts that have been created or updated in the period between the start_time and end_time parameters. The method creates the query, submits it to the GDataService, and then displays the results. Note that while the start_time is inclusive, the end_time is exclusive, so specifying an end_time of '2007-07-01' will include those posts up until 2007-6-30 11:59:59PM. The start_time specifies the beginning of the search period (inclusive), while end_time specifies the end of the search period (exclusive). """ # Create query and submit a request. query = service.Query() query.feed = '/feeds/' + self.blog_id + '/posts/default' query.updated_min = start_time query.updated_max = end_time query.orderby = 'updated' feed = self.service.Get(query.ToUri()) # Print the results. print feed.title.text + " posts between " + start_time + " and " + end_time print feed.title.text for entry in feed.entry: if not entry.title.text: print "\tNo Title" else: print "\t" + entry.title.text print def UpdatePostTitle(self, entry_to_update, new_title): """This method updates the title of the given post. The GDataEntry object is updated with the new title, then a request is sent to the GDataService. If the insertion is successful, the updated post will be returned. Note that other characteristics of the post can also be modified by updating the values of the entry object before submitting the request. The entry_to_update is a GDatEntry containing the post to update. The new_title is the text to use for the post's new title. Returns: a GDataEntry containing the newly-updated post. """ # Set the new title in the Entry object entry_to_update.title = atom.Title('xhtml', new_title) # Grab the edit URI edit_uri = entry_to_update.GetEditLink().href return self.service.Put(entry_to_update, edit_uri) def CreateComment(self, post_id, comment_text): """This method adds a comment to the specified post. First the comment feed's URI is built using the given post ID. Then a GDataEntry is created for the comment and submitted to the GDataService. The post_id is the ID of the post on which to post comments. The comment_text is the text of the comment to store. Returns: an entry containing the newly-created comment NOTE: This functionality is not officially supported yet. """ # Build the comment feed URI feed_uri = '/feeds/' + self.blog_id + '/' + post_id + '/comments/default' # Create a new entry for the comment and submit it to the GDataService entry = gdata.GDataEntry() entry.content = atom.Content(content_type='xhtml', text=comment_text) return self.service.Post(entry, feed_uri) def PrintAllComments(self, post_id): """This method displays all the comments for the given post. First the comment feed's URI is built using the given post ID. Then the method requests the comments feed and displays the results. Takes the post_id of the post on which to view comments. """ # Build comment feed URI and request comments on the specified post feed_url = '/feeds/' + self.blog_id + '/comments/default' feed = self.service.Get(feed_url) # Display the results print feed.title.text for entry in feed.entry: print "\t" + entry.title.text print "\t" + entry.updated.text print def DeleteComment(self, post_id, comment_id): """This method removes the comment specified by the given edit_link_href, the URI for editing the comment. """ feed_uri = '/feeds/' + self.blog_id + '/' + post_id + '/comments/default/' + comment_id self.service.Delete(feed_uri) def DeletePost(self, edit_link_href): """This method removes the post specified by the given edit_link_href, the URI for editing the post. """ self.service.Delete(edit_link_href) def run(self): """Runs each of the example methods defined above, demonstrating how to interface with the Blogger service. """ # Demonstrate retrieving a list of the user's blogs. self.PrintUserBlogTitles() # Demonstrate how to create a draft post. draft_post = self.CreatePost("Snorkling in Aruba", "

We had so much fun snorkling in Aruba

", "Post author", True) print "Successfully created draft post: \"" + draft_post.title.text + "\".\n" # Demonstrate how to publish a public post. public_post = self.CreatePost("Back from vacation", "

I didn't want to leave Aruba, but I ran out of money :(

", "Post author", False) print "Successfully created public post: \"" + public_post.title.text + "\".\n" # Demonstrate various feed queries. print "Now listing all posts." self.PrintAllPosts() print "Now listing all posts between 2007-04-04 and 2007-04-23." self.PrintPostsInDateRange("2007-04-04", "2007-04-23") # Demonstrate updating a post's title. print "Now updating the title of the post we just created:" public_post = self.UpdatePostTitle(public_post, "The party's over") print "Successfully changed the post's title to \"" + public_post.title.text + "\".\n" # Demonstrate how to retrieve the comments for a post. # Get the post ID and build the comments feed URI for the specified post self_id = public_post.id.text tokens = self_id.split("-") post_id = tokens[-1] print "Now posting a comment on the post titled: \"" + public_post.title.text + "\"." comment = self.CreateComment(post_id, "Did you see any sharks?") print "Successfully posted \"" + comment.content.text + "\" on the post titled: \"" + public_post.title.text + "\".\n" comment_id = comment.GetEditLink().href.split("/")[-1] print "Now printing all comments" self.PrintAllComments(post_id) # Delete the comment we just posted print "Now deleting the comment we just posted" self.DeleteComment(post_id, comment_id) print "Successfully deleted comment." self.PrintAllComments(post_id) # Get the post's edit URI edit_uri = public_post.GetEditLink().href # Demonstrate deleting posts. print "Now deleting the post titled: \"" + public_post.title.text + "\"." self.DeletePost(edit_uri) print "Successfully deleted post." self.PrintAllPosts() def main(): """The main function runs the BloggerExample application with the provided username and password values. Authentication credentials are required. NOTE: It is recommended that you run this sample using a test account.""" # parse command line options try: opts, args = getopt.getopt(sys.argv[1:], "", ["email=", "password="]) except getopt.error, msg: print ('python BloggerExample.py --email [email] --password [password] ') sys.exit(2) email = '' password = '' # Process options for o, a in opts: if o == "--email": email = a elif o == "--password": password = a if email == '' or password == '': print ('python BloggerExample.py --email [email] --password [password]') sys.exit(2) sample = BloggerExample(email, password) sample.run() if __name__ == '__main__': main() gdata-2.0.18/samples/analytics/0000750036466300116100000000000012156625015016365 5ustar afshareng00000000000000gdata-2.0.18/samples/analytics/data_feed_demo.py0000750036466300116100000001414612156622362021652 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright 2009 Google Inc. All Rights Reserved. # # 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. """Sample Google Analytics Data Export API Data Feed application. This sample demonstrates how to make requests and retrieve the important information from the Google Analytics Data Export API Data Feed. This sample requires a Google Analytics username and password and uses the Client Login authorization routine. Class DataFeedDemo: Prints all the important Data Feed informantion. """ __author__ = 'api.nickm@google.com (Nick Mihailovski)' import gdata.analytics.client import gdata.sample_util def main(): """Main function for the sample.""" demo = DataFeedDemo() demo.PrintFeedDetails() demo.PrintDataSources() demo.PrintFeedAggregates() demo.PrintSegmentInfo() demo.PrintOneEntry() demo.PrintFeedTable() class DataFeedDemo(object): """Gets data from the Data Feed. Attributes: data_feed: Google Analytics AccountList returned form the API. """ def __init__(self): """Inits DataFeedDemo.""" SOURCE_APP_NAME = 'Google-dataFeedDemoPython-v2' my_client = gdata.analytics.client.AnalyticsClient(source=SOURCE_APP_NAME) try: gdata.sample_util.authorize_client( my_client, service=my_client.auth_service, source=SOURCE_APP_NAME, scopes=['https://www.google.com/analytics/feeds/']) except gdata.client.BadAuthentication: exit('Invalid user credentials given.') except gdata.client.Error: exit('Login Error') table_id = gdata.sample_util.get_param( name='table_id', prompt='Please enter your Google Analytics Table id (format ga:xxxx)') # DataFeedQuery simplifies constructing API queries and uri encodes params. data_query = gdata.analytics.client.DataFeedQuery({ 'ids': table_id, 'start-date': '2008-10-01', 'end-date': '2008-10-30', 'dimensions': 'ga:source,ga:medium', 'metrics': 'ga:visits', 'sort': '-ga:visits', 'filters': 'ga:medium==referral', 'max-results': '50'}) self.feed = my_client.GetDataFeed(data_query) def PrintFeedDetails(self): """Prints important Analytics related data found at the top of the feed.""" print '\n-------- Feed Data --------' print 'Feed Title = ' + self.feed.title.text print 'Feed Id = ' + self.feed.id.text print 'Total Results Found = ' + self.feed.total_results.text print 'Start Index = ' + self.feed.start_index.text print 'Results Returned = ' + self.feed.items_per_page.text print 'Start Date = ' + self.feed.start_date.text print 'End Date = ' + self.feed.end_date.text print 'Has Sampled Data = ' + str(self.feed.HasSampledData()) def PrintDataSources(self): """Prints data found in the data source elements. This data has information about the Google Analytics account the referenced table ID belongs to. Note there is currently exactly one data source in the data feed. """ data_source = self.feed.data_source[0] print '\n-------- Data Source Data --------' print 'Table ID = ' + data_source.table_id.text print 'Table Name = ' + data_source.table_name.text print 'Web Property Id = ' + data_source.GetProperty('ga:webPropertyId').value print 'Profile Id = ' + data_source.GetProperty('ga:profileId').value print 'Account Name = ' + data_source.GetProperty('ga:accountName').value def PrintFeedAggregates(self): """Prints data found in the aggregates elements. This contains the sum of all the metrics defined in the query across. This sum spans all the rows matched in the feed.total_results property and not just the rows returned by the response. """ aggregates = self.feed.aggregates print '\n-------- Metric Aggregates --------' for met in aggregates.metric: print '' print 'Metric Name = ' + met.name print 'Metric Value = ' + met.value print 'Metric Type = ' + met.type print 'Metric CI = ' + met.confidence_interval def PrintSegmentInfo(self): """Prints segment information if the query has advanced segments defined.""" print '-------- Advanced Segments Information --------' if self.feed.segment: if segment.name: print 'Segment Name = ' + str(segment.name) if segment.id: print 'Segment Id = ' + str(segment.id) print 'Segment Definition = ' + segment.definition.text else: print 'No segments defined' def PrintOneEntry(self): """Prints all the important Google Analytics data found in an entry""" print '\n-------- One Entry --------' if len(self.feed.entry) == 0: print 'No entries found' return entry = self.feed.entry[0] print 'ID = ' + entry.id.text for dim in entry.dimension: print 'Dimension Name = ' + dim.name print 'Dimension Value = ' + dim.value for met in entry.metric: print 'Metric Name = ' + met.name print 'Metric Value = ' + met.value print 'Metric Type = ' + met.type print 'Metric CI = ' + met.confidence_interval def PrintFeedTable(self): """Prints all the entries as a table.""" print '\n-------- All Entries In a Table --------' for entry in self.feed.entry: for dim in entry.dimension: print ('Dimension Name = %s \t Dimension Value = %s' % (dim.name, dim.value)) for met in entry.metric: print ('Metric Name = %s \t Metric Value = %s' % (met.name, met.value)) print '---' if __name__ == '__main__': main() gdata-2.0.18/samples/analytics/account_feed_demo.py0000750036466300116100000001507012156622362022372 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright 2009 Google Inc. All Rights Reserved. # # 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. """Sample Google Analytics Data Export API Account Feed application. This sample demonstrates how to retrieve the important data from the Google Analytics Data Export API Account feed using the Python Client library. This requires a Google Analytics username and password and uses the Client Login authorization routine. Class AccountFeedDemo: Prints all the import Account Feed data. """ __author__ = 'api.nickm@google.com (Nick Mihailovski)' import gdata.analytics.client import gdata.sample_util def main(): """Main fucntion for the sample.""" demo = AccountFeedDemo() demo.PrintFeedDetails() demo.PrintAdvancedSegments() demo.PrintCustomVarForOneEntry() demo.PrintGoalsForOneEntry() demo.PrintAccountEntries() class AccountFeedDemo(object): """Prints the Google Analytics account feed Attributes: account_feed: Google Analytics AccountList returned form the API. """ def __init__(self): """Inits AccountFeedDemo.""" SOURCE_APP_NAME = 'Google-accountFeedDemoPython-v1' my_client = gdata.analytics.client.AnalyticsClient(source=SOURCE_APP_NAME) try: gdata.sample_util.authorize_client( my_client, service=my_client.auth_service, source=SOURCE_APP_NAME, scopes=['https://www.google.com/analytics/feeds/']) except gdata.client.BadAuthentication: exit('Invalid user credentials given.') except gdata.client.Error: exit('Login Error') account_query = gdata.analytics.client.AccountFeedQuery() self.feed = my_client.GetAccountFeed(account_query) def PrintFeedDetails(self): """Prints important Analytics related data found at the top of the feed.""" print '-------- Important Feed Data --------' print 'Feed Title = ' + self.feed.title.text print 'Feed Id = ' + self.feed.id.text print 'Total Results Found = ' + self.feed.total_results.text print 'Start Index = ' + self.feed.start_index.text print 'Results Returned = ' + self.feed.items_per_page.text def PrintAdvancedSegments(self): """Prints the advanced segments for this user.""" print '-------- Advances Segments --------' if not self.feed.segment: print 'No advanced segments found' else: for segment in self.feed.segment: print 'Segment Name = ' + segment.name print 'Segment Id = ' + segment.id print 'Segment Definition = ' + segment.definition.text def PrintCustomVarForOneEntry(self): """Prints custom variable information for the first profile that has custom variable configured.""" print '-------- Custom Variables --------' if not self.feed.entry: print 'No entries found' else: for entry in self.feed.entry: if entry.custom_variable: for custom_variable in entry.custom_variable: print 'Custom Variable Index = ' + custom_variable.index print 'Custom Variable Name = ' + custom_variable.name print 'Custom Variable Scope = ' + custom_variable.scope return print 'No custom variables defined for this user' def PrintGoalsForOneEntry(self): """Prints All the goal information for one profile.""" print '-------- Goal Configuration --------' if not self.feed.entry: print 'No entries found' else: for entry in self.feed.entry: if entry.goal: for goal in entry.goal: print 'Goal Number = ' + goal.number print 'Goal Name = ' + goal.name print 'Goal Value = ' + goal.value print 'Goal Active = ' + goal.active if goal.destination: self.PrintDestinationGoal(goal.destination) elif goal.engagement: self.PrintEngagementGoal(goal.engagement) return def PrintDestinationGoal(self, destination): """Prints the important information for destination goals including all the configured steps if they exist. Args: destination: gdata.data.Destination The destination goal configuration. """ print '----- Destination Goal -----' print 'Expression = ' + destination.expression print 'Match Type = ' + destination.match_type print 'Step 1 Required = ' + destination.step1_required print 'Case Sensitive = ' + destination.case_sensitive # Print goal steps. if destination.step: print '----- Destination Goal Steps -----' for step in destination.step: print 'Step Number = ' + step.number print 'Step Name = ' + step.name print 'Step Path = ' + step.path def PrintEngagementGoal(self, engagement): """Prints the important information for engagement goals. Args: engagement: gdata.data.Engagement The engagement goal configuration. """ print '----- Engagement Goal -----' print 'Goal Type = ' + engagement.type print 'Goal Engagement = ' + engagement.comparison print 'Goal Threshold = ' + engagement.threshold_value def PrintAccountEntries(self): """Prints important Analytics data found in each account entry""" print '-------- First 1000 Profiles in Account Feed --------' if not self.feed.entry: print 'No entries found' else: for entry in self.feed.entry: print 'Web Property ID = ' + entry.GetProperty('ga:webPropertyId').value print 'Account Name = ' + entry.GetProperty('ga:accountName').value print 'Account Id = ' + entry.GetProperty('ga:accountId').value print 'Profile Name = ' + entry.title.text print 'Profile ID = ' + entry.GetProperty('ga:profileId').value print 'Table ID = ' + entry.table_id.text print 'Currency = ' + entry.GetProperty('ga:currency').value print 'TimeZone = ' + entry.GetProperty('ga:timezone').value if entry.custom_variable: print 'This profile has custom variables' if entry.goal: print 'This profile has goals' if __name__ == '__main__': main() gdata-2.0.18/samples/analytics/mgmt_feed_demo.py0000750036466300116100000002023412156622362021700 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright 2010 Google Inc. All Rights Reserved. # # 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. """Google Analytics Management API Demo. This script demonstrates how to retrieve the important data from the Google Analytics Data Management API using the Python Client library. This example requires a Google Analytics account with data and a username and password. Each feed in the Management API is retrieved and printed using the respective print method in ManagementFeedDemo. To simplify setting filters and query parameters, each feed has it's own query class. Check the gdata.analytics.client module for more details on usage. main: The main method of this example. GetAnalyticsClient: Returns an authorized AnalyticsClient object. Class ManagementFeedDemo: Prints all the import Account Feed data. """ __author__ = 'api.nickm@google.com (Nick Mihailovski)' import gdata.analytics.client import gdata.sample_util ACCOUNT_ID = '~all' WEB_PROPERTY_ID = '~all' PROFILE_ID = '~all' def main(): """Main example script. Un-comment each method to print the feed.""" demo = ManagementFeedDemo(GetAnalyticsClient()) demo.PrintAccountFeed() # demo.PrintWebPropertyFeed() # demo.PrintProfileFeed() # demo.PrintGoalFeed() # demo.PrintSegmentFeed() def GetAnalyticsClient(): """Returns an authorized GoogleAnalayticsClient object. Uses the Google Data python samples wrapper to prompt the user for credentials then tries to authorize the client object with the Google Analytics API. Returns: An authorized GoogleAnalyticsClient object. """ SOURCE_APP_NAME = 'Analytics-ManagementAPI-Demo-v1' my_client = gdata.analytics.client.AnalyticsClient(source=SOURCE_APP_NAME) try: gdata.sample_util.authorize_client( my_client, service=my_client.auth_service, source=SOURCE_APP_NAME, scopes=['https://www.google.com/analytics/feeds/']) except gdata.client.BadAuthentication: exit('Invalid user credentials given.') except gdata.client.Error: exit('Login Error') return my_client class ManagementFeedDemo(object): """The main demo for the management feed. Attributes: my_client: gdata.analytics.client The AnalyticsClient object for this demo. """ def __init__(self, my_client): """Initializes the ManagementFeedDemo class. Args: my_client: gdata.analytics.client An authorized GoogleAnalyticsClient object. """ self.my_client = my_client def PrintAccountFeed(self): """Requests and prints the important data in the Account Feed. Note: AccountQuery is used for the ManagementAPI. AccountFeedQuery is used for the Data Export API. """ account_query = gdata.analytics.client.AccountQuery() results = self.my_client.GetManagementFeed(account_query) print '-------- Account Feed Data --------' if not results.entry: print 'no entries found' else: for entry in results.entry: print 'Account Name = ' + entry.GetProperty('ga:accountName').value print 'Account ID = ' + entry.GetProperty('ga:accountId').value print 'Child Feed Link = ' + entry.GetChildLink('analytics#webproperties').href print def PrintWebPropertyFeed(self): """Requests and prints the important data in the Web Property Feed.""" web_property_query = gdata.analytics.client.WebPropertyQuery( acct_id=ACCOUNT_ID) results = self.my_client.GetManagementFeed(web_property_query) print '-------- Web Property Feed Data --------' if not results.entry: print 'no entries found' else: for entry in results.entry: print 'Account ID = ' + entry.GetProperty('ga:accountId').value print 'Web Property ID = ' + entry.GetProperty('ga:webPropertyId').value print 'Child Feed Link = ' + entry.GetChildLink('analytics#profiles').href print def PrintProfileFeed(self): """Requests and prints the important data in the Profile Feed. Note: TableId has a different namespace (dxp:) than all the other properties (ga:). """ profile_query = gdata.analytics.client.ProfileQuery( acct_id=ACCOUNT_ID, web_prop_id=WEB_PROPERTY_ID) results = self.my_client.GetManagementFeed(profile_query) print '-------- Profile Feed Data --------' if not results.entry: print 'no entries found' else: for entry in results.entry: print 'Account ID = ' + entry.GetProperty('ga:accountId').value print 'Web Property ID = ' + entry.GetProperty('ga:webPropertyId').value print 'Profile ID = ' + entry.GetProperty('ga:profileId').value print 'Currency = ' + entry.GetProperty('ga:currency').value print 'Timezone = ' + entry.GetProperty('ga:timezone').value print 'TableId = ' + entry.GetProperty('dxp:tableId').value print 'Child Feed Link = ' + entry.GetChildLink('analytics#goals').href print def PrintGoalFeed(self): """Requests and prints the important data in the Goal Feed. Note: There are two types of goals, destination and engagement which need to be handled differently. """ goal_query = gdata.analytics.client.GoalQuery( acct_id=ACCOUNT_ID, web_prop_id=WEB_PROPERTY_ID, profile_id=PROFILE_ID) results = self.my_client.GetManagementFeed(goal_query) print '-------- Goal Feed Data --------' if not results.entry: print 'no entries found' else: for entry in results.entry: print 'Goal Number = ' + entry.goal.number print 'Goal Name = ' + entry.goal.name print 'Goal Value = ' + entry.goal.value print 'Goal Active = ' + entry.goal.active if entry.goal.destination: self.PrintDestinationGoal(entry.goal.destination) elif entry.goal.engagement: self.PrintEngagementGoal(entry.goal.engagement) def PrintDestinationGoal(self, destination): """Prints the important information for destination goals including all the configured steps if they exist. Args: destination: gdata.data.Destination The destination goal configuration. """ print '\t----- Destination Goal -----' print '\tExpression = ' + destination.expression print '\tMatch Type = ' + destination.match_type print '\tStep 1 Required = ' + destination.step1_required print '\tCase Sensitive = ' + destination.case_sensitive if destination.step: print '\t\t----- Destination Goal Steps -----' for step in destination.step: print '\t\tStep Number = ' + step.number print '\t\tStep Name = ' + step.name print '\t\tStep Path = ' + step.path print def PrintEngagementGoal(self, engagement): """Prints the important information for engagement goals. Args: engagement: gdata.data.Engagement The engagement goal configuration. """ print '\t----- Engagement Goal -----' print '\tGoal Type = ' + engagement.type print '\tGoal Engagement = ' + engagement.comparison print '\tGoal Threshold = ' + engagement.threshold_value print def PrintSegmentFeed(self): """Requests and prints the important data in the Profile Feed.""" adv_seg_query = gdata.analytics.client.AdvSegQuery() results = self.my_client.GetManagementFeed(adv_seg_query) print '-------- Advanced Segment Feed Data --------' if not results.entry: print 'no entries found' else: for entry in results.entry: print 'Segment ID = ' + entry.segment.id print 'Segment Name = ' + entry.segment.name print 'Segment Definition = ' + entry.segment.definition.text print if __name__ == '__main__': main() gdata-2.0.18/samples/mashups/0000750036466300116100000000000012156625015016056 5ustar afshareng00000000000000gdata-2.0.18/samples/mashups/birthdaySample.py0000750036466300116100000002566212156622362021420 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright (C) 2007 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. # # # This sample uses the Google Spreadsheets data API and the Google # Calendar data API. The script pulls a list of birthdays from a # Google Spreadsheet and inserts them as webContent events in the # user's Google Calendar. # # The script expects a certain format in the spreadsheet: Name, # Birthday, Photo URL, and Edit URL as headers. Expected format # of the birthday is: MM/DD. Edit URL is to be left blank by the # user - the script uses this column to determine whether to insert # a new event or to update an event at the URL. # # See the spreadsheet below for an example: # http://spreadsheets.google.com/pub?key=pfMX-JDVnx47J0DxqssIQHg # __author__ = 'api.stephaniel@google.com (Stephanie Liu)' try: from xml.etree import ElementTree # for Python 2.5 users except: from elementtree import ElementTree import gdata.spreadsheet.service import gdata.calendar.service import gdata.calendar import gdata.service import atom.service import gdata.spreadsheet import atom import string import time import datetime import getopt import getpass import sys class BirthdaySample: # CONSTANTS: Expected column headers: name, birthday, photourl, editurl & # default calendar reminder set to 2 days NAME = "name" BIRTHDAY = "birthday" PHOTO_URL = "photourl" EDIT_URL = "editurl" REMINDER = 60 * 24 * 2 # minutes def __init__(self, email, password): """ Initializes spreadsheet and calendar clients. Creates SpreadsheetsService and CalendarService objects and authenticates to each with ClientLogin. For more information about ClientLogin authentication: http://code.google.com/apis/accounts/AuthForInstalledApps.html Args: email: string password: string """ self.s_client = gdata.spreadsheet.service.SpreadsheetsService() self.s_client.email = email self.s_client.password = password self.s_client.source = 'exampleCo-birthdaySample-1' self.s_client.ProgrammaticLogin() self.c_client = gdata.calendar.service.CalendarService() self.c_client.email = email self.c_client.password = password self.c_client.source = 'exampleCo-birthdaySample-1' self.c_client.ProgrammaticLogin() def _PrintFeed(self, feed): """ Prints out Spreadsheet feeds in human readable format. Generic function taken from spreadsheetsExample.py. Args: feed: SpreadsheetsCellsFeed, SpreadsheetsListFeed, SpreadsheetsWorksheetsFeed, or SpreadsheetsSpreadsheetsFeed """ for i, entry in enumerate(feed.entry): if isinstance(feed, gdata.spreadsheet.SpreadsheetsCellsFeed): print '%s %s\n' % (entry.title.text, entry.content.text) elif isinstance(feed, gdata.spreadsheet.SpreadsheetsListFeed): print '%s %s %s\n' % (i, entry.title.text, entry.content.text) else: print '%s %s\n' % (i, entry.title.text) def _PromptForSpreadsheet(self): """ Prompts user to select spreadsheet. Gets and displays titles of all spreadsheets for user to select. Generic function taken from spreadsheetsExample.py. Args: none Returns: spreadsheet ID that the user selected: string """ feed = self.s_client.GetSpreadsheetsFeed() self._PrintFeed(feed) input = raw_input('\nSelection: ') # extract and return the spreadsheet ID return feed.entry[string.atoi(input)].id.text.rsplit('/', 1)[1] def _PromptForWorksheet(self, key): """ Prompts user to select desired worksheet. Gets and displays titles of all worksheets for user to select. Generic function taken from spreadsheetsExample.py. Args: key: string Returns: the worksheet ID that the user selected: string """ feed = self.s_client.GetWorksheetsFeed(key) self._PrintFeed(feed) input = raw_input('\nSelection: ') # extract and return the worksheet ID return feed.entry[string.atoi(input)].id.text.rsplit('/', 1)[1] def _AddReminder(self, event, minutes): """ Adds a reminder to a calendar event. This function sets the reminder attribute of the CalendarEventEntry. The script sets it to 2 days by default, and this value is not settable by the user. However, it can easily be changed to take this option. Args: event: CalendarEventEntry minutes: int Returns: the updated event: CalendarEventEntry """ for a_when in event.when: if len(a_when.reminder) > 0: a_when.reminder[0].minutes = minutes else: a_when.reminder.append(gdata.calendar.Reminder(minutes=minutes)) return self.c_client.UpdateEvent(event.GetEditLink().href, event) def _CreateBirthdayWebContentEvent(self, name, birthday, photo_url): """ Create the birthday web content event. This function creates and populates a CalendarEventEntry. webContent specific attributes are set. To learn more about the webContent format: http://www.google.com/support/calendar/bin/answer.py?answer=48528 Args: name: string birthday: string - expected format (MM/DD) photo_url: string Returns: the webContent CalendarEventEntry """ title = "%s's Birthday!" % name content = "It's %s's Birthday!" % name month = string.atoi(birthday.split("/")[0]) day = string.atoi(birthday.split("/")[1]) # Get current year year = time.ctime()[-4:] year = string.atoi(year) # Calculate the "end date" for the all day event start_time = datetime.date(year, month, day) one_day = datetime.timedelta(days=1) end_time = start_time + one_day start_time_str = start_time.strftime("%Y-%m-%d") end_time_str = end_time.strftime("%Y-%m-%d") # Create yearly recurrence rule recurrence_data = ("DTSTART;VALUE=DATE:%s\r\n" "DTEND;VALUE=DATE:%s\r\n" "RRULE:FREQ=YEARLY;WKST=SU\r\n" % (start_time.strftime("%Y%m%d"), end_time.strftime("%Y%m%d"))) web_rel = "http://schemas.google.com/gCal/2005/webContent" icon_href = "http://www.perstephanie.com/images/birthdayicon.gif" icon_type = "image/gif" extension_text = ( 'gCal:webContent xmlns:gCal="http://schemas.google.com/gCal/2005"' ' url="%s" width="300" height="225"' % (photo_url)) event = gdata.calendar.CalendarEventEntry() event.title = atom.Title(text=title) event.content = atom.Content(text=content) event.recurrence = gdata.calendar.Recurrence(text=recurrence_data) event.when.append(gdata.calendar.When(start_time=start_time_str, end_time=end_time_str)) # Adding the webContent specific XML event.link.append(atom.Link(rel=web_rel, title=title, href=icon_href, link_type=icon_type)) event.link[0].extension_elements.append( atom.ExtensionElement(extension_text)) return event def _InsertBirthdayWebContentEvent(self, event): """ Insert event into the authenticated user's calendar. Args: event: CalendarEventEntry Returns: the newly created CalendarEventEntry """ edit_uri = '/calendar/feeds/default/private/full' return self.c_client.InsertEvent(event, edit_uri) def Run(self): """ Run sample. TODO: add exception handling Args: none """ key_id = self._PromptForSpreadsheet() wksht_id = self._PromptForWorksheet(key_id) feed = self.s_client.GetListFeed(key_id, wksht_id) found_name = False found_birthday = False found_photourl = False found_editurl = False # Check to make sure all headers are present # Need to find at least one instance of name, birthday, photourl # editurl if len(feed.entry) > 0: for name, custom in feed.entry[0].custom.iteritems(): if custom.column == self.NAME: found_name = True elif custom.column == self.BIRTHDAY: found_birthday = True elif custom.column == self.PHOTO_URL: found_photourl = True elif custom.column == self.EDIT_URL: found_editurl = True if not found_name and found_birthday and found_photourl and found_editurl: print ("ERROR - Unexpected number of column headers. Should have: %s," " %s, %s, and %s." % (self.NAME, self.BIRTHDAY, self.PHOTO_URL, self.EDIT_URL)) sys.exit(1) # For every row in the spreadsheet, grab all the data and either insert # a new event into the calendar, or update the existing event # Create dict to represent the row data to update edit link back to # Spreadsheet for entry in feed.entry: d = {} input_valid = True for name, custom in entry.custom.iteritems(): d[custom.column] = custom.text month = int(d[self.BIRTHDAY].split("/")[0]) day = int(d[self.BIRTHDAY].split("/")[1]) # Some input checking. Script will allow the insert to continue with # a missing name value. if d[self.NAME] is None: d[self.NAME] = " " if d[self.PHOTO_URL] is None: input_valid = False if d[self.BIRTHDAY] is None: input_valid = False elif not 1 <= month <= 12 or not 1 <= day <= 31: input_valid = False if d[self.EDIT_URL] is None and input_valid: event = self._CreateBirthdayWebContentEvent(d[self.NAME], d[self.BIRTHDAY], d[self.PHOTO_URL]) event = self._InsertBirthdayWebContentEvent(event) event = self._AddReminder(event, self.REMINDER) print "Added %s's birthday!" % d[self.NAME] elif input_valid: # Event already exists edit_link = d[self.EDIT_URL] event = self._CreateBirthdayWebContentEvent(d[self.NAME], d[self.BIRTHDAY], d[self.PHOTO_URL]) event = self.c_client.UpdateEvent(edit_link, event) event = self._AddReminder(event, self.REMINDER) print "Updated %s's birthday!" % d[self.NAME] if input_valid: d[self.EDIT_URL] = event.GetEditLink().href self.s_client.UpdateRow(entry, d) else: print "Warning - Skipping row, missing valid input." def main(): email = raw_input("Please enter your email: ") password = getpass.getpass("Please enter your password: ") sample = BirthdaySample(email, password) sample.Run() if __name__ == '__main__': main() gdata-2.0.18/samples/oauth/0000750036466300116100000000000012156625015015516 5ustar afshareng00000000000000gdata-2.0.18/samples/oauth/2_legged_oauth.py0000750036466300116100000000463712156622362020757 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright (C) 2009 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. __author__ = 'e.bidelman (Eric Bidelman)' import gdata.contacts import gdata.contacts.service import gdata.docs import gdata.docs.service CONSUMER_KEY = 'yourdomain.com' CONSUMER_SECRET = 'YOUR_CONSUMER_KEY' SIG_METHOD = gdata.auth.OAuthSignatureMethod.HMAC_SHA1 requestor_id = 'any.user@yourdomain.com' # Contacts Data API ============================================================ contacts = gdata.contacts.service.ContactsService() contacts.SetOAuthInputParameters( SIG_METHOD, CONSUMER_KEY, consumer_secret=CONSUMER_SECRET, two_legged_oauth=True, requestor_id=requestor_id) # GET - fetch user's contact list print "\nList of contacts for %s:" % (requestor_id,) feed = contacts.GetContactsFeed() for entry in feed.entry: print entry.title.text # GET - fetch another user's contact list requestor_id = 'another_user@yourdomain.com' print "\nList of contacts for %s:" % (requestor_id,) contacts.GetOAuthInputParameters().requestor_id = requestor_id feed = contacts.GetContactsFeed() for entry in feed.entry: print entry.title.text # Google Documents List Data API =============================================== docs = gdata.docs.service.DocsService() docs.SetOAuthInputParameters( SIG_METHOD, CONSUMER_KEY, consumer_secret=CONSUMER_SECRET, two_legged_oauth=True, requestor_id=requestor_id) # POST - upload a document print "\nUploading document to %s's Google Documents account:" % (requestor_id,) ms = gdata.MediaSource( file_path='/path/to/test.txt', content_type=gdata.docs.service.SUPPORTED_FILETYPES['TXT']) # GET - fetch user's document list entry = docs.UploadDocument(ms, 'Company Perks') print 'Document now accessible online at:', entry.GetAlternateLink().href print "\nList of Google Documents for %s" % (requestor_id,) feed = docs.GetDocumentListFeed() for entry in feed.entry: print entry.title.text gdata-2.0.18/samples/oauth/TwoLeggedOAuthExample.py0000750036466300116100000000462112156622362022236 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright (C) 2009 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. # Note: # This sample demonstrates 2 Legged OAuth using v2 of the Google Data APIs. # See 2_legged_oauth.py for an example of using 2LO with v1.0 of the APIs. __author__ = 'e.bidelman (Eric Bidelman)' import gdata.gauth import gdata.contacts.client import gdata.docs.client SOURCE_APP_NAME = 'google-PyClient2LOSample-v2.0' CONSUMER_KEY = 'yourdomain.com' CONSUMER_SECRET = 'YOUR_CONSUMER_KEY' def PrintContacts(client): print '\nListing contacts for %s...' % client.auth_token.requestor_id feed = client.GetContacts() for entry in feed.entry: print entry.title.text # Contacts Data API Example ==================================================== requestor_id = 'any.user@' + CONSUMER_KEY two_legged_oauth_token = gdata.gauth.TwoLeggedOAuthHmacToken( CONSUMER_KEY, CONSUMER_SECRET, requestor_id) contacts_client = gdata.contacts.client.ContactsClient(source=SOURCE_APP_NAME) contacts_client.auth_token = two_legged_oauth_token # GET - fetch user's contact list PrintContacts(contacts_client) # GET - fetch another user's contact list contacts_client.auth_token.requestor_id = 'different.user' + CONSUMER_KEY PrintContacts(contacts_client) # Documents List Data API Example ============================================== docs_client = gdata.docs.client.DocsClient(source=SOURCE_APP_NAME) docs_client.auth_token = two_legged_oauth_token docs_client.ssl = True # POST - upload a document print "\nUploading doc to %s's account..." % docs_client.auth_token.requestor_id entry = docs_client.Upload('test.txt', 'MyDocTitle', content_type='text/plain') print 'Document now accessible online at:', entry.GetAlternateLink().href # GET - fetch the user's document list print '\nListing Google Docs for %s...' % docs_client.auth_token.requestor_id feed = docs_client.GetDocList() for entry in feed.entry: print entry.title.text gdata-2.0.18/samples/oauth/oauth_example.py0000750036466300116100000001031712156622362020732 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright (C) 2007 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. __author__ = 'kunalmshah.userid (Kunal Shah)' import sys import os.path import getopt import gdata.auth import gdata.docs.service class OAuthSample(object): """Sample class demonstrating the three-legged OAuth process.""" def __init__(self, consumer_key, consumer_secret): """Constructor for the OAuthSample object. Takes a consumer key and consumer secret, store them in class variables, creates a DocsService client to be used to make calls to the Documents List Data API. Args: consumer_key: string Domain identifying third_party web application. consumer_secret: string Secret generated during registration. """ self.consumer_key = consumer_key self.consumer_secret = consumer_secret self.gd_client = gdata.docs.service.DocsService() def _PrintFeed(self, feed): """Prints out the contents of a feed to the console. Args: feed: A gdata.docs.DocumentListFeed instance. """ if not feed.entry: print 'No entries in feed.\n' docs_list = list(enumerate(feed.entry, start = 1)) for i, entry in docs_list: print '%d. %s\n' % (i, entry.title.text.encode('UTF-8')) def _ListAllDocuments(self): """Retrieves a list of all of a user's documents and displays them.""" feed = self.gd_client.GetDocumentListFeed() self._PrintFeed(feed) def Run(self): """Demonstrates usage of OAuth authentication mode and retrieves a list of documents using the Document List Data API.""" print '\nSTEP 1: Set OAuth input parameters.' self.gd_client.SetOAuthInputParameters( gdata.auth.OAuthSignatureMethod.HMAC_SHA1, self.consumer_key, consumer_secret=self.consumer_secret) print '\nSTEP 2: Fetch OAuth Request token.' request_token = self.gd_client.FetchOAuthRequestToken() print 'Request Token fetched: %s' % request_token print '\nSTEP 3: Set the fetched OAuth token.' self.gd_client.SetOAuthToken(request_token) print 'OAuth request token set.' print '\nSTEP 4: Generate OAuth authorization URL.' auth_url = self.gd_client.GenerateOAuthAuthorizationURL() print 'Authorization URL: %s' % auth_url raw_input('Manually go to the above URL and authenticate.' 'Press a key after authorization.') print '\nSTEP 5: Upgrade to an OAuth access token.' self.gd_client.UpgradeToOAuthAccessToken() print 'Access Token: %s' % ( self.gd_client.token_store.find_token(request_token.scopes[0])) print '\nYour Documents:\n' self._ListAllDocuments() print 'STEP 6: Revoke the OAuth access token after use.' self.gd_client.RevokeOAuthToken() print 'OAuth access token revoked.' def main(): """Demonstrates usage of OAuth authentication mode. Prints a list of documents. This demo uses HMAC-SHA1 signature method. """ # Parse command line options try: opts, args = getopt.getopt(sys.argv[1:], '', ['consumer_key=', 'consumer_secret=']) except getopt.error, msg: print ('python oauth_example.py --consumer_key [consumer_key] ' '--consumer_secret [consumer_secret] ') sys.exit(2) consumer_key = '' consumer_secret = '' # Process options for option, arg in opts: if option == '--consumer_key': consumer_key = arg elif option == '--consumer_secret': consumer_secret = arg while not consumer_key: consumer_key = raw_input('Please enter consumer key: ') while not consumer_secret: consumer_secret = raw_input('Please enter consumer secret: ') sample = OAuthSample(consumer_key, consumer_secret) sample.Run() if __name__ == '__main__': main() gdata-2.0.18/samples/contacts/0000750036466300116100000000000012156625015016214 5ustar afshareng00000000000000gdata-2.0.18/samples/contacts/unshare_profiles.py0000750036466300116100000001023212156622362022141 0ustar afshareng00000000000000#!/usr/bin/env python # # Copyright 2011 Google Inc. All Rights Reserved. # # 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. """Unshare domain users contact information when contact sharing is enabled.""" __author__ = 'alainv@google.com (Alain Vongsouvanh)' import sys import gdata.contacts.client import gdata.contacts.data import gdata.gauth class BatchResult(object): """Hold batch processing results. Attributes: success_count: Number of successful operations. error_count: Number of failed operations. error_entries: List of failed entries. """ success_count = 0 error_count = 0 error_entries = [] class ProfilesManager(object): """ProfilesManager object used to unshare domain users contact information. Basic usage is: >>> manager = ProfilesManager(CONSUMER_KEY, CONSUMER_SECRET, ADMIN_EMAIL) >>> result = manager.UnshareProfiles() >>> print 'Success: %s - Error: %s' % (result.success, result.error_count) Attributes: profiles: List of ProfilesEntry. batch_size: Number of operations per batch request (default to 100). """ def __init__(self, consumer_key, consumer_secret, admin_email): domain = admin_email[admin_email.index('@') + 1:] self._gd_client = gdata.contacts.client.ContactsClient( source='GoogleInc-UnshareProfiles-1', domain=domain) self._gd_client.auth_token = gdata.gauth.TwoLeggedOAuthHmacToken( consumer_key, consumer_secret, admin_email) self._profiles = None self.batch_size = 100 @property def profiles(self): """Get the list of profiles for the domain. Returns: List of ProfilesEntry. """ if not self._profiles: self.GetAllProfiles() return self._profiles def GetAllProfiles(self): """Retrieve the list of user profiles for the domain.""" profiles = [] feed_uri = self._gd_client.GetFeedUri('profiles') while feed_uri: feed = self._gd_client.GetProfilesFeed(uri=feed_uri) profiles.extend(feed.entry) feed_uri = feed.FindNextLink() self._profiles = profiles def UnshareProfiles(self): """Unshare users' contact information. Uses batch request to optimize the resources. Returns: BatchResult object. """ if not self._profiles: self.GetAllProfiles() batch_size = max(self.batch_size, 100) index = 0 result = BatchResult() while index < len(self._profiles): request_feed = gdata.contacts.data.ProfilesFeed() for entry in self._profiles[index:index + batch_size]: entry.status = gdata.contacts.data.Status(indexed='false') request_feed.AddUpdate(entry=entry) result_feed = self._gd_client.ExecuteBatchProfiles(request_feed) for entry in result_feed.entry: if entry.batch_status.code == '200': self._profiles[index] = entry result.success_count += 1 else: result.error_entries.append(entry) result.error_count += 1 index += 1 return result def main(): """Demonstrates the use of the Profiles API to unshare profiles.""" if len(sys.argv) > 3: consumer_key = sys.argv[1] consumer_secret = sys.argv[2] admin_email = sys.argv[3] else: print ('python unshare_profiles.py [consumer_key] [consumer_secret]' ' [admin_email]') sys.exit(2) manager = ProfilesManager(consumer_key, consumer_secret, admin_email) result = manager.UnshareProfiles() print 'Success: %s - Error: %s' % (result.success_count, result.error_count) for entry in result.error_entries: print ' > Failed to update %s: (%s) %s' % ( entry.id.text, entry.batch_status.code, entry.batch_status.reason) sys.exit(result.error_count) if __name__ == '__main__': main() gdata-2.0.18/samples/contacts/profiles_example.py0000750036466300116100000002025412156622362022134 0ustar afshareng00000000000000#!/usr/bin/env python # # Copyright 2009 Google Inc. All Rights Reserved. # # 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. """Contains a Sample for Google Profiles. ProfilesSample: demonstrates operations with the Profiles feed. """ __author__ = 'jtoledo (Julian Toledo)' import getopt import getpass import sys import gdata.contacts import gdata.contacts.service class ProfilesSample(object): """ProfilesSample object demonstrates operations with the Profiles feed.""" def __init__(self, email, password, domain): """Constructor for the ProfilesSample object. Takes an email and password corresponding to a gmail account to demonstrate the functionality of the Profiles feed. Args: email: [string] The e-mail address of the account to use for the sample. password: [string] The password corresponding to the account specified by the email parameter. domain: [string] The domain for the Profiles feed """ self.gd_client = gdata.contacts.service.ContactsService( contact_list=domain) self.gd_client.email = email self.gd_client.password = password self.gd_client.source = 'GoogleInc-ProfilesPythonSample-1' self.gd_client.ProgrammaticLogin() def PrintFeed(self, feed, ctr=0): """Prints out the contents of a feed to the console. Args: feed: A gdata.profiles.ProfilesFeed instance. ctr: [int] The number of entries in this feed previously printed. This allows continuous entry numbers when paging through a feed. Returns: The number of entries printed, including those previously printed as specified in ctr. This is for passing as an ar1gument to ctr on successive calls to this method. """ if not feed.entry: print '\nNo entries in feed.\n' return 0 for entry in feed.entry: self.PrintEntry(entry) return len(feed.entry) + ctr def PrintEntry(self, entry): """Prints out the contents of a single Entry to the console. Args: entry: A gdata.contacts.ProfilesEntry """ print '\n%s' % (entry.title.text) for email in entry.email: if email.primary == 'true': print 'Email: %s (primary)' % (email.address) else: print 'Email: %s' % (email.address) if entry.nickname: print 'Nickname: %s' % (entry.nickname.text) if entry.occupation: print 'Occupation: %s' % (entry.occupation.text) if entry.gender: print 'Gender: %s' % (entry.gender.value) if entry.birthday: print 'Birthday: %s' % (entry.birthday.when) for relation in entry.relation: print 'Relation: %s %s' % (relation.rel, relation.text) for user_defined_field in entry.user_defined_field: print 'UserDefinedField: %s %s' % (user_defined_field.key, user_defined_field.value) for website in entry.website: print 'Website: %s %s' % (website.href, website.rel) for phone_number in entry.phone_number: print 'Phone Number: %s' % phone_number.text for organization in entry.organization: print 'Organization:' if organization.org_name: print ' Name: %s' % (organization.org_name.text) if organization.org_title: print ' Title: %s' % (organization.org_title.text) if organization.org_department: print ' Department: %s' % (organization.org_department.text) if organization.org_job_description: print ' Job Desc: %s' % (organization.org_job_description.text) def PrintPaginatedFeed(self, feed, print_method): """Print all pages of a paginated feed. This will iterate through a paginated feed, requesting each page and printing the entries contained therein. Args: feed: A gdata.contacts.ProfilesFeed instance. print_method: The method which will be used to print each page of the """ ctr = 0 while feed: # Print contents of current feed ctr = print_method(feed=feed, ctr=ctr) # Prepare for next feed iteration next = feed.GetNextLink() feed = None if next: if self.PromptOperationShouldContinue(): # Another feed is available, and the user has given us permission # to fetch it feed = self.gd_client.GetProfilesFeed(next.href) else: # User has asked us to terminate feed = None def PromptOperationShouldContinue(self): """Display a "Continue" prompt. This give is used to give users a chance to break out of a loop, just in case they have too many profiles/groups. Returns: A boolean value, True if the current operation should continue, False if the current operation should terminate. """ while True: key_input = raw_input('Continue [Y/n]? ') if key_input is 'N' or key_input is 'n': return False elif key_input is 'Y' or key_input is 'y' or key_input is '': return True def ListAllProfiles(self): """Retrieves a list of profiles and displays name and primary email.""" feed = self.gd_client.GetProfilesFeed() self.PrintPaginatedFeed(feed, self.PrintFeed) def SelectProfile(self): username = raw_input('Please enter your username for the profile: ') entry_uri = self.gd_client.GetFeedUri('profiles')+'/'+username try: entry = self.gd_client.GetProfile(entry_uri) self.PrintEntry(entry) except gdata.service.RequestError: print 'Invalid username for the profile.' def PrintMenu(self): """Displays a menu of options for the user to choose from.""" print ('\nProfiles Sample\n' '1) List all of your Profiles.\n' '2) Get a single Profile.\n' '3) Exit.\n') def GetMenuChoice(self, maximum): """Retrieves the menu selection from the user. Args: maximum: [int] The maximum number of allowed choices (inclusive) Returns: The integer of the menu item chosen by the user. """ while True: key_input = raw_input('> ') try: num = int(key_input) except ValueError: print 'Invalid choice. Please choose a value between 1 and', maximum continue if num > maximum or num < 1: print 'Invalid choice. Please choose a value between 1 and', maximum else: return num def Run(self): """Prompts the user to choose funtionality to be demonstrated.""" try: while True: self.PrintMenu() choice = self.GetMenuChoice(3) if choice == 1: self.ListAllProfiles() elif choice == 2: self.SelectProfile() elif choice == 3: return except KeyboardInterrupt: print '\nGoodbye.' return def main(): """Demonstrates use of the Profiles using the ProfilesSample object.""" # Parse command line options try: opts, args = getopt.getopt(sys.argv[1:], '', ['user=', 'pw=', 'domain=']) except getopt.error, msg: print 'python profiles_example.py --user [username] --pw [password]' print ' --domain [domain]' sys.exit(2) user = '' pw = '' domain = '' # Process options for option, arg in opts: if option == '--user': user = arg elif option == '--pw': pw = arg elif option == '--domain': domain = arg while not user: print 'NOTE: Please run these tests only with a test account.' user = raw_input('Please enter your email: ') while not pw: pw = getpass.getpass('Please enter password: ') if not pw: print 'Password cannot be blank.' while not domain: domain = raw_input('Please enter your Apps domain: ') try: sample = ProfilesSample(user, pw, domain) except gdata.service.BadAuthentication: print 'Invalid user credentials given.' return sample.Run() if __name__ == '__main__': main() gdata-2.0.18/samples/contacts/contacts_example.py0000750036466300116100000002762412156622362022137 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright (C) 2008 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. __author__ = 'api.jscudder (Jeffrey Scudder)' import sys import getopt import getpass import atom import gdata.contacts.data import gdata.contacts.client class ContactsSample(object): """ContactsSample object demonstrates operations with the Contacts feed.""" def __init__(self, email, password): """Constructor for the ContactsSample object. Takes an email and password corresponding to a gmail account to demonstrate the functionality of the Contacts feed. Args: email: [string] The e-mail address of the account to use for the sample. password: [string] The password corresponding to the account specified by the email parameter. Yields: A ContactsSample object used to run the sample demonstrating the functionality of the Contacts feed. """ self.gd_client = gdata.contacts.client.ContactsClient(source='GoogleInc-ContactsPythonSample-1') self.gd_client.ClientLogin(email, password, self.gd_client.source) def PrintFeed(self, feed, ctr=0): """Prints out the contents of a feed to the console. Args: feed: A gdata.contacts.ContactsFeed instance. ctr: [int] The number of entries in this feed previously printed. This allows continuous entry numbers when paging through a feed. Returns: The number of entries printed, including those previously printed as specified in ctr. This is for passing as an argument to ctr on successive calls to this method. """ if not feed.entry: print '\nNo entries in feed.\n' return 0 for i, entry in enumerate(feed.entry): print '\n%s %s' % (ctr+i+1, entry.title.text) if entry.content: print ' %s' % (entry.content.text) for email in entry.email: if email.primary and email.primary == 'true': print ' %s' % (email.address) # Show the contact groups that this contact is a member of. for group in entry.group_membership_info: print ' Member of group: %s' % (group.href) # Display extended properties. for extended_property in entry.extended_property: if extended_property.value: value = extended_property.value else: value = extended_property.GetXmlBlob() print ' Extended Property %s: %s' % (extended_property.name, value) return len(feed.entry) + ctr def PrintPaginatedFeed(self, feed, print_method): """ Print all pages of a paginated feed. This will iterate through a paginated feed, requesting each page and printing the entries contained therein. Args: feed: A gdata.contacts.ContactsFeed instance. print_method: The method which will be used to print each page of the feed. Must accept these two named arguments: feed: A gdata.contacts.ContactsFeed instance. ctr: [int] The number of entries in this feed previously printed. This allows continuous entry numbers when paging through a feed. """ ctr = 0 while feed: # Print contents of current feed ctr = print_method(feed=feed, ctr=ctr) # Prepare for next feed iteration next = feed.GetNextLink() feed = None if next: if self.PromptOperationShouldContinue(): # Another feed is available, and the user has given us permission # to fetch it feed = self.gd_client.GetContacts(uri=next.href) else: # User has asked us to terminate feed = None def PromptOperationShouldContinue(self): """ Display a "Continue" prompt. This give is used to give users a chance to break out of a loop, just in case they have too many contacts/groups. Returns: A boolean value, True if the current operation should continue, False if the current operation should terminate. """ while True: input = raw_input("Continue [Y/n]? ") if input is 'N' or input is 'n': return False elif input is 'Y' or input is 'y' or input is '': return True def ListAllContacts(self): """Retrieves a list of contacts and displays name and primary email.""" feed = self.gd_client.GetContacts() self.PrintPaginatedFeed(feed, self.PrintContactsFeed) def PrintGroupsFeed(self, feed, ctr): if not feed.entry: print '\nNo groups in feed.\n' return 0 for i, entry in enumerate(feed.entry): print '\n%s %s' % (ctr+i+1, entry.title.text) if entry.content: print ' %s' % (entry.content.text) # Display the group id which can be used to query the contacts feed. print ' Group ID: %s' % entry.id.text # Display extended properties. for extended_property in entry.extended_property: if extended_property.value: value = extended_property.value else: value = extended_property.GetXmlBlob() print ' Extended Property %s: %s' % (extended_property.name, value) return len(feed.entry) + ctr def PrintContactsFeed(self, feed, ctr): if not feed.entry: print '\nNo contacts in feed.\n' return 0 for i, entry in enumerate(feed.entry): if not entry.name is None: family_name = entry.name.family_name is None and " " or entry.name.family_name.text full_name = entry.name.full_name is None and " " or entry.name.full_name.text given_name = entry.name.given_name is None and " " or entry.name.given_name.text print '\n%s %s: %s - %s' % (ctr+i+1, full_name, given_name, family_name) else: print '\n%s %s (title)' % (ctr+i+1, entry.title.text) if entry.content: print ' %s' % (entry.content.text) for p in entry.structured_postal_address: print ' %s' % (p.formatted_address.text) # Display the group id which can be used to query the contacts feed. print ' Group ID: %s' % entry.id.text # Display extended properties. for extended_property in entry.extended_property: if extended_property.value: value = extended_property.value else: value = extended_property.GetXmlBlob() print ' Extended Property %s: %s' % (extended_property.name, value) for user_defined_field in entry.user_defined_field: print ' User Defined Field %s: %s' % (user_defined_field.key, user_defined_field.value) return len(feed.entry) + ctr def ListAllGroups(self): feed = self.gd_client.GetGroups() self.PrintPaginatedFeed(feed, self.PrintGroupsFeed) def CreateMenu(self): """Prompts that enable a user to create a contact.""" name = raw_input('Enter contact\'s name: ') notes = raw_input('Enter notes for contact: ') primary_email = raw_input('Enter primary email address: ') new_contact = gdata.contacts.data.ContactEntry(name=gdata.data.Name(full_name=gdata.data.FullName(text=name))) new_contact.content = atom.data.Content(text=notes) # Create a work email address for the contact and use as primary. new_contact.email.append(gdata.data.Email(address=primary_email, primary='true', rel=gdata.data.WORK_REL)) entry = self.gd_client.CreateContact(new_contact) if entry: print 'Creation successful!' print 'ID for the new contact:', entry.id.text else: print 'Upload error.' def QueryMenu(self): """Prompts for updated-min query parameters and displays results.""" updated_min = raw_input( 'Enter updated min (example: 2007-03-16T00:00:00): ') query = gdata.contacts.client.ContactsQuery() query.updated_min = updated_min feed = self.gd_client.GetContacts(q=query) self.PrintFeed(feed) def QueryGroupsMenu(self): """Prompts for updated-min query parameters and displays results.""" updated_min = raw_input( 'Enter updated min (example: 2007-03-16T00:00:00): ') query = gdata.contacts.client.ContactsQuery(feed='/m8/feeds/groups/default/full') query.updated_min = updated_min feed = self.gd_client.GetGroups(q=query) self.PrintGroupsFeed(feed, 0) def _SelectContact(self): feed = self.gd_client.GetContacts() self.PrintFeed(feed) selection = 5000 while selection > len(feed.entry)+1 or selection < 1: selection = int(raw_input( 'Enter the number for the contact you would like to modify: ')) return feed.entry[selection-1] def UpdateContactMenu(self): selected_entry = self._SelectContact() new_name = raw_input('Enter a new name for the contact: ') if not selected_entry.name: selected_entry.name = gdata.data.Name() selected_entry.name.full_name = gdata.data.FullName(text=new_name) self.gd_client.Update(selected_entry) def DeleteContactMenu(self): selected_entry = self._SelectContact() self.gd_client.Delete(selected_entry) def PrintMenu(self): """Displays a menu of options for the user to choose from.""" print ('\nContacts Sample\n' '1) List all of your contacts.\n' '2) Create a contact.\n' '3) Query contacts on updated time.\n' '4) Modify a contact.\n' '5) Delete a contact.\n' '6) List all of your contact groups.\n' '7) Query your groups on updated time.\n' '8) Exit.\n') def GetMenuChoice(self, max): """Retrieves the menu selection from the user. Args: max: [int] The maximum number of allowed choices (inclusive) Returns: The integer of the menu item chosen by the user. """ while True: input = raw_input('> ') try: num = int(input) except ValueError: print 'Invalid choice. Please choose a value between 1 and', max continue if num > max or num < 1: print 'Invalid choice. Please choose a value between 1 and', max else: return num def Run(self): """Prompts the user to choose funtionality to be demonstrated.""" try: while True: self.PrintMenu() choice = self.GetMenuChoice(8) if choice == 1: self.ListAllContacts() elif choice == 2: self.CreateMenu() elif choice == 3: self.QueryMenu() elif choice == 4: self.UpdateContactMenu() elif choice == 5: self.DeleteContactMenu() elif choice == 6: self.ListAllGroups() elif choice == 7: self.QueryGroupsMenu() elif choice == 8: return except KeyboardInterrupt: print '\nGoodbye.' return def main(): """Demonstrates use of the Contacts extension using the ContactsSample object.""" # Parse command line options try: opts, args = getopt.getopt(sys.argv[1:], '', ['user=', 'pw=']) except getopt.error, msg: print 'python contacts_example.py --user [username] --pw [password]' sys.exit(2) user = '' pw = '' # Process options for option, arg in opts: if option == '--user': user = arg elif option == '--pw': pw = arg while not user: print 'NOTE: Please run these tests only with a test account.' user = raw_input('Please enter your username: ') while not pw: pw = getpass.getpass() if not pw: print 'Password cannot be blank.' try: sample = ContactsSample(user, pw) except gdata.client.BadAuthentication: print 'Invalid user credentials given.' return sample.Run() if __name__ == '__main__': main() gdata-2.0.18/samples/authsub/0000750036466300116100000000000012156625015016051 5ustar afshareng00000000000000gdata-2.0.18/samples/authsub/secure_authsub.py0000750036466300116100000001462612156622362021462 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright 2008 Google Inc. All Rights Reserved. # # 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. """Sample to demonstrate using secure AuthSub in the Google Data Python client. This sample focuses on the Google Health Data API because it requires the use of secure tokens. This samples makes queries against the H9 Developer's Sandbox (https://www.google.com/h9). To run this sample: 1.) Use Apache's mod_python 2.) Run from your local webserver (e.g. http://localhost/...) 3.) You need to have entered medication data into H9 HealthAubSubHelper: Class to handle secure AuthSub tokens. GetMedicationHTML: Returns the user's medication formatted in HTML. index: Main entry point for the web app. """ __author__ = 'e.bidelman@google.com (Eric Bidelman)' import os import sys import urllib import gdata.auth import gdata.service H9_PROFILE_FEED_URL = 'https://www.google.com/h9/feeds/profile/default' class HealthAuthSubHelper(object): """A secure AuthSub helper to interact with the Google Health Data API""" H9_AUTHSUB_HANDLER = 'https://www.google.com/h9/authsub' H9_SCOPE = 'https://www.google.com/h9/feeds/' def GetNextUrl(self, req): """Computes the current URL the web app is running from. Args: req: mod_python mp_request instance to build the URL from. Returns: A string representing the web app's URL. """ if req.is_https(): next_url = 'https://' else: next_url = 'http://' next_url += req.hostname + req.unparsed_uri return next_url def GenerateAuthSubRequestUrl(self, next, scopes=[H9_SCOPE], secure=True, session=True, extra_params=None, include_scopes_in_next=True): """Constructs the URL to the AuthSub token handler. Args: next: string The URL AuthSub will redirect back to. Use self.GetNextUrl() to return that URL. scopes: (optional) string or list of scopes the token will be valid for. secure: (optional) boolean True if the token should be a secure one session: (optional) boolean True if the token will be exchanged for a session token. extra_params: (optional) dict of additional parameters to pass to AuthSub. include_scopes_in_next: (optional) boolean True if the scopes in the scopes should be passed to AuthSub. Returns: A string (as a URL) to use for the AuthSubRequest endpoint. """ auth_sub_url = gdata.service.GenerateAuthSubRequestUrl( next, scopes, hd='default', secure=secure, session=session, request_url=self.H9_AUTHSUB_HANDLER, include_scopes_in_next=include_scopes_in_next) if extra_params: auth_sub_url = '%s&%s' % (auth_sub_url, urllib.urlencode(extra_params)) return auth_sub_url def SetPrivateKey(self, filename): """Reads the private key from the specified file. See http://code.google.com/apis/gdata/authsub.html#Registered for\ information on how to create a RSA private key/public cert pair. Args: filename: string .pem file the key is stored in. Returns: The private key as a string. Raises: IOError: The file could not be read or does not exist. """ try: f = open(filename) rsa_private_key = f.read() f.close() except IOError, (errno, strerror): raise 'I/O error(%s): %s' % (errno, strerror) self.rsa_key = rsa_private_key return rsa_private_key def GetMedicationHTML(feed): """Prints out the user's medication to the console. Args: feed: A gdata.GDataFeed instance. Returns: An HTML formatted string containing the user's medication data. """ if not feed.entry: return 'No entries in feed
' html = [] for entry in feed.entry: try: ccr = entry.FindExtensions('ContinuityOfCareRecord')[0] body = ccr.FindChildren('Body')[0] meds = body.FindChildren('Medications')[0].FindChildren('Medication') for med in meds: name = med.FindChildren('Product')[0].FindChildren('ProductName')[0] html.append('

  • %s
  • ' % name.FindChildren('Text')[0].text) except: html.append('No medication data in this profile
    ') return '' % ''.join(html) def index(req): req.content_type = 'text/html' authsub = HealthAuthSubHelper() client = gdata.service.GDataService(service='weaver') current_url = authsub.GetNextUrl(req) rsa_key = authsub.SetPrivateKey('/path/to/yourRSAPrivateKey.pem') # Strip token query parameter's value from URL if it exists token = gdata.auth.extract_auth_sub_token_from_url(current_url, rsa_key=rsa_key) if not token: """STEP 1: No single use token in the URL or a saved session token. Generate the AuthSub URL to fetch a single use token.""" params = {'permission': 1} authsub_url = authsub.GenerateAuthSubRequestUrl(current_url, extra_params=params) req.write('Link your Google Health Profile' % authsub_url) else: """STEP 2: A single use token was extracted from the URL. Upgrade the one time token to a session token.""" req.write('Single use token: %s
    ' % str(token)) client.UpgradeToSessionToken(token) # calls gdata.service.SetAuthSubToken() """STEP 3: Done with AuthSub :) Save the token for subsequent requests. Query the Health Data API""" req.write('Token info: %s
    ' % client.AuthSubTokenInfo()) req.write('Session token: %s
    ' % client.GetAuthSubToken()) # Query the Health Data API params = {'digest': 'true', 'strict': 'true'} uri = '%s?%s' % (H9_PROFILE_FEED_URL, urllib.urlencode(params)) feed = client.GetFeed(uri) req.write('

    Listing medications

    ') req.write(GetMedicationHTML(feed)) """STEP 4: Revoke the session token.""" req.write('Revoked session token') client.RevokeAuthSubToken() gdata-2.0.18/samples/sites/0000750036466300116100000000000012156625015015525 5ustar afshareng00000000000000gdata-2.0.18/samples/sites/sites_example.py0000750036466300116100000003460012156622362020751 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright 2009 Google Inc. All Rights Reserved. # # 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. __author__ = 'e.bidelman (Eric Bidelman)' import getopt import mimetypes import os.path import sys import gdata.sample_util import gdata.sites.client import gdata.sites.data SOURCE_APP_NAME = 'googleInc-GoogleSitesAPIPythonLibSample-v1.1' MAIN_MENU = ['1) List site content', '2) List recent activity', '3) List revision history', '4) Create webpage', '5) Create web attachment', '6) Upload attachment', '7) Download attachment', '8) Delete item', '9) List sites', '10) Create a new site', "11) List site's sharing permissions", '12) Change settings', '13) Exit'] SETTINGS_MENU = ['1) Change current site.', '2) Change domain.'] class SitesExample(object): """Wrapper around the Sites API functionality.""" def __init__(self, site_name=None, site_domain=None, debug=False): if site_domain is None: site_domain = self.PromptDomain() if site_name is None: site_name = self.PromptSiteName() mimetypes.init() self.client = gdata.sites.client.SitesClient( source=SOURCE_APP_NAME, site=site_name, domain=site_domain) self.client.http_client.debug = debug try: gdata.sample_util.authorize_client( self.client, service=self.client.auth_service, source=SOURCE_APP_NAME, scopes=['http://sites.google.com/feeds/', 'https://sites.google.com/feeds/']) except gdata.client.BadAuthentication: exit('Invalid user credentials given.') except gdata.client.Error: exit('Login Error') def PrintMainMenu(self): """Displays a menu of options for the user to choose from.""" print '\nSites API Sample' print '================================' print '\n'.join(MAIN_MENU) print '================================\n' def PrintSettingsMenu(self): """Displays a menu of settings for the user change.""" print '\nSites API Sample > Settings' print '================================' print '\n'.join(SETTINGS_MENU) print '================================\n' def GetMenuChoice(self, menu): """Retrieves the menu selection from the user. Args: menu: list The menu to get a selection from. Returns: The integer of the menu item chosen by the user. """ max_choice = len(menu) while True: user_input = raw_input(': ') try: num = int(user_input) except ValueError: continue if num <= max_choice and num > 0: return num def PromptSiteName(self): site_name = '' while not site_name: site_name = raw_input('site name: ') if not site_name: print 'Please enter the name of your Google Site.' return site_name def PromptDomain(self): return raw_input(('If your Site is hosted on a Google Apps domain, ' 'enter it (e.g. example.com): ')) or 'site' def GetChoiceSelection(self, feed, message): for i, entry in enumerate(feed.entry): print '%d.) %s' % (i + 1, entry.title.text) choice = 0 while not choice or not 0 <= choice <= len(feed.entry): choice = int(raw_input(message)) print return choice def PrintEntry(self, entry): print '%s [%s]' % (entry.title.text, entry.Kind()) if entry.page_name: print ' page name:\t%s' % entry.page_name.text if entry.content: print ' content\t%s...' % str(entry.content.html)[0:100] def PrintListItem(self, entry): print '%s [%s]' % (entry.title.text, entry.Kind()) for col in entry.field: print ' %s %s\t%s' % (col.index, col.name, col.text) def PrintListPage(self, entry): print '%s [%s]' % (entry.title.text, entry.Kind()) for col in entry.data.column: print ' %s %s' % (col.index, col.name) def PrintFileCabinetPage(self, entry): print '%s [%s]' % (entry.title.text, entry.Kind()) print ' page name:\t%s' % entry.page_name.text print ' content\t%s...' % str(entry.content.html)[0:100] def PrintAttachment(self, entry): print '%s [%s]' % (entry.title.text, entry.Kind()) if entry.summary is not None: print ' description:\t%s' % entry.summary.text print ' content\t%s, %s' % (entry.content.type, entry.content.src) def PrintWebAttachment(self, entry): print '%s [%s]' % (entry.title.text, entry.Kind()) if entry.summary.text is not None: print ' description:\t%s' % entry.summary.text print ' content src\t%s' % entry.content.src def Run(self): """Executes the demo application.""" try: while True: self.PrintMainMenu() choice = self.GetMenuChoice(MAIN_MENU) if choice == 1: kind_choice = raw_input('What kind (all|%s)?: ' % '|'.join( gdata.sites.data.SUPPORT_KINDS)) if kind_choice in gdata.sites.data.SUPPORT_KINDS: uri = '%s?kind=%s' % (self.client.make_content_feed_uri(), kind_choice) feed = self.client.GetContentFeed(uri=uri) else: feed = self.client.GetContentFeed() print "\nFetching content feed of '%s'...\n" % self.client.site for entry in feed.entry: kind = entry.Kind() if kind == 'attachment': self.PrintAttachment(entry) elif kind == 'webattachment': self.PrintWebAttachment(entry) elif kind == 'filecabinet': self.PrintFileCabinetPage(entry) elif kind == 'listitem': self.PrintListItem(entry) elif kind == 'listpage': self.PrintListPage(entry) else: self.PrintEntry(entry) print ' revision:\t%s' % entry.revision.text print ' updated:\t%s' % entry.updated.text parent_link = entry.FindParentLink() if parent_link: print ' parent link:\t%s' % parent_link if entry.GetAlternateLink(): print ' view in Sites:\t%s' % entry.GetAlternateLink().href if entry.feed_link: print ' feed of items:\t%s' % entry.feed_link.href if entry.IsDeleted(): print ' deleted:\t%s' % entry.IsDeleted() if entry.in_reply_to: print ' in reply to:\t%s' % entry.in_reply_to.href print elif choice == 2: print "\nFetching activity feed of '%s'..." % self.client.site feed = self.client.GetActivityFeed() for entry in feed.entry: print ' %s [%s on %s]' % (entry.title.text, entry.Kind(), entry.updated.text) elif choice == 3: print "\nFetching content feed of '%s'...\n" % self.client.site feed = self.client.GetContentFeed() try: selection = self.GetChoiceSelection( feed, 'Select a page to fetch revisions for: ') except TypeError: continue except ValueError: continue feed = self.client.GetRevisionFeed( feed.entry[selection - 1].GetNodeId()) for entry in feed.entry: print entry.title.text print ' new version on:\t%s' % entry.updated.text print ' view changes:\t%s' % entry.GetAlternateLink().href print ' current version:\t%s...' % str(entry.content.html)[0:100] print elif choice == 4: print "\nFetching content feed of '%s'...\n" % self.client.site feed = self.client.GetContentFeed() try: selection = self.GetChoiceSelection( feed, 'Select a parent to upload to (or hit ENTER for none): ') except ValueError: selection = None page_title = raw_input('Enter a page title: ') parent = None if selection is not None: parent = feed.entry[selection - 1] new_entry = self.client.CreatePage( 'webpage', page_title, 'Your html content', parent=parent) if new_entry.GetAlternateLink(): print 'Created. View it at: %s' % new_entry.GetAlternateLink().href elif choice == 5: print "\nFetching filecabinets on '%s'...\n" % self.client.site uri = '%s?kind=%s' % (self.client.make_content_feed_uri(), 'filecabinet') feed = self.client.GetContentFeed(uri=uri) selection = self.GetChoiceSelection( feed, 'Select a filecabinet to create the web attachment on: ') url = raw_input('Enter the URL of the attachment: ') content_type = raw_input("Enter the attachment's mime type: ") title = raw_input('Enter a title for the web attachment: ') description = raw_input('Enter a description: ') parent_entry = None if selection is not None: parent_entry = feed.entry[selection - 1] self.client.CreateWebAttachment(url, content_type, title, parent_entry, description=description) print 'Created!' elif choice == 6: print "\nFetching filecainets on '%s'...\n" % self.client.site uri = '%s?kind=%s' % (self.client.make_content_feed_uri(), 'filecabinet') feed = self.client.GetContentFeed(uri=uri) selection = self.GetChoiceSelection( feed, 'Select a filecabinet to upload to: ') filepath = raw_input('Enter a filename: ') page_title = raw_input('Enter a title for the file: ') description = raw_input('Enter a description: ') filename = os.path.basename(filepath) file_ex = filename[filename.rfind('.'):] if not file_ex in mimetypes.types_map: content_type = raw_input( 'Unrecognized file extension. Please enter the mime type: ') else: content_type = mimetypes.types_map[file_ex] entry = None if selection is not None: entry = feed.entry[selection - 1] new_entry = self.client.UploadAttachment( filepath, entry, content_type=content_type, title=page_title, description=description) print 'Uploaded. View it at: %s' % new_entry.GetAlternateLink().href elif choice == 7: print "\nFetching all attachments on '%s'...\n" % self.client.site uri = '%s?kind=%s' % (self.client.make_content_feed_uri(), 'attachment') feed = self.client.GetContentFeed(uri=uri) selection = self.GetChoiceSelection( feed, 'Select an attachment to download: ') filepath = raw_input('Save as: ') entry = None if selection is not None: entry = feed.entry[selection - 1] self.client.DownloadAttachment(entry, filepath) print 'Downloaded.' elif choice == 8: print "\nFetching content feed of '%s'...\n" % self.client.site feed = self.client.GetContentFeed() selection = self.GetChoiceSelection(feed, 'Select a page to delete: ') entry = None if selection is not None: entry = feed.entry[selection - 1] self.client.Delete(entry) print 'Removed!' elif choice == 9: print ('\nFetching your list of sites for domain: %s...\n' % self.client.domain) feed = self.client.GetSiteFeed() for entry in feed.entry: print entry.title.text print ' site name: ' + entry.site_name.text if entry.summary.text: print ' summary: ' + entry.summary.text if entry.FindSourceLink(): print ' copied from site: ' + entry.FindSourceLink() print ' acl feed: %s\n' % entry.FindAclLink() elif choice == 10: title = raw_input('Enter a title: ') summary = raw_input('Enter a description: ') theme = raw_input('Theme name (ex. "default"): ') new_entry = self.client.CreateSite( title, description=summary, theme=theme) print 'Site created! View it at: ' + new_entry.GetAlternateLink().href elif choice == 11: print "\nFetching acl permissions of '%s'...\n" % self.client.site feed = self.client.GetAclFeed() for entry in feed.entry: print '%s (%s) - %s' % (entry.scope.value, entry.scope.type, entry.role.value) elif choice == 12: self.PrintSettingsMenu() settings_choice = self.GetMenuChoice(SETTINGS_MENU) if settings_choice == 1: self.client.site = self.PromptSiteName() elif settings_choice == 2: self.client.domain = self.PromptDomain() elif choice == 13: print 'Later!\n' return except gdata.client.RequestError, error: print error except KeyboardInterrupt: return def main(): """The main function runs the SitesExample application.""" print 'NOTE: Please run these tests only with a test account.\n' try: opts, args = getopt.getopt(sys.argv[1:], '', ['site=', 'domain=', 'debug']) except getopt.error, msg: print """python sites_sample.py --site [sitename] --domain [domain or "site"] --debug [prints debug info if set]""" sys.exit(2) site = None domain = None debug = False for option, arg in opts: if option == '--site': site = arg elif option == '--domain': domain = arg elif option == '--debug': debug = True sample = SitesExample(site, domain, debug=debug) sample.Run() if __name__ == '__main__': main() gdata-2.0.18/samples/apps/0000750036466300116100000000000012156625015015341 5ustar afshareng00000000000000gdata-2.0.18/samples/apps/list_group_members.py0000640036466300116100000000773512156622362021633 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright 2012 Google Inc. All Rights Reserved. # # 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. """Sample to list all the members of a group. The sample demonstrates how to get all members from a group (say Group1) including members from groups that are members of Group1. It recursively finds the members of groups that are the member of given group and lists them. """ __author__ = 'Shraddha Gupta ' from optparse import OptionParser import gdata.apps.groups.client SCOPE = 'https://apps-apis.google.com/a/feeds/groups/' USER_AGENT = 'ListUserMembersFromGroup' def GetAuthToken(client_id, client_secret): """Get an OAuth 2.0 access token using the given client_id and client_secret. Client_id and client_secret can be found on the API Access tab on the Google APIs Console . Args: client_id: String, client id of the developer. client_secret: String, client secret of the developer. Returns: token: String, authorized OAuth 2.0 token. """ token = gdata.gauth.OAuth2Token( client_id=client_id, client_secret=client_secret, scope=SCOPE, user_agent=USER_AGENT) uri = token.generate_authorize_url() print 'Please visit this URL to authorize the application:' print uri # Get the verification code from the standard input. code = raw_input('What is the verification code? ').strip() token.get_access_token(code) return token def ListAllMembers(group_client, group_id): """Lists all members including members of sub-groups. Args: group_client: gdata.apps.groups.client.GroupsProvisioningClient instance. group_id: String, identifier of the group from which members are listed. Returns: members_list: List containing the member_id of group members. """ members_list = [] try: group_members = group_client.RetrieveAllMembers(group_id) for member in group_members.entry: if member.member_type == 'Group': temp_list = ListAllMembers(group_client, member.member_id) members_list.extend(temp_list) else: members_list.append(member.member_id) except gdata.client.RequestError, e: print 'Request error: %s %s %s' % (e.status, e.reason, e.body) return members_list def main(): """Demonstrates retrieval of members of a group.""" usage = ('Usage: %prog --DOMAIN --CLIENT_ID ' '--CLIENT_SECRET --GROUP_ID ') parser = OptionParser(usage=usage) parser.add_option('--DOMAIN', help='Google Apps Domain, e.g. "domain.com".') parser.add_option('--CLIENT_ID', help='Registered CLIENT_ID of Domain.') parser.add_option('--CLIENT_SECRET', help='Registered CLIENT_SECRET of Domain.') parser.add_option('--GROUP_ID', help='Group identifier of the group to list members from.') (options, args) = parser.parse_args() if not (options.DOMAIN and options.CLIENT_ID and options.CLIENT_SECRET and options.GROUP_ID): parser.print_help() return group_client = gdata.apps.groups.client.GroupsProvisioningClient( domain=options.DOMAIN) token = GetAuthToken(options.CLIENT_ID, options.CLIENT_SECRET) token.authorize(group_client) members_list = ListAllMembers(group_client, options.GROUP_ID) no_duplicate_members_list = list(set(members_list)) print 'User members of the group %s are: ' % options.GROUP_ID print no_duplicate_members_list if __name__ == '__main__': main() gdata-2.0.18/samples/apps/marketplace_sample/0000750036466300116100000000000012156625015021172 5ustar afshareng00000000000000gdata-2.0.18/samples/apps/marketplace_sample/js/0000750036466300116100000000000012156625015021606 5ustar afshareng00000000000000gdata-2.0.18/samples/apps/marketplace_sample/js/json2.js0000640036466300116100000004200512156622362023203 0ustar afshareng00000000000000/* http://www.JSON.org/json2.js 2011-02-23 Public Domain. NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK. See http://www.JSON.org/js.html This code should be minified before deployment. See http://javascript.crockford.com/jsmin.html USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO NOT CONTROL. This file creates a global JSON object containing two methods: stringify and parse. JSON.stringify(value, replacer, space) value any JavaScript value, usually an object or array. replacer an optional parameter that determines how object values are stringified for objects. It can be a function or an array of strings. space an optional parameter that specifies the indentation of nested structures. If it is omitted, the text will be packed without extra whitespace. If it is a number, it will specify the number of spaces to indent at each level. If it is a string (such as '\t' or ' '), it contains the characters used to indent at each level. This method produces a JSON text from a JavaScript value. When an object value is found, if the object contains a toJSON method, its toJSON method will be called and the result will be stringified. A toJSON method does not serialize: it returns the value represented by the name/value pair that should be serialized, or undefined if nothing should be serialized. The toJSON method will be passed the key associated with the value, and this will be bound to the value For example, this would serialize Dates as ISO strings. Date.prototype.toJSON = function (key) { function f(n) { // Format integers to have at least two digits. return n < 10 ? '0' + n : n; } return this.getUTCFullYear() + '-' + f(this.getUTCMonth() + 1) + '-' + f(this.getUTCDate()) + 'T' + f(this.getUTCHours()) + ':' + f(this.getUTCMinutes()) + ':' + f(this.getUTCSeconds()) + 'Z'; }; You can provide an optional replacer method. It will be passed the key and value of each member, with this bound to the containing object. The value that is returned from your method will be serialized. If your method returns undefined, then the member will be excluded from the serialization. If the replacer parameter is an array of strings, then it will be used to select the members to be serialized. It filters the results such that only members with keys listed in the replacer array are stringified. Values that do not have JSON representations, such as undefined or functions, will not be serialized. Such values in objects will be dropped; in arrays they will be replaced with null. You can use a replacer function to replace those with JSON values. JSON.stringify(undefined) returns undefined. The optional space parameter produces a stringification of the value that is filled with line breaks and indentation to make it easier to read. If the space parameter is a non-empty string, then that string will be used for indentation. If the space parameter is a number, then the indentation will be that many spaces. Example: text = JSON.stringify(['e', {pluribus: 'unum'}]); // text is '["e",{"pluribus":"unum"}]' text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t'); // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]' text = JSON.stringify([new Date()], function (key, value) { return this[key] instanceof Date ? 'Date(' + this[key] + ')' : value; }); // text is '["Date(---current time---)"]' JSON.parse(text, reviver) This method parses a JSON text to produce an object or array. It can throw a SyntaxError exception. The optional reviver parameter is a function that can filter and transform the results. It receives each of the keys and values, and its return value is used instead of the original value. If it returns what it received, then the structure is not modified. If it returns undefined then the member is deleted. Example: // Parse the text. Values that look like ISO date strings will // be converted to Date objects. myData = JSON.parse(text, function (key, value) { var a; if (typeof value === 'string') { a = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value); if (a) { return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4], +a[5], +a[6])); } } return value; }); myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) { var d; if (typeof value === 'string' && value.slice(0, 5) === 'Date(' && value.slice(-1) === ')') { d = new Date(value.slice(5, -1)); if (d) { return d; } } return value; }); This is a reference implementation. You are free to copy, modify, or redistribute. */ /*jslint evil: true, strict: false, regexp: false */ /*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply, call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours, getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join, lastIndex, length, parse, prototype, push, replace, slice, stringify, test, toJSON, toString, valueOf */ // Create a JSON object only if one does not already exist. We create the // methods in a closure to avoid creating global variables. var JSON; if (!JSON) { JSON = {}; } (function () { "use strict"; function f(n) { // Format integers to have at least two digits. return n < 10 ? '0' + n : n; } if (typeof Date.prototype.toJSON !== 'function') { Date.prototype.toJSON = function (key) { return isFinite(this.valueOf()) ? this.getUTCFullYear() + '-' + f(this.getUTCMonth() + 1) + '-' + f(this.getUTCDate()) + 'T' + f(this.getUTCHours()) + ':' + f(this.getUTCMinutes()) + ':' + f(this.getUTCSeconds()) + 'Z' : null; }; String.prototype.toJSON = Number.prototype.toJSON = Boolean.prototype.toJSON = function (key) { return this.valueOf(); }; } var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, gap, indent, meta = { // table of character substitutions '\b': '\\b', '\t': '\\t', '\n': '\\n', '\f': '\\f', '\r': '\\r', '"' : '\\"', '\\': '\\\\' }, rep; function quote(string) { // If the string contains no control characters, no quote characters, and no // backslash characters, then we can safely slap some quotes around it. // Otherwise we must also replace the offending characters with safe escape // sequences. escapable.lastIndex = 0; return escapable.test(string) ? '"' + string.replace(escapable, function (a) { var c = meta[a]; return typeof c === 'string' ? c : '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); }) + '"' : '"' + string + '"'; } function str(key, holder) { // Produce a string from holder[key]. var i, // The loop counter. k, // The member key. v, // The member value. length, mind = gap, partial, value = holder[key]; // If the value has a toJSON method, call it to obtain a replacement value. if (value && typeof value === 'object' && typeof value.toJSON === 'function') { value = value.toJSON(key); } // If we were called with a replacer function, then call the replacer to // obtain a replacement value. if (typeof rep === 'function') { value = rep.call(holder, key, value); } // What happens next depends on the value's type. switch (typeof value) { case 'string': return quote(value); case 'number': // JSON numbers must be finite. Encode non-finite numbers as null. return isFinite(value) ? String(value) : 'null'; case 'boolean': case 'null': // If the value is a boolean or null, convert it to a string. Note: // typeof null does not produce 'null'. The case is included here in // the remote chance that this gets fixed someday. return String(value); // If the type is 'object', we might be dealing with an object or an array or // null. case 'object': // Due to a specification blunder in ECMAScript, typeof null is 'object', // so watch out for that case. if (!value) { return 'null'; } // Make an array to hold the partial results of stringifying this object value. gap += indent; partial = []; // Is the value an array? if (Object.prototype.toString.apply(value) === '[object Array]') { // The value is an array. Stringify every element. Use null as a placeholder // for non-JSON values. length = value.length; for (i = 0; i < length; i += 1) { partial[i] = str(i, value) || 'null'; } // Join all of the elements together, separated with commas, and wrap them in // brackets. v = partial.length === 0 ? '[]' : gap ? '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']' : '[' + partial.join(',') + ']'; gap = mind; return v; } // If the replacer is an array, use it to select the members to be stringified. if (rep && typeof rep === 'object') { length = rep.length; for (i = 0; i < length; i += 1) { if (typeof rep[i] === 'string') { k = rep[i]; v = str(k, value); if (v) { partial.push(quote(k) + (gap ? ': ' : ':') + v); } } } } else { // Otherwise, iterate through all of the keys in the object. for (k in value) { if (Object.prototype.hasOwnProperty.call(value, k)) { v = str(k, value); if (v) { partial.push(quote(k) + (gap ? ': ' : ':') + v); } } } } // Join all of the member texts together, separated with commas, // and wrap them in braces. v = partial.length === 0 ? '{}' : gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}' : '{' + partial.join(',') + '}'; gap = mind; return v; } } // If the JSON object does not yet have a stringify method, give it one. if (typeof JSON.stringify !== 'function') { JSON.stringify = function (value, replacer, space) { // The stringify method takes a value and an optional replacer, and an optional // space parameter, and returns a JSON text. The replacer can be a function // that can replace values, or an array of strings that will select the keys. // A default replacer method can be provided. Use of the space parameter can // produce text that is more easily readable. var i; gap = ''; indent = ''; // If the space parameter is a number, make an indent string containing that // many spaces. if (typeof space === 'number') { for (i = 0; i < space; i += 1) { indent += ' '; } // If the space parameter is a string, it will be used as the indent string. } else if (typeof space === 'string') { indent = space; } // If there is a replacer, it must be a function or an array. // Otherwise, throw an error. rep = replacer; if (replacer && typeof replacer !== 'function' && (typeof replacer !== 'object' || typeof replacer.length !== 'number')) { throw new Error('JSON.stringify'); } // Make a fake root object containing our value under the key of ''. // Return the result of stringifying the value. return str('', {'': value}); }; } // If the JSON object does not yet have a parse method, give it one. if (typeof JSON.parse !== 'function') { JSON.parse = function (text, reviver) { // The parse method takes a text and an optional reviver function, and returns // a JavaScript value if the text is a valid JSON text. var j; function walk(holder, key) { // The walk method is used to recursively walk the resulting structure so // that modifications can be made. var k, v, value = holder[key]; if (value && typeof value === 'object') { for (k in value) { if (Object.prototype.hasOwnProperty.call(value, k)) { v = walk(value, k); if (v !== undefined) { value[k] = v; } else { delete value[k]; } } } } return reviver.call(holder, key, value); } // Parsing happens in four stages. In the first stage, we replace certain // Unicode characters with escape sequences. JavaScript handles many characters // incorrectly, either silently deleting them, or treating them as line endings. text = String(text); cx.lastIndex = 0; if (cx.test(text)) { text = text.replace(cx, function (a) { return '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); }); } // In the second stage, we run the text against regular expressions that look // for non-JSON patterns. We are especially concerned with '()' and 'new' // because they can cause invocation, and '=' because it can cause mutation. // But just to be safe, we want to reject all unexpected forms. // We split the second stage into 4 regexp operations in order to work around // crippling inefficiencies in IE's and Safari's regexp engines. First we // replace the JSON backslash pairs with '@' (a non-JSON character). Second, we // replace all simple value tokens with ']' characters. Third, we delete all // open brackets that follow a colon or comma or that begin the text. Finally, // we look to see that the remaining characters are only whitespace or ']' or // ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval. if (/^[\],:{}\s]*$/ .test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@') .replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']') .replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) { // In the third stage we use the eval function to compile the text into a // JavaScript structure. The '{' operator is subject to a syntactic ambiguity // in JavaScript: it can begin a block or an object literal. We wrap the text // in parens to eliminate the ambiguity. j = eval('(' + text + ')'); // In the optional fourth stage, we recursively walk the new structure, passing // each name/value pair to a reviver function for possible transformation. return typeof reviver === 'function' ? walk({'': j}, '') : j; } // If the text is not JSON parseable, then a SyntaxError is thrown. throw new SyntaxError('JSON.parse'); }; } }()); gdata-2.0.18/samples/apps/marketplace_sample/js/main.js0000640036466300116100000000560412156622362023100 0ustar afshareng00000000000000/* Copyright 2012 Google Inc. All Rights Reserved. * * @fileoverview This file first retrieves a list of all of the domain users * using an ajax call to the server. It also retrieves the user details for a * particular user again using an ajax call to the server. * * @author gunjansharma@google.com (Gunjan Sharma) */ $(document).ready(function() { $('#get_details').attr('disabled', true); $.ajax({ type: 'POST', url: '../', dataType: 'text', error: function(request, textStatus) { alert('There was a problem loading data from the server.'); }, success: function(data, textStatus, request) { var users = jQuery.parseJSON(data); for (var i = 0; i < users.length; i++) { $('#users_list') .append($('') .attr('value', i) .text(users[i])); } $('#get_details').attr('disabled', false); } }); $('#get_details').click(function() { this.disabled = true; var username = $('#users_list option:selected').text(); request_url = '../getdetails/' + username; $.ajax({ type: 'GET', url: request_url, dataType: 'text', error: function(request, textStatus) { alert('There was a problem loading data from the server.'); $('#get_details').attr('disabled', false); }, success: function(data, textStatus, request) { var details = jQuery.parseJSON(data); var groups = details['groups']; var html = '{0}{1}{2}'; var groups_html = 'NameID'; for (var i = 0; i < groups.length; i++) { group = groups[i]; groups_html += ('' + group['name'] + '' + group['id'] + ''); } groups_html = '' + groups_html + '
    '; var orgunit = details['orgunit']; var orgunit_html = 'Name: ' + orgunit['name'] + '
    Path: ' + orgunit['path']; var nicknames = details['nicknames']; nicknames_html = ''; for (var i = 0; i < nicknames.length; i++) { nicknames_html += nicknames[i] + '
    '; } $('#details_table > tbody:last').append( html.format(groups_html, nicknames_html, orgunit_html)); $('#get_details').attr('disabled', false); } }); }); }); /** * Formats the string by replacing with the appropriate parameter. * The function replaces {i} with the i-th parameter. Here i is an integer. * * @return {String} the formated string. */ String.prototype.format = function() { var args = arguments; return this.replace(/{(\d+)}/g, function(match, number) { return typeof args[number] != 'undefined' ? args[number] : match; }); }; gdata-2.0.18/samples/apps/marketplace_sample/app.yaml0000640036466300116100000000044112156622362022640 0ustar afshareng00000000000000application: domain-mgmt version: 1 runtime: python api_version: 1 handlers: - url: /css static_dir: css - url: /js static_dir: js - url: /images static_dir: images - url: /_ah/login_required script: domain_mgmt_app.py - url: .* script: domain_mgmt_app.py login: required gdata-2.0.18/samples/apps/marketplace_sample/domain_mgmt_app.py0000640036466300116100000001753512156622362024715 0ustar afshareng00000000000000#!/usr/bin/python2.4 # # Copyright 2012 Google Inc. All Rights Reserved. # # 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. """Google Apps marketplace sample app. Demonstartes how to use provisoining data in marketplace apps. """ __author__ = 'Gunjan Sharma ' import logging import os import re import urllib from urlparse import urlparse from django.utils import simplejson as json from google.appengine.api import users from google.appengine.ext import webapp from google.appengine.ext.webapp import template from google.appengine.ext.webapp import util from appengine_utilities.sessions import Session from gdata.apps.client import AppsClient from gdata.apps.groups.client import GroupsProvisioningClient from gdata.apps.organization.client import OrganizationUnitProvisioningClient import gdata.auth CONSUMER_KEY = '965697648820.apps.googleusercontent.com' CONSUMER_SECRET = '3GBNP4EJykV7wq8tuN0LTFLr' class TwoLeggedOauthTokenGenerator(webapp.RequestHandler): def Get2loToken(self): user = users.get_current_user() return gdata.gauth.TwoLeggedOAuthHmacToken( CONSUMER_KEY, CONSUMER_SECRET, user.email()) class MainHandler(TwoLeggedOauthTokenGenerator): """Handles initial get request and post request to '/' URL.""" def get(self): """Handels the get request for the MainHandler. It checks if a the user is logged in and also that he belogs to the domain, if not redirects it to the login page else to the index.html page. """ domain = self.request.get('domain') if not domain: self.response.out.write( 'Missing required params. To use the app start with following URL: ' 'http://domain-mgmt.appspot.com?from=google&domain=yourdomain.com') return user = users.get_current_user() if user and self.CheckEmail(user): logging.debug('logged in user: %s', user.email()) session = Session() session['domain'] = domain else: self.redirect('/_ah/login_required?' + urllib.urlencode((self.request.str_params))) path = os.path.join(os.path.dirname(__file__), 'templates/index.html') self.response.out.write(template.render(path, {})) def CheckEmail(self, user): """Performs basic validation of the supplied email address. Args: user: A User object corresponding to logged in user. Returns: True if user is valid, False otherwise. """ domain = urlparse(user.federated_identity()).hostname m = re.search('.*@' + domain, user.email()) if m: return True else: return False def post(self): """Handels the get request for the MainHandler. Retrieves a list of all of the domain's users and sends it to the Client as a JSON object. """ users_list = [] session = Session() domain = session['domain'] client = AppsClient(domain=domain) client.auth_token = self.Get2loToken() client.ssl = True feed = client.RetrieveAllUsers() for entry in feed.entry: users_list.append(entry.login.user_name) self.response.out.write(json.dumps(users_list)) class UserDetailsHandler(TwoLeggedOauthTokenGenerator): """Handles get request to '/getdetails' URL.""" def get(self, username): """Handels the get request for the UserDetailsHandler. Sends groups, organization unit and nicknames for the user in a JSON object. Args: username: A string denoting the user's username. """ session = Session() domain = session['domain'] if not domain: self.redirect('/') details = {} details['groups'] = self.GetGroups(domain, username) details['orgunit'] = self.GetOrgunit(domain, username) details['nicknames'] = self.GetNicknames(domain, username) data = json.dumps(details) logging.debug('Sending data...') logging.debug(data) self.response.out.write(data) logging.debug('Data sent successfully') def GetGroups(self, domain, username): """Retrieves a list of groups for the given user. Args: domain: A string determining the user's domain. username: A string denoting the user's username. Returns: A list of dicts of groups with their name and ID if successful. Otherwise a list with single dict entry containing error message. """ try: groups_client = GroupsProvisioningClient(domain=domain) groups_client.auth_token = self.Get2loToken() groups_client.ssl = True feed = groups_client.RetrieveGroups(username, True) groups = [] for entry in feed.entry: group = {} group['name'] = entry.group_name group['id'] = entry.group_id groups.append(group) return groups except: return [{'name': 'An error occured while retriving Groups for the user', 'id': 'An error occured while retriving Groups for the user'}] def GetOrgunit(self, domain, username): """Retrieves the Org Unit corresponding to the user. Args: domain: A string determining the user's domain. username: A string denoting the user's username. Returns: A dict of orgunit having its name and path if successful. Otherwise a dict entry containing error message. """ try: ouclient = OrganizationUnitProvisioningClient(domain=domain) ouclient.auth_token = self.Get2loToken() ouclient.ssl = True customer_id = ouclient.RetrieveCustomerId().customer_id entry = ouclient.RetrieveOrgUser(customer_id, username + '@' + domain) oupath = entry.org_unit_path orgunit = {} if not oupath: orgunit['name'] = 'MAIN ORG UNIT' orgunit['path'] = '/' return orgunit entry = ouclient.RetrieveOrgUnit(customer_id, oupath) orgunit['name'] = entry.org_unit_name orgunit['path'] = entry.org_unit_path return orgunit except: return {'name': 'An error occured while retriving OrgUnit for the user.', 'path': 'An error occured while retriving OrgUnit for the user.'} def GetNicknames(self, domain, username): """Retrieves the list of all the nicknames for the user. Args: domain: A string determining the user's domain. username: A string denoting the user's username. Returns: A list of user's nicknames if successful. Otherwise a list with a single entry containing error message. """ try: client = AppsClient(domain=domain) client.auth_token = self.Get2loToken() client.ssl = True feed = client.RetrieveNicknames(username) nicknames = [] for entry in feed.entry: nicknames.append(entry.nickname.name) return nicknames except: return ['An error occured while retriving Nicknames for the user.'] class OpenIDHandler(webapp.RequestHandler): def get(self): """Begins the OpenID flow for the supplied domain.""" domain = self.request.get('domain') self.redirect(users.create_login_url( dest_url='https://domain-mgmt.appspot.com?domain=' + domain, _auth_domain=None, federated_identity=domain)) def main(): application = webapp.WSGIApplication([('/', MainHandler), ('/getdetails/(.*)', UserDetailsHandler), ('/_ah/login_required', OpenIDHandler)], debug=True) util.run_wsgi_app(application) if __name__ == '__main__': main() gdata-2.0.18/samples/apps/marketplace_sample/templates/0000750036466300116100000000000012156625015023170 5ustar afshareng00000000000000gdata-2.0.18/samples/apps/marketplace_sample/templates/index.html0000640036466300116100000000205112156622362025166 0ustar afshareng00000000000000 User Details

    Choose one user


    gdata-2.0.18/samples/apps/marketplace_sample/appengine_utilities/0000750036466300116100000000000012156625015025233 5ustar afshareng00000000000000gdata-2.0.18/samples/apps/marketplace_sample/appengine_utilities/event.py0000640036466300116100000001111012156622362026723 0ustar afshareng00000000000000""" Copyright (c) 2008, appengine-utilities project All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - Neither the name of the appengine-utilities project nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """ import sys class Event(object): """ Event is a simple publish/subscribe based event dispatcher. It's a way to add, or take advantage of, hooks in your application. If you want to tie actions in with lower level classes you're developing within your application, you can set events to fire, and then subscribe to them with callback methods in other methods in your application. It sets itself to the sys.modules['__main__'] function. In order to use it, you must import it with your sys.modules['__main__'] method, and make sure you import sys.modules['__main__'] and it's accessible for the methods where you want to use it. For example, from sessions.py # if the event class has been loaded, fire off the sessionDeleted # event if u"AEU_Events" in sys.modules['__main__'].__dict__: sys.modules['__main__'].AEU_Events.fire_event(u"sessionDelete") You can the subscribe to session delete events, adding a callback if u"AEU_Events" in sys.modules['__main__'].__dict__: sys.modules['__main__'].AEU_Events.subscribe(u"sessionDelete", \ clear_user_session) """ def __init__(self): self.events = [] def subscribe(self, event, callback, args = None): """ This method will subscribe a callback function to an event name. Args: event: The event to subscribe to. callback: The callback method to run. args: Optional arguments to pass with the callback. Returns True """ if not {"event": event, "callback": callback, "args": args, } \ in self.events: self.events.append({"event": event, "callback": callback, \ "args": args, }) return True def unsubscribe(self, event, callback, args = None): """ This method will unsubscribe a callback from an event. Args: event: The event to subscribe to. callback: The callback method to run. args: Optional arguments to pass with the callback. Returns True """ if {"event": event, "callback": callback, "args": args, }\ in self.events: self.events.remove({"event": event, "callback": callback,\ "args": args, }) return True def fire_event(self, event = None): """ This method is what a method uses to fire an event, initiating all registered callbacks Args: event: The name of the event to fire. Returns True """ for e in self.events: if e["event"] == event: if type(e["args"]) == type([]): e["callback"](*e["args"]) elif type(e["args"]) == type({}): e["callback"](**e["args"]) elif e["args"] == None: e["callback"]() else: e["callback"](e["args"]) return True """ Assign to the event class to sys.modules['__main__'] """ sys.modules['__main__'].AEU_Events = Event() gdata-2.0.18/samples/apps/marketplace_sample/appengine_utilities/__init__.py0000640036466300116100000000000012156622362027335 0ustar afshareng00000000000000gdata-2.0.18/samples/apps/marketplace_sample/appengine_utilities/flash.py0000640036466300116100000001152212156622362026706 0ustar afshareng00000000000000""" Copyright (c) 2008, appengine-utilities project All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - Neither the name of the appengine-utilities project nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """ import os import Cookie from time import strftime from django.utils import simplejson # settings try: import settings_default import settings if settings.__name__.rsplit('.', 1)[0] != settings_default.__name__.rsplit('.', 1)[0]: settings = settings_default except: settings = settings_default COOKIE_NAME = settings.flash["COOKIE_NAME"] class Flash(object): """ Send messages to the user between pages. When you instantiate the class, the attribute 'msg' will be set from the cookie, and the cookie will be deleted. If there is no flash cookie, 'msg' will default to None. To set a flash message for the next page, simply set the 'msg' attribute. Example psuedocode: if new_entity.put(): flash = Flash() flash.msg = 'Your new entity has been created!' return redirect_to_entity_list() Then in the template on the next page: {% if flash.msg %}
    {{ flash.msg }}
    {% endif %} """ def __init__(self, cookie=None): """ Load the flash message and clear the cookie. """ print self.no_cache_headers() # load cookie if cookie is None: browser_cookie = os.environ.get('HTTP_COOKIE', '') self.cookie = Cookie.SimpleCookie() self.cookie.load(browser_cookie) else: self.cookie = cookie # check for flash data if self.cookie.get(COOKIE_NAME): # set 'msg' attribute cookie_val = self.cookie[COOKIE_NAME].value # we don't want to trigger __setattr__(), which creates a cookie try: self.__dict__['msg'] = simplejson.loads(cookie_val) except: # not able to load the json, so do not set message. This should # catch for when the browser doesn't delete the cookie in time for # the next request, and only blanks out the content. pass # clear the cookie self.cookie[COOKIE_NAME] = '' self.cookie[COOKIE_NAME]['path'] = '/' self.cookie[COOKIE_NAME]['expires'] = 0 print self.cookie[COOKIE_NAME] else: # default 'msg' attribute to None self.__dict__['msg'] = None def __setattr__(self, name, value): """ Create a cookie when setting the 'msg' attribute. """ if name == 'cookie': self.__dict__['cookie'] = value elif name == 'msg': self.__dict__['msg'] = value self.__dict__['cookie'][COOKIE_NAME] = simplejson.dumps(value) self.__dict__['cookie'][COOKIE_NAME]['path'] = '/' print self.cookie else: raise ValueError('You can only set the "msg" attribute.') def no_cache_headers(self): """ Generates headers to avoid any page caching in the browser. Useful for highly dynamic sites. Returns a unicode string of headers. """ return u"".join([u"Expires: Tue, 03 Jul 2001 06:00:00 GMT", strftime("Last-Modified: %a, %d %b %y %H:%M:%S %Z").decode("utf-8"), u"Cache-Control: no-store, no-cache, must-revalidate, max-age=0", u"Cache-Control: post-check=0, pre-check=0", u"Pragma: no-cache", ]) gdata-2.0.18/samples/apps/marketplace_sample/appengine_utilities/django-middleware/0000750036466300116100000000000012156625015030610 5ustar afshareng00000000000000gdata-2.0.18/samples/apps/marketplace_sample/appengine_utilities/django-middleware/middleware.py0000640036466300116100000000447012156622362033307 0ustar afshareng00000000000000import Cookie import os from common.appengine_utilities import sessions class SessionMiddleware(object): TEST_COOKIE_NAME = 'testcookie' TEST_COOKIE_VALUE = 'worked' def process_request(self, request): """ Check to see if a valid session token exists, if not, then use a cookie only session. It's up to the application to convert the session to a datastore session. Once this has been done, the session will continue to use the datastore unless the writer is set to "cookie". Setting the session to use the datastore is as easy as resetting request.session anywhere if your application. Example: from common.appengine_utilities import sessions request.session = sessions.Session() """ self.request = request if sessions.Session.check_token(): request.session = sessions.Session() else: request.session = sessions.Session(writer="cookie") request.session.set_test_cookie = self.set_test_cookie request.session.test_cookie_worked = self.test_cookie_worked request.session.delete_test_cookie = self.delete_test_cookie request.session.save = self.save return None def set_test_cookie(self): string_cookie = os.environ.get('HTTP_COOKIE', '') self.cookie = Cookie.SimpleCookie() self.cookie.load(string_cookie) self.cookie[self.TEST_COOKIE_NAME] = self.TEST_COOKIE_VALUE print self.cookie def test_cookie_worked(self): string_cookie = os.environ.get('HTTP_COOKIE', '') self.cookie = Cookie.SimpleCookie() self.cookie.load(string_cookie) return self.cookie.get(self.TEST_COOKIE_NAME) def delete_test_cookie(self): string_cookie = os.environ.get('HTTP_COOKIE', '') self.cookie = Cookie.SimpleCookie() self.cookie.load(string_cookie) self.cookie[self.TEST_COOKIE_NAME] = '' self.cookie[self.TEST_COOKIE_NAME]['path'] = '/' self.cookie[self.TEST_COOKIE_NAME]['expires'] = 0 def save(self): self.request.session = sessions.Session() def process_response(self, request, response): if hasattr(request, "session"): response.cookies= request.session.output_cookie return response gdata-2.0.18/samples/apps/marketplace_sample/appengine_utilities/django-middleware/__init__.py0000640036466300116100000000000012156622362032712 0ustar afshareng00000000000000gdata-2.0.18/samples/apps/marketplace_sample/appengine_utilities/cache.py0000640036466300116100000002771312156622362026665 0ustar afshareng00000000000000# -*- coding: utf-8 -*- """ Copyright (c) 2008, appengine-utilities project All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - Neither the name of the appengine-utilities project nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """ # main python imports import datetime import pickle import random import sys # google appengine import from google.appengine.ext import db from google.appengine.api import memcache # settings try: import settings_default import settings if settings.__name__.rsplit('.', 1)[0] != settings_default.__name__.rsplit('.', 1)[0]: settings = settings_default except: settings = settings_default class _AppEngineUtilities_Cache(db.Model): cachekey = db.StringProperty() createTime = db.DateTimeProperty(auto_now_add=True) timeout = db.DateTimeProperty() value = db.BlobProperty() class Cache(object): """ Cache is used for storing pregenerated output and/or objects in the Big Table datastore to minimize the amount of queries needed for page displays. The idea is that complex queries that generate the same results really should only be run once. Cache can be used to store pregenerated value made from queries (or other calls such as urlFetch()), or the query objects themselves. Cache is a standard dictionary object and can be used as such. It attesmpts to store data in both memcache, and the datastore. However, should a datastore write fail, it will not try again. This is for performance reasons. """ def __init__(self, clean_check_percent = settings.cache["CLEAN_CHECK_PERCENT"], max_hits_to_clean = settings.cache["MAX_HITS_TO_CLEAN"], default_timeout = settings.cache["DEFAULT_TIMEOUT"]): """ Initializer Args: clean_check_percent: how often cache initialization should run the cache cleanup max_hits_to_clean: maximum number of stale hits to clean default_timeout: default length a cache item is good for """ self.clean_check_percent = clean_check_percent self.max_hits_to_clean = max_hits_to_clean self.default_timeout = default_timeout if random.randint(1, 100) < self.clean_check_percent: try: self._clean_cache() except: pass if 'AEU_Events' in sys.modules['__main__'].__dict__: sys.modules['__main__'].AEU_Events.fire_event('cacheInitialized') def _clean_cache(self): """ _clean_cache is a routine that is run to find and delete cache items that are old. This helps keep the size of your over all datastore down. It only deletes the max_hits_to_clean per attempt, in order to maximize performance. Default settings are 20 hits, 50% of requests. Generally less hits cleaned on more requests will give you better performance. Returns True on completion """ query = _AppEngineUtilities_Cache.all() query.filter('timeout < ', datetime.datetime.now()) results = query.fetch(self.max_hits_to_clean) db.delete(results) return True def _validate_key(self, key): """ Internal method for key validation. This can be used by a superclass to introduce more checks on key names. Args: key: Key name to check Returns True is key is valid, otherwise raises KeyError. """ if key == None: raise KeyError return True def _validate_value(self, value): """ Internal method for value validation. This can be used by a superclass to introduce more checks on key names. Args: value: value to check Returns True is value is valid, otherwise raises ValueError. """ if value == None: raise ValueError return True def _validate_timeout(self, timeout): """ Internal method to validate timeouts. If no timeout is passed, then the default_timeout is used. Args: timeout: datetime.datetime format Returns the timeout """ if timeout == None: timeout = datetime.datetime.now() +\ datetime.timedelta(seconds=self.default_timeout) if type(timeout) == type(1): timeout = datetime.datetime.now() + \ datetime.timedelta(seconds = timeout) if type(timeout) != datetime.datetime: raise TypeError if timeout < datetime.datetime.now(): raise ValueError return timeout def add(self, key = None, value = None, timeout = None): """ Adds an entry to the cache, if one does not already exist. If they key already exists, KeyError will be raised. Args: key: Key name of the cache object value: Value of the cache object timeout: timeout value for the cache object. Returns the cache object. """ self._validate_key(key) self._validate_value(value) timeout = self._validate_timeout(timeout) if key in self: raise KeyError cacheEntry = _AppEngineUtilities_Cache() cacheEntry.cachekey = key cacheEntry.value = pickle.dumps(value) cacheEntry.timeout = timeout # try to put the entry, if it fails silently pass # failures may happen due to timeouts, the datastore being read # only for maintenance or other applications. However, cache # not being able to write to the datastore should not # break the application try: cacheEntry.put() except: pass memcache_timeout = timeout - datetime.datetime.now() memcache.set('cache-%s' % (key), value, int(memcache_timeout.seconds)) if 'AEU_Events' in sys.modules['__main__'].__dict__: sys.modules['__main__'].AEU_Events.fire_event('cacheAdded') return self.get(key) def set(self, key = None, value = None, timeout = None): """ Sets an entry to the cache, overwriting an existing value if one already exists. Args: key: Key name of the cache object value: Value of the cache object timeout: timeout value for the cache object. Returns the cache object. """ self._validate_key(key) self._validate_value(value) timeout = self._validate_timeout(timeout) cacheEntry = self._read(key) if not cacheEntry: cacheEntry = _AppEngineUtilities_Cache() cacheEntry.cachekey = key cacheEntry.value = pickle.dumps(value) cacheEntry.timeout = timeout try: cacheEntry.put() except: pass memcache_timeout = timeout - datetime.datetime.now() memcache.set('cache-%s' % (key), value, int(memcache_timeout.seconds)) if 'AEU_Events' in sys.modules['__main__'].__dict__: sys.modules['__main__'].AEU_Events.fire_event('cacheSet') return value def _read(self, key = None): """ _read is an internal method that will get the cache entry directly from the datastore, and return the entity. This is used for datastore maintenance within the class. Args: key: The key to retrieve Returns the cache entity """ query = _AppEngineUtilities_Cache.all() query.filter('cachekey', key) query.filter('timeout > ', datetime.datetime.now()) results = query.fetch(1) if len(results) is 0: return None if 'AEU_Events' in sys.modules['__main__'].__dict__: sys.modules['__main__'].AEU_Events.fire_event('cacheReadFromDatastore') if 'AEU_Events' in sys.modules['__main__'].__dict__: sys.modules['__main__'].AEU_Events.fire_event('cacheRead') return results[0] def delete(self, key = None): """ Deletes a cache object. Args: key: The key of the cache object to delete. Returns True. """ memcache.delete('cache-%s' % (key)) result = self._read(key) if result: if 'AEU_Events' in sys.modules['__main__'].__dict__: sys.modules['__main__'].AEU_Events.fire_event('cacheDeleted') result.delete() return True def get(self, key): """ Used to return the cache value associated with the key passed. Args: key: The key of the value to retrieve. Returns the value of the cache item. """ mc = memcache.get('cache-%s' % (key)) if mc: if 'AEU_Events' in sys.modules['__main__'].__dict__: sys.modules['__main__'].AEU_Events.fire_event('cacheReadFromMemcache') if 'AEU_Events' in sys.modules['__main__'].__dict__: sys.modules['__main__'].AEU_Events.fire_event('cacheRead') return mc result = self._read(key) if result: timeout = result.timeout - datetime.datetime.now() memcache.set('cache-%s' % (key), pickle.loads(result.value), int(timeout.seconds)) if 'AEU_Events' in sys.modules['__main__'].__dict__: sys.modules['__main__'].AEU_Events.fire_event('cacheRead') return pickle.loads(result.value) else: raise KeyError def get_many(self, keys): """ Returns a dict mapping each key in keys to its value. If the given key is missing, it will be missing from the response dict. Args: keys: A list of keys to retrieve. Returns a dictionary of key/value pairs. """ dict = {} for key in keys: value = self.get(key) if value is not None: dict[key] = value return dict def __getitem__(self, key): """ __getitem__ is necessary for this object to emulate a container. """ return self.get(key) def __setitem__(self, key, value): """ __setitem__ is necessary for this object to emulate a container. """ return self.set(key, value) def __delitem__(self, key): """ Implement the 'del' keyword """ return self.delete(key) def __contains__(self, key): """ Implements "in" operator """ try: self.__getitem__(key) except KeyError: return False return True def has_key(self, keyname): """ Equivalent to k in a, use that form in new code """ return self.__contains__(keyname) gdata-2.0.18/samples/apps/marketplace_sample/appengine_utilities/settings_default.py0000640036466300116100000000702712156622362031162 0ustar afshareng00000000000000""" Copyright (c) 2008, appengine-utilities project All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - Neither the name of the appengine-utilities project nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """ __author__="jbowman" __date__ ="$Sep 11, 2009 4:20:11 PM$" # Configuration settings for the session class. session = { "COOKIE_NAME": "gaeutilities_session", "DEFAULT_COOKIE_PATH": "/", "DEFAULT_COOKIE_DOMAIN": False, # Set to False if you do not want this value # set on the cookie, otherwise put the # domain value you wish used. "SESSION_EXPIRE_TIME": 7200, # sessions are valid for 7200 seconds # (2 hours) "INTEGRATE_FLASH": True, # integrate functionality from flash module? "SET_COOKIE_EXPIRES": True, # Set to True to add expiration field to # cookie "WRITER":"datastore", # Use the datastore writer by default. # cookie is the other option. "CLEAN_CHECK_PERCENT": 50, # By default, 50% of all requests will clean # the datastore of expired sessions "CHECK_IP": True, # validate sessions by IP "CHECK_USER_AGENT": True, # validate sessions by user agent "SESSION_TOKEN_TTL": 5, # Number of seconds a session token is valid # for. "UPDATE_LAST_ACTIVITY": 60, # Number of seconds that may pass before # last_activity is updated } # Configuration settings for the cache class cache = { "DEFAULT_TIMEOUT": 3600, # cache expires after one hour (3600 sec) "CLEAN_CHECK_PERCENT": 50, # 50% of all requests will clean the database "MAX_HITS_TO_CLEAN": 20, # the maximum number of cache hits to clean } # Configuration settings for the flash class flash = { "COOKIE_NAME": "appengine-utilities-flash", } # Configuration settings for the paginator class paginator = { "DEFAULT_COUNT": 10, "CACHE": 10, "DEFAULT_SORT_ORDER": "ASC", } rotmodel = { "RETRY_ATTEMPTS": 3, "RETRY_INTERVAL": .2, } if __name__ == "__main__": print "Hello World"; gdata-2.0.18/samples/apps/marketplace_sample/appengine_utilities/sessions.py0000640036466300116100000012057712156622362027472 0ustar afshareng00000000000000# -*- coding: utf-8 -*- """ Copyright (c) 2008, appengine-utilities project All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - Neither the name of the appengine-utilities project nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """ # main python imports import os import time import datetime import random import hashlib import Cookie import pickle import sys import logging from time import strftime # google appengine imports from google.appengine.ext import db from google.appengine.api import memcache from django.utils import simplejson # settings try: import settings_default import settings if settings.__name__.rsplit('.', 1)[0] != settings_default.__name__.rsplit('.', 1)[0]: settings = settings_default except: settings = settings_default class _AppEngineUtilities_Session(db.Model): """ Model for the sessions in the datastore. This contains the identifier and validation information for the session. """ sid = db.StringListProperty() ip = db.StringProperty() ua = db.StringProperty() last_activity = db.DateTimeProperty() dirty = db.BooleanProperty(default=False) working = db.BooleanProperty(default=False) deleted = db.BooleanProperty(default=False) def put(self): """ Extends put so that it writes vaules to memcache as well as the datastore, and keeps them in sync, even when datastore writes fails. Returns the session object. """ try: memcache.set(u"_AppEngineUtilities_Session_%s" % \ (str(self.key())), self) except: # new session, generate a new key, which will handle the # put and set the memcache db.put(self) self.last_activity = datetime.datetime.now() try: self.dirty = False db.put(self) memcache.set(u"_AppEngineUtilities_Session_%s" % \ (str(self.key())), self) except: self.dirty = True memcache.set(u"_AppEngineUtilities_Session_%s" % \ (str(self.key())), self) return self @classmethod def get_session(cls, session_obj=None): """ Uses the passed objects sid to get a session object from memcache, or datastore if a valid one exists. Args: session_obj: a session object Returns a validated session object. """ if session_obj.sid == None: return None session_key = session_obj.sid.split(u'_')[0] session = memcache.get(u"_AppEngineUtilities_Session_%s" % \ (str(session_key))) if session: if session.deleted == True: session.delete() return None if session.dirty == True and session.working != False: # the working bit is used to make sure multiple requests, # which can happen with ajax oriented sites, don't try to put # at the same time session.working = True memcache.set(u"_AppEngineUtilities_Session_%s" % \ (str(session_key)), session) session.put() if session_obj.sid in session.sid: sessionAge = datetime.datetime.now() - session.last_activity if sessionAge.seconds > session_obj.session_expire_time: session.delete() return None return session else: return None # Not in memcache, check datastore ds_session = db.get(str(session_key)) if ds_session: sessionAge = datetime.datetime.now() - ds_session.last_activity if sessionAge.seconds > session_obj.session_expire_time: ds_session.delete() return None memcache.set(u"_AppEngineUtilities_Session_%s" % \ (str(session_key)), ds_session) memcache.set(u"_AppEngineUtilities_SessionData_%s" % \ (str(session_key)), ds_session.get_items_ds()) return ds_session def get_items(self): """ Returns all the items stored in a session. Queries memcache first and will try the datastore next. """ items = memcache.get(u"_AppEngineUtilities_SessionData_%s" % \ (str(self.key()))) if items: for item in items: if item.deleted == True: item.delete() items.remove(item) return items query = _AppEngineUtilities_SessionData.all() query.filter(u"session", self) results = query.fetch(1000) return results def get_item(self, keyname = None): """ Returns a single session data item from the memcache or datastore Args: keyname: keyname of the session data object Returns the session data object if it exists, otherwise returns None """ mc = memcache.get(u"_AppEngineUtilities_SessionData_%s" % \ (str(self.key()))) if mc: for item in mc: if item.keyname == keyname: if item.deleted == True: item.delete() return None return item query = _AppEngineUtilities_SessionData.all() query.filter(u"session = ", self) query.filter(u"keyname = ", keyname) results = query.fetch(1) if len(results) > 0: memcache.set(u"_AppEngineUtilities_SessionData_%s" % \ (str(self.key())), self.get_items_ds()) return results[0] return None def get_items_ds(self): """ This gets all session data objects from the datastore, bypassing memcache. Returns a list of session data entities. """ query = _AppEngineUtilities_SessionData.all() query.filter(u"session", self) results = query.fetch(1000) return results def delete(self): """ Deletes a session and all it's associated data from the datastore and memcache. Returns True """ try: query = _AppEngineUtilities_SessionData.all() query.filter(u"session = ", self) results = query.fetch(1000) db.delete(results) db.delete(self) memcache.delete_multi([u"_AppEngineUtilities_Session_%s" % \ (str(self.key())), \ u"_AppEngineUtilities_SessionData_%s" % \ (str(self.key()))]) except: mc = memcache.get(u"_AppEngineUtilities_Session_%s" %+ \ (str(self.key()))) if mc: mc.deleted = True else: # not in the memcache, check to see if it should be query = _AppEngineUtilities_Session.all() query.filter(u"sid = ", self.sid) results = query.fetch(1) if len(results) > 0: results[0].deleted = True memcache.set(u"_AppEngineUtilities_Session_%s" % \ (unicode(self.key())), results[0]) return True class _AppEngineUtilities_SessionData(db.Model): """ Model for the session data in the datastore. """ # session_key = db.FloatProperty() keyname = db.StringProperty() content = db.BlobProperty() model = db.ReferenceProperty() session = db.ReferenceProperty(_AppEngineUtilities_Session) dirty = db.BooleanProperty(default=False) deleted = db.BooleanProperty(default=False) def put(self): """ Adds a keyname/value for session to the datastore and memcache Returns the key from the datastore put or u"dirty" """ # update or insert in datastore try: return_val = db.put(self) self.dirty = False except: return_val = u"dirty" self.dirty = True # update or insert in memcache mc_items = memcache.get(u"_AppEngineUtilities_SessionData_%s" % \ (str(self.session.key()))) if mc_items: value_updated = False for item in mc_items: if value_updated == True: break if item.keyname == self.keyname: item.content = self.content item.model = self.model memcache.set(u"_AppEngineUtilities_SessionData_%s" % \ (str(self.session.key())), mc_items) value_updated = True break if value_updated == False: mc_items.append(self) memcache.set(u"_AppEngineUtilities_SessionData_%s" % \ (str(self.session.key())), mc_items) return return_val def delete(self): """ Deletes an entity from the session in memcache and the datastore Returns True """ try: db.delete(self) except: self.deleted = True mc_items = memcache.get(u"_AppEngineUtilities_SessionData_%s" % \ (str(self.session.key()))) value_handled = False for item in mc_items: if value_handled == True: break if item.keyname == self.keyname: if self.deleted == True: item.deleted = True else: mc_items.remove(item) memcache.set(u"_AppEngineUtilities_SessionData_%s" % \ (str(self.session.key())), mc_items) return True class _DatastoreWriter(object): def put(self, keyname, value, session): """ Insert a keyname/value pair into the datastore for the session. Args: keyname: The keyname of the mapping. value: The value of the mapping. Returns the model entity key """ keyname = session._validate_key(keyname) if value is None: raise ValueError(u"You must pass a value to put.") # datestore write trumps cookie. If there is a cookie value # with this keyname, delete it so we don't have conflicting # entries. if session.cookie_vals.has_key(keyname): del(session.cookie_vals[keyname]) session.output_cookie["%s_data" % (session.cookie_name)] = \ simplejson.dumps(session.cookie_vals) session.output_cookie["%s_data" % (session.cookie_name)]["path"] = \ session.cookie_path if session.cookie_domain: session.output_cookie["%s_data" % \ (session.cookie_name)]["domain"] = session.cookie_domain print session.output_cookie.output() sessdata = session._get(keyname=keyname) if sessdata is None: sessdata = _AppEngineUtilities_SessionData() # sessdata.session_key = session.session.key() sessdata.keyname = keyname try: db.model_to_protobuf(value) if not value.is_saved(): value.put() sessdata.model = value except: sessdata.content = pickle.dumps(value) sessdata.model = None sessdata.session = session.session session.cache[keyname] = value return sessdata.put() class _CookieWriter(object): def put(self, keyname, value, session): """ Insert a keyname/value pair into the datastore for the session. Args: keyname: The keyname of the mapping. value: The value of the mapping. Returns True """ keyname = session._validate_key(keyname) if value is None: raise ValueError(u"You must pass a value to put.") # Use simplejson for cookies instead of pickle. session.cookie_vals[keyname] = value # update the requests session cache as well. session.cache[keyname] = value # simplejson will raise any error I'd raise about an invalid value # so let it raise exceptions session.output_cookie["%s_data" % (session.cookie_name)] = \ simplejson.dumps(session.cookie_vals) session.output_cookie["%s_data" % (session.cookie_name)]["path"] = \ session.cookie_path if session.cookie_domain: session.output_cookie["%s_data" % \ (session.cookie_name)]["domain"] = session.cookie_domain print session.output_cookie.output() return True class Session(object): """ Sessions are used to maintain user presence between requests. Sessions can either be stored server side in the datastore/memcache, or be kept entirely as cookies. This is set either with the settings file or on initialization, using the writer argument/setting field. Valid values are "datastore" or "cookie". Session can be used as a standard dictionary object. session = appengine_utilities.sessions.Session() session["keyname"] = "value" # sets keyname to value print session["keyname"] # will print value Datastore Writer: The datastore writer was written with the focus being on security, reliability, and performance. In that order. It is based off of a session token system. All data is stored server side in the datastore and memcache. A token is given to the browser, and stored server side. Optionally (and on by default), user agent and ip checking is enabled. Tokens have a configurable time to live (TTL), which defaults to 5 seconds. The current token, plus the previous 2, are valid for any request. This is done in order to manage ajax enabled sites which may have more than on request happening at a time. This means any token is valid for 15 seconds. A request with a token who's TTL has passed will have a new token generated. In order to take advantage of the token system for an authentication system, you will want to tie sessions to accounts, and make sure only one session is valid for an account. You can do this by setting a db.ReferenceProperty(_AppEngineUtilities_Session) attribute on your user Model, and use the get_ds_entity() method on a valid session to populate it on login. Note that even with this complex system, sessions can still be hijacked and it will take the user logging in to retrieve the account. In the future an ssl only cookie option may be implemented for the datastore writer, which would further protect the session token from being sniffed, however it would be restricted to using cookies on the .appspot.com domain, and ssl requests are a finite resource. This is why such a thing is not currently implemented. Session data objects are stored in the datastore pickled, so any python object is valid for storage. Cookie Writer: Sessions using the cookie writer are stored entirely in the browser and no interaction with the datastore is required. This creates a drastic improvement in performance, but provides no security for session hijack. This is useful for requests where identity is not important, but you wish to keep state between requests. Information is stored in a json format, as pickled data from the server is unreliable. Note: There is no checksum validation of session data on this method, it's streamlined for pure performance. If you need to make sure data is not tampered with, use the datastore writer which stores the data server side. django-middleware: Included with the GAEUtilties project is a django-middleware.middleware.SessionMiddleware which can be included in your settings file. This uses the cookie writer for anonymous requests, and you can switch to the datastore writer on user login. This will require an extra set in your login process of calling request.session.save() once you validated the user information. This will convert the cookie writer based session to a datastore writer. """ # cookie name declaration for class methods COOKIE_NAME = settings.session["COOKIE_NAME"] def __init__(self, cookie_path=settings.session["DEFAULT_COOKIE_PATH"], cookie_domain=settings.session["DEFAULT_COOKIE_DOMAIN"], cookie_name=settings.session["COOKIE_NAME"], session_expire_time=settings.session["SESSION_EXPIRE_TIME"], clean_check_percent=settings.session["CLEAN_CHECK_PERCENT"], integrate_flash=settings.session["INTEGRATE_FLASH"], check_ip=settings.session["CHECK_IP"], check_user_agent=settings.session["CHECK_USER_AGENT"], set_cookie_expires=settings.session["SET_COOKIE_EXPIRES"], session_token_ttl=settings.session["SESSION_TOKEN_TTL"], last_activity_update=settings.session["UPDATE_LAST_ACTIVITY"], writer=settings.session["WRITER"]): """ Initializer Args: cookie_path: The path setting for the cookie. cookie_domain: The domain setting for the cookie. (Set to False to not use) cookie_name: The name for the session cookie stored in the browser. session_expire_time: The amount of time between requests before the session expires. clean_check_percent: The percentage of requests the will fire off a cleaning routine that deletes stale session data. integrate_flash: If appengine-utilities flash utility should be integrated into the session object. check_ip: If browser IP should be used for session validation check_user_agent: If the browser user agent should be used for sessoin validation. set_cookie_expires: True adds an expires field to the cookie so it saves even if the browser is closed. session_token_ttl: Number of sessions a session token is valid for before it should be regenerated. """ self.cookie_path = cookie_path self.cookie_domain = cookie_domain self.cookie_name = cookie_name self.session_expire_time = session_expire_time self.integrate_flash = integrate_flash self.check_user_agent = check_user_agent self.check_ip = check_ip self.set_cookie_expires = set_cookie_expires self.session_token_ttl = session_token_ttl self.last_activity_update = last_activity_update self.writer = writer # make sure the page is not cached in the browser print self.no_cache_headers() # Check the cookie and, if necessary, create a new one. self.cache = {} string_cookie = os.environ.get(u"HTTP_COOKIE", u"") self.cookie = Cookie.SimpleCookie() self.output_cookie = Cookie.SimpleCookie() if string_cookie == "": self.cookie_vals = {} else: self.cookie.load(string_cookie) try: self.cookie_vals = \ simplejson.loads(self.cookie["%s_data" % (self.cookie_name)].value) # sync self.cache and self.cookie_vals which will make those # values available for all gets immediately. for k in self.cookie_vals: self.cache[k] = self.cookie_vals[k] # sync the input cookie with the output cookie self.output_cookie["%s_data" % (self.cookie_name)] = \ simplejson.dumps(self.cookie_vals) #self.cookie["%s_data" % (self.cookie_name)] except Exception, e: self.cookie_vals = {} if writer == "cookie": pass else: self.sid = None new_session = True # do_put is used to determine if a datastore write should # happen on this request. do_put = False # check for existing cookie if self.cookie.get(cookie_name): self.sid = self.cookie[cookie_name].value # The following will return None if the sid has expired. self.session = _AppEngineUtilities_Session.get_session(self) if self.session: new_session = False if new_session: # start a new session self.session = _AppEngineUtilities_Session() self.session.put() self.sid = self.new_sid() if u"HTTP_USER_AGENT" in os.environ: self.session.ua = os.environ[u"HTTP_USER_AGENT"] else: self.session.ua = None if u"REMOTE_ADDR" in os.environ: self.session.ip = os.environ["REMOTE_ADDR"] else: self.session.ip = None self.session.sid = [self.sid] # do put() here to get the session key self.session.put() else: # check the age of the token to determine if a new one # is required duration = datetime.timedelta(seconds=self.session_token_ttl) session_age_limit = datetime.datetime.now() - duration if self.session.last_activity < session_age_limit: self.sid = self.new_sid() if len(self.session.sid) > 2: self.session.sid.remove(self.session.sid[0]) self.session.sid.append(self.sid) do_put = True else: self.sid = self.session.sid[-1] # check if last_activity needs updated ula = datetime.timedelta(seconds=self.last_activity_update) if datetime.datetime.now() > self.session.last_activity + \ ula: do_put = True self.output_cookie[cookie_name] = self.sid self.output_cookie[cookie_name]["path"] = self.cookie_path if self.cookie_domain: self.output_cookie[cookie_name]["domain"] = self.cookie_domain if self.set_cookie_expires: self.output_cookie[cookie_name]["expires"] = \ self.session_expire_time self.cache[u"sid"] = self.sid if do_put: if self.sid != None or self.sid != u"": self.session.put() if self.set_cookie_expires: if not self.output_cookie.has_key("%s_data" % (cookie_name)): self.output_cookie["%s_data" % (cookie_name)] = u"" self.output_cookie["%s_data" % (cookie_name)]["expires"] = \ self.session_expire_time print self.output_cookie.output() # fire up a Flash object if integration is enabled if self.integrate_flash: import flash self.flash = flash.Flash(cookie=self.cookie) # randomly delete old stale sessions in the datastore (see # CLEAN_CHECK_PERCENT variable) if random.randint(1, 100) < clean_check_percent: self._clean_old_sessions() def new_sid(self): """ Create a new session id. Returns session id as a unicode string. """ sid = u"%s_%s" % (str(self.session.key()), hashlib.md5(repr(time.time()) + \ unicode(random.random())).hexdigest() ) #sid = unicode(self.session.session_key) + "_" + \ # hashlib.md5(repr(time.time()) + \ # unicode(random.random())).hexdigest() return sid def _get(self, keyname=None): """ private method Return all of the SessionData object data from the datastore only, unless keyname is specified, in which case only that instance of SessionData is returned. Important: This does not interact with memcache and pulls directly from the datastore. This also does not get items from the cookie store. Args: keyname: The keyname of the value you are trying to retrieve. Returns a list of datastore entities. """ if hasattr(self, 'session'): if keyname != None: return self.session.get_item(keyname) return self.session.get_items() return None def _validate_key(self, keyname): """ private method Validate the keyname, making sure it is set and not a reserved name. Returns the validated keyname. """ if keyname is None: raise ValueError( u"You must pass a keyname for the session data content." ) elif keyname in (u"sid", u"flash"): raise ValueError(u"%s is a reserved keyname." % keyname) if type(keyname) != type([str, unicode]): return unicode(keyname) return keyname def _put(self, keyname, value): """ Insert a keyname/value pair into the datastore for the session. Args: keyname: The keyname of the mapping. value: The value of the mapping. Returns the value from the writer put operation, varies based on writer. """ if self.writer == "datastore": writer = _DatastoreWriter() else: writer = _CookieWriter() return writer.put(keyname, value, self) def _delete_session(self): """ private method Delete the session and all session data. Returns True. """ # if the event class has been loaded, fire off the preSessionDelete event if u"AEU_Events" in sys.modules['__main__'].__dict__: sys.modules['__main__'].AEU_Events.fire_event(u"preSessionDelete") if hasattr(self, u"session"): self.session.delete() self.cookie_vals = {} self.cache = {} self.output_cookie["%s_data" % (self.cookie_name)] = \ simplejson.dumps(self.cookie_vals) self.output_cookie["%s_data" % (self.cookie_name)]["path"] = \ self.cookie_path if self.cookie_domain: self.output_cookie["%s_data" % \ (self.cookie_name)]["domain"] = self.cookie_domain print self.output_cookie.output() # if the event class has been loaded, fire off the sessionDelete event if u"AEU_Events" in sys.modules['__main__'].__dict__: sys.modules['__main__'].AEU_Events.fire_event(u"sessionDelete") return True def delete(self): """ Delete the current session and start a new one. This is useful for when you need to get rid of all data tied to a current session, such as when you are logging out a user. Returns True """ self._delete_session() @classmethod def delete_all_sessions(cls): """ Deletes all sessions and session data from the data store. This does not delete the entities from memcache (yet). Depending on the amount of sessions active in your datastore, this request could timeout before completion and may have to be called multiple times. NOTE: This can not delete cookie only sessions as it has no way to access them. It will only delete datastore writer sessions. Returns True on completion. """ all_sessions_deleted = False while not all_sessions_deleted: query = _AppEngineUtilities_Session.all() results = query.fetch(75) if len(results) is 0: all_sessions_deleted = True else: for result in results: result.delete() return True def _clean_old_sessions(self): """ Delete 50 expired sessions from the datastore. This is only called for CLEAN_CHECK_PERCENT percent of requests because it could be rather intensive. Returns True on completion """ self.clean_old_sessions(self.session_expire_time, 50) @classmethod def clean_old_sessions(cls, session_expire_time, count=50): """ Delete expired sessions from the datastore. This is a class method which can be used by applications for maintenance if they don't want to use the built in session cleaning. Args: count: The amount of session to clean. session_expire_time: The age in seconds to determine outdated sessions. Returns True on completion """ duration = datetime.timedelta(seconds=session_expire_time) session_age = datetime.datetime.now() - duration query = _AppEngineUtilities_Session.all() query.filter(u"last_activity <", session_age) results = query.fetch(50) for result in results: result.delete() return True def cycle_key(self): """ Changes the session id/token. Returns new token. """ self.sid = self.new_sid() if len(self.session.sid) > 2: self.session.sid.remove(self.session.sid[0]) self.session.sid.append(self.sid) return self.sid def flush(self): """ Delete's the current session, creating a new one. Returns True """ self._delete_session() self.__init__() return True def no_cache_headers(self): """ Generates headers to avoid any page caching in the browser. Useful for highly dynamic sites. Returns a unicode string of headers. """ return u"".join([u"Expires: Tue, 03 Jul 2001 06:00:00 GMT", strftime("Last-Modified: %a, %d %b %y %H:%M:%S %Z").decode("utf-8"), u"Cache-Control: no-store, no-cache, must-revalidate, max-age=0", u"Cache-Control: post-check=0, pre-check=0", u"Pragma: no-cache", ]) def clear(self): """ Removes session data items, doesn't delete the session. It does work with cookie sessions, and must be called before any output is sent to the browser, as it set cookies. Returns True """ sessiondata = self._get() # delete from datastore if sessiondata is not None: for sd in sessiondata: sd.delete() # delete from memcache self.cache = {} self.cookie_vals = {} self.output_cookie["%s_data" %s (self.cookie_name)] = \ simplejson.dumps(self.cookie_vals) self.output_cookie["%s_data" % (self.cookie_name)]["path"] = \ self.cookie_path if self.cookie_domain: self.output_cookie["%s_data" % \ (self.cookie_name)]["domain"] = self.cookie_domain print self.output_cookie.output() return True def has_key(self, keyname): """ Equivalent to k in a, use that form in new code Args: keyname: keyname to check Returns True/False """ return self.__contains__(keyname) def items(self): """ Creates a copy of just the data items. Returns dictionary of session data objects. """ op = {} for k in self: op[k] = self[k] return op def keys(self): """ Returns a list of keys. """ l = [] for k in self: l.append(k) return l def update(self, *dicts): """ Updates with key/value pairs from b, overwriting existing keys Returns None """ for dict in dicts: for k in dict: self._put(k, dict[k]) return None def values(self): """ Returns a list object of just values in the session. """ v = [] for k in self: v.append(self[k]) return v def get(self, keyname, default = None): """ Returns either the value for the keyname or a default value passed. Args: keyname: keyname to look up default: (optional) value to return on keyname miss Returns value of keyname, or default, or None """ try: return self.__getitem__(keyname) except KeyError: if default is not None: return default return None def setdefault(self, keyname, default = None): """ Returns either the value for the keyname or a default value passed. If keyname lookup is a miss, the keyname is set with a value of default. Args: keyname: keyname to look up default: (optional) value to return on keyname miss Returns value of keyname, or default, or None """ try: return self.__getitem__(keyname) except KeyError: if default is not None: self.__setitem__(keyname, default) return default return None @classmethod def check_token(cls, cookie_name=COOKIE_NAME, delete_invalid=True): """ Retrieves the token from a cookie and validates that it is a valid token for an existing cookie. Cookie validation is based on the token existing on a session that has not expired. This is useful for determining if datastore or cookie writer should be used in hybrid implementations. Args: cookie_name: Name of the cookie to check for a token. delete_invalid: If the token is not valid, delete the session cookie, to avoid datastore queries on future requests. Returns True/False """ string_cookie = os.environ.get(u"HTTP_COOKIE", u"") cookie = Cookie.SimpleCookie() cookie.load(string_cookie) if cookie.has_key(cookie_name): query = _AppEngineUtilities_Session.all() query.filter(u"sid", cookie[cookie_name].value) results = query.fetch(1) if len(results) > 0: return True else: if delete_invalid: output_cookie = Cookie.SimpleCookie() output_cookie[cookie_name] = cookie[cookie_name] output_cookie[cookie_name][u"expires"] = 0 print output_cookie.output() return False def get_ds_entity(self): """ Will return the session entity from the datastore if one exists, otherwise will return None (as in the case of cookie writer session. """ if hasattr(self, u"session"): return self.session return None # Implement Python container methods def __getitem__(self, keyname): """ Get item from session data. keyname: The keyname of the mapping. """ # flash messages don't go in the datastore if self.integrate_flash and (keyname == u"flash"): return self.flash.msg if keyname in self.cache: return self.cache[keyname] if keyname in self.cookie_vals: return self.cookie_vals[keyname] if hasattr(self, u"session"): data = self._get(keyname) if data: # TODO: It's broke here, but I'm not sure why, it's # returning a model object, but I can't seem to modify # it. try: if data.model != None: self.cache[keyname] = data.model return self.cache[keyname] else: self.cache[keyname] = pickle.loads(data.content) return self.cache[keyname] except: self.delete_item(keyname) else: raise KeyError(unicode(keyname)) raise KeyError(unicode(keyname)) def __setitem__(self, keyname, value): """ Set item in session data. Args: keyname: They keyname of the mapping. value: The value of mapping. """ if self.integrate_flash and (keyname == u"flash"): self.flash.msg = value else: keyname = self._validate_key(keyname) self.cache[keyname] = value return self._put(keyname, value) def delete_item(self, keyname, throw_exception=False): """ Delete item from session data, ignoring exceptions if necessary. Args: keyname: The keyname of the object to delete. throw_exception: false if exceptions are to be ignored. Returns: Nothing. """ if throw_exception: self.__delitem__(keyname) return None else: try: self.__delitem__(keyname) except KeyError: return None def __delitem__(self, keyname): """ Delete item from session data. Args: keyname: The keyname of the object to delete. """ bad_key = False sessdata = self._get(keyname = keyname) if sessdata is None: bad_key = True else: sessdata.delete() if keyname in self.cookie_vals: del self.cookie_vals[keyname] bad_key = False self.output_cookie["%s_data" % (self.cookie_name)] = \ simplejson.dumps(self.cookie_vals) self.output_cookie["%s_data" % (self.cookie_name)]["path"] = \ self.cookie_path if self.cookie_domain: self.output_cookie["%s_data" % \ (self.cookie_name)]["domain"] = self.cookie_domain print self.output_cookie.output() if bad_key: raise KeyError(unicode(keyname)) if keyname in self.cache: del self.cache[keyname] def __len__(self): """ Return size of session. """ # check memcache first if hasattr(self, u"session"): results = self._get() if results is not None: return len(results) + len(self.cookie_vals) else: return 0 return len(self.cookie_vals) def __contains__(self, keyname): """ Check if an item is in the session data. Args: keyname: The keyname being searched. """ try: self.__getitem__(keyname) except KeyError: return False return True def __iter__(self): """ Iterate over the keys in the session data. """ # try memcache first if hasattr(self, u"session"): vals = self._get() if vals is not None: for k in vals: yield k.keyname for k in self.cookie_vals: yield k def __str__(self): """ Return string representation. """ return u"{%s}" % ', '.join(['"%s" = "%s"' % (k, self[k]) for k in self]) gdata-2.0.18/samples/apps/marketplace_sample/appengine_utilities/rotmodel.py0000640036466300116100000001145012156622362027436 0ustar afshareng00000000000000""" Copyright (c) 2008, appengine-utilities project All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - Neither the name of the appengine-utilities project nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """ import time from google.appengine.api import datastore from google.appengine.ext import db # settings try: import settings_default import settings if settings.__name__.rsplit('.', 1)[0] != settings_default.__name__.rsplit('.', 1)[0]: settings = settings_default except: settings = settings_default class ROTModel(db.Model): """ ROTModel overrides the db.Model functions, retrying each method each time a timeout exception is raised. Methods superclassed from db.Model are: get(cls, keys) get_by_id(cls, ids, parent) get_by_key_name(cls, key_names, parent) get_or_insert(cls, key_name, kwargs) put(self) """ @classmethod def get(cls, keys): count = 0 while count < settings.rotmodel["RETRY_ATTEMPTS"]: try: return db.Model.get(keys) except db.Timeout: count += 1 time.sleep(count * settings.rotmodel["RETRY_INTERVAL"]) else: raise db.Timeout() @classmethod def get_by_id(cls, ids, parent=None): count = 0 while count < settings.rotmodel["RETRY_ATTEMPTS"]: try: return db.Model.get_by_id(ids, parent) except db.Timeout: count += 1 time.sleep(count * settings.rotmodel["RETRY_INTERVAL"]) else: raise db.Timeout() @classmethod def get_by_key_name(cls, key_names, parent=None): if isinstance(parent, db.Model): parent = parent.key() key_names, multiple = datastore.NormalizeAndTypeCheck(key_names, basestring) keys = [datastore.Key.from_path(cls.kind(), name, parent=parent) for name in key_names] count = 0 if multiple: while count < settings.rotmodel["RETRY_ATTEMPTS"]: try: return db.get(keys) except db.Timeout: count += 1 time.sleep(count * settings.rotmodel["RETRY_INTERVAL"]) else: while count < settings.rotmodel["RETRY_ATTEMPTS"]: try: return db.get(*keys) except db.Timeout: count += 1 time.sleep(count * settings.rotmodel["RETRY_INTERVAL"]) @classmethod def get_or_insert(cls, key_name, **kwargs): def txn(): entity = cls.get_by_key_name(key_name, parent=kwargs.get('parent')) if entity is None: entity = cls(key_name=key_name, **kwargs) entity.put() return entity return db.run_in_transaction(txn) def put(self): count = 0 while count < settings.rotmodel["RETRY_ATTEMPTS"]: try: return db.Model.put(self) except db.Timeout: count += 1 time.sleep(count * settings.rotmodel["RETRY_INTERVAL"]) else: raise db.Timeout() def delete(self): count = 0 while count < settings.rotmodel["RETRY_ATTEMPTS"]: try: return db.Model.delete(self) except db.Timeout: count += 1 time.sleep(count * settings.rotmodel["RETRY_INTERVAL"]) else: raise db.Timeout() gdata-2.0.18/samples/apps/marketplace_sample/appengine_utilities/interface/0000750036466300116100000000000012156625015027173 5ustar afshareng00000000000000gdata-2.0.18/samples/apps/marketplace_sample/appengine_utilities/interface/templates/0000750036466300116100000000000012156625015031171 5ustar afshareng00000000000000gdata-2.0.18/samples/apps/marketplace_sample/appengine_utilities/interface/templates/404.html0000640036466300116100000000000412156622362032363 0ustar afshareng00000000000000404 gdata-2.0.18/samples/apps/marketplace_sample/appengine_utilities/interface/templates/base.html0000640036466300116100000000414212156622362032775 0ustar afshareng00000000000000GAEUtilities

    gaeutilities

    Utility classes to make working with appengine easier.

    {% block header-content %}{% endblock %}
    CRON SUPPORT IS NOW DEPRECATED, NO FURTHER UPDATES WILL BE MADE. GOOGLE HAS NOW PROVIDED A CRON FOR APPENGINE APPLICATIONS

    Menu: Cron
    {% block content %}{% endblock %}

    ././@LongLink0000000000000000000000000000015100000000000011212 Lustar 00000000000000gdata-2.0.18/samples/apps/marketplace_sample/appengine_utilities/interface/templates/scheduler_form.htmlgdata-2.0.18/samples/apps/marketplace_sample/appengine_utilities/interface/templates/scheduler_form.0000640036466300116100000000173112156622362034200 0ustar afshareng00000000000000{% extends "base.html" %} {% block header-content %}

    Cron

    This utility allows you to schedule url's to be fetched at certain times/intervals. These actions are initiated during normal requests to your website, as long as request includes an import appengine_utlities.event.
    NOTE: This will not work on the local SDK due to it being single threaded an unable to make a request to itself.

    {% endblock %} {% block content %}

    Groups

    Nicknames

    Organization Unit

    {% for cron in cron_entries %} {% endfor %}
    Cron EntryNext Run Time
    {{ cron.cron_entry }}{{ cron.next_run }}
    Add cron entry

    {% endblock %} gdata-2.0.18/samples/apps/marketplace_sample/appengine_utilities/interface/__init__.py0000640036466300116100000000000012156622362031275 0ustar afshareng00000000000000gdata-2.0.18/samples/apps/marketplace_sample/appengine_utilities/interface/main.py0000640036466300116100000000567412156622362030510 0ustar afshareng00000000000000''' Copyright (c) 2008, appengine-utilities project All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. Neither the name of the appengine-utilities project nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ''' import os, cgi, __main__ from google.appengine.ext.webapp import template import wsgiref.handlers from google.appengine.ext import webapp from google.appengine.api import memcache from google.appengine.ext import db from appengine_utilities import cron class MainPage(webapp.RequestHandler): def get(self): c = cron.Cron() query = cron._AppEngineUtilities_Cron.all() results = query.fetch(1000) template_values = {"cron_entries" : results} path = os.path.join(os.path.dirname(__file__), 'templates/scheduler_form.html') self.response.out.write(template.render(path, template_values)) def post(self): if str(self.request.get('action')) == 'Add': cron.Cron().add_cron(str(self.request.get('cron_entry'))) elif str(self.request.get('action')) == 'Delete': entry = db.get(db.Key(str(self.request.get('key')))) entry.delete() query = cron._AppEngineUtilities_Cron.all() results = query.fetch(1000) template_values = {"cron_entries" : results} path = os.path.join(os.path.dirname(__file__), 'templates/scheduler_form.html') self.response.out.write(template.render(path, template_values)) def main(): application = webapp.WSGIApplication( [('/gaeutilities/', MainPage)], debug=True) wsgiref.handlers.CGIHandler().run(application) if __name__ == "__main__": main()gdata-2.0.18/samples/apps/marketplace_sample/appengine_utilities/interface/css/0000750036466300116100000000000012156625015027763 5ustar afshareng00000000000000gdata-2.0.18/samples/apps/marketplace_sample/appengine_utilities/interface/css/cssbase/0000750036466300116100000000000012156625015031406 5ustar afshareng00000000000000././@LongLink0000000000000000000000000000015400000000000011215 Lustar 00000000000000gdata-2.0.18/samples/apps/marketplace_sample/appengine_utilities/interface/css/cssbase/base-context-min.cssgdata-2.0.18/samples/apps/marketplace_sample/appengine_utilities/interface/css/cssbase/base-context-0000640036466300116100000000432112156622362034005 0ustar afshareng00000000000000/* Copyright (c) 2008, Yahoo! Inc. All rights reserved. Code licensed under the BSD License: http://developer.yahoo.net/yui/license.txt version: 3.0.0pr1 */ /* base.css, part of YUI's CSS Foundation */ .yui-cssbase h1 { /*18px via YUI Fonts CSS foundation*/ font-size:138.5%; } .yui-cssbase h2 { /*16px via YUI Fonts CSS foundation*/ font-size:123.1%; } .yui-cssbase h3 { /*14px via YUI Fonts CSS foundation*/ font-size:108%; } .yui-cssbase h1,.yui-cssbase h2,.yui-cssbase h3 { /* top & bottom margin based on font size */ margin:1em 0; } .yui-cssbase h1,.yui-cssbase h2,.yui-cssbase h3,.yui-cssbase h4,.yui-cssbase h5,.yui-cssbase h6,.yui-cssbase strong { /*bringing boldness back to headers and the strong element*/ font-weight:bold; } .yui-cssbase abbr,.yui-cssbase acronym { /*indicating to users that more info is available */ border-bottom:1px dotted #000; cursor:help; } .yui-cssbase em { /*bringing italics back to the em element*/ font-style:italic; } .yui-cssbase blockquote,.yui-cssbase ul,.yui-cssbase ol,.yui-cssbase dl { /*giving blockquotes and lists room to breath*/ margin:1em; } .yui-cssbase ol,.yui-cssbase ul,.yui-cssbase dl { /*bringing lists on to the page with breathing room */ margin-left:2em; } .yui-cssbase ol li { /*giving OL's LIs generated numbers*/ list-style: decimal outside; } .yui-cssbase ul li { /*giving UL's LIs generated disc markers*/ list-style: disc outside; } .yui-cssbase dl dd { /*giving UL's LIs generated numbers*/ margin-left:1em; } .yui-cssbase th,.yui-cssbase td { /*borders and padding to make the table readable*/ border:1px solid #000; padding:.5em; } .yui-cssbase th { /*distinguishing table headers from data cells*/ font-weight:bold; text-align:center; } .yui-cssbase caption { /*coordinated margin to match cell's padding*/ margin-bottom:.5em; /*centered so it doesn't blend in to other content*/ text-align:center; } .yui-cssbase p,.yui-cssbase fieldset,.yui-cssbase table,.yui-cssbase pre { /*so things don't run into each other*/ margin-bottom:1em; } /* setting a consistent width, 160px; control of type=file still not possible */ .yui-cssbase input[type=text],.yui-cssbase input[type=password],.yui-cssbase textarea{width:12.25em;*width:11.9em;} gdata-2.0.18/samples/apps/marketplace_sample/appengine_utilities/interface/css/cssbase/base.css0000640036466300116100000000336012156622362033037 0ustar afshareng00000000000000/* Copyright (c) 2008, Yahoo! Inc. All rights reserved. Code licensed under the BSD License: http://developer.yahoo.net/yui/license.txt version: 3.0.0pr1 */ /* base.css, part of YUI's CSS Foundation */ h1 { /*18px via YUI Fonts CSS foundation*/ font-size:138.5%; } h2 { /*16px via YUI Fonts CSS foundation*/ font-size:123.1%; } h3 { /*14px via YUI Fonts CSS foundation*/ font-size:108%; } h1,h2,h3 { /* top & bottom margin based on font size */ margin:1em 0; } h1,h2,h3,h4,h5,h6,strong { /*bringing boldness back to headers and the strong element*/ font-weight:bold; } abbr,acronym { /*indicating to users that more info is available */ border-bottom:1px dotted #000; cursor:help; } em { /*bringing italics back to the em element*/ font-style:italic; } blockquote,ul,ol,dl { /*giving blockquotes and lists room to breath*/ margin:1em; } ol,ul,dl { /*bringing lists on to the page with breathing room */ margin-left:2em; } ol li { /*giving OL's LIs generated numbers*/ list-style: decimal outside; } ul li { /*giving UL's LIs generated disc markers*/ list-style: disc outside; } dl dd { /*giving UL's LIs generated numbers*/ margin-left:1em; } th,td { /*borders and padding to make the table readable*/ border:1px solid #000; padding:.5em; } th { /*distinguishing table headers from data cells*/ font-weight:bold; text-align:center; } caption { /*coordinated margin to match cell's padding*/ margin-bottom:.5em; /*centered so it doesn't blend in to other content*/ text-align:center; } p,fieldset,table,pre { /*so things don't run into each other*/ margin-bottom:1em; } /* setting a consistent width, 160px; control of type=file still not possible */ input[type=text],input[type=password],textarea{width:12.25em;*width:11.9em;} gdata-2.0.18/samples/apps/marketplace_sample/appengine_utilities/interface/css/cssbase/base-min.css0000640036466300116100000000336012156622362033620 0ustar afshareng00000000000000/* Copyright (c) 2008, Yahoo! Inc. All rights reserved. Code licensed under the BSD License: http://developer.yahoo.net/yui/license.txt version: 3.0.0pr1 */ /* base.css, part of YUI's CSS Foundation */ h1 { /*18px via YUI Fonts CSS foundation*/ font-size:138.5%; } h2 { /*16px via YUI Fonts CSS foundation*/ font-size:123.1%; } h3 { /*14px via YUI Fonts CSS foundation*/ font-size:108%; } h1,h2,h3 { /* top & bottom margin based on font size */ margin:1em 0; } h1,h2,h3,h4,h5,h6,strong { /*bringing boldness back to headers and the strong element*/ font-weight:bold; } abbr,acronym { /*indicating to users that more info is available */ border-bottom:1px dotted #000; cursor:help; } em { /*bringing italics back to the em element*/ font-style:italic; } blockquote,ul,ol,dl { /*giving blockquotes and lists room to breath*/ margin:1em; } ol,ul,dl { /*bringing lists on to the page with breathing room */ margin-left:2em; } ol li { /*giving OL's LIs generated numbers*/ list-style: decimal outside; } ul li { /*giving UL's LIs generated disc markers*/ list-style: disc outside; } dl dd { /*giving UL's LIs generated numbers*/ margin-left:1em; } th,td { /*borders and padding to make the table readable*/ border:1px solid #000; padding:.5em; } th { /*distinguishing table headers from data cells*/ font-weight:bold; text-align:center; } caption { /*coordinated margin to match cell's padding*/ margin-bottom:.5em; /*centered so it doesn't blend in to other content*/ text-align:center; } p,fieldset,table,pre { /*so things don't run into each other*/ margin-bottom:1em; } /* setting a consistent width, 160px; control of type=file still not possible */ input[type=text],input[type=password],textarea{width:12.25em;*width:11.9em;} ././@LongLink0000000000000000000000000000015000000000000011211 Lustar 00000000000000gdata-2.0.18/samples/apps/marketplace_sample/appengine_utilities/interface/css/cssbase/base-context.cssgdata-2.0.18/samples/apps/marketplace_sample/appengine_utilities/interface/css/cssbase/base-context.0000640036466300116100000000432112156622362034006 0ustar afshareng00000000000000/* Copyright (c) 2008, Yahoo! Inc. All rights reserved. Code licensed under the BSD License: http://developer.yahoo.net/yui/license.txt version: 3.0.0pr1 */ /* base.css, part of YUI's CSS Foundation */ .yui-cssbase h1 { /*18px via YUI Fonts CSS foundation*/ font-size:138.5%; } .yui-cssbase h2 { /*16px via YUI Fonts CSS foundation*/ font-size:123.1%; } .yui-cssbase h3 { /*14px via YUI Fonts CSS foundation*/ font-size:108%; } .yui-cssbase h1,.yui-cssbase h2,.yui-cssbase h3 { /* top & bottom margin based on font size */ margin:1em 0; } .yui-cssbase h1,.yui-cssbase h2,.yui-cssbase h3,.yui-cssbase h4,.yui-cssbase h5,.yui-cssbase h6,.yui-cssbase strong { /*bringing boldness back to headers and the strong element*/ font-weight:bold; } .yui-cssbase abbr,.yui-cssbase acronym { /*indicating to users that more info is available */ border-bottom:1px dotted #000; cursor:help; } .yui-cssbase em { /*bringing italics back to the em element*/ font-style:italic; } .yui-cssbase blockquote,.yui-cssbase ul,.yui-cssbase ol,.yui-cssbase dl { /*giving blockquotes and lists room to breath*/ margin:1em; } .yui-cssbase ol,.yui-cssbase ul,.yui-cssbase dl { /*bringing lists on to the page with breathing room */ margin-left:2em; } .yui-cssbase ol li { /*giving OL's LIs generated numbers*/ list-style: decimal outside; } .yui-cssbase ul li { /*giving UL's LIs generated disc markers*/ list-style: disc outside; } .yui-cssbase dl dd { /*giving UL's LIs generated numbers*/ margin-left:1em; } .yui-cssbase th,.yui-cssbase td { /*borders and padding to make the table readable*/ border:1px solid #000; padding:.5em; } .yui-cssbase th { /*distinguishing table headers from data cells*/ font-weight:bold; text-align:center; } .yui-cssbase caption { /*coordinated margin to match cell's padding*/ margin-bottom:.5em; /*centered so it doesn't blend in to other content*/ text-align:center; } .yui-cssbase p,.yui-cssbase fieldset,.yui-cssbase table,.yui-cssbase pre { /*so things don't run into each other*/ margin-bottom:1em; } /* setting a consistent width, 160px; control of type=file still not possible */ .yui-cssbase input[type=text],.yui-cssbase input[type=password],.yui-cssbase textarea{width:12.25em;*width:11.9em;} gdata-2.0.18/samples/apps/marketplace_sample/appengine_utilities/interface/css/cssreset/0000750036466300116100000000000012156625015031616 5ustar afshareng00000000000000././@LongLink0000000000000000000000000000015200000000000011213 Lustar 00000000000000gdata-2.0.18/samples/apps/marketplace_sample/appengine_utilities/interface/css/cssreset/reset-context.cssgdata-2.0.18/samples/apps/marketplace_sample/appengine_utilities/interface/css/cssreset/reset-contex0000640036466300116100000000457112156622362034173 0ustar afshareng00000000000000/* Copyright (c) 2008, Yahoo! Inc. All rights reserved. Code licensed under the BSD License: http://developer.yahoo.net/yui/license.txt version: 3.0.0pr1 */ /* TODO will need to remove settings on HTML since we can't namespace it. TODO with the prefix, should I group by selector or property for weight savings? */ .yui-cssreset html{ color:#000; background:#FFF; } /* TODO remove settings on BODY since we can't namespace it. */ /* TODO test putting a class on HEAD. - Fails on FF. */ .yui-cssreset body, .yui-cssreset div, .yui-cssreset dl, .yui-cssreset dt, .yui-cssreset dd, .yui-cssreset ul, .yui-cssreset ol, .yui-cssreset li, .yui-cssreset h1, .yui-cssreset h2, .yui-cssreset h3, .yui-cssreset h4, .yui-cssreset h5, .yui-cssreset h6, .yui-cssreset pre, .yui-cssreset code, .yui-cssreset form, .yui-cssreset fieldset, .yui-cssreset legend, .yui-cssreset input, .yui-cssreset textarea, .yui-cssreset p, .yui-cssreset blockquote, .yui-cssreset th, .yui-cssreset td { margin:0; padding:0; } .yui-cssreset table { border-collapse:collapse; border-spacing:0; } .yui-cssreset fieldset, .yui-cssreset img { border:0; } /* TODO think about hanlding inheritence differently, maybe letting IE6 fail a bit... */ .yui-cssreset address, .yui-cssreset caption, .yui-cssreset cite, .yui-cssreset code, .yui-cssreset dfn, .yui-cssreset em, .yui-cssreset strong, .yui-cssreset th, .yui-cssreset var { font-style:normal; font-weight:normal; } /* TODO Figure out where this list-style rule is best set. Hedger has a request to investigate. */ .yui-cssreset li { list-style:none; } .yui-cssreset caption, .yui-cssreset th { text-align:left; } .yui-cssreset h1, .yui-cssreset h2, .yui-cssreset h3, .yui-cssreset h4, .yui-cssreset h5, .yui-cssreset h6 { font-size:100%; font-weight:normal; } .yui-cssreset q:before, .yui-cssreset q:after { content:''; } .yui-cssreset abbr, .yui-cssreset acronym { border:0; font-variant:normal; } /* to preserve line-height and selector appearance */ .yui-cssreset sup { vertical-align:text-top; } .yui-cssreset sub { vertical-align:text-bottom; } .yui-cssreset input, .yui-cssreset textarea, .yui-cssreset select { font-family:inherit; font-size:inherit; font-weight:inherit; } /*to enable resizing for IE*/ .yui-cssreset input, .yui-cssreset textarea, .yui-cssreset select { *font-size:100%; } /*because legend doesn't inherit in IE */ .yui-cssreset legend { color:#000; }././@LongLink0000000000000000000000000000015600000000000011217 Lustar 00000000000000gdata-2.0.18/samples/apps/marketplace_sample/appengine_utilities/interface/css/cssreset/reset-context-min.cssgdata-2.0.18/samples/apps/marketplace_sample/appengine_utilities/interface/css/cssreset/reset-contex0000640036466300116100000000457112156622362034173 0ustar afshareng00000000000000/* Copyright (c) 2008, Yahoo! Inc. All rights reserved. Code licensed under the BSD License: http://developer.yahoo.net/yui/license.txt version: 3.0.0pr1 */ /* TODO will need to remove settings on HTML since we can't namespace it. TODO with the prefix, should I group by selector or property for weight savings? */ .yui-cssreset html{ color:#000; background:#FFF; } /* TODO remove settings on BODY since we can't namespace it. */ /* TODO test putting a class on HEAD. - Fails on FF. */ .yui-cssreset body, .yui-cssreset div, .yui-cssreset dl, .yui-cssreset dt, .yui-cssreset dd, .yui-cssreset ul, .yui-cssreset ol, .yui-cssreset li, .yui-cssreset h1, .yui-cssreset h2, .yui-cssreset h3, .yui-cssreset h4, .yui-cssreset h5, .yui-cssreset h6, .yui-cssreset pre, .yui-cssreset code, .yui-cssreset form, .yui-cssreset fieldset, .yui-cssreset legend, .yui-cssreset input, .yui-cssreset textarea, .yui-cssreset p, .yui-cssreset blockquote, .yui-cssreset th, .yui-cssreset td { margin:0; padding:0; } .yui-cssreset table { border-collapse:collapse; border-spacing:0; } .yui-cssreset fieldset, .yui-cssreset img { border:0; } /* TODO think about hanlding inheritence differently, maybe letting IE6 fail a bit... */ .yui-cssreset address, .yui-cssreset caption, .yui-cssreset cite, .yui-cssreset code, .yui-cssreset dfn, .yui-cssreset em, .yui-cssreset strong, .yui-cssreset th, .yui-cssreset var { font-style:normal; font-weight:normal; } /* TODO Figure out where this list-style rule is best set. Hedger has a request to investigate. */ .yui-cssreset li { list-style:none; } .yui-cssreset caption, .yui-cssreset th { text-align:left; } .yui-cssreset h1, .yui-cssreset h2, .yui-cssreset h3, .yui-cssreset h4, .yui-cssreset h5, .yui-cssreset h6 { font-size:100%; font-weight:normal; } .yui-cssreset q:before, .yui-cssreset q:after { content:''; } .yui-cssreset abbr, .yui-cssreset acronym { border:0; font-variant:normal; } /* to preserve line-height and selector appearance */ .yui-cssreset sup { vertical-align:text-top; } .yui-cssreset sub { vertical-align:text-bottom; } .yui-cssreset input, .yui-cssreset textarea, .yui-cssreset select { font-family:inherit; font-size:inherit; font-weight:inherit; } /*to enable resizing for IE*/ .yui-cssreset input, .yui-cssreset textarea, .yui-cssreset select { *font-size:100%; } /*because legend doesn't inherit in IE */ .yui-cssreset legend { color:#000; }gdata-2.0.18/samples/apps/marketplace_sample/appengine_utilities/interface/css/cssreset/reset.css0000640036466300116100000000306112156622362033455 0ustar afshareng00000000000000/* Copyright (c) 2008, Yahoo! Inc. All rights reserved. Code licensed under the BSD License: http://developer.yahoo.net/yui/license.txt version: 3.0.0pr1 */ /* TODO will need to remove settings on HTML since we can't namespace it. TODO with the prefix, should I group by selector or property for weight savings? */ html{ color:#000; background:#FFF; } /* TODO remove settings on BODY since we can't namespace it. */ /* TODO test putting a class on HEAD. - Fails on FF. */ body, div, dl, dt, dd, ul, ol, li, h1, h2, h3, h4, h5, h6, pre, code, form, fieldset, legend, input, textarea, p, blockquote, th, td { margin:0; padding:0; } table { border-collapse:collapse; border-spacing:0; } fieldset, img { border:0; } /* TODO think about hanlding inheritence differently, maybe letting IE6 fail a bit... */ address, caption, cite, code, dfn, em, strong, th, var { font-style:normal; font-weight:normal; } /* TODO Figure out where this list-style rule is best set. Hedger has a request to investigate. */ li { list-style:none; } caption, th { text-align:left; } h1, h2, h3, h4, h5, h6 { font-size:100%; font-weight:normal; } q:before, q:after { content:''; } abbr, acronym { border:0; font-variant:normal; } /* to preserve line-height and selector appearance */ sup { vertical-align:text-top; } sub { vertical-align:text-bottom; } input, textarea, select { font-family:inherit; font-size:inherit; font-weight:inherit; } /*to enable resizing for IE*/ input, textarea, select { *font-size:100%; } /*because legend doesn't inherit in IE */ legend { color:#000; }././@LongLink0000000000000000000000000000014600000000000011216 Lustar 00000000000000gdata-2.0.18/samples/apps/marketplace_sample/appengine_utilities/interface/css/cssreset/reset-min.cssgdata-2.0.18/samples/apps/marketplace_sample/appengine_utilities/interface/css/cssreset/reset-min.cs0000640036466300116100000000306112156622362034053 0ustar afshareng00000000000000/* Copyright (c) 2008, Yahoo! Inc. All rights reserved. Code licensed under the BSD License: http://developer.yahoo.net/yui/license.txt version: 3.0.0pr1 */ /* TODO will need to remove settings on HTML since we can't namespace it. TODO with the prefix, should I group by selector or property for weight savings? */ html{ color:#000; background:#FFF; } /* TODO remove settings on BODY since we can't namespace it. */ /* TODO test putting a class on HEAD. - Fails on FF. */ body, div, dl, dt, dd, ul, ol, li, h1, h2, h3, h4, h5, h6, pre, code, form, fieldset, legend, input, textarea, p, blockquote, th, td { margin:0; padding:0; } table { border-collapse:collapse; border-spacing:0; } fieldset, img { border:0; } /* TODO think about hanlding inheritence differently, maybe letting IE6 fail a bit... */ address, caption, cite, code, dfn, em, strong, th, var { font-style:normal; font-weight:normal; } /* TODO Figure out where this list-style rule is best set. Hedger has a request to investigate. */ li { list-style:none; } caption, th { text-align:left; } h1, h2, h3, h4, h5, h6 { font-size:100%; font-weight:normal; } q:before, q:after { content:''; } abbr, acronym { border:0; font-variant:normal; } /* to preserve line-height and selector appearance */ sup { vertical-align:text-top; } sub { vertical-align:text-bottom; } input, textarea, select { font-family:inherit; font-size:inherit; font-weight:inherit; } /*to enable resizing for IE*/ input, textarea, select { *font-size:100%; } /*because legend doesn't inherit in IE */ legend { color:#000; }gdata-2.0.18/samples/apps/marketplace_sample/appengine_utilities/interface/css/main.css0000640036466300116100000000630412156622362031427 0ustar afshareng00000000000000/* project: Conceptnova (free CSS template) author: luka cvrk (www.solucija.com) */ *{ margin: 0; padding: 0; }* body { background: #fff repeat-x; font: 74% Arial, Helvetica, Sans-Serif; color: #454545; line-height: 1.6em; } a { text-decoration: none; color: #C40000; background: inherit; } a:hover { color: #808080; background: inherit; } a:focus { outline: 0; } h1 { font-size: 2.8em; line-height: 35px; } h2 { color: #000; font: bold 170% Arial, Sans-Serif; letter-spacing: -1px; padding: 0; margin: 0 0 10px; } img { border: 0; } .grey { color: #484848; } #wrap { width: 900px; margin: 10px auto 0; } #logo { float: left; width: 400px; margin: 0 0 7px; } #header_top { background: no-repeat top right; border-right: 2px solid #fff; color: #454545; padding: 10px 0 0 7px; clear: both; margin: 0 0 0px 0; height: 74px; } #header_top p { padding: 0; margin: 0; } #header_top ul { float: right; padding: 12px 160px 0 0; margin: 0 0 5px 0; } #header_top ul li { list-style-type: none; display: inline; font-weight: bold; } #header_top ul li a { padding-left: 7px; background: #FFF no-repeat center left; margin-right: 5px; color: #C40000; } #header_top ul li a:hover { padding-left: 7px; background: #FFF no-repeat center left; margin-right: 5px; color: #808080; } #header_bottom { background: #454545 no-repeat top right; color: #EBEBEB; padding: 20px 240px 20px 10px; clear: both; border-left: 2px solid #fff; border-right: 2px solid #fff; margin: 0 0 1px 0; } #header_bottom h2 { color: #F3F2BF; width: 250px; margin: 0 0 10px; background: #454545; } #header_bottom a { text-decoration: none; color: #FFFFFF; background: inherit; } #header_bottom a:hover { text-decoration: underline; } #header_bottom ul {padding-top: 1em;} #header_bottom li {margin-left: 1em;} #slogan { clear: left; width: 730px; border-top: 1px solid #eee; margin: 0; padding: 5px 0 0 0; } #maincontent { padding: 20px 0; clear: both; background: repeat-x; } #left { float: left; width: 630px; padding: 5px 15px 0 5px; margin: 0 0 25px 0; } #left p { margin: 0 0 15px; } #right { width: 220px; float: right; } #info { border: 1px solid #ccc; padding: 7px; font-size: .9em; background: #f4f4f4; } #searchform { background: no-repeat; padding: 8px 4px 10px 4px; height: 27px; font: bold 0.9em Arial, Sans-Serif; margin: 0 0 20px; } #searchform p { padding: 0; margin: 0; } input.search { width: 127px; border: none; background: no-repeat; padding: 7px; margin: 0 4px 0 5px; } input.submit { width: 53px; height: 29px; border: none; background: #FFF no-repeat; font: bold 1.2em Arial, Sans-Serif; padding: 0px; color: #FFF; } #footer { clear: both; padding: 10px; margin: 10px 0 0 0; border-top: 1px solid #ccc; line-height: 2em; } .code { background: #454545 no-repeat top right; color: #EBEBEB; padding: 20px 240px 20px 10px; clear: both; border-left: 2px solid #fff; border-right: 2px solid #fff; margin: 0 0 1px 0; } pre { white-space: pre-wrap; /* CSS2.1 compliant */ white-space: -moz-pre-wrap; /* Mozilla-based browsers */ white-space: o-pre-wrap; /* Opera 7+ */ } td { padding: .5em; } th { background: #454545 no-repeat top right; color: #EBEBEB; }gdata-2.0.18/samples/apps/marketplace_sample/appengine_utilities/interface/css/cssgrids/0000750036466300116100000000000012156625015031604 5ustar afshareng00000000000000././@LongLink0000000000000000000000000000015600000000000011217 Lustar 00000000000000gdata-2.0.18/samples/apps/marketplace_sample/appengine_utilities/interface/css/cssgrids/grids-context-min.cssgdata-2.0.18/samples/apps/marketplace_sample/appengine_utilities/interface/css/cssgrids/grids-contex0000640036466300116100000001636612156622362034154 0ustar afshareng00000000000000/* Copyright (c) 2008, Yahoo! Inc. All rights reserved. Code licensed under the BSD License: http://developer.yahoo.net/yui/license.txt version: 3.0.0pr1 */ /* TODO README: removed yui-t7 */ /** * * The YUI CSS Foundation uses the *property and _property CSS filter * techniques to shield a value from A-grade browsers [1] other than * IE6 & IE7 (*property) and IE6 (_property) * / /** * Center's the page / Section: General Rules */ /* TODO Maybe remove */ .yui-grids body { text-align:center; margin-left:auto;margin-right:auto; } /* TODO - not sure I need this if the BD is already clearing. #ft { clear:both; } */ /* Section: Page Width Rules (#doc, #doc2, #doc3, #doc4) */ /* Subsection: General */ .yui-grids .yui-d0, /* 100% */ .yui-grids .yui-d1, /* 750px */ .yui-grids .yui-d2, /* 950px */ .yui-grids .yui-d3, /* 974px */ .yui-t1, .yui-t2, .yui-t3, .yui-t4, .yui-t5, .yui-t6 /* the Ts are here to collect margin and text-alignment rules */{ margin:auto; text-align:left; width:57.69em;*width:56.25em; /* doc1*/ min-width:750px; } .yui-t1, .yui-t2, .yui-t3, .yui-t4, .yui-t5, .yui-t6 {width:100%;} /* Subsection: 100% (doc) */ .yui-grids .yui-d0 { /* Left and Right margins are not a structural part of Grids. Without them Grids works fine, but content bleeds to the very edge of the document, which often impairs readability and usability. They are provided because they prevent the content from "bleeding" into the browser's chrome.*/ margin:auto 10px; width:auto; } .yui-grids .yui-d0f {width:100%;} /* Subsection: 950 Centered (doc2) */ .yui-grids .yui-d2 {width:73.076em;*width:71.25em;} .yui-grids .yui-d2f {width:950px;} /* Subsection: 974 Centered (doc3) */ .yui-grids .yui-d3 {width:74.923em;*width:73.05em;} .yui-grids .yui-d3f {width:974px;} /* Section: Preset Template Rules (.yui-t[1-6]) */ /* Subsection: General */ /* to preserve source-order independence for Gecko without breaking */ .yui-b{position:relative;} .yui-b{_position:static;} .yui-main .yui-b{position:static;} .yui-main {width:100%;} .yui-t1 .yui-main, .yui-t2 .yui-main, .yui-t3 .yui-main{float:right;margin-left:-25em;/* IE: preserve layout at narrow widths */} .yui-t4 .yui-main, .yui-t5 .yui-main, .yui-t6 .yui-main{float:left;margin-right:-25em;/* IE: preserve layout at narrow widths */} /* Subsection: For Specific Template Presets */ /** * Nudge down to get to 13px equivalent for these form elements */ /* TODO Create t1-6's that are based on fixed widths */ .yui-t1 .yui-b {float:left;width:12.30769em;*width:12.00em;} .yui-t1 .yui-main .yui-b{margin-left:13.30769em;*margin-left:12.975em;} .yui-t2 .yui-b {float:left;width:13.84615em;*width:13.50em;} .yui-t2 .yui-main .yui-b {margin-left:14.84615em;*margin-left:14.475em;} .yui-t3 .yui-b {float:left;width:23.0769em;*width:22.50em;} .yui-t3 .yui-main .yui-b {margin-left:24.0769em;*margin-left:23.475em;} .yui-t4 .yui-b {float:right;width:13.8456em;*width:13.50em;} .yui-t4 .yui-main .yui-b {margin-right:14.8456em;*margin-right:14.475em;} .yui-t5 .yui-b {float:right;width:18.4615em;*width:18.00em;} .yui-t5 .yui-main .yui-b {margin-right:19.4615em;*margin-right:18.975em;} .yui-t6 .yui-b {float:right;width:23.0769em;*width:22.50em;} .yui-t6 .yui-main .yui-b {margin-right:24.0769em;*margin-right:23.475em;} .yui-t7 .yui-main .yui-b {display:block;margin:0 0 1em 0;} .yui-main .yui-b {float:none;width:auto;} /* Section: Grids and Nesting Grids */ /* Subsection: Children generally take half the available space */ .yui-gb .yui-u, .yui-g .yui-gb .yui-u, .yui-gb .yui-g, .yui-gb .yui-gb, .yui-gb .yui-gc, .yui-gb .yui-gd, .yui-gb .yui-ge, .yui-gb .yui-gf, .yui-gc .yui-u, .yui-gc .yui-g, .yui-gd .yui-u {float:right;} /*Float units (and sub grids) to the right */ .yui-g .yui-u, .yui-g .yui-g, .yui-g .yui-gb, .yui-g .yui-gc, .yui-g .yui-gd, .yui-g .yui-ge, .yui-g .yui-gf, .yui-gc .yui-u, .yui-gd .yui-g, .yui-g .yui-gc .yui-u, .yui-ge .yui-u, .yui-ge .yui-g, .yui-gf .yui-g, .yui-gf .yui-u{float:right;} /*Float units (and sub grids) to the left */ .yui-g div.first, .yui-gb div.first, .yui-gc div.first, .yui-gd div.first, .yui-ge div.first, .yui-gf div.first, .yui-g .yui-gc div.first, .yui-g .yui-ge div.first, .yui-gc div.first div.first {float:left;} .yui-g .yui-u, .yui-g .yui-g, .yui-g .yui-gb, .yui-g .yui-gc, .yui-g .yui-gd, .yui-g .yui-ge, .yui-g .yui-gf {width:49.1%;} .yui-gb .yui-u, .yui-g .yui-gb .yui-u, .yui-gb .yui-g, .yui-gb .yui-gb, .yui-gb .yui-gc, .yui-gb .yui-gd, .yui-gb .yui-ge, .yui-gb .yui-gf, .yui-gc .yui-u, .yui-gc .yui-g, .yui-gd .yui-u {width:32%;margin-left:2.0%;} /* Give IE some extra breathing room for 1/3-based rounding issues */ .yui-gb .yui-u {*width:31.8%;*margin-left:1.9%;} .yui-gc div.first, .yui-gd .yui-u {width:66%;_width:65.7%;} .yui-gd div.first {width:32%;_width:31.5%;} .yui-ge div.first, .yui-gf .yui-u{width:74.2%;_width:74%;} .yui-ge .yui-u, .yui-gf div.first {width:24%;_width:23.8%;} .yui-g .yui-gb div.first, .yui-gb div.first, .yui-gc div.first, .yui-gd div.first {margin-left:0;} /* Section: Deep Nesting */ .yui-g .yui-g .yui-u, .yui-gb .yui-g .yui-u, .yui-gc .yui-g .yui-u, .yui-gd .yui-g .yui-u, .yui-ge .yui-g .yui-u, .yui-gf .yui-g .yui-u {width:49%;*width:48.1%;*margin-left:0;} .yui-g .yui-gb div.first, .yui-gb .yui-gb div.first {*margin-right:0;*width:32%;_width:31.7%;} .yui-g .yui-gc div.first, .yui-gd .yui-g {width:66%;} .yui-gb .yui-g div.first {*margin-right:4%;_margin-right:1.3%;} .yui-gb .yui-gc div.first, .yui-gb .yui-gd div.first {*margin-right:0;} .yui-gb .yui-gb .yui-u, .yui-gb .yui-gc .yui-u {*margin-left:1.8%;_margin-left:4%;} .yui-g .yui-gb .yui-u {_margin-left:1.0%;} .yui-gb .yui-gd .yui-u {*width:66%;_width:61.2%;} .yui-gb .yui-gd div.first {*width:31%;_width:29.5%;} .yui-g .yui-gc .yui-u, .yui-gb .yui-gc .yui-u {width:32%;_float:right;margin-right:0;_margin-left:0;} .yui-gb .yui-gc div.first {width:66%;*float:left;*margin-left:0;} .yui-gb .yui-ge .yui-u, .yui-gb .yui-gf .yui-u {margin:0;} .yui-gb .yui-gb .yui-u {_margin-left:.7%;} .yui-gb .yui-g div.first, .yui-gb .yui-gb div.first {*margin-left:0;} .yui-gc .yui-g .yui-u, .yui-gd .yui-g .yui-u {*width:48.1%;*margin-left:0;} .yui-gb .yui-gd div.first {width:32%;} .yui-g .yui-gd div.first {_width:29.9%;} .yui-ge .yui-g {width:24%;} .yui-gf .yui-g {width:74.2%;} .yui-gb .yui-ge div.yui-u, .yui-gb .yui-gf div.yui-u {float:right;} .yui-gb .yui-ge div.first, .yui-gb .yui-gf div.first {float:left;} /* Width Accommodation for Nested Contexts */ .yui-gb .yui-ge .yui-u, .yui-gb .yui-gf div.first {*width:24%;_width:20%;} /* Width Accommodation for Nested Contexts */ .yui-gb .yui-ge div.first, .yui-gb .yui-gf .yui-u{*width:73.5%;_width:65.5%;} /* Patch for GD within GE */ .yui-ge div.first .yui-gd .yui-u {width:65%;} .yui-ge div.first .yui-gd div.first {width:32%;} /* Section: Clearing */ #bd:after, .yui-g:after, .yui-gb:after, .yui-gc:after, .yui-gd:after, .yui-ge:after, .yui-gf:after{content:".";display:block;height:0;clear:both;visibility:hidden;} #bd, .yui-g, .yui-gb, .yui-gc, .yui-gd, .yui-ge, .yui-gf{zoom:1;} gdata-2.0.18/samples/apps/marketplace_sample/appengine_utilities/interface/css/cssgrids/grids.css0000640036466300116100000001632412156622362033437 0ustar afshareng00000000000000/* Copyright (c) 2008, Yahoo! Inc. All rights reserved. Code licensed under the BSD License: http://developer.yahoo.net/yui/license.txt version: 3.0.0pr1 */ /* TODO README: removed yui-t7 */ /** * * The YUI CSS Foundation uses the *property and _property CSS filter * techniques to shield a value from A-grade browsers [1] other than * IE6 & IE7 (*property) and IE6 (_property) * / /** * Center's the page / Section: General Rules */ /* TODO Maybe remove */ body { text-align:center; margin-left:auto;margin-right:auto; } /* TODO - not sure I need this if the BD is already clearing. #ft { clear:both; } */ /* Section: Page Width Rules (#doc, #doc2, #doc3, #doc4) */ /* Subsection: General */ .yui-d0, /* 100% */ .yui-d1, /* 750px */ .yui-d1f, /* 750px fixed */ .yui-d2, /* 950px */ .yui-d2f, /* 950px fixed */ .yui-d3, /* 974px */ .yui-d3f, /* 974px fixed */ .yui-t1, .yui-t2, .yui-t3, .yui-t4, .yui-t5, .yui-t6 /* the Ts are here to collect margin and text-alignment rules */{ margin:auto; text-align:left; width:57.69em;*width:56.25em; /* doc1*/ min-width:750px; } .yui-t1, .yui-t2, .yui-t3, .yui-t4, .yui-t5, .yui-t6 {width:100%;} /* Subsection: 100% (doc) */ .yui-d0 { /* Left and Right margins are not a structural part of Grids. Without them Grids works fine, but content bleeds to the very edge of the document, which often impairs readability and usability. They are provided because they prevent the content from "bleeding" into the browser's chrome.*/ margin:auto 10px; width:auto; } .yui-d0f {width:100%;} /* Subsection: 950 Centered (doc2) */ .yui-d2 {width:73.076em;*width:71.25em;} .yui-d2f {width:950px;} /* Subsection: 974 Centered (doc3) */ .yui-d3 {width:74.923em;*width:73.05em;} .yui-d3f {width:974px;} /* Section: Preset Template Rules (.yui-t[1-6]) */ /* Subsection: General */ /* to preserve source-order independence for Gecko without breaking */ .yui-b{position:relative;} .yui-b{_position:static;} .yui-main .yui-b{position:static;} .yui-main {width:100%;} .yui-t1 .yui-main, .yui-t2 .yui-main, .yui-t3 .yui-main{float:right;margin-left:-25em;/* IE: preserve layout at narrow widths */} .yui-t4 .yui-main, .yui-t5 .yui-main, .yui-t6 .yui-main{float:left;margin-right:-25em;/* IE: preserve layout at narrow widths */} /* Subsection: For Specific Template Presets */ /** * Nudge down to get to 13px equivalent for these form elements */ /* TODO Create t1-6's that are based on fixed widths */ .yui-t1 .yui-b {float:left;width:12.30769em;*width:12.00em;} .yui-t1 .yui-main .yui-b{margin-left:13.30769em;*margin-left:12.975em;} .yui-t2 .yui-b {float:left;width:13.84615em;*width:13.50em;} .yui-t2 .yui-main .yui-b {margin-left:14.84615em;*margin-left:14.475em;} .yui-t3 .yui-b {float:left;width:23.0769em;*width:22.50em;} .yui-t3 .yui-main .yui-b {margin-left:24.0769em;*margin-left:23.475em;} .yui-t4 .yui-b {float:right;width:13.8456em;*width:13.50em;} .yui-t4 .yui-main .yui-b {margin-right:14.8456em;*margin-right:14.475em;} .yui-t5 .yui-b {float:right;width:18.4615em;*width:18.00em;} .yui-t5 .yui-main .yui-b {margin-right:19.4615em;*margin-right:18.975em;} .yui-t6 .yui-b {float:right;width:23.0769em;*width:22.50em;} .yui-t6 .yui-main .yui-b {margin-right:24.0769em;*margin-right:23.475em;} .yui-t7 .yui-main .yui-b {display:block;margin:0 0 1em 0;} .yui-main .yui-b {float:none;width:auto;} /* Section: Grids and Nesting Grids */ /* Subsection: Children generally take half the available space */ .yui-gb .yui-u, .yui-g .yui-gb .yui-u, .yui-gb .yui-g, .yui-gb .yui-gb, .yui-gb .yui-gc, .yui-gb .yui-gd, .yui-gb .yui-ge, .yui-gb .yui-gf, .yui-gc .yui-u, .yui-gc .yui-g, .yui-gd .yui-u {float:right;} /*Float units (and sub grids) to the right */ .yui-g .yui-u, .yui-g .yui-g, .yui-g .yui-gb, .yui-g .yui-gc, .yui-g .yui-gd, .yui-g .yui-ge, .yui-g .yui-gf, .yui-gc .yui-u, .yui-gd .yui-g, .yui-g .yui-gc .yui-u, .yui-ge .yui-u, .yui-ge .yui-g, .yui-gf .yui-g, .yui-gf .yui-u{float:right;} /*Float units (and sub grids) to the left */ .yui-g div.first, .yui-gb div.first, .yui-gc div.first, .yui-gd div.first, .yui-ge div.first, .yui-gf div.first, .yui-g .yui-gc div.first, .yui-g .yui-ge div.first, .yui-gc div.first div.first {float:left;} .yui-g .yui-u, .yui-g .yui-g, .yui-g .yui-gb, .yui-g .yui-gc, .yui-g .yui-gd, .yui-g .yui-ge, .yui-g .yui-gf {width:49.1%;} .yui-gb .yui-u, .yui-g .yui-gb .yui-u, .yui-gb .yui-g, .yui-gb .yui-gb, .yui-gb .yui-gc, .yui-gb .yui-gd, .yui-gb .yui-ge, .yui-gb .yui-gf, .yui-gc .yui-u, .yui-gc .yui-g, .yui-gd .yui-u {width:32%;margin-left:2.0%;} /* Give IE some extra breathing room for 1/3-based rounding issues */ .yui-gb .yui-u {*width:31.8%;*margin-left:1.9%;} .yui-gc div.first, .yui-gd .yui-u {width:66%;_width:65.7%;} .yui-gd div.first {width:32%;_width:31.5%;} .yui-ge div.first, .yui-gf .yui-u{width:74.2%;_width:74%;} .yui-ge .yui-u, .yui-gf div.first {width:24%;_width:23.8%;} .yui-g .yui-gb div.first, .yui-gb div.first, .yui-gc div.first, .yui-gd div.first {margin-left:0;} /* Section: Deep Nesting */ .yui-g .yui-g .yui-u, .yui-gb .yui-g .yui-u, .yui-gc .yui-g .yui-u, .yui-gd .yui-g .yui-u, .yui-ge .yui-g .yui-u, .yui-gf .yui-g .yui-u {width:49%;*width:48.1%;*margin-left:0;} .yui-g .yui-gb div.first, .yui-gb .yui-gb div.first {*margin-right:0;*width:32%;_width:31.7%;} .yui-g .yui-gc div.first, .yui-gd .yui-g {width:66%;} .yui-gb .yui-g div.first {*margin-right:4%;_margin-right:1.3%;} .yui-gb .yui-gc div.first, .yui-gb .yui-gd div.first {*margin-right:0;} .yui-gb .yui-gb .yui-u, .yui-gb .yui-gc .yui-u {*margin-left:1.8%;_margin-left:4%;} .yui-g .yui-gb .yui-u {_margin-left:1.0%;} .yui-gb .yui-gd .yui-u {*width:66%;_width:61.2%;} .yui-gb .yui-gd div.first {*width:31%;_width:29.5%;} .yui-g .yui-gc .yui-u, .yui-gb .yui-gc .yui-u {width:32%;_float:right;margin-right:0;_margin-left:0;} .yui-gb .yui-gc div.first {width:66%;*float:left;*margin-left:0;} .yui-gb .yui-ge .yui-u, .yui-gb .yui-gf .yui-u {margin:0;} .yui-gb .yui-gb .yui-u {_margin-left:.7%;} .yui-gb .yui-g div.first, .yui-gb .yui-gb div.first {*margin-left:0;} .yui-gc .yui-g .yui-u, .yui-gd .yui-g .yui-u {*width:48.1%;*margin-left:0;} .yui-gb .yui-gd div.first {width:32%;} .yui-g .yui-gd div.first {_width:29.9%;} .yui-ge .yui-g {width:24%;} .yui-gf .yui-g {width:74.2%;} .yui-gb .yui-ge div.yui-u, .yui-gb .yui-gf div.yui-u {float:right;} .yui-gb .yui-ge div.first, .yui-gb .yui-gf div.first {float:left;} /* Width Accommodation for Nested Contexts */ .yui-gb .yui-ge .yui-u, .yui-gb .yui-gf div.first {*width:24%;_width:20%;} /* Width Accommodation for Nested Contexts */ .yui-gb .yui-ge div.first, .yui-gb .yui-gf .yui-u{*width:73.5%;_width:65.5%;} /* Patch for GD within GE */ .yui-ge div.first .yui-gd .yui-u {width:65%;} .yui-ge div.first .yui-gd div.first {width:32%;} /* Section: Clearing */ #bd:after, .yui-g:after, .yui-gb:after, .yui-gc:after, .yui-gd:after, .yui-ge:after, .yui-gf:after{content:".";display:block;height:0;clear:both;visibility:hidden;} #bd, .yui-g, .yui-gb, .yui-gc, .yui-gd, .yui-ge, .yui-gf{zoom:1;} ././@LongLink0000000000000000000000000000014600000000000011216 Lustar 00000000000000gdata-2.0.18/samples/apps/marketplace_sample/appengine_utilities/interface/css/cssgrids/grids-min.cssgdata-2.0.18/samples/apps/marketplace_sample/appengine_utilities/interface/css/cssgrids/grids-min.cs0000640036466300116100000001632412156622362034035 0ustar afshareng00000000000000/* Copyright (c) 2008, Yahoo! Inc. All rights reserved. Code licensed under the BSD License: http://developer.yahoo.net/yui/license.txt version: 3.0.0pr1 */ /* TODO README: removed yui-t7 */ /** * * The YUI CSS Foundation uses the *property and _property CSS filter * techniques to shield a value from A-grade browsers [1] other than * IE6 & IE7 (*property) and IE6 (_property) * / /** * Center's the page / Section: General Rules */ /* TODO Maybe remove */ body { text-align:center; margin-left:auto;margin-right:auto; } /* TODO - not sure I need this if the BD is already clearing. #ft { clear:both; } */ /* Section: Page Width Rules (#doc, #doc2, #doc3, #doc4) */ /* Subsection: General */ .yui-d0, /* 100% */ .yui-d1, /* 750px */ .yui-d1f, /* 750px fixed */ .yui-d2, /* 950px */ .yui-d2f, /* 950px fixed */ .yui-d3, /* 974px */ .yui-d3f, /* 974px fixed */ .yui-t1, .yui-t2, .yui-t3, .yui-t4, .yui-t5, .yui-t6 /* the Ts are here to collect margin and text-alignment rules */{ margin:auto; text-align:left; width:57.69em;*width:56.25em; /* doc1*/ min-width:750px; } .yui-t1, .yui-t2, .yui-t3, .yui-t4, .yui-t5, .yui-t6 {width:100%;} /* Subsection: 100% (doc) */ .yui-d0 { /* Left and Right margins are not a structural part of Grids. Without them Grids works fine, but content bleeds to the very edge of the document, which often impairs readability and usability. They are provided because they prevent the content from "bleeding" into the browser's chrome.*/ margin:auto 10px; width:auto; } .yui-d0f {width:100%;} /* Subsection: 950 Centered (doc2) */ .yui-d2 {width:73.076em;*width:71.25em;} .yui-d2f {width:950px;} /* Subsection: 974 Centered (doc3) */ .yui-d3 {width:74.923em;*width:73.05em;} .yui-d3f {width:974px;} /* Section: Preset Template Rules (.yui-t[1-6]) */ /* Subsection: General */ /* to preserve source-order independence for Gecko without breaking */ .yui-b{position:relative;} .yui-b{_position:static;} .yui-main .yui-b{position:static;} .yui-main {width:100%;} .yui-t1 .yui-main, .yui-t2 .yui-main, .yui-t3 .yui-main{float:right;margin-left:-25em;/* IE: preserve layout at narrow widths */} .yui-t4 .yui-main, .yui-t5 .yui-main, .yui-t6 .yui-main{float:left;margin-right:-25em;/* IE: preserve layout at narrow widths */} /* Subsection: For Specific Template Presets */ /** * Nudge down to get to 13px equivalent for these form elements */ /* TODO Create t1-6's that are based on fixed widths */ .yui-t1 .yui-b {float:left;width:12.30769em;*width:12.00em;} .yui-t1 .yui-main .yui-b{margin-left:13.30769em;*margin-left:12.975em;} .yui-t2 .yui-b {float:left;width:13.84615em;*width:13.50em;} .yui-t2 .yui-main .yui-b {margin-left:14.84615em;*margin-left:14.475em;} .yui-t3 .yui-b {float:left;width:23.0769em;*width:22.50em;} .yui-t3 .yui-main .yui-b {margin-left:24.0769em;*margin-left:23.475em;} .yui-t4 .yui-b {float:right;width:13.8456em;*width:13.50em;} .yui-t4 .yui-main .yui-b {margin-right:14.8456em;*margin-right:14.475em;} .yui-t5 .yui-b {float:right;width:18.4615em;*width:18.00em;} .yui-t5 .yui-main .yui-b {margin-right:19.4615em;*margin-right:18.975em;} .yui-t6 .yui-b {float:right;width:23.0769em;*width:22.50em;} .yui-t6 .yui-main .yui-b {margin-right:24.0769em;*margin-right:23.475em;} .yui-t7 .yui-main .yui-b {display:block;margin:0 0 1em 0;} .yui-main .yui-b {float:none;width:auto;} /* Section: Grids and Nesting Grids */ /* Subsection: Children generally take half the available space */ .yui-gb .yui-u, .yui-g .yui-gb .yui-u, .yui-gb .yui-g, .yui-gb .yui-gb, .yui-gb .yui-gc, .yui-gb .yui-gd, .yui-gb .yui-ge, .yui-gb .yui-gf, .yui-gc .yui-u, .yui-gc .yui-g, .yui-gd .yui-u {float:right;} /*Float units (and sub grids) to the right */ .yui-g .yui-u, .yui-g .yui-g, .yui-g .yui-gb, .yui-g .yui-gc, .yui-g .yui-gd, .yui-g .yui-ge, .yui-g .yui-gf, .yui-gc .yui-u, .yui-gd .yui-g, .yui-g .yui-gc .yui-u, .yui-ge .yui-u, .yui-ge .yui-g, .yui-gf .yui-g, .yui-gf .yui-u{float:right;} /*Float units (and sub grids) to the left */ .yui-g div.first, .yui-gb div.first, .yui-gc div.first, .yui-gd div.first, .yui-ge div.first, .yui-gf div.first, .yui-g .yui-gc div.first, .yui-g .yui-ge div.first, .yui-gc div.first div.first {float:left;} .yui-g .yui-u, .yui-g .yui-g, .yui-g .yui-gb, .yui-g .yui-gc, .yui-g .yui-gd, .yui-g .yui-ge, .yui-g .yui-gf {width:49.1%;} .yui-gb .yui-u, .yui-g .yui-gb .yui-u, .yui-gb .yui-g, .yui-gb .yui-gb, .yui-gb .yui-gc, .yui-gb .yui-gd, .yui-gb .yui-ge, .yui-gb .yui-gf, .yui-gc .yui-u, .yui-gc .yui-g, .yui-gd .yui-u {width:32%;margin-left:2.0%;} /* Give IE some extra breathing room for 1/3-based rounding issues */ .yui-gb .yui-u {*width:31.8%;*margin-left:1.9%;} .yui-gc div.first, .yui-gd .yui-u {width:66%;_width:65.7%;} .yui-gd div.first {width:32%;_width:31.5%;} .yui-ge div.first, .yui-gf .yui-u{width:74.2%;_width:74%;} .yui-ge .yui-u, .yui-gf div.first {width:24%;_width:23.8%;} .yui-g .yui-gb div.first, .yui-gb div.first, .yui-gc div.first, .yui-gd div.first {margin-left:0;} /* Section: Deep Nesting */ .yui-g .yui-g .yui-u, .yui-gb .yui-g .yui-u, .yui-gc .yui-g .yui-u, .yui-gd .yui-g .yui-u, .yui-ge .yui-g .yui-u, .yui-gf .yui-g .yui-u {width:49%;*width:48.1%;*margin-left:0;} .yui-g .yui-gb div.first, .yui-gb .yui-gb div.first {*margin-right:0;*width:32%;_width:31.7%;} .yui-g .yui-gc div.first, .yui-gd .yui-g {width:66%;} .yui-gb .yui-g div.first {*margin-right:4%;_margin-right:1.3%;} .yui-gb .yui-gc div.first, .yui-gb .yui-gd div.first {*margin-right:0;} .yui-gb .yui-gb .yui-u, .yui-gb .yui-gc .yui-u {*margin-left:1.8%;_margin-left:4%;} .yui-g .yui-gb .yui-u {_margin-left:1.0%;} .yui-gb .yui-gd .yui-u {*width:66%;_width:61.2%;} .yui-gb .yui-gd div.first {*width:31%;_width:29.5%;} .yui-g .yui-gc .yui-u, .yui-gb .yui-gc .yui-u {width:32%;_float:right;margin-right:0;_margin-left:0;} .yui-gb .yui-gc div.first {width:66%;*float:left;*margin-left:0;} .yui-gb .yui-ge .yui-u, .yui-gb .yui-gf .yui-u {margin:0;} .yui-gb .yui-gb .yui-u {_margin-left:.7%;} .yui-gb .yui-g div.first, .yui-gb .yui-gb div.first {*margin-left:0;} .yui-gc .yui-g .yui-u, .yui-gd .yui-g .yui-u {*width:48.1%;*margin-left:0;} .yui-gb .yui-gd div.first {width:32%;} .yui-g .yui-gd div.first {_width:29.9%;} .yui-ge .yui-g {width:24%;} .yui-gf .yui-g {width:74.2%;} .yui-gb .yui-ge div.yui-u, .yui-gb .yui-gf div.yui-u {float:right;} .yui-gb .yui-ge div.first, .yui-gb .yui-gf div.first {float:left;} /* Width Accommodation for Nested Contexts */ .yui-gb .yui-ge .yui-u, .yui-gb .yui-gf div.first {*width:24%;_width:20%;} /* Width Accommodation for Nested Contexts */ .yui-gb .yui-ge div.first, .yui-gb .yui-gf .yui-u{*width:73.5%;_width:65.5%;} /* Patch for GD within GE */ .yui-ge div.first .yui-gd .yui-u {width:65%;} .yui-ge div.first .yui-gd div.first {width:32%;} /* Section: Clearing */ #bd:after, .yui-g:after, .yui-gb:after, .yui-gc:after, .yui-gd:after, .yui-ge:after, .yui-gf:after{content:".";display:block;height:0;clear:both;visibility:hidden;} #bd, .yui-g, .yui-gb, .yui-gc, .yui-gd, .yui-ge, .yui-gf{zoom:1;} ././@LongLink0000000000000000000000000000015200000000000011213 Lustar 00000000000000gdata-2.0.18/samples/apps/marketplace_sample/appengine_utilities/interface/css/cssgrids/grids-context.cssgdata-2.0.18/samples/apps/marketplace_sample/appengine_utilities/interface/css/cssgrids/grids-contex0000640036466300116100000001655612156622362034155 0ustar afshareng00000000000000/* Copyright (c) 2008, Yahoo! Inc. All rights reserved. Code licensed under the BSD License: http://developer.yahoo.net/yui/license.txt version: 3.0.0pr1 */ /* TODO README: removed yui-t7 */ /** * * The YUI CSS Foundation uses the *property and _property CSS filter * techniques to shield a value from A-grade browsers [1] other than * IE6 & IE7 (*property) and IE6 (_property) * / /** * Center's the page / Section: General Rules */ /* TODO Maybe remove */ .yui-grids body { text-align:center; margin-left:auto;margin-right:auto; } /* TODO - not sure I need this if the BD is already clearing. #ft { clear:both; } */ /* Section: Page Width Rules (#doc, #doc2, #doc3, #doc4) */ /* Subsection: General */ .yui-grids .yui-d0, /* 100% */ .yui-grids .yui-d1, /* 750px */ .yui-grids .yui-d1f, /* 750px fixed */ .yui-grids .yui-d2, /* 950px */ .yui-grids .yui-d2f, /* 950px fixed */ .yui-grids .yui-d3, /* 974px */ .yui-grids .yui-d3f, /* 974px fixed */ .yui-t1, .yui-t2, .yui-t3, .yui-t4, .yui-t5, .yui-t6 /* the Ts are here to collect margin and text-alignment rules */{ margin:auto; text-align:left; width:57.69em;*width:56.25em; /* doc1*/ min-width:750px; } .yui-t1, .yui-t2, .yui-t3, .yui-t4, .yui-t5, .yui-t6 {width:100%;} /* Subsection: 100% (doc) */ .yui-grids .yui-d0 { /* Left and Right margins are not a structural part of Grids. Without them Grids works fine, but content bleeds to the very edge of the document, which often impairs readability and usability. They are provided because they prevent the content from "bleeding" into the browser's chrome.*/ margin:auto 10px; width:auto; } .yui-grids .yui-d0f {width:100%;} /* Subsection: 950 Centered (doc2) */ .yui-grids .yui-d2 {width:73.076em;*width:71.25em;} .yui-grids .yui-d2f {width:950px;} /* Subsection: 974 Centered (doc3) */ .yui-grids .yui-d3 {width:74.923em;*width:73.05em;} .yui-grids .yui-d3f {width:974px;} /* Section: Preset Template Rules (.yui-t[1-6]) */ /* Subsection: General */ /* to preserve source-order independence for Gecko without breaking */ .yui-b{position:relative;} .yui-b{_position:static;} .yui-main .yui-b{position:static;} .yui-main {width:100%;} .yui-t1 .yui-main, .yui-t2 .yui-main, .yui-t3 .yui-main{float:right;margin-left:-25em;/* IE: preserve layout at narrow widths */} .yui-t4 .yui-main, .yui-t5 .yui-main, .yui-t6 .yui-main{float:left;margin-right:-25em;/* IE: preserve layout at narrow widths */} /* Subsection: For Specific Template Presets */ /** * Nudge down to get to 13px equivalent for these form elements */ /* TODO Create t1-6's that are based on fixed widths */ .yui-t1 .yui-b {float:left;width:12.30769em;*width:12.00em;} .yui-t1 .yui-main .yui-b{margin-left:13.30769em;*margin-left:12.975em;} .yui-t2 .yui-b {float:left;width:13.84615em;*width:13.50em;} .yui-t2 .yui-main .yui-b {margin-left:14.84615em;*margin-left:14.475em;} .yui-t3 .yui-b {float:left;width:23.0769em;*width:22.50em;} .yui-t3 .yui-main .yui-b {margin-left:24.0769em;*margin-left:23.475em;} .yui-t4 .yui-b {float:right;width:13.8456em;*width:13.50em;} .yui-t4 .yui-main .yui-b {margin-right:14.8456em;*margin-right:14.475em;} .yui-t5 .yui-b {float:right;width:18.4615em;*width:18.00em;} .yui-t5 .yui-main .yui-b {margin-right:19.4615em;*margin-right:18.975em;} .yui-t6 .yui-b {float:right;width:23.0769em;*width:22.50em;} .yui-t6 .yui-main .yui-b {margin-right:24.0769em;*margin-right:23.475em;} .yui-t7 .yui-main .yui-b {display:block;margin:0 0 1em 0;} .yui-main .yui-b {float:none;width:auto;} /* Section: Grids and Nesting Grids */ /* Subsection: Children generally take half the available space */ .yui-gb .yui-u, .yui-g .yui-gb .yui-u, .yui-gb .yui-g, .yui-gb .yui-gb, .yui-gb .yui-gc, .yui-gb .yui-gd, .yui-gb .yui-ge, .yui-gb .yui-gf, .yui-gc .yui-u, .yui-gc .yui-g, .yui-gd .yui-u {float:right;} /*Float units (and sub grids) to the right */ .yui-g .yui-u, .yui-g .yui-g, .yui-g .yui-gb, .yui-g .yui-gc, .yui-g .yui-gd, .yui-g .yui-ge, .yui-g .yui-gf, .yui-gc .yui-u, .yui-gd .yui-g, .yui-g .yui-gc .yui-u, .yui-ge .yui-u, .yui-ge .yui-g, .yui-gf .yui-g, .yui-gf .yui-u{float:right;} /*Float units (and sub grids) to the left */ .yui-g div.first, .yui-gb div.first, .yui-gc div.first, .yui-gd div.first, .yui-ge div.first, .yui-gf div.first, .yui-g .yui-gc div.first, .yui-g .yui-ge div.first, .yui-gc div.first div.first {float:left;} .yui-g .yui-u, .yui-g .yui-g, .yui-g .yui-gb, .yui-g .yui-gc, .yui-g .yui-gd, .yui-g .yui-ge, .yui-g .yui-gf {width:49.1%;} .yui-gb .yui-u, .yui-g .yui-gb .yui-u, .yui-gb .yui-g, .yui-gb .yui-gb, .yui-gb .yui-gc, .yui-gb .yui-gd, .yui-gb .yui-ge, .yui-gb .yui-gf, .yui-gc .yui-u, .yui-gc .yui-g, .yui-gd .yui-u {width:32%;margin-left:2.0%;} /* Give IE some extra breathing room for 1/3-based rounding issues */ .yui-gb .yui-u {*width:31.8%;*margin-left:1.9%;} .yui-gc div.first, .yui-gd .yui-u {width:66%;_width:65.7%;} .yui-gd div.first {width:32%;_width:31.5%;} .yui-ge div.first, .yui-gf .yui-u{width:74.2%;_width:74%;} .yui-ge .yui-u, .yui-gf div.first {width:24%;_width:23.8%;} .yui-g .yui-gb div.first, .yui-gb div.first, .yui-gc div.first, .yui-gd div.first {margin-left:0;} /* Section: Deep Nesting */ .yui-g .yui-g .yui-u, .yui-gb .yui-g .yui-u, .yui-gc .yui-g .yui-u, .yui-gd .yui-g .yui-u, .yui-ge .yui-g .yui-u, .yui-gf .yui-g .yui-u {width:49%;*width:48.1%;*margin-left:0;} .yui-g .yui-gb div.first, .yui-gb .yui-gb div.first {*margin-right:0;*width:32%;_width:31.7%;} .yui-g .yui-gc div.first, .yui-gd .yui-g {width:66%;} .yui-gb .yui-g div.first {*margin-right:4%;_margin-right:1.3%;} .yui-gb .yui-gc div.first, .yui-gb .yui-gd div.first {*margin-right:0;} .yui-gb .yui-gb .yui-u, .yui-gb .yui-gc .yui-u {*margin-left:1.8%;_margin-left:4%;} .yui-g .yui-gb .yui-u {_margin-left:1.0%;} .yui-gb .yui-gd .yui-u {*width:66%;_width:61.2%;} .yui-gb .yui-gd div.first {*width:31%;_width:29.5%;} .yui-g .yui-gc .yui-u, .yui-gb .yui-gc .yui-u {width:32%;_float:right;margin-right:0;_margin-left:0;} .yui-gb .yui-gc div.first {width:66%;*float:left;*margin-left:0;} .yui-gb .yui-ge .yui-u, .yui-gb .yui-gf .yui-u {margin:0;} .yui-gb .yui-gb .yui-u {_margin-left:.7%;} .yui-gb .yui-g div.first, .yui-gb .yui-gb div.first {*margin-left:0;} .yui-gc .yui-g .yui-u, .yui-gd .yui-g .yui-u {*width:48.1%;*margin-left:0;} .yui-gb .yui-gd div.first {width:32%;} .yui-g .yui-gd div.first {_width:29.9%;} .yui-ge .yui-g {width:24%;} .yui-gf .yui-g {width:74.2%;} .yui-gb .yui-ge div.yui-u, .yui-gb .yui-gf div.yui-u {float:right;} .yui-gb .yui-ge div.first, .yui-gb .yui-gf div.first {float:left;} /* Width Accommodation for Nested Contexts */ .yui-gb .yui-ge .yui-u, .yui-gb .yui-gf div.first {*width:24%;_width:20%;} /* Width Accommodation for Nested Contexts */ .yui-gb .yui-ge div.first, .yui-gb .yui-gf .yui-u{*width:73.5%;_width:65.5%;} /* Patch for GD within GE */ .yui-ge div.first .yui-gd .yui-u {width:65%;} .yui-ge div.first .yui-gd div.first {width:32%;} /* Section: Clearing */ #bd:after, .yui-g:after, .yui-gb:after, .yui-gc:after, .yui-gd:after, .yui-ge:after, .yui-gf:after{content:".";display:block;height:0;clear:both;visibility:hidden;} #bd, .yui-g, .yui-gb, .yui-gc, .yui-gd, .yui-ge, .yui-gf{zoom:1;} gdata-2.0.18/samples/apps/marketplace_sample/appengine_utilities/interface/css/cssfonts/0000750036466300116100000000000012156625015031625 5ustar afshareng00000000000000././@LongLink0000000000000000000000000000015600000000000011217 Lustar 00000000000000gdata-2.0.18/samples/apps/marketplace_sample/appengine_utilities/interface/css/cssfonts/fonts-context-min.cssgdata-2.0.18/samples/apps/marketplace_sample/appengine_utilities/interface/css/cssfonts/fonts-contex0000640036466300116100000000173312156622362034206 0ustar afshareng00000000000000/* Copyright (c) 2008, Yahoo! Inc. All rights reserved. Code licensed under the BSD License: http://developer.yahoo.net/yui/license.txt version: 3.0.0pr1 */ /** * Percents could work for IE, but for backCompat purposes, we are using keywords. * x-small is for IE6/7 quirks mode. */ .yui-cssfonts body { font:13px/1.231 arial,helvetica,clean,sans-serif; *font-size:small; /* for IE */ *font:x-small; /* for IE in quirks mode */ } /** * Nudge down to get to 13px equivalent for these form elements */ .yui-cssfonts select, .yui-cssfonts input, .yui-cssfonts button, .yui-cssfonts textarea { font:99% arial,helvetica,clean,sans-serif; } /** * To help tables remember to inherit */ .yui-cssfonts table { font-size:inherit; font:100%; } /** * Bump up IE to get to 13px equivalent for these fixed-width elements */ .yui-cssfonts pre, .yui-cssfonts code, .yui-cssfonts kbd, .yui-cssfonts samp, .yui-cssfonts tt { font-family:monospace; *font-size:108%; line-height:100%; }././@LongLink0000000000000000000000000000015200000000000011213 Lustar 00000000000000gdata-2.0.18/samples/apps/marketplace_sample/appengine_utilities/interface/css/cssfonts/fonts-context.cssgdata-2.0.18/samples/apps/marketplace_sample/appengine_utilities/interface/css/cssfonts/fonts-contex0000640036466300116100000000175212156622362034207 0ustar afshareng00000000000000/* Copyright (c) 2008, Yahoo! Inc. All rights reserved. Code licensed under the BSD License: http://developer.yahoo.net/yui/license.txt version: 3.0.0pr1 */ /** * Percents could work for IE, but for backCompat purposes, we are using keywords. * x-small is for IE6/7 quirks mode. */ .yui-cssfonts body, .yui-cssfonts { font:13px/1.231 arial,helvetica,clean,sans-serif; *font-size:small; /* for IE */ *font:x-small; /* for IE in quirks mode */ } /** * Nudge down to get to 13px equivalent for these form elements */ .yui-cssfonts select, .yui-cssfonts input, .yui-cssfonts button, .yui-cssfonts textarea { font:99% arial,helvetica,clean,sans-serif; } /** * To help tables remember to inherit */ .yui-cssfonts table { font-size:inherit; font:100%; } /** * Bump up IE to get to 13px equivalent for these fixed-width elements */ .yui-cssfonts pre, .yui-cssfonts code, .yui-cssfonts kbd, .yui-cssfonts samp, .yui-cssfonts tt { font-family:monospace; *font-size:108%; line-height:100%; }././@LongLink0000000000000000000000000000014600000000000011216 Lustar 00000000000000gdata-2.0.18/samples/apps/marketplace_sample/appengine_utilities/interface/css/cssfonts/fonts-min.cssgdata-2.0.18/samples/apps/marketplace_sample/appengine_utilities/interface/css/cssfonts/fonts-min.cs0000640036466300116100000000150112156622362034066 0ustar afshareng00000000000000/* Copyright (c) 2008, Yahoo! Inc. All rights reserved. Code licensed under the BSD License: http://developer.yahoo.net/yui/license.txt version: 3.0.0pr1 */ /** * Percents could work for IE, but for backCompat purposes, we are using keywords. * x-small is for IE6/7 quirks mode. */ body { font:13px/1.231 arial,helvetica,clean,sans-serif; *font-size:small; /* for IE */ *font:x-small; /* for IE in quirks mode */ } /** * Nudge down to get to 13px equivalent for these form elements */ select, input, button, textarea { font:99% arial,helvetica,clean,sans-serif; } /** * To help tables remember to inherit */ table { font-size:inherit; font:100%; } /** * Bump up IE to get to 13px equivalent for these fixed-width elements */ pre, code, kbd, samp, tt { font-family:monospace; *font-size:108%; line-height:100%; }gdata-2.0.18/samples/apps/marketplace_sample/appengine_utilities/interface/css/cssfonts/fonts.css0000640036466300116100000000150112156622362033470 0ustar afshareng00000000000000/* Copyright (c) 2008, Yahoo! Inc. All rights reserved. Code licensed under the BSD License: http://developer.yahoo.net/yui/license.txt version: 3.0.0pr1 */ /** * Percents could work for IE, but for backCompat purposes, we are using keywords. * x-small is for IE6/7 quirks mode. */ body { font:13px/1.231 arial,helvetica,clean,sans-serif; *font-size:small; /* for IE */ *font:x-small; /* for IE in quirks mode */ } /** * Nudge down to get to 13px equivalent for these form elements */ select, input, button, textarea { font:99% arial,helvetica,clean,sans-serif; } /** * To help tables remember to inherit */ table { font-size:inherit; font:100%; } /** * Bump up IE to get to 13px equivalent for these fixed-width elements */ pre, code, kbd, samp, tt { font-family:monospace; *font-size:108%; line-height:100%; }gdata-2.0.18/samples/apps/marketplace_sample/gdata/0000750036466300116100000000000012156625015022252 5ustar afshareng00000000000000gdata-2.0.18/samples/apps/marketplace_sample/gdata/test_data.py0000750036466300116100000125011112156622362024602 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright (C) 2006 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. XML_ENTRY_1 = """ http://www.google.com/test/id/url Testing 2000 series laptop
    A Testing Laptop
    Computer Laptop testing laptop products
    """ TEST_BASE_ENTRY = """ Testing 2000 series laptop
    A Testing Laptop
    yes Computer Laptop testing laptop products
    """ BIG_FEED = """ dive into mark A <em>lot</em> of effort went into making this effortless 2005-07-31T12:29:29Z tag:example.org,2003:3 Copyright (c) 2003, Mark Pilgrim Example Toolkit Atom draft-07 snapshot tag:example.org,2003:3.2397 2005-07-31T12:29:29Z 2003-12-13T08:29:29-04:00 Mark Pilgrim http://example.org/ f8dy@example.com Sam Ruby Joe Gregorio

    [Update: The Atom draft is finished.]

    """ SMALL_FEED = """ Example Feed 2003-12-13T18:30:02Z John Doe urn:uuid:60a76c80-d399-11d9-b93C-0003939e0af6 Atom-Powered Robots Run Amok urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a 2003-12-13T18:30:02Z Some text. """ GBASE_FEED = """ http://www.google.com/base/feeds/snippets 2007-02-08T23:18:21.935Z Items matching query: digital camera GoogleBase 2171885 1 25 http://www.google.com/base/feeds/snippets/13246453826751927533 2007-02-08T13:23:27.000Z 2007-02-08T16:40:57.000Z Digital Camera Battery Notebook Computer 12v DC Power Cable - 5.5mm x 2.5mm (Center +) Camera Connecting Cables Notebook Computer 12v DC Power Cable - 5.5mm x 2.1mm (Center +) This connection cable will allow any Digital Pursuits battery pack to power portable computers that operate with 12v power and have a 2.1mm power connector (center +) Digital ... B&H Photo-Video anon-szot0wdsq0at@base.google.com PayPal & Bill Me Later credit available online only. new 420 9th Ave. 10001 305668-REG Products Digital Camera Battery 2007-03-10T13:23:27.000Z 1172711 34.95 usd Digital Photography>Camera Connecting Cables EN DCB5092 US 1.0 http://base.google.com/base_image?q=http%3A%2F%2Fwww.bhphotovideo.com%2Fimages%2Fitems%2F305668.jpg&dhm=ffffffff84c9a95e&size=6 http://www.google.com/base/feeds/snippets/10145771037331858608 2007-02-08T13:23:27.000Z 2007-02-08T16:40:57.000Z Digital Camera Battery Electronic Device 5v DC Power Cable - 5.5mm x 2.5mm (Center +) Camera Connecting Cables Electronic Device 5v DC Power Cable - 5.5mm x 2.5mm (Center +) This connection cable will allow any Digital Pursuits battery pack to power any electronic device that operates with 5v power and has a 2.5mm power connector (center +) Digital ... B&H Photo-Video anon-szot0wdsq0at@base.google.com 420 9th Ave. 10001 new 0.18 US Digital Photography>Camera Connecting Cables PayPal & Bill Me Later credit available online only. 305656-REG http://base.google.com/base_image?q=http%3A%2F%2Fwww.bhphotovideo.com%2Fimages%2Fitems%2F305656.jpg&dhm=7315bdc8&size=6 DCB5108 838098005108 34.95 usd EN Digital Camera Battery 1172711 Products 2007-03-10T13:23:27.000Z http://www.google.com/base/feeds/snippets/3128608193804768644 2007-02-08T02:21:27.000Z 2007-02-08T15:40:13.000Z Digital Camera Battery Power Cable for Kodak 645 Pro-Back ProBack & DCS-300 Series Camera Connecting Cables Camera Connection Cable - to Power Kodak 645 Pro-Back DCS-300 Series Digital Cameras This connection cable will allow any Digital Pursuits battery pack to power the following digital cameras: Kodak DCS Pro Back 645 DCS-300 series Digital Photography ... B&H Photo-Video anon-szot0wdsq0at@base.google.com 0.3 DCB6006 http://base.google.com/base_image?q=http%3A%2F%2Fwww.bhphotovideo.com%2Fimages%2Fitems%2F305685.jpg&dhm=72f0ca0a&size=6 420 9th Ave. 10001 PayPal & Bill Me Later credit available online only. Products US digital kodak camera Digital Camera Battery 2007-03-10T02:21:27.000Z EN new 34.95 usd 1172711 Digital Photography>Camera Connecting Cables 305685-REG """ EXTENSION_TREE = """ John Doe Bar """ TEST_AUTHOR = """ John Doe johndoes@someemailadress.com http://www.google.com """ TEST_LINK = """ """ TEST_GBASE_ATTRIBUTE = """ Digital Camera Battery """ CALENDAR_FEED = """ http://www.google.com/calendar/feeds/default 2007-03-20T22:48:57.833Z GData Ops Demo's Calendar List GData Ops Demo gdata.ops.demo@gmail.com Google Calendar 1 http://www.google.com/calendar/feeds/default/gdata.ops.demo%40gmail.com 2007-03-20T22:48:57.837Z 2007-03-20T22:48:52.000Z GData Ops Demo GData Ops Demo gdata.ops.demo@gmail.com http://www.google.com/calendar/feeds/default/jnh21ovnjgfph21h32gvms2758%40group.calendar.google.com 2007-03-20T22:48:57.837Z 2007-03-20T22:48:53.000Z GData Ops Demo Secondary Calendar GData Ops Demo Secondary Calendar """ CALENDAR_FULL_EVENT_FEED = """ http://www.google.com/calendar/feeds/default/private/full 2007-03-20T21:29:57.000Z GData Ops Demo GData Ops Demo GData Ops Demo gdata.ops.demo@gmail.com Google Calendar 10 1 25 http://www.google.com/calendar/feeds/default/private/full/o99flmgmkfkfrr8u745ghr3100 2007-03-20T21:29:52.000Z 2007-03-20T21:29:57.000Z test deleted GData Ops Demo gdata.ops.demo@gmail.com http://www.google.com/calendar/feeds/default/private/full/2qt3ao5hbaq7m9igr5ak9esjo0 2007-03-20T21:26:04.000Z 2007-03-20T21:28:46.000Z Afternoon at Dolores Park with Kim GData Ops Demo gdata.ops.demo@gmail.com http://www.google.com/calendar/feeds/default/private/full/uvsqhg7klnae40v50vihr1pvos 2007-03-20T21:28:37.000Z 2007-03-20T21:28:37.000Z Team meeting GData Ops Demo gdata.ops.demo@gmail.com DTSTART;TZID=America/Los_Angeles:20070323T090000 DTEND;TZID=America/Los_Angeles:20070323T100000 RRULE:FREQ=WEEKLY;BYDAY=FR;UNTIL=20070817T160000Z;WKST=SU BEGIN:VTIMEZONE TZID:America/Los_Angeles X-LIC-LOCATION:America/Los_Angeles BEGIN:STANDARD TZOFFSETFROM:-0700 TZOFFSETTO:-0800 TZNAME:PST DTSTART:19701025T020000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU END:STANDARD BEGIN:DAYLIGHT TZOFFSETFROM:-0800 TZOFFSETTO:-0700 TZNAME:PDT DTSTART:19700405T020000 RRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=1SU END:DAYLIGHT END:VTIMEZONE http://www.google.com/calendar/feeds/default/private/full/st4vk9kiffs6rasrl32e4a7alo 2007-03-20T21:25:46.000Z 2007-03-20T21:25:46.000Z Movie with Kim and danah GData Ops Demo gdata.ops.demo@gmail.com http://www.google.com/calendar/feeds/default/private/full/ofl1e45ubtsoh6gtu127cls2oo 2007-03-20T21:24:43.000Z 2007-03-20T21:25:08.000Z Dinner with Kim and Sarah GData Ops Demo gdata.ops.demo@gmail.com http://www.google.com/calendar/feeds/default/private/full/b69s2avfi2joigsclecvjlc91g 2007-03-20T21:24:19.000Z 2007-03-20T21:25:05.000Z Dinner with Jane and John GData Ops Demo gdata.ops.demo@gmail.com http://www.google.com/calendar/feeds/default/private/full/u9p66kkiotn8bqh9k7j4rcnjjc 2007-03-20T21:24:33.000Z 2007-03-20T21:24:33.000Z Tennis with Elizabeth GData Ops Demo gdata.ops.demo@gmail.com http://www.google.com/calendar/feeds/default/private/full/76oj2kceidob3s708tvfnuaq3c 2007-03-20T21:24:00.000Z 2007-03-20T21:24:00.000Z Lunch with Jenn GData Ops Demo gdata.ops.demo@gmail.com http://www.google.com/calendar/feeds/default/private/full/5np9ec8m7uoauk1vedh5mhodco 2007-03-20T07:50:02.000Z 2007-03-20T20:39:26.000Z test entry test desc GData Ops Demo gdata.ops.demo@gmail.com http://www.google.com/calendar/feeds/default/private/full/fu6sl0rqakf3o0a13oo1i1a1mg 2007-02-14T23:23:37.000Z 2007-02-14T23:25:30.000Z test GData Ops Demo gdata.ops.demo@gmail.com http://www.google.com/calendar/feeds/default/private/full/h7a0haa4da8sil3rr19ia6luvc 2007-07-16T22:13:28.000Z 2007-07-16T22:13:29.000Z GData Ops Demo gdata.ops.demo@gmail.com """ CALENDAR_BATCH_REQUEST = """ 1 Event inserted via batch 2 http://www.google.com/calendar/feeds/default/private/full/glcs0kv2qqa0gf52qi1jo018gc Event queried via batch 3 http://www.google.com/calendar/feeds/default/private/full/ujm0go5dtngdkr6u91dcqvj0qs Event updated via batch 4 http://www.google.com/calendar/feeds/default/private/full/d8qbg9egk1n6lhsgq1sjbqffqc Event deleted via batch """ CALENDAR_BATCH_RESPONSE = """ http://www.google.com/calendar/feeds/default/private/full 2007-09-21T23:01:00.380Z Batch Feed 1 http://www.google.com/calendar/feeds/default/private/full/n9ug78gd9tv53ppn4hdjvk68ek Event inserted via batch 2 http://www.google.com/calendar/feeds/default/private/full/glsc0kv2aqa0ff52qi1jo018gc Event queried via batch 3 http://www.google.com/calendar/feeds/default/private/full/ujm0go5dtngdkr6u91dcqvj0qs Event updated via batch 3 4 http://www.google.com/calendar/feeds/default/private/full/d8qbg9egk1n6lhsgq1sjbqffqc Event deleted via batch Deleted """ GBASE_ATTRIBUTE_FEED = """ http://www.google.com/base/feeds/attributes 2006-11-01T20:35:59.578Z Attribute histogram for query: [item type:jobs] GoogleBase 16 1 16 http://www.google.com/base/feeds/attributes/job+industry%28text%29N%5Bitem+type%3Ajobs%5D 2006-11-01T20:36:00.100Z job industry(text) Attribute"job industry" of type text. it internet healthcare information technology accounting clerical and administrative other sales and sales management information systems engineering and architecture sales """ GBASE_ATTRIBUTE_ENTRY = """ http://www.google.com/base/feeds/attributes/job+industry%28text%29N%5Bitem+type%3Ajobs%5D 2006-11-01T20:36:00.100Z job industry(text) Attribute"job industry" of type text. it internet healthcare information technology accounting clerical and administrative other sales and sales management information systems engineering and architecture sales """ GBASE_LOCALES_FEED = """ http://www.google.com/base/feeds/locales/ 2006-06-13T18:11:40.120Z Locales Google Inc. base@google.com GoogleBase 3 25 http://www.google.com/base/feeds/locales/en_US 2006-03-27T22:27:36.658Z en_US en_US http://www.google.com/base/feeds/locales/en_GB 2006-06-13T18:14:18.601Z en_GB en_GB http://www.google.com/base/feeds/locales/de_DE 2006-06-13T18:14:18.601Z de_DE de_DE """ GBASE_STRING_ENCODING_ENTRY = """ http://www.google.com/base/feeds/snippets/17495780256183230088 2007-12-09T03:13:07.000Z 2008-01-07T03:26:46.000Z Digital Camera Cord Fits SONY Cybershot DSC-R1 S40 SONY \xC2\xB7 Cybershot Digital Camera Usb Cable DESCRIPTION This is a 2.5 USB 2.0 A to Mini B (5 Pin) high quality digital camera cable used for connecting your Sony Digital Cameras and Camcoders. Backward Compatible with USB 2.0, 1.0 and 1.1. Fully ... eBay Products EN US 0.99 usd http://thumbs.ebaystatic.com/pict/270195049057_1.jpg Cameras & Photo>Digital Camera Accessories>Cables Cords & Connectors>USB Cables>For Other Brands 11729 270195049057 2008-02-06T03:26:46Z """ RECURRENCE_EXCEPTION_ENTRY = """ http://www.google.com/calendar/feeds/default/private/composite/i7lgfj69mjqjgnodklif3vbm7g 2007-04-05T21:51:49.000Z 2007-04-05T21:51:49.000Z testDavid gdata ops gdata.ops.test@gmail.com DTSTART;TZID=America/Anchorage:20070403T100000 DTEND;TZID=America/Anchorage:20070403T110000 RRULE:FREQ=DAILY;UNTIL=20070408T180000Z;WKST=SU EXDATE;TZID=America/Anchorage:20070407T100000 EXDATE;TZID=America/Anchorage:20070405T100000 EXDATE;TZID=America/Anchorage:20070404T100000 BEGIN:VTIMEZONE TZID:America/Anchorage X-LIC-LOCATION:America/Anchorage BEGIN:STANDARD TZOFFSETFROM:-0800 TZOFFSETTO:-0900 TZNAME:AKST DTSTART:19701025T020000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU END:STANDARD BEGIN:DAYLIGHT TZOFFSETFROM:-0900 TZOFFSETTO:-0800 TZNAME:AKDT DTSTART:19700405T020000 RRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=1SU END:DAYLIGHT END:VTIMEZONE i7lgfj69mjqjgnodklif3vbm7g_20070407T180000Z 2007-04-05T21:51:49.000Z 2007-04-05T21:52:58.000Z testDavid gdata ops gdata.ops.test@gmail.com 2007-04-05T21:54:09.285Z Comments for: testDavid """ NICK_ENTRY = """ https://apps-apis.google.com/a/feeds/example.com/nickname/2.0/Foo 1970-01-01T00:00:00.000Z Foo """ NICK_FEED = """ http://apps-apis.google.com/a/feeds/example.com/nickname/2.0 1970-01-01T00:00:00.000Z Nicknames for user SusanJones 1 2 http://apps-apis.google.com/a/feeds/example.com/nickname/2.0/Foo Foo http://apps-apis.google.com/a/feeds/example.com/nickname/2.0/suse suse """ USER_ENTRY = """ https://apps-apis.google.com/a/feeds/example.com/user/2.0/TestUser 1970-01-01T00:00:00.000Z TestUser """ USER_FEED = """ http://apps-apis.google.com/a/feeds/example.com/user/2.0 1970-01-01T00:00:00.000Z Users """ EMAIL_LIST_ENTRY = """ https://apps-apis.google.com/a/feeds/example.com/emailList/2.0/testlist 1970-01-01T00:00:00.000Z testlist """ EMAIL_LIST_FEED = """ http://apps-apis.google.com/a/feeds/example.com/emailList/2.0 1970-01-01T00:00:00.000Z EmailLists """ EMAIL_LIST_RECIPIENT_ENTRY = """ https://apps-apis.google.com/a/feeds/example.com/emailList/2.0/us-sales/recipient/TestUser%40example.com 1970-01-01T00:00:00.000Z TestUser """ EMAIL_LIST_RECIPIENT_FEED = """ http://apps-apis.google.com/a/feeds/example.com/emailList/2.0/us-sales/recipient 1970-01-01T00:00:00.000Z Recipients for email list us-sales """ ACL_FEED = """ http://www.google.com/calendar/feeds/liz%40gmail.com/acl/full 2007-04-21T00:52:04.000Z Elizabeth Bennet's access control list Google Calendar 2 1 http://www.google.com/calendar/feeds/liz%40gmail.com/acl/full/user%3Aliz%40gmail.com 2007-04-21T00:52:04.000Z owner Elizabeth Bennet liz@gmail.com http://www.google.com/calendar/feeds/liz%40gmail.com/acl/full/default 2007-04-21T00:52:04.000Z read Elizabeth Bennet liz@gmail.com """ ACL_ENTRY = """ http://www.google.com/calendar/feeds/liz%40gmail.com/acl/full/user%3Aliz%40gmail.com 2007-04-21T00:52:04.000Z owner Elizabeth Bennet liz@gmail.com """ DOCUMENT_LIST_FEED = """ 21test.usertest.user@gmail.comhttps://docs.google.com/feeds/documents/private/full/spreadsheet%3AsupercalifragilisticexpeadociousTest Spreadsheet2007-07-03T18:03:32.045Z document:dfrkj84g_3348jbxpxcd test.user test.user@gmail.com 2009-03-05T07:48:21.493Z test.usertest.user@gmail.comhttp://docs.google.com/feeds/documents/private/full/document%3Agr00vyTest Document2007-07-03T18:02:50.338Z test.user test.user@gmail.com 2009-03-05T07:48:21.493Z http://docs.google.com/feeds/documents/private/fullAvailable Documents - test.user@gmail.com2007-07-09T23:07:21.898Z """ DOCUMENT_LIST_ENTRY = """ test.usertest.user@gmail.com https://docs.google.com/feeds/documents/private/full/spreadsheet%3Asupercalifragilisticexpealidocious Test Spreadsheet2007-07-03T18:03:32.045Z spreadsheet:supercalifragilisticexpealidocious test.user test.user@gmail.com 2009-03-05T07:48:21.493Z """ DOCUMENT_LIST_ENTRY_V3 = """ test.usertest.user@gmail.com https://docs.google.com/feeds/documents/private/full/spreadsheet%3Asupercalifragilisticexpealidocious Test Spreadsheet2007-07-03T18:03:32.045Z spreadsheet:supercalifragilisticexpealidocious test.user test.user@gmail.com 2009-03-05T07:48:21.493Z 1000 """ DOCUMENT_LIST_ACL_ENTRY = """ """ DOCUMENT_LIST_ACL_WITHKEY_ENTRY = """ """ DOCUMENT_LIST_ACL_ADDITIONAL_ROLE_ENTRY = """ """ DOCUMENT_LIST_ACL_FEED = """ http://docs.google.com/feeds/acl/private/full/spreadsheet%3ApFrmMi8feTQYCgZpwUQ 2009-02-22T03:48:25.895Z Document Permissions 2 1 http://docs.google.com/feeds/acl/private/full/spreadsheet%3ApFrmMi8feTQp4pwUwUQ/user%3Auser%40gmail.com 2009-02-22T03:48:25.896Z Document Permission - user@gmail.com http://docs.google.com/feeds/acl/private/full/spreadsheet%3ApFrmMi8fCgZp4pwUwUQ/user%3Auser2%40google.com 2009-02-22T03:48:26.257Z Document Permission - user2@google.com """ DOCUMENT_LIST_REVISION_FEED = """ https://docs.google.com/feeds/default/private/full/resource_id/revisions 2009-08-17T04:22:10.378Z Document Revisions 6 1 https://docs.google.com/feeds/id/resource_id/revisions/2 2009-08-17T04:22:10.440Z 2009-08-14T07:11:34.197Z Revision 2 another_user another_user@gmail.com """ DOCUMENT_LIST_METADATA = """ """ BATCH_ENTRY = """ http://www.google.com/base/feeds/items/2173859253842813008 2006-07-11T14:51:43.560Z 2006-07-11T14:51: 43.560Z title content recipes itemB """ BATCH_FEED_REQUEST = """ My Batch Feed http://www.google.com/base/feeds/items/13308004346459454600 http://www.google.com/base/feeds/items/17437536661927313949 ... ... itemA recipes ... ... itemB recipes """ BATCH_FEED_RESULT = """ http://www.google.com/base/feeds/items 2006-07-11T14:51:42.894Z My Batch http://www.google.com/base/feeds/items/2173859253842813008 2006-07-11T14:51:43.560Z 2006-07-11T14:51: 43.560Z ... ... recipes itemB http://www.google.com/base/feeds/items/11974645606383737963 2006-07-11T14:51:43.247Z 2006-07-11T14:51: 43.247Z ... ... recipes itemA http://www.google.com/base/feeds/items/13308004346459454600 2006-07-11T14:51:42.894Z Error Bad request http://www.google.com/base/feeds/items/17437536661927313949 2006-07-11T14:51:43.246Z Deleted """ ALBUM_FEED = """ http://picasaweb.google.com/data/feed/api/user/sample.user/albumid/1 2007-09-21T18:23:05.000Z Test public http://lh6.google.com/sample.user/Rt8WNoDZEJE/AAAAAAAAABk/HQGlDhpIgWo/s160-c/Test.jpg sample http://picasaweb.google.com/sample.user Picasaweb 4 1 500 1 Test public 1188975600000 2 sample.user sample true 0 http://picasaweb.google.com/data/entry/api/user/sample.user/albumid/1/photoid/2 2007-09-05T20:49:23.000Z 2007-09-21T18:23:05.000Z Aqua Blue.jpg Blue 2 1190398985145172 0.0 1 2560 1600 883405 1189025362000 true c041ce17aaa637eb656c81d9cf526c24 true 1 Aqua Blue.jpg Blue tag, test sample http://picasaweb.google.com/data/entry/api/user/sample.user/albumid/1/photoid/3 2007-09-05T20:49:24.000Z 2007-09-21T18:19:38.000Z Aqua Graphite.jpg Gray 3 1190398778006402 1.0 1 2560 1600 798334 1189025363000 true a5ce2e36b9df7d3cb081511c72e73926 true 0 Aqua Graphite.jpg Gray sample http://picasaweb.google.com/data/entry/api/user/sample.user/albumid/1/tag/tag 2007-09-05T20:49:24.000Z tag tag sample http://picasaweb.google.com/sample.user http://picasaweb.google.com/data/entry/api/user/sample.user/albumid/1/tag/test 2007-09-05T20:49:24.000Z test test sample http://picasaweb.google.com/sample.user """ CODE_SEARCH_FEED = """ http://www.google.com/codesearch/feeds/search?q=malloc 2007-12-19T16:08:04Z Google Code Search Google Code Search 2530000 1 Google Code Search http://www.google.com/codesearch http://www.google.com/codesearch?hl=en&q=+malloc+show:LDjwp-Iqc7U:84hEYaYsZk8:xDGReDhvNi0&sa=N&ct=rx&cd=1&cs_p=http://www.gnu.org&cs_f=software/autoconf/manual/autoconf-2.60/autoconf.html-002&cs_p=http://www.gnu.org&cs_f=software/autoconf/manual/autoconf-2.60/autoconf.html-002#first2007-12-19T16:08:04ZCode owned by external author.software/autoconf/manual/autoconf-2.60/autoconf.html<pre> 8: void *<b>malloc</b> (); </pre><pre> #undef <b>malloc</b> </pre><pre> void *<b>malloc</b> (); </pre><pre> rpl_<b>malloc</b> (size_t n) </pre><pre> return <b>malloc</b> (n); </pre> http://www.google.com/codesearch?hl=en&q=+malloc+show:h4hfh-fV-jI:niBq_bwWZNs:H0OhClf0HWQ&sa=N&ct=rx&cd=2&cs_p=ftp://ftp.gnu.org/gnu/guile/guile-1.6.8.tar.gz&cs_f=guile-1.6.8/libguile/mallocs.c&cs_p=ftp://ftp.gnu.org/gnu/guile/guile-1.6.8.tar.gz&cs_f=guile-1.6.8/libguile/mallocs.c#first2007-12-19T16:08:04ZCode owned by external author.guile-1.6.8/libguile/mallocs.c<pre> 86: { scm_t_bits mem = n ? (scm_t_bits) <b>malloc</b> (n) : 0; if (n &amp;&amp; !mem) </pre><pre>#include &lt;<b>malloc</b>.h&gt; </pre><pre>scm_t_bits scm_tc16_<b>malloc</b>; </pre><pre><b>malloc</b>_free (SCM ptr) </pre><pre><b>malloc</b>_print (SCM exp, SCM port, scm_print_state *pstate SCM_UNUSED) </pre><pre> scm_puts(&quot;#&lt;<b>malloc</b> &quot;, port); </pre><pre> scm_t_bits mem = n ? (scm_t_bits) <b>malloc</b> (n) : 0; </pre><pre> SCM_RETURN_NEWSMOB (scm_tc16_<b>malloc</b>, mem); </pre><pre> scm_tc16_<b>malloc</b> = scm_make_smob_type (&quot;<b>malloc</b>&quot;, 0); </pre><pre> scm_set_smob_free (scm_tc16_<b>malloc</b>, <b>malloc</b>_free); </pre>GPL http://www.google.com/codesearch?hl=en&q=+malloc+show:9wyZUG-N_30:7_dFxoC1ZrY:C0_iYbFj90M&sa=N&ct=rx&cd=3&cs_p=http://ftp.gnu.org/gnu/bash/bash-3.0.tar.gz&cs_f=bash-3.0/lib/malloc/alloca.c&cs_p=http://ftp.gnu.org/gnu/bash/bash-3.0.tar.gz&cs_f=bash-3.0/lib/malloc/alloca.c#first2007-12-19T16:08:04ZCode owned by external author.bash-3.0/lib/malloc/alloca.c<pre> 78: #ifndef emacs #define <b>malloc</b> x<b>malloc</b> extern pointer x<b>malloc</b> (); </pre><pre> <b>malloc</b>. The Emacs executable needs alloca to call x<b>malloc</b>, because </pre><pre> ordinary <b>malloc</b> isn&#39;t protected from input signals. On the other </pre><pre> hand, the utilities in lib-src need alloca to call <b>malloc</b>; some of </pre><pre> them are very simple, and don&#39;t have an x<b>malloc</b> routine. </pre><pre> Callers below should use <b>malloc</b>. */ </pre><pre>#define <b>malloc</b> x<b>malloc</b> </pre><pre>extern pointer x<b>malloc</b> (); </pre><pre> It is very important that sizeof(header) agree with <b>malloc</b> </pre><pre> register pointer new = <b>malloc</b> (sizeof (header) + size); </pre>GPL http://www.google.com/codesearch?hl=en&q=+malloc+show:uhVCKyPcT6k:8juMxxzmUJw:H7_IDsTB2L4&sa=N&ct=rx&cd=4&cs_p=http://ftp.mozilla.org/pub/mozilla.org/mozilla/releases/mozilla1.7b/src/mozilla-source-1.7b-source.tar.bz2&cs_f=mozilla/xpcom/build/malloc.c&cs_p=http://ftp.mozilla.org/pub/mozilla.org/mozilla/releases/mozilla1.7b/src/mozilla-source-1.7b-source.tar.bz2&cs_f=mozilla/xpcom/build/malloc.c#first2007-12-19T16:08:04ZCode owned by external author.mozilla/xpcom/build/malloc.c<pre> 54: http://gee.cs.oswego.edu/dl/html/<b>malloc</b>.html You may already by default be using a c library containing a <b>malloc</b> </pre><pre>/* ---------- To make a <b>malloc</b>.h, start cutting here ------------ */ </pre><pre> Note: There may be an updated version of this <b>malloc</b> obtainable at </pre><pre> ftp://gee.cs.oswego.edu/pub/misc/<b>malloc</b>.c </pre><pre>* Why use this <b>malloc</b>? </pre><pre> most tunable <b>malloc</b> ever written. However it is among the fastest </pre><pre> allocator for <b>malloc</b>-intensive programs. </pre><pre> http://gee.cs.oswego.edu/dl/html/<b>malloc</b>.html </pre><pre> You may already by default be using a c library containing a <b>malloc</b> </pre><pre> that is somehow based on some version of this <b>malloc</b> (for example in </pre>Mozilla http://www.google.com/codesearch?hl=en&q=+malloc+show:4n1P2HVOISs:Ybbpph0wR2M:OhIN_sDrG0U&sa=N&ct=rx&cd=5&cs_p=http://regexps.srparish.net/src/hackerlab/hackerlab-1.0pre2.tar.gz&cs_f=hackerlab-1.0pre2/src/hackerlab/tests/mem-tests/unit-must-malloc.sh&cs_p=http://regexps.srparish.net/src/hackerlab/hackerlab-1.0pre2.tar.gz&cs_f=hackerlab-1.0pre2/src/hackerlab/tests/mem-tests/unit-must-malloc.sh#first2007-12-19T16:08:04ZCode owned by external author.hackerlab-1.0pre2/src/hackerlab/tests/mem-tests/unit-must-malloc.sh<pre> 11: echo ================ unit-must-<b>malloc</b> tests ================ ./unit-must-<b>malloc</b> echo ...passed </pre><pre># tag: Tom Lord Tue Dec 4 14:54:29 2001 (mem-tests/unit-must-<b>malloc</b>.sh) </pre><pre>echo ================ unit-must-<b>malloc</b> tests ================ </pre><pre>./unit-must-<b>malloc</b> </pre>GPL http://www.google.com/codesearch?hl=en&q=+malloc+show:GzkwiWG266M:ykuz3bG00ws:2sTvVSif08g&sa=N&ct=rx&cd=6&cs_p=http://ftp.gnu.org/gnu/tar/tar-1.14.tar.bz2&cs_f=tar-1.14/lib/malloc.c&cs_p=http://ftp.gnu.org/gnu/tar/tar-1.14.tar.bz2&cs_f=tar-1.14/lib/malloc.c#first2007-12-19T16:08:04ZCode owned by external author.tar-1.14/lib/malloc.c<pre> 22: #endif #undef <b>malloc</b> </pre><pre>/* Work around bug on some systems where <b>malloc</b> (0) fails. </pre><pre>#undef <b>malloc</b> </pre><pre>rpl_<b>malloc</b> (size_t n) </pre><pre> return <b>malloc</b> (n); </pre>GPL http://www.google.com/codesearch?hl=en&q=+malloc+show:o_TFIeBY6dY:ktI_dt8wPao:AI03BD1Dz0Y&sa=N&ct=rx&cd=7&cs_p=http://ftp.gnu.org/gnu/tar/tar-1.16.1.tar.gz&cs_f=tar-1.16.1/lib/malloc.c&cs_p=http://ftp.gnu.org/gnu/tar/tar-1.16.1.tar.gz&cs_f=tar-1.16.1/lib/malloc.c#first2007-12-19T16:08:04ZCode owned by external author.tar-1.16.1/lib/malloc.c<pre> 21: #include &lt;config.h&gt; #undef <b>malloc</b> </pre><pre>/* <b>malloc</b>() function that is glibc compatible. </pre><pre>#undef <b>malloc</b> </pre><pre>rpl_<b>malloc</b> (size_t n) </pre><pre> return <b>malloc</b> (n); </pre>GPL http://www.google.com/codesearch?hl=en&q=+malloc+show:_ibw-VLkMoI:jBOtIJSmFd4:-0NUEVeCwfY&sa=N&ct=rx&cd=8&cs_p=http://freshmeat.net/redir/uclibc/20616/url_bz2/uClibc-0.9.28.1.tar.bz2&cs_f=uClibc-0.9.29/include/malloc.h&cs_p=http://freshmeat.net/redir/uclibc/20616/url_bz2/uClibc-0.9.28.1.tar.bz2&cs_f=uClibc-0.9.29/include/malloc.h#first2007-12-19T16:08:04ZCode owned by external author.uClibc-0.9.29/include/malloc.h<pre> 1: /* Prototypes and definition for <b>malloc</b> implementation. Copyright (C) 1996, 1997, 1999, 2000 Free Software Foundation, Inc. </pre><pre>/* Prototypes and definition for <b>malloc</b> implementation. </pre><pre> `pt<b>malloc</b>&#39;, a <b>malloc</b> implementation for multiple threads without </pre><pre> See the files `pt<b>malloc</b>.c&#39; or `COPYRIGHT&#39; for copying conditions. </pre><pre> This work is mainly derived from <b>malloc</b>-2.6.4 by Doug Lea </pre><pre> ftp://g.oswego.edu/pub/misc/<b>malloc</b>.c </pre><pre> `pt<b>malloc</b>.c&#39;. </pre><pre># define __<b>malloc</b>_ptr_t void * </pre><pre># define __<b>malloc</b>_ptr_t char * </pre><pre># define __<b>malloc</b>_size_t size_t </pre>LGPL http://www.google.com/codesearch?hl=en&q=+malloc+show:F6qHcZ9vefo:bTX7o9gKfks:hECF4r_eKC0&sa=N&ct=rx&cd=9&cs_p=http://ftp.gnu.org/gnu/glibc/glibc-2.0.1.tar.gz&cs_f=glibc-2.0.1/hurd/hurdmalloc.h&cs_p=http://ftp.gnu.org/gnu/glibc/glibc-2.0.1.tar.gz&cs_f=glibc-2.0.1/hurd/hurdmalloc.h#first2007-12-19T16:08:04ZCode owned by external author.glibc-2.0.1/hurd/hurdmalloc.h<pre> 15: #define <b>malloc</b> _hurd_<b>malloc</b> #define realloc _hurd_realloc </pre><pre> All hurd-internal code which uses <b>malloc</b> et al includes this file so it </pre><pre> will use the internal <b>malloc</b> routines _hurd_{<b>malloc</b>,realloc,free} </pre><pre> of <b>malloc</b> et al is the unixoid one using sbrk. </pre><pre>extern void *_hurd_<b>malloc</b> (size_t); </pre><pre>#define <b>malloc</b> _hurd_<b>malloc</b> </pre>GPL http://www.google.com/codesearch?hl=en&q=+malloc+show:CHUvHYzyLc8:pdcAfzDA6lY:wjofHuNLTHg&sa=N&ct=rx&cd=10&cs_p=ftp://apache.mirrors.pair.com/httpd/httpd-2.2.4.tar.bz2&cs_f=httpd-2.2.4/srclib/apr/include/arch/netware/apr_private.h&cs_p=ftp://apache.mirrors.pair.com/httpd/httpd-2.2.4.tar.bz2&cs_f=httpd-2.2.4/srclib/apr/include/arch/netware/apr_private.h#first2007-12-19T16:08:04ZCode owned by external author.httpd-2.2.4/srclib/apr/include/arch/netware/apr_private.h<pre> 173: #undef <b>malloc</b> #define <b>malloc</b>(x) library_<b>malloc</b>(gLibHandle,x) </pre><pre>/* Redefine <b>malloc</b> to use the library <b>malloc</b> call so </pre><pre>#undef <b>malloc</b> </pre><pre>#define <b>malloc</b>(x) library_<b>malloc</b>(gLibHandle,x) </pre>Apache """ YOUTUBE_VIDEO_FEED = """http://gdata.youtube.com/feeds/api/standardfeeds/top_rated2008-05-14T02:24:07.000-07:00Top Ratedhttp://www.youtube.com/img/pic_youtubelogo_123x63.gifYouTubehttp://www.youtube.com/YouTube data API100125 http://gdata.youtube.com/feeds/api/videos/C71ypXYGho82008-03-20T10:17:27.000-07:002008-05-14T04:26:37.000-07:00Me odeio por te amar - KARYN GARCIAhttp://www.karyngarcia.com.brTvKarynGarciahttp://gdata.youtube.com/feeds/api/users/tvkaryngarciaMe odeio por te amar - KARYN GARCIAhttp://www.karyngarcia.com.bramar, boyfriend, garcia, karyn, me, odeio, por, teMusictest111test222 http://gdata.youtube.com/feeds/api/videos/gsVaTyb1tBw2008-02-15T04:31:45.000-08:002008-05-14T05:09:42.000-07:00extreme helmet cam Kani, Keil and Patotrimmedperaltamagichttp://gdata.youtube.com/feeds/api/users/peraltamagicextreme helmet cam Kani, Keil and Patotrimmedalcala, cam, campillo, dirt, extreme, helmet, kani, patoSports """ YOUTUBE_ENTRY_PRIVATE = """ http://gdata.youtube.com/feeds/videos/UMFI1hdm96E 2007-01-07T01:50:15.000Z 2007-01-07T01:50:15.000Z "Crazy (Gnarles Barkley)" - Acoustic Cover <div style="color: #000000;font-family: Arial, Helvetica, sans-serif; font-size:12px; font-size: 12px; width: 555px;"><table cellspacing="0" cellpadding="0" border="0"><tbody><tr><td width="140" valign="top" rowspan="2"><div style="border: 1px solid #999999; margin: 0px 10px 5px 0px;"><a href="http://www.youtube.com/watch?v=UMFI1hdm96E"><img alt="" src="http://img.youtube.com/vi/UMFI1hdm96E/2.jpg"></a></div></td> <td width="256" valign="top"><div style="font-size: 12px; font-weight: bold;"><a style="font-size: 15px; font-weight: bold; font-decoration: none;" href="http://www.youtube.com/watch?v=UMFI1hdm96E">&quot;Crazy (Gnarles Barkley)&quot; - Acoustic Cover</a> <br></div> <div style="font-size: 12px; margin: 3px 0px;"><span>Gnarles Barkley acoustic cover http://www.myspace.com/davidchoimusic</span></div></td> <td style="font-size: 11px; line-height: 1.4em; padding-left: 20px; padding-top: 1px;" width="146" valign="top"><div><span style="color: #666666; font-size: 11px;">From:</span> <a href="http://www.youtube.com/profile?user=davidchoimusic">davidchoimusic</a></div> <div><span style="color: #666666; font-size: 11px;">Views:</span> 113321</div> <div style="white-space: nowrap;text-align: left"><img style="border: 0px none; margin: 0px; padding: 0px; vertical-align: middle; font-size: 11px;" align="top" alt="" src="http://gdata.youtube.com/static/images/icn_star_full_11x11.gif"> <img style="border: 0px none; margin: 0px; padding: 0px; vertical-align: middle; font-size: 11px;" align="top" alt="" src="http://gdata.youtube.com/static/images/icn_star_full_11x11.gif"> <img style="border: 0px none; margin: 0px; padding: 0px; vertical-align: middle; font-size: 11px;" align="top" alt="" src="http://gdata.youtube.com/static/images/icn_star_full_11x11.gif"> <img style="border: 0px none; margin: 0px; padding: 0px; vertical-align: middle; font-size: 11px;" align="top" alt="" src="http://gdata.youtube.com/static/images/icn_star_full_11x11.gif"> <img style="border: 0px none; margin: 0px; padding: 0px; vertical-align: middle; font-size: 11px;" align="top" alt="" src="http://gdata.youtube.com/static/images/icn_star_half_11x11.gif"></div> <div style="font-size: 11px;">1005 <span style="color: #666666; font-size: 11px;">ratings</span></div></td></tr> <tr><td><span style="color: #666666; font-size: 11px;">Time:</span> <span style="color: #000000; font-size: 11px; font-weight: bold;">04:15</span></td> <td style="font-size: 11px; padding-left: 20px;"><span style="color: #666666; font-size: 11px;">More in</span> <a href="http://www.youtube.com/categories_portal?c=10">Music</a></td></tr></tbody></table></div> davidchoimusic http://gdata.youtube.com/feeds/users/davidchoimusic "Crazy (Gnarles Barkley)" - Acoustic Cover Gnarles Barkley acoustic cover http://www.myspace.com/davidchoimusic music, singing, gnarls, barkley, acoustic, cover Music DeveloperTag1 37.398529052734375 -122.0635986328125 yes The content of this video may violate the terms of use. """ YOUTUBE_COMMENT_FEED = """ http://gdata.youtube.com/feeds/videos/2Idhz9ef5oU/comments2008-05-19T21:45:45.261ZCommentshttp://www.youtube.com/img/pic_youtubelogo_123x63.gifYouTubehttp://www.youtube.com/YouTube data API0125 http://gdata.youtube.com/feeds/videos/2Idhz9ef5oU/comments/91F809A3DE2EB81B 2008-02-22T15:27:15.000-08:002008-02-22T15:27:15.000-08:00 test66 test66 apitestjhartmannhttp://gdata.youtube.com/feeds/users/apitestjhartmann http://gdata.youtube.com/feeds/videos/2Idhz9ef5oU/comments/A261AEEFD23674AA 2008-02-22T15:27:01.000-08:002008-02-22T15:27:01.000-08:00 test333 test333 apitestjhartmannhttp://gdata.youtube.com/feeds/users/apitestjhartmann http://gdata.youtube.com/feeds/videos/2Idhz9ef5oU/comments/0DCF1E3531B3FF85 2008-02-22T15:11:06.000-08:002008-02-22T15:11:06.000-08:00 test2 test2 apitestjhartmannhttp://gdata.youtube.com/feeds/users/apitestjhartmann """ YOUTUBE_PLAYLIST_FEED = """ http://gdata.youtube.com/feeds/users/andyland74/playlists?start-index=1&max-results=25 2008-02-26T00:26:15.635Z andyland74's Playlists http://www.youtube.com/img/pic_youtubelogo_123x63.gif andyland74 http://gdata.youtube.com/feeds/users/andyland74 YouTube data API 1 1 25 My new playlist Description http://gdata.youtube.com/feeds/users/andyland74/playlists/8BCDD04DE8F771B2 2007-11-04T17:30:27.000-08:00 2008-02-22T09:55:14.000-08:00 My New Playlist Title My new playlist Description andyland74 http://gdata.youtube.com/feeds/users/andyland74 """ YOUTUBE_PLAYLIST_VIDEO_FEED = """http://gdata.youtube.com/feeds/api/playlists/BCB3BB96DF51B5052008-05-16T12:03:17.000-07:00Test PlaylistTest playlist 1http://www.youtube.com/img/pic_youtubelogo_123x63.gifgdpythonhttp://gdata.youtube.com/feeds/api/users/gdpythonYouTube data API1125Test PlaylistTest playlist 1http://gdata.youtube.com/feeds/api/playlists/BCB3BB96DF51B505/B0F29389E537F8882008-05-16T20:54:08.520ZUploading YouTube Videos with the PHP Client LibraryJochen Hartmann demonstrates the basics of how to use the PHP Client Library with the YouTube Data API. PHP Developer's Guide: http://code.google.com/apis/youtube/developers_guide_php.html Other documentation: http://code.google.com/apis/youtube/GoogleDevelopershttp://gdata.youtube.com/feeds/api/users/googledevelopersUploading YouTube Videos with the PHP Client LibraryJochen Hartmann demonstrates the basics of how to use the PHP Client Library with the YouTube Data API. PHP Developer's Guide: http://code.google.com/apis/youtube/developers_guide_php.html Other documentation: http://code.google.com/apis/youtube/api, data, demo, php, screencast, tutorial, uploading, walkthrough, youtubeEducationundefined1""" YOUTUBE_SUBSCRIPTION_FEED = """ http://gdata.youtube.com/feeds/users/andyland74/subscriptions?start-index=1&max-results=25 2008-02-26T00:26:15.635Z andyland74's Subscriptions http://www.youtube.com/img/pic_youtubelogo_123x63.gif andyland74 http://gdata.youtube.com/feeds/users/andyland74 YouTube data API 1 1 25 http://gdata.youtube.com/feeds/users/andyland74/subscriptions/d411759045e2ad8c 2007-11-04T17:30:27.000-08:00 2008-02-22T09:55:14.000-08:00 Videos published by : NBC andyland74 http://gdata.youtube.com/feeds/users/andyland74 NBC """ YOUTUBE_VIDEO_RESPONSE_FEED = """ http://gdata.youtube.com/feeds/videos/2c3q9K4cHzY/responses2008-05-19T22:37:34.076ZVideos responses to 'Giant NES controller coffee table'http://www.youtube.com/img/pic_youtubelogo_123x63.gifYouTubehttp://www.youtube.com/YouTube data API8125 http://gdata.youtube.com/feeds/videos/7b9EnRI9VbY2008-03-11T19:08:53.000-07:002008-05-18T21:33:10.000-07:00 Catnip Partysnipped PismoBeachhttp://gdata.youtube.com/feeds/users/pismobeach Catnip Party Uncle, Hillary, Hankette, and B4 all but overdose on the patioBrattman, cat, catmint, catnip, cats, chat, drug, gato, gatto, kat, kato, katt, Katze, kedi, kissa, OD, overdose, party, sex, Uncle Animals """ YOUTUBE_PROFILE = """ http://gdata.youtube.com/feeds/users/andyland74 2006-10-16T00:09:45.000-07:00 2008-02-26T11:48:21.000-08:00 andyland74 Channel andyland74 http://gdata.youtube.com/feeds/users/andyland74 33 andyland74 andy example Catch-22 m Google Testing YouTube APIs Somewhere US Aqua Teen Hungerforce Elliott Smith Technical Writer University of North Carolina """ YOUTUBE_CONTACTS_FEED = """ http://gdata.youtube.com/feeds/users/apitestjhartmann/contacts2008-05-16T19:24:34.916Zapitestjhartmann's Contactshttp://www.youtube.com/img/pic_youtubelogo_123x63.gifapitestjhartmannhttp://gdata.youtube.com/feeds/users/apitestjhartmannYouTube data API2125 http://gdata.youtube.com/feeds/users/apitestjhartmann/contacts/test898990902008-02-04T11:27:54.000-08:002008-05-16T19:24:34.916Ztest89899090apitestjhartmannhttp://gdata.youtube.com/feeds/users/apitestjhartmanntest89899090requested http://gdata.youtube.com/feeds/users/apitestjhartmann/contacts/testjfisher2008-02-26T14:13:03.000-08:002008-05-16T19:24:34.916Ztestjfisherapitestjhartmannhttp://gdata.youtube.com/feeds/users/apitestjhartmanntestjfisherpending """ NEW_CONTACT = """ http://www.google.com/m8/feeds/contacts/liz%40gmail.com/base/8411573 2008-02-28T18:47:02.303Z Fitzgerald Notes (206)555-1212 456-123-2133 (206)555-1213 1600 Amphitheatre Pkwy Mountain View """ CONTACTS_FEED = """ http://www.google.com/m8/feeds/contacts/liz%40gmail.com/base 2008-03-05T12:36:38.836Z Contacts Elizabeth Bennet liz@gmail.com Contacts 1 1 25 http://www.google.com/m8/feeds/contacts/liz%40gmail.com/base/c9012de 2008-03-05T12:36:38.835Z Fitzgerald 456 """ CONTACT_GROUPS_FEED = """ jo@gmail.com 2008-05-21T21:11:25.237Z Jo's Contact Groups Jo Brown jo@gmail.com Contacts 3 1 25 http://google.com/m8/feeds/groups/jo%40gmail.com/base/270f 2008-05-14T13:10:19.070Z joggers joggers """ CONTACT_GROUP_ENTRY = """ http://www.google.com/feeds/groups/jo%40gmail.com/base/1234 2005-01-18T21:00:00Z 2006-01-01T00:00:00Z Salsa group Salsa group Very nice people. """ CALENDAR_RESOURCE_ENTRY = """ """ CALENDAR_RESOURCES_FEED = """ https://apps-apis.google.com/a/feeds/calendar/resource/2.0/yourdomain.com 2008-10-17T15:29:21.064Z 1 https://apps-apis.google.com/a/feeds/calendar/resource/2.0/yourdomain.com/CR-NYC-14-12-BR 2008-10-17T15:29:21.064Z https://apps-apis.google.com/a/feeds/calendar/resource/2.0/yourdomain.com/?start=(Bike)-London-43-Lobby-Bike-1 2008-10-17T15:29:21.064Z """ BLOG_ENTRY = """ tag:blogger.com,1999:blog-blogID.post-postID 2006-08-02T18:44:43.089-07:00 2006-11-08T18:10:23.020-08:00 Lizzy's Diary Being the journal of Elizabeth Bennet Elizabeth Bennet liz@gmail.com """ BLOG_POST = """ Marriage!

    Mr. Darcy has proposed marriage to me!

    He is the last man on earth I would ever desire to marry.

    Whatever shall I do?

    Elizabeth Bennet liz@gmail.com
    """ BLOG_POSTS_FEED = """ tag:blogger.com,1999:blog-blogID 2006-11-08T18:10:23.020-08:00 Lizzy's Diary Elizabeth Bennet liz@gmail.com Blogger tag:blogger.com,1999:blog-blogID.post-postID 2006-11-08T18:10:00.000-08:00 2006-11-08T18:10:14.954-08:00 Quite disagreeable <p>I met Mr. Bingley's friend Mr. Darcy this evening. I found him quite disagreeable.</p> Elizabeth Bennet liz@gmail.com """ BLOG_COMMENTS_FEED = """ tag:blogger.com,1999:blog-blogID.postpostID..comments 2007-04-04T21:56:29.803-07:00 My Blog : Time to relax Blog Author name Blogger 1 1 tag:blogger.com,1999:blog-blogID.post-commentID 2007-04-04T21:56:00.000-07:00 2007-04-04T21:56:29.803-07:00 This is my first comment This is my first comment Blog Author name """ SITES_FEED = """ https://www.google.com/webmasters/tools/feeds/sites Sites 1 2008-10-02T07:26:51.833Z http://www.example.com http://www.example.com 2007-11-17T18:27:32.543Z true 2008-09-14T08:59:28.000 US none normal true false 456456-google.html """ SITEMAPS_FEED = """ http://www.example.com http://www.example.com/ 2006-11-17T18:27:32.543Z HTML WAP Value1 Value2 Value3 http://www.example.com/sitemap-index.xml http://www.example.com/sitemap-index.xml 2006-11-17T18:27:32.543Z WEB StatusValue 2006-11-18T19:27:32.543Z 102 http://www.example.com/mobile/sitemap-index.xml http://www.example.com/mobile/sitemap-index.xml 2006-11-17T18:27:32.543Z StatusValue 2006-11-18T19:27:32.543Z 102 HTML http://www.example.com/news/sitemap-index.xml http://www.example.com/news/sitemap-index.xml 2006-11-17T18:27:32.543Z StatusValue 2006-11-18T19:27:32.543Z 102 LabelValue """ HEALTH_CCR_NOTICE_PAYLOAD = """ Start date 2007-04-04T07:00:00Z Aortic valve disorders 410.10 ICD9 2004 Active """ HEALTH_PROFILE_ENTRY_DIGEST = """ https://www.google.com/health/feeds/profile/default/vneCn5qdEIY_digest 2008-09-29T07:52:17.176Z vneCn5qdEIY English en ISO-639-1 V1.0 2008-09-29T07:52:17.176Z Google Health Profile Pregnancy status Not pregnant user@google.com Patient Breastfeeding status Not breastfeeding user@gmail.com Patient Hn0FE0IlcY-FMFFgSTxkvA/CONDITION/0 Start date 2007-04-04T07:00:00Z Aortic valve disorders 410.10 ICD9 2004 Active example.com Information Provider Malaria 136.9 ICD9_Broader 084.6 ICD9 ACTIVE user@gmail.com Patient Race S15814 HL7 White user@gmail.com Patient Allergy A-Fil ACTIVE user@gmail.com Patient Severe Allergy A.E.R Traveler ACTIVE user@gmail.com Patient Severe ACTIVE user@gmail.com Patient A& D 0 0 To skin C38305 FDA 0 ACTIVE user@gmail.com Patient A-Fil 0 0 To skin C38305 FDA 0 ACTIVE user@gmail.com Patient Lipitor 0 0 By mouth C38288 FDA 0 user@gmail.com Patient Chickenpox Vaccine 21 HL7 user@gmail.com Patient Height 0 70 inches user@gmail.com Patient Weight 0 2480 ounces user@gmail.com Patient Blood Type 0 O+ user@gmail.com Patient Collection start date 2008-09-03 Acetaldehyde - Blood 0 Abdominal Ultrasound user@gmail.com Patient Abdominoplasty user@gmail.com Patient Google Health Profile 1984-07-22 Male user@gmail.com Patient """ HEALTH_PROFILE_FEED = """ https://www.google.com/health/feeds/profile/default 2008-09-30T01:07:17.888Z Profile Feed 1 https://www.google.com/health/feeds/profile/default/DysasdfARnFAao 2008-09-29T03:12:50.850Z 2008-09-29T03:12:50.850Z <content type="html"/> <link rel="http://schemas.google.com/health/data#complete" type="application/atom+xml" href="https://www.google.com/health/feeds/profile/default/-/MEDICATION/%7Bhttp%3A%2F%2Fschemas.google.com%2Fg%2F2005%23kind%7Dhttp%3A%2F%2Fschemas.google.com%2Fhealth%2Fkinds%23profile/%7Bhttp%3A%2F%2Fschemas.google.com%2Fhealth%2Fitem%7DA%26+D"/> <link rel="self" type="application/atom+xml" href="https://www.google.com/health/feeds/profile/default/DysasdfARnFAao"/> <link rel="edit" type="application/atom+xml" href="https://www.google.com/health/feeds/profile/default/DysasdfARnFAao"/> <author> <name>User Name</name> <email>user@gmail.com</email> </author> <ContinuityOfCareRecord xmlns="urn:astm-org:CCR"> <CCRDocumentObjectID>hiD9sEigSzdk8nNT0evR4g</CCRDocumentObjectID> <Language/> <DateTime> <Type/> </DateTime> <Patient/> <Body> <Medications> <Medication> <Type/> <Description/> <Status> <Text>ACTIVE</Text> </Status> <Source> <Actor> <ActorID>user@gmail.com</ActorID> <ActorRole> <Text>Patient</Text> </ActorRole> </Actor> </Source> <Product> <ProductName> <Text>A& D</Text> </ProductName> <Strength> <Units/> <StrengthSequencePosition>0</StrengthSequencePosition> <VariableStrengthModifier/> </Strength> </Product> <Directions> <Direction> <Description/> <DeliveryMethod/> <Dose> <Units/> <DoseSequencePosition>0</DoseSequencePosition> <VariableDoseModifier/> </Dose> <Route> <Text>To skin</Text> <Code> <Value>C38305</Value> <CodingSystem>FDA</CodingSystem> </Code> <RouteSequencePosition>0</RouteSequencePosition> <MultipleRouteModifier/> </Route> </Direction> </Directions> <Refills/> </Medication> </Medications> </Body> </ContinuityOfCareRecord> </entry> <entry> <id>https://www.google.com/health/feeds/profile/default/7I1WQzZrgp4</id> <published>2008-09-29T03:27:14.909Z</published> <updated>2008-09-29T03:27:14.909Z</updated> <category scheme="http://schemas.google.com/g/2005#kind" term="http://schemas.google.com/health/kinds#profile"/> <category scheme="http://schemas.google.com/health/item" term="A-Fil"/> <category term="ALLERGY"/> <title type="text"/> <content type="html"/> <link rel="http://schemas.google.com/health/data#complete" type="application/atom+xml" href="https://www.google.com/health/feeds/profile/default/-/%7Bhttp%3A%2F%2Fschemas.google.com%2Fg%2F2005%23kind%7Dhttp%3A%2F%2Fschemas.google.com%2Fhealth%2Fkinds%23profile/%7Bhttp%3A%2F%2Fschemas.google.com%2Fhealth%2Fitem%7DA-Fil/ALLERGY"/> <link rel="self" type="application/atom+xml" href="https://www.google.com/health/feeds/profile/default/7I1WQzZrgp4"/> <link rel="edit" type="application/atom+xml" href="https://www.google.com/health/feeds/profile/default/7I1WQzZrgp4"/> <author> <name>User Name</name> <email>user@gmail.com</email> </author> <ContinuityOfCareRecord xmlns="urn:astm-org:CCR"> <CCRDocumentObjectID>YOyHDxQUiECCPgnsjV8SlQ</CCRDocumentObjectID> <Language/> <DateTime> <Type/> </DateTime> <Patient/> <Body> <Alerts> <Alert> <Type> <Text>Allergy</Text> </Type> <Description> <Text>A-Fil</Text> </Description> <Status> <Text>ACTIVE</Text> </Status> <Source> <Actor> <ActorID>user@gmail.com</ActorID> <ActorRole> <Text>Patient</Text> </ActorRole> </Actor> </Source> <Reaction> <Description/> <Severity> <Text>Severe</Text> </Severity> </Reaction> </Alert> </Alerts> </Body> </ContinuityOfCareRecord> </entry> <entry> <id>https://www.google.com/health/feeds/profile/default/Dz9wV83sKFg</id> <published>2008-09-29T03:12:52.166Z</published> <updated>2008-09-29T03:12:52.167Z</updated> <category term="MEDICATION"/> <category scheme="http://schemas.google.com/g/2005#kind" term="http://schemas.google.com/health/kinds#profile"/> <category scheme="http://schemas.google.com/health/item" term="A-Fil"/> <title type="text"/> <content type="html"/> <link rel="http://schemas.google.com/health/data#complete" type="application/atom+xml" href="https://www.google.com/health/feeds/profile/default/-/MEDICATION/%7Bhttp%3A%2F%2Fschemas.google.com%2Fg%2F2005%23kind%7Dhttp%3A%2F%2Fschemas.google.com%2Fhealth%2Fkinds%23profile/%7Bhttp%3A%2F%2Fschemas.google.com%2Fhealth%2Fitem%7DA-Fil"/> <link rel="self" type="application/atom+xml" href="https://www.google.com/health/feeds/profile/default/Dz9wV83sKFg"/> <link rel="edit" type="application/atom+xml" href="https://www.google.com/health/feeds/profile/default/Dz9wV83sKFg"/> <author> <name>User Name</name> <email>user@gmail.com</email> </author> <ContinuityOfCareRecord xmlns="urn:astm-org:CCR"> <CCRDocumentObjectID>7w.XFEPeuIYN3Rn32pUiUw</CCRDocumentObjectID> <Language/> <DateTime> <Type/> </DateTime> <Patient/> <Body> <Medications> <Medication> <Type/> <Description/> <Status> <Text>ACTIVE</Text> </Status> <Source> <Actor> <ActorID>user@gmail.com</ActorID> <ActorRole> <Text>Patient</Text> </ActorRole> </Actor> </Source> <Product> <ProductName> <Text>A-Fil</Text> </ProductName> <Strength> <Units/> <StrengthSequencePosition>0</StrengthSequencePosition> <VariableStrengthModifier/> </Strength> </Product> <Directions> <Direction> <Description/> <DeliveryMethod/> <Dose> <Units/> <DoseSequencePosition>0</DoseSequencePosition> <VariableDoseModifier/> </Dose> <Route> <Text>To skin</Text> <Code> <Value>C38305</Value> <CodingSystem>FDA</CodingSystem> </Code> <RouteSequencePosition>0</RouteSequencePosition> <MultipleRouteModifier/> </Route> </Direction> </Directions> <Refills/> </Medication> </Medications> </Body> </ContinuityOfCareRecord> </entry> <entry> <id>https://www.google.com/health/feeds/profile/default/lzsxVzqZUyw</id> <published>2008-09-29T03:13:07.496Z</published> <updated>2008-09-29T03:13:07.497Z</updated> <category scheme="http://schemas.google.com/health/item" term="A.E.R Traveler"/> <category scheme="http://schemas.google.com/g/2005#kind" term="http://schemas.google.com/health/kinds#profile"/> <category term="ALLERGY"/> <title type="text"/> <content type="html"/> <link rel="http://schemas.google.com/health/data#complete" type="application/atom+xml" href="https://www.google.com/health/feeds/profile/default/-/%7Bhttp%3A%2F%2Fschemas.google.com%2Fhealth%2Fitem%7DA.E.R+Traveler/%7Bhttp%3A%2F%2Fschemas.google.com%2Fg%2F2005%23kind%7Dhttp%3A%2F%2Fschemas.google.com%2Fhealth%2Fkinds%23profile/ALLERGY"/> <link rel="self" type="application/atom+xml" href="https://www.google.com/health/feeds/profile/default/lzsxVzqZUyw"/> <link rel="edit" type="application/atom+xml" href="https://www.google.com/health/feeds/profile/default/lzsxVzqZUyw"/> <author> <name>User Name</name> <email>user@gmail.com</email> </author> <ContinuityOfCareRecord xmlns="urn:astm-org:CCR"> <CCRDocumentObjectID>5efFB0J2WgEHNUvk2z3A1A</CCRDocumentObjectID> <Language/> <DateTime> <Type/> </DateTime> <Patient/> <Body> <Alerts> <Alert> <Type> <Text>Allergy</Text> </Type> <Description> <Text>A.E.R Traveler</Text> </Description> <Status> <Text>ACTIVE</Text> </Status> <Source> <Actor> <ActorID>user@gmail.com</ActorID> <ActorRole> <Text>Patient</Text> </ActorRole> </Actor> </Source> <Reaction> <Description/> <Severity> <Text>Severe</Text> </Severity> </Reaction> </Alert> </Alerts> </Body> </ContinuityOfCareRecord> </entry> <entry> <id>https://www.google.com/health/feeds/profile/default/6PvhfKAXyYw</id> <published>2008-09-29T03:13:02.123Z</published> <updated>2008-09-29T03:13:02.124Z</updated> <category scheme="http://schemas.google.com/g/2005#kind" term="http://schemas.google.com/health/kinds#profile"/> <category term="PROCEDURE"/> <category scheme="http://schemas.google.com/health/item" term="Abdominal Ultrasound"/> <title type="text"/> <content type="html"/> <link rel="http://schemas.google.com/health/data#complete" type="application/atom+xml" href="https://www.google.com/health/feeds/profile/default/-/%7Bhttp%3A%2F%2Fschemas.google.com%2Fg%2F2005%23kind%7Dhttp%3A%2F%2Fschemas.google.com%2Fhealth%2Fkinds%23profile/PROCEDURE/%7Bhttp%3A%2F%2Fschemas.google.com%2Fhealth%2Fitem%7DAbdominal+Ultrasound"/> <link rel="self" type="application/atom+xml" href="https://www.google.com/health/feeds/profile/default/6PvhfKAXyYw"/> <link rel="edit" type="application/atom+xml" href="https://www.google.com/health/feeds/profile/default/6PvhfKAXyYw"/> <author> <name>User Name</name> <email>user@gmail.com</email> </author> <ContinuityOfCareRecord xmlns="urn:astm-org:CCR"> <CCRDocumentObjectID>W3Wbvx_QHwG5pxVchpuF1A</CCRDocumentObjectID> <Language/> <DateTime> <Type/> </DateTime> <Patient/> <Body> <Procedures> <Procedure> <Type/> <Description> <Text>Abdominal Ultrasound</Text> </Description> <Status/> <Source> <Actor> <ActorID>user@gmail.com</ActorID> <ActorRole> <Text>Patient</Text> </ActorRole> </Actor> </Source> </Procedure> </Procedures> </Body> </ContinuityOfCareRecord> </entry> <entry> <id>https://www.google.com/health/feeds/profile/default/r2zGPGewCeU</id> <published>2008-09-29T03:13:03.434Z</published> <updated>2008-09-29T03:13:03.435Z</updated> <category scheme="http://schemas.google.com/health/item" term="Abdominoplasty"/> <category scheme="http://schemas.google.com/g/2005#kind" term="http://schemas.google.com/health/kinds#profile"/> <category term="PROCEDURE"/> <title type="text"/> <content type="html"/> <link rel="http://schemas.google.com/health/data#complete" type="application/atom+xml" href="https://www.google.com/health/feeds/profile/default/-/%7Bhttp%3A%2F%2Fschemas.google.com%2Fhealth%2Fitem%7DAbdominoplasty/%7Bhttp%3A%2F%2Fschemas.google.com%2Fg%2F2005%23kind%7Dhttp%3A%2F%2Fschemas.google.com%2Fhealth%2Fkinds%23profile/PROCEDURE"/> <link rel="self" type="application/atom+xml" href="https://www.google.com/health/feeds/profile/default/r2zGPGewCeU"/> <link rel="edit" type="application/atom+xml" href="https://www.google.com/health/feeds/profile/default/r2zGPGewCeU"/> <author> <name>User Name</name> <email>user@gmail.com</email> </author> <ContinuityOfCareRecord xmlns="urn:astm-org:CCR"> <CCRDocumentObjectID>OUKgj5X0KMnbkC5sDL.yHA</CCRDocumentObjectID> <Language/> <DateTime> <Type/> </DateTime> <Patient/> <Body> <Procedures> <Procedure> <Type/> <Description> <Text>Abdominoplasty</Text> </Description> <Status/> <Source> <Actor> <ActorID>user@gmail.com</ActorID> <ActorRole> <Text>Patient</Text> </ActorRole> </Actor> </Source> </Procedure> </Procedures> </Body> </ContinuityOfCareRecord> </entry> <entry> <id>https://www.google.com/health/feeds/profile/default/_cCCbQ0O3ug</id> <published>2008-09-29T03:13:29.041Z</published> <updated>2008-09-29T03:13:29.042Z</updated> <category scheme="http://schemas.google.com/g/2005#kind" term="http://schemas.google.com/health/kinds#profile"/> <category scheme="http://schemas.google.com/health/item" term="Acetaldehyde - Blood"/> <category term="LABTEST"/> <title type="text"/> <content type="html"/> <link rel="http://schemas.google.com/health/data#complete" type="application/atom+xml" href="https://www.google.com/health/feeds/profile/default/-/%7Bhttp%3A%2F%2Fschemas.google.com%2Fg%2F2005%23kind%7Dhttp%3A%2F%2Fschemas.google.com%2Fhealth%2Fkinds%23profile/%7Bhttp%3A%2F%2Fschemas.google.com%2Fhealth%2Fitem%7DAcetaldehyde+-+Blood/LABTEST"/> <link rel="self" type="application/atom+xml" href="https://www.google.com/health/feeds/profile/default/_cCCbQ0O3ug"/> <link rel="edit" type="application/atom+xml" href="https://www.google.com/health/feeds/profile/default/_cCCbQ0O3ug"/> <author> <name>User Name</name> <email>user@gmail.com</email> </author> <ContinuityOfCareRecord xmlns="urn:astm-org:CCR"> <CCRDocumentObjectID>YWtomFb8aG.DueZ7z7fyug</CCRDocumentObjectID> <Language/> <DateTime> <Type/> </DateTime> <Patient/> <Body> <Results> <Result> <Type/> <Description/> <Status/> <Source> <Actor> <ActorID>user@gmail.com</ActorID> <ActorRole> <Text>Patient</Text> </ActorRole> </Actor> </Source> <Substance/> <Test> <DateTime> <Type> <Text>Collection start date</Text> </Type> <ExactDateTime>2008-09-03</ExactDateTime> </DateTime> <Type/> <Description> <Text>Acetaldehyde - Blood</Text> </Description> <Status/> <TestResult> <ResultSequencePosition>0</ResultSequencePosition> <VariableResultModifier/> <Units/> </TestResult> <ConfidenceValue/> </Test> </Result> </Results> </Body> </ContinuityOfCareRecord> </entry> <entry> <id>https://www.google.com/health/feeds/profile/default/BdyA3iJZyCc</id> <published>2008-09-29T03:00:45.915Z</published> <updated>2008-09-29T03:00:45.915Z</updated> <category scheme="http://schemas.google.com/health/item" term="Aortic valve disorders"/> <category scheme="http://schemas.google.com/g/2005#kind" term="http://schemas.google.com/health/kinds#profile"/> <category term="CONDITION"/> <title type="text">Aortic valve disorders example.com example.com h1ljpoeKJ85li.1FHsG9Gw Hn0FE0IlcY-FMFFgSTxkvA/CONDITION/0 Start date 2007-04-04T07:00:00Z Aortic valve disorders 410.10 ICD9 2004 Active example.com Information Provider https://www.google.com/health/feeds/profile/default/Cl.aMWIH5VA 2008-09-29T03:13:34.996Z 2008-09-29T03:13:34.997Z <content type="html"/> <link rel="http://schemas.google.com/health/data#complete" type="application/atom+xml" href="https://www.google.com/health/feeds/profile/default/-/%7Bhttp%3A%2F%2Fschemas.google.com%2Fg%2F2005%23kind%7Dhttp%3A%2F%2Fschemas.google.com%2Fhealth%2Fkinds%23profile/%7Bhttp%3A%2F%2Fschemas.google.com%2Fhealth%2Fitem%7DChickenpox+Vaccine/IMMUNIZATION"/> <link rel="self" type="application/atom+xml" href="https://www.google.com/health/feeds/profile/default/Cl.aMWIH5VA"/> <link rel="edit" type="application/atom+xml" href="https://www.google.com/health/feeds/profile/default/Cl.aMWIH5VA"/> <author> <name>User Name</name> <email>user@gmail.com</email> </author> <ContinuityOfCareRecord xmlns="urn:astm-org:CCR"> <CCRDocumentObjectID>KlhUqfftgELIitpKbqYalw</CCRDocumentObjectID> <Language/> <DateTime> <Type/> </DateTime> <Patient/> <Body> <Immunizations> <Immunization> <Type/> <Description/> <Status/> <Source> <Actor> <ActorID>user@gmail.com</ActorID> <ActorRole> <Text>Patient</Text> </ActorRole> </Actor> </Source> <Product> <ProductName> <Text>Chickenpox Vaccine</Text> <Code> <Value>21</Value> <CodingSystem>HL7</CodingSystem> </Code> </ProductName> </Product> <Directions> <Direction> <Description/> <DeliveryMethod/> </Direction> </Directions> <Refills/> </Immunization> </Immunizations> </Body> </ContinuityOfCareRecord> </entry> <entry> <id>https://www.google.com/health/feeds/profile/default/l0a7.FlX3_0</id> <published>2008-09-29T03:14:47.461Z</published> <updated>2008-09-29T03:14:47.461Z</updated> <category scheme="http://schemas.google.com/g/2005#kind" term="http://schemas.google.com/health/kinds#profile"/> <category term="DEMOGRAPHICS"/> <category scheme="http://schemas.google.com/health/item" term="Demographics"/> <title type="text">Demographics User Name user@gmail.com U5GDAVOxFbexQw3iyvqPYg 1984-07-22 Male user@gmail.com Patient https://www.google.com/health/feeds/profile/default/oIBDdgwFLyo 2008-09-29T03:14:47.690Z 2008-09-29T03:14:47.691Z FunctionalStatus User Name user@gmail.com W.EJcnhxb7W5M4eR4Tr1YA Pregnancy status Not pregnant user@gmail.com Patient Breastfeeding status Not breastfeeding user@gmail.com Patient https://www.google.com/health/feeds/profile/default/wwljIlXuTVg 2008-09-29T03:26:10.080Z 2008-09-29T03:26:10.081Z <content type="html"/> <link rel="http://schemas.google.com/health/data#complete" type="application/atom+xml" href="https://www.google.com/health/feeds/profile/default/-/MEDICATION/%7Bhttp%3A%2F%2Fschemas.google.com%2Fg%2F2005%23kind%7Dhttp%3A%2F%2Fschemas.google.com%2Fhealth%2Fkinds%23profile/%7Bhttp%3A%2F%2Fschemas.google.com%2Fhealth%2Fitem%7DLipitor"/> <link rel="self" type="application/atom+xml" href="https://www.google.com/health/feeds/profile/default/wwljIlXuTVg"/> <link rel="edit" type="application/atom+xml" href="https://www.google.com/health/feeds/profile/default/wwljIlXuTVg"/> <author> <name>User Name</name> <email>user@gmail.com</email> </author> <ContinuityOfCareRecord xmlns="urn:astm-org:CCR"> <CCRDocumentObjectID>OrpghzvvbG_YaO5koqT2ug</CCRDocumentObjectID> <Language/> <DateTime> <Type/> </DateTime> <Patient/> <Body> <Medications> <Medication> <Type/> <Description/> <Status> <Text>ACTIVE</Text> </Status> <Source> <Actor> <ActorID>user@gmail.com</ActorID> <ActorRole> <Text>Patient</Text> </ActorRole> </Actor> </Source> <Product> <ProductName> <Text>Lipitor</Text> </ProductName> <Strength> <Units/> <StrengthSequencePosition>0</StrengthSequencePosition> <VariableStrengthModifier/> </Strength> </Product> <Directions> <Direction> <Description/> <DeliveryMethod/> <Dose> <Units/> <DoseSequencePosition>0</DoseSequencePosition> <VariableDoseModifier/> </Dose> <Route> <Text>By mouth</Text> <Code> <Value>C38288</Value> <CodingSystem>FDA</CodingSystem> </Code> <RouteSequencePosition>0</RouteSequencePosition> <MultipleRouteModifier/> </Route> </Direction> </Directions> <Refills/> </Medication> </Medications> </Body> </ContinuityOfCareRecord> </entry> <entry> <id>https://www.google.com/health/feeds/profile/default/dd09TR12SiY</id> <published>2008-09-29T07:52:17.175Z</published> <updated>2008-09-29T07:52:17.176Z</updated> <category scheme="http://schemas.google.com/g/2005#kind" term="http://schemas.google.com/health/kinds#profile"/> <category scheme="http://schemas.google.com/health/item" term="Malaria"/> <category term="CONDITION"/> <title type="text"/> <content type="html"/> <link rel="http://schemas.google.com/health/data#complete" type="application/atom+xml" href="https://www.google.com/health/feeds/profile/default/-/%7Bhttp%3A%2F%2Fschemas.google.com%2Fg%2F2005%23kind%7Dhttp%3A%2F%2Fschemas.google.com%2Fhealth%2Fkinds%23profile/%7Bhttp%3A%2F%2Fschemas.google.com%2Fhealth%2Fitem%7DMalaria/CONDITION"/> <link rel="self" type="application/atom+xml" href="https://www.google.com/health/feeds/profile/default/dd09TR12SiY"/> <link rel="edit" type="application/atom+xml" href="https://www.google.com/health/feeds/profile/default/dd09TR12SiY"/> <author> <name>User Name</name> <email>user@gmail.com</email> </author> <ContinuityOfCareRecord xmlns="urn:astm-org:CCR"> <CCRDocumentObjectID>XF99N6X4lpy.jfPUPLMMSQ</CCRDocumentObjectID> <Language/> <DateTime> <Type/> </DateTime> <Patient/> <Body> <Problems> <Problem> <Type/> <Description> <Text>Malaria</Text> <Code> <Value>136.9</Value> <CodingSystem>ICD9_Broader</CodingSystem> </Code> <Code> <Value>084.6</Value> <CodingSystem>ICD9</CodingSystem> </Code> </Description> <Status> <Text>ACTIVE</Text> </Status> <Source> <Actor> <ActorID>user@gmail.com</ActorID> <ActorRole> <Text>Patient</Text> </ActorRole> </Actor> </Source> <HealthStatus> <Description/> </HealthStatus> </Problem> </Problems> </Body> </ContinuityOfCareRecord> </entry> <entry> <id>https://www.google.com/health/feeds/profile/default/aS0Cf964DPs</id> <published>2008-09-29T03:14:47.463Z</published> <updated>2008-09-29T03:14:47.463Z</updated> <category scheme="http://schemas.google.com/g/2005#kind" term="http://schemas.google.com/health/kinds#profile"/> <category term="DEMOGRAPHICS"/> <category scheme="http://schemas.google.com/health/item" term="SocialHistory (Drinking, Smoking)"/> <title type="text">SocialHistory (Drinking, Smoking) User Name user@gmail.com kXylGU5YXLBzriv61xPGZQ Race S15814 HL7 White user@gmail.com Patient https://www.google.com/health/feeds/profile/default/s5lII5xfj_g 2008-09-29T03:14:47.544Z 2008-09-29T03:14:47.545Z VitalSigns User Name user@gmail.com FTTIiY0TVVj35kZqFFjPjQ user@gmail.com Patient Height 0 70 inches user@gmail.com Patient Weight 0 2480 ounces user@gmail.com Patient Blood Type 0 O+ """ HEALTH_PROFILE_LIST_ENTRY = """ https://www.google.com/health/feeds/profile/list/vndCn5sdfwdEIY 1970-01-01T00:00:00.000Z profile name vndCn5sdfwdEIY user@gmail.com """ BOOK_ENTRY = """"""\ """"""\ """http://www.google.com/books/feeds/volumes/b7GZr5Btp30C"""\ """2009-04-24T23:35:16.000Z"""\ """"""\ """A theory of justice"""\ """"""\ """"""\ """"""\ """"""\ """"""\ """"""\ """"""\ """"""\ """"""\ """John Rawls"""\ """1999"""\ """p Since it appeared in 1971, John Rawls's i A Theory of Justice /i has become a classic. The author has now revised the original edition to clear up a number of difficulties he and others have found in the original book. /p p Rawls aims to express an essential part of the common core of the democratic tradition--justice as fairness--and to provide an alternative to utilitarianism, which had dominated the Anglo-Saxon tradition of political thought since the nineteenth century. Rawls substitutes the ideal of the social contract as a more satisfactory account of the basic rights and liberties of citizens as free and equal persons. "Each person," writes Rawls, "possesses an inviolability founded on justice that even the welfare of society as a whole cannot override." Advancing the ideas of Rousseau, Kant, Emerson, and Lincoln, Rawls's theory is as powerful today as it was when first published. /p"""\ """538 pages"""\ """b7GZr5Btp30C"""\ """ISBN:0198250541"""\ """ISBN:9780198250548"""\ """en"""\ """Oxford University Press"""\ """A theory of justice"""\ """""" BOOK_FEED = """"""\ """"""\ """http://www.google.com/books/feeds/volumes"""\ """2009-04-24T23:39:47.000Z"""\ """"""\ """Search results for 9780198250548"""\ """"""\ """"""\ """"""\ """"""\ """Google Books Search"""\ """http://www.google.com"""\ """"""\ """Google Book Search data API"""\ """1"""\ """1"""\ """20"""\ """"""\ """http://www.google.com/books/feeds/volumes/b7GZr5Btp30C"""\ """2009-04-24T23:39:47.000Z"""\ """"""\ """A theory of justice"""\ """"""\ """"""\ """"""\ """"""\ """"""\ """"""\ """"""\ """"""\ """"""\ """John Rawls"""\ """1999"""\ """... 9780198250548 ..."""\ """538 pages"""\ """b7GZr5Btp30C"""\ """ISBN:0198250541"""\ """ISBN:9780198250548"""\ """Law"""\ """A theory of justice"""\ """"""\ """""" MAP_FEED = """ http://maps.google.com/maps/feeds/maps/208825816854482607313 2009-07-27T18:48:29.631Z My maps Roman 1 1 1 http://maps.google.com/maps/feeds/maps/208825816854482607313/00046fb45f88fa910bcea 2009-07-27T18:46:34.451Z 2009-07-27T18:48:29.631Z 2009-07-27T18:48:29.631Z yes Untitled Roman """ MAP_ENTRY = """ http://maps.google.com/maps/feeds/maps/208825816854482607313/00046fb45f88fa910bcea 2009-07-27T18:46:34.451Z 2009-07-27T18:48:29.631Z 2009-07-27T18:48:29.631Z yes Untitled Roman """ MAP_FEATURE_FEED = """ http://maps.google.com/maps/feeds/features/208825816854482607313/00046fb45f88fa910bcea 2009-07-27T18:48:29.631Z Untitled 4 1 4 http://maps.google.com/maps/feeds/features/208825816854482607313/00046fb45f88fa910bcea/00046fb4632573b19e0b7 2009-07-27T18:47:35.037Z 2009-07-27T18:47:35.037Z 2009-07-27T18:47:35.037Z Some feature title Some feature title Some feature content
    ]]> -113.818359,41.442726,0.0 Roman Roman http://maps.google.com/maps/feeds/features/208825816854482607313/00046fb45f88fa910bcea/00046fb46325e839a11e6 2009-07-27T18:47:35.067Z 2009-07-27T18:48:22.184Z 2009-07-27T18:48:22.184Z A cool poly! A cool poly! And a description
    ]]> 1 -109.775391,47.457809,0.0 -99.755859,51.508742,0.0 -92.900391,48.04871,0.0 -92.8125,44.339565,0.0 -95.273437,44.402392,0.0 -97.207031,46.619261,0.0 -100.898437,46.073231,0.0 -102.480469,43.068888,0.0 -110.742187,45.274886,0.0 -109.775391,47.457809,0.0 Roman Roman http://maps.google.com/maps/feeds/features/208825816854482607313/00046fb45f88fa910bcea/00046fb465f5002e56b7a 2009-07-27T18:48:22.194Z 2009-07-27T18:48:22.194Z 2009-07-27T18:48:22.194Z New Mexico New Mexico Word.]]> 1 -110.039062,37.788081,0.0 -103.183594,37.926868,0.0 -103.183594,32.472695,0.0 -108.896484,32.026706,0.0 -109.863281,31.203405,0.0 -110.039062,37.788081,0.0 Roman Roman """ MAP_FEATURE_ENTRY = """ http://maps.google.com/maps/feeds/features/208825816854482607313/00046fb45f88fa910bcea/00046fb4632573b19e0b7 2009-07-27T18:47:35.037Z 2009-07-27T18:47:35.037Z 2009-07-27T18:47:35.037Z Some feature title Some feature title Some feature content]]> -113.818359,41.442726,0.0 Roman Roman """ MAP_FEATURE_KML = """ Some feature title Some feature content]]> -113.818359,41.442726,0.0 """ SITES_LISTPAGE_ENTRY = ''' http:///sites.google.com/feeds/content/site/gdatatestsite/1712987567114738703 2009-06-16T00:37:37.393Z ListPagesTitle
    stuff go here
    asdf
    sdf

    Test User test@gmail.com
    ''' SITES_COMMENT_ENTRY = ''' http://sites.google.com/feeds/content/site/gdatatestsite/abc123 2009-06-15T18:40:22.407Z <content type="xhtml"> <div xmlns="http://www.w3.org/1999/xhtml">first comment</div> </content> <link rel="http://schemas.google.com/sites/2008#parent" type="application/atom+xml" href="http://sites.google.com/feeds/content/site/gdatatestsite/abc123parent"/> <link rel="self" type="application/atom+xml" href="http://sites.google.com/feeds/content/site/gdatatestsite/abc123"/> <link rel="edit" type="application/atom+xml" href="http://sites.google.com/feeds/content/site/gdatatestsite/abc123"/> <author> <name>Test User</name> <email>test@gmail.com</email> </author> <thr:in-reply-to xmlns:thr="http://purl.org/syndication/thread/1.0" href="http://sites.google.com/site/gdatatestsite/annoucment/testpost" ref="http://sites.google.com/feeds/content/site/gdatatestsite/abc123" source="http://sites.google.com/feeds/content/site/gdatatestsite" type="text/html"/> </entry>''' SITES_LISTITEM_ENTRY = '''<?xml version="1.0" encoding="UTF-8"?> <entry xmlns="http://www.w3.org/2005/Atom"> <id>http://sites.google.com/feeds/content/site/gdatatestsite/abc123</id> <updated>2009-06-16T00:34:55.633Z</updated> <category scheme="http://schemas.google.com/g/2005#kind" term="http://schemas.google.com/sites/2008#listitem"/> <title type="text"/> <link rel="http://schemas.google.com/sites/2008#parent" type="application/atom+xml" href="http://sites.google.com/feeds/content/site/gdatatestsite/abc123def"/> <link rel="self" type="application/atom+xml" href="http://sites.google.com/feeds/content/site/gdatatestsite/abc123"/> <link rel="edit" type="application/atom+xml" href="http://sites.google.com/feeds/content/site/gdatatestsite/abc123"/> <author> <name>Test User</name> <email>test@gmail.com</email> </author> <gs:field xmlns:gs="http://schemas.google.com/spreadsheets/2006" index="A" name="Owner">test value</gs:field> <gs:field xmlns:gs="http://schemas.google.com/spreadsheets/2006" index="B" name="Description">test</gs:field> <gs:field xmlns:gs="http://schemas.google.com/spreadsheets/2006" index="C" name="Resolution">90</gs:field> <gs:field xmlns:gs="http://schemas.google.com/spreadsheets/2006" index="D" name="Complete"/> <gs:field xmlns:gs="http://schemas.google.com/spreadsheets/2006" index="E" name="MyCo">2009-05-31</gs:field> </entry>''' SITES_CONTENT_FEED = '''<?xml version="1.0" encoding="UTF-8"?> <feed xmlns="http://www.w3.org/2005/Atom" xmlns:openSearch="http://a9.com/-/spec/opensearch/1.1/" xmlns:sites="http://schemas.google.com/sites/2008" xmlns:gs="http://schemas.google.com/spreadsheets/2006" xmlns:dc="http://purl.org/dc/terms" xmlns:batch="http://schemas.google.com/gdata/batch" xmlns:gd="http://schemas.google.com/g/2005" xmlns:thr="http://purl.org/syndication/thread/1.0"> <id>http://sites.google.com/feeds/content/site/gdatatestsite</id> <updated>2009-06-15T21:35:43.282Z</updated> <link rel="http://schemas.google.com/g/2005#feed" type="application/atom+xml" href="http://sites.google.com/feeds/content/site/gdatatestsite"/> <link rel="http://schemas.google.com/g/2005#post" type="application/atom+xml" href="http://sites.google.com/feeds/content/site/gdatatestsite"/> <link rel="self" type="application/atom+xml" href="http://sites.google.com/feeds/content/site/gdatatestsite"/> <generator version="1" uri="http://sites.google.com">Google Sites</generator> <openSearch:startIndex>1</openSearch:startIndex> <entry> <id>http:///sites.google.com/feeds/content/site/gdatatestsite/1712987567114738703</id> <updated>2009-06-16T00:37:37.393Z</updated> <category scheme="http://schemas.google.com/g/2005#kind" term="http://schemas.google.com/sites/2008#listpage"/> <title type="text">ListPagesTitle
    stuff go here
    asdf
    sdf

    Test User test@gmail.com 2 home
    http://sites.google.com/feeds/content/site/gdatatestsite/abc123 2009-06-17T00:40:37.082Z filecabinet
    sdf
    Test User test@gmail.com
    http://sites.google.com/feeds/content/site/gdatatestsite/abc123 2009-06-16T00:34:55.633Z <link rel="http://schemas.google.com/sites/2008#parent" type="application/atom+xml" href="http://sites.google.com/feeds/content/site/gdatatestsite/abc123def"/> <link rel="self" type="application/atom+xml" href="http://sites.google.com/feeds/content/site/gdatatestsite/abc123"/> <link rel="edit" type="application/atom+xml" href="http://sites.google.com/feeds/content/site/gdatatestsite/abc123"/> <link rel="http://schemas.google.com/sites/2008#revision" type="application/atom+xml" href="http:///sites.google.com/feeds/content/site/gdatatestsite/abc123"/> <author> <name>Test User</name> <email>test@gmail.com</email> </author> <gs:field xmlns:gs="http://schemas.google.com/spreadsheets/2006" index="A" name="Owner">test value</gs:field> <gs:field xmlns:gs="http://schemas.google.com/spreadsheets/2006" index="B" name="Description">test</gs:field> <gs:field xmlns:gs="http://schemas.google.com/spreadsheets/2006" index="C" name="Resolution">90</gs:field> <gs:field xmlns:gs="http://schemas.google.com/spreadsheets/2006" index="D" name="Complete"/> <gs:field xmlns:gs="http://schemas.google.com/spreadsheets/2006" index="E" name="MyCo">2009-05-31</gs:field> </entry> <entry> <id>http://sites.google.com/feeds/content/site/gdatatestsite/abc123</id> <updated>2009-06-15T18:40:32.922Z</updated> <category scheme="http://schemas.google.com/g/2005#kind" term="http://schemas.google.com/sites/2008#attachment"/> <title type="text">testFile.ods Test User test@gmail.com something else http://sites.google.com/feeds/content/site/gdatatestsite/abc123 2009-06-15T18:40:22.407Z <content type="xhtml"> <div xmlns="http://www.w3.org/1999/xhtml">first comment</div> </content> <link rel="http://schemas.google.com/sites/2008#parent" type="application/atom+xml" href="http://sites.google.com/feeds/content/site/gdatatestsite/abc123"/> <link rel="self" type="application/atom+xml" href="http://sites.google.com/feeds/content/site/gdatatestsite/abc123"/> <link rel="edit" type="application/atom+xml" href="http://sites.google.com/feeds/content/site/gdatatestsite/abc123"/> <link rel="http://schemas.google.com/sites/2008#revision" type="application/atom+xml" href="http:///sites.google.com/feeds/content/site/gdatatestsite/abc123"/> <author> <name>Test User</name> <email>test@gmail.com</email> </author> <thr:in-reply-to xmlns:thr="http://purl.org/syndication/thread/1.0" href="http://sites.google.com/site/gdatatestsite/annoucment/testpost" ref="http://sites.google.com/feeds/content/site/gdatatestsite/abc123" source="http://sites.google.com/feeds/content/site/gdatatestsite" type="text/html"/> </entry> <entry> <id>http://sites.google.com/feeds/content/site/gdatatestsite/abc123</id> <updated>2009-06-15T18:40:16.388Z</updated> <category scheme="http://schemas.google.com/g/2005#kind" term="http://schemas.google.com/sites/2008#announcement"/> <title type="text">TestPost
    content goes here
    Test User test@gmail.com
    http://sites.google.com/feeds/content/site/gdatatestsite/abc123 2009-06-12T23:37:59.417Z Home
    Some Content goes here


    Test User test@gmail.com
    http://sites.google.com/feeds/content/site/gdatatestsite/2639323850129333500 2009-06-12T23:32:09.191Z annoucment
    Test User test@gmail.com
    ''' SITES_ACTIVITY_FEED = ''' http://sites.google.com/feeds/activity/site/siteName 2009-08-19T05:46:01.503Z Activity Google Sites 1 http://sites.google.com/feeds/activity/site/siteName/197441951793148343 2009-08-17T00:08:19.387Z NewWebpage3
    User deleted NewWebpage3
    User user@gmail.com
    http://sites.google.com/feeds/activity/site/siteName/7299542210274956360 2009-08-17T00:08:03.711Z NewWebpage3
    User edited NewWebpage3
    User user@gmail.com
    ''' SITES_REVISION_FEED = ''' http://sites.google.com/feeds/revision/site/siteName/2947510322163358574 2009-08-19T06:20:18.151Z Revisions Google Sites 1 http://sites.google.com/feeds/revision/site/siteName/2947510322163358574/1 2009-08-19T04:33:14.856Z <content type="xhtml"> <div xmlns="http://www.w3.org/1999/xhtml"> <table cellspacing="0" class="sites-layout-name-one-column sites-layout-hbox"> <tbody> <tr> <td class="sites-layout-tile sites-tile-name-content-1">testcomment</td> </tr> </tbody> </table> </div> </content> <link rel="http://schemas.google.com/sites/2008#parent" type="application/atom+xml" href="http://sites.google.com/feeds/content/site/siteName/54395424125706119"/> <link rel="alternate" type="text" href="http://sites.google.com/site/system/app/pages/admin/compare?wuid=wuid%3Agx%3A28e7a9057c581b6e&rev1=1"/> <link rel="self" type="application/atom+xml" href="http://sites.google.com/feeds/revision/site/siteName/2947510322163358574/1"/> <author> <name>User</name> <email>user@gmail.com</email> </author> <thr:in-reply-to href="http://sites.google.com/site/siteName/code/js" ref="http://sites.google.com/feeds/content/site/siteName/54395424125706119" source="http://sites.google.com/feeds/content/google.com/siteName" type="text/html;charset=UTF-8"/> <sites:revision>1</sites:revision> </entry> </feed>''' SITES_SITE_FEED = ''' <feed xmlns="http://www.w3.org/2005/Atom" xmlns:openSearch="http://a9.com/-/spec/opensearch/1.1/" xmlns:gAcl="http://schemas.google.com/acl/2007" xmlns:sites="http://schemas.google.com/sites/2008" xmlns:gs="http://schemas.google.com/spreadsheets/2006" xmlns:dc="http://purl.org/dc/terms" xmlns:batch="http://schemas.google.com/gdata/batch" xmlns:gd="http://schemas.google.com/g/2005" xmlns:thr="http://purl.org/syndication/thread/1.0"> <id>https://sites.google.com/feeds/site/example.com</id> <updated>2009-12-09T01:05:54.631Z</updated> <title>Site Google Sites 1 https://sites.google.com/feeds/site/example.com/new-test-site 2009-12-02T22:55:31.040Z 2009-12-02T22:55:31.040Z New Test Site A new site to hold memories new-test-site iceberg https://sites.google.com/feeds/site/example.com/newautosite2 2009-12-05T00:28:01.077Z 2009-12-05T00:28:01.077Z newAutoSite3 A new site to hold memories2 newautosite2 default ''' SITES_ACL_FEED = ''' https://sites.google.comsites.google.com/feeds/acl/site/example.com/new-test-site 2009-12-09T01:24:59.080Z Acl Google Sites 1 https://sites.google.com/feeds/acl/site/google.com/new-test-site/user%3Auser%40example.com 2009-12-09T01:24:59.080Z 2009-12-09T01:24:59.080Z ''' ANALYTICS_ACCOUNT_FEED_old = ''' http://www.google.com/analytics/feeds/accounts/abc@test.com 2009-06-25T03:55:22.000-07:00 Profile list for abc@test.com Google Analytics Google Analytics 12 1 12 http://www.google.com/analytics/feeds/accounts/ga:1174 2009-06-25T03:55:22.000-07:00 www.googlestore.com ga:1174 ''' ANALYTICS_ACCOUNT_FEED = ''' http://www.google.com/analytics/feeds/accounts/api.nickm@google.com 2009-10-14T09:14:25.000-07:00 Profile list for abc@test.com Google Analytics Google Analytics 37 1 37 ga:operatingSystem==iPhone http://www.google.com/analytics/feeds/accounts/ga:1174 2009-10-14T09:14:25.000-07:00 www.googlestore.com ga:1174 ''' ANALYTICS_DATA_FEED = ''' http://www.google.com/analytics/feeds/data?ids=ga:1174&dimensions=ga:medium,ga:source&metrics=ga:bounces,ga:visits&filters=ga:medium%3D%3Dreferral&start-date=2008-10-01&end-date=2008-10-31 2008-10-31T16:59:59.999-07:00 Google Analytics Data for Profile 1174 Google Analytics Google Analytics 6451 1 2 2008-10-01 2008-10-31 ga:operatingSystem==iPhone true ga:1174 www.googlestore.com http://www.google.com/analytics/feeds/data?ids=ga:1174&ga:medium=referral&ga:source=blogger.com&filters=ga:medium%3D%3Dreferral&start-date=2008-10-01&end-date=2008-10-31 2008-10-30T17:00:00.001-07:00 ga:source=blogger.com | ga:medium=referral ''' ANALYTICS_MGMT_PROFILE_FEED = ''' https://www.google.com/analytics/feeds/datasources/ga/accounts/~all/webproperties/~all/profiles 2010-06-14T22:18:48.676Z Google Analytics Profiles for superman@gmail.com Google Analytics Google Analytics 1 1 1000 https://www.google.com/analytics/feeds/datasources/ga/accounts/30481/webproperties/UA-30481-1/profiles/1174 2010-06-09T05:58:15.436-07:00 Google Analytics Profile www.googlestore.com ''' ANALYTICS_MGMT_GOAL_FEED = ''' https://www.google.com/analytics/feeds/datasources/ga/accounts/~all/webproperties/~all/profiles/~all/goals 2010-06-14T22:21:18.485Z Google Analytics Goals for superman@gmail.com Google Analytics Google Analytics 3 1 1000 https://www.google.com/analytics/feeds/datasources/ga/accounts/30481/webproperties/UA-30481-1/profiles/1174/goals/1 2010-02-07T13:12:43.377-08:00 Google Analytics Goal 1 https://www.google.com/analytics/feeds/datasources/ga/accounts/30481/webproperties/UA-30481-1/profiles/1174/goals/2 2010-02-07T13:12:43.376-08:00 Google Analytics Goal 2 ''' ANALYTICS_MGMT_ADV_SEGMENT_FEED = ''' https://www.google.com/analytics/feeds/datasources/ga/segments 2010-06-14T22:22:02.728Z Google Analytics Advanced Segments for superman@gmail.com Google Analytics Google Analytics 2 1 1000 https://www.google.com/analytics/feeds/datasources/ga/segments/gaid::0 2009-10-26T13:00:44.915-07:00 Google Analytics Advanced Segment Sources Form Google ga:source=~^\Qgoogle\E ''' MULTIDOMAIN_USER_ENTRY = """ """ MULTIDOMAIN_USER_FEED = """ https://apps-apis.google.com/a/feeds/user/2.0/example.com 2010-01-26T23:38:13.215Z 1 https://apps-apis.google.com/a/feeds/user/2.0/example.com/admin%40example.com 2010-01-26T23:38:13.210Z https://apps-apis.google.com/a/feeds/user/2.0/example.com/liz%40example.com 2010-01-26T23:38:13.210Z """ MULTIDOMAIN_USER_RENAME_REQUEST = """ """ MULTIDOMAIN_ALIAS_ENTRY = """ https://apps-apis.google.com/a/feeds/alias/2.0/gethelp_example.com/helpdesk%40gethelp_example.com 2008-10-17T15:02:45.646Z """ MULTIDOMAIN_ALIAS_FEED = """ https://apps-apis.google.com/a/feeds/alias/2.0/gethelp_example.com 2010-01-26T23:38:13.215Z 1 https://apps-apis.google.com/a/feeds/alias/2.0/gethelp_example.com/helpdesk%40gethelp_example.com 2010-01-26T23:38:13.210Z https://apps-apis.google.com/a/feeds/alias/2.0/gethelp_example.com/support%40gethelp_example.com 2010-01-26T23:38:13.210Z """ USER_ENTRY1 = """ http://apps-apis.google.com/a/feeds/srkapps.com/user/2.0/abcd12310 1970-01-01T00:00:00.000Z abcd12310 """ USER_FEED1 = """ https://apps-apis.google.com/a/feeds/srkapps.com/user/2.0 1 Users 1970-01-01T00:00:00.000Z https://apps-apis.google.com/a/feeds/srkapps.com/user/2.0/user8306 1970-01-01T00:00:00.000Z user8306 https://apps-apis.google.com/a/feeds/srkapps.com/user/2.0/user8307 1970-01-01T00:00:00.000Z user8307 """ NICKNAME_ENTRY = """ https://apps-apis.google.com/a/feeds/srkapps.net/nickname/2.0/nehag 1970-01-01T00:00:00.000Z nehag """ NICKNAME_FEED = """ https://apps-apis.google.com/a/feeds/srkapps.net/nickname/2.0 1970-01-01T00:00:00.000Z Nicknames 1 https://apps-apis.google.com/a/feeds/srkapps.net/nickname/2.0/nehag 1970-01-01T00:00:00.000Z nehag https://apps-apis.google.com/a/feeds/srkapps.net/nickname/2.0/richag 1970-01-01T00:00:00.000Z richag """ GROUP_ENTRY = """ http://apps-apis.google.com/a/feeds/group/2.0/srkapps.com/trial%40srkapps.com 2011-11-10T16:54:56.784Z """ GROUP_FEED= """ http://apps-apis.google.com/a/feeds/group/2.0/srkapps.com 2011-11-10T16:56:03.830Z 1 http://apps-apis.google.com/a/feeds/group/2.0/srkapps.com/firstgroup%40srkapps.com 2011-11-10T16:56:03.830Z http://apps-apis.google.com/a/feeds/group/2.0/srkapps.com/trial%40srkapps.com 2011-11-10T16:56:03.830Z """ GROUP_MEMBER_ENTRY = """ http://apps-apis.google.com/a/feeds/group/2.0/srkapps.com/trial/member/abcd12310%40srkapps.com 2011-11-10T16:58:40.804Z """ GROUP_MEMBER_FEED = """ http://apps-apis.google.com/a/feeds/group/2.0/srkapps.com/trial/member 2011-11-10T16:57:15.574Z 1 http://apps-apis.google.com/a/feeds/group/2.0/srkapps.com/trial/member/abcd12310%40srkapps.com 2011-11-10T16:57:15.574Z http://apps-apis.google.com/a/feeds/group/2.0/srkapps.com/trial/member/neha.technocrat%40srkapps.com 2011-11-10T16:57:15.574Z """ ORGANIZATION_UNIT_CUSTOMER_ID_ENTRY = """ https://apps-apis.google.com/a/feeds/customer/2.0/C123A456B 2011-11-21T13:17:02.274Z """ ORGANIZATION_UNIT_ORGUNIT_ENTRY = """ https://apps-apis.google.com/a/feeds/orgunit/2.0/C123A456B/Test+Organization 2011-11-21T13:32:12.334Z """ ORGANIZATION_UNIT_ORGUNIT_FEED = """ https://apps-apis.google.com/a/feeds/orgunit/2.0/C123A456B 2011-11-21T13:47:12.551Z 1 https://apps-apis.google.com/a/feeds/orgunit/2.0/C123A456B/testOrgUnit92 2011-11-21T13:42:45.349Z https://apps-apis.google.com/a/feeds/orgunit/2.0/C123A456B/testOrgUnit93 2011-11-21T13:42:45.349Z """ ORGANIZATION_UNIT_ORGUSER_ENTRY = """ https://apps-apis.google.com/a/feeds/orguser/2.0/C123A456B/admin%40example.com 2011-11-21T14:05:17.734Z """ ORGANIZATION_UNIT_ORGUSER_FEED = """ https://apps-apis.google.com/a/feeds/orguser/2.0/C123A456B 2011-11-21T14:10:48.206Z 1 https://apps-apis.google.com/a/feeds/orguser/2.0/C123A456B/user720430%40example.com 2011-11-21T14:09:16.600Z https://apps-apis.google.com/a/feeds/orguser/2.0/C123A456B/user832648%40example.com 2011-11-21T14:09:16.600Z """ gdata-2.0.18/samples/apps/marketplace_sample/gdata/apps_property.py0000640036466300116100000000214112156622362025534 0ustar afshareng00000000000000#!/usr/bin/env python # # 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. # This module is used for version 2 of the Google Data APIs. """Provides a base class to represent property elements in feeds. This module is used for version 2 of the Google Data APIs. The primary class in this module is AppsProperty. """ __author__ = 'Vic Fryzel ' import atom.core import gdata.apps class AppsProperty(atom.core.XmlElement): """Represents an element in a feed.""" _qname = gdata.apps.APPS_TEMPLATE % 'property' name = 'name' value = 'value' gdata-2.0.18/samples/apps/marketplace_sample/gdata/core.py0000640036466300116100000002005712156622362023563 0ustar afshareng00000000000000#!/usr/bin/env python # # 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. # This module is used for version 2 of the Google Data APIs. __author__ = 'j.s@google.com (Jeff Scudder)' """Provides classes and methods for working with JSON-C. This module is experimental and subject to backwards incompatible changes. Jsonc: Class which represents JSON-C data and provides pythonic member access which is a bit cleaner than working with plain old dicts. parse_json: Converts a JSON-C string into a Jsonc object. jsonc_to_string: Converts a Jsonc object into a string of JSON-C. """ try: import simplejson except ImportError: try: # Try to import from django, should work on App Engine from django.utils import simplejson except ImportError: # Should work for Python2.6 and higher. import json as simplejson def _convert_to_jsonc(x): """Builds a Jsonc objects which wraps the argument's members.""" if isinstance(x, dict): jsonc_obj = Jsonc() # Recursively transform all members of the dict. # When converting a dict, we do not convert _name items into private # Jsonc members. for key, value in x.iteritems(): jsonc_obj._dict[key] = _convert_to_jsonc(value) return jsonc_obj elif isinstance(x, list): # Recursively transform all members of the list. members = [] for item in x: members.append(_convert_to_jsonc(item)) return members else: # Return the base object. return x def parse_json(json_string): """Converts a JSON-C string into a Jsonc object. Args: json_string: str or unicode The JSON to be parsed. Returns: A new Jsonc object. """ return _convert_to_jsonc(simplejson.loads(json_string)) def parse_json_file(json_file): return _convert_to_jsonc(simplejson.load(json_file)) def jsonc_to_string(jsonc_obj): """Converts a Jsonc object into a string of JSON-C.""" return simplejson.dumps(_convert_to_object(jsonc_obj)) def prettify_jsonc(jsonc_obj, indentation=2): """Converts a Jsonc object to a pretified (intented) JSON string.""" return simplejson.dumps(_convert_to_object(jsonc_obj), indent=indentation) def _convert_to_object(jsonc_obj): """Creates a new dict or list which has the data in the Jsonc object. Used to convert the Jsonc object to a plain old Python object to simplify conversion to a JSON-C string. Args: jsonc_obj: A Jsonc object to be converted into simple Python objects (dicts, lists, etc.) Returns: Either a dict, list, or other object with members converted from Jsonc objects to the corresponding simple Python object. """ if isinstance(jsonc_obj, Jsonc): plain = {} for key, value in jsonc_obj._dict.iteritems(): plain[key] = _convert_to_object(value) return plain elif isinstance(jsonc_obj, list): plain = [] for item in jsonc_obj: plain.append(_convert_to_object(item)) return plain else: return jsonc_obj def _to_jsonc_name(member_name): """Converts a Python style member name to a JSON-C style name. JSON-C uses camelCaseWithLower while Python tends to use lower_with_underscores so this method converts as follows: spam becomes spam spam_and_eggs becomes spamAndEggs Args: member_name: str or unicode The Python syle name which should be converted to JSON-C style. Returns: The JSON-C style name as a str or unicode. """ characters = [] uppercase_next = False for character in member_name: if character == '_': uppercase_next = True elif uppercase_next: characters.append(character.upper()) uppercase_next = False else: characters.append(character) return ''.join(characters) class Jsonc(object): """Represents JSON-C data in an easy to access object format. To access the members of a JSON structure which looks like this: { "data": { "totalItems": 800, "items": [ { "content": { "1": "rtsp://v5.cache3.c.youtube.com/CiILENy.../0/0/0/video.3gp" }, "viewCount": 220101, "commentCount": 22, "favoriteCount": 201 } ] }, "apiVersion": "2.0" } You would do the following: x = gdata.core.parse_json(the_above_string) # Gives you 800 x.data.total_items # Should be 22 x.data.items[0].comment_count # The apiVersion is '2.0' x.api_version To create a Jsonc object which would produce the above JSON, you would do: gdata.core.Jsonc( api_version='2.0', data=gdata.core.Jsonc( total_items=800, items=[ gdata.core.Jsonc( view_count=220101, comment_count=22, favorite_count=201, content={ '1': ('rtsp://v5.cache3.c.youtube.com' '/CiILENy.../0/0/0/video.3gp')})])) or x = gdata.core.Jsonc() x.api_version = '2.0' x.data = gdata.core.Jsonc() x.data.total_items = 800 x.data.items = [] # etc. How it works: The JSON-C data is stored in an internal dictionary (._dict) and the getattr, setattr, and delattr methods rewrite the name which you provide to mirror the expected format in JSON-C. (For more details on name conversion see _to_jsonc_name.) You may also access members using getitem, setitem, delitem as you would for a dictionary. For example x.data.total_items is equivalent to x['data']['totalItems'] (Not all dict methods are supported so if you need something other than the item operations, then you will want to use the ._dict member). You may need to use getitem or the _dict member to access certain properties in cases where the JSON-C syntax does not map neatly to Python objects. For example the YouTube Video feed has some JSON like this: "content": {"1": "rtsp://v5.cache3.c.youtube.com..."...} You cannot do x.content.1 in Python, so you would use the getitem as follows: x.content['1'] or you could use the _dict member as follows: x.content._dict['1'] If you need to create a new object with such a mapping you could use. x.content = gdata.core.Jsonc(_dict={'1': 'rtsp://cache3.c.youtube.com...'}) """ def __init__(self, _dict=None, **kwargs): json = _dict or {} for key, value in kwargs.iteritems(): if key.startswith('_'): object.__setattr__(self, key, value) else: json[_to_jsonc_name(key)] = _convert_to_jsonc(value) object.__setattr__(self, '_dict', json) def __setattr__(self, name, value): if name.startswith('_'): object.__setattr__(self, name, value) else: object.__getattribute__( self, '_dict')[_to_jsonc_name(name)] = _convert_to_jsonc(value) def __getattr__(self, name): if name.startswith('_'): object.__getattribute__(self, name) else: try: return object.__getattribute__(self, '_dict')[_to_jsonc_name(name)] except KeyError: raise AttributeError( 'No member for %s or [\'%s\']' % (name, _to_jsonc_name(name))) def __delattr__(self, name): if name.startswith('_'): object.__delattr__(self, name) else: try: del object.__getattribute__(self, '_dict')[_to_jsonc_name(name)] except KeyError: raise AttributeError( 'No member for %s (or [\'%s\'])' % (name, _to_jsonc_name(name))) # For container methods pass-through to the underlying dict. def __getitem__(self, key): return self._dict[key] def __setitem__(self, key, value): self._dict[key] = value def __delitem__(self, key): del self._dict[key] gdata-2.0.18/samples/apps/marketplace_sample/gdata/data.py0000640036466300116100000011601312156622362023542 0ustar afshareng00000000000000#!/usr/bin/env python # # Copyright (C) 2009 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. # This module is used for version 2 of the Google Data APIs. """Provides classes and constants for the XML in the Google Data namespace. Documentation for the raw XML which these classes represent can be found here: http://code.google.com/apis/gdata/docs/2.0/elements.html """ __author__ = 'j.s@google.com (Jeff Scudder)' import os import atom.core import atom.data GDATA_TEMPLATE = '{http://schemas.google.com/g/2005}%s' GD_TEMPLATE = GDATA_TEMPLATE OPENSEARCH_TEMPLATE_V1 = '{http://a9.com/-/spec/opensearchrss/1.0/}%s' OPENSEARCH_TEMPLATE_V2 = '{http://a9.com/-/spec/opensearch/1.1/}%s' BATCH_TEMPLATE = '{http://schemas.google.com/gdata/batch}%s' # Labels used in batch request entries to specify the desired CRUD operation. BATCH_INSERT = 'insert' BATCH_UPDATE = 'update' BATCH_DELETE = 'delete' BATCH_QUERY = 'query' EVENT_LOCATION = 'http://schemas.google.com/g/2005#event' ALTERNATE_LOCATION = 'http://schemas.google.com/g/2005#event.alternate' PARKING_LOCATION = 'http://schemas.google.com/g/2005#event.parking' CANCELED_EVENT = 'http://schemas.google.com/g/2005#event.canceled' CONFIRMED_EVENT = 'http://schemas.google.com/g/2005#event.confirmed' TENTATIVE_EVENT = 'http://schemas.google.com/g/2005#event.tentative' CONFIDENTIAL_EVENT = 'http://schemas.google.com/g/2005#event.confidential' DEFAULT_EVENT = 'http://schemas.google.com/g/2005#event.default' PRIVATE_EVENT = 'http://schemas.google.com/g/2005#event.private' PUBLIC_EVENT = 'http://schemas.google.com/g/2005#event.public' OPAQUE_EVENT = 'http://schemas.google.com/g/2005#event.opaque' TRANSPARENT_EVENT = 'http://schemas.google.com/g/2005#event.transparent' CHAT_MESSAGE = 'http://schemas.google.com/g/2005#message.chat' INBOX_MESSAGE = 'http://schemas.google.com/g/2005#message.inbox' SENT_MESSAGE = 'http://schemas.google.com/g/2005#message.sent' SPAM_MESSAGE = 'http://schemas.google.com/g/2005#message.spam' STARRED_MESSAGE = 'http://schemas.google.com/g/2005#message.starred' UNREAD_MESSAGE = 'http://schemas.google.com/g/2005#message.unread' BCC_RECIPIENT = 'http://schemas.google.com/g/2005#message.bcc' CC_RECIPIENT = 'http://schemas.google.com/g/2005#message.cc' SENDER = 'http://schemas.google.com/g/2005#message.from' REPLY_TO = 'http://schemas.google.com/g/2005#message.reply-to' TO_RECIPIENT = 'http://schemas.google.com/g/2005#message.to' ASSISTANT_REL = 'http://schemas.google.com/g/2005#assistant' CALLBACK_REL = 'http://schemas.google.com/g/2005#callback' CAR_REL = 'http://schemas.google.com/g/2005#car' COMPANY_MAIN_REL = 'http://schemas.google.com/g/2005#company_main' FAX_REL = 'http://schemas.google.com/g/2005#fax' HOME_REL = 'http://schemas.google.com/g/2005#home' HOME_FAX_REL = 'http://schemas.google.com/g/2005#home_fax' ISDN_REL = 'http://schemas.google.com/g/2005#isdn' MAIN_REL = 'http://schemas.google.com/g/2005#main' MOBILE_REL = 'http://schemas.google.com/g/2005#mobile' OTHER_REL = 'http://schemas.google.com/g/2005#other' OTHER_FAX_REL = 'http://schemas.google.com/g/2005#other_fax' PAGER_REL = 'http://schemas.google.com/g/2005#pager' RADIO_REL = 'http://schemas.google.com/g/2005#radio' TELEX_REL = 'http://schemas.google.com/g/2005#telex' TTL_TDD_REL = 'http://schemas.google.com/g/2005#tty_tdd' WORK_REL = 'http://schemas.google.com/g/2005#work' WORK_FAX_REL = 'http://schemas.google.com/g/2005#work_fax' WORK_MOBILE_REL = 'http://schemas.google.com/g/2005#work_mobile' WORK_PAGER_REL = 'http://schemas.google.com/g/2005#work_pager' NETMEETING_REL = 'http://schemas.google.com/g/2005#netmeeting' OVERALL_REL = 'http://schemas.google.com/g/2005#overall' PRICE_REL = 'http://schemas.google.com/g/2005#price' QUALITY_REL = 'http://schemas.google.com/g/2005#quality' EVENT_REL = 'http://schemas.google.com/g/2005#event' EVENT_ALTERNATE_REL = 'http://schemas.google.com/g/2005#event.alternate' EVENT_PARKING_REL = 'http://schemas.google.com/g/2005#event.parking' AIM_PROTOCOL = 'http://schemas.google.com/g/2005#AIM' MSN_PROTOCOL = 'http://schemas.google.com/g/2005#MSN' YAHOO_MESSENGER_PROTOCOL = 'http://schemas.google.com/g/2005#YAHOO' SKYPE_PROTOCOL = 'http://schemas.google.com/g/2005#SKYPE' QQ_PROTOCOL = 'http://schemas.google.com/g/2005#QQ' GOOGLE_TALK_PROTOCOL = 'http://schemas.google.com/g/2005#GOOGLE_TALK' ICQ_PROTOCOL = 'http://schemas.google.com/g/2005#ICQ' JABBER_PROTOCOL = 'http://schemas.google.com/g/2005#JABBER' REGULAR_COMMENTS = 'http://schemas.google.com/g/2005#regular' REVIEW_COMMENTS = 'http://schemas.google.com/g/2005#reviews' MAIL_BOTH = 'http://schemas.google.com/g/2005#both' MAIL_LETTERS = 'http://schemas.google.com/g/2005#letters' MAIL_PARCELS = 'http://schemas.google.com/g/2005#parcels' MAIL_NEITHER = 'http://schemas.google.com/g/2005#neither' GENERAL_ADDRESS = 'http://schemas.google.com/g/2005#general' LOCAL_ADDRESS = 'http://schemas.google.com/g/2005#local' OPTIONAL_ATENDEE = 'http://schemas.google.com/g/2005#event.optional' REQUIRED_ATENDEE = 'http://schemas.google.com/g/2005#event.required' ATTENDEE_ACCEPTED = 'http://schemas.google.com/g/2005#event.accepted' ATTENDEE_DECLINED = 'http://schemas.google.com/g/2005#event.declined' ATTENDEE_INVITED = 'http://schemas.google.com/g/2005#event.invited' ATTENDEE_TENTATIVE = 'http://schemas.google.com/g/2005#event.tentative' FULL_PROJECTION = 'full' VALUES_PROJECTION = 'values' BASIC_PROJECTION = 'basic' PRIVATE_VISIBILITY = 'private' PUBLIC_VISIBILITY = 'public' OPAQUE_TRANSPARENCY = 'http://schemas.google.com/g/2005#event.opaque' TRANSPARENT_TRANSPARENCY = 'http://schemas.google.com/g/2005#event.transparent' CONFIDENTIAL_EVENT_VISIBILITY = 'http://schemas.google.com/g/2005#event.confidential' DEFAULT_EVENT_VISIBILITY = 'http://schemas.google.com/g/2005#event.default' PRIVATE_EVENT_VISIBILITY = 'http://schemas.google.com/g/2005#event.private' PUBLIC_EVENT_VISIBILITY = 'http://schemas.google.com/g/2005#event.public' CANCELED_EVENT_STATUS = 'http://schemas.google.com/g/2005#event.canceled' CONFIRMED_EVENT_STATUS = 'http://schemas.google.com/g/2005#event.confirmed' TENTATIVE_EVENT_STATUS = 'http://schemas.google.com/g/2005#event.tentative' ACL_REL = 'http://schemas.google.com/acl/2007#accessControlList' class Error(Exception): pass class MissingRequiredParameters(Error): pass class LinkFinder(atom.data.LinkFinder): """Mixin used in Feed and Entry classes to simplify link lookups by type. Provides lookup methods for edit, edit-media, post, ACL and other special links which are common across Google Data APIs. """ def find_html_link(self): """Finds the first link with rel of alternate and type of text/html.""" for link in self.link: if link.rel == 'alternate' and link.type == 'text/html': return link.href return None FindHtmlLink = find_html_link def get_html_link(self): for a_link in self.link: if a_link.rel == 'alternate' and a_link.type == 'text/html': return a_link return None GetHtmlLink = get_html_link def find_post_link(self): """Get the URL to which new entries should be POSTed. The POST target URL is used to insert new entries. Returns: A str for the URL in the link with a rel matching the POST type. """ return self.find_url('http://schemas.google.com/g/2005#post') FindPostLink = find_post_link def get_post_link(self): return self.get_link('http://schemas.google.com/g/2005#post') GetPostLink = get_post_link def find_acl_link(self): acl_link = self.get_acl_link() if acl_link: return acl_link.href return None FindAclLink = find_acl_link def get_acl_link(self): """Searches for a link or feed_link (if present) with the rel for ACL.""" acl_link = self.get_link(ACL_REL) if acl_link: return acl_link elif hasattr(self, 'feed_link'): for a_feed_link in self.feed_link: if a_feed_link.rel == ACL_REL: return a_feed_link return None GetAclLink = get_acl_link def find_feed_link(self): return self.find_url('http://schemas.google.com/g/2005#feed') FindFeedLink = find_feed_link def get_feed_link(self): return self.get_link('http://schemas.google.com/g/2005#feed') GetFeedLink = get_feed_link def find_previous_link(self): return self.find_url('previous') FindPreviousLink = find_previous_link def get_previous_link(self): return self.get_link('previous') GetPreviousLink = get_previous_link class TotalResults(atom.core.XmlElement): """opensearch:TotalResults for a GData feed.""" _qname = (OPENSEARCH_TEMPLATE_V1 % 'totalResults', OPENSEARCH_TEMPLATE_V2 % 'totalResults') class StartIndex(atom.core.XmlElement): """The opensearch:startIndex element in GData feed.""" _qname = (OPENSEARCH_TEMPLATE_V1 % 'startIndex', OPENSEARCH_TEMPLATE_V2 % 'startIndex') class ItemsPerPage(atom.core.XmlElement): """The opensearch:itemsPerPage element in GData feed.""" _qname = (OPENSEARCH_TEMPLATE_V1 % 'itemsPerPage', OPENSEARCH_TEMPLATE_V2 % 'itemsPerPage') class ExtendedProperty(atom.core.XmlElement): """The Google Data extendedProperty element. Used to store arbitrary key-value information specific to your application. The value can either be a text string stored as an XML attribute (.value), or an XML node (XmlBlob) as a child element. This element is used in the Google Calendar data API and the Google Contacts data API. """ _qname = GDATA_TEMPLATE % 'extendedProperty' name = 'name' value = 'value' def get_xml_blob(self): """Returns the XML blob as an atom.core.XmlElement. Returns: An XmlElement representing the blob's XML, or None if no blob was set. """ if self._other_elements: return self._other_elements[0] else: return None GetXmlBlob = get_xml_blob def set_xml_blob(self, blob): """Sets the contents of the extendedProperty to XML as a child node. Since the extendedProperty is only allowed one child element as an XML blob, setting the XML blob will erase any preexisting member elements in this object. Args: blob: str or atom.core.XmlElement representing the XML blob stored in the extendedProperty. """ # Erase any existing extension_elements, clears the child nodes from the # extendedProperty. if isinstance(blob, atom.core.XmlElement): self._other_elements = [blob] else: self._other_elements = [atom.core.parse(str(blob))] SetXmlBlob = set_xml_blob class GDEntry(atom.data.Entry, LinkFinder): """Extends Atom Entry to provide data processing""" etag = '{http://schemas.google.com/g/2005}etag' def get_id(self): if self.id is not None and self.id.text is not None: return self.id.text.strip() return None GetId = get_id def is_media(self): if self.find_edit_media_link(): return True return False IsMedia = is_media def find_media_link(self): """Returns the URL to the media content, if the entry is a media entry. Otherwise returns None. """ if self.is_media(): return self.content.src return None FindMediaLink = find_media_link class GDFeed(atom.data.Feed, LinkFinder): """A Feed from a GData service.""" etag = '{http://schemas.google.com/g/2005}etag' total_results = TotalResults start_index = StartIndex items_per_page = ItemsPerPage entry = [GDEntry] def get_id(self): if self.id is not None and self.id.text is not None: return self.id.text.strip() return None GetId = get_id def get_generator(self): if self.generator and self.generator.text: return self.generator.text.strip() return None class BatchId(atom.core.XmlElement): """Identifies a single operation in a batch request.""" _qname = BATCH_TEMPLATE % 'id' class BatchOperation(atom.core.XmlElement): """The CRUD operation which this batch entry represents.""" _qname = BATCH_TEMPLATE % 'operation' type = 'type' class BatchStatus(atom.core.XmlElement): """The batch:status element present in a batch response entry. A status element contains the code (HTTP response code) and reason as elements. In a single request these fields would be part of the HTTP response, but in a batch request each Entry operation has a corresponding Entry in the response feed which includes status information. See http://code.google.com/apis/gdata/batch.html#Handling_Errors """ _qname = BATCH_TEMPLATE % 'status' code = 'code' reason = 'reason' content_type = 'content-type' class BatchEntry(GDEntry): """An atom:entry for use in batch requests. The BatchEntry contains additional members to specify the operation to be performed on this entry and a batch ID so that the server can reference individual operations in the response feed. For more information, see: http://code.google.com/apis/gdata/batch.html """ batch_operation = BatchOperation batch_id = BatchId batch_status = BatchStatus class BatchInterrupted(atom.core.XmlElement): """The batch:interrupted element sent if batch request was interrupted. Only appears in a feed if some of the batch entries could not be processed. See: http://code.google.com/apis/gdata/batch.html#Handling_Errors """ _qname = BATCH_TEMPLATE % 'interrupted' reason = 'reason' success = 'success' failures = 'failures' parsed = 'parsed' class BatchFeed(GDFeed): """A feed containing a list of batch request entries.""" interrupted = BatchInterrupted entry = [BatchEntry] def add_batch_entry(self, entry=None, id_url_string=None, batch_id_string=None, operation_string=None): """Logic for populating members of a BatchEntry and adding to the feed. If the entry is not a BatchEntry, it is converted to a BatchEntry so that the batch specific members will be present. The id_url_string can be used in place of an entry if the batch operation applies to a URL. For example query and delete operations require just the URL of an entry, no body is sent in the HTTP request. If an id_url_string is sent instead of an entry, a BatchEntry is created and added to the feed. This method also assigns the desired batch id to the entry so that it can be referenced in the server's response. If the batch_id_string is None, this method will assign a batch_id to be the index at which this entry will be in the feed's entry list. Args: entry: BatchEntry, atom.data.Entry, or another Entry flavor (optional) The entry which will be sent to the server as part of the batch request. The item must have a valid atom id so that the server knows which entry this request references. id_url_string: str (optional) The URL of the entry to be acted on. You can find this URL in the text member of the atom id for an entry. If an entry is not sent, this id will be used to construct a new BatchEntry which will be added to the request feed. batch_id_string: str (optional) The batch ID to be used to reference this batch operation in the results feed. If this parameter is None, the current length of the feed's entry array will be used as a count. Note that batch_ids should either always be specified or never, mixing could potentially result in duplicate batch ids. operation_string: str (optional) The desired batch operation which will set the batch_operation.type member of the entry. Options are 'insert', 'update', 'delete', and 'query' Raises: MissingRequiredParameters: Raised if neither an id_ url_string nor an entry are provided in the request. Returns: The added entry. """ if entry is None and id_url_string is None: raise MissingRequiredParameters('supply either an entry or URL string') if entry is None and id_url_string is not None: entry = BatchEntry(id=atom.data.Id(text=id_url_string)) if batch_id_string is not None: entry.batch_id = BatchId(text=batch_id_string) elif entry.batch_id is None or entry.batch_id.text is None: entry.batch_id = BatchId(text=str(len(self.entry))) if operation_string is not None: entry.batch_operation = BatchOperation(type=operation_string) self.entry.append(entry) return entry AddBatchEntry = add_batch_entry def add_insert(self, entry, batch_id_string=None): """Add an insert request to the operations in this batch request feed. If the entry doesn't yet have an operation or a batch id, these will be set to the insert operation and a batch_id specified as a parameter. Args: entry: BatchEntry The entry which will be sent in the batch feed as an insert request. batch_id_string: str (optional) The batch ID to be used to reference this batch operation in the results feed. If this parameter is None, the current length of the feed's entry array will be used as a count. Note that batch_ids should either always be specified or never, mixing could potentially result in duplicate batch ids. """ self.add_batch_entry(entry=entry, batch_id_string=batch_id_string, operation_string=BATCH_INSERT) AddInsert = add_insert def add_update(self, entry, batch_id_string=None): """Add an update request to the list of batch operations in this feed. Sets the operation type of the entry to insert if it is not already set and assigns the desired batch id to the entry so that it can be referenced in the server's response. Args: entry: BatchEntry The entry which will be sent to the server as an update (HTTP PUT) request. The item must have a valid atom id so that the server knows which entry to replace. batch_id_string: str (optional) The batch ID to be used to reference this batch operation in the results feed. If this parameter is None, the current length of the feed's entry array will be used as a count. See also comments for AddInsert. """ self.add_batch_entry(entry=entry, batch_id_string=batch_id_string, operation_string=BATCH_UPDATE) AddUpdate = add_update def add_delete(self, url_string=None, entry=None, batch_id_string=None): """Adds a delete request to the batch request feed. This method takes either the url_string which is the atom id of the item to be deleted, or the entry itself. The atom id of the entry must be present so that the server knows which entry should be deleted. Args: url_string: str (optional) The URL of the entry to be deleted. You can find this URL in the text member of the atom id for an entry. entry: BatchEntry (optional) The entry to be deleted. batch_id_string: str (optional) Raises: MissingRequiredParameters: Raised if neither a url_string nor an entry are provided in the request. """ self.add_batch_entry(entry=entry, id_url_string=url_string, batch_id_string=batch_id_string, operation_string=BATCH_DELETE) AddDelete = add_delete def add_query(self, url_string=None, entry=None, batch_id_string=None): """Adds a query request to the batch request feed. This method takes either the url_string which is the query URL whose results will be added to the result feed. The query URL will be encapsulated in a BatchEntry, and you may pass in the BatchEntry with a query URL instead of sending a url_string. Args: url_string: str (optional) entry: BatchEntry (optional) batch_id_string: str (optional) Raises: MissingRequiredParameters """ self.add_batch_entry(entry=entry, id_url_string=url_string, batch_id_string=batch_id_string, operation_string=BATCH_QUERY) AddQuery = add_query def find_batch_link(self): return self.find_url('http://schemas.google.com/g/2005#batch') FindBatchLink = find_batch_link class EntryLink(atom.core.XmlElement): """The gd:entryLink element. Represents a logically nested entry. For example, a representing a contact might have a nested entry from a contact feed. """ _qname = GDATA_TEMPLATE % 'entryLink' entry = GDEntry rel = 'rel' read_only = 'readOnly' href = 'href' class FeedLink(atom.core.XmlElement): """The gd:feedLink element. Represents a logically nested feed. For example, a calendar feed might have a nested feed representing all comments on entries. """ _qname = GDATA_TEMPLATE % 'feedLink' feed = GDFeed rel = 'rel' read_only = 'readOnly' count_hint = 'countHint' href = 'href' class AdditionalName(atom.core.XmlElement): """The gd:additionalName element. Specifies additional (eg. middle) name of the person. Contains an attribute for the phonetic representaton of the name. """ _qname = GDATA_TEMPLATE % 'additionalName' yomi = 'yomi' class Comments(atom.core.XmlElement): """The gd:comments element. Contains a comments feed for the enclosing entry (such as a calendar event). """ _qname = GDATA_TEMPLATE % 'comments' rel = 'rel' feed_link = FeedLink class Country(atom.core.XmlElement): """The gd:country element. Country name along with optional country code. The country code is given in accordance with ISO 3166-1 alpha-2: http://www.iso.org/iso/iso-3166-1_decoding_table """ _qname = GDATA_TEMPLATE % 'country' code = 'code' class EmailImParent(atom.core.XmlElement): address = 'address' label = 'label' rel = 'rel' primary = 'primary' class Email(EmailImParent): """The gd:email element. An email address associated with the containing entity (which is usually an entity representing a person or a location). """ _qname = GDATA_TEMPLATE % 'email' display_name = 'displayName' class FamilyName(atom.core.XmlElement): """The gd:familyName element. Specifies family name of the person, eg. "Smith". """ _qname = GDATA_TEMPLATE % 'familyName' yomi = 'yomi' class Im(EmailImParent): """The gd:im element. An instant messaging address associated with the containing entity. """ _qname = GDATA_TEMPLATE % 'im' protocol = 'protocol' class GivenName(atom.core.XmlElement): """The gd:givenName element. Specifies given name of the person, eg. "John". """ _qname = GDATA_TEMPLATE % 'givenName' yomi = 'yomi' class NamePrefix(atom.core.XmlElement): """The gd:namePrefix element. Honorific prefix, eg. 'Mr' or 'Mrs'. """ _qname = GDATA_TEMPLATE % 'namePrefix' class NameSuffix(atom.core.XmlElement): """The gd:nameSuffix element. Honorific suffix, eg. 'san' or 'III'. """ _qname = GDATA_TEMPLATE % 'nameSuffix' class FullName(atom.core.XmlElement): """The gd:fullName element. Unstructured representation of the name. """ _qname = GDATA_TEMPLATE % 'fullName' class Name(atom.core.XmlElement): """The gd:name element. Allows storing person's name in a structured way. Consists of given name, additional name, family name, prefix, suffix and full name. """ _qname = GDATA_TEMPLATE % 'name' given_name = GivenName additional_name = AdditionalName family_name = FamilyName name_prefix = NamePrefix name_suffix = NameSuffix full_name = FullName class OrgDepartment(atom.core.XmlElement): """The gd:orgDepartment element. Describes a department within an organization. Must appear within a gd:organization element. """ _qname = GDATA_TEMPLATE % 'orgDepartment' class OrgJobDescription(atom.core.XmlElement): """The gd:orgJobDescription element. Describes a job within an organization. Must appear within a gd:organization element. """ _qname = GDATA_TEMPLATE % 'orgJobDescription' class OrgName(atom.core.XmlElement): """The gd:orgName element. The name of the organization. Must appear within a gd:organization element. Contains a Yomigana attribute (Japanese reading aid) for the organization name. """ _qname = GDATA_TEMPLATE % 'orgName' yomi = 'yomi' class OrgSymbol(atom.core.XmlElement): """The gd:orgSymbol element. Provides a symbol of an organization. Must appear within a gd:organization element. """ _qname = GDATA_TEMPLATE % 'orgSymbol' class OrgTitle(atom.core.XmlElement): """The gd:orgTitle element. The title of a person within an organization. Must appear within a gd:organization element. """ _qname = GDATA_TEMPLATE % 'orgTitle' class Organization(atom.core.XmlElement): """The gd:organization element. An organization, typically associated with a contact. """ _qname = GDATA_TEMPLATE % 'organization' label = 'label' primary = 'primary' rel = 'rel' department = OrgDepartment job_description = OrgJobDescription name = OrgName symbol = OrgSymbol title = OrgTitle class When(atom.core.XmlElement): """The gd:when element. Represents a period of time or an instant. """ _qname = GDATA_TEMPLATE % 'when' end = 'endTime' start = 'startTime' value = 'valueString' class OriginalEvent(atom.core.XmlElement): """The gd:originalEvent element. Equivalent to the Recurrence ID property specified in section 4.8.4.4 of RFC 2445. Appears in every instance of a recurring event, to identify the original event. Contains a element specifying the original start time of the instance that has become an exception. """ _qname = GDATA_TEMPLATE % 'originalEvent' id = 'id' href = 'href' when = When class PhoneNumber(atom.core.XmlElement): """The gd:phoneNumber element. A phone number associated with the containing entity (which is usually an entity representing a person or a location). """ _qname = GDATA_TEMPLATE % 'phoneNumber' label = 'label' rel = 'rel' uri = 'uri' primary = 'primary' class PostalAddress(atom.core.XmlElement): """The gd:postalAddress element.""" _qname = GDATA_TEMPLATE % 'postalAddress' label = 'label' rel = 'rel' uri = 'uri' primary = 'primary' class Rating(atom.core.XmlElement): """The gd:rating element. Represents a numeric rating of the enclosing entity, such as a comment. Each rating supplies its own scale, although it may be normalized by a service; for example, some services might convert all ratings to a scale from 1 to 5. """ _qname = GDATA_TEMPLATE % 'rating' average = 'average' max = 'max' min = 'min' num_raters = 'numRaters' rel = 'rel' value = 'value' class Recurrence(atom.core.XmlElement): """The gd:recurrence element. Represents the dates and times when a recurring event takes place. The string that defines the recurrence consists of a set of properties, each of which is defined in the iCalendar standard (RFC 2445). Specifically, the string usually begins with a DTSTART property that indicates the starting time of the first instance of the event, and often a DTEND property or a DURATION property to indicate when the first instance ends. Next come RRULE, RDATE, EXRULE, and/or EXDATE properties, which collectively define a recurring event and its exceptions (but see below). (See section 4.8.5 of RFC 2445 for more information about these recurrence component properties.) Last comes a VTIMEZONE component, providing detailed timezone rules for any timezone ID mentioned in the preceding properties. Google services like Google Calendar don't generally generate EXRULE and EXDATE properties to represent exceptions to recurring events; instead, they generate elements. However, Google services may include EXRULE and/or EXDATE properties anyway; for example, users can import events and exceptions into Calendar, and if those imported events contain EXRULE or EXDATE properties, then Calendar will provide those properties when it sends a element. Note the the use of means that you can't be sure just from examining a element whether there are any exceptions to the recurrence description. To ensure that you find all exceptions, look for elements in the feed, and use their elements to match them up with elements. """ _qname = GDATA_TEMPLATE % 'recurrence' class RecurrenceException(atom.core.XmlElement): """The gd:recurrenceException element. Represents an event that's an exception to a recurring event-that is, an instance of a recurring event in which one or more aspects of the recurring event (such as attendance list, time, or location) have been changed. Contains a element that specifies the original recurring event that this event is an exception to. When you change an instance of a recurring event, that instance becomes an exception. Depending on what change you made to it, the exception behaves in either of two different ways when the original recurring event is changed: - If you add, change, or remove comments, attendees, or attendee responses, then the exception remains tied to the original event, and changes to the original event also change the exception. - If you make any other changes to the exception (such as changing the time or location) then the instance becomes "specialized," which means that it's no longer as tightly tied to the original event. If you change the original event, specialized exceptions don't change. But see below. For example, say you have a meeting every Tuesday and Thursday at 2:00 p.m. If you change the attendance list for this Thursday's meeting (but not for the regularly scheduled meeting), then it becomes an exception. If you change the time for this Thursday's meeting (but not for the regularly scheduled meeting), then it becomes specialized. Regardless of whether an exception is specialized or not, if you do something that deletes the instance that the exception was derived from, then the exception is deleted. Note that changing the day or time of a recurring event deletes all instances, and creates new ones. For example, after you've specialized this Thursday's meeting, say you change the recurring meeting to happen on Monday, Wednesday, and Friday. That change deletes all of the recurring instances of the Tuesday/Thursday meeting, including the specialized one. If a particular instance of a recurring event is deleted, then that instance appears as a containing a that has its set to "http://schemas.google.com/g/2005#event.canceled". (For more information about canceled events, see RFC 2445.) """ _qname = GDATA_TEMPLATE % 'recurrenceException' specialized = 'specialized' entry_link = EntryLink original_event = OriginalEvent class Reminder(atom.core.XmlElement): """The gd:reminder element. A time interval, indicating how long before the containing entity's start time or due time attribute a reminder should be issued. Alternatively, may specify an absolute time at which a reminder should be issued. Also specifies a notification method, indicating what medium the system should use to remind the user. """ _qname = GDATA_TEMPLATE % 'reminder' absolute_time = 'absoluteTime' method = 'method' days = 'days' hours = 'hours' minutes = 'minutes' class Transparency(atom.core.XmlElement): """The gd:transparency element: Extensible enum corresponding to the TRANSP property defined in RFC 244. """ _qname = GDATA_TEMPLATE % 'transparency' value = 'value' class Agent(atom.core.XmlElement): """The gd:agent element. The agent who actually receives the mail. Used in work addresses. Also for 'in care of' or 'c/o'. """ _qname = GDATA_TEMPLATE % 'agent' class HouseName(atom.core.XmlElement): """The gd:housename element. Used in places where houses or buildings have names (and not necessarily numbers), eg. "The Pillars". """ _qname = GDATA_TEMPLATE % 'housename' class Street(atom.core.XmlElement): """The gd:street element. Can be street, avenue, road, etc. This element also includes the house number and room/apartment/flat/floor number. """ _qname = GDATA_TEMPLATE % 'street' class PoBox(atom.core.XmlElement): """The gd:pobox element. Covers actual P.O. boxes, drawers, locked bags, etc. This is usually but not always mutually exclusive with street. """ _qname = GDATA_TEMPLATE % 'pobox' class Neighborhood(atom.core.XmlElement): """The gd:neighborhood element. This is used to disambiguate a street address when a city contains more than one street with the same name, or to specify a small place whose mail is routed through a larger postal town. In China it could be a county or a minor city. """ _qname = GDATA_TEMPLATE % 'neighborhood' class City(atom.core.XmlElement): """The gd:city element. Can be city, village, town, borough, etc. This is the postal town and not necessarily the place of residence or place of business. """ _qname = GDATA_TEMPLATE % 'city' class Subregion(atom.core.XmlElement): """The gd:subregion element. Handles administrative districts such as U.S. or U.K. counties that are not used for mail addressing purposes. Subregion is not intended for delivery addresses. """ _qname = GDATA_TEMPLATE % 'subregion' class Region(atom.core.XmlElement): """The gd:region element. A state, province, county (in Ireland), Land (in Germany), departement (in France), etc. """ _qname = GDATA_TEMPLATE % 'region' class Postcode(atom.core.XmlElement): """The gd:postcode element. Postal code. Usually country-wide, but sometimes specific to the city (e.g. "2" in "Dublin 2, Ireland" addresses). """ _qname = GDATA_TEMPLATE % 'postcode' class Country(atom.core.XmlElement): """The gd:country element. The name or code of the country. """ _qname = GDATA_TEMPLATE % 'country' class FormattedAddress(atom.core.XmlElement): """The gd:formattedAddress element. The full, unstructured postal address. """ _qname = GDATA_TEMPLATE % 'formattedAddress' class StructuredPostalAddress(atom.core.XmlElement): """The gd:structuredPostalAddress element. Postal address split into components. It allows to store the address in locale independent format. The fields can be interpreted and used to generate formatted, locale dependent address. The following elements reperesent parts of the address: agent, house name, street, P.O. box, neighborhood, city, subregion, region, postal code, country. The subregion element is not used for postal addresses, it is provided for extended uses of addresses only. In order to store postal address in an unstructured form formatted address field is provided. """ _qname = GDATA_TEMPLATE % 'structuredPostalAddress' rel = 'rel' mail_class = 'mailClass' usage = 'usage' label = 'label' primary = 'primary' agent = Agent house_name = HouseName street = Street po_box = PoBox neighborhood = Neighborhood city = City subregion = Subregion region = Region postcode = Postcode country = Country formatted_address = FormattedAddress class Where(atom.core.XmlElement): """The gd:where element. A place (such as an event location) associated with the containing entity. The type of the association is determined by the rel attribute; the details of the location are contained in an embedded or linked-to Contact entry. A element is more general than a element. The former identifies a place using a text description and/or a Contact entry, while the latter identifies a place using a specific geographic location. """ _qname = GDATA_TEMPLATE % 'where' label = 'label' rel = 'rel' value = 'valueString' entry_link = EntryLink class AttendeeType(atom.core.XmlElement): """The gd:attendeeType element.""" _qname = GDATA_TEMPLATE % 'attendeeType' value = 'value' class AttendeeStatus(atom.core.XmlElement): """The gd:attendeeStatus element.""" _qname = GDATA_TEMPLATE % 'attendeeStatus' value = 'value' class EventStatus(atom.core.XmlElement): """The gd:eventStatus element.""" _qname = GDATA_TEMPLATE % 'eventStatus' value = 'value' class Visibility(atom.core.XmlElement): """The gd:visibility element.""" _qname = GDATA_TEMPLATE % 'visibility' value = 'value' class Who(atom.core.XmlElement): """The gd:who element. A person associated with the containing entity. The type of the association is determined by the rel attribute; the details about the person are contained in an embedded or linked-to Contact entry. The element can be used to specify email senders and recipients, calendar event organizers, and so on. """ _qname = GDATA_TEMPLATE % 'who' email = 'email' rel = 'rel' value = 'valueString' attendee_status = AttendeeStatus attendee_type = AttendeeType entry_link = EntryLink class Deleted(atom.core.XmlElement): """gd:deleted when present, indicates the containing entry is deleted.""" _qname = GD_TEMPLATE % 'deleted' class Money(atom.core.XmlElement): """Describes money""" _qname = GD_TEMPLATE % 'money' amount = 'amount' currency_code = 'currencyCode' class MediaSource(object): """GData Entries can refer to media sources, so this class provides a place to store references to these objects along with some metadata. """ def __init__(self, file_handle=None, content_type=None, content_length=None, file_path=None, file_name=None): """Creates an object of type MediaSource. Args: file_handle: A file handle pointing to the file to be encapsulated in the MediaSource. content_type: string The MIME type of the file. Required if a file_handle is given. content_length: int The size of the file. Required if a file_handle is given. file_path: string (optional) A full path name to the file. Used in place of a file_handle. file_name: string The name of the file without any path information. Required if a file_handle is given. """ self.file_handle = file_handle self.content_type = content_type self.content_length = content_length self.file_name = file_name if (file_handle is None and content_type is not None and file_path is not None): self.set_file_handle(file_path, content_type) def set_file_handle(self, file_name, content_type): """A helper function which can create a file handle from a given filename and set the content type and length all at once. Args: file_name: string The path and file name to the file containing the media content_type: string A MIME type representing the type of the media """ self.file_handle = open(file_name, 'rb') self.content_type = content_type self.content_length = os.path.getsize(file_name) self.file_name = os.path.basename(file_name) SetFileHandle = set_file_handle def modify_request(self, http_request): http_request.add_body_part(self.file_handle, self.content_type, self.content_length) return http_request ModifyRequest = modify_request gdata-2.0.18/samples/apps/marketplace_sample/gdata/__init__.py0000750036466300116100000007225112156622362024377 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright (C) 2006 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. """Contains classes representing Google Data elements. Extends Atom classes to add Google Data specific elements. """ __author__ = 'j.s@google.com (Jeffrey Scudder)' import os import atom try: from xml.etree import cElementTree as ElementTree except ImportError: try: import cElementTree as ElementTree except ImportError: try: from xml.etree import ElementTree except ImportError: from elementtree import ElementTree # XML namespaces which are often used in GData entities. GDATA_NAMESPACE = 'http://schemas.google.com/g/2005' GDATA_TEMPLATE = '{http://schemas.google.com/g/2005}%s' OPENSEARCH_NAMESPACE = 'http://a9.com/-/spec/opensearchrss/1.0/' OPENSEARCH_TEMPLATE = '{http://a9.com/-/spec/opensearchrss/1.0/}%s' BATCH_NAMESPACE = 'http://schemas.google.com/gdata/batch' GACL_NAMESPACE = 'http://schemas.google.com/acl/2007' GACL_TEMPLATE = '{http://schemas.google.com/acl/2007}%s' # Labels used in batch request entries to specify the desired CRUD operation. BATCH_INSERT = 'insert' BATCH_UPDATE = 'update' BATCH_DELETE = 'delete' BATCH_QUERY = 'query' class Error(Exception): pass class MissingRequiredParameters(Error): pass class MediaSource(object): """GData Entries can refer to media sources, so this class provides a place to store references to these objects along with some metadata. """ def __init__(self, file_handle=None, content_type=None, content_length=None, file_path=None, file_name=None): """Creates an object of type MediaSource. Args: file_handle: A file handle pointing to the file to be encapsulated in the MediaSource content_type: string The MIME type of the file. Required if a file_handle is given. content_length: int The size of the file. Required if a file_handle is given. file_path: string (optional) A full path name to the file. Used in place of a file_handle. file_name: string The name of the file without any path information. Required if a file_handle is given. """ self.file_handle = file_handle self.content_type = content_type self.content_length = content_length self.file_name = file_name if (file_handle is None and content_type is not None and file_path is not None): self.setFile(file_path, content_type) def setFile(self, file_name, content_type): """A helper function which can create a file handle from a given filename and set the content type and length all at once. Args: file_name: string The path and file name to the file containing the media content_type: string A MIME type representing the type of the media """ self.file_handle = open(file_name, 'rb') self.content_type = content_type self.content_length = os.path.getsize(file_name) self.file_name = os.path.basename(file_name) class LinkFinder(atom.LinkFinder): """An "interface" providing methods to find link elements GData Entry elements often contain multiple links which differ in the rel attribute or content type. Often, developers are interested in a specific type of link so this class provides methods to find specific classes of links. This class is used as a mixin in GData entries. """ def GetSelfLink(self): """Find the first link with rel set to 'self' Returns: An atom.Link or none if none of the links had rel equal to 'self' """ for a_link in self.link: if a_link.rel == 'self': return a_link return None def GetEditLink(self): for a_link in self.link: if a_link.rel == 'edit': return a_link return None def GetEditMediaLink(self): """The Picasa API mistakenly returns media-edit rather than edit-media, but this may change soon. """ for a_link in self.link: if a_link.rel == 'edit-media': return a_link if a_link.rel == 'media-edit': return a_link return None def GetHtmlLink(self): """Find the first link with rel of alternate and type of text/html Returns: An atom.Link or None if no links matched """ for a_link in self.link: if a_link.rel == 'alternate' and a_link.type == 'text/html': return a_link return None def GetPostLink(self): """Get a link containing the POST target URL. The POST target URL is used to insert new entries. Returns: A link object with a rel matching the POST type. """ for a_link in self.link: if a_link.rel == 'http://schemas.google.com/g/2005#post': return a_link return None def GetAclLink(self): for a_link in self.link: if a_link.rel == 'http://schemas.google.com/acl/2007#accessControlList': return a_link return None def GetFeedLink(self): for a_link in self.link: if a_link.rel == 'http://schemas.google.com/g/2005#feed': return a_link return None def GetNextLink(self): for a_link in self.link: if a_link.rel == 'next': return a_link return None def GetPrevLink(self): for a_link in self.link: if a_link.rel == 'previous': return a_link return None class TotalResults(atom.AtomBase): """opensearch:TotalResults for a GData feed""" _tag = 'totalResults' _namespace = OPENSEARCH_NAMESPACE _children = atom.AtomBase._children.copy() _attributes = atom.AtomBase._attributes.copy() def __init__(self, extension_elements=None, extension_attributes=None, text=None): self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} def TotalResultsFromString(xml_string): return atom.CreateClassFromXMLString(TotalResults, xml_string) class StartIndex(atom.AtomBase): """The opensearch:startIndex element in GData feed""" _tag = 'startIndex' _namespace = OPENSEARCH_NAMESPACE _children = atom.AtomBase._children.copy() _attributes = atom.AtomBase._attributes.copy() def __init__(self, extension_elements=None, extension_attributes=None, text=None): self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} def StartIndexFromString(xml_string): return atom.CreateClassFromXMLString(StartIndex, xml_string) class ItemsPerPage(atom.AtomBase): """The opensearch:itemsPerPage element in GData feed""" _tag = 'itemsPerPage' _namespace = OPENSEARCH_NAMESPACE _children = atom.AtomBase._children.copy() _attributes = atom.AtomBase._attributes.copy() def __init__(self, extension_elements=None, extension_attributes=None, text=None): self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} def ItemsPerPageFromString(xml_string): return atom.CreateClassFromXMLString(ItemsPerPage, xml_string) class ExtendedProperty(atom.AtomBase): """The Google Data extendedProperty element. Used to store arbitrary key-value information specific to your application. The value can either be a text string stored as an XML attribute (.value), or an XML node (XmlBlob) as a child element. This element is used in the Google Calendar data API and the Google Contacts data API. """ _tag = 'extendedProperty' _namespace = GDATA_NAMESPACE _children = atom.AtomBase._children.copy() _attributes = atom.AtomBase._attributes.copy() _attributes['name'] = 'name' _attributes['value'] = 'value' def __init__(self, name=None, value=None, extension_elements=None, extension_attributes=None, text=None): self.name = name self.value = value self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} def GetXmlBlobExtensionElement(self): """Returns the XML blob as an atom.ExtensionElement. Returns: An atom.ExtensionElement representing the blob's XML, or None if no blob was set. """ if len(self.extension_elements) < 1: return None else: return self.extension_elements[0] def GetXmlBlobString(self): """Returns the XML blob as a string. Returns: A string containing the blob's XML, or None if no blob was set. """ blob = self.GetXmlBlobExtensionElement() if blob: return blob.ToString() return None def SetXmlBlob(self, blob): """Sets the contents of the extendedProperty to XML as a child node. Since the extendedProperty is only allowed one child element as an XML blob, setting the XML blob will erase any preexisting extension elements in this object. Args: blob: str, ElementTree Element or atom.ExtensionElement representing the XML blob stored in the extendedProperty. """ # Erase any existing extension_elements, clears the child nodes from the # extendedProperty. self.extension_elements = [] if isinstance(blob, atom.ExtensionElement): self.extension_elements.append(blob) elif ElementTree.iselement(blob): self.extension_elements.append(atom._ExtensionElementFromElementTree( blob)) else: self.extension_elements.append(atom.ExtensionElementFromString(blob)) def ExtendedPropertyFromString(xml_string): return atom.CreateClassFromXMLString(ExtendedProperty, xml_string) class GDataEntry(atom.Entry, LinkFinder): """Extends Atom Entry to provide data processing""" _tag = atom.Entry._tag _namespace = atom.Entry._namespace _children = atom.Entry._children.copy() _attributes = atom.Entry._attributes.copy() def __GetId(self): return self.__id # This method was created to strip the unwanted whitespace from the id's # text node. def __SetId(self, id): self.__id = id if id is not None and id.text is not None: self.__id.text = id.text.strip() id = property(__GetId, __SetId) def IsMedia(self): """Determines whether or not an entry is a GData Media entry. """ if (self.GetEditMediaLink()): return True else: return False def GetMediaURL(self): """Returns the URL to the media content, if the entry is a media entry. Otherwise returns None. """ if not self.IsMedia(): return None else: return self.content.src def GDataEntryFromString(xml_string): """Creates a new GDataEntry instance given a string of XML.""" return atom.CreateClassFromXMLString(GDataEntry, xml_string) class GDataFeed(atom.Feed, LinkFinder): """A Feed from a GData service""" _tag = 'feed' _namespace = atom.ATOM_NAMESPACE _children = atom.Feed._children.copy() _attributes = atom.Feed._attributes.copy() _children['{%s}totalResults' % OPENSEARCH_NAMESPACE] = ('total_results', TotalResults) _children['{%s}startIndex' % OPENSEARCH_NAMESPACE] = ('start_index', StartIndex) _children['{%s}itemsPerPage' % OPENSEARCH_NAMESPACE] = ('items_per_page', ItemsPerPage) # Add a conversion rule for atom:entry to make it into a GData # Entry. _children['{%s}entry' % atom.ATOM_NAMESPACE] = ('entry', [GDataEntry]) def __GetId(self): return self.__id def __SetId(self, id): self.__id = id if id is not None and id.text is not None: self.__id.text = id.text.strip() id = property(__GetId, __SetId) def __GetGenerator(self): return self.__generator def __SetGenerator(self, generator): self.__generator = generator if generator is not None: self.__generator.text = generator.text.strip() generator = property(__GetGenerator, __SetGenerator) def __init__(self, author=None, category=None, contributor=None, generator=None, icon=None, atom_id=None, link=None, logo=None, rights=None, subtitle=None, title=None, updated=None, entry=None, total_results=None, start_index=None, items_per_page=None, extension_elements=None, extension_attributes=None, text=None): """Constructor for Source Args: author: list (optional) A list of Author instances which belong to this class. category: list (optional) A list of Category instances contributor: list (optional) A list on Contributor instances generator: Generator (optional) icon: Icon (optional) id: Id (optional) The entry's Id element link: list (optional) A list of Link instances logo: Logo (optional) rights: Rights (optional) The entry's Rights element subtitle: Subtitle (optional) The entry's subtitle element title: Title (optional) the entry's title element updated: Updated (optional) the entry's updated element entry: list (optional) A list of the Entry instances contained in the feed. text: String (optional) The text contents of the element. This is the contents of the Entry's XML text node. (Example: This is the text) extension_elements: list (optional) A list of ExtensionElement instances which are children of this element. extension_attributes: dict (optional) A dictionary of strings which are the values for additional XML attributes of this element. """ self.author = author or [] self.category = category or [] self.contributor = contributor or [] self.generator = generator self.icon = icon self.id = atom_id self.link = link or [] self.logo = logo self.rights = rights self.subtitle = subtitle self.title = title self.updated = updated self.entry = entry or [] self.total_results = total_results self.start_index = start_index self.items_per_page = items_per_page self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} def GDataFeedFromString(xml_string): return atom.CreateClassFromXMLString(GDataFeed, xml_string) class BatchId(atom.AtomBase): _tag = 'id' _namespace = BATCH_NAMESPACE _children = atom.AtomBase._children.copy() _attributes = atom.AtomBase._attributes.copy() def BatchIdFromString(xml_string): return atom.CreateClassFromXMLString(BatchId, xml_string) class BatchOperation(atom.AtomBase): _tag = 'operation' _namespace = BATCH_NAMESPACE _children = atom.AtomBase._children.copy() _attributes = atom.AtomBase._attributes.copy() _attributes['type'] = 'type' def __init__(self, op_type=None, extension_elements=None, extension_attributes=None, text=None): self.type = op_type atom.AtomBase.__init__(self, extension_elements=extension_elements, extension_attributes=extension_attributes, text=text) def BatchOperationFromString(xml_string): return atom.CreateClassFromXMLString(BatchOperation, xml_string) class BatchStatus(atom.AtomBase): """The batch:status element present in a batch response entry. A status element contains the code (HTTP response code) and reason as elements. In a single request these fields would be part of the HTTP response, but in a batch request each Entry operation has a corresponding Entry in the response feed which includes status information. See http://code.google.com/apis/gdata/batch.html#Handling_Errors """ _tag = 'status' _namespace = BATCH_NAMESPACE _children = atom.AtomBase._children.copy() _attributes = atom.AtomBase._attributes.copy() _attributes['code'] = 'code' _attributes['reason'] = 'reason' _attributes['content-type'] = 'content_type' def __init__(self, code=None, reason=None, content_type=None, extension_elements=None, extension_attributes=None, text=None): self.code = code self.reason = reason self.content_type = content_type atom.AtomBase.__init__(self, extension_elements=extension_elements, extension_attributes=extension_attributes, text=text) def BatchStatusFromString(xml_string): return atom.CreateClassFromXMLString(BatchStatus, xml_string) class BatchEntry(GDataEntry): """An atom:entry for use in batch requests. The BatchEntry contains additional members to specify the operation to be performed on this entry and a batch ID so that the server can reference individual operations in the response feed. For more information, see: http://code.google.com/apis/gdata/batch.html """ _tag = GDataEntry._tag _namespace = GDataEntry._namespace _children = GDataEntry._children.copy() _children['{%s}operation' % BATCH_NAMESPACE] = ('batch_operation', BatchOperation) _children['{%s}id' % BATCH_NAMESPACE] = ('batch_id', BatchId) _children['{%s}status' % BATCH_NAMESPACE] = ('batch_status', BatchStatus) _attributes = GDataEntry._attributes.copy() def __init__(self, author=None, category=None, content=None, contributor=None, atom_id=None, link=None, published=None, rights=None, source=None, summary=None, control=None, title=None, updated=None, batch_operation=None, batch_id=None, batch_status=None, extension_elements=None, extension_attributes=None, text=None): self.batch_operation = batch_operation self.batch_id = batch_id self.batch_status = batch_status GDataEntry.__init__(self, author=author, category=category, content=content, contributor=contributor, atom_id=atom_id, link=link, published=published, rights=rights, source=source, summary=summary, control=control, title=title, updated=updated, extension_elements=extension_elements, extension_attributes=extension_attributes, text=text) def BatchEntryFromString(xml_string): return atom.CreateClassFromXMLString(BatchEntry, xml_string) class BatchInterrupted(atom.AtomBase): """The batch:interrupted element sent if batch request was interrupted. Only appears in a feed if some of the batch entries could not be processed. See: http://code.google.com/apis/gdata/batch.html#Handling_Errors """ _tag = 'interrupted' _namespace = BATCH_NAMESPACE _children = atom.AtomBase._children.copy() _attributes = atom.AtomBase._attributes.copy() _attributes['reason'] = 'reason' _attributes['success'] = 'success' _attributes['failures'] = 'failures' _attributes['parsed'] = 'parsed' def __init__(self, reason=None, success=None, failures=None, parsed=None, extension_elements=None, extension_attributes=None, text=None): self.reason = reason self.success = success self.failures = failures self.parsed = parsed atom.AtomBase.__init__(self, extension_elements=extension_elements, extension_attributes=extension_attributes, text=text) def BatchInterruptedFromString(xml_string): return atom.CreateClassFromXMLString(BatchInterrupted, xml_string) class BatchFeed(GDataFeed): """A feed containing a list of batch request entries.""" _tag = GDataFeed._tag _namespace = GDataFeed._namespace _children = GDataFeed._children.copy() _attributes = GDataFeed._attributes.copy() _children['{%s}entry' % atom.ATOM_NAMESPACE] = ('entry', [BatchEntry]) _children['{%s}interrupted' % BATCH_NAMESPACE] = ('interrupted', BatchInterrupted) def __init__(self, author=None, category=None, contributor=None, generator=None, icon=None, atom_id=None, link=None, logo=None, rights=None, subtitle=None, title=None, updated=None, entry=None, total_results=None, start_index=None, items_per_page=None, interrupted=None, extension_elements=None, extension_attributes=None, text=None): self.interrupted = interrupted GDataFeed.__init__(self, author=author, category=category, contributor=contributor, generator=generator, icon=icon, atom_id=atom_id, link=link, logo=logo, rights=rights, subtitle=subtitle, title=title, updated=updated, entry=entry, total_results=total_results, start_index=start_index, items_per_page=items_per_page, extension_elements=extension_elements, extension_attributes=extension_attributes, text=text) def AddBatchEntry(self, entry=None, id_url_string=None, batch_id_string=None, operation_string=None): """Logic for populating members of a BatchEntry and adding to the feed. If the entry is not a BatchEntry, it is converted to a BatchEntry so that the batch specific members will be present. The id_url_string can be used in place of an entry if the batch operation applies to a URL. For example query and delete operations require just the URL of an entry, no body is sent in the HTTP request. If an id_url_string is sent instead of an entry, a BatchEntry is created and added to the feed. This method also assigns the desired batch id to the entry so that it can be referenced in the server's response. If the batch_id_string is None, this method will assign a batch_id to be the index at which this entry will be in the feed's entry list. Args: entry: BatchEntry, atom.Entry, or another Entry flavor (optional) The entry which will be sent to the server as part of the batch request. The item must have a valid atom id so that the server knows which entry this request references. id_url_string: str (optional) The URL of the entry to be acted on. You can find this URL in the text member of the atom id for an entry. If an entry is not sent, this id will be used to construct a new BatchEntry which will be added to the request feed. batch_id_string: str (optional) The batch ID to be used to reference this batch operation in the results feed. If this parameter is None, the current length of the feed's entry array will be used as a count. Note that batch_ids should either always be specified or never, mixing could potentially result in duplicate batch ids. operation_string: str (optional) The desired batch operation which will set the batch_operation.type member of the entry. Options are 'insert', 'update', 'delete', and 'query' Raises: MissingRequiredParameters: Raised if neither an id_ url_string nor an entry are provided in the request. Returns: The added entry. """ if entry is None and id_url_string is None: raise MissingRequiredParameters('supply either an entry or URL string') if entry is None and id_url_string is not None: entry = BatchEntry(atom_id=atom.Id(text=id_url_string)) # TODO: handle cases in which the entry lacks batch_... members. #if not isinstance(entry, BatchEntry): # Convert the entry to a batch entry. if batch_id_string is not None: entry.batch_id = BatchId(text=batch_id_string) elif entry.batch_id is None or entry.batch_id.text is None: entry.batch_id = BatchId(text=str(len(self.entry))) if operation_string is not None: entry.batch_operation = BatchOperation(op_type=operation_string) self.entry.append(entry) return entry def AddInsert(self, entry, batch_id_string=None): """Add an insert request to the operations in this batch request feed. If the entry doesn't yet have an operation or a batch id, these will be set to the insert operation and a batch_id specified as a parameter. Args: entry: BatchEntry The entry which will be sent in the batch feed as an insert request. batch_id_string: str (optional) The batch ID to be used to reference this batch operation in the results feed. If this parameter is None, the current length of the feed's entry array will be used as a count. Note that batch_ids should either always be specified or never, mixing could potentially result in duplicate batch ids. """ entry = self.AddBatchEntry(entry=entry, batch_id_string=batch_id_string, operation_string=BATCH_INSERT) def AddUpdate(self, entry, batch_id_string=None): """Add an update request to the list of batch operations in this feed. Sets the operation type of the entry to insert if it is not already set and assigns the desired batch id to the entry so that it can be referenced in the server's response. Args: entry: BatchEntry The entry which will be sent to the server as an update (HTTP PUT) request. The item must have a valid atom id so that the server knows which entry to replace. batch_id_string: str (optional) The batch ID to be used to reference this batch operation in the results feed. If this parameter is None, the current length of the feed's entry array will be used as a count. See also comments for AddInsert. """ entry = self.AddBatchEntry(entry=entry, batch_id_string=batch_id_string, operation_string=BATCH_UPDATE) def AddDelete(self, url_string=None, entry=None, batch_id_string=None): """Adds a delete request to the batch request feed. This method takes either the url_string which is the atom id of the item to be deleted, or the entry itself. The atom id of the entry must be present so that the server knows which entry should be deleted. Args: url_string: str (optional) The URL of the entry to be deleted. You can find this URL in the text member of the atom id for an entry. entry: BatchEntry (optional) The entry to be deleted. batch_id_string: str (optional) Raises: MissingRequiredParameters: Raised if neither a url_string nor an entry are provided in the request. """ entry = self.AddBatchEntry(entry=entry, id_url_string=url_string, batch_id_string=batch_id_string, operation_string=BATCH_DELETE) def AddQuery(self, url_string=None, entry=None, batch_id_string=None): """Adds a query request to the batch request feed. This method takes either the url_string which is the query URL whose results will be added to the result feed. The query URL will be encapsulated in a BatchEntry, and you may pass in the BatchEntry with a query URL instead of sending a url_string. Args: url_string: str (optional) entry: BatchEntry (optional) batch_id_string: str (optional) Raises: MissingRequiredParameters """ entry = self.AddBatchEntry(entry=entry, id_url_string=url_string, batch_id_string=batch_id_string, operation_string=BATCH_QUERY) def GetBatchLink(self): for link in self.link: if link.rel == 'http://schemas.google.com/g/2005#batch': return link return None def BatchFeedFromString(xml_string): return atom.CreateClassFromXMLString(BatchFeed, xml_string) class EntryLink(atom.AtomBase): """The gd:entryLink element""" _tag = 'entryLink' _namespace = GDATA_NAMESPACE _children = atom.AtomBase._children.copy() _attributes = atom.AtomBase._attributes.copy() # The entry used to be an atom.Entry, now it is a GDataEntry. _children['{%s}entry' % atom.ATOM_NAMESPACE] = ('entry', GDataEntry) _attributes['rel'] = 'rel' _attributes['readOnly'] = 'read_only' _attributes['href'] = 'href' def __init__(self, href=None, read_only=None, rel=None, entry=None, extension_elements=None, extension_attributes=None, text=None): self.href = href self.read_only = read_only self.rel = rel self.entry = entry self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} def EntryLinkFromString(xml_string): return atom.CreateClassFromXMLString(EntryLink, xml_string) class FeedLink(atom.AtomBase): """The gd:feedLink element""" _tag = 'feedLink' _namespace = GDATA_NAMESPACE _children = atom.AtomBase._children.copy() _attributes = atom.AtomBase._attributes.copy() _children['{%s}feed' % atom.ATOM_NAMESPACE] = ('feed', GDataFeed) _attributes['rel'] = 'rel' _attributes['readOnly'] = 'read_only' _attributes['countHint'] = 'count_hint' _attributes['href'] = 'href' def __init__(self, count_hint=None, href=None, read_only=None, rel=None, feed=None, extension_elements=None, extension_attributes=None, text=None): self.count_hint = count_hint self.href = href self.read_only = read_only self.rel = rel self.feed = feed self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} def FeedLinkFromString(xml_string): return atom.CreateClassFromXMLString(FeedLink, xml_string) gdata-2.0.18/samples/apps/marketplace_sample/gdata/client.py0000640036466300116100000014253012156622362024112 0ustar afshareng00000000000000#!/usr/bin/env python # # Copyright (C) 2008, 2009 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. # This module is used for version 2 of the Google Data APIs. """Provides a client to interact with Google Data API servers. This module is used for version 2 of the Google Data APIs. The primary class in this module is GDClient. GDClient: handles auth and CRUD operations when communicating with servers. GDataClient: deprecated client for version one services. Will be removed. """ __author__ = 'j.s@google.com (Jeff Scudder)' import re import atom.client import atom.core import atom.http_core import gdata.gauth import gdata.data class Error(Exception): pass class RequestError(Error): status = None reason = None body = None headers = None class RedirectError(RequestError): pass class CaptchaChallenge(RequestError): captcha_url = None captcha_token = None class ClientLoginTokenMissing(Error): pass class MissingOAuthParameters(Error): pass class ClientLoginFailed(RequestError): pass class UnableToUpgradeToken(RequestError): pass class Unauthorized(Error): pass class BadAuthenticationServiceURL(RedirectError): pass class BadAuthentication(RequestError): pass class NotModified(RequestError): pass class NotImplemented(RequestError): pass def error_from_response(message, http_response, error_class, response_body=None): """Creates a new exception and sets the HTTP information in the error. Args: message: str human readable message to be displayed if the exception is not caught. http_response: The response from the server, contains error information. error_class: The exception to be instantiated and populated with information from the http_response response_body: str (optional) specify if the response has already been read from the http_response object. """ if response_body is None: body = http_response.read() else: body = response_body error = error_class('%s: %i, %s' % (message, http_response.status, body)) error.status = http_response.status error.reason = http_response.reason error.body = body error.headers = atom.http_core.get_headers(http_response) return error def get_xml_version(version): """Determines which XML schema to use based on the client API version. Args: version: string which is converted to an int. The version string is in the form 'Major.Minor.x.y.z' and only the major version number is considered. If None is provided assume version 1. """ if version is None: return 1 return int(version.split('.')[0]) class GDClient(atom.client.AtomPubClient): """Communicates with Google Data servers to perform CRUD operations. This class is currently experimental and may change in backwards incompatible ways. This class exists to simplify the following three areas involved in using the Google Data APIs. CRUD Operations: The client provides a generic 'request' method for making HTTP requests. There are a number of convenience methods which are built on top of request, which include get_feed, get_entry, get_next, post, update, and delete. These methods contact the Google Data servers. Auth: Reading user-specific private data requires authorization from the user as do any changes to user data. An auth_token object can be passed into any of the HTTP requests to set the Authorization header in the request. You may also want to set the auth_token member to a an object which can use modify_request to set the Authorization header in the HTTP request. If you are authenticating using the email address and password, you can use the client_login method to obtain an auth token and set the auth_token member. If you are using browser redirects, specifically AuthSub, you will want to use gdata.gauth.AuthSubToken.from_url to obtain the token after the redirect, and you will probably want to updgrade this since use token to a multiple use (session) token using the upgrade_token method. API Versions: This client is multi-version capable and can be used with Google Data API version 1 and version 2. The version should be specified by setting the api_version member to a string, either '1' or '2'. """ # The gsessionid is used by Google Calendar to prevent redirects. __gsessionid = None api_version = None # Name of the Google Data service when making a ClientLogin request. auth_service = None # URL prefixes which should be requested for AuthSub and OAuth. auth_scopes = None # Name of alternate auth service to use in certain cases alt_auth_service = None def request(self, method=None, uri=None, auth_token=None, http_request=None, converter=None, desired_class=None, redirects_remaining=4, **kwargs): """Make an HTTP request to the server. See also documentation for atom.client.AtomPubClient.request. If a 302 redirect is sent from the server to the client, this client assumes that the redirect is in the form used by the Google Calendar API. The same request URI and method will be used as in the original request, but a gsessionid URL parameter will be added to the request URI with the value provided in the server's 302 redirect response. If the 302 redirect is not in the format specified by the Google Calendar API, a RedirectError will be raised containing the body of the server's response. The method calls the client's modify_request method to make any changes required by the client before the request is made. For example, a version 2 client could add a GData-Version: 2 header to the request in its modify_request method. Args: method: str The HTTP verb for this request, usually 'GET', 'POST', 'PUT', or 'DELETE' uri: atom.http_core.Uri, str, or unicode The URL being requested. auth_token: An object which sets the Authorization HTTP header in its modify_request method. Recommended classes include gdata.gauth.ClientLoginToken and gdata.gauth.AuthSubToken among others. http_request: (optional) atom.http_core.HttpRequest converter: function which takes the body of the response as its only argument and returns the desired object. desired_class: class descended from atom.core.XmlElement to which a successful response should be converted. If there is no converter function specified (converter=None) then the desired_class will be used in calling the atom.core.parse function. If neither the desired_class nor the converter is specified, an HTTP reponse object will be returned. redirects_remaining: (optional) int, if this number is 0 and the server sends a 302 redirect, the request method will raise an exception. This parameter is used in recursive request calls to avoid an infinite loop. Any additional arguments are passed through to atom.client.AtomPubClient.request. Returns: An HTTP response object (see atom.http_core.HttpResponse for a description of the object's interface) if no converter was specified and no desired_class was specified. If a converter function was provided, the results of calling the converter are returned. If no converter was specified but a desired_class was provided, the response body will be converted to the class using atom.core.parse. """ if isinstance(uri, (str, unicode)): uri = atom.http_core.Uri.parse_uri(uri) # Add the gsession ID to the URL to prevent further redirects. # TODO: If different sessions are using the same client, there will be a # multitude of redirects and session ID shuffling. # If the gsession ID is in the URL, adopt it as the standard location. if uri is not None and uri.query is not None and 'gsessionid' in uri.query: self.__gsessionid = uri.query['gsessionid'] # The gsession ID could also be in the HTTP request. elif (http_request is not None and http_request.uri is not None and http_request.uri.query is not None and 'gsessionid' in http_request.uri.query): self.__gsessionid = http_request.uri.query['gsessionid'] # If the gsession ID is stored in the client, and was not present in the # URI then add it to the URI. elif self.__gsessionid is not None: uri.query['gsessionid'] = self.__gsessionid # The AtomPubClient should call this class' modify_request before # performing the HTTP request. #http_request = self.modify_request(http_request) response = atom.client.AtomPubClient.request(self, method=method, uri=uri, auth_token=auth_token, http_request=http_request, **kwargs) # On success, convert the response body using the desired converter # function if present. if response is None: return None if response.status == 200 or response.status == 201: if converter is not None: return converter(response) elif desired_class is not None: if self.api_version is not None: return atom.core.parse(response.read(), desired_class, version=get_xml_version(self.api_version)) else: # No API version was specified, so allow parse to # use the default version. return atom.core.parse(response.read(), desired_class) else: return response # TODO: move the redirect logic into the Google Calendar client once it # exists since the redirects are only used in the calendar API. elif response.status == 302: if redirects_remaining > 0: location = (response.getheader('Location') or response.getheader('location')) if location is not None: # Make a recursive call with the gsession ID in the URI to follow # the redirect. return self.request(method=method, uri=location, auth_token=auth_token, http_request=http_request, converter=converter, desired_class=desired_class, redirects_remaining=redirects_remaining-1, **kwargs) else: raise error_from_response('302 received without Location header', response, RedirectError) else: raise error_from_response('Too many redirects from server', response, RedirectError) elif response.status == 401: raise error_from_response('Unauthorized - Server responded with', response, Unauthorized) elif response.status == 304: raise error_from_response('Entry Not Modified - Server responded with', response, NotModified) elif response.status == 501: raise error_from_response( 'This API operation is not implemented. - Server responded with', response, NotImplemented) # If the server's response was not a 200, 201, 302, 304, 401, or 501, raise # an exception. else: raise error_from_response('Server responded with', response, RequestError) Request = request def request_client_login_token( self, email, password, source, service=None, account_type='HOSTED_OR_GOOGLE', auth_url=atom.http_core.Uri.parse_uri( 'https://www.google.com/accounts/ClientLogin'), captcha_token=None, captcha_response=None): service = service or self.auth_service # Set the target URL. http_request = atom.http_core.HttpRequest(uri=auth_url, method='POST') http_request.add_body_part( gdata.gauth.generate_client_login_request_body(email=email, password=password, service=service, source=source, account_type=account_type, captcha_token=captcha_token, captcha_response=captcha_response), 'application/x-www-form-urlencoded') # Use the underlying http_client to make the request. response = self.http_client.request(http_request) response_body = response.read() if response.status == 200: token_string = gdata.gauth.get_client_login_token_string(response_body) if token_string is not None: return gdata.gauth.ClientLoginToken(token_string) else: raise ClientLoginTokenMissing( 'Recieved a 200 response to client login request,' ' but no token was present. %s' % (response_body,)) elif response.status == 403: captcha_challenge = gdata.gauth.get_captcha_challenge(response_body) if captcha_challenge: challenge = CaptchaChallenge('CAPTCHA required') challenge.captcha_url = captcha_challenge['url'] challenge.captcha_token = captcha_challenge['token'] raise challenge elif response_body.splitlines()[0] == 'Error=BadAuthentication': raise BadAuthentication('Incorrect username or password') else: raise error_from_response('Server responded with a 403 code', response, RequestError, response_body) elif response.status == 302: # Google tries to redirect all bad URLs back to # http://www.google.. If a redirect # attempt is made, assume the user has supplied an incorrect # authentication URL raise error_from_response('Server responded with a redirect', response, BadAuthenticationServiceURL, response_body) else: raise error_from_response('Server responded to ClientLogin request', response, ClientLoginFailed, response_body) RequestClientLoginToken = request_client_login_token def client_login(self, email, password, source, service=None, account_type='HOSTED_OR_GOOGLE', auth_url=atom.http_core.Uri.parse_uri( 'https://www.google.com/accounts/ClientLogin'), captcha_token=None, captcha_response=None): """Performs an auth request using the user's email address and password. In order to modify user specific data and read user private data, your application must be authorized by the user. One way to demonstrage authorization is by including a Client Login token in the Authorization HTTP header of all requests. This method requests the Client Login token by sending the user's email address, password, the name of the application, and the service code for the service which will be accessed by the application. If the username and password are correct, the server will respond with the client login code and a new ClientLoginToken object will be set in the client's auth_token member. With the auth_token set, future requests from this client will include the Client Login token. For a list of service names, see http://code.google.com/apis/gdata/faq.html#clientlogin For more information on Client Login, see: http://code.google.com/apis/accounts/docs/AuthForInstalledApps.html Args: email: str The user's email address or username. password: str The password for the user's account. source: str The name of your application. This can be anything you like but should should give some indication of which app is making the request. service: str The service code for the service you would like to access. For example, 'cp' for contacts, 'cl' for calendar. For a full list see http://code.google.com/apis/gdata/faq.html#clientlogin If you are using a subclass of the gdata.client.GDClient, the service will usually be filled in for you so you do not need to specify it. For example see BloggerClient, SpreadsheetsClient, etc. account_type: str (optional) The type of account which is being authenticated. This can be either 'GOOGLE' for a Google Account, 'HOSTED' for a Google Apps Account, or the default 'HOSTED_OR_GOOGLE' which will select the Google Apps Account if the same email address is used for both a Google Account and a Google Apps Account. auth_url: str (optional) The URL to which the login request should be sent. captcha_token: str (optional) If a previous login attempt was reponded to with a CAPTCHA challenge, this is the token which identifies the challenge (from the CAPTCHA's URL). captcha_response: str (optional) If a previous login attempt was reponded to with a CAPTCHA challenge, this is the response text which was contained in the challenge. Returns: Generated token, which is also stored in this object. Raises: A RequestError or one of its suclasses: BadAuthentication, BadAuthenticationServiceURL, ClientLoginFailed, ClientLoginTokenMissing, or CaptchaChallenge """ service = service or self.auth_service self.auth_token = self.request_client_login_token(email, password, source, service=service, account_type=account_type, auth_url=auth_url, captcha_token=captcha_token, captcha_response=captcha_response) if self.alt_auth_service is not None: self.alt_auth_token = self.request_client_login_token( email, password, source, service=self.alt_auth_service, account_type=account_type, auth_url=auth_url, captcha_token=captcha_token, captcha_response=captcha_response) return self.auth_token ClientLogin = client_login def upgrade_token(self, token=None, url=atom.http_core.Uri.parse_uri( 'https://www.google.com/accounts/AuthSubSessionToken')): """Asks the Google auth server for a multi-use AuthSub token. For details on AuthSub, see: http://code.google.com/apis/accounts/docs/AuthSub.html Args: token: gdata.gauth.AuthSubToken or gdata.gauth.SecureAuthSubToken (optional) If no token is passed in, the client's auth_token member is used to request the new token. The token object will be modified to contain the new session token string. url: str or atom.http_core.Uri (optional) The URL to which the token upgrade request should be sent. Defaults to: https://www.google.com/accounts/AuthSubSessionToken Returns: The upgraded gdata.gauth.AuthSubToken object. """ # Default to using the auth_token member if no token is provided. if token is None: token = self.auth_token # We cannot upgrade a None token. if token is None: raise UnableToUpgradeToken('No token was provided.') if not isinstance(token, gdata.gauth.AuthSubToken): raise UnableToUpgradeToken( 'Cannot upgrade the token because it is not an AuthSubToken object.') http_request = atom.http_core.HttpRequest(uri=url, method='GET') token.modify_request(http_request) # Use the lower level HttpClient to make the request. response = self.http_client.request(http_request) if response.status == 200: token._upgrade_token(response.read()) return token else: raise UnableToUpgradeToken( 'Server responded to token upgrade request with %s: %s' % ( response.status, response.read())) UpgradeToken = upgrade_token def revoke_token(self, token=None, url=atom.http_core.Uri.parse_uri( 'https://www.google.com/accounts/AuthSubRevokeToken')): """Requests that the token be invalidated. This method can be used for both AuthSub and OAuth tokens (to invalidate a ClientLogin token, the user must change their password). Returns: True if the server responded with a 200. Raises: A RequestError if the server responds with a non-200 status. """ # Default to using the auth_token member if no token is provided. if token is None: token = self.auth_token http_request = atom.http_core.HttpRequest(uri=url, method='GET') token.modify_request(http_request) response = self.http_client.request(http_request) if response.status != 200: raise error_from_response('Server sent non-200 to revoke token', response, RequestError, response.read()) return True RevokeToken = revoke_token def get_oauth_token(self, scopes, next, consumer_key, consumer_secret=None, rsa_private_key=None, url=gdata.gauth.REQUEST_TOKEN_URL): """Obtains an OAuth request token to allow the user to authorize this app. Once this client has a request token, the user can authorize the request token by visiting the authorization URL in their browser. After being redirected back to this app at the 'next' URL, this app can then exchange the authorized request token for an access token. For more information see the documentation on Google Accounts with OAuth: http://code.google.com/apis/accounts/docs/OAuth.html#AuthProcess Args: scopes: list of strings or atom.http_core.Uri objects which specify the URL prefixes which this app will be accessing. For example, to access the Google Calendar API, you would want to use scopes: ['https://www.google.com/calendar/feeds/', 'http://www.google.com/calendar/feeds/'] next: str or atom.http_core.Uri object, The URL which the user's browser should be sent to after they authorize access to their data. This should be a URL in your application which will read the token information from the URL and upgrade the request token to an access token. consumer_key: str This is the identifier for this application which you should have received when you registered your application with Google to use OAuth. consumer_secret: str (optional) The shared secret between your app and Google which provides evidence that this request is coming from you application and not another app. If present, this libraries assumes you want to use an HMAC signature to verify requests. Keep this data a secret. rsa_private_key: str (optional) The RSA private key which is used to generate a digital signature which is checked by Google's server. If present, this library assumes that you want to use an RSA signature to verify requests. Keep this data a secret. url: The URL to which a request for a token should be made. The default is Google's OAuth request token provider. """ http_request = None if rsa_private_key is not None: http_request = gdata.gauth.generate_request_for_request_token( consumer_key, gdata.gauth.RSA_SHA1, scopes, rsa_key=rsa_private_key, auth_server_url=url, next=next) elif consumer_secret is not None: http_request = gdata.gauth.generate_request_for_request_token( consumer_key, gdata.gauth.HMAC_SHA1, scopes, consumer_secret=consumer_secret, auth_server_url=url, next=next) else: raise MissingOAuthParameters( 'To request an OAuth token, you must provide your consumer secret' ' or your private RSA key.') response = self.http_client.request(http_request) response_body = response.read() if response.status != 200: raise error_from_response('Unable to obtain OAuth request token', response, RequestError, response_body) if rsa_private_key is not None: return gdata.gauth.rsa_token_from_body(response_body, consumer_key, rsa_private_key, gdata.gauth.REQUEST_TOKEN) elif consumer_secret is not None: return gdata.gauth.hmac_token_from_body(response_body, consumer_key, consumer_secret, gdata.gauth.REQUEST_TOKEN) GetOAuthToken = get_oauth_token def get_access_token(self, request_token, url=gdata.gauth.ACCESS_TOKEN_URL): """Exchanges an authorized OAuth request token for an access token. Contacts the Google OAuth server to upgrade a previously authorized request token. Once the request token is upgraded to an access token, the access token may be used to access the user's data. For more details, see the Google Accounts OAuth documentation: http://code.google.com/apis/accounts/docs/OAuth.html#AccessToken Args: request_token: An OAuth token which has been authorized by the user. url: (optional) The URL to which the upgrade request should be sent. Defaults to: https://www.google.com/accounts/OAuthAuthorizeToken """ http_request = gdata.gauth.generate_request_for_access_token( request_token, auth_server_url=url) response = self.http_client.request(http_request) response_body = response.read() if response.status != 200: raise error_from_response( 'Unable to upgrade OAuth request token to access token', response, RequestError, response_body) return gdata.gauth.upgrade_to_access_token(request_token, response_body) GetAccessToken = get_access_token def modify_request(self, http_request): """Adds or changes request before making the HTTP request. This client will add the API version if it is specified. Subclasses may override this method to add their own request modifications before the request is made. """ http_request = atom.client.AtomPubClient.modify_request(self, http_request) if self.api_version is not None: http_request.headers['GData-Version'] = self.api_version return http_request ModifyRequest = modify_request def get_feed(self, uri, auth_token=None, converter=None, desired_class=gdata.data.GDFeed, **kwargs): return self.request(method='GET', uri=uri, auth_token=auth_token, converter=converter, desired_class=desired_class, **kwargs) GetFeed = get_feed def get_entry(self, uri, auth_token=None, converter=None, desired_class=gdata.data.GDEntry, etag=None, **kwargs): http_request = atom.http_core.HttpRequest() # Conditional retrieval if etag is not None: http_request.headers['If-None-Match'] = etag return self.request(method='GET', uri=uri, auth_token=auth_token, http_request=http_request, converter=converter, desired_class=desired_class, **kwargs) GetEntry = get_entry def get_next(self, feed, auth_token=None, converter=None, desired_class=None, **kwargs): """Fetches the next set of results from the feed. When requesting a feed, the number of entries returned is capped at a service specific default limit (often 25 entries). You can specify your own entry-count cap using the max-results URL query parameter. If there are more results than could fit under max-results, the feed will contain a next link. This method performs a GET against this next results URL. Returns: A new feed object containing the next set of entries in this feed. """ if converter is None and desired_class is None: desired_class = feed.__class__ return self.get_feed(feed.find_next_link(), auth_token=auth_token, converter=converter, desired_class=desired_class, **kwargs) GetNext = get_next # TODO: add a refresh method to re-fetch the entry/feed from the server # if it has been updated. def post(self, entry, uri, auth_token=None, converter=None, desired_class=None, **kwargs): if converter is None and desired_class is None: desired_class = entry.__class__ http_request = atom.http_core.HttpRequest() http_request.add_body_part( entry.to_string(get_xml_version(self.api_version)), 'application/atom+xml') return self.request(method='POST', uri=uri, auth_token=auth_token, http_request=http_request, converter=converter, desired_class=desired_class, **kwargs) Post = post def update(self, entry, auth_token=None, force=False, uri=None, **kwargs): """Edits the entry on the server by sending the XML for this entry. Performs a PUT and converts the response to a new entry object with a matching class to the entry passed in. Args: entry: auth_token: force: boolean stating whether an update should be forced. Defaults to False. Normally, if a change has been made since the passed in entry was obtained, the server will not overwrite the entry since the changes were based on an obsolete version of the entry. Setting force to True will cause the update to silently overwrite whatever version is present. uri: The uri to put to. If provided, this uri is PUT to rather than the inferred uri from the entry's edit link. Returns: A new Entry object of a matching type to the entry which was passed in. """ http_request = atom.http_core.HttpRequest() http_request.add_body_part( entry.to_string(get_xml_version(self.api_version)), 'application/atom+xml') # Include the ETag in the request if present. if force: http_request.headers['If-Match'] = '*' elif hasattr(entry, 'etag') and entry.etag: http_request.headers['If-Match'] = entry.etag if uri is None: uri = entry.find_edit_link() return self.request(method='PUT', uri=uri, auth_token=auth_token, http_request=http_request, desired_class=entry.__class__, **kwargs) Update = update def delete(self, entry_or_uri, auth_token=None, force=False, **kwargs): http_request = atom.http_core.HttpRequest() # Include the ETag in the request if present. if force: http_request.headers['If-Match'] = '*' elif hasattr(entry_or_uri, 'etag') and entry_or_uri.etag: http_request.headers['If-Match'] = entry_or_uri.etag # If the user passes in a URL, just delete directly, may not work as # the service might require an ETag. if isinstance(entry_or_uri, (str, unicode, atom.http_core.Uri)): return self.request(method='DELETE', uri=entry_or_uri, http_request=http_request, auth_token=auth_token, **kwargs) return self.request(method='DELETE', uri=entry_or_uri.find_edit_link(), http_request=http_request, auth_token=auth_token, **kwargs) Delete = delete def batch(self, feed, uri=None, force=False, auth_token=None, **kwargs): """Sends a batch request to the server to execute operation entries. Args: feed: A batch feed containing batch entries, each is an operation. uri: (optional) The uri to which the batch request feed should be POSTed. If none is provided, then the feed's edit link will be used. force: (optional) boolean set to True if you want the batch update to clobber all data. If False, the version in the information in the feed object will cause the server to check to see that no changes intervened between when you fetched the data and when you sent the changes. auth_token: (optional) An object which sets the Authorization HTTP header in its modify_request method. Recommended classes include gdata.gauth.ClientLoginToken and gdata.gauth.AuthSubToken among others. """ http_request = atom.http_core.HttpRequest() http_request.add_body_part( feed.to_string(get_xml_version(self.api_version)), 'application/atom+xml') if force: http_request.headers['If-Match'] = '*' elif hasattr(feed, 'etag') and feed.etag: http_request.headers['If-Match'] = feed.etag if uri is None: uri = feed.find_edit_link() return self.request(method='POST', uri=uri, auth_token=auth_token, http_request=http_request, desired_class=feed.__class__, **kwargs) Batch = batch # TODO: add a refresh method to request a conditional update to an entry # or feed. def _add_query_param(param_string, value, http_request): if value: http_request.uri.query[param_string] = value class Query(object): def __init__(self, text_query=None, categories=None, author=None, alt=None, updated_min=None, updated_max=None, pretty_print=False, published_min=None, published_max=None, start_index=None, max_results=None, strict=False, **custom_parameters): """Constructs a Google Data Query to filter feed contents serverside. Args: text_query: Full text search str (optional) categories: list of strings (optional). Each string is a required category. To include an 'or' query, put a | in the string between terms. For example, to find everything in the Fitz category and the Laurie or Jane category (Fitz and (Laurie or Jane)) you would set categories to ['Fitz', 'Laurie|Jane']. author: str (optional) The service returns entries where the author name and/or email address match your query string. alt: str (optional) for the Alternative representation type you'd like the feed in. If you don't specify an alt parameter, the service returns an Atom feed. This is equivalent to alt='atom'. alt='rss' returns an RSS 2.0 result feed. alt='json' returns a JSON representation of the feed. alt='json-in-script' Requests a response that wraps JSON in a script tag. alt='atom-in-script' Requests an Atom response that wraps an XML string in a script tag. alt='rss-in-script' Requests an RSS response that wraps an XML string in a script tag. updated_min: str (optional), RFC 3339 timestamp format, lower bounds. For example: 2005-08-09T10:57:00-08:00 updated_max: str (optional) updated time must be earlier than timestamp. pretty_print: boolean (optional) If True the server's XML response will be indented to make it more human readable. Defaults to False. published_min: str (optional), Similar to updated_min but for published time. published_max: str (optional), Similar to updated_max but for published time. start_index: int or str (optional) 1-based index of the first result to be retrieved. Note that this isn't a general cursoring mechanism. If you first send a query with ?start-index=1&max-results=10 and then send another query with ?start-index=11&max-results=10, the service cannot guarantee that the results are equivalent to ?start-index=1&max-results=20, because insertions and deletions could have taken place in between the two queries. max_results: int or str (optional) Maximum number of results to be retrieved. Each service has a default max (usually 25) which can vary from service to service. There is also a service-specific limit to the max_results you can fetch in a request. strict: boolean (optional) If True, the server will return an error if the server does not recognize any of the parameters in the request URL. Defaults to False. custom_parameters: other query parameters that are not explicitly defined. """ self.text_query = text_query self.categories = categories or [] self.author = author self.alt = alt self.updated_min = updated_min self.updated_max = updated_max self.pretty_print = pretty_print self.published_min = published_min self.published_max = published_max self.start_index = start_index self.max_results = max_results self.strict = strict self.custom_parameters = custom_parameters def add_custom_parameter(self, key, value): self.custom_parameters[key] = value AddCustomParameter = add_custom_parameter def modify_request(self, http_request): _add_query_param('q', self.text_query, http_request) if self.categories: http_request.uri.query['category'] = ','.join(self.categories) _add_query_param('author', self.author, http_request) _add_query_param('alt', self.alt, http_request) _add_query_param('updated-min', self.updated_min, http_request) _add_query_param('updated-max', self.updated_max, http_request) if self.pretty_print: http_request.uri.query['prettyprint'] = 'true' _add_query_param('published-min', self.published_min, http_request) _add_query_param('published-max', self.published_max, http_request) if self.start_index is not None: http_request.uri.query['start-index'] = str(self.start_index) if self.max_results is not None: http_request.uri.query['max-results'] = str(self.max_results) if self.strict: http_request.uri.query['strict'] = 'true' http_request.uri.query.update(self.custom_parameters) ModifyRequest = modify_request class GDQuery(atom.http_core.Uri): def _get_text_query(self): return self.query['q'] def _set_text_query(self, value): self.query['q'] = value text_query = property(_get_text_query, _set_text_query, doc='The q parameter for searching for an exact text match on content') class ResumableUploader(object): """Resumable upload helper for the Google Data protocol.""" DEFAULT_CHUNK_SIZE = 5242880 # 5MB # Initial chunks which are smaller than 256KB might be dropped. The last # chunk for a file can be smaller tan this. MIN_CHUNK_SIZE = 262144 # 256KB def __init__(self, client, file_handle, content_type, total_file_size, chunk_size=None, desired_class=None): """Starts a resumable upload to a service that supports the protocol. Args: client: gdata.client.GDClient A Google Data API service. file_handle: object A file-like object containing the file to upload. content_type: str The mimetype of the file to upload. total_file_size: int The file's total size in bytes. chunk_size: int The size of each upload chunk. If None, the DEFAULT_CHUNK_SIZE will be used. desired_class: object (optional) The type of gdata.data.GDEntry to parse the completed entry as. This should be specific to the API. """ self.client = client self.file_handle = file_handle self.content_type = content_type self.total_file_size = total_file_size self.chunk_size = chunk_size or self.DEFAULT_CHUNK_SIZE if self.chunk_size < self.MIN_CHUNK_SIZE: self.chunk_size = self.MIN_CHUNK_SIZE self.desired_class = desired_class or gdata.data.GDEntry self.upload_uri = None # Send the entire file if the chunk size is less than fize's total size. if self.total_file_size <= self.chunk_size: self.chunk_size = total_file_size def _init_session(self, resumable_media_link, entry=None, headers=None, auth_token=None, method='POST'): """Starts a new resumable upload to a service that supports the protocol. The method makes a request to initiate a new upload session. The unique upload uri returned by the server (and set in this method) should be used to send upload chunks to the server. Args: resumable_media_link: str The full URL for the #resumable-create-media or #resumable-edit-media link for starting a resumable upload request or updating media using a resumable PUT. entry: A (optional) gdata.data.GDEntry containging metadata to create the upload from. headers: dict (optional) Additional headers to send in the initial request to create the resumable upload request. These headers will override any default headers sent in the request. For example: headers={'Slug': 'MyTitle'}. auth_token: (optional) An object which sets the Authorization HTTP header in its modify_request method. Recommended classes include gdata.gauth.ClientLoginToken and gdata.gauth.AuthSubToken among others. method: (optional) Type of HTTP request to start the session with. Defaults to 'POST', but may also be 'PUT'. Returns: Result of HTTP request to intialize the session. See atom.client.request. Raises: RequestError if the unique upload uri is not set or the server returns something other than an HTTP 308 when the upload is incomplete. """ http_request = atom.http_core.HttpRequest() # Send empty body if Atom XML wasn't specified. if entry is None: http_request.add_body_part('', self.content_type, size=0) else: http_request.add_body_part(str(entry), 'application/atom+xml', size=len(str(entry))) http_request.headers['X-Upload-Content-Type'] = self.content_type http_request.headers['X-Upload-Content-Length'] = str(self.total_file_size) if headers is not None: http_request.headers.update(headers) response = self.client.request(method=method, uri=resumable_media_link, auth_token=auth_token, http_request=http_request) self.upload_uri = (response.getheader('location') or response.getheader('Location')) return response _InitSession = _init_session def upload_chunk(self, start_byte, content_bytes): """Uploads a byte range (chunk) to the resumable upload server. Args: start_byte: int The byte offset of the total file where the byte range passed in lives. content_bytes: str The file contents of this chunk. Returns: The final Atom entry created on the server. The entry object's type will be the class specified in self.desired_class. Raises: RequestError if the unique upload uri is not set or the server returns something other than an HTTP 308 when the upload is incomplete. """ if self.upload_uri is None: raise RequestError('Resumable upload request not initialized.') # Adjustment if last byte range is less than defined chunk size. chunk_size = self.chunk_size if len(content_bytes) <= chunk_size: chunk_size = len(content_bytes) http_request = atom.http_core.HttpRequest() http_request.add_body_part(content_bytes, self.content_type, size=len(content_bytes)) http_request.headers['Content-Range'] = ('bytes %s-%s/%s' % (start_byte, start_byte + chunk_size - 1, self.total_file_size)) try: response = self.client.request(method='PUT', uri=self.upload_uri, http_request=http_request, desired_class=self.desired_class) return response except RequestError, error: if error.status == 308: return None else: raise error UploadChunk = upload_chunk def upload_file(self, resumable_media_link, entry=None, headers=None, auth_token=None, **kwargs): """Uploads an entire file in chunks using the resumable upload protocol. If you are interested in pausing an upload or controlling the chunking yourself, use the upload_chunk() method instead. Args: resumable_media_link: str The full URL for the #resumable-create-media for starting a resumable upload request. entry: A (optional) gdata.data.GDEntry containging metadata to create the upload from. headers: dict Additional headers to send in the initial request to create the resumable upload request. These headers will override any default headers sent in the request. For example: headers={'Slug': 'MyTitle'}. auth_token: (optional) An object which sets the Authorization HTTP header in its modify_request method. Recommended classes include gdata.gauth.ClientLoginToken and gdata.gauth.AuthSubToken among others. kwargs: (optional) Other args to pass to self._init_session. Returns: The final Atom entry created on the server. The entry object's type will be the class specified in self.desired_class. Raises: RequestError if anything other than a HTTP 308 is returned when the request raises an exception. """ self._init_session(resumable_media_link, headers=headers, auth_token=auth_token, entry=entry, **kwargs) start_byte = 0 entry = None while not entry: entry = self.upload_chunk( start_byte, self.file_handle.read(self.chunk_size)) start_byte += self.chunk_size return entry UploadFile = upload_file def update_file(self, entry_or_resumable_edit_link, headers=None, force=False, auth_token=None, update_metadata=False, uri_params=None): """Updates the contents of an existing file using the resumable protocol. If you are interested in pausing an upload or controlling the chunking yourself, use the upload_chunk() method instead. Args: entry_or_resumable_edit_link: object or string A gdata.data.GDEntry for the entry/file to update or the full uri of the link with rel #resumable-edit-media. headers: dict Additional headers to send in the initial request to create the resumable upload request. These headers will override any default headers sent in the request. For example: headers={'Slug': 'MyTitle'}. force boolean (optional) True to force an update and set the If-Match header to '*'. If False and entry_or_resumable_edit_link is a gdata.data.GDEntry object, its etag value is used. Otherwise this parameter should be set to True to force the update. auth_token: (optional) An object which sets the Authorization HTTP header in its modify_request method. Recommended classes include gdata.gauth.ClientLoginToken and gdata.gauth.AuthSubToken among others. update_metadata: (optional) True to also update the entry's metadata with that in the given GDEntry object in entry_or_resumable_edit_link. uri_params: (optional) Dict of additional parameters to attach to the URI. Some non-dict types are valid here, too, like list of tuple pairs. Returns: The final Atom entry created on the server. The entry object's type will be the class specified in self.desired_class. Raises: RequestError if anything other than a HTTP 308 is returned when the request raises an exception. """ custom_headers = {} if headers is not None: custom_headers.update(headers) uri = None entry = None if isinstance(entry_or_resumable_edit_link, gdata.data.GDEntry): uri = entry_or_resumable_edit_link.find_url( 'http://schemas.google.com/g/2005#resumable-edit-media') custom_headers['If-Match'] = entry_or_resumable_edit_link.etag if update_metadata: entry = entry_or_resumable_edit_link else: uri = entry_or_resumable_edit_link uri = atom.http_core.parse_uri(uri) if uri_params is not None: uri.query.update(uri_params) if force: custom_headers['If-Match'] = '*' return self.upload_file(str(uri), entry=entry, headers=custom_headers, auth_token=auth_token, method='PUT') UpdateFile = update_file def query_upload_status(self, uri=None): """Queries the current status of a resumable upload request. Args: uri: str (optional) A resumable upload uri to query and override the one that is set in this object. Returns: An integer representing the file position (byte) to resume the upload from or True if the upload is complete. Raises: RequestError if anything other than a HTTP 308 is returned when the request raises an exception. """ # Override object's unique upload uri. if uri is None: uri = self.upload_uri http_request = atom.http_core.HttpRequest() http_request.headers['Content-Length'] = '0' http_request.headers['Content-Range'] = 'bytes */%s' % self.total_file_size try: response = self.client.request( method='POST', uri=uri, http_request=http_request) if response.status == 201: return True else: raise error_from_response( '%s returned by server' % response.status, response, RequestError) except RequestError, error: if error.status == 308: for pair in error.headers: if pair[0].capitalize() == 'Range': return int(pair[1].split('-')[1]) + 1 else: raise error QueryUploadStatus = query_upload_status gdata-2.0.18/samples/apps/marketplace_sample/gdata/oauth/0000750036466300116100000000000012156625015023372 5ustar afshareng00000000000000gdata-2.0.18/samples/apps/marketplace_sample/gdata/oauth/CHANGES.txt0000750036466300116100000000145612156622362025216 0ustar afshareng000000000000001. Moved oauth.py to __init__.py 2. Refactored __init__.py for compatibility with python 2.2 (Issue 59) 3. Refactored rsa.py for compatibility with python 2.2 (Issue 59) 4. Refactored OAuthRequest.from_token_and_callback since the callback url was getting double url-encoding the callback url in place of single. (Issue 43) 5. Added build_signature_base_string method to rsa.py since it used the implementation of this method from oauth.OAuthSignatureMethod_HMAC_SHA1 which was incorrect since it enforced the presence of a consumer secret and a token secret. Also, changed its super class from oauth.OAuthSignatureMethod_HMAC_SHA1 to oauth.OAuthSignatureMethod (Issue 64) 6. Refactored .to_header method since it returned non-oauth params as well which was incorrect. (Issue 31)gdata-2.0.18/samples/apps/marketplace_sample/gdata/oauth/rsa.py0000750036466300116100000001110412156622362024533 0ustar afshareng00000000000000#!/usr/bin/python """ requires tlslite - http://trevp.net/tlslite/ """ import binascii try: from gdata.tlslite.utils import keyfactory except ImportError: from tlslite.tlslite.utils import keyfactory try: from gdata.tlslite.utils import cryptomath except ImportError: from tlslite.tlslite.utils import cryptomath # XXX andy: ugly local import due to module name, oauth.oauth import gdata.oauth as oauth class OAuthSignatureMethod_RSA_SHA1(oauth.OAuthSignatureMethod): def get_name(self): return "RSA-SHA1" def _fetch_public_cert(self, oauth_request): # not implemented yet, ideas are: # (1) do a lookup in a table of trusted certs keyed off of consumer # (2) fetch via http using a url provided by the requester # (3) some sort of specific discovery code based on request # # either way should return a string representation of the certificate raise NotImplementedError def _fetch_private_cert(self, oauth_request): # not implemented yet, ideas are: # (1) do a lookup in a table of trusted certs keyed off of consumer # # either way should return a string representation of the certificate raise NotImplementedError def build_signature_base_string(self, oauth_request, consumer, token): sig = ( oauth.escape(oauth_request.get_normalized_http_method()), oauth.escape(oauth_request.get_normalized_http_url()), oauth.escape(oauth_request.get_normalized_parameters()), ) key = '' raw = '&'.join(sig) return key, raw def build_signature(self, oauth_request, consumer, token): key, base_string = self.build_signature_base_string(oauth_request, consumer, token) # Fetch the private key cert based on the request cert = self._fetch_private_cert(oauth_request) # Pull the private key from the certificate privatekey = keyfactory.parsePrivateKey(cert) # Convert base_string to bytes #base_string_bytes = cryptomath.createByteArraySequence(base_string) # Sign using the key signed = privatekey.hashAndSign(base_string) return binascii.b2a_base64(signed)[:-1] def check_signature(self, oauth_request, consumer, token, signature): decoded_sig = base64.b64decode(signature); key, base_string = self.build_signature_base_string(oauth_request, consumer, token) # Fetch the public key cert based on the request cert = self._fetch_public_cert(oauth_request) # Pull the public key from the certificate publickey = keyfactory.parsePEMKey(cert, public=True) # Check the signature ok = publickey.hashAndVerify(decoded_sig, base_string) return ok class TestOAuthSignatureMethod_RSA_SHA1(OAuthSignatureMethod_RSA_SHA1): def _fetch_public_cert(self, oauth_request): cert = """ -----BEGIN CERTIFICATE----- MIIBpjCCAQ+gAwIBAgIBATANBgkqhkiG9w0BAQUFADAZMRcwFQYDVQQDDA5UZXN0 IFByaW5jaXBhbDAeFw03MDAxMDEwODAwMDBaFw0zODEyMzEwODAwMDBaMBkxFzAV BgNVBAMMDlRlc3QgUHJpbmNpcGFsMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB gQC0YjCwIfYoprq/FQO6lb3asXrxLlJFuCvtinTF5p0GxvQGu5O3gYytUvtC2JlY zypSRjVxwxrsuRcP3e641SdASwfrmzyvIgP08N4S0IFzEURkV1wp/IpH7kH41Etb mUmrXSwfNZsnQRE5SYSOhh+LcK2wyQkdgcMv11l4KoBkcwIDAQABMA0GCSqGSIb3 DQEBBQUAA4GBAGZLPEuJ5SiJ2ryq+CmEGOXfvlTtEL2nuGtr9PewxkgnOjZpUy+d 4TvuXJbNQc8f4AMWL/tO9w0Fk80rWKp9ea8/df4qMq5qlFWlx6yOLQxumNOmECKb WpkUQDIDJEoFUzKMVuJf4KO/FJ345+BNLGgbJ6WujreoM1X/gYfdnJ/J -----END CERTIFICATE----- """ return cert def _fetch_private_cert(self, oauth_request): cert = """ -----BEGIN PRIVATE KEY----- MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBALRiMLAh9iimur8V A7qVvdqxevEuUkW4K+2KdMXmnQbG9Aa7k7eBjK1S+0LYmVjPKlJGNXHDGuy5Fw/d 7rjVJ0BLB+ubPK8iA/Tw3hLQgXMRRGRXXCn8ikfuQfjUS1uZSatdLB81mydBETlJ hI6GH4twrbDJCR2Bwy/XWXgqgGRzAgMBAAECgYBYWVtleUzavkbrPjy0T5FMou8H X9u2AC2ry8vD/l7cqedtwMPp9k7TubgNFo+NGvKsl2ynyprOZR1xjQ7WgrgVB+mm uScOM/5HVceFuGRDhYTCObE+y1kxRloNYXnx3ei1zbeYLPCHdhxRYW7T0qcynNmw rn05/KO2RLjgQNalsQJBANeA3Q4Nugqy4QBUCEC09SqylT2K9FrrItqL2QKc9v0Z zO2uwllCbg0dwpVuYPYXYvikNHHg+aCWF+VXsb9rpPsCQQDWR9TT4ORdzoj+Nccn qkMsDmzt0EfNaAOwHOmVJ2RVBspPcxt5iN4HI7HNeG6U5YsFBb+/GZbgfBT3kpNG WPTpAkBI+gFhjfJvRw38n3g/+UeAkwMI2TJQS4n8+hid0uus3/zOjDySH3XHCUno cn1xOJAyZODBo47E+67R4jV1/gzbAkEAklJaspRPXP877NssM5nAZMU0/O/NGCZ+ 3jPgDUno6WbJn5cqm8MqWhW1xGkImgRk+fkDBquiq4gPiT898jusgQJAd5Zrr6Q8 AO/0isr/3aa6O6NLQxISLKcPDk2NOccAfS/xOtfOz4sJYM3+Bs4Io9+dZGSDCA54 Lw03eHTNQghS0A== -----END PRIVATE KEY----- """ return cert gdata-2.0.18/samples/apps/marketplace_sample/gdata/oauth/__init__.py0000750036466300116100000004627612156622362025527 0ustar afshareng00000000000000import cgi import urllib import time import random import urlparse import hmac import binascii VERSION = '1.0' # Hi Blaine! HTTP_METHOD = 'GET' SIGNATURE_METHOD = 'PLAINTEXT' # Generic exception class class OAuthError(RuntimeError): def __init__(self, message='OAuth error occured.'): self.message = message # optional WWW-Authenticate header (401 error) def build_authenticate_header(realm=''): return {'WWW-Authenticate': 'OAuth realm="%s"' % realm} # url escape def escape(s): # escape '/' too return urllib.quote(s, safe='~') # util function: current timestamp # seconds since epoch (UTC) def generate_timestamp(): return int(time.time()) # util function: nonce # pseudorandom number def generate_nonce(length=8): return ''.join([str(random.randint(0, 9)) for i in range(length)]) # OAuthConsumer is a data type that represents the identity of the Consumer # via its shared secret with the Service Provider. class OAuthConsumer(object): key = None secret = None def __init__(self, key, secret): self.key = key self.secret = secret # OAuthToken is a data type that represents an End User via either an access # or request token. class OAuthToken(object): # access tokens and request tokens key = None secret = None ''' key = the token secret = the token secret ''' def __init__(self, key, secret): self.key = key self.secret = secret def to_string(self): return urllib.urlencode({'oauth_token': self.key, 'oauth_token_secret': self.secret}) # return a token from something like: # oauth_token_secret=digg&oauth_token=digg def from_string(s): params = cgi.parse_qs(s, keep_blank_values=False) key = params['oauth_token'][0] secret = params['oauth_token_secret'][0] return OAuthToken(key, secret) from_string = staticmethod(from_string) def __str__(self): return self.to_string() # OAuthRequest represents the request and can be serialized class OAuthRequest(object): ''' OAuth parameters: - oauth_consumer_key - oauth_token - oauth_signature_method - oauth_signature - oauth_timestamp - oauth_nonce - oauth_version ... any additional parameters, as defined by the Service Provider. ''' parameters = None # oauth parameters http_method = HTTP_METHOD http_url = None version = VERSION def __init__(self, http_method=HTTP_METHOD, http_url=None, parameters=None): self.http_method = http_method self.http_url = http_url self.parameters = parameters or {} def set_parameter(self, parameter, value): self.parameters[parameter] = value def get_parameter(self, parameter): try: return self.parameters[parameter] except: raise OAuthError('Parameter not found: %s' % parameter) def _get_timestamp_nonce(self): return self.get_parameter('oauth_timestamp'), self.get_parameter('oauth_nonce') # get any non-oauth parameters def get_nonoauth_parameters(self): parameters = {} for k, v in self.parameters.iteritems(): # ignore oauth parameters if k.find('oauth_') < 0: parameters[k] = v return parameters # serialize as a header for an HTTPAuth request def to_header(self, realm=''): auth_header = 'OAuth realm="%s"' % realm # add the oauth parameters if self.parameters: for k, v in self.parameters.iteritems(): if k[:6] == 'oauth_': auth_header += ', %s="%s"' % (k, escape(str(v))) return {'Authorization': auth_header} # serialize as post data for a POST request def to_postdata(self): return '&'.join(['%s=%s' % (escape(str(k)), escape(str(v))) for k, v in self.parameters.iteritems()]) # serialize as a url for a GET request def to_url(self): return '%s?%s' % (self.get_normalized_http_url(), self.to_postdata()) # return a string that consists of all the parameters that need to be signed def get_normalized_parameters(self): params = self.parameters try: # exclude the signature if it exists del params['oauth_signature'] except: pass key_values = params.items() # sort lexicographically, first after key, then after value key_values.sort() # combine key value pairs in string and escape return '&'.join(['%s=%s' % (escape(str(k)), escape(str(v))) for k, v in key_values]) # just uppercases the http method def get_normalized_http_method(self): return self.http_method.upper() # parses the url and rebuilds it to be scheme://host/path def get_normalized_http_url(self): parts = urlparse.urlparse(self.http_url) host = parts[1].lower() if host.endswith(':80') or host.endswith(':443'): host = host.split(':')[0] url_string = '%s://%s%s' % (parts[0], host, parts[2]) # scheme, netloc, path return url_string # set the signature parameter to the result of build_signature def sign_request(self, signature_method, consumer, token): # set the signature method self.set_parameter('oauth_signature_method', signature_method.get_name()) # set the signature self.set_parameter('oauth_signature', self.build_signature(signature_method, consumer, token)) def build_signature(self, signature_method, consumer, token): # call the build signature method within the signature method return signature_method.build_signature(self, consumer, token) def from_request(http_method, http_url, headers=None, parameters=None, query_string=None): # combine multiple parameter sources if parameters is None: parameters = {} # headers if headers and 'Authorization' in headers: auth_header = headers['Authorization'] # check that the authorization header is OAuth if auth_header.index('OAuth') > -1: try: # get the parameters from the header header_params = OAuthRequest._split_header(auth_header) parameters.update(header_params) except: raise OAuthError('Unable to parse OAuth parameters from Authorization header.') # GET or POST query string if query_string: query_params = OAuthRequest._split_url_string(query_string) parameters.update(query_params) # URL parameters param_str = urlparse.urlparse(http_url)[4] # query url_params = OAuthRequest._split_url_string(param_str) parameters.update(url_params) if parameters: return OAuthRequest(http_method, http_url, parameters) return None from_request = staticmethod(from_request) def from_consumer_and_token(oauth_consumer, token=None, http_method=HTTP_METHOD, http_url=None, parameters=None): if not parameters: parameters = {} defaults = { 'oauth_consumer_key': oauth_consumer.key, 'oauth_timestamp': generate_timestamp(), 'oauth_nonce': generate_nonce(), 'oauth_version': OAuthRequest.version, } defaults.update(parameters) parameters = defaults if token: parameters['oauth_token'] = token.key return OAuthRequest(http_method, http_url, parameters) from_consumer_and_token = staticmethod(from_consumer_and_token) def from_token_and_callback(token, callback=None, http_method=HTTP_METHOD, http_url=None, parameters=None): if not parameters: parameters = {} parameters['oauth_token'] = token.key if callback: parameters['oauth_callback'] = callback return OAuthRequest(http_method, http_url, parameters) from_token_and_callback = staticmethod(from_token_and_callback) # util function: turn Authorization: header into parameters, has to do some unescaping def _split_header(header): params = {} parts = header[6:].split(',') for param in parts: # ignore realm parameter if param.find('realm') > -1: continue # remove whitespace param = param.strip() # split key-value param_parts = param.split('=', 1) # remove quotes and unescape the value params[param_parts[0]] = urllib.unquote(param_parts[1].strip('\"')) return params _split_header = staticmethod(_split_header) # util function: turn url string into parameters, has to do some unescaping # even empty values should be included def _split_url_string(param_str): parameters = cgi.parse_qs(param_str, keep_blank_values=True) for k, v in parameters.iteritems(): parameters[k] = urllib.unquote(v[0]) return parameters _split_url_string = staticmethod(_split_url_string) # OAuthServer is a worker to check a requests validity against a data store class OAuthServer(object): timestamp_threshold = 300 # in seconds, five minutes version = VERSION signature_methods = None data_store = None def __init__(self, data_store=None, signature_methods=None): self.data_store = data_store self.signature_methods = signature_methods or {} def set_data_store(self, oauth_data_store): self.data_store = oauth_data_store def get_data_store(self): return self.data_store def add_signature_method(self, signature_method): self.signature_methods[signature_method.get_name()] = signature_method return self.signature_methods # process a request_token request # returns the request token on success def fetch_request_token(self, oauth_request): try: # get the request token for authorization token = self._get_token(oauth_request, 'request') except OAuthError: # no token required for the initial token request version = self._get_version(oauth_request) consumer = self._get_consumer(oauth_request) self._check_signature(oauth_request, consumer, None) # fetch a new token token = self.data_store.fetch_request_token(consumer) return token # process an access_token request # returns the access token on success def fetch_access_token(self, oauth_request): version = self._get_version(oauth_request) consumer = self._get_consumer(oauth_request) # get the request token token = self._get_token(oauth_request, 'request') self._check_signature(oauth_request, consumer, token) new_token = self.data_store.fetch_access_token(consumer, token) return new_token # verify an api call, checks all the parameters def verify_request(self, oauth_request): # -> consumer and token version = self._get_version(oauth_request) consumer = self._get_consumer(oauth_request) # get the access token token = self._get_token(oauth_request, 'access') self._check_signature(oauth_request, consumer, token) parameters = oauth_request.get_nonoauth_parameters() return consumer, token, parameters # authorize a request token def authorize_token(self, token, user): return self.data_store.authorize_request_token(token, user) # get the callback url def get_callback(self, oauth_request): return oauth_request.get_parameter('oauth_callback') # optional support for the authenticate header def build_authenticate_header(self, realm=''): return {'WWW-Authenticate': 'OAuth realm="%s"' % realm} # verify the correct version request for this server def _get_version(self, oauth_request): try: version = oauth_request.get_parameter('oauth_version') except: version = VERSION if version and version != self.version: raise OAuthError('OAuth version %s not supported.' % str(version)) return version # figure out the signature with some defaults def _get_signature_method(self, oauth_request): try: signature_method = oauth_request.get_parameter('oauth_signature_method') except: signature_method = SIGNATURE_METHOD try: # get the signature method object signature_method = self.signature_methods[signature_method] except: signature_method_names = ', '.join(self.signature_methods.keys()) raise OAuthError('Signature method %s not supported try one of the following: %s' % (signature_method, signature_method_names)) return signature_method def _get_consumer(self, oauth_request): consumer_key = oauth_request.get_parameter('oauth_consumer_key') if not consumer_key: raise OAuthError('Invalid consumer key.') consumer = self.data_store.lookup_consumer(consumer_key) if not consumer: raise OAuthError('Invalid consumer.') return consumer # try to find the token for the provided request token key def _get_token(self, oauth_request, token_type='access'): token_field = oauth_request.get_parameter('oauth_token') consumer = self._get_consumer(oauth_request) token = self.data_store.lookup_token(consumer, token_type, token_field) if not token: raise OAuthError('Invalid %s token: %s' % (token_type, token_field)) return token def _check_signature(self, oauth_request, consumer, token): timestamp, nonce = oauth_request._get_timestamp_nonce() self._check_timestamp(timestamp) self._check_nonce(consumer, token, nonce) signature_method = self._get_signature_method(oauth_request) try: signature = oauth_request.get_parameter('oauth_signature') except: raise OAuthError('Missing signature.') # validate the signature valid_sig = signature_method.check_signature(oauth_request, consumer, token, signature) if not valid_sig: key, base = signature_method.build_signature_base_string(oauth_request, consumer, token) raise OAuthError('Invalid signature. Expected signature base string: %s' % base) built = signature_method.build_signature(oauth_request, consumer, token) def _check_timestamp(self, timestamp): # verify that timestamp is recentish timestamp = int(timestamp) now = int(time.time()) lapsed = now - timestamp if lapsed > self.timestamp_threshold: raise OAuthError('Expired timestamp: given %d and now %s has a greater difference than threshold %d' % (timestamp, now, self.timestamp_threshold)) def _check_nonce(self, consumer, token, nonce): # verify that the nonce is uniqueish nonce = self.data_store.lookup_nonce(consumer, token, nonce) if nonce: raise OAuthError('Nonce already used: %s' % str(nonce)) # OAuthClient is a worker to attempt to execute a request class OAuthClient(object): consumer = None token = None def __init__(self, oauth_consumer, oauth_token): self.consumer = oauth_consumer self.token = oauth_token def get_consumer(self): return self.consumer def get_token(self): return self.token def fetch_request_token(self, oauth_request): # -> OAuthToken raise NotImplementedError def fetch_access_token(self, oauth_request): # -> OAuthToken raise NotImplementedError def access_resource(self, oauth_request): # -> some protected resource raise NotImplementedError # OAuthDataStore is a database abstraction used to lookup consumers and tokens class OAuthDataStore(object): def lookup_consumer(self, key): # -> OAuthConsumer raise NotImplementedError def lookup_token(self, oauth_consumer, token_type, token_token): # -> OAuthToken raise NotImplementedError def lookup_nonce(self, oauth_consumer, oauth_token, nonce, timestamp): # -> OAuthToken raise NotImplementedError def fetch_request_token(self, oauth_consumer): # -> OAuthToken raise NotImplementedError def fetch_access_token(self, oauth_consumer, oauth_token): # -> OAuthToken raise NotImplementedError def authorize_request_token(self, oauth_token, user): # -> OAuthToken raise NotImplementedError # OAuthSignatureMethod is a strategy class that implements a signature method class OAuthSignatureMethod(object): def get_name(self): # -> str raise NotImplementedError def build_signature_base_string(self, oauth_request, oauth_consumer, oauth_token): # -> str key, str raw raise NotImplementedError def build_signature(self, oauth_request, oauth_consumer, oauth_token): # -> str raise NotImplementedError def check_signature(self, oauth_request, consumer, token, signature): built = self.build_signature(oauth_request, consumer, token) return built == signature class OAuthSignatureMethod_HMAC_SHA1(OAuthSignatureMethod): def get_name(self): return 'HMAC-SHA1' def build_signature_base_string(self, oauth_request, consumer, token): sig = ( escape(oauth_request.get_normalized_http_method()), escape(oauth_request.get_normalized_http_url()), escape(oauth_request.get_normalized_parameters()), ) key = '%s&' % escape(consumer.secret) if token: key += escape(token.secret) raw = '&'.join(sig) return key, raw def build_signature(self, oauth_request, consumer, token): # build the base signature string key, raw = self.build_signature_base_string(oauth_request, consumer, token) # hmac object try: import hashlib # 2.5 hashed = hmac.new(key, raw, hashlib.sha1) except: import sha # deprecated hashed = hmac.new(key, raw, sha) # calculate the digest base 64 return binascii.b2a_base64(hashed.digest())[:-1] class OAuthSignatureMethod_PLAINTEXT(OAuthSignatureMethod): def get_name(self): return 'PLAINTEXT' def build_signature_base_string(self, oauth_request, consumer, token): # concatenate the consumer key and secret sig = escape(consumer.secret) + '&' if token: sig = sig + escape(token.secret) return sig def build_signature(self, oauth_request, consumer, token): return self.build_signature_base_string(oauth_request, consumer, token) gdata-2.0.18/samples/apps/marketplace_sample/gdata/sample_util.py0000640036466300116100000002473212156622362025155 0ustar afshareng00000000000000#!/usr/bin/env python # # Copyright (C) 2009 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. """Provides utility functions used with command line samples.""" # This module is used for version 2 of the Google Data APIs. import sys import getpass import urllib import gdata.gauth __author__ = 'j.s@google.com (Jeff Scudder)' CLIENT_LOGIN = 1 AUTHSUB = 2 OAUTH = 3 HMAC = 1 RSA = 2 class SettingsUtil(object): """Gather's user preferences from flags or command prompts. An instance of this object stores the choices made by the user. At some point it might be useful to save the user's preferences so that they do not need to always set flags or answer preference prompts. """ def __init__(self, prefs=None): self.prefs = prefs or {} def get_param(self, name, prompt='', secret=False, ask=True, reuse=False): # First, check in this objects stored preferences. if name in self.prefs: return self.prefs[name] # Second, check for a command line parameter. value = None for i in xrange(len(sys.argv)): if sys.argv[i].startswith('--%s=' % name): value = sys.argv[i].split('=')[1] elif sys.argv[i] == '--%s' % name: value = sys.argv[i + 1] # Third, if it was not on the command line, ask the user to input the # value. if value is None and ask: prompt = '%s: ' % prompt if secret: value = getpass.getpass(prompt) else: value = raw_input(prompt) # If we want to save the preference for reuse in future requests, add it # to this object's prefs. if value is not None and reuse: self.prefs[name] = value return value def authorize_client(self, client, auth_type=None, service=None, source=None, scopes=None, oauth_type=None, consumer_key=None, consumer_secret=None): """Uses command line arguments, or prompts user for token values.""" if 'client_auth_token' in self.prefs: return if auth_type is None: auth_type = int(self.get_param( 'auth_type', 'Please choose the authorization mechanism you want' ' to use.\n' '1. to use your email address and password (ClientLogin)\n' '2. to use a web browser to visit an auth web page (AuthSub)\n' '3. if you have registed to use OAuth\n', reuse=True)) # Get the scopes for the services we want to access. if auth_type == AUTHSUB or auth_type == OAUTH: if scopes is None: scopes = self.get_param( 'scopes', 'Enter the URL prefixes (scopes) for the resources you ' 'would like to access.\nFor multiple scope URLs, place a comma ' 'between each URL.\n' 'Example: http://www.google.com/calendar/feeds/,' 'http://www.google.com/m8/feeds/\n', reuse=True).split(',') elif isinstance(scopes, (str, unicode)): scopes = scopes.split(',') if auth_type == CLIENT_LOGIN: email = self.get_param('email', 'Please enter your username', reuse=False) password = self.get_param('password', 'Password', True, reuse=False) if service is None: service = self.get_param( 'service', 'What is the name of the service you wish to access?' '\n(See list:' ' http://code.google.com/apis/gdata/faq.html#clientlogin)', reuse=True) if source is None: source = self.get_param('source', ask=False, reuse=True) client.client_login(email, password, source=source, service=service) elif auth_type == AUTHSUB: auth_sub_token = self.get_param('auth_sub_token', ask=False, reuse=True) session_token = self.get_param('session_token', ask=False, reuse=True) private_key = None auth_url = None single_use_token = None rsa_private_key = self.get_param( 'rsa_private_key', 'If you want to use secure mode AuthSub, please provide the\n' ' location of your RSA private key which corresponds to the\n' ' certificate you have uploaded for your domain. If you do not\n' ' have an RSA key, simply press enter', reuse=True) if rsa_private_key: try: private_key_file = open(rsa_private_key, 'rb') private_key = private_key_file.read() private_key_file.close() except IOError: print 'Unable to read private key from file' if private_key is not None: if client.auth_token is None: if session_token: client.auth_token = gdata.gauth.SecureAuthSubToken( session_token, private_key, scopes) self.prefs['client_auth_token'] = gdata.gauth.token_to_blob( client.auth_token) return elif auth_sub_token: client.auth_token = gdata.gauth.SecureAuthSubToken( auth_sub_token, private_key, scopes) client.upgrade_token() self.prefs['client_auth_token'] = gdata.gauth.token_to_blob( client.auth_token) return auth_url = gdata.gauth.generate_auth_sub_url( 'http://gauthmachine.appspot.com/authsub', scopes, True) print 'with a private key, get ready for this URL', auth_url else: if client.auth_token is None: if session_token: client.auth_token = gdata.gauth.AuthSubToken(session_token, scopes) self.prefs['client_auth_token'] = gdata.gauth.token_to_blob( client.auth_token) return elif auth_sub_token: client.auth_token = gdata.gauth.AuthSubToken(auth_sub_token, scopes) client.upgrade_token() self.prefs['client_auth_token'] = gdata.gauth.token_to_blob( client.auth_token) return auth_url = gdata.gauth.generate_auth_sub_url( 'http://gauthmachine.appspot.com/authsub', scopes) print 'Visit the following URL in your browser to authorize this app:' print str(auth_url) print 'After agreeing to authorize the app, copy the token value from' print ' the URL. Example: "www.google.com/?token=ab12" token value is' print ' ab12' token_value = raw_input('Please enter the token value: ') if private_key is not None: single_use_token = gdata.gauth.SecureAuthSubToken( token_value, private_key, scopes) else: single_use_token = gdata.gauth.AuthSubToken(token_value, scopes) client.auth_token = single_use_token client.upgrade_token() elif auth_type == OAUTH: if oauth_type is None: oauth_type = int(self.get_param( 'oauth_type', 'Please choose the authorization mechanism you want' ' to use.\n' '1. use an HMAC signature using your consumer key and secret\n' '2. use RSA with your private key to sign requests\n', reuse=True)) consumer_key = self.get_param( 'consumer_key', 'Please enter your OAuth conumer key ' 'which identifies your app', reuse=True) if oauth_type == HMAC: consumer_secret = self.get_param( 'consumer_secret', 'Please enter your OAuth conumer secret ' 'which you share with the OAuth provider', True, reuse=False) # Swap out this code once the client supports requesting an oauth # token. # Get a request token. request_token = client.get_oauth_token( scopes, 'http://gauthmachine.appspot.com/oauth', consumer_key, consumer_secret=consumer_secret) elif oauth_type == RSA: rsa_private_key = self.get_param( 'rsa_private_key', 'Please provide the location of your RSA private key which\n' ' corresponds to the certificate you have uploaded for your' ' domain.', reuse=True) try: private_key_file = open(rsa_private_key, 'rb') private_key = private_key_file.read() private_key_file.close() except IOError: print 'Unable to read private key from file' request_token = client.get_oauth_token( scopes, 'http://gauthmachine.appspot.com/oauth', consumer_key, rsa_private_key=private_key) else: print 'Invalid OAuth signature type' return None # Authorize the request token in the browser. print 'Visit the following URL in your browser to authorize this app:' print str(request_token.generate_authorization_url()) print 'After agreeing to authorize the app, copy URL from the browser\'s' print ' address bar.' url = raw_input('Please enter the url: ') gdata.gauth.authorize_request_token(request_token, url) # Exchange for an access token. client.auth_token = client.get_access_token(request_token) else: print 'Invalid authorization type.' return None if client.auth_token: self.prefs['client_auth_token'] = gdata.gauth.token_to_blob( client.auth_token) def get_param(name, prompt='', secret=False, ask=True): settings = SettingsUtil() return settings.get_param(name=name, prompt=prompt, secret=secret, ask=ask) def authorize_client(client, auth_type=None, service=None, source=None, scopes=None, oauth_type=None, consumer_key=None, consumer_secret=None): """Uses command line arguments, or prompts user for token values.""" settings = SettingsUtil() return settings.authorize_client(client=client, auth_type=auth_type, service=service, source=source, scopes=scopes, oauth_type=oauth_type, consumer_key=consumer_key, consumer_secret=consumer_secret) def print_options(): """Displays usage information, available command line params.""" # TODO: fill in the usage description for authorizing the client. print '' gdata-2.0.18/samples/apps/marketplace_sample/gdata/auth.py0000640036466300116100000011226512156622362023577 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright (C) 2007 - 2009 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. import cgi import math import random import re import time import types import urllib import atom.http_interface import atom.token_store import atom.url import gdata.oauth as oauth import gdata.oauth.rsa as oauth_rsa try: import gdata.tlslite.utils.keyfactory as keyfactory except ImportError: from tlslite.tlslite.utils import keyfactory try: import gdata.tlslite.utils.cryptomath as cryptomath except ImportError: from tlslite.tlslite.utils import cryptomath import gdata.gauth __author__ = 'api.jscudder (Jeff Scudder)' PROGRAMMATIC_AUTH_LABEL = 'GoogleLogin auth=' AUTHSUB_AUTH_LABEL = 'AuthSub token=' """This module provides functions and objects used with Google authentication. Details on Google authorization mechanisms used with the Google Data APIs can be found here: http://code.google.com/apis/gdata/auth.html http://code.google.com/apis/accounts/ The essential functions are the following. Related to ClientLogin: generate_client_login_request_body: Constructs the body of an HTTP request to obtain a ClientLogin token for a specific service. extract_client_login_token: Creates a ClientLoginToken with the token from a success response to a ClientLogin request. get_captcha_challenge: If the server responded to the ClientLogin request with a CAPTCHA challenge, this method extracts the CAPTCHA URL and identifying CAPTCHA token. Related to AuthSub: generate_auth_sub_url: Constructs a full URL for a AuthSub request. The user's browser must be sent to this Google Accounts URL and redirected back to the app to obtain the AuthSub token. extract_auth_sub_token_from_url: Once the user's browser has been redirected back to the web app, use this function to create an AuthSubToken with the correct authorization token and scope. token_from_http_body: Extracts the AuthSubToken value string from the server's response to an AuthSub session token upgrade request. """ def generate_client_login_request_body(email, password, service, source, account_type='HOSTED_OR_GOOGLE', captcha_token=None, captcha_response=None): """Creates the body of the autentication request See http://code.google.com/apis/accounts/AuthForInstalledApps.html#Request for more details. Args: email: str password: str service: str source: str account_type: str (optional) Defaul is 'HOSTED_OR_GOOGLE', other valid values are 'GOOGLE' and 'HOSTED' captcha_token: str (optional) captcha_response: str (optional) Returns: The HTTP body to send in a request for a client login token. """ return gdata.gauth.generate_client_login_request_body(email, password, service, source, account_type, captcha_token, captcha_response) GenerateClientLoginRequestBody = generate_client_login_request_body def GenerateClientLoginAuthToken(http_body): """Returns the token value to use in Authorization headers. Reads the token from the server's response to a Client Login request and creates header value to use in requests. Args: http_body: str The body of the server's HTTP response to a Client Login request Returns: The value half of an Authorization header. """ token = get_client_login_token(http_body) if token: return 'GoogleLogin auth=%s' % token return None def get_client_login_token(http_body): """Returns the token value for a ClientLoginToken. Reads the token from the server's response to a Client Login request and creates the token value string to use in requests. Args: http_body: str The body of the server's HTTP response to a Client Login request Returns: The token value string for a ClientLoginToken. """ return gdata.gauth.get_client_login_token_string(http_body) def extract_client_login_token(http_body, scopes): """Parses the server's response and returns a ClientLoginToken. Args: http_body: str The body of the server's HTTP response to a Client Login request. It is assumed that the login request was successful. scopes: list containing atom.url.Urls or strs. The scopes list contains all of the partial URLs under which the client login token is valid. For example, if scopes contains ['http://example.com/foo'] then the client login token would be valid for http://example.com/foo/bar/baz Returns: A ClientLoginToken which is valid for the specified scopes. """ token_string = get_client_login_token(http_body) token = ClientLoginToken(scopes=scopes) token.set_token_string(token_string) return token def get_captcha_challenge(http_body, captcha_base_url='http://www.google.com/accounts/'): """Returns the URL and token for a CAPTCHA challenge issued by the server. Args: http_body: str The body of the HTTP response from the server which contains the CAPTCHA challenge. captcha_base_url: str This function returns a full URL for viewing the challenge image which is built from the server's response. This base_url is used as the beginning of the URL because the server only provides the end of the URL. For example the server provides 'Captcha?ctoken=Hi...N' and the URL for the image is 'http://www.google.com/accounts/Captcha?ctoken=Hi...N' Returns: A dictionary containing the information needed to repond to the CAPTCHA challenge, the image URL and the ID token of the challenge. The dictionary is in the form: {'token': string identifying the CAPTCHA image, 'url': string containing the URL of the image} Returns None if there was no CAPTCHA challenge in the response. """ return gdata.gauth.get_captcha_challenge(http_body, captcha_base_url) GetCaptchaChallenge = get_captcha_challenge def GenerateOAuthRequestTokenUrl( oauth_input_params, scopes, request_token_url='https://www.google.com/accounts/OAuthGetRequestToken', extra_parameters=None): """Generate a URL at which a request for OAuth request token is to be sent. Args: oauth_input_params: OAuthInputParams OAuth input parameters. scopes: list of strings The URLs of the services to be accessed. request_token_url: string The beginning of the request token URL. This is normally 'https://www.google.com/accounts/OAuthGetRequestToken' or '/accounts/OAuthGetRequestToken' extra_parameters: dict (optional) key-value pairs as any additional parameters to be included in the URL and signature while making a request for fetching an OAuth request token. All the OAuth parameters are added by default. But if provided through this argument, any default parameters will be overwritten. For e.g. a default parameter oauth_version 1.0 can be overwritten if extra_parameters = {'oauth_version': '2.0'} Returns: atom.url.Url OAuth request token URL. """ scopes_string = ' '.join([str(scope) for scope in scopes]) parameters = {'scope': scopes_string} if extra_parameters: parameters.update(extra_parameters) oauth_request = oauth.OAuthRequest.from_consumer_and_token( oauth_input_params.GetConsumer(), http_url=request_token_url, parameters=parameters) oauth_request.sign_request(oauth_input_params.GetSignatureMethod(), oauth_input_params.GetConsumer(), None) return atom.url.parse_url(oauth_request.to_url()) def GenerateOAuthAuthorizationUrl( request_token, authorization_url='https://www.google.com/accounts/OAuthAuthorizeToken', callback_url=None, extra_params=None, include_scopes_in_callback=False, scopes_param_prefix='oauth_token_scope'): """Generates URL at which user will login to authorize the request token. Args: request_token: gdata.auth.OAuthToken OAuth request token. authorization_url: string The beginning of the authorization URL. This is normally 'https://www.google.com/accounts/OAuthAuthorizeToken' or '/accounts/OAuthAuthorizeToken' callback_url: string (optional) The URL user will be sent to after logging in and granting access. extra_params: dict (optional) Additional parameters to be sent. include_scopes_in_callback: Boolean (default=False) if set to True, and if 'callback_url' is present, the 'callback_url' will be modified to include the scope(s) from the request token as a URL parameter. The key for the 'callback' URL's scope parameter will be OAUTH_SCOPE_URL_PARAM_NAME. The benefit of including the scope URL as a parameter to the 'callback' URL, is that the page which receives the OAuth token will be able to tell which URLs the token grants access to. scopes_param_prefix: string (default='oauth_token_scope') The URL parameter key which maps to the list of valid scopes for the token. This URL parameter will be included in the callback URL along with the scopes of the token as value if include_scopes_in_callback=True. Returns: atom.url.Url OAuth authorization URL. """ scopes = request_token.scopes if isinstance(scopes, list): scopes = ' '.join(scopes) if include_scopes_in_callback and callback_url: if callback_url.find('?') > -1: callback_url += '&' else: callback_url += '?' callback_url += urllib.urlencode({scopes_param_prefix:scopes}) oauth_token = oauth.OAuthToken(request_token.key, request_token.secret) oauth_request = oauth.OAuthRequest.from_token_and_callback( token=oauth_token, callback=callback_url, http_url=authorization_url, parameters=extra_params) return atom.url.parse_url(oauth_request.to_url()) def GenerateOAuthAccessTokenUrl( authorized_request_token, oauth_input_params, access_token_url='https://www.google.com/accounts/OAuthGetAccessToken', oauth_version='1.0', oauth_verifier=None): """Generates URL at which user will login to authorize the request token. Args: authorized_request_token: gdata.auth.OAuthToken OAuth authorized request token. oauth_input_params: OAuthInputParams OAuth input parameters. access_token_url: string The beginning of the authorization URL. This is normally 'https://www.google.com/accounts/OAuthGetAccessToken' or '/accounts/OAuthGetAccessToken' oauth_version: str (default='1.0') oauth_version parameter. oauth_verifier: str (optional) If present, it is assumed that the client will use the OAuth v1.0a protocol which includes passing the oauth_verifier (as returned by the SP) in the access token step. Returns: atom.url.Url OAuth access token URL. """ oauth_token = oauth.OAuthToken(authorized_request_token.key, authorized_request_token.secret) parameters = {'oauth_version': oauth_version} if oauth_verifier is not None: parameters['oauth_verifier'] = oauth_verifier oauth_request = oauth.OAuthRequest.from_consumer_and_token( oauth_input_params.GetConsumer(), token=oauth_token, http_url=access_token_url, parameters=parameters) oauth_request.sign_request(oauth_input_params.GetSignatureMethod(), oauth_input_params.GetConsumer(), oauth_token) return atom.url.parse_url(oauth_request.to_url()) def GenerateAuthSubUrl(next, scope, secure=False, session=True, request_url='https://www.google.com/accounts/AuthSubRequest', domain='default'): """Generate a URL at which the user will login and be redirected back. Users enter their credentials on a Google login page and a token is sent to the URL specified in next. See documentation for AuthSub login at: http://code.google.com/apis/accounts/AuthForWebApps.html Args: request_url: str The beginning of the request URL. This is normally 'http://www.google.com/accounts/AuthSubRequest' or '/accounts/AuthSubRequest' next: string The URL user will be sent to after logging in. scope: string The URL of the service to be accessed. secure: boolean (optional) Determines whether or not the issued token is a secure token. session: boolean (optional) Determines whether or not the issued token can be upgraded to a session token. domain: str (optional) The Google Apps domain for this account. If this is not a Google Apps account, use 'default' which is the default value. """ # Translate True/False values for parameters into numeric values acceoted # by the AuthSub service. if secure: secure = 1 else: secure = 0 if session: session = 1 else: session = 0 request_params = urllib.urlencode({'next': next, 'scope': scope, 'secure': secure, 'session': session, 'hd': domain}) if request_url.find('?') == -1: return '%s?%s' % (request_url, request_params) else: # The request URL already contained url parameters so we should add # the parameters using the & seperator return '%s&%s' % (request_url, request_params) def generate_auth_sub_url(next, scopes, secure=False, session=True, request_url='https://www.google.com/accounts/AuthSubRequest', domain='default', scopes_param_prefix='auth_sub_scopes'): """Constructs a URL string for requesting a multiscope AuthSub token. The generated token will contain a URL parameter to pass along the requested scopes to the next URL. When the Google Accounts page redirects the broswser to the 'next' URL, it appends the single use AuthSub token value to the URL as a URL parameter with the key 'token'. However, the information about which scopes were requested is not included by Google Accounts. This method adds the scopes to the next URL before making the request so that the redirect will be sent to a page, and both the token value and the list of scopes can be extracted from the request URL. Args: next: atom.url.URL or string The URL user will be sent to after authorizing this web application to access their data. scopes: list containint strings The URLs of the services to be accessed. secure: boolean (optional) Determines whether or not the issued token is a secure token. session: boolean (optional) Determines whether or not the issued token can be upgraded to a session token. request_url: atom.url.Url or str The beginning of the request URL. This is normally 'http://www.google.com/accounts/AuthSubRequest' or '/accounts/AuthSubRequest' domain: The domain which the account is part of. This is used for Google Apps accounts, the default value is 'default' which means that the requested account is a Google Account (@gmail.com for example) scopes_param_prefix: str (optional) The requested scopes are added as a URL parameter to the next URL so that the page at the 'next' URL can extract the token value and the valid scopes from the URL. The key for the URL parameter defaults to 'auth_sub_scopes' Returns: An atom.url.Url which the user's browser should be directed to in order to authorize this application to access their information. """ if isinstance(next, (str, unicode)): next = atom.url.parse_url(next) scopes_string = ' '.join([str(scope) for scope in scopes]) next.params[scopes_param_prefix] = scopes_string if isinstance(request_url, (str, unicode)): request_url = atom.url.parse_url(request_url) request_url.params['next'] = str(next) request_url.params['scope'] = scopes_string if session: request_url.params['session'] = 1 else: request_url.params['session'] = 0 if secure: request_url.params['secure'] = 1 else: request_url.params['secure'] = 0 request_url.params['hd'] = domain return request_url def AuthSubTokenFromUrl(url): """Extracts the AuthSub token from the URL. Used after the AuthSub redirect has sent the user to the 'next' page and appended the token to the URL. This function returns the value to be used in the Authorization header. Args: url: str The URL of the current page which contains the AuthSub token as a URL parameter. """ token = TokenFromUrl(url) if token: return 'AuthSub token=%s' % token return None def TokenFromUrl(url): """Extracts the AuthSub token from the URL. Returns the raw token value. Args: url: str The URL or the query portion of the URL string (after the ?) of the current page which contains the AuthSub token as a URL parameter. """ if url.find('?') > -1: query_params = url.split('?')[1] else: query_params = url for pair in query_params.split('&'): if pair.startswith('token='): return pair[6:] return None def extract_auth_sub_token_from_url(url, scopes_param_prefix='auth_sub_scopes', rsa_key=None): """Creates an AuthSubToken and sets the token value and scopes from the URL. After the Google Accounts AuthSub pages redirect the user's broswer back to the web application (using the 'next' URL from the request) the web app must extract the token from the current page's URL. The token is provided as a URL parameter named 'token' and if generate_auth_sub_url was used to create the request, the token's valid scopes are included in a URL parameter whose name is specified in scopes_param_prefix. Args: url: atom.url.Url or str representing the current URL. The token value and valid scopes should be included as URL parameters. scopes_param_prefix: str (optional) The URL parameter key which maps to the list of valid scopes for the token. Returns: An AuthSubToken with the token value from the URL and set to be valid for the scopes passed in on the URL. If no scopes were included in the URL, the AuthSubToken defaults to being valid for no scopes. If there was no 'token' parameter in the URL, this function returns None. """ if isinstance(url, (str, unicode)): url = atom.url.parse_url(url) if 'token' not in url.params: return None scopes = [] if scopes_param_prefix in url.params: scopes = url.params[scopes_param_prefix].split(' ') token_value = url.params['token'] if rsa_key: token = SecureAuthSubToken(rsa_key, scopes=scopes) else: token = AuthSubToken(scopes=scopes) token.set_token_string(token_value) return token def AuthSubTokenFromHttpBody(http_body): """Extracts the AuthSub token from an HTTP body string. Used to find the new session token after making a request to upgrade a single use AuthSub token. Args: http_body: str The repsonse from the server which contains the AuthSub key. For example, this function would find the new session token from the server's response to an upgrade token request. Returns: The header value to use for Authorization which contains the AuthSub token. """ token_value = token_from_http_body(http_body) if token_value: return '%s%s' % (AUTHSUB_AUTH_LABEL, token_value) return None def token_from_http_body(http_body): """Extracts the AuthSub token from an HTTP body string. Used to find the new session token after making a request to upgrade a single use AuthSub token. Args: http_body: str The repsonse from the server which contains the AuthSub key. For example, this function would find the new session token from the server's response to an upgrade token request. Returns: The raw token value to use in an AuthSubToken object. """ for response_line in http_body.splitlines(): if response_line.startswith('Token='): # Strip off Token= and return the token value string. return response_line[6:] return None TokenFromHttpBody = token_from_http_body def OAuthTokenFromUrl(url, scopes_param_prefix='oauth_token_scope'): """Creates an OAuthToken and sets token key and scopes (if present) from URL. After the Google Accounts OAuth pages redirect the user's broswer back to the web application (using the 'callback' URL from the request) the web app can extract the token from the current page's URL. The token is same as the request token, but it is either authorized (if user grants access) or unauthorized (if user denies access). The token is provided as a URL parameter named 'oauth_token' and if it was chosen to use GenerateOAuthAuthorizationUrl with include_scopes_in_param=True, the token's valid scopes are included in a URL parameter whose name is specified in scopes_param_prefix. Args: url: atom.url.Url or str representing the current URL. The token value and valid scopes should be included as URL parameters. scopes_param_prefix: str (optional) The URL parameter key which maps to the list of valid scopes for the token. Returns: An OAuthToken with the token key from the URL and set to be valid for the scopes passed in on the URL. If no scopes were included in the URL, the OAuthToken defaults to being valid for no scopes. If there was no 'oauth_token' parameter in the URL, this function returns None. """ if isinstance(url, (str, unicode)): url = atom.url.parse_url(url) if 'oauth_token' not in url.params: return None scopes = [] if scopes_param_prefix in url.params: scopes = url.params[scopes_param_prefix].split(' ') token_key = url.params['oauth_token'] token = OAuthToken(key=token_key, scopes=scopes) return token def OAuthTokenFromHttpBody(http_body): """Parses the HTTP response body and returns an OAuth token. The returned OAuth token will just have key and secret parameters set. It won't have any knowledge about the scopes or oauth_input_params. It is your responsibility to make it aware of the remaining parameters. Returns: OAuthToken OAuth token. """ token = oauth.OAuthToken.from_string(http_body) oauth_token = OAuthToken(key=token.key, secret=token.secret) return oauth_token class OAuthSignatureMethod(object): """Holds valid OAuth signature methods. RSA_SHA1: Class to build signature according to RSA-SHA1 algorithm. HMAC_SHA1: Class to build signature according to HMAC-SHA1 algorithm. """ HMAC_SHA1 = oauth.OAuthSignatureMethod_HMAC_SHA1 class RSA_SHA1(oauth_rsa.OAuthSignatureMethod_RSA_SHA1): """Provides implementation for abstract methods to return RSA certs.""" def __init__(self, private_key, public_cert): self.private_key = private_key self.public_cert = public_cert def _fetch_public_cert(self, unused_oauth_request): return self.public_cert def _fetch_private_cert(self, unused_oauth_request): return self.private_key class OAuthInputParams(object): """Stores OAuth input parameters. This class is a store for OAuth input parameters viz. consumer key and secret, signature method and RSA key. """ def __init__(self, signature_method, consumer_key, consumer_secret=None, rsa_key=None, requestor_id=None): """Initializes object with parameters required for using OAuth mechanism. NOTE: Though consumer_secret and rsa_key are optional, either of the two is required depending on the value of the signature_method. Args: signature_method: class which provides implementation for strategy class oauth.oauth.OAuthSignatureMethod. Signature method to be used for signing each request. Valid implementations are provided as the constants defined by gdata.auth.OAuthSignatureMethod. Currently they are gdata.auth.OAuthSignatureMethod.RSA_SHA1 and gdata.auth.OAuthSignatureMethod.HMAC_SHA1. Instead of passing in the strategy class, you may pass in a string for 'RSA_SHA1' or 'HMAC_SHA1'. If you plan to use OAuth on App Engine (or another WSGI environment) I recommend specifying signature method using a string (the only options are 'RSA_SHA1' and 'HMAC_SHA1'). In these environments there are sometimes issues with pickling an object in which a member references a class or function. Storing a string to refer to the signature method mitigates complications when pickling. consumer_key: string Domain identifying third_party web application. consumer_secret: string (optional) Secret generated during registration. Required only for HMAC_SHA1 signature method. rsa_key: string (optional) Private key required for RSA_SHA1 signature method. requestor_id: string (optional) User email adress to make requests on their behalf. This parameter should only be set when performing 2 legged OAuth requests. """ if (signature_method == OAuthSignatureMethod.RSA_SHA1 or signature_method == 'RSA_SHA1'): self.__signature_strategy = 'RSA_SHA1' elif (signature_method == OAuthSignatureMethod.HMAC_SHA1 or signature_method == 'HMAC_SHA1'): self.__signature_strategy = 'HMAC_SHA1' else: self.__signature_strategy = signature_method self.rsa_key = rsa_key self._consumer = oauth.OAuthConsumer(consumer_key, consumer_secret) self.requestor_id = requestor_id def __get_signature_method(self): if self.__signature_strategy == 'RSA_SHA1': return OAuthSignatureMethod.RSA_SHA1(self.rsa_key, None) elif self.__signature_strategy == 'HMAC_SHA1': return OAuthSignatureMethod.HMAC_SHA1() else: return self.__signature_strategy() def __set_signature_method(self, signature_method): if (signature_method == OAuthSignatureMethod.RSA_SHA1 or signature_method == 'RSA_SHA1'): self.__signature_strategy = 'RSA_SHA1' elif (signature_method == OAuthSignatureMethod.HMAC_SHA1 or signature_method == 'HMAC_SHA1'): self.__signature_strategy = 'HMAC_SHA1' else: self.__signature_strategy = signature_method _signature_method = property(__get_signature_method, __set_signature_method, doc="""Returns object capable of signing the request using RSA of HMAC. Replaces the _signature_method member to avoid pickle errors.""") def GetSignatureMethod(self): """Gets the OAuth signature method. Returns: object of supertype """ return self._signature_method def GetConsumer(self): """Gets the OAuth consumer. Returns: object of type """ return self._consumer class ClientLoginToken(atom.http_interface.GenericToken): """Stores the Authorization header in auth_header and adds to requests. This token will add it's Authorization header to an HTTP request as it is made. Ths token class is simple but some Token classes must calculate portions of the Authorization header based on the request being made, which is why the token is responsible for making requests via an http_client parameter. Args: auth_header: str The value for the Authorization header. scopes: list of str or atom.url.Url specifying the beginnings of URLs for which this token can be used. For example, if scopes contains 'http://example.com/foo', then this token can be used for a request to 'http://example.com/foo/bar' but it cannot be used for a request to 'http://example.com/baz' """ def __init__(self, auth_header=None, scopes=None): self.auth_header = auth_header self.scopes = scopes or [] def __str__(self): return self.auth_header def perform_request(self, http_client, operation, url, data=None, headers=None): """Sets the Authorization header and makes the HTTP request.""" if headers is None: headers = {'Authorization':self.auth_header} else: headers['Authorization'] = self.auth_header return http_client.request(operation, url, data=data, headers=headers) def get_token_string(self): """Removes PROGRAMMATIC_AUTH_LABEL to give just the token value.""" return self.auth_header[len(PROGRAMMATIC_AUTH_LABEL):] def set_token_string(self, token_string): self.auth_header = '%s%s' % (PROGRAMMATIC_AUTH_LABEL, token_string) def valid_for_scope(self, url): """Tells the caller if the token authorizes access to the desired URL. """ if isinstance(url, (str, unicode)): url = atom.url.parse_url(url) for scope in self.scopes: if scope == atom.token_store.SCOPE_ALL: return True if isinstance(scope, (str, unicode)): scope = atom.url.parse_url(scope) if scope == url: return True # Check the host and the path, but ignore the port and protocol. elif scope.host == url.host and not scope.path: return True elif scope.host == url.host and scope.path and not url.path: continue elif scope.host == url.host and url.path.startswith(scope.path): return True return False class AuthSubToken(ClientLoginToken): def get_token_string(self): """Removes AUTHSUB_AUTH_LABEL to give just the token value.""" return self.auth_header[len(AUTHSUB_AUTH_LABEL):] def set_token_string(self, token_string): self.auth_header = '%s%s' % (AUTHSUB_AUTH_LABEL, token_string) class OAuthToken(atom.http_interface.GenericToken): """Stores the token key, token secret and scopes for which token is valid. This token adds the authorization header to each request made. It re-calculates authorization header for every request since the OAuth signature to be added to the authorization header is dependent on the request parameters. Attributes: key: str The value for the OAuth token i.e. token key. secret: str The value for the OAuth token secret. scopes: list of str or atom.url.Url specifying the beginnings of URLs for which this token can be used. For example, if scopes contains 'http://example.com/foo', then this token can be used for a request to 'http://example.com/foo/bar' but it cannot be used for a request to 'http://example.com/baz' oauth_input_params: OAuthInputParams OAuth input parameters. """ def __init__(self, key=None, secret=None, scopes=None, oauth_input_params=None): self.key = key self.secret = secret self.scopes = scopes or [] self.oauth_input_params = oauth_input_params def __str__(self): return self.get_token_string() def get_token_string(self): """Returns the token string. The token string returned is of format oauth_token=[0]&oauth_token_secret=[1], where [0] and [1] are some strings. Returns: A token string of format oauth_token=[0]&oauth_token_secret=[1], where [0] and [1] are some strings. If self.secret is absent, it just returns oauth_token=[0]. If self.key is absent, it just returns oauth_token_secret=[1]. If both are absent, it returns None. """ if self.key and self.secret: return urllib.urlencode({'oauth_token': self.key, 'oauth_token_secret': self.secret}) elif self.key: return 'oauth_token=%s' % self.key elif self.secret: return 'oauth_token_secret=%s' % self.secret else: return None def set_token_string(self, token_string): """Sets the token key and secret from the token string. Args: token_string: str Token string of form oauth_token=[0]&oauth_token_secret=[1]. If oauth_token is not present, self.key will be None. If oauth_token_secret is not present, self.secret will be None. """ token_params = cgi.parse_qs(token_string, keep_blank_values=False) if 'oauth_token' in token_params: self.key = token_params['oauth_token'][0] if 'oauth_token_secret' in token_params: self.secret = token_params['oauth_token_secret'][0] def GetAuthHeader(self, http_method, http_url, realm=''): """Get the authentication header. Args: http_method: string HTTP method i.e. operation e.g. GET, POST, PUT, etc. http_url: string or atom.url.Url HTTP URL to which request is made. realm: string (default='') realm parameter to be included in the authorization header. Returns: dict Header to be sent with every subsequent request after authentication. """ if isinstance(http_url, types.StringTypes): http_url = atom.url.parse_url(http_url) header = None token = None if self.key or self.secret: token = oauth.OAuthToken(self.key, self.secret) oauth_request = oauth.OAuthRequest.from_consumer_and_token( self.oauth_input_params.GetConsumer(), token=token, http_url=str(http_url), http_method=http_method, parameters=http_url.params) oauth_request.sign_request(self.oauth_input_params.GetSignatureMethod(), self.oauth_input_params.GetConsumer(), token) header = oauth_request.to_header(realm=realm) header['Authorization'] = header['Authorization'].replace('+', '%2B') return header def perform_request(self, http_client, operation, url, data=None, headers=None): """Sets the Authorization header and makes the HTTP request.""" if not headers: headers = {} if self.oauth_input_params.requestor_id: url.params['xoauth_requestor_id'] = self.oauth_input_params.requestor_id headers.update(self.GetAuthHeader(operation, url)) return http_client.request(operation, url, data=data, headers=headers) def valid_for_scope(self, url): if isinstance(url, (str, unicode)): url = atom.url.parse_url(url) for scope in self.scopes: if scope == atom.token_store.SCOPE_ALL: return True if isinstance(scope, (str, unicode)): scope = atom.url.parse_url(scope) if scope == url: return True # Check the host and the path, but ignore the port and protocol. elif scope.host == url.host and not scope.path: return True elif scope.host == url.host and scope.path and not url.path: continue elif scope.host == url.host and url.path.startswith(scope.path): return True return False class SecureAuthSubToken(AuthSubToken): """Stores the rsa private key, token, and scopes for the secure AuthSub token. This token adds the authorization header to each request made. It re-calculates authorization header for every request since the secure AuthSub signature to be added to the authorization header is dependent on the request parameters. Attributes: rsa_key: string The RSA private key in PEM format that the token will use to sign requests token_string: string (optional) The value for the AuthSub token. scopes: list of str or atom.url.Url specifying the beginnings of URLs for which this token can be used. For example, if scopes contains 'http://example.com/foo', then this token can be used for a request to 'http://example.com/foo/bar' but it cannot be used for a request to 'http://example.com/baz' """ def __init__(self, rsa_key, token_string=None, scopes=None): self.rsa_key = keyfactory.parsePEMKey(rsa_key) self.token_string = token_string or '' self.scopes = scopes or [] def __str__(self): return self.get_token_string() def get_token_string(self): return str(self.token_string) def set_token_string(self, token_string): self.token_string = token_string def GetAuthHeader(self, http_method, http_url): """Generates the Authorization header. The form of the secure AuthSub Authorization header is Authorization: AuthSub token="token" sigalg="sigalg" data="data" sig="sig" and data represents a string in the form data = http_method http_url timestamp nonce Args: http_method: string HTTP method i.e. operation e.g. GET, POST, PUT, etc. http_url: string or atom.url.Url HTTP URL to which request is made. Returns: dict Header to be sent with every subsequent request after authentication. """ timestamp = int(math.floor(time.time())) nonce = '%lu' % random.randrange(1, 2**64) data = '%s %s %d %s' % (http_method, str(http_url), timestamp, nonce) sig = cryptomath.bytesToBase64(self.rsa_key.hashAndSign(data)) header = {'Authorization': '%s"%s" data="%s" sig="%s" sigalg="rsa-sha1"' % (AUTHSUB_AUTH_LABEL, self.token_string, data, sig)} return header def perform_request(self, http_client, operation, url, data=None, headers=None): """Sets the Authorization header and makes the HTTP request.""" if not headers: headers = {} headers.update(self.GetAuthHeader(operation, url)) return http_client.request(operation, url, data=data, headers=headers) gdata-2.0.18/samples/apps/marketplace_sample/gdata/apps/0000750036466300116100000000000012156625015023215 5ustar afshareng00000000000000gdata-2.0.18/samples/apps/marketplace_sample/gdata/apps/migration/0000750036466300116100000000000012156625015025206 5ustar afshareng00000000000000gdata-2.0.18/samples/apps/marketplace_sample/gdata/apps/migration/__init__.py0000640036466300116100000002060612156622362027326 0ustar afshareng00000000000000#!/usr/bin/python2.4 # # Copyright 2008 Google Inc. All Rights Reserved. # # 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. """Contains objects used with Google Apps.""" __author__ = 'google-apps-apis@googlegroups.com' import atom import gdata # XML namespaces which are often used in Google Apps entity. APPS_NAMESPACE = 'http://schemas.google.com/apps/2006' APPS_TEMPLATE = '{http://schemas.google.com/apps/2006}%s' class Rfc822Msg(atom.AtomBase): """The Migration rfc822Msg element.""" _tag = 'rfc822Msg' _namespace = APPS_NAMESPACE _children = atom.AtomBase._children.copy() _attributes = atom.AtomBase._attributes.copy() _attributes['encoding'] = 'encoding' def __init__(self, extension_elements=None, extension_attributes=None, text=None): self.text = text self.encoding = 'base64' self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} def Rfc822MsgFromString(xml_string): """Parse in the Rrc822 message from the XML definition.""" return atom.CreateClassFromXMLString(Rfc822Msg, xml_string) class MailItemProperty(atom.AtomBase): """The Migration mailItemProperty element.""" _tag = 'mailItemProperty' _namespace = APPS_NAMESPACE _children = atom.AtomBase._children.copy() _attributes = atom.AtomBase._attributes.copy() _attributes['value'] = 'value' def __init__(self, value=None, extension_elements=None, extension_attributes=None, text=None): self.value = value self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} def MailItemPropertyFromString(xml_string): """Parse in the MailItemProperiy from the XML definition.""" return atom.CreateClassFromXMLString(MailItemProperty, xml_string) class Label(atom.AtomBase): """The Migration label element.""" _tag = 'label' _namespace = APPS_NAMESPACE _children = atom.AtomBase._children.copy() _attributes = atom.AtomBase._attributes.copy() _attributes['labelName'] = 'label_name' def __init__(self, label_name=None, extension_elements=None, extension_attributes=None, text=None): self.label_name = label_name self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} def LabelFromString(xml_string): """Parse in the mailItemProperty from the XML definition.""" return atom.CreateClassFromXMLString(Label, xml_string) class MailEntry(gdata.GDataEntry): """A Google Migration flavor of an Atom Entry.""" _tag = 'entry' _namespace = atom.ATOM_NAMESPACE _children = gdata.GDataEntry._children.copy() _attributes = gdata.GDataEntry._attributes.copy() _children['{%s}rfc822Msg' % APPS_NAMESPACE] = ('rfc822_msg', Rfc822Msg) _children['{%s}mailItemProperty' % APPS_NAMESPACE] = ('mail_item_property', [MailItemProperty]) _children['{%s}label' % APPS_NAMESPACE] = ('label', [Label]) def __init__(self, author=None, category=None, content=None, atom_id=None, link=None, published=None, title=None, updated=None, rfc822_msg=None, mail_item_property=None, label=None, extended_property=None, extension_elements=None, extension_attributes=None, text=None): gdata.GDataEntry.__init__(self, author=author, category=category, content=content, atom_id=atom_id, link=link, published=published, title=title, updated=updated) self.rfc822_msg = rfc822_msg self.mail_item_property = mail_item_property self.label = label self.extended_property = extended_property or [] self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} def MailEntryFromString(xml_string): """Parse in the MailEntry from the XML definition.""" return atom.CreateClassFromXMLString(MailEntry, xml_string) class BatchMailEntry(gdata.BatchEntry): """A Google Migration flavor of an Atom Entry.""" _tag = gdata.BatchEntry._tag _namespace = gdata.BatchEntry._namespace _children = gdata.BatchEntry._children.copy() _attributes = gdata.BatchEntry._attributes.copy() _children['{%s}rfc822Msg' % APPS_NAMESPACE] = ('rfc822_msg', Rfc822Msg) _children['{%s}mailItemProperty' % APPS_NAMESPACE] = ('mail_item_property', [MailItemProperty]) _children['{%s}label' % APPS_NAMESPACE] = ('label', [Label]) def __init__(self, author=None, category=None, content=None, atom_id=None, link=None, published=None, title=None, updated=None, rfc822_msg=None, mail_item_property=None, label=None, batch_operation=None, batch_id=None, batch_status=None, extended_property=None, extension_elements=None, extension_attributes=None, text=None): gdata.BatchEntry.__init__(self, author=author, category=category, content=content, atom_id=atom_id, link=link, published=published, batch_operation=batch_operation, batch_id=batch_id, batch_status=batch_status, title=title, updated=updated) self.rfc822_msg = rfc822_msg or None self.mail_item_property = mail_item_property or [] self.label = label or [] self.extended_property = extended_property or [] self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} def BatchMailEntryFromString(xml_string): """Parse in the BatchMailEntry from the XML definition.""" return atom.CreateClassFromXMLString(BatchMailEntry, xml_string) class BatchMailEventFeed(gdata.BatchFeed): """A Migration event feed flavor of an Atom Feed.""" _tag = gdata.BatchFeed._tag _namespace = gdata.BatchFeed._namespace _children = gdata.BatchFeed._children.copy() _attributes = gdata.BatchFeed._attributes.copy() _children['{%s}entry' % atom.ATOM_NAMESPACE] = ('entry', [BatchMailEntry]) def __init__(self, author=None, category=None, contributor=None, generator=None, icon=None, atom_id=None, link=None, logo=None, rights=None, subtitle=None, title=None, updated=None, entry=None, total_results=None, start_index=None, items_per_page=None, interrupted=None, extension_elements=None, extension_attributes=None, text=None): gdata.BatchFeed.__init__(self, author=author, category=category, contributor=contributor, generator=generator, icon=icon, atom_id=atom_id, link=link, logo=logo, rights=rights, subtitle=subtitle, title=title, updated=updated, entry=entry, total_results=total_results, start_index=start_index, items_per_page=items_per_page, interrupted=interrupted, extension_elements=extension_elements, extension_attributes=extension_attributes, text=text) class MailEntryProperties(object): """Represents a mail message and its attributes.""" def __init__(self, mail_message=None, mail_item_properties=None, mail_labels=None, identifier=None): self.mail_message = mail_message self.mail_item_properties = mail_item_properties or [] self.mail_labels = mail_labels or [] self.identifier = identifier def BatchMailEventFeedFromString(xml_string): """Parse in the BatchMailEventFeed from the XML definition.""" return atom.CreateClassFromXMLString(BatchMailEventFeed, xml_string) gdata-2.0.18/samples/apps/marketplace_sample/gdata/apps/migration/service.py0000640036466300116100000001704212156622362027227 0ustar afshareng00000000000000#!/usr/bin/python2.4 # # Copyright 2008 Google Inc. All Rights Reserved. # # 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. """Contains the methods to import mail via Google Apps Email Migration API. MigrationService: Provides methods to import mail. """ __author__ = ('google-apps-apis@googlegroups.com', 'pti@google.com (Prashant Tiwari)') import base64 import threading import time from atom.service import deprecation from gdata.apps import migration from gdata.apps.migration import MailEntryProperties import gdata.apps.service import gdata.service API_VER = '2.0' class MigrationService(gdata.apps.service.AppsService): """Client for the EMAPI migration service. Use either ImportMail to import one message at a time, or AddMailEntry and ImportMultipleMails to import a bunch of messages at a time. """ def __init__(self, email=None, password=None, domain=None, source=None, server='apps-apis.google.com', additional_headers=None): gdata.apps.service.AppsService.__init__( self, email=email, password=password, domain=domain, source=source, server=server, additional_headers=additional_headers) self.mail_batch = migration.BatchMailEventFeed() self.mail_entries = [] self.exceptions = 0 def _BaseURL(self): return '/a/feeds/migration/%s/%s' % (API_VER, self.domain) def ImportMail(self, user_name, mail_message, mail_item_properties, mail_labels): """Imports a single mail message. Args: user_name: The username to import messages to. mail_message: An RFC822 format email message. mail_item_properties: A list of Gmail properties to apply to the message. mail_labels: A list of labels to apply to the message. Returns: A MailEntry representing the successfully imported message. Raises: AppsForYourDomainException: An error occurred importing the message. """ uri = '%s/%s/mail' % (self._BaseURL(), user_name) mail_entry = migration.MailEntry() mail_entry.rfc822_msg = migration.Rfc822Msg(text=(base64.b64encode( mail_message))) mail_entry.rfc822_msg.encoding = 'base64' mail_entry.mail_item_property = map( lambda x: migration.MailItemProperty(value=x), mail_item_properties) mail_entry.label = map(lambda x: migration.Label(label_name=x), mail_labels) try: return migration.MailEntryFromString(str(self.Post(mail_entry, uri))) except gdata.service.RequestError, e: # Store the number of failed imports when importing several at a time self.exceptions += 1 raise gdata.apps.service.AppsForYourDomainException(e.args[0]) def AddBatchEntry(self, mail_message, mail_item_properties, mail_labels): """Adds a message to the current batch that you later will submit. Deprecated, use AddMailEntry instead Args: mail_message: An RFC822 format email message. mail_item_properties: A list of Gmail properties to apply to the message. mail_labels: A list of labels to apply to the message. Returns: The length of the MailEntry representing the message. """ deprecation("calling deprecated method AddBatchEntry") mail_entry = migration.BatchMailEntry() mail_entry.rfc822_msg = migration.Rfc822Msg(text=(base64.b64encode( mail_message))) mail_entry.rfc822_msg.encoding = 'base64' mail_entry.mail_item_property = map( lambda x: migration.MailItemProperty(value=x), mail_item_properties) mail_entry.label = map(lambda x: migration.Label(label_name=x), mail_labels) self.mail_batch.AddBatchEntry(mail_entry) return len(str(mail_entry)) def SubmitBatch(self, user_name): """Sends all the mail items you have added to the batch to the server. Deprecated, use ImportMultipleMails instead Args: user_name: The username to import messages to. Returns: An HTTPResponse from the web service call. Raises: AppsForYourDomainException: An error occurred importing the batch. """ deprecation("calling deprecated method SubmitBatch") uri = '%s/%s/mail/batch' % (self._BaseURL(), user_name) try: self.result = self.Post(self.mail_batch, uri, converter=migration.BatchMailEventFeedFromString) except gdata.service.RequestError, e: raise gdata.apps.service.AppsForYourDomainException(e.args[0]) self.mail_batch = migration.BatchMailEventFeed() return self.result def AddMailEntry(self, mail_message, mail_item_properties=None, mail_labels=None, identifier=None): """Prepares a list of mail messages to import using ImportMultipleMails. Args: mail_message: An RFC822 format email message as a string. mail_item_properties: List of Gmail properties to apply to the message. mail_labels: List of Gmail labels to apply to the message. identifier: The optional file identifier string Returns: The number of email messages to be imported. """ mail_entry_properties = MailEntryProperties( mail_message=mail_message, mail_item_properties=mail_item_properties, mail_labels=mail_labels, identifier=identifier) self.mail_entries.append(mail_entry_properties) return len(self.mail_entries) def ImportMultipleMails(self, user_name, threads_per_batch=20): """Launches separate threads to import every message added by AddMailEntry. Args: user_name: The user account name to import messages to. threads_per_batch: Number of messages to import at a time. Returns: The number of email messages that were successfully migrated. Raises: Exception: An error occurred while importing mails. """ num_entries = len(self.mail_entries) if not num_entries: return 0 threads = [] for mail_entry_properties in self.mail_entries: t = threading.Thread(name=mail_entry_properties.identifier, target=self.ImportMail, args=(user_name, mail_entry_properties.mail_message, mail_entry_properties.mail_item_properties, mail_entry_properties.mail_labels)) threads.append(t) try: # Determine the number of batches needed with threads_per_batch in each batches = num_entries / threads_per_batch + ( 0 if num_entries % threads_per_batch == 0 else 1) batch_min = 0 # Start the threads, one batch at a time for batch in range(batches): batch_max = ((batch + 1) * threads_per_batch if (batch + 1) * threads_per_batch < num_entries else num_entries) for i in range(batch_min, batch_max): threads[i].start() time.sleep(1) for i in range(batch_min, batch_max): threads[i].join() batch_min = batch_max self.mail_entries = [] except Exception, e: raise Exception(e.args[0]) else: return num_entries - self.exceptions gdata-2.0.18/samples/apps/marketplace_sample/gdata/apps/organization/0000750036466300116100000000000012156625015025721 5ustar afshareng00000000000000gdata-2.0.18/samples/apps/marketplace_sample/gdata/apps/organization/data.py0000640036466300116100000002763212156622362027221 0ustar afshareng00000000000000#!/usr/bin/python2.4 # # Copyright 2011 Google Inc. All Rights Reserved. # # 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. """Data model classes for the Organization Unit Provisioning API.""" __author__ = 'Gunjan Sharma ' import gdata.apps import gdata.apps.apps_property_entry import gdata.apps_property import gdata.data # This is required to work around a naming conflict between the Google # Spreadsheets API and Python's built-in property function pyproperty = property # The apps:property name of an organization unit ORG_UNIT_NAME = 'name' # The apps:property orgUnitPath of an organization unit ORG_UNIT_PATH = 'orgUnitPath' # The apps:property parentOrgUnitPath of an organization unit PARENT_ORG_UNIT_PATH = 'parentOrgUnitPath' # The apps:property description of an organization unit ORG_UNIT_DESCRIPTION = 'description' # The apps:property blockInheritance of an organization unit ORG_UNIT_BLOCK_INHERITANCE = 'blockInheritance' # The apps:property userEmail of a user entry USER_EMAIL = 'orgUserEmail' # The apps:property list of users to move USERS_TO_MOVE = 'usersToMove' # The apps:property list of moved users MOVED_USERS = 'usersMoved' # The apps:property customerId for the domain CUSTOMER_ID = 'customerId' # The apps:property name of the customer org unit CUSTOMER_ORG_UNIT_NAME = 'customerOrgUnitName' # The apps:property description of the customer org unit CUSTOMER_ORG_UNIT_DESCRIPTION = 'customerOrgUnitDescription' # The apps:property old organization unit's path for a user OLD_ORG_UNIT_PATH = 'oldOrgUnitPath' class CustomerIdEntry(gdata.apps.apps_property_entry.AppsPropertyEntry): """Represents a customerId entry in object form.""" def GetCustomerId(self): """Get the customer ID of the customerId object. Returns: The customer ID of this customerId object as a string or None. """ return self._GetProperty(CUSTOMER_ID) customer_id = pyproperty(GetCustomerId) def GetOrgUnitName(self): """Get the Organization Unit name of the customerId object. Returns: The Organization unit name of this customerId object as a string or None. """ return self._GetProperty(ORG_UNIT_NAME) org_unit_name = pyproperty(GetOrgUnitName) def GetCustomerOrgUnitName(self): """Get the Customer Organization Unit name of the customerId object. Returns: The Customer Organization unit name of this customerId object as a string or None. """ return self._GetProperty(CUSTOMER_ORG_UNIT_NAME) customer_org_unit_name = pyproperty(GetCustomerOrgUnitName) def GetOrgUnitDescription(self): """Get the Organization Unit Description of the customerId object. Returns: The Organization Unit Description of this customerId object as a string or None. """ return self._GetProperty(ORG_UNIT_DESCRIPTION) org_unit_description = pyproperty(GetOrgUnitDescription) def GetCustomerOrgUnitDescription(self): """Get the Customer Organization Unit Description of the customerId object. Returns: The Customer Organization Unit Description of this customerId object as a string or None. """ return self._GetProperty(CUSTOMER_ORG_UNIT_DESCRIPTION) customer_org_unit_description = pyproperty(GetCustomerOrgUnitDescription) class OrgUnitEntry(gdata.apps.apps_property_entry.AppsPropertyEntry): """Represents an OrganizationUnit in object form.""" def GetOrgUnitName(self): """Get the Organization Unit name of the OrganizationUnit object. Returns: The Organization unit name of this OrganizationUnit object as a string or None. """ return self._GetProperty(ORG_UNIT_NAME) def SetOrgUnitName(self, value): """Set the Organization Unit name of the OrganizationUnit object. Args: value: [string] The new Organization Unit name to give this object. """ self._SetProperty(ORG_UNIT_NAME, value) org_unit_name = pyproperty(GetOrgUnitName, SetOrgUnitName) def GetOrgUnitPath(self): """Get the Organization Unit Path of the OrganizationUnit object. Returns: The Organization Unit Path of this OrganizationUnit object as a string or None. """ return self._GetProperty(ORG_UNIT_PATH) def SetOrgUnitPath(self, value): """Set the Organization Unit path of the OrganizationUnit object. Args: value: [string] The new Organization Unit path to give this object. """ self._SetProperty(ORG_UNIT_PATH, value) org_unit_path = pyproperty(GetOrgUnitPath, SetOrgUnitPath) def GetParentOrgUnitPath(self): """Get the Parent Organization Unit Path of the OrganizationUnit object. Returns: The Parent Organization Unit Path of this OrganizationUnit object as a string or None. """ return self._GetProperty(PARENT_ORG_UNIT_PATH) def SetParentOrgUnitPath(self, value): """Set the Parent Organization Unit path of the OrganizationUnit object. Args: value: [string] The new Parent Organization Unit path to give this object. """ self._SetProperty(PARENT_ORG_UNIT_PATH, value) parent_org_unit_path = pyproperty(GetParentOrgUnitPath, SetParentOrgUnitPath) def GetOrgUnitDescription(self): """Get the Organization Unit Description of the OrganizationUnit object. Returns: The Organization Unit Description of this OrganizationUnit object as a string or None. """ return self._GetProperty(ORG_UNIT_DESCRIPTION) def SetOrgUnitDescription(self, value): """Set the Organization Unit Description of the OrganizationUnit object. Args: value: [string] The new Organization Unit Description to give this object. """ self._SetProperty(ORG_UNIT_DESCRIPTION, value) org_unit_description = pyproperty(GetOrgUnitDescription, SetOrgUnitDescription) def GetOrgUnitBlockInheritance(self): """Get the block_inheritance flag of the OrganizationUnit object. Returns: The the block_inheritance flag of this OrganizationUnit object as a string or None. """ return self._GetProperty(ORG_UNIT_BLOCK_INHERITANCE) def SetOrgUnitBlockInheritance(self, value): """Set the block_inheritance flag of the OrganizationUnit object. Args: value: [string] The new block_inheritance flag to give this object. """ self._SetProperty(ORG_UNIT_BLOCK_INHERITANCE, value) org_unit_block_inheritance = pyproperty(GetOrgUnitBlockInheritance, SetOrgUnitBlockInheritance) def GetMovedUsers(self): """Get the moved users of the OrganizationUnit object. Returns: The the moved users of this OrganizationUnit object as a string or None. """ return self._GetProperty(MOVED_USERS) def SetUsersToMove(self, value): """Set the Users to Move of the OrganizationUnit object. Args: value: [string] The comma seperated list of users to move to give this object. """ self._SetProperty(USERS_TO_MOVE, value) move_users = pyproperty(GetMovedUsers, SetUsersToMove) def __init__( self, org_unit_name=None, org_unit_path=None, parent_org_unit_path=None, org_unit_description=None, org_unit_block_inheritance=None, move_users=None, *args, **kwargs): """Constructs a new OrganizationUnit object with the given arguments. Args: org_unit_name: string (optional) The organization unit name for the object. org_unit_path: string (optional) The organization unit path for the object. parent_org_unit_path: string (optional) The parent organization unit path for the object. org_unit_description: string (optional) The organization unit description for the object. org_unit_block_inheritance: boolean (optional) weather or not inheritance from the organization unit is blocked. move_users: string (optional) comma seperated list of users to move. args: The other parameters to pass to gdata.entry.GDEntry constructor. kwargs: The other parameters to pass to gdata.entry.GDEntry constructor. """ super(OrgUnitEntry, self).__init__(*args, **kwargs) if org_unit_name: self.org_unit_name = org_unit_name if org_unit_path: self.org_unit_path = org_unit_path if parent_org_unit_path: self.parent_org_unit_path = parent_org_unit_path if org_unit_description: self.org_unit_description = org_unit_description if org_unit_block_inheritance is not None: self.org_unit_block_inheritance = str(org_unit_block_inheritance) if move_users: self.move_users = move_users class OrgUnitFeed(gdata.data.GDFeed): """Represents a feed of OrgUnitEntry objects.""" # Override entry so that this feed knows how to type its list of entries. entry = [OrgUnitEntry] class OrgUserEntry(gdata.apps.apps_property_entry.AppsPropertyEntry): """Represents an OrgUser in object form.""" def GetUserEmail(self): """Get the user email address of the OrgUser object. Returns: The user email address of this OrgUser object as a string or None. """ return self._GetProperty(USER_EMAIL) def SetUserEmail(self, value): """Set the user email address of this OrgUser object. Args: value: string The new user email address to give this object. """ self._SetProperty(USER_EMAIL, value) user_email = pyproperty(GetUserEmail, SetUserEmail) def GetOrgUnitPath(self): """Get the Organization Unit Path of the OrgUser object. Returns: The Organization Unit Path of this OrgUser object as a string or None. """ return self._GetProperty(ORG_UNIT_PATH) def SetOrgUnitPath(self, value): """Set the Organization Unit path of the OrgUser object. Args: value: [string] The new Organization Unit path to give this object. """ self._SetProperty(ORG_UNIT_PATH, value) org_unit_path = pyproperty(GetOrgUnitPath, SetOrgUnitPath) def GetOldOrgUnitPath(self): """Get the Old Organization Unit Path of the OrgUser object. Returns: The Old Organization Unit Path of this OrgUser object as a string or None. """ return self._GetProperty(OLD_ORG_UNIT_PATH) def SetOldOrgUnitPath(self, value): """Set the Old Organization Unit path of the OrgUser object. Args: value: [string] The new Old Organization Unit path to give this object. """ self._SetProperty(OLD_ORG_UNIT_PATH, value) old_org_unit_path = pyproperty(GetOldOrgUnitPath, SetOldOrgUnitPath) def __init__( self, user_email=None, org_unit_path=None, old_org_unit_path=None, *args, **kwargs): """Constructs a new OrgUser object with the given arguments. Args: user_email: string (optional) The user email address for the object. org_unit_path: string (optional) The organization unit path for the object. old_org_unit_path: string (optional) The old organization unit path for the object. args: The other parameters to pass to gdata.entry.GDEntry constructor. kwargs: The other parameters to pass to gdata.entry.GDEntry constructor. """ super(OrgUserEntry, self).__init__(*args, **kwargs) if user_email: self.user_email = user_email if org_unit_path: self.org_unit_path = org_unit_path if old_org_unit_path: self.old_org_unit_path = old_org_unit_path class OrgUserFeed(gdata.data.GDFeed): """Represents a feed of OrgUserEntry objects.""" # Override entry so that this feed knows how to type its list of entries. entry = [OrgUserEntry] gdata-2.0.18/samples/apps/marketplace_sample/gdata/apps/organization/__init__.py0000640036466300116100000000000012156622362030023 0ustar afshareng00000000000000gdata-2.0.18/samples/apps/marketplace_sample/gdata/apps/organization/client.py0000640036466300116100000004717612156622362027573 0ustar afshareng00000000000000#!/usr/bin/python2.4 # # Copyright 2011 Google Inc. All Rights Reserved. # # 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. """OrganizationUnitProvisioningClient simplifies OrgUnit Provisioning API calls. OrganizationUnitProvisioningClient extends gdata.client.GDClient to ease interaction with the Google Organization Unit Provisioning API. These interactions include the ability to create, retrieve, update and delete organization units, move users within organization units, retrieve customerId and update and retrieve users in organization units. """ __author__ = 'Gunjan Sharma ' import urllib import gdata.apps.organization.data import gdata.client CUSTOMER_ID_URI_TEMPLATE = '/a/feeds/customer/%s/customerId' # OrganizationUnit URI templates # The strings in this template are eventually replaced with the feed type # (orgunit/orguser), API version and Google Apps domain name, respectively. ORGANIZATION_UNIT_URI_TEMPLATE = '/a/feeds/%s/%s/%s' # The value for orgunit requests ORGANIZATION_UNIT_FEED = 'orgunit' # The value for orguser requests ORGANIZATION_USER_FEED = 'orguser' class OrganizationUnitProvisioningClient(gdata.client.GDClient): """Client extension for the Google Org Unit Provisioning API service. Attributes: host: string The hostname for the MultiDomain Provisioning API service. api_version: string The version of the MultiDomain Provisioning API. """ host = 'apps-apis.google.com' api_version = '2.0' auth_service = 'apps' auth_scopes = gdata.gauth.AUTH_SCOPES['apps'] ssl = True def __init__(self, domain, auth_token=None, **kwargs): """Constructs a new client for the Organization Unit Provisioning API. Args: domain: string The Google Apps domain with Organization Unit Provisioning. auth_token: (optional) gdata.gauth.ClientLoginToken, AuthSubToken, or OAuthToken which authorizes this client to edit the Organization Units. """ gdata.client.GDClient.__init__(self, auth_token=auth_token, **kwargs) self.domain = domain def make_organization_unit_provisioning_uri( self, feed_type, customer_id, org_unit_path_or_user_email=None, params=None): """Creates a resource feed URI for the Organization Unit Provisioning API. Using this client's Google Apps domain, create a feed URI for organization unit provisioning in that domain. If an org unit path or org user email address is provided, return a URI for that specific resource. If params are provided, append them as GET params. Args: feed_type: string The type of feed (orgunit/orguser) customer_id: string The customerId of the user. org_unit_path_or_user_email: string (optional) The org unit path or org user email address for which to make a feed URI. params: dict (optional) key -> value params to append as GET vars to the URI. Example: params={'start': 'my-resource-id'} Returns: A string giving the URI for organization unit provisioning for this client's Google Apps domain. """ uri = ORGANIZATION_UNIT_URI_TEMPLATE % (feed_type, self.api_version, customer_id) if org_unit_path_or_user_email: uri += '/' + org_unit_path_or_user_email if params: uri += '?' + urllib.urlencode(params) return uri MakeOrganizationUnitProvisioningUri = make_organization_unit_provisioning_uri def make_organization_unit_orgunit_provisioning_uri(self, customer_id, org_unit_path=None, params=None): """Creates a resource feed URI for the orgunit's Provisioning API calls. Using this client's Google Apps domain, create a feed URI for organization unit orgunit's provisioning in that domain. If an org_unit_path is provided, return a URI for that specific resource. If params are provided, append them as GET params. Args: customer_id: string The customerId of the user. org_unit_path: string (optional) The organization unit's path for which to make a feed URI. params: dict (optional) key -> value params to append as GET vars to the URI. Example: params={'start': 'my-resource-id'} Returns: A string giving the URI for organization unit provisioning for given org_unit_path """ return self.make_organization_unit_provisioning_uri( ORGANIZATION_UNIT_FEED, customer_id, org_unit_path, params) MakeOrganizationUnitOrgunitProvisioningUri = make_organization_unit_orgunit_provisioning_uri def make_organization_unit_orguser_provisioning_uri(self, customer_id, org_user_email=None, params=None): """Creates a resource feed URI for the orguser's Provisioning API calls. Using this client's Google Apps domain, create a feed URI for organization unit orguser's provisioning in that domain. If an org_user_email is provided, return a URI for that specific resource. If params are provided, append them as GET params. Args: customer_id: string The customerId of the user. org_user_email: string (optional) The organization unit's path for which to make a feed URI. params: dict (optional) key -> value params to append as GET vars to the URI. Example: params={'start': 'my-resource-id'} Returns: A string giving the URI for organization user provisioning for given org_user_email """ return self.make_organization_unit_provisioning_uri( ORGANIZATION_USER_FEED, customer_id, org_user_email, params) MakeOrganizationUnitOrguserProvisioningUri = make_organization_unit_orguser_provisioning_uri def make_customer_id_feed_uri(self): """Creates a feed uri for retrieving customerId of the user. Returns: A string giving the URI for retrieving customerId of the user. """ uri = CUSTOMER_ID_URI_TEMPLATE % (self.api_version) return uri MakeCustomerIdFeedUri = make_customer_id_feed_uri def retrieve_customer_id(self, **kwargs): """Retrieve the Customer ID for the customer domain. Returns: A gdata.apps.organization.data.CustomerIdEntry. """ uri = self.MakeCustomerIdFeedUri() return self.GetEntry( uri, desired_class=gdata.apps.organization.data.CustomerIdEntry, **kwargs) RetrieveCustomerId = retrieve_customer_id def create_org_unit(self, customer_id, name, parent_org_unit_path='/', description='', block_inheritance=False, **kwargs): """Create a Organization Unit. Args: customer_id: string The ID of the Google Apps customer. name: string The simple organization unit text name, not the full path name. parent_org_unit_path: string The full path of the parental tree to this organization unit (default: '/'). [Note: Each element of the path MUST be URL encoded (example: finance%2Forganization/suborganization)] description: string The human readable text description of the organization unit (optional). block_inheritance: boolean This parameter blocks policy setting inheritance from organization units higher in the organization tree (default: False). Returns: A gdata.apps.organization.data.OrgUnitEntry representing an organization unit. """ new_org_unit = gdata.apps.organization.data.OrgUnitEntry( org_unit_name=name, parent_org_unit_path=parent_org_unit_path, org_unit_description=description, org_unit_block_inheritance=block_inheritance) return self.post( new_org_unit, self.MakeOrganizationUnitOrgunitProvisioningUri(customer_id), **kwargs) CreateOrgUnit = create_org_unit def update_org_unit(self, customer_id, org_unit_path, org_unit_entry, **kwargs): """Update a Organization Unit. Args: customer_id: string The ID of the Google Apps customer. org_unit_path: string The organization's full path name. [Note: Each element of the path MUST be URL encoded (example: finance%2Forganization/suborganization)] org_unit_entry: gdata.apps.organization.data.OrgUnitEntry The updated organization unit entry. Returns: A gdata.apps.organization.data.OrgUnitEntry representing an organization unit. """ if not org_unit_entry.GetParentOrgUnitPath(): org_unit_entry.SetParentOrgUnitPath('/') return self.update(org_unit_entry, uri=self.MakeOrganizationUnitOrgunitProvisioningUri( customer_id, org_unit_path=org_unit_path), **kwargs) UpdateOrgUnit = update_org_unit def move_users_to_org_unit(self, customer_id, org_unit_path, users_to_move, **kwargs): """Move a user to an Organization Unit. Args: customer_id: string The ID of the Google Apps customer. org_unit_path: string The organization's full path name. [Note: Each element of the path MUST be URL encoded (example: finance%2Forganization/suborganization)] users_to_move: list Email addresses of users to move in list format. [Note: You can move a maximum of 25 users at one time.] Returns: A gdata.apps.organization.data.OrgUnitEntry representing an organization unit. """ org_unit_entry = self.retrieve_org_unit(customer_id, org_unit_path) org_unit_entry.SetUsersToMove(', '.join(users_to_move)) if not org_unit_entry.GetParentOrgUnitPath(): org_unit_entry.SetParentOrgUnitPath('/') return self.update(org_unit_entry, uri=self.MakeOrganizationUnitOrgunitProvisioningUri( customer_id, org_unit_path=org_unit_path), **kwargs) MoveUserToOrgUnit = move_users_to_org_unit def retrieve_org_unit(self, customer_id, org_unit_path, **kwargs): """Retrieve a Orgunit based on its path. Args: customer_id: string The ID of the Google Apps customer. org_unit_path: string The organization's full path name. [Note: Each element of the path MUST be URL encoded (example: finance%2Forganization/suborganization)] Returns: A gdata.apps.organization.data.OrgUnitEntry representing an organization unit. """ uri = self.MakeOrganizationUnitOrgunitProvisioningUri( customer_id, org_unit_path=org_unit_path) return self.GetEntry( uri, desired_class=gdata.apps.organization.data.OrgUnitEntry, **kwargs) RetrieveOrgUnit = retrieve_org_unit def retrieve_feed_from_uri(self, uri, desired_class, **kwargs): """Retrieve feed from given uri. Args: uri: string The uri from where to get the feed. desired_class: Feed The type of feed that if to be retrieved. Returns: Feed of type desired class. """ return self.GetFeed(uri, desired_class=desired_class, **kwargs) RetrieveFeedFromUri = retrieve_feed_from_uri def retrieve_all_org_units_from_uri(self, uri, **kwargs): """Retrieve all OrgUnits from given uri. Args: uri: string The uri from where to get the orgunits. Returns: gdata.apps.organisation.data.OrgUnitFeed object """ orgunit_feed = gdata.apps.organization.data.OrgUnitFeed() temp_feed = self.RetrieveFeedFromUri( uri, gdata.apps.organization.data.OrgUnitFeed) orgunit_feed.entry = temp_feed.entry next_link = temp_feed.GetNextLink() while next_link is not None: uri = next_link.GetAttributes()[0].value temp_feed = self.GetFeed( uri, desired_class=gdata.apps.organization.data.OrgUnitFeed, **kwargs) orgunit_feed.entry[0:0] = temp_feed.entry next_link = temp_feed.GetNextLink() return orgunit_feed RetrieveAllOrgUnitsFromUri = retrieve_all_org_units_from_uri def retrieve_all_org_units(self, customer_id, **kwargs): """Retrieve all OrgUnits in the customer's domain. Args: customer_id: string The ID of the Google Apps customer. Returns: gdata.apps.organisation.data.OrgUnitFeed object """ uri = self.MakeOrganizationUnitOrgunitProvisioningUri( customer_id, params={'get': 'all'}, **kwargs) return self.RetrieveAllOrgUnitsFromUri(uri) RetrieveAllOrgUnits = retrieve_all_org_units def retrieve_page_of_org_units(self, customer_id, startKey=None, **kwargs): """Retrieve one page of OrgUnits in the customer's domain. Args: customer_id: string The ID of the Google Apps customer. startKey: string The key to continue for pagination through all OrgUnits. Returns: gdata.apps.organisation.data.OrgUnitFeed object """ uri = '' if startKey is not None: uri = self.MakeOrganizationUnitOrgunitProvisioningUri( customer_id, params={'get': 'all', 'startKey': startKey}, **kwargs) else: uri = self.MakeOrganizationUnitOrgunitProvisioningUri( customer_id, params={'get': 'all'}, **kwargs) return self.GetFeed( uri, desired_class=gdata.apps.organization.data.OrgUnitFeed, **kwargs) RetrievePageOfOrgUnits = retrieve_page_of_org_units def retrieve_sub_org_units(self, customer_id, org_unit_path, **kwargs): """Retrieve all Sub-OrgUnits of the provided OrgUnit. Args: customer_id: string The ID of the Google Apps customer. org_unit_path: string The organization's full path name. [Note: Each element of the path MUST be URL encoded (example: finance%2Forganization/suborganization)] Returns: gdata.apps.organisation.data.OrgUnitFeed object """ uri = self.MakeOrganizationUnitOrgunitProvisioningUri( customer_id, params={'get': 'children', 'orgUnitPath': org_unit_path}, **kwargs) return self.RetrieveAllOrgUnitsFromUri(uri) RetrieveSubOrgUnits = retrieve_sub_org_units def delete_org_unit(self, customer_id, org_unit_path, **kwargs): """Delete a Orgunit based on its path. Args: customer_id: string The ID of the Google Apps customer. org_unit_path: string The organization's full path name. [Note: Each element of the path MUST be URL encoded (example: finance%2Forganization/suborganization)] Returns: An HTTP response object. See gdata.client.request(). """ return self.delete(self.MakeOrganizationUnitOrgunitProvisioningUri( customer_id, org_unit_path=org_unit_path), **kwargs) DeleteOrgUnit = delete_org_unit def update_org_user(self, customer_id, user_email, org_unit_path, **kwargs): """Update the OrgUnit of a OrgUser. Args: customer_id: string The ID of the Google Apps customer. user_email: string The email address of the user. org_unit_path: string The new organization's full path name. [Note: Each element of the path MUST be URL encoded (example: finance%2Forganization/suborganization)] Returns: A gdata.apps.organization.data.OrgUserEntry representing an organization user. """ old_user_entry = self.RetrieveOrgUser(customer_id, user_email) old_org_unit_path = old_user_entry.GetOrgUnitPath() if not old_org_unit_path: old_org_unit_path = '/' old_user_entry.SetOldOrgUnitPath(old_org_unit_path) old_user_entry.SetOrgUnitPath(org_unit_path) return self.update(old_user_entry, uri=self.MakeOrganizationUnitOrguserProvisioningUri( customer_id, user_email), **kwargs) UpdateOrgUser = update_org_user def retrieve_org_user(self, customer_id, user_email, **kwargs): """Retrieve an organization user. Args: customer_id: string The ID of the Google Apps customer. user_email: string The email address of the user. Returns: A gdata.apps.organization.data.OrgUserEntry representing an organization user. """ uri = self.MakeOrganizationUnitOrguserProvisioningUri(customer_id, user_email) return self.GetEntry( uri, desired_class=gdata.apps.organization.data.OrgUserEntry, **kwargs) RetrieveOrgUser = retrieve_org_user def retrieve_all_org_users_from_uri(self, uri, **kwargs): """Retrieve all OrgUsers from given uri. Args: uri: string The uri from where to get the orgusers. Returns: gdata.apps.organisation.data.OrgUserFeed object """ orguser_feed = gdata.apps.organization.data.OrgUserFeed() temp_feed = self.RetrieveFeedFromUri( uri, gdata.apps.organization.data.OrgUserFeed) orguser_feed.entry = temp_feed.entry next_link = temp_feed.GetNextLink() while next_link is not None: uri = next_link.GetAttributes()[0].value temp_feed = self.GetFeed( uri, desired_class=gdata.apps.organization.data.OrgUserFeed, **kwargs) orguser_feed.entry[0:0] = temp_feed.entry next_link = temp_feed.GetNextLink() return orguser_feed RetrieveAllOrgUsersFromUri = retrieve_all_org_users_from_uri def retrieve_all_org_users(self, customer_id, **kwargs): """Retrieve all OrgUsers in the customer's domain. Args: customer_id: string The ID of the Google Apps customer. Returns: gdata.apps.organisation.data.OrgUserFeed object """ uri = self.MakeOrganizationUnitOrguserProvisioningUri( customer_id, params={'get': 'all'}, **kwargs) return self.RetrieveAllOrgUsersFromUri(uri) RetrieveAllOrgUsers = retrieve_all_org_users def retrieve_page_of_org_users(self, customer_id, startKey=None, **kwargs): """Retrieve one page of OrgUsers in the customer's domain. Args: customer_id: string The ID of the Google Apps customer. startKey: The string key to continue for pagination through all OrgUnits. Returns: gdata.apps.organisation.data.OrgUserFeed object """ uri = '' if startKey is not None: uri = self.MakeOrganizationUnitOrguserProvisioningUri( customer_id, params={'get': 'all', 'startKey': startKey}, **kwargs) else: uri = self.MakeOrganizationUnitOrguserProvisioningUri( customer_id, params={'get': 'all'}) return self.GetFeed( uri, desired_class=gdata.apps.organization.data.OrgUserFeed, **kwargs) RetrievePageOfOrgUsers = retrieve_page_of_org_users def retrieve_org_unit_users(self, customer_id, org_unit_path, **kwargs): """Retrieve all OrgUsers of the provided OrgUnit. Args: customer_id: string The ID of the Google Apps customer. org_unit_path: string The organization's full path name. [Note: Each element of the path MUST be URL encoded (example: finance%2Forganization/suborganization)] Returns: gdata.apps.organisation.data.OrgUserFeed object """ uri = self.MakeOrganizationUnitOrguserProvisioningUri( customer_id, params={'get': 'children', 'orgUnitPath': org_unit_path}) return self.RetrieveAllOrgUsersFromUri(uri, **kwargs) RetrieveOrgUnitUsers = retrieve_org_unit_users gdata-2.0.18/samples/apps/marketplace_sample/gdata/apps/organization/service.py0000640036466300116100000002544112156622362027744 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright (C) 2008 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. """Allow Google Apps domain administrators to manage organization unit and organization user. OrganizationService: Provides methods to manage organization unit and organization user. """ __author__ = 'Alexandre Vivien (alex@simplecode.fr)' import gdata.apps import gdata.apps.service import gdata.service API_VER = '2.0' CUSTOMER_BASE_URL = '/a/feeds/customer/2.0/customerId' BASE_UNIT_URL = '/a/feeds/orgunit/' + API_VER + '/%s' UNIT_URL = BASE_UNIT_URL + '/%s' UNIT_ALL_URL = BASE_UNIT_URL + '?get=all' UNIT_CHILD_URL = BASE_UNIT_URL + '?get=children&orgUnitPath=%s' BASE_USER_URL = '/a/feeds/orguser/' + API_VER + '/%s' USER_URL = BASE_USER_URL + '/%s' USER_ALL_URL = BASE_USER_URL + '?get=all' USER_CHILD_URL = BASE_USER_URL + '?get=children&orgUnitPath=%s' class OrganizationService(gdata.apps.service.PropertyService): """Client for the Google Apps Organizations service.""" def _Bool2Str(self, b): if b is None: return None return str(b is True).lower() def RetrieveCustomerId(self): """Retrieve the Customer ID for the account of the authenticated administrator making this request. Args: None. Returns: A dict containing the result of the retrieve operation. """ uri = CUSTOMER_BASE_URL return self._GetProperties(uri) def CreateOrgUnit(self, customer_id, name, parent_org_unit_path='/', description='', block_inheritance=False): """Create a Organization Unit. Args: customer_id: The ID of the Google Apps customer. name: The simple organization unit text name, not the full path name. parent_org_unit_path: The full path of the parental tree to this organization unit (default: '/'). Note: Each element of the path MUST be URL encoded (example: finance%2Forganization/suborganization) description: The human readable text description of the organization unit (optional). block_inheritance: This parameter blocks policy setting inheritance from organization units higher in the organization tree (default: False). Returns: A dict containing the result of the create operation. """ uri = BASE_UNIT_URL % (customer_id) properties = {} properties['name'] = name properties['parentOrgUnitPath'] = parent_org_unit_path properties['description'] = description properties['blockInheritance'] = self._Bool2Str(block_inheritance) return self._PostProperties(uri, properties) def UpdateOrgUnit(self, customer_id, org_unit_path, name=None, parent_org_unit_path=None, description=None, block_inheritance=None): """Update a Organization Unit. Args: customer_id: The ID of the Google Apps customer. org_unit_path: The organization's full path name. Note: Each element of the path MUST be URL encoded (example: finance%2Forganization/suborganization) name: The simple organization unit text name, not the full path name. parent_org_unit_path: The full path of the parental tree to this organization unit. Note: Each element of the path MUST be URL encoded (example: finance%2Forganization/suborganization) description: The human readable text description of the organization unit. block_inheritance: This parameter blocks policy setting inheritance from organization units higher in the organization tree. Returns: A dict containing the result of the update operation. """ uri = UNIT_URL % (customer_id, org_unit_path) properties = {} if name: properties['name'] = name if parent_org_unit_path: properties['parentOrgUnitPath'] = parent_org_unit_path if description: properties['description'] = description if block_inheritance: properties['blockInheritance'] = self._Bool2Str(block_inheritance) return self._PutProperties(uri, properties) def MoveUserToOrgUnit(self, customer_id, org_unit_path, users_to_move): """Move a user to an Organization Unit. Args: customer_id: The ID of the Google Apps customer. org_unit_path: The organization's full path name. Note: Each element of the path MUST be URL encoded (example: finance%2Forganization/suborganization) users_to_move: Email addresses list of users to move. Note: You can move a maximum of 25 users at one time. Returns: A dict containing the result of the update operation. """ uri = UNIT_URL % (customer_id, org_unit_path) properties = {} if users_to_move and isinstance(users_to_move, list): properties['usersToMove'] = ', '.join(users_to_move) return self._PutProperties(uri, properties) def RetrieveOrgUnit(self, customer_id, org_unit_path): """Retrieve a Orgunit based on its path. Args: customer_id: The ID of the Google Apps customer. org_unit_path: The organization's full path name. Note: Each element of the path MUST be URL encoded (example: finance%2Forganization/suborganization) Returns: A dict containing the result of the retrieve operation. """ uri = UNIT_URL % (customer_id, org_unit_path) return self._GetProperties(uri) def DeleteOrgUnit(self, customer_id, org_unit_path): """Delete a Orgunit based on its path. Args: customer_id: The ID of the Google Apps customer. org_unit_path: The organization's full path name. Note: Each element of the path MUST be URL encoded (example: finance%2Forganization/suborganization) Returns: A dict containing the result of the delete operation. """ uri = UNIT_URL % (customer_id, org_unit_path) return self._DeleteProperties(uri) def RetrieveAllOrgUnits(self, customer_id): """Retrieve all OrgUnits in the customer's domain. Args: customer_id: The ID of the Google Apps customer. Returns: A list containing the result of the retrieve operation. """ uri = UNIT_ALL_URL % (customer_id) return self._GetPropertiesList(uri) def RetrievePageOfOrgUnits(self, customer_id, startKey=None): """Retrieve one page of OrgUnits in the customer's domain. Args: customer_id: The ID of the Google Apps customer. startKey: The key to continue for pagination through all OrgUnits. Returns: A feed object containing the result of the retrieve operation. """ uri = UNIT_ALL_URL % (customer_id) if startKey is not None: uri += "&startKey=" + startKey property_feed = self._GetPropertyFeed(uri) return property_feed def RetrieveSubOrgUnits(self, customer_id, org_unit_path): """Retrieve all Sub-OrgUnits of the provided OrgUnit. Args: customer_id: The ID of the Google Apps customer. org_unit_path: The organization's full path name. Note: Each element of the path MUST be URL encoded (example: finance%2Forganization/suborganization) Returns: A list containing the result of the retrieve operation. """ uri = UNIT_CHILD_URL % (customer_id, org_unit_path) return self._GetPropertiesList(uri) def RetrieveOrgUser(self, customer_id, user_email): """Retrieve the OrgUnit of the user. Args: customer_id: The ID of the Google Apps customer. user_email: The email address of the user. Returns: A dict containing the result of the retrieve operation. """ uri = USER_URL % (customer_id, user_email) return self._GetProperties(uri) def UpdateOrgUser(self, customer_id, user_email, org_unit_path): """Update the OrgUnit of a OrgUser. Args: customer_id: The ID of the Google Apps customer. user_email: The email address of the user. org_unit_path: The new organization's full path name. Note: Each element of the path MUST be URL encoded (example: finance%2Forganization/suborganization) Returns: A dict containing the result of the update operation. """ uri = USER_URL % (customer_id, user_email) properties = {} if org_unit_path: properties['orgUnitPath'] = org_unit_path return self._PutProperties(uri, properties) def RetrieveAllOrgUsers(self, customer_id): """Retrieve all OrgUsers in the customer's domain. Args: customer_id: The ID of the Google Apps customer. Returns: A list containing the result of the retrieve operation. """ uri = USER_ALL_URL % (customer_id) return self._GetPropertiesList(uri) def RetrievePageOfOrgUsers(self, customer_id, startKey=None): """Retrieve one page of OrgUsers in the customer's domain. Args: customer_id: The ID of the Google Apps customer. startKey: The key to continue for pagination through all OrgUnits. Returns: A feed object containing the result of the retrieve operation. """ uri = USER_ALL_URL % (customer_id) if startKey is not None: uri += "&startKey=" + startKey property_feed = self._GetPropertyFeed(uri) return property_feed def RetrieveOrgUnitUsers(self, customer_id, org_unit_path): """Retrieve all OrgUsers of the provided OrgUnit. Args: customer_id: The ID of the Google Apps customer. org_unit_path: The organization's full path name. Note: Each element of the path MUST be URL encoded (example: finance%2Forganization/suborganization) Returns: A list containing the result of the retrieve operation. """ uri = USER_CHILD_URL % (customer_id, org_unit_path) return self._GetPropertiesList(uri) def RetrieveOrgUnitPageOfUsers(self, customer_id, org_unit_path, startKey=None): """Retrieve one page of OrgUsers of the provided OrgUnit. Args: customer_id: The ID of the Google Apps customer. org_unit_path: The organization's full path name. Note: Each element of the path MUST be URL encoded (example: finance%2Forganization/suborganization) startKey: The key to continue for pagination through all OrgUsers. Returns: A feed object containing the result of the retrieve operation. """ uri = USER_CHILD_URL % (customer_id, org_unit_path) if startKey is not None: uri += "&startKey=" + startKey property_feed = self._GetPropertyFeed(uri) return property_feed gdata-2.0.18/samples/apps/marketplace_sample/gdata/apps/emailsettings/0000750036466300116100000000000012156625015026065 5ustar afshareng00000000000000gdata-2.0.18/samples/apps/marketplace_sample/gdata/apps/emailsettings/data.py0000640036466300116100000011224112156622362027354 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright 2010 Google Inc. All Rights Reserved. # # 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. """Data model classes for the Email Settings API.""" __author__ = 'Claudio Cherubino ' import atom.data import gdata.apps import gdata.apps_property import gdata.data # This is required to work around a naming conflict between the Google # Spreadsheets API and Python's built-in property function pyproperty = property # The apps:property label of the label property LABEL_NAME = 'label' # The apps:property from of the filter property FILTER_FROM_NAME = 'from' # The apps:property to of the filter property FILTER_TO_NAME = 'to' # The apps:property subject of the filter property FILTER_SUBJECT_NAME = 'subject' # The apps:property hasTheWord of the filter property FILTER_HAS_THE_WORD_NAME = 'hasTheWord' # The apps:property doesNotHaveTheWord of the filter property FILTER_DOES_NOT_HAVE_THE_WORD_NAME = 'doesNotHaveTheWord' # The apps:property hasAttachment of the filter property FILTER_HAS_ATTACHMENTS_NAME = 'hasAttachment' # The apps:property label of the filter action property FILTER_LABEL = 'label' # The apps:property shouldMarkAsRead of the filter action property FILTER_MARK_AS_READ = 'shouldMarkAsRead' # The apps:property shouldArchive of the filter action propertylabel FILTER_ARCHIVE = 'shouldArchive' # The apps:property name of the send-as alias property SENDAS_ALIAS_NAME = 'name' # The apps:property address of theAPPS_TEMPLATE send-as alias property SENDAS_ALIAS_ADDRESS = 'address' # The apps:property replyTo of the send-as alias property SENDAS_ALIAS_REPLY_TO = 'replyTo' # The apps:property makeDefault of the send-as alias property SENDAS_ALIAS_MAKE_DEFAULT = 'makeDefault' # The apps:property enable of the webclip property WEBCLIP_ENABLE = 'enable' # The apps:property enable of the forwarding property FORWARDING_ENABLE = 'enable' # The apps:property forwardTo of the forwarding property FORWARDING_TO = 'forwardTo' # The apps:property action of the forwarding property FORWARDING_ACTION = 'action' # The apps:property enable of the POP property POP_ENABLE = 'enable' # The apps:property enableFor of the POP propertyACTION POP_ENABLE_FOR = 'enableFor' # The apps:property action of the POP property POP_ACTION = 'action' # The apps:property enable of the IMAP property IMAP_ENABLE = 'enable' # The apps:property enable of the vacation responder property VACATION_RESPONDER_ENABLE = 'enable' # The apps:property subject of the vacation responder property VACATION_RESPONDER_SUBJECT = 'subject' # The apps:property message of the vacation responder property VACATION_RESPONDER_MESSAGE = 'message' # The apps:property startDate of the vacation responder property VACATION_RESPONDER_STARTDATE = 'startDate' # The apps:property endDate of the vacation responder property VACATION_RESPONDER_ENDDATE = 'endDate' # The apps:property contactsOnly of the vacation responder property VACATION_RESPONDER_CONTACTS_ONLY = 'contactsOnly' # The apps:property domainOnly of the vacation responder property VACATION_RESPONDER_DOMAIN_ONLY = 'domainOnly' # The apps:property signature of the signature property SIGNATURE_VALUE = 'signature' # The apps:property language of the language property LANGUAGE_TAG = 'language' # The apps:property pageSize of the general settings property GENERAL_PAGE_SIZE = 'pageSize' # The apps:property shortcuts of the general settings property GENERAL_SHORTCUTS = 'shortcuts' # The apps:property arrows of the general settings property GENERAL_ARROWS = 'arrows' # The apps:prgdata.appsoperty snippets of the general settings property GENERAL_SNIPPETS = 'snippets' # The apps:property uniAppsProcode of the general settings property GENERAL_UNICODE = 'unicode' # The apps:property delegationId of the email delegation property DELEGATION_ID = 'delegationId' # The apps:property address of the email delegation property DELEGATION_ADDRESS = 'address' # The apps:property delegate of the email delegation property DELEGATION_DELEGATE = 'delegate' # The apps:property status of the email delegation property DELEGATION_STATUS = 'status' class EmailSettingsEntry(gdata.data.GDEntry): """Represents an Email Settings entry in object form.""" property = [gdata.apps_property.AppsProperty] def _GetProperty(self, name): """Get the apps:property value with the given name. Args: name: string Name of the apps:property value to get. Returns: The apps:property value with the given name, or None if the name was invalid. """ value = None for p in self.property: if p.name == name: value = p.value break return value def _SetProperty(self, name, value): """Set the apps:property value with the given name to the given value. Args: name: string Name of the apps:property value to set. value: string Value to give the apps:property value with the given name. """ found = False for i in range(len(self.property)): if self.property[i].name == name: self.property[i].value = value found = True break if not found: self.property.append(gdata.apps_property.AppsProperty(name=name, value=value)) def find_edit_link(self): return self.uri class EmailSettingsLabel(EmailSettingsEntry): """Represents a Label in object form.""" def GetName(self): """Get the name of the Label object. Returns: The name of this Label object as a string or None. """ return self._GetProperty(LABEL_NAME) def SetName(self, value): """Set the name of this Label object. Args: value: string The new label name to give this object. """ self._SetProperty(LABEL_NAME, value) name = pyproperty(GetName, SetName) def __init__(self, uri=None, name=None, *args, **kwargs): """Constructs a new EmailSettingsLabel object with the given arguments. Args: uri: string (optional) The uri of of this object for HTTP requests. name: string (optional) The name to give this new object. args: The other parameters to pass to gdata.entry.GDEntry constructor. kwargs: The other parameters to pass to gdata.entry.GDEntry constructor. """ super(EmailSettingsLabel, self).__init__(*args, **kwargs) if uri: self.uri = uri if name: self.name = name class EmailSettingsFilter(EmailSettingsEntry): """Represents an Email Settings Filter in object form.""" def GetFrom(self): """Get the From value of the Filter object. Returns: The From value of this Filter object as a string or None. """ return self._GetProperty(FILTER_FROM_NAME) def SetFrom(self, value): """Set the From value of this Filter object. Args: value: string The new From value to give this object. """ self._SetProperty(FILTER_FROM_NAME, value) from_address = pyproperty(GetFrom, SetFrom) def GetTo(self): """Get the To value of the Filter object. Returns: The To value of this Filter object as a string or None. """ return self._GetProperty(FILTER_TO_NAME) def SetTo(self, value): """Set the To value of this Filter object. Args: value: string The new To value to give this object. """ self._SetProperty(FILTER_TO_NAME, value) to_address = pyproperty(GetTo, SetTo) def GetSubject(self): """Get the Subject value of the Filter object. Returns: The Subject value of this Filter object as a string or None. """ return self._GetProperty(FILTER_SUBJECT_NAME) def SetSubject(self, value): """Set the Subject value of this Filter object. Args: value: string The new Subject value to give this object. """ self._SetProperty(FILTER_SUBJECT_NAME, value) subject = pyproperty(GetSubject, SetSubject) def GetHasTheWord(self): """Get the HasTheWord value of the Filter object. Returns: The HasTheWord value of this Filter object as a string or None. """ return self._GetProperty(FILTER_HAS_THE_WORD_NAME) def SetHasTheWord(self, value): """Set the HasTheWord value of this Filter object. Args: value: string The new HasTheWord value to give this object. """ self._SetProperty(FILTER_HAS_THE_WORD_NAME, value) has_the_word = pyproperty(GetHasTheWord, SetHasTheWord) def GetDoesNotHaveTheWord(self): """Get the DoesNotHaveTheWord value of the Filter object. Returns: The DoesNotHaveTheWord value of this Filter object as a string or None. """ return self._GetProperty(FILTER_DOES_NOT_HAVE_THE_WORD_NAME) def SetDoesNotHaveTheWord(self, value): """Set the DoesNotHaveTheWord value of this Filter object. Args: value: string The new DoesNotHaveTheWord value to give this object. """ self._SetProperty(FILTER_DOES_NOT_HAVE_THE_WORD_NAME, value) does_not_have_the_word = pyproperty(GetDoesNotHaveTheWord, SetDoesNotHaveTheWord) def GetHasAttachments(self): """Get the HasAttachments value of the Filter object. Returns: The HasAttachments value of this Filter object as a string or None. """ return self._GetProperty(FILTER_HAS_ATTACHMENTS_NAME) def SetHasAttachments(self, value): """Set the HasAttachments value of this Filter object. Args: value: string The new HasAttachments value to give this object. """ self._SetProperty(FILTER_HAS_ATTACHMENTS_NAME, value) has_attachments = pyproperty(GetHasAttachments, SetHasAttachments) def GetLabel(self): """Get the Label value of the Filter object. Returns: The Label value of this Filter object as a string or None. """ return self._GetProperty(FILTER_LABEL) def SetLabel(self, value): """Set the Label value of this Filter object. Args: value: string The new Label value to give this object. """ self._SetProperty(FILTER_LABEL, value) label = pyproperty(GetLabel, SetLabel) def GetMarkAsRead(self): """Get the MarkAsRead value of the Filter object. Returns: The MarkAsRead value of this Filter object as a string or None. """ return self._GetProperty(FILTER_MARK_AS_READ) def SetMarkAsRead(self, value): """Set the MarkAsRead value of this Filter object. Args: value: string The new MarkAsRead value to give this object. """ self._SetProperty(FILTER_MARK_AS_READ, value) mark_as_read = pyproperty(GetMarkAsRead, SetMarkAsRead) def GetArchive(self): """Get the Archive value of the Filter object. Returns: The Archive value of this Filter object as a string or None. """ return self._GetProperty(FILTER_ARCHIVE) def SetArchive(self, value): """Set the Archive value of this Filter object. Args: value: string The new Archive value to give this object. """ self._SetProperty(FILTER_ARCHIVE, value) archive = pyproperty(GetArchive, SetArchive) def __init__(self, uri=None, from_address=None, to_address=None, subject=None, has_the_word=None, does_not_have_the_word=None, has_attachments=None, label=None, mark_as_read=None, archive=None, *args, **kwargs): """Constructs a new EmailSettingsFilter object with the given arguments. Args: uri: string (optional) The uri of of this object for HTTP requests. from_address: string (optional) The source email address for the filter. to_address: string (optional) The destination email address for the filter. subject: string (optional) The value the email must have in its subject to be filtered. has_the_word: string (optional) The value the email must have in its subject or body to be filtered. does_not_have_the_word: string (optional) The value the email cannot have in its subject or body to be filtered. has_attachments: Boolean (optional) Whether or not the email must have an attachment to be filtered. label: string (optional) The name of the label to apply to messages matching the filter criteria. mark_as_read: Boolean (optional) Whether or not to mark messages matching the filter criteria as read. archive: Boolean (optional) Whether or not to move messages matching to Archived state. args: The other parameters to pass to gdata.entry.GDEntry constructor. kwargs: The other parameters to pass to gdata.entry.GDEntry constructor. """ super(EmailSettingsFilter, self).__init__(*args, **kwargs) if uri: self.uri = uri if from_address: self.from_address = from_address if to_address: self.to_address = to_address if subject: self.subject = subject if has_the_word: self.has_the_word = has_the_word if does_not_have_the_word: self.does_not_have_the_word = does_not_have_the_word if has_attachments is not None: self.has_attachments = str(has_attachments) if label: self.label = label if mark_as_read is not None: self.mark_as_read = str(mark_as_read) if archive is not None: self.archive = str(archive) class EmailSettingsSendAsAlias(EmailSettingsEntry): """Represents an Email Settings send-as Alias in object form.""" def GetName(self): """Get the Name of the send-as Alias object. Returns: The Name of this send-as Alias object as a string or None. """ return self._GetProperty(SENDAS_ALIAS_NAME) def SetName(self, value): """Set the Name of this send-as Alias object. Args: value: string The new Name to give this object. """ self._SetProperty(SENDAS_ALIAS_NAME, value) name = pyproperty(GetName, SetName) def GetAddress(self): """Get the Address of the send-as Alias object. Returns: The Address of this send-as Alias object as a string or None. """ return self._GetProperty(SENDAS_ALIAS_ADDRESS) def SetAddress(self, value): """Set the Address of this send-as Alias object. Args: value: string The new Address to give this object. """ self._SetProperty(SENDAS_ALIAS_ADDRESS, value) address = pyproperty(GetAddress, SetAddress) def GetReplyTo(self): """Get the ReplyTo address of the send-as Alias object. Returns: The ReplyTo address of this send-as Alias object as a string or None. """ return self._GetProperty(SENDAS_ALIAS_REPLY_TO) def SetReplyTo(self, value): """Set the ReplyTo address of this send-as Alias object. Args: value: string The new ReplyTo address to give this object. """ self._SetProperty(SENDAS_ALIAS_REPLY_TO, value) reply_to = pyproperty(GetReplyTo, SetReplyTo) def GetMakeDefault(self): """Get the MakeDefault value of the send-as Alias object. Returns: The MakeDefault value of this send-as Alias object as a string or None. """ return self._GetProperty(SENDAS_ALIAS_MAKE_DEFAULT) def SetMakeDefault(self, value): """Set the MakeDefault value of this send-as Alias object. Args: value: string The new MakeDefault valueto give this object.WebClip """ self._SetProperty(SENDAS_ALIAS_MAKE_DEFAULT, value) make_default = pyproperty(GetMakeDefault, SetMakeDefault) def __init__(self, uri=None, name=None, address=None, reply_to=None, make_default=None, *args, **kwargs): """Constructs a new EmailSettingsSendAsAlias object with the given arguments. Args: uri: string (optional) The uri of of this object for HTTP requests. name: string (optional) The name that will appear in the "From" field for this user. address: string (optional) The email address that appears as the origination address for emails sent by this user. reply_to: string (optional) The address to be used as the reply-to address in email sent using the alias. make_default: Boolean (optional) Whether or not this alias should become the default alias for this user. args: The other parameters to pass to gdata.entry.GDEntry constructor. kwargs: The other parameters to pass to gdata.entry.GDEntry constructor. """ super(EmailSettingsSendAsAlias, self).__init__(*args, **kwargs) if uri: self.uri = uri if name: self.name = name if address: self.address = address if reply_to: self.reply_to = reply_to if make_default is not None: self.make_default = str(make_default) class EmailSettingsWebClip(EmailSettingsEntry): """Represents a WebClip in object form.""" def GetEnable(self): """Get the Enable value of the WebClip object. Returns: The Enable value of this WebClip object as a string or None. """ return self._GetProperty(WEBCLIP_ENABLE) def SetEnable(self, value): """Set the Enable value of this WebClip object. Args: value: string The new Enable value to give this object. """ self._SetProperty(WEBCLIP_ENABLE, value) enable = pyproperty(GetEnable, SetEnable) def __init__(self, uri=None, enable=None, *args, **kwargs): """Constructs a new EmailSettingsWebClip object with the given arguments. Args: uri: string (optional) The uri of of this object for HTTP requests. enable: Boolean (optional) Whether to enable showing Web clips. args: The other parameters to pass to gdata.entry.GDEntry constructor. kwargs: The other parameters to pass to gdata.entry.GDEntry constructor. """ super(EmailSettingsWebClip, self).__init__(*args, **kwargs) if uri: self.uri = uri if enable is not None: self.enable = str(enable) class EmailSettingsForwarding(EmailSettingsEntry): """Represents Forwarding settings in object form.""" def GetEnable(self): """Get the Enable value of the Forwarding object. Returns: The Enable value of this Forwarding object as a string or None. """ return self._GetProperty(FORWARDING_ENABLE) def SetEnable(self, value): """Set the Enable value of this Forwarding object. Args: value: string The new Enable value to give this object. """ self._SetProperty(FORWARDING_ENABLE, value) enable = pyproperty(GetEnable, SetEnable) def GetForwardTo(self): """Get the ForwardTo value of the Forwarding object. Returns: The ForwardTo value of this Forwarding object as a string or None. """ return self._GetProperty(FORWARDING_TO) def SetForwardTo(self, value): """Set the ForwardTo value of this Forwarding object. Args: value: string The new ForwardTo value to give this object. """ self._SetProperty(FORWARDING_TO, value) forward_to = pyproperty(GetForwardTo, SetForwardTo) def GetAction(self): """Get the Action value of the Forwarding object. Returns: The Action value of this Forwarding object as a string or None. """ return self._GetProperty(FORWARDING_ACTION) def SetAction(self, value): """Set the Action value of this Forwarding object. Args: value: string The new Action value to give this object. """ self._SetProperty(FORWARDING_ACTION, value) action = pyproperty(GetAction, SetAction) def __init__(self, uri=None, enable=None, forward_to=None, action=None, *args, **kwargs): """Constructs a new EmailSettingsForwarding object with the given arguments. Args: uri: string (optional) The uri of of this object for HTTP requests. enable: Boolean (optional) Whether to enable incoming email forwarding. forward_to: string (optional) The address email will be forwarded to. action: string (optional) The action to perform after forwarding an email ("KEEP", "ARCHIVE", "DELETE"). args: The other parameters to pass to gdata.entry.GDEntry constructor. kwargs: The other parameters to pass to gdata.entry.GDEntry constructor. """ super(EmailSettingsForwarding, self).__init__(*args, **kwargs) if uri: self.uri = uri if enable is not None: self.enable = str(enable) if forward_to: self.forward_to = forward_to if action: self.action = action class EmailSettingsPop(EmailSettingsEntry): """Represents POP settings in object form.""" def GetEnable(self): """Get the Enable value of the POP object. Returns: The Enable value of this POP object as a string or None. """ return self._GetProperty(POP_ENABLE) def SetEnable(self, value): """Set the Enable value of this POP object. Args: value: string The new Enable value to give this object. """ self._SetProperty(POP_ENABLE, value) enable = pyproperty(GetEnable, SetEnable) def GetEnableFor(self): """Get the EnableFor value of the POP object. Returns: The EnableFor value of this POP object as a string or None. """ return self._GetProperty(POP_ENABLE_FOR) def SetEnableFor(self, value): """Set the EnableFor value of this POP object. Args: value: string The new EnableFor value to give this object. """ self._SetProperty(POP_ENABLE_FOR, value) enable_for = pyproperty(GetEnableFor, SetEnableFor) def GetPopAction(self): """Get the Action value of the POP object. Returns: The Action value of this POP object as a string or None. """ return self._GetProperty(POP_ACTION) def SetPopAction(self, value): """Set the Action value of this POP object. Args: value: string The new Action value to give this object. """ self._SetProperty(POP_ACTION, value) action = pyproperty(GetPopAction, SetPopAction) def __init__(self, uri=None, enable=None, enable_for=None, action=None, *args, **kwargs): """Constructs a new EmailSettingsPOP object with the given arguments. Args: uri: string (optional) The uri of of this object for HTTP requests. enable: Boolean (optional) Whether to enable incoming POP3 access. enable_for: string (optional) Whether to enable POP3 for all mail ("ALL_MAIL"), or mail from now on ("MAIL_FROM_NOW_ON"). action: string (optional) What Google Mail should do with its copy of the email after it is retrieved using POP ("KEEP", "ARCHIVE", or "DELETE"). args: The other parameters to pass to gdata.entry.GDEntry constructor. kwargs: The other parameters to pass to gdata.entry.GDEntry constructor. """ super(EmailSettingsPop, self).__init__(*args, **kwargs) if uri: self.uri = uri if enable is not None: self.enable = str(enable) if enable_for: self.enable_for = enable_for if action: self.action = action class EmailSettingsImap(EmailSettingsEntry): """Represents IMAP settings in object form.""" def GetEnable(self): """Get the Enable value of the IMAP object. Returns: The Enable value of this IMAP object as a string or None. """ return self._GetProperty(IMAP_ENABLE) def SetEnable(self, value): """Set the Enable value of this IMAP object. Args: value: string The new Enable value to give this object. """ self._SetProperty(IMAP_ENABLE, value) enable = pyproperty(GetEnable, SetEnable) def __init__(self, uri=None, enable=None, *args, **kwargs): """Constructs a new EmailSettingsImap object with the given arguments. Args: uri: string (optional) The uri of of this object for HTTP requests. enable: Boolean (optional) Whether to enable IMAP access. args: The other parameters to pass to gdata.entry.GDEntry constructor. kwargs: The other parameters to pass to gdata.entry.GDEntry constructor. """ super(EmailSettingsImap, self).__init__(*args, **kwargs) if uri: self.uri = uri if enable is not None: self.enable = str(enable) class EmailSettingsVacationResponder(EmailSettingsEntry): """Represents Vacation Responder settings in object form.""" def GetEnable(self): """Get the Enable value of the Vacation Responder object. Returns: The Enable value of this Vacation Responder object as a string or None. """ return self._GetProperty(VACATION_RESPONDER_ENABLE) def SetEnable(self, value): """Set the Enable value of this Vacation Responder object. Args: value: string The new Enable value to give this object. """ self._SetProperty(VACATION_RESPONDER_ENABLE, value) enable = pyproperty(GetEnable, SetEnable) def GetSubject(self): """Get the Subject value of the Vacation Responder object. Returns: The Subject value of this Vacation Responder object as a string or None. """ return self._GetProperty(VACATION_RESPONDER_SUBJECT) def SetSubject(self, value): """Set the Subject value of this Vacation Responder object. Args: value: string The new Subject value to give this object. """ self._SetProperty(VACATION_RESPONDER_SUBJECT, value) subject = pyproperty(GetSubject, SetSubject) def GetMessage(self): """Get the Message value of the Vacation Responder object. Returns: The Message value of this Vacation Responder object as a string or None. """ return self._GetProperty(VACATION_RESPONDER_MESSAGE) def SetMessage(self, value): """Set the Message value of this Vacation Responder object. Args: value: string The new Message value to give this object. """ self._SetProperty(VACATION_RESPONDER_MESSAGE, value) message = pyproperty(GetMessage, SetMessage) def GetStartDate(self): """Get the StartDate value of the Vacation Responder object. Returns: The StartDate value of this Vacation Responder object as a string(YYYY-MM-DD) or None. """ return self._GetProperty(VACATION_RESPONDER_STARTDATE) def SetStartDate(self, value): """Set the StartDate value of this Vacation Responder object. Args: value: string The new StartDate value to give this object. """ self._SetProperty(VACATION_RESPONDER_STARTDATE, value) start_date = pyproperty(GetStartDate, SetStartDate) def GetEndDate(self): """Get the EndDate value of the Vacation Responder object. Returns: The EndDate value of this Vacation Responder object as a string(YYYY-MM-DD) or None. """ return self._GetProperty(VACATION_RESPONDER_ENDDATE) def SetEndDate(self, value): """Set the EndDate value of this Vacation Responder object. Args: value: string The new EndDate value to give this object. """ self._SetProperty(VACATION_RESPONDER_ENDDATE, value) end_date = pyproperty(GetEndDate, SetEndDate) def GetContactsOnly(self): """Get the ContactsOnly value of the Vacation Responder object. Returns: The ContactsOnly value of this Vacation Responder object as a string or None. """ return self._GetProperty(VACATION_RESPONDER_CONTACTS_ONLY) def SetContactsOnly(self, value): """Set the ContactsOnly value of this Vacation Responder object. Args: value: string The new ContactsOnly value to give this object. """ self._SetProperty(VACATION_RESPONDER_CONTACTS_ONLY, value) contacts_only = pyproperty(GetContactsOnly, SetContactsOnly) def GetDomainOnly(self): """Get the DomainOnly value of the Vacation Responder object. Returns: The DomainOnly value of this Vacation Responder object as a string or None. """ return self._GetProperty(VACATION_RESPONDER_DOMAIN_ONLY) def SetDomainOnly(self, value): """Set the DomainOnly value of this Vacation Responder object. Args: value: string The new DomainOnly value to give this object. """ self._SetProperty(VACATION_RESPONDER_DOMAIN_ONLY, value) domain_only = pyproperty(GetDomainOnly, SetDomainOnly) def __init__(self, uri=None, enable=None, subject=None, message=None, start_date=None, end_date=None, contacts_only=None, domain_only=None, *args, **kwargs): """Constructs a new EmailSettingsVacationResponder object with the given arguments. Args: uri: string (optional) The uri of of this object for HTTP requests. enable: Boolean (optional) Whether to enable the vacation responder. subject: string (optional) The subject line of the vacation responder autoresponse. message: string (optional) The message body of the vacation responder autoresponse. start_date: string (optional) The start date of the vacation responder autoresponse end_date: string (optional) The end date of the vacation responder autoresponse contacts_only: Boolean (optional) Whether to only send autoresponses to known contacts. domain_only: Boolean (optional) Whether to only send autoresponses to users in the same primary domain . args: The other parameters to pass to gdata.entry.GDEntry constructor. kwargs: The other parameters to pass to gdata.entry.GDEntry constructor. """ super(EmailSettingsVacationResponder, self).__init__(*args, **kwargs) if uri: self.uri = uri if enable is not None: self.enable = str(enable) if subject: self.subject = subject if message: self.message = message if start_date: self.start_date = start_date if end_date: self.end_date = end_date if contacts_only is not None: self.contacts_only = str(contacts_only) if domain_only is not None: self.domain_only = str(domain_only) class EmailSettingsSignature(EmailSettingsEntry): """Represents a Signature in object form.""" def GetValue(self): """Get the value of the Signature object. Returns: The value of this Signature object as a string or None. """ value = self._GetProperty(SIGNATURE_VALUE) if value == ' ': # hack to support empty signature return '' else: return value def SetValue(self, value): """Set the name of this Signature object. Args: value: string The new signature value to give this object. """ if value == '': # hack to support empty signature value = ' ' self._SetProperty(SIGNATURE_VALUE, value) signature_value = pyproperty(GetValue, SetValue) def __init__(self, uri=None, signature=None, *args, **kwargs): """Constructs a new EmailSettingsSignature object with the given arguments. Args: uri: string (optional) The uri of of this object for HTTP requests. signature: string (optional) The signature to be appended to outgoing messages. args: The other parameters to pass to gdata.entry.GDEntry constructor. kwargs: The other parameters to pass to gdata.entry.GDEntry constructor. """ super(EmailSettingsSignature, self).__init__(*args, **kwargs) if uri: self.uri = uri if signature is not None: self.signature_value = signature class EmailSettingsLanguage(EmailSettingsEntry): """Represents Language Settings in object form.""" def GetLanguage(self): """Get the tag of the Language object. Returns: The tag of this Language object as a string or None. """ return self._GetProperty(LANGUAGE_TAG) def SetLanguage(self, value): """Set the tag of this Language object. Args: value: string The new tag value to give this object. """ self._SetProperty(LANGUAGE_TAG, value) language_tag = pyproperty(GetLanguage, SetLanguage) def __init__(self, uri=None, language=None, *args, **kwargs): """Constructs a new EmailSettingsLanguage object with the given arguments. Args: uri: string (optional) The uri of of this object for HTTP requests. language: string (optional) The language tag for Google Mail's display language. args: The other parameters to pass to gdata.entry.GDEntry constructor. kwargs: The other parameters to pass to gdata.entry.GDEntry constructor. """ super(EmailSettingsLanguage, self).__init__(*args, **kwargs) if uri: self.uri = uri if language: self.language_tag = language class EmailSettingsGeneral(EmailSettingsEntry): """Represents General Settings in object form.""" def GetPageSize(self): """Get the Page Size value of the General Settings object. Returns: The Page Size value of this General Settings object as a string or None. """ return self._GetProperty(GENERAL_PAGE_SIZE) def SetPageSize(self, value): """Set the Page Size value of this General Settings object. Args: value: string The new Page Size value to give this object. """ self._SetProperty(GENERAL_PAGE_SIZE, value) page_size = pyproperty(GetPageSize, SetPageSize) def GetShortcuts(self): """Get the Shortcuts value of the General Settings object. Returns: The Shortcuts value of this General Settings object as a string or None. """ return self._GetProperty(GENERAL_SHORTCUTS) def SetShortcuts(self, value): """Set the Shortcuts value of this General Settings object. Args: value: string The new Shortcuts value to give this object. """ self._SetProperty(GENERAL_SHORTCUTS, value) shortcuts = pyproperty(GetShortcuts, SetShortcuts) def GetArrows(self): """Get the Arrows value of the General Settings object. Returns: The Arrows value of this General Settings object as a string or None. """ return self._GetProperty(GENERAL_ARROWS) def SetArrows(self, value): """Set the Arrows value of this General Settings object. Args: value: string The new Arrows value to give this object. """ self._SetProperty(GENERAL_ARROWS, value) arrows = pyproperty(GetArrows, SetArrows) def GetSnippets(self): """Get the Snippets value of the General Settings object. Returns: The Snippets value of this General Settings object as a string or None. """ return self._GetProperty(GENERAL_SNIPPETS) def SetSnippets(self, value): """Set the Snippets value of this General Settings object. Args: value: string The new Snippets value to give this object. """ self._SetProperty(GENERAL_SNIPPETS, value) snippets = pyproperty(GetSnippets, SetSnippets) def GetUnicode(self): """Get the Unicode value of the General Settings object. Returns: The Unicode value of this General Settings object as a string or None. """ return self._GetProperty(GENERAL_UNICODE) def SetUnicode(self, value): """Set the Unicode value of this General Settings object. Args: value: string The new Unicode value to give this object. """ self._SetProperty(GENERAL_UNICODE, value) use_unicode = pyproperty(GetUnicode, SetUnicode) def __init__(self, uri=None, page_size=None, shortcuts=None, arrows=None, snippets=None, use_unicode=None, *args, **kwargs): """Constructs a new EmailSettingsGeneral object with the given arguments. Args: uri: string (optional) The uri of of this object for HTTP requests. page_size: int (optional) The number of conversations to be shown per page. shortcuts: Boolean (optional) Whether to enable keyboard shortcuts. arrows: Boolean (optional) Whether to display arrow-shaped personal indicators next to email sent specifically to the user. snippets: Boolean (optional) Whether to display snippets of the messages in the inbox and when searching. use_unicode: Boolean (optional) Whether to use UTF-8 (unicode) encoding for all outgoing messages. args: The other parameters to pass to gdata.entry.GDEntry constructor. kwargs: The other parameters to pass to gdata.entry.GDEntry constructor. """ super(EmailSettingsGeneral, self).__init__(*args, **kwargs) if uri: self.uri = uri if page_size is not None: self.page_size = str(page_size) if shortcuts is not None: self.shortcuts = str(shortcuts) if arrows is not None: self.arrows = str(arrows) if snippets is not None: self.snippets = str(snippets) if use_unicode is not None: self.use_unicode = str(use_unicode) class EmailSettingsDelegation(EmailSettingsEntry): """Represents an Email Settings delegation entry in object form.""" def GetAddress(self): """Get the email address of the delegated user. Returns: The email address of the delegated user as a string or None. """ return self._GetProperty(DELEGATION_ADDRESS) def SetAddress(self, value): """Set the email address of of the delegated user. Args: value: string The email address of another user on the same domain """ self._SetProperty(DELEGATION_ADDRESS, value) address = pyproperty(GetAddress, SetAddress) def __init__(self, uri=None, address=None, *args, **kwargs): """Constructs a new EmailSettingsDelegation object with the given arguments. Args: uri: string (optional) The uri of of this object for HTTP requests. address: string The email address of the delegated user. """ super(EmailSettingsDelegation, self).__init__(*args, **kwargs) if uri: self.uri = uri if address: self.address = address gdata-2.0.18/samples/apps/marketplace_sample/gdata/apps/emailsettings/__init__.py0000640036466300116100000000112312156622362030176 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright (C) 2008 Google # # 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. gdata-2.0.18/samples/apps/marketplace_sample/gdata/apps/emailsettings/client.py0000640036466300116100000005364712156622362027737 0ustar afshareng00000000000000#!/usr/bin/python2.4 # # Copyright 2010 Google Inc. All Rights Reserved. # # 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. """EmailSettingsClient simplifies Email Settings API calls. EmailSettingsClient extends gdata.client.GDClient to ease interaction with the Google Apps Email Settings API. These interactions include the ability to create labels, filters, aliases, and update web-clip, forwarding, POP, IMAP, vacation-responder, signature, language, and general settings, and retrieve labels, send-as, forwarding, pop, imap, vacation and signature settings. """ __author__ = 'Claudio Cherubino ' import urllib import gdata.apps.emailsettings.data import gdata.client # Email Settings URI template # The strings in this template are eventually replaced with the API version, # Google Apps domain name, username, and settingID, respectively. EMAIL_SETTINGS_URI_TEMPLATE = '/a/feeds/emailsettings/%s/%s/%s/%s' # The settingID value for the label requests SETTING_ID_LABEL = 'label' # The settingID value for the filter requests SETTING_ID_FILTER = 'filter' # The settingID value for the send-as requests SETTING_ID_SENDAS = 'sendas' # The settingID value for the webclip requests SETTING_ID_WEBCLIP = 'webclip' # The settingID value for the forwarding requests SETTING_ID_FORWARDING = 'forwarding' # The settingID value for the POP requests SETTING_ID_POP = 'pop' # The settingID value for the IMAP requests SETTING_ID_IMAP = 'imap' # The settingID value for the vacation responder requests SETTING_ID_VACATION_RESPONDER = 'vacation' # The settingID value for the signature requests SETTING_ID_SIGNATURE = 'signature' # The settingID value for the language requests SETTING_ID_LANGUAGE = 'language' # The settingID value for the general requests SETTING_ID_GENERAL = 'general' # The settingID value for the delegation requests SETTING_ID_DELEGATION = 'delegation' # The KEEP action for the email settings ACTION_KEEP = 'KEEP' # The ARCHIVE action for the email settings ACTION_ARCHIVE = 'ARCHIVE' # The DELETE action for the email settings ACTION_DELETE = 'DELETE' # The ALL_MAIL setting for POP enable_for property POP_ENABLE_FOR_ALL_MAIL = 'ALL_MAIL' # The MAIL_FROM_NOW_ON setting for POP enable_for property POP_ENABLE_FOR_MAIL_FROM_NOW_ON = 'MAIL_FROM_NOW_ON' class EmailSettingsClient(gdata.client.GDClient): """Client extension for the Google Email Settings API service. Attributes: host: string The hostname for the Email Settings API service. api_version: string The version of the Email Settings API. """ host = 'apps-apis.google.com' api_version = '2.0' auth_service = 'apps' auth_scopes = gdata.gauth.AUTH_SCOPES['apps'] ssl = True def __init__(self, domain, auth_token=None, **kwargs): """Constructs a new client for the Email Settings API. Args: domain: string The Google Apps domain with Email Settings. auth_token: (optional) gdata.gauth.ClientLoginToken, AuthSubToken, or OAuthToken which authorizes this client to edit the email settings. kwargs: The other parameters to pass to the gdata.client.GDClient constructor. """ gdata.client.GDClient.__init__(self, auth_token=auth_token, **kwargs) self.domain = domain def make_email_settings_uri(self, username, setting_id): """Creates the URI for the Email Settings API call. Using this client's Google Apps domain, create the URI to setup email settings for the given user in that domain. If params are provided, append them as GET params. Args: username: string The name of the user affected by this setting. setting_id: string The key of the setting to be configured. Returns: A string giving the URI for Email Settings API calls for this client's Google Apps domain. """ if '@' in username: username, domain = username.split('@', 1) else: domain = self.domain uri = EMAIL_SETTINGS_URI_TEMPLATE % (self.api_version, domain, username, setting_id) return uri MakeEmailSettingsUri = make_email_settings_uri def create_label(self, username, name, **kwargs): """Creates a label with the given properties. Args: username: string The name of the user. name: string The name of the label. kwargs: The other parameters to pass to gdata.client.GDClient.post(). Returns: gdata.apps.emailsettings.data.EmailSettingsLabel of the new resource. """ uri = self.MakeEmailSettingsUri(username=username, setting_id=SETTING_ID_LABEL) new_label = gdata.apps.emailsettings.data.EmailSettingsLabel( uri=uri, name=name) return self.post(new_label, uri, **kwargs) CreateLabel = create_label def retrieve_labels(self, username, **kwargs): """Retrieves email labels for the specified username Args: username: string The name of the user to get the labels for Returns: A gdata.data.GDFeed of the user's email labels """ uri = self.MakeEmailSettingsUri(username=username, setting_id=SETTING_ID_LABEL) return self.GetFeed(uri, auth_token=None, query=None, **kwargs) RetrieveLabels = retrieve_labels def delete_label(self, username, label, **kwargs): """Delete a label from the specified account. Args: username: string Name of the user label: string Name of the label to be deleted Returns: An atom.http_core.HttpResponse() with the result of the request """ uri = self.MakeEmailSettingsUri(username=username, setting_id=SETTING_ID_LABEL) uri = '/'.join([uri, urllib.quote_plus(label)]) return self.delete(uri, **kwargs) DeleteLabel = delete_label def create_filter(self, username, from_address=None, to_address=None, subject=None, has_the_word=None, does_not_have_the_word=None, has_attachments=None, label=None, mark_as_read=None, archive=None, **kwargs): """Creates a filter with the given properties. Args: username: string The name of the user. from_address: string The source email address for the filter. to_address: string (optional) The destination email address for the filter. subject: string (optional) The value the email must have in its subject to be filtered. has_the_word: string (optional) The value the email must have in its subject or body to be filtered. does_not_have_the_word: string (optional) The value the email cannot have in its subject or body to be filtered. has_attachments: string (optional) A boolean string representing whether the email must have an attachment to be filtered. label: string (optional) The name of the label to apply to messages matching the filter criteria. mark_as_read: Boolean (optional) Whether or not to mark messages matching the filter criteria as read. archive: Boolean (optional) Whether or not to move messages matching to Archived state. kwargs: The other parameters to pass to gdata.client.GDClient.post(). Returns: gdata.apps.emailsettings.data.EmailSettingsFilter of the new resource. """ uri = self.MakeEmailSettingsUri(username=username, setting_id=SETTING_ID_FILTER) new_filter = gdata.apps.emailsettings.data.EmailSettingsFilter( uri=uri, from_address=from_address, to_address=to_address, subject=subject, has_the_word=has_the_word, does_not_have_the_word=does_not_have_the_word, has_attachments=has_attachments, label=label, mark_as_read=mark_as_read, archive=archive) return self.post(new_filter, uri, **kwargs) CreateFilter = create_filter def create_send_as(self, username, name, address, reply_to=None, make_default=None, **kwargs): """Creates a send-as alias with the given properties. Args: username: string The name of the user. name: string The name that will appear in the "From" field. address: string The email address that appears as the origination address for emails sent by this user. reply_to: string (optional) The address to be used as the reply-to address in email sent using the alias. make_default: Boolean (optional) Whether or not this alias should become the default alias for this user. kwargs: The other parameters to pass to gdata.client.GDClient.post(). Returns: gdata.apps.emailsettings.data.EmailSettingsSendAsAlias of the new resource. """ uri = self.MakeEmailSettingsUri(username=username, setting_id=SETTING_ID_SENDAS) new_alias = gdata.apps.emailsettings.data.EmailSettingsSendAsAlias( uri=uri, name=name, address=address, reply_to=reply_to, make_default=make_default) return self.post(new_alias, uri, **kwargs) CreateSendAs = create_send_as def retrieve_send_as(self, username, **kwargs): """Retrieves send-as aliases for the specified username Args: username: string The name of the user to get the send-as for Returns: A gdata.data.GDFeed of the user's send-as alias settings """ uri = self.MakeEmailSettingsUri(username=username, setting_id=SETTING_ID_SENDAS) return self.GetFeed(uri, auth_token=None, query=None, **kwargs) RetrieveSendAs = retrieve_send_as def update_webclip(self, username, enable, **kwargs): """Enable/Disable Google Mail web clip. Args: username: string The name of the user. enable: Boolean Whether to enable showing Web clips. kwargs: The other parameters to pass to the update method. Returns: gdata.apps.emailsettings.data.EmailSettingsWebClip of the updated resource. """ uri = self.MakeEmailSettingsUri(username=username, setting_id=SETTING_ID_WEBCLIP) new_webclip = gdata.apps.emailsettings.data.EmailSettingsWebClip( uri=uri, enable=enable) return self.update(new_webclip, **kwargs) UpdateWebclip = update_webclip def update_forwarding(self, username, enable, forward_to=None, action=None, **kwargs): """Update Google Mail Forwarding settings. Args: username: string The name of the user. enable: Boolean Whether to enable incoming email forwarding. forward_to: (optional) string The address email will be forwarded to. action: string (optional) The action to perform after forwarding an email (ACTION_KEEP, ACTION_ARCHIVE, ACTION_DELETE). kwargs: The other parameters to pass to the update method. Returns: gdata.apps.emailsettings.data.EmailSettingsForwarding of the updated resource """ uri = self.MakeEmailSettingsUri(username=username, setting_id=SETTING_ID_FORWARDING) new_forwarding = gdata.apps.emailsettings.data.EmailSettingsForwarding( uri=uri, enable=enable, forward_to=forward_to, action=action) return self.update(new_forwarding, **kwargs) UpdateForwarding = update_forwarding def retrieve_forwarding(self, username, **kwargs): """Retrieves forwarding settings for the specified username Args: username: string The name of the user to get the forwarding settings for Returns: A gdata.data.GDEntry of the user's email forwarding settings """ uri = self.MakeEmailSettingsUri(username=username, setting_id=SETTING_ID_FORWARDING) return self.GetEntry(uri, auth_token=None, query=None, **kwargs) RetrieveForwarding = retrieve_forwarding def update_pop(self, username, enable, enable_for=None, action=None, **kwargs): """Update Google Mail POP settings. Args: username: string The name of the user. enable: Boolean Whether to enable incoming POP3 access. enable_for: string (optional) Whether to enable POP3 for all mail (POP_ENABLE_FOR_ALL_MAIL), or mail from now on (POP_ENABLE_FOR_MAIL_FROM_NOW_ON). action: string (optional) What Google Mail should do with its copy of the email after it is retrieved using POP (ACTION_KEEP, ACTION_ARCHIVE, ACTION_DELETE). kwargs: The other parameters to pass to the update method. Returns: gdata.apps.emailsettings.data.EmailSettingsPop of the updated resource. """ uri = self.MakeEmailSettingsUri(username=username, setting_id=SETTING_ID_POP) new_pop = gdata.apps.emailsettings.data.EmailSettingsPop( uri=uri, enable=enable, enable_for=enable_for, action=action) return self.update(new_pop, **kwargs) UpdatePop = update_pop def retrieve_pop(self, username, **kwargs): """Retrieves POP settings for the specified username Args: username: string The name of the user to get the POP settings for Returns: A gdata.data.GDEntry of the user's POP settings """ uri = self.MakeEmailSettingsUri(username=username, setting_id=SETTING_ID_POP) return self.GetEntry(uri, auth_token=None, query=None, **kwargs) RetrievePop = retrieve_pop def update_imap(self, username, enable, **kwargs): """Update Google Mail IMAP settings. Args: username: string The name of the user. enable: Boolean Whether to enable IMAP access.language kwargs: The other parameters to pass to the update method. Returns: gdata.apps.emailsettings.data.EmailSettingsImap of the updated resource. """ uri = self.MakeEmailSettingsUri(username=username, setting_id=SETTING_ID_IMAP) new_imap = gdata.apps.emailsettings.data.EmailSettingsImap( uri=uri, enable=enable) return self.update(new_imap, **kwargs) UpdateImap = update_imap def retrieve_imap(self, username, **kwargs): """Retrieves imap settings for the specified username Args: username: string The name of the user to get the imap settings for Returns: A gdata.data.GDEntry of the user's IMAP settings """ uri = self.MakeEmailSettingsUri(username=username, setting_id=SETTING_ID_IMAP) return self.GetEntry(uri, auth_token=None, query=None, **kwargs) RetrieveImap = retrieve_imap def update_vacation(self, username, enable, subject=None, message=None, start_date=None, end_date=None, contacts_only=None, domain_only=None, **kwargs): """Update Google Mail vacation-responder settings. Args: username: string The name of the user. enable: Boolean Whether to enable the vacation responder. subject: string (optional) The subject line of the vacation responder autoresponse. message: string (optional) The message body of the vacation responder autoresponse. startDate: string (optional) The start date of the vacation responder autoresponse. endDate: string (optional) The end date of the vacation responder autoresponse. contacts_only: Boolean (optional) Whether to only send autoresponses to known contacts. domain_only: Boolean (optional) Whether to only send autoresponses to users in the primary domain. kwargs: The other parameters to pass to the update method. Returns: gdata.apps.emailsettings.data.EmailSettingsVacationResponder of the updated resource. """ uri = self.MakeEmailSettingsUri(username=username, setting_id=SETTING_ID_VACATION_RESPONDER) new_vacation = gdata.apps.emailsettings.data.EmailSettingsVacationResponder( uri=uri, enable=enable, subject=subject, message=message, start_date=start_date, end_date=end_date, contacts_only=contacts_only, domain_only=domain_only) return self.update(new_vacation, **kwargs) UpdateVacation = update_vacation def retrieve_vacation(self, username, **kwargs): """Retrieves vacation settings for the specified username Args: username: string The name of the user to get the vacation settings for Returns: A gdata.data.GDEntry of the user's vacation auto-responder settings """ uri = self.MakeEmailSettingsUri(username=username, setting_id=SETTING_ID_VACATION_RESPONDER) return self.GetEntry(uri, auth_token=None, query=None, **kwargs) RetrieveVacation = retrieve_vacation def update_signature(self, username, signature, **kwargs): """Update Google Mail signature. Args: username: string The name of the user. signature: string The signature to be appended to outgoing messages. kwargs: The other parameters to pass to the update method. Returns: gdata.apps.emailsettings.data.EmailSettingsSignature of the updated resource. """ uri = self.MakeEmailSettingsUri(username=username, setting_id=SETTING_ID_SIGNATURE) new_signature = gdata.apps.emailsettings.data.EmailSettingsSignature( uri=uri, signature=signature) return self.update(new_signature, **kwargs) UpdateSignature = update_signature def retrieve_signature(self, username, **kwargs): """Retrieves signature settings for the specified username Args: username: string The name of the user to get the signature settings for Returns: A gdata.data.GDEntry of the user's signature settings """ uri = self.MakeEmailSettingsUri(username=username, setting_id=SETTING_ID_SIGNATURE) return self.GetEntry(uri, auth_token=None, query=None, **kwargs) RetrieveSignature = retrieve_signature def update_language(self, username, language, **kwargs): """Update Google Mail language settings. Args: username: string The name of the user. language: string The language tag for Google Mail's display language. kwargs: The other parameters to pass to the update method. Returns: gdata.apps.emailsettings.data.EmailSettingsLanguage of the updated resource. """ uri = self.MakeEmailSettingsUri(username=username, setting_id=SETTING_ID_LANGUAGE) new_language = gdata.apps.emailsettings.data.EmailSettingsLanguage( uri=uri, language=language) return self.update(new_language, **kwargs) UpdateLanguage = update_language def update_general_settings(self, username, page_size=None, shortcuts=None, arrows=None, snippets=None, use_unicode=None, **kwargs): """Update Google Mail general settings. Args: username: string The name of the user. page_size: int (optional) The number of conversations to be shown per page. shortcuts: Boolean (optional) Whether to enable keyboard shortcuts. arrows: Boolean (optional) Whether to display arrow-shaped personal indicators next to email sent specifically to the user. snippets: Boolean (optional) Whether to display snippets of the messages in the inbox and when searching. use_unicode: Boolean (optional) Whether to use UTF-8 (unicode) encoding for all outgoing messages. kwargs: The other parameters to pass to the update method. Returns: gdata.apps.emailsettings.data.EmailSettingsGeneral of the updated resource. """ uri = self.MakeEmailSettingsUri(username=username, setting_id=SETTING_ID_GENERAL) new_general = gdata.apps.emailsettings.data.EmailSettingsGeneral( uri=uri, page_size=page_size, shortcuts=shortcuts, arrows=arrows, snippets=snippets, use_unicode=use_unicode) return self.update(new_general, **kwargs) UpdateGeneralSettings = update_general_settings def add_email_delegate(self, username, address, **kwargs): """Add an email delegate to the mail account Args: username: string The name of the user address: string The email address of the delegated account """ uri = self.MakeEmailSettingsUri(username=username, setting_id=SETTING_ID_DELEGATION) new_delegation = gdata.apps.emailsettings.data.EmailSettingsDelegation( uri=uri, address=address) return self.post(new_delegation, uri, **kwargs) AddEmailDelegate = add_email_delegate def retrieve_email_delegates(self, username, **kwargs): """Retrieve a feed of the email delegates for the specified username Args: username: string The name of the user to get the email delegates for Returns: A gdata.data.GDFeed of the user's email delegates """ uri = self.MakeEmailSettingsUri(username=username, setting_id=SETTING_ID_DELEGATION) return self.GetFeed(uri, auth_token=None, query=None, **kwargs) RetrieveEmailDelegates = retrieve_email_delegates def delete_email_delegate(self, username, address, **kwargs): """Delete an email delegate from the specified account Args: username: string The name of the user address: string The email address of the delegated account """ uri = self.MakeEmailSettingsUri(username=username, setting_id=SETTING_ID_DELEGATION) uri = uri + '/' + address return self.delete(uri, **kwargs) DeleteEmailDelegate = delete_email_delegate gdata-2.0.18/samples/apps/marketplace_sample/gdata/apps/emailsettings/service.py0000640036466300116100000002135312156622362030106 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright (C) 2008 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. """Allow Google Apps domain administrators to set users' email settings. EmailSettingsService: Set various email settings. """ __author__ = 'google-apps-apis@googlegroups.com' import gdata.apps import gdata.apps.service import gdata.service API_VER='2.0' # Forwarding and POP3 options KEEP='KEEP' ARCHIVE='ARCHIVE' DELETE='DELETE' ALL_MAIL='ALL_MAIL' MAIL_FROM_NOW_ON='MAIL_FROM_NOW_ON' class EmailSettingsService(gdata.apps.service.PropertyService): """Client for the Google Apps Email Settings service.""" def _serviceUrl(self, setting_id, username, domain=None): if domain is None: domain = self.domain return '/a/feeds/emailsettings/%s/%s/%s/%s' % (API_VER, domain, username, setting_id) def CreateLabel(self, username, label): """Create a label. Args: username: User to create label for. label: Label to create. Returns: A dict containing the result of the create operation. """ uri = self._serviceUrl('label', username) properties = {'label': label} return self._PostProperties(uri, properties) def CreateFilter(self, username, from_=None, to=None, subject=None, has_the_word=None, does_not_have_the_word=None, has_attachment=None, label=None, should_mark_as_read=None, should_archive=None): """Create a filter. Args: username: User to create filter for. from_: Filter from string. to: Filter to string. subject: Filter subject. has_the_word: Words to filter in. does_not_have_the_word: Words to filter out. has_attachment: Boolean for message having attachment. label: Label to apply. should_mark_as_read: Boolean for marking message as read. should_archive: Boolean for archiving message. Returns: A dict containing the result of the create operation. """ uri = self._serviceUrl('filter', username) properties = {} properties['from'] = from_ properties['to'] = to properties['subject'] = subject properties['hasTheWord'] = has_the_word properties['doesNotHaveTheWord'] = does_not_have_the_word properties['hasAttachment'] = gdata.apps.service._bool2str(has_attachment) properties['label'] = label properties['shouldMarkAsRead'] = gdata.apps.service._bool2str(should_mark_as_read) properties['shouldArchive'] = gdata.apps.service._bool2str(should_archive) return self._PostProperties(uri, properties) def CreateSendAsAlias(self, username, name, address, reply_to=None, make_default=None): """Create alias to send mail as. Args: username: User to create alias for. name: Name of alias. address: Email address to send from. reply_to: Email address to reply to. make_default: Boolean for whether this is the new default sending alias. Returns: A dict containing the result of the create operation. """ uri = self._serviceUrl('sendas', username) properties = {} properties['name'] = name properties['address'] = address properties['replyTo'] = reply_to properties['makeDefault'] = gdata.apps.service._bool2str(make_default) return self._PostProperties(uri, properties) def UpdateWebClipSettings(self, username, enable): """Update WebClip Settings Args: username: User to update forwarding for. enable: Boolean whether to enable Web Clip. Returns: A dict containing the result of the update operation. """ uri = self._serviceUrl('webclip', username) properties = {} properties['enable'] = gdata.apps.service._bool2str(enable) return self._PutProperties(uri, properties) def UpdateForwarding(self, username, enable, forward_to=None, action=None): """Update forwarding settings. Args: username: User to update forwarding for. enable: Boolean whether to enable this forwarding rule. forward_to: Email address to forward to. action: Action to take after forwarding. Returns: A dict containing the result of the update operation. """ uri = self._serviceUrl('forwarding', username) properties = {} properties['enable'] = gdata.apps.service._bool2str(enable) if enable is True: properties['forwardTo'] = forward_to properties['action'] = action return self._PutProperties(uri, properties) def UpdatePop(self, username, enable, enable_for=None, action=None): """Update POP3 settings. Args: username: User to update POP3 settings for. enable: Boolean whether to enable POP3. enable_for: Which messages to make available via POP3. action: Action to take after user retrieves email via POP3. Returns: A dict containing the result of the update operation. """ uri = self._serviceUrl('pop', username) properties = {} properties['enable'] = gdata.apps.service._bool2str(enable) if enable is True: properties['enableFor'] = enable_for properties['action'] = action return self._PutProperties(uri, properties) def UpdateImap(self, username, enable): """Update IMAP settings. Args: username: User to update IMAP settings for. enable: Boolean whether to enable IMAP. Returns: A dict containing the result of the update operation. """ uri = self._serviceUrl('imap', username) properties = {'enable': gdata.apps.service._bool2str(enable)} return self._PutProperties(uri, properties) def UpdateVacation(self, username, enable, subject=None, message=None, contacts_only=None): """Update vacation settings. Args: username: User to update vacation settings for. enable: Boolean whether to enable vacation responses. subject: Vacation message subject. message: Vacation message body. contacts_only: Boolean whether to send message only to contacts. Returns: A dict containing the result of the update operation. """ uri = self._serviceUrl('vacation', username) properties = {} properties['enable'] = gdata.apps.service._bool2str(enable) if enable is True: properties['subject'] = subject properties['message'] = message properties['contactsOnly'] = gdata.apps.service._bool2str(contacts_only) return self._PutProperties(uri, properties) def UpdateSignature(self, username, signature): """Update signature. Args: username: User to update signature for. signature: Signature string. Returns: A dict containing the result of the update operation. """ uri = self._serviceUrl('signature', username) properties = {'signature': signature} return self._PutProperties(uri, properties) def UpdateLanguage(self, username, language): """Update user interface language. Args: username: User to update language for. language: Language code. Returns: A dict containing the result of the update operation. """ uri = self._serviceUrl('language', username) properties = {'language': language} return self._PutProperties(uri, properties) def UpdateGeneral(self, username, page_size=None, shortcuts=None, arrows=None, snippets=None, unicode=None): """Update general settings. Args: username: User to update general settings for. page_size: Number of messages to show. shortcuts: Boolean whether shortcuts are enabled. arrows: Boolean whether arrows are enabled. snippets: Boolean whether snippets are enabled. unicode: Wheter unicode is enabled. Returns: A dict containing the result of the update operation. """ uri = self._serviceUrl('general', username) properties = {} if page_size != None: properties['pageSize'] = str(page_size) if shortcuts != None: properties['shortcuts'] = gdata.apps.service._bool2str(shortcuts) if arrows != None: properties['arrows'] = gdata.apps.service._bool2str(arrows) if snippets != None: properties['snippets'] = gdata.apps.service._bool2str(snippets) if unicode != None: properties['unicode'] = gdata.apps.service._bool2str(unicode) return self._PutProperties(uri, properties) gdata-2.0.18/samples/apps/marketplace_sample/gdata/apps/data.py0000640036466300116100000000350712156622362024510 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright 2010 Google Inc. All Rights Reserved. # # 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. """Data model classes for the Provisioning API.""" __author__ = 'Shraddha Gupta shraddhag@google.com>' import atom.core import atom.data import gdata.apps import gdata.data class Login(atom.core.XmlElement): _qname = gdata.apps.APPS_TEMPLATE % 'login' user_name = 'userName' password = 'password' hash_function_name = 'hashFunctionName' suspended = 'suspended' admin = 'admin' agreed_to_terms = 'agreedToTerms' change_password = 'changePasswordAtNextLogin' ip_whitelisted = 'ipWhitelisted' class Name(atom.core.XmlElement): _qname = gdata.apps.APPS_TEMPLATE % 'name' given_name = 'givenName' family_name = 'familyName' class Quota(atom.core.XmlElement): _qname = gdata.apps.APPS_TEMPLATE % 'quota' limit = 'limit' class UserEntry(gdata.data.GDEntry): _qname = atom.data.ATOM_TEMPLATE % 'entry' login = Login name = Name quota = Quota class UserFeed(gdata.data.GDFeed): entry = [UserEntry] class Nickname(atom.core.XmlElement): _qname = gdata.apps.APPS_TEMPLATE % 'nickname' name = 'name' class NicknameEntry(gdata.data.GDEntry): _qname = atom.data.ATOM_TEMPLATE % 'entry' nickname = Nickname login = Login class NicknameFeed(gdata.data.GDFeed): entry = [NicknameEntry] gdata-2.0.18/samples/apps/marketplace_sample/gdata/apps/__init__.py0000640036466300116100000005123012156622362025332 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright (C) 2007 SIOS Technology, 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. """Contains objects used with Google Apps.""" __author__ = 'tmatsuo@sios.com (Takashi MATSUO)' import atom import gdata # XML namespaces which are often used in Google Apps entity. APPS_NAMESPACE = 'http://schemas.google.com/apps/2006' APPS_TEMPLATE = '{http://schemas.google.com/apps/2006}%s' class EmailList(atom.AtomBase): """The Google Apps EmailList element""" _tag = 'emailList' _namespace = APPS_NAMESPACE _children = atom.AtomBase._children.copy() _attributes = atom.AtomBase._attributes.copy() _attributes['name'] = 'name' def __init__(self, name=None, extension_elements=None, extension_attributes=None, text=None): self.name = name self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} def EmailListFromString(xml_string): return atom.CreateClassFromXMLString(EmailList, xml_string) class Who(atom.AtomBase): """The Google Apps Who element""" _tag = 'who' _namespace = gdata.GDATA_NAMESPACE _children = atom.AtomBase._children.copy() _attributes = atom.AtomBase._attributes.copy() _attributes['rel'] = 'rel' _attributes['email'] = 'email' def __init__(self, rel=None, email=None, extension_elements=None, extension_attributes=None, text=None): self.rel = rel self.email = email self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} def WhoFromString(xml_string): return atom.CreateClassFromXMLString(Who, xml_string) class Login(atom.AtomBase): """The Google Apps Login element""" _tag = 'login' _namespace = APPS_NAMESPACE _children = atom.AtomBase._children.copy() _attributes = atom.AtomBase._attributes.copy() _attributes['userName'] = 'user_name' _attributes['password'] = 'password' _attributes['suspended'] = 'suspended' _attributes['admin'] = 'admin' _attributes['changePasswordAtNextLogin'] = 'change_password' _attributes['agreedToTerms'] = 'agreed_to_terms' _attributes['ipWhitelisted'] = 'ip_whitelisted' _attributes['hashFunctionName'] = 'hash_function_name' def __init__(self, user_name=None, password=None, suspended=None, ip_whitelisted=None, hash_function_name=None, admin=None, change_password=None, agreed_to_terms=None, extension_elements=None, extension_attributes=None, text=None): self.user_name = user_name self.password = password self.suspended = suspended self.admin = admin self.change_password = change_password self.agreed_to_terms = agreed_to_terms self.ip_whitelisted = ip_whitelisted self.hash_function_name = hash_function_name self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} def LoginFromString(xml_string): return atom.CreateClassFromXMLString(Login, xml_string) class Quota(atom.AtomBase): """The Google Apps Quota element""" _tag = 'quota' _namespace = APPS_NAMESPACE _children = atom.AtomBase._children.copy() _attributes = atom.AtomBase._attributes.copy() _attributes['limit'] = 'limit' def __init__(self, limit=None, extension_elements=None, extension_attributes=None, text=None): self.limit = limit self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} def QuotaFromString(xml_string): return atom.CreateClassFromXMLString(Quota, xml_string) class Name(atom.AtomBase): """The Google Apps Name element""" _tag = 'name' _namespace = APPS_NAMESPACE _children = atom.AtomBase._children.copy() _attributes = atom.AtomBase._attributes.copy() _attributes['familyName'] = 'family_name' _attributes['givenName'] = 'given_name' def __init__(self, family_name=None, given_name=None, extension_elements=None, extension_attributes=None, text=None): self.family_name = family_name self.given_name = given_name self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} def NameFromString(xml_string): return atom.CreateClassFromXMLString(Name, xml_string) class Nickname(atom.AtomBase): """The Google Apps Nickname element""" _tag = 'nickname' _namespace = APPS_NAMESPACE _children = atom.AtomBase._children.copy() _attributes = atom.AtomBase._attributes.copy() _attributes['name'] = 'name' def __init__(self, name=None, extension_elements=None, extension_attributes=None, text=None): self.name = name self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} def NicknameFromString(xml_string): return atom.CreateClassFromXMLString(Nickname, xml_string) class NicknameEntry(gdata.GDataEntry): """A Google Apps flavor of an Atom Entry for Nickname""" _tag = 'entry' _namespace = atom.ATOM_NAMESPACE _children = gdata.GDataEntry._children.copy() _attributes = gdata.GDataEntry._attributes.copy() _children['{%s}login' % APPS_NAMESPACE] = ('login', Login) _children['{%s}nickname' % APPS_NAMESPACE] = ('nickname', Nickname) def __init__(self, author=None, category=None, content=None, atom_id=None, link=None, published=None, title=None, updated=None, login=None, nickname=None, extended_property=None, extension_elements=None, extension_attributes=None, text=None): gdata.GDataEntry.__init__(self, author=author, category=category, content=content, atom_id=atom_id, link=link, published=published, title=title, updated=updated) self.login = login self.nickname = nickname self.extended_property = extended_property or [] self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} def NicknameEntryFromString(xml_string): return atom.CreateClassFromXMLString(NicknameEntry, xml_string) class NicknameFeed(gdata.GDataFeed, gdata.LinkFinder): """A Google Apps Nickname feed flavor of an Atom Feed""" _tag = 'feed' _namespace = atom.ATOM_NAMESPACE _children = gdata.GDataFeed._children.copy() _attributes = gdata.GDataFeed._attributes.copy() _children['{%s}entry' % atom.ATOM_NAMESPACE] = ('entry', [NicknameEntry]) def __init__(self, author=None, category=None, contributor=None, generator=None, icon=None, atom_id=None, link=None, logo=None, rights=None, subtitle=None, title=None, updated=None, entry=None, total_results=None, start_index=None, items_per_page=None, extension_elements=None, extension_attributes=None, text=None): gdata.GDataFeed.__init__(self, author=author, category=category, contributor=contributor, generator=generator, icon=icon, atom_id=atom_id, link=link, logo=logo, rights=rights, subtitle=subtitle, title=title, updated=updated, entry=entry, total_results=total_results, start_index=start_index, items_per_page=items_per_page, extension_elements=extension_elements, extension_attributes=extension_attributes, text=text) def NicknameFeedFromString(xml_string): return atom.CreateClassFromXMLString(NicknameFeed, xml_string) class UserEntry(gdata.GDataEntry): """A Google Apps flavor of an Atom Entry""" _tag = 'entry' _namespace = atom.ATOM_NAMESPACE _children = gdata.GDataEntry._children.copy() _attributes = gdata.GDataEntry._attributes.copy() _children['{%s}login' % APPS_NAMESPACE] = ('login', Login) _children['{%s}name' % APPS_NAMESPACE] = ('name', Name) _children['{%s}quota' % APPS_NAMESPACE] = ('quota', Quota) # This child may already be defined in GDataEntry, confirm before removing. _children['{%s}feedLink' % gdata.GDATA_NAMESPACE] = ('feed_link', [gdata.FeedLink]) _children['{%s}who' % gdata.GDATA_NAMESPACE] = ('who', Who) def __init__(self, author=None, category=None, content=None, atom_id=None, link=None, published=None, title=None, updated=None, login=None, name=None, quota=None, who=None, feed_link=None, extended_property=None, extension_elements=None, extension_attributes=None, text=None): gdata.GDataEntry.__init__(self, author=author, category=category, content=content, atom_id=atom_id, link=link, published=published, title=title, updated=updated) self.login = login self.name = name self.quota = quota self.who = who self.feed_link = feed_link or [] self.extended_property = extended_property or [] self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} def UserEntryFromString(xml_string): return atom.CreateClassFromXMLString(UserEntry, xml_string) class UserFeed(gdata.GDataFeed, gdata.LinkFinder): """A Google Apps User feed flavor of an Atom Feed""" _tag = 'feed' _namespace = atom.ATOM_NAMESPACE _children = gdata.GDataFeed._children.copy() _attributes = gdata.GDataFeed._attributes.copy() _children['{%s}entry' % atom.ATOM_NAMESPACE] = ('entry', [UserEntry]) def __init__(self, author=None, category=None, contributor=None, generator=None, icon=None, atom_id=None, link=None, logo=None, rights=None, subtitle=None, title=None, updated=None, entry=None, total_results=None, start_index=None, items_per_page=None, extension_elements=None, extension_attributes=None, text=None): gdata.GDataFeed.__init__(self, author=author, category=category, contributor=contributor, generator=generator, icon=icon, atom_id=atom_id, link=link, logo=logo, rights=rights, subtitle=subtitle, title=title, updated=updated, entry=entry, total_results=total_results, start_index=start_index, items_per_page=items_per_page, extension_elements=extension_elements, extension_attributes=extension_attributes, text=text) def UserFeedFromString(xml_string): return atom.CreateClassFromXMLString(UserFeed, xml_string) class EmailListEntry(gdata.GDataEntry): """A Google Apps EmailList flavor of an Atom Entry""" _tag = 'entry' _namespace = atom.ATOM_NAMESPACE _children = gdata.GDataEntry._children.copy() _attributes = gdata.GDataEntry._attributes.copy() _children['{%s}emailList' % APPS_NAMESPACE] = ('email_list', EmailList) # Might be able to remove this _children entry. _children['{%s}feedLink' % gdata.GDATA_NAMESPACE] = ('feed_link', [gdata.FeedLink]) def __init__(self, author=None, category=None, content=None, atom_id=None, link=None, published=None, title=None, updated=None, email_list=None, feed_link=None, extended_property=None, extension_elements=None, extension_attributes=None, text=None): gdata.GDataEntry.__init__(self, author=author, category=category, content=content, atom_id=atom_id, link=link, published=published, title=title, updated=updated) self.email_list = email_list self.feed_link = feed_link or [] self.extended_property = extended_property or [] self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} def EmailListEntryFromString(xml_string): return atom.CreateClassFromXMLString(EmailListEntry, xml_string) class EmailListFeed(gdata.GDataFeed, gdata.LinkFinder): """A Google Apps EmailList feed flavor of an Atom Feed""" _tag = 'feed' _namespace = atom.ATOM_NAMESPACE _children = gdata.GDataFeed._children.copy() _attributes = gdata.GDataFeed._attributes.copy() _children['{%s}entry' % atom.ATOM_NAMESPACE] = ('entry', [EmailListEntry]) def __init__(self, author=None, category=None, contributor=None, generator=None, icon=None, atom_id=None, link=None, logo=None, rights=None, subtitle=None, title=None, updated=None, entry=None, total_results=None, start_index=None, items_per_page=None, extension_elements=None, extension_attributes=None, text=None): gdata.GDataFeed.__init__(self, author=author, category=category, contributor=contributor, generator=generator, icon=icon, atom_id=atom_id, link=link, logo=logo, rights=rights, subtitle=subtitle, title=title, updated=updated, entry=entry, total_results=total_results, start_index=start_index, items_per_page=items_per_page, extension_elements=extension_elements, extension_attributes=extension_attributes, text=text) def EmailListFeedFromString(xml_string): return atom.CreateClassFromXMLString(EmailListFeed, xml_string) class EmailListRecipientEntry(gdata.GDataEntry): """A Google Apps EmailListRecipient flavor of an Atom Entry""" _tag = 'entry' _namespace = atom.ATOM_NAMESPACE _children = gdata.GDataEntry._children.copy() _attributes = gdata.GDataEntry._attributes.copy() _children['{%s}who' % gdata.GDATA_NAMESPACE] = ('who', Who) def __init__(self, author=None, category=None, content=None, atom_id=None, link=None, published=None, title=None, updated=None, who=None, extended_property=None, extension_elements=None, extension_attributes=None, text=None): gdata.GDataEntry.__init__(self, author=author, category=category, content=content, atom_id=atom_id, link=link, published=published, title=title, updated=updated) self.who = who self.extended_property = extended_property or [] self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} def EmailListRecipientEntryFromString(xml_string): return atom.CreateClassFromXMLString(EmailListRecipientEntry, xml_string) class EmailListRecipientFeed(gdata.GDataFeed, gdata.LinkFinder): """A Google Apps EmailListRecipient feed flavor of an Atom Feed""" _tag = 'feed' _namespace = atom.ATOM_NAMESPACE _children = gdata.GDataFeed._children.copy() _attributes = gdata.GDataFeed._attributes.copy() _children['{%s}entry' % atom.ATOM_NAMESPACE] = ('entry', [EmailListRecipientEntry]) def __init__(self, author=None, category=None, contributor=None, generator=None, icon=None, atom_id=None, link=None, logo=None, rights=None, subtitle=None, title=None, updated=None, entry=None, total_results=None, start_index=None, items_per_page=None, extension_elements=None, extension_attributes=None, text=None): gdata.GDataFeed.__init__(self, author=author, category=category, contributor=contributor, generator=generator, icon=icon, atom_id=atom_id, link=link, logo=logo, rights=rights, subtitle=subtitle, title=title, updated=updated, entry=entry, total_results=total_results, start_index=start_index, items_per_page=items_per_page, extension_elements=extension_elements, extension_attributes=extension_attributes, text=text) def EmailListRecipientFeedFromString(xml_string): return atom.CreateClassFromXMLString(EmailListRecipientFeed, xml_string) class Property(atom.AtomBase): """The Google Apps Property element""" _tag = 'property' _namespace = APPS_NAMESPACE _children = atom.AtomBase._children.copy() _attributes = atom.AtomBase._attributes.copy() _attributes['name'] = 'name' _attributes['value'] = 'value' def __init__(self, name=None, value=None, extension_elements=None, extension_attributes=None, text=None): self.name = name self.value = value self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} def PropertyFromString(xml_string): return atom.CreateClassFromXMLString(Property, xml_string) class PropertyEntry(gdata.GDataEntry): """A Google Apps Property flavor of an Atom Entry""" _tag = 'entry' _namespace = atom.ATOM_NAMESPACE _children = gdata.GDataEntry._children.copy() _attributes = gdata.GDataEntry._attributes.copy() _children['{%s}property' % APPS_NAMESPACE] = ('property', [Property]) def __init__(self, author=None, category=None, content=None, atom_id=None, link=None, published=None, title=None, updated=None, property=None, extended_property=None, extension_elements=None, extension_attributes=None, text=None): gdata.GDataEntry.__init__(self, author=author, category=category, content=content, atom_id=atom_id, link=link, published=published, title=title, updated=updated) self.property = property self.extended_property = extended_property or [] self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} def PropertyEntryFromString(xml_string): return atom.CreateClassFromXMLString(PropertyEntry, xml_string) class PropertyFeed(gdata.GDataFeed, gdata.LinkFinder): """A Google Apps Property feed flavor of an Atom Feed""" _tag = 'feed' _namespace = atom.ATOM_NAMESPACE _children = gdata.GDataFeed._children.copy() _attributes = gdata.GDataFeed._attributes.copy() _children['{%s}entry' % atom.ATOM_NAMESPACE] = ('entry', [PropertyEntry]) def __init__(self, author=None, category=None, contributor=None, generator=None, icon=None, atom_id=None, link=None, logo=None, rights=None, subtitle=None, title=None, updated=None, entry=None, total_results=None, start_index=None, items_per_page=None, extension_elements=None, extension_attributes=None, text=None): gdata.GDataFeed.__init__(self, author=author, category=category, contributor=contributor, generator=generator, icon=icon, atom_id=atom_id, link=link, logo=logo, rights=rights, subtitle=subtitle, title=title, updated=updated, entry=entry, total_results=total_results, start_index=start_index, items_per_page=items_per_page, extension_elements=extension_elements, extension_attributes=extension_attributes, text=text) def PropertyFeedFromString(xml_string): return atom.CreateClassFromXMLString(PropertyFeed, xml_string) gdata-2.0.18/samples/apps/marketplace_sample/gdata/apps/audit/0000750036466300116100000000000012156625015024323 5ustar afshareng00000000000000gdata-2.0.18/samples/apps/marketplace_sample/gdata/apps/audit/__init__.py0000640036466300116100000000000112156622362026426 0ustar afshareng00000000000000 gdata-2.0.18/samples/apps/marketplace_sample/gdata/apps/audit/service.py0000640036466300116100000002244612156622362026350 0ustar afshareng00000000000000# Copyright (C) 2008 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. """Allow Google Apps domain administrators to audit user data. AuditService: Set auditing.""" __author__ = 'jlee@pbu.edu' from base64 import b64encode import gdata.apps import gdata.apps.service import gdata.service class AuditService(gdata.apps.service.PropertyService): """Client for the Google Apps Audit service.""" def _serviceUrl(self, setting_id, domain=None, user=None): if domain is None: domain = self.domain if user is None: return '/a/feeds/compliance/audit/%s/%s' % (setting_id, domain) else: return '/a/feeds/compliance/audit/%s/%s/%s' % (setting_id, domain, user) def updatePGPKey(self, pgpkey): """Updates Public PGP Key Google uses to encrypt audit data Args: pgpkey: string, ASCII text of PGP Public Key to be used Returns: A dict containing the result of the POST operation.""" uri = self._serviceUrl('publickey') b64pgpkey = b64encode(pgpkey) properties = {} properties['publicKey'] = b64pgpkey return self._PostProperties(uri, properties) def createEmailMonitor(self, source_user, destination_user, end_date, begin_date=None, incoming_headers_only=False, outgoing_headers_only=False, drafts=False, drafts_headers_only=False, chats=False, chats_headers_only=False): """Creates a email monitor, forwarding the source_users emails/chats Args: source_user: string, the user whose email will be audited destination_user: string, the user to receive the audited email end_date: string, the date the audit will end in "yyyy-MM-dd HH:mm" format, required begin_date: string, the date the audit will start in "yyyy-MM-dd HH:mm" format, leave blank to use current time incoming_headers_only: boolean, whether to audit only the headers of mail delivered to source user outgoing_headers_only: boolean, whether to audit only the headers of mail sent from the source user drafts: boolean, whether to audit draft messages of the source user drafts_headers_only: boolean, whether to audit only the headers of mail drafts saved by the user chats: boolean, whether to audit archived chats of the source user chats_headers_only: boolean, whether to audit only the headers of archived chats of the source user Returns: A dict containing the result of the POST operation.""" uri = self._serviceUrl('mail/monitor', user=source_user) properties = {} properties['destUserName'] = destination_user if begin_date is not None: properties['beginDate'] = begin_date properties['endDate'] = end_date if incoming_headers_only: properties['incomingEmailMonitorLevel'] = 'HEADER_ONLY' else: properties['incomingEmailMonitorLevel'] = 'FULL_MESSAGE' if outgoing_headers_only: properties['outgoingEmailMonitorLevel'] = 'HEADER_ONLY' else: properties['outgoingEmailMonitorLevel'] = 'FULL_MESSAGE' if drafts: if drafts_headers_only: properties['draftMonitorLevel'] = 'HEADER_ONLY' else: properties['draftMonitorLevel'] = 'FULL_MESSAGE' if chats: if chats_headers_only: properties['chatMonitorLevel'] = 'HEADER_ONLY' else: properties['chatMonitorLevel'] = 'FULL_MESSAGE' return self._PostProperties(uri, properties) def getEmailMonitors(self, user): """"Gets the email monitors for the given user Args: user: string, the user to retrieve email monitors for Returns: list results of the POST operation """ uri = self._serviceUrl('mail/monitor', user=user) return self._GetPropertiesList(uri) def deleteEmailMonitor(self, source_user, destination_user): """Deletes the email monitor for the given user Args: source_user: string, the user who is being monitored destination_user: string, theuser who recieves the monitored emails Returns: Nothing """ uri = self._serviceUrl('mail/monitor', user=source_user+'/'+destination_user) try: return self._DeleteProperties(uri) except gdata.service.RequestError, e: raise AppsForYourDomainException(e.args[0]) def createAccountInformationRequest(self, user): """Creates a request for account auditing details Args: user: string, the user to request account information for Returns: A dict containing the result of the post operation.""" uri = self._serviceUrl('account', user=user) properties = {} #XML Body is left empty try: return self._PostProperties(uri, properties) except gdata.service.RequestError, e: raise AppsForYourDomainException(e.args[0]) def getAccountInformationRequestStatus(self, user, request_id): """Gets the status of an account auditing request Args: user: string, the user whose account auditing details were requested request_id: string, the request_id Returns: A dict containing the result of the get operation.""" uri = self._serviceUrl('account', user=user+'/'+request_id) try: return self._GetProperties(uri) except gdata.service.RequestError, e: raise AppsForYourDomainException(e.args[0]) def getAllAccountInformationRequestsStatus(self): """Gets the status of all account auditing requests for the domain Args: None Returns: list results of the POST operation """ uri = self._serviceUrl('account') return self._GetPropertiesList(uri) def deleteAccountInformationRequest(self, user, request_id): """Deletes the request for account auditing information Args: user: string, the user whose account auditing details were requested request_id: string, the request_id Returns: Nothing """ uri = self._serviceUrl('account', user=user+'/'+request_id) try: return self._DeleteProperties(uri) except gdata.service.RequestError, e: raise AppsForYourDomainException(e.args[0]) def createMailboxExportRequest(self, user, begin_date=None, end_date=None, include_deleted=False, search_query=None, headers_only=False): """Creates a mailbox export request Args: user: string, the user whose mailbox export is being requested begin_date: string, date of earliest emails to export, optional, defaults to date of account creation format is 'yyyy-MM-dd HH:mm' end_date: string, date of latest emails to export, optional, defaults to current date format is 'yyyy-MM-dd HH:mm' include_deleted: boolean, whether to include deleted emails in export, mutually exclusive with search_query search_query: string, gmail style search query, matched emails will be exported, mutually exclusive with include_deleted Returns: A dict containing the result of the post operation.""" uri = self._serviceUrl('mail/export', user=user) properties = {} if begin_date is not None: properties['beginDate'] = begin_date if end_date is not None: properties['endDate'] = end_date if include_deleted is not None: properties['includeDeleted'] = gdata.apps.service._bool2str(include_deleted) if search_query is not None: properties['searchQuery'] = search_query if headers_only is True: properties['packageContent'] = 'HEADER_ONLY' else: properties['packageContent'] = 'FULL_MESSAGE' return self._PostProperties(uri, properties) def getMailboxExportRequestStatus(self, user, request_id): """Gets the status of an mailbox export request Args: user: string, the user whose mailbox were requested request_id: string, the request_id Returns: A dict containing the result of the get operation.""" uri = self._serviceUrl('mail/export', user=user+'/'+request_id) try: return self._GetProperties(uri) except gdata.service.RequestError, e: raise AppsForYourDomainException(e.args[0]) def getAllMailboxExportRequestsStatus(self): """Gets the status of all mailbox export requests for the domain Args: None Returns: list results of the POST operation """ uri = self._serviceUrl('mail/export') return self._GetPropertiesList(uri) def deleteMailboxExportRequest(self, user, request_id): """Deletes the request for mailbox export Args: user: string, the user whose mailbox were requested request_id: string, the request_id Returns: Nothing """ uri = self._serviceUrl('mail/export', user=user+'/'+request_id) try: return self._DeleteProperties(uri) except gdata.service.RequestError, e: raise AppsForYourDomainException(e.args[0]) gdata-2.0.18/samples/apps/marketplace_sample/gdata/apps/client.py0000750036466300116100000001366012156622362025060 0ustar afshareng00000000000000# Copyright 2010 Google Inc. All Rights Reserved. # # 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. """AppsClient adds Client Architecture to Provisioning API.""" __author__ = '' import gdata.apps.data import gdata.client import gdata.service class AppsClient(gdata.client.GDClient): """Client extension for the Google Provisioning API service. Attributes: host: string The hostname for the Provisioning API service. api_version: string The version of the Provisioning API. """ host = 'apps-apis.google.com' api_version = '2.0' auth_service = 'apps' auth_scopes = gdata.gauth.AUTH_SCOPES['apps'] def __init__(self, domain, auth_token=None, **kwargs): """Constructs a new client for the Provisioning API. Args: domain: string Google Apps domain name. auth_token: (optional) gdata.gauth.ClientLoginToken, AuthSubToken, or OAuthToken which authorizes client to make calls to Provisioning API. """ gdata.client.GDClient.__init__(self, auth_token=auth_token, **kwargs) self.domain = domain def _baseURL(self): return '/a/feeds/%s' % self.domain def _userURL(self): return '%s/user/%s' % (self._baseURL(), self.api_version) def _nicknameURL(self): return '%s/nickname/%s' % (self._baseURL(), self.api_version) def RetrieveAllPages(self, feed, desired_class=gdata.data.GDFeed): """Retrieve all pages and add all elements. Args: feed: gdata.data.GDFeed object with linked elements. desired_class: type of feed to be returned. Returns: desired_class: subclass of gdata.data.GDFeed. """ next = feed.GetNextLink() while next is not None: next_feed = self.GetFeed(next.href, desired_class=desired_class) for a_entry in next_feed.entry: feed.entry.append(a_entry) next = next_feed.GetNextLink() return feed def CreateUser(self, user_name, family_name, given_name, password, suspended=False, admin=None, quota_limit=None, password_hash_function=None, agreed_to_terms=None, change_password=None): """Create a user account.""" uri = self._userURL() user_entry = gdata.apps.data.UserEntry() user_entry.login = gdata.apps.data.Login(user_name=user_name, password=password, suspended=suspended, admin=admin, hash_function_name=password_hash_function, agreed_to_terms=agreed_to_terms, change_password=change_password) user_entry.name = gdata.apps.data.Name(family_name=family_name, given_name=given_name) return self.Post(user_entry, uri) def RetrieveUser(self, user_name): """Retrieve a user account. Args: user_name: string user_name to be retrieved. Returns: gdata.apps.data.UserEntry """ uri = '%s/%s' % (self._userURL(), user_name) return self.GetEntry(uri, desired_class=gdata.apps.data.UserEntry) def RetrievePageOfUsers(self, start_username=None): """Retrieve one page of users in this domain. Args: start_username: string user to start from for retrieving a page of users. Returns: gdata.apps.data.UserFeed """ uri = self._userURL() if start_username is not None: uri += '?startUsername=%s' % start_username return self.GetFeed(uri, desired_class=gdata.apps.data.UserFeed) def RetrieveAllUsers(self): """Retrieve all users in this domain. Returns: gdata.apps.data.UserFeed """ ret = self.RetrievePageOfUsers() # pagination return self.RetrieveAllPages(ret, gdata.apps.data.UserFeed) def UpdateUser(self, user_name, user_entry): """Update a user account. Args: user_name: string user_name to be updated. user_entry: gdata.apps.data.UserEntry updated user entry. Returns: gdata.apps.data.UserEntry """ uri = '%s/%s' % (self._userURL(), user_name) return self.Update(entry=user_entry, uri=uri) def DeleteUser(self, user_name): """Delete a user account.""" uri = '%s/%s' % (self._userURL(), user_name) self.Delete(uri) def CreateNickname(self, user_name, nickname): """Create a nickname for a user. Args: user_name: string user whose nickname is being created. nickname: string nickname. Returns: gdata.apps.data.NicknameEntry """ uri = self._nicknameURL() nickname_entry = gdata.apps.data.NicknameEntry() nickname_entry.login = gdata.apps.data.Login(user_name=user_name) nickname_entry.nickname = gdata.apps.data.Nickname(name=nickname) return self.Post(nickname_entry, uri) def RetrieveNickname(self, nickname): """Retrieve a nickname. Args: nickname: string nickname to be retrieved. Returns: gdata.apps.data.NicknameEntry """ uri = '%s/%s' % (self._nicknameURL(), nickname) return self.GetEntry(uri, desired_class=gdata.apps.data.NicknameEntry) def RetrieveNicknames(self, user_name): """Retrieve nicknames of the user. Args: user_name: string user whose nicknames are retrieved. Returns: gdata.apps.data.NicknameFeed """ uri = '%s?username=%s' % (self._nicknameURL(), user_name) ret = self.GetFeed(uri, desired_class=gdata.apps.data.NicknameFeed) # pagination return self.RetrieveAllPages(ret, gdata.apps.data.NicknameFeed) def DeleteNickname(self, nickname): """Delete a nickname.""" uri = '%s/%s' % (self._nicknameURL(), nickname) self.Delete(uri) gdata-2.0.18/samples/apps/marketplace_sample/gdata/apps/multidomain/0000750036466300116100000000000012156625015025537 5ustar afshareng00000000000000gdata-2.0.18/samples/apps/marketplace_sample/gdata/apps/multidomain/data.py0000750036466300116100000003105512156622362027033 0ustar afshareng00000000000000#!/usr/bin/python2.4 # # Copyright 2011 Google Inc. All Rights Reserved. # # 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. """Data model classes for the Multidomain Provisioning API.""" __author__ = 'Claudio Cherubino ' import gdata.apps import gdata.apps.apps_property_entry import gdata.apps_property import gdata.data # This is required to work around a naming conflict between the Google # Spreadsheets API and Python's built-in property function pyproperty = property # The apps:property firstName of a user entry USER_FIRST_NAME = 'firstName' # The apps:property lastName of a user entry USER_LAST_NAME = 'lastName' # The apps:property userEmail of a user entry USER_EMAIL = 'userEmail' # The apps:property password of a user entry USER_PASSWORD = 'password' # The apps:property hashFunction of a user entry USER_HASH_FUNCTION = 'hashFunction' # The apps:property isChangePasswordAtNextLogin of a user entry USER_CHANGE_PASSWORD = 'isChangePasswordAtNextLogin' # The apps:property agreedToTerms of a user entry USER_AGREED_TO_TERMS = 'agreedToTerms' # The apps:property isSuspended of a user entry USER_SUSPENDED = 'isSuspended' # The apps:property isAdmin of a user entry USER_ADMIN = 'isAdmin' # The apps:property ipWhitelisted of a user entry USER_IP_WHITELISTED = 'ipWhitelisted' # The apps:property quotaInGb of a user entry USER_QUOTA = 'quotaInGb' # The apps:property newEmail of a user rename request entry USER_NEW_EMAIL = 'newEmail' # The apps:property aliasEmail of an alias entry ALIAS_EMAIL = 'aliasEmail' class UserEntry(gdata.apps.apps_property_entry.AppsPropertyEntry): """Represents an User in object form.""" def GetFirstName(self): """Get the first name of the User object. Returns: The first name of this User object as a string or None. """ return self._GetProperty(USER_FIRST_NAME) def SetFirstName(self, value): """Set the first name of this User object. Args: value: string The new first name to give this object. """ self._SetProperty(USER_FIRST_NAME, value) first_name = pyproperty(GetFirstName, SetFirstName) def GetLastName(self): """Get the last name of the User object. Returns: The last name of this User object as a string or None. """ return self._GetProperty(USER_LAST_NAME) def SetLastName(self, value): """Set the last name of this User object. Args: value: string The new last name to give this object. """ self._SetProperty(USER_LAST_NAME, value) last_name = pyproperty(GetLastName, SetLastName) def GetEmail(self): """Get the email address of the User object. Returns: The email address of this User object as a string or None. """ return self._GetProperty(USER_EMAIL) def SetEmail(self, value): """Set the email address of this User object. Args: value: string The new email address to give this object. """ self._SetProperty(USER_EMAIL, value) email = pyproperty(GetEmail, SetEmail) def GetPassword(self): """Get the password of the User object. Returns: The password of this User object as a string or None. """ return self._GetProperty(USER_PASSWORD) def SetPassword(self, value): """Set the password of this User object. Args: value: string The new password to give this object. """ self._SetProperty(USER_PASSWORD, value) password = pyproperty(GetPassword, SetPassword) def GetHashFunction(self): """Get the hash function of the User object. Returns: The hash function of this User object as a string or None. """ return self._GetProperty(USER_HASH_FUNCTION) def SetHashFunction(self, value): """Set the hash function of this User object. Args: value: string The new hash function to give this object. """ self._SetProperty(USER_HASH_FUNCTION, value) hash_function = pyproperty(GetHashFunction, SetHashFunction) def GetChangePasswordAtNextLogin(self): """Get the change password at next login flag of the User object. Returns: The change password at next login flag of this User object as a string or None. """ return self._GetProperty(USER_CHANGE_PASSWORD) def SetChangePasswordAtNextLogin(self, value): """Set the change password at next login flag of this User object. Args: value: string The new change password at next login flag to give this object. """ self._SetProperty(USER_CHANGE_PASSWORD, value) change_password_at_next_login = pyproperty(GetChangePasswordAtNextLogin, SetChangePasswordAtNextLogin) def GetAgreedToTerms(self): """Get the agreed to terms flag of the User object. Returns: The agreed to terms flag of this User object as a string or None. """ return self._GetProperty(USER_AGREED_TO_TERMS) agreed_to_terms = pyproperty(GetAgreedToTerms) def GetSuspended(self): """Get the suspended flag of the User object. Returns: The suspended flag of this User object as a string or None. """ return self._GetProperty(USER_SUSPENDED) def SetSuspended(self, value): """Set the suspended flag of this User object. Args: value: string The new suspended flag to give this object. """ self._SetProperty(USER_SUSPENDED, value) suspended = pyproperty(GetSuspended, SetSuspended) def GetIsAdmin(self): """Get the isAdmin flag of the User object. Returns: The isAdmin flag of this User object as a string or None. """ return self._GetProperty(USER_ADMIN) def SetIsAdmin(self, value): """Set the isAdmin flag of this User object. Args: value: string The new isAdmin flag to give this object. """ self._SetProperty(USER_ADMIN, value) is_admin = pyproperty(GetIsAdmin, SetIsAdmin) def GetIpWhitelisted(self): """Get the ipWhitelisted flag of the User object. Returns: The ipWhitelisted flag of this User object as a string or None. """ return self._GetProperty(USER_IP_WHITELISTED) def SetIpWhitelisted(self, value): """Set the ipWhitelisted flag of this User object. Args: value: string The new ipWhitelisted flag to give this object. """ self._SetProperty(USER_IP_WHITELISTED, value) ip_whitelisted = pyproperty(GetIpWhitelisted, SetIpWhitelisted) def GetQuota(self): """Get the quota of the User object. Returns: The quota of this User object as a string or None. """ return self._GetProperty(USER_QUOTA) def SetQuota(self, value): """Set the quota of this User object. Args: value: string The new quota to give this object. """ self._SetProperty(USER_QUOTA, value) quota = pyproperty(GetQuota, GetQuota) def __init__(self, uri=None, email=None, first_name=None, last_name=None, password=None, hash_function=None, change_password=None, agreed_to_terms=None, suspended=None, is_admin=None, ip_whitelisted=None, quota=None, *args, **kwargs): """Constructs a new UserEntry object with the given arguments. Args: uri: string (optional) The uri of of this object for HTTP requests. email: string (optional) The email address of the user. first_name: string (optional) The first name of the user. last_name: string (optional) The last name of the user. password: string (optional) The password of the user. hash_function: string (optional) The name of the function used to hash the password. change_password: Boolean (optional) Whether or not the user must change password at first login. agreed_to_terms: Boolean (optional) Whether or not the user has agreed to the Terms of Service. suspended: Boolean (optional) Whether or not the user is suspended. is_admin: Boolean (optional) Whether or not the user has administrator privileges. ip_whitelisted: Boolean (optional) Whether or not the user's ip is whitelisted. quota: string (optional) The value (in GB) of the user's quota. args: The other parameters to pass to gdata.entry.GDEntry constructor. kwargs: The other parameters to pass to gdata.entry.GDEntry constructor. """ super(UserEntry, self).__init__(*args, **kwargs) if uri: self.uri = uri if email: self.email = email if first_name: self.first_name = first_name if last_name: self.last_name = last_name if password: self.password = password if hash_function: self.hash_function = hash_function if change_password is not None: self.change_password_at_next_login = str(change_password) if agreed_to_terms is not None: self.agreed_to_terms = str(agreed_to_terms) if suspended is not None: self.suspended = str(suspended) if is_admin is not None: self.is_admin = str(is_admin) if ip_whitelisted is not None: self.ip_whitelisted = str(ip_whitelisted) if quota: self.quota = quota class UserFeed(gdata.data.GDFeed): """Represents a feed of UserEntry objects.""" # Override entry so that this feed knows how to type its list of entries. entry = [UserEntry] class UserRenameRequest(gdata.apps.apps_property_entry.AppsPropertyEntry): """Represents an User rename request in object form.""" def GetNewEmail(self): """Get the new email address for the User object. Returns: The new email address for the User object as a string or None. """ return self._GetProperty(USER_NEW_EMAIL) def SetNewEmail(self, value): """Set the new email address for the User object. Args: value: string The new email address to give this object. """ self._SetProperty(USER_NEW_EMAIL, value) new_email = pyproperty(GetNewEmail, SetNewEmail) def __init__(self, new_email=None, *args, **kwargs): """Constructs a new UserRenameRequest object with the given arguments. Args: new_email: string (optional) The new email address for the target user. args: The other parameters to pass to gdata.entry.GDEntry constructor. kwargs: The other parameters to pass to gdata.entry.GDEntry constructor. """ super(UserRenameRequest, self).__init__(*args, **kwargs) if new_email: self.new_email = new_email class AliasEntry(gdata.apps.apps_property_entry.AppsPropertyEntry): """Represents an Alias in object form.""" def GetUserEmail(self): """Get the user email address of the Alias object. Returns: The user email address of this Alias object as a string or None. """ return self._GetProperty(USER_EMAIL) def SetUserEmail(self, value): """Set the user email address of this Alias object. Args: value: string The new user email address to give this object. """ self._SetProperty(USER_EMAIL, value) user_email = pyproperty(GetUserEmail, SetUserEmail) def GetAliasEmail(self): """Get the alias email address of the Alias object. Returns: The alias email address of this Alias object as a string or None. """ return self._GetProperty(ALIAS_EMAIL) def SetAliasEmail(self, value): """Set the alias email address of this Alias object. Args: value: string The new alias email address to give this object. """ self._SetProperty(ALIAS_EMAIL, value) alias_email = pyproperty(GetAliasEmail, SetAliasEmail) def __init__(self, user_email=None, alias_email=None, *args, **kwargs): """Constructs a new AliasEntry object with the given arguments. Args: user_email: string (optional) The user email address for the object. alias_email: string (optional) The alias email address for the object. args: The other parameters to pass to gdata.entry.GDEntry constructor. kwargs: The other parameters to pass to gdata.entry.GDEntry constructor. """ super(AliasEntry, self).__init__(*args, **kwargs) if user_email: self.user_email = user_email if alias_email: self.alias_email = alias_email class AliasFeed(gdata.data.GDFeed): """Represents a feed of AliasEntry objects.""" # Override entry so that this feed knows how to type its list of entries. entry = [AliasEntry] gdata-2.0.18/samples/apps/marketplace_sample/gdata/apps/multidomain/__init__.py0000750036466300116100000000000012156622362027643 0ustar afshareng00000000000000gdata-2.0.18/samples/apps/marketplace_sample/gdata/apps/multidomain/client.py0000750036466300116100000003300612156622362027376 0ustar afshareng00000000000000#!/usr/bin/python2.4 # # Copyright 2011 Google Inc. All Rights Reserved. # # 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. """MultiDomainProvisioningClient simplifies Multidomain Provisioning API calls. MultiDomainProvisioningClient extends gdata.client.GDClient to ease interaction with the Google Multidomain Provisioning API. These interactions include the ability to create, retrieve, update and delete users and aliases in multiple domains. """ __author__ = 'Claudio Cherubino ' import urllib import gdata.apps.multidomain.data import gdata.client # Multidomain URI templates # The strings in this template are eventually replaced with the feed type # (user/alias), API version and Google Apps domain name, respectively. MULTIDOMAIN_URI_TEMPLATE = '/a/feeds/%s/%s/%s' # The strings in this template are eventually replaced with the API version, # Google Apps domain name and old email address, respectively. MULTIDOMAIN_USER_RENAME_URI_TEMPLATE = '/a/feeds/user/userEmail/%s/%s/%s' # The value for user requests MULTIDOMAIN_USER_FEED = 'user' # The value for alias requests MULTIDOMAIN_ALIAS_FEED = 'alias' class MultiDomainProvisioningClient(gdata.client.GDClient): """Client extension for the Google MultiDomain Provisioning API service. Attributes: host: string The hostname for the MultiDomain Provisioning API service. api_version: string The version of the MultiDomain Provisioning API. """ host = 'apps-apis.google.com' api_version = '2.0' auth_service = 'apps' auth_scopes = gdata.gauth.AUTH_SCOPES['apps'] ssl = True def __init__(self, domain, auth_token=None, **kwargs): """Constructs a new client for the MultiDomain Provisioning API. Args: domain: string The Google Apps domain with MultiDomain Provisioning. auth_token: (optional) gdata.gauth.ClientLoginToken, AuthSubToken, or OAuthToken which authorizes this client to edit the email settings. kwargs: The other parameters to pass to the gdata.client.GDClient constructor. """ gdata.client.GDClient.__init__(self, auth_token=auth_token, **kwargs) self.domain = domain def make_multidomain_provisioning_uri( self, feed_type, email=None, params=None): """Creates a resource feed URI for the MultiDomain Provisioning API. Using this client's Google Apps domain, create a feed URI for multidomain provisioning in that domain. If an email address is provided, return a URI for that specific resource. If params are provided, append them as GET params. Args: feed_type: string The type of feed (user/alias) email: string (optional) The email address of multidomain resource for which to make a feed URI. params: dict (optional) key -> value params to append as GET vars to the URI. Example: params={'start': 'my-resource-id'} Returns: A string giving the URI for multidomain provisioning for this client's Google Apps domain. """ uri = MULTIDOMAIN_URI_TEMPLATE % (feed_type, self.api_version, self.domain) if email: uri += '/' + email if params: uri += '?' + urllib.urlencode(params) return uri MakeMultidomainProvisioningUri = make_multidomain_provisioning_uri def make_multidomain_user_provisioning_uri(self, email=None, params=None): """Creates a resource feed URI for the MultiDomain User Provisioning API. Using this client's Google Apps domain, create a feed URI for multidomain user provisioning in that domain. If an email address is provided, return a URI for that specific resource. If params are provided, append them as GET params. Args: email: string (optional) The email address of multidomain user for which to make a feed URI. params: dict (optional) key -> value params to append as GET vars to the URI. Example: params={'start': 'my-resource-id'} Returns: A string giving the URI for multidomain user provisioning for thisis that client's Google Apps domain. """ return self.make_multidomain_provisioning_uri( MULTIDOMAIN_USER_FEED, email, params) MakeMultidomainUserProvisioningUri = make_multidomain_user_provisioning_uri def make_multidomain_alias_provisioning_uri(self, email=None, params=None): """Creates a resource feed URI for the MultiDomain Alias Provisioning API. Using this client's Google Apps domain, create a feed URI for multidomain alias provisioning in that domain. If an email address is provided, return a URI for that specific resource. If params are provided, append them as GET params. Args: email: string (optional) The email address of multidomain alias for which to make a feed URI. params: dict (optional) key -> value params to append as GET vars to the URI. Example: params={'start': 'my-resource-id'} Returns: A string giving the URI for multidomain alias provisioning for this client's Google Apps domain. """ return self.make_multidomain_provisioning_uri( MULTIDOMAIN_ALIAS_FEED, email, params) MakeMultidomainAliasProvisioningUri = make_multidomain_alias_provisioning_uri def retrieve_all_pages(self, uri, desired_class=gdata.data.GDFeed, **kwargs): """Retrieves all pages from uri. Args: uri: The uri where the first page is. desired_class: Type of feed that is retrieved. kwargs: The other parameters to pass to gdata.client.GDClient.GetFeed() Returns: A desired_class feed object. """ feed = self.GetFeed( uri, desired_class=desired_class, **kwargs) next_link = feed.GetNextLink() while next_link is not None: uri = next_link.href temp_feed = self.GetFeed( uri, desired_class=desired_class, **kwargs) feed.entry = feed.entry + temp_feed.entry next_link = temp_feed.GetNextLink() return feed RetrieveAllPages = retrieve_all_pages def retrieve_all_users(self, **kwargs): """Retrieves all users in all domains. Args: kwargs: The other parameters to pass to gdata.client.GDClient.GetFeed() Returns: A gdata.data.GDFeed of the domain users """ uri = self.MakeMultidomainUserProvisioningUri() return self.RetrieveAllPages( uri, desired_class=gdata.apps.multidomain.data.UserFeed, **kwargs) RetrieveAllUsers = retrieve_all_users def retrieve_user(self, email, **kwargs): """Retrieves a single user in the domain. Args: email: string The email address of the user to be retrieved kwargs: The other parameters to pass to gdata.client.GDClient.GetEntry() Returns: A gdata.apps.multidomain.data.UserEntry representing the user """ uri = self.MakeMultidomainUserProvisioningUri(email=email) return self.GetEntry( uri, desired_class=gdata.apps.multidomain.data.UserEntry, **kwargs) RetrieveUser = retrieve_user def create_user(self, email, first_name, last_name, password, is_admin, hash_function=None, suspended=None, change_password=None, ip_whitelisted=None, quota=None, **kwargs): """Creates an user in the domain with the given properties. Args: email: string The email address of the user. first_name: string The first name of the user. last_name: string The last name of the user. password: string The password of the user. is_admin: Boolean Whether or not the user has administrator privileges. hash_function: string (optional) The name of the function used to hash the password. suspended: Boolean (optional) Whether or not the user is suspended. change_password: Boolean (optional) Whether or not the user must change password at first login. ip_whitelisted: Boolean (optional) Whether or not the user's ip is whitelisted. quota: string (optional) The value (in GB) of the user's quota. kwargs: The other parameters to pass to gdata.client.GDClient.post(). Returns: A gdata.apps.multidomain.data.UserEntry of the new user """ new_user = gdata.apps.multidomain.data.UserEntry( email=email, first_name=first_name, last_name=last_name, password=password, is_admin=is_admin, hash_function=hash_function, suspended=suspended, change_password=change_password, ip_whitelisted=ip_whitelisted, quota=quota) return self.post(new_user, self.MakeMultidomainUserProvisioningUri(), **kwargs) CreateUser = create_user def update_user(self, email, user_entry, **kwargs): """Deletes the user with the given email address. Args: email: string The email address of the user to be updated. user_entry: UserEntry The user entry with updated values. kwargs: The other parameters to pass to gdata.client.GDClient.put() Returns: A gdata.apps.multidomain.data.UserEntry representing the user """ return self.update(user_entry, uri=self.MakeMultidomainUserProvisioningUri(email), **kwargs) UpdateUser = update_user def delete_user(self, email, **kwargs): """Deletes the user with the given email address. Args: email: string The email address of the user to delete. kwargs: The other parameters to pass to gdata.client.GDClient.delete() Returns: An HTTP response object. See gdata.client.request(). """ return self.delete(self.MakeMultidomainUserProvisioningUri(email), **kwargs) DeleteUser = delete_user def rename_user(self, old_email, new_email, **kwargs): """Renames an user's account to a different domain. Args: old_email: string The old email address of the user to rename. new_email: string The new email address for the user to be renamed. kwargs: The other parameters to pass to gdata.client.GDClient.put() Returns: A gdata.apps.multidomain.data.UserRenameRequest representing the request. """ rename_uri = MULTIDOMAIN_USER_RENAME_URI_TEMPLATE % (self.api_version, self.domain, old_email) entry = gdata.apps.multidomain.data.UserRenameRequest(new_email) return self.update(entry, uri=rename_uri, **kwargs) RenameUser = rename_user def retrieve_all_aliases(self, **kwargs): """Retrieves all aliases in the domain. Args: kwargs: The other parameters to pass to gdata.client.GDClient.GetFeed() Returns: A gdata.data.GDFeed of the domain aliases """ uri = self.MakeMultidomainAliasProvisioningUri() return self.RetrieveAllPages( uri, desired_class=gdata.apps.multidomain.data.AliasFeed, **kwargs) RetrieveAllAliases = retrieve_all_aliases def retrieve_alias(self, email, **kwargs): """Retrieves a single alias in the domain. Args: email: string The email address of the alias to be retrieved kwargs: The other parameters to pass to gdata.client.GDClient.GetEntry() Returns: A gdata.apps.multidomain.data.AliasEntry representing the alias """ uri = self.MakeMultidomainAliasProvisioningUri(email=email) return self.GetEntry( uri, desired_class=gdata.apps.multidomain.data.AliasEntry, **kwargs) RetrieveAlias = retrieve_alias def retrieve_all_user_aliases(self, user_email, **kwargs): """Retrieves all aliases for a given user in the domain. Args: user_email: string Email address of the user whose aliases are to be retrieved kwargs: The other parameters to pass to gdata.client.GDClient.GetFeed() Returns: A gdata.data.GDFeed of the user aliases """ uri = self.MakeMultidomainAliasProvisioningUri( params = {'userEmail' : user_email}) return self.RetrieveAllPages( uri, desired_class=gdata.apps.multidomain.data.AliasFeed, **kwargs) RetrieveAllUserAliases = retrieve_all_user_aliases def create_alias(self, user_email, alias_email, **kwargs): """Creates an alias in the domain with the given properties. Args: user_email: string The email address of the user. alias_email: string The first name of the user. kwargs: The other parameters to pass to gdata.client.GDClient.post(). Returns: A gdata.apps.multidomain.data.AliasEntry of the new alias """ new_alias = gdata.apps.multidomain.data.AliasEntry( user_email=user_email, alias_email=alias_email) return self.post(new_alias, self.MakeMultidomainAliasProvisioningUri(), **kwargs) CreateAlias = create_alias def delete_alias(self, email, **kwargs): """Deletes the alias with the given email address. Args: email: string The email address of the alias to delete. kwargs: The other parameters to pass to gdata.client.GDClient.delete() Returns: An HTTP response object. See gdata.client.request(). """ return self.delete(self.MakeMultidomainAliasProvisioningUri(email), **kwargs) DeleteAlias = delete_alias gdata-2.0.18/samples/apps/marketplace_sample/gdata/apps/apps_property_entry.py0000640036466300116100000000360112156622362027722 0ustar afshareng00000000000000#!/usr/bin/python2.4 # # Copyright 2011 Google Inc. All Rights Reserved. # # 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. """Generic class for Set/Get properties of GData Provisioning clients.""" __author__ = 'Gunjan Sharma ' import gdata.apps import gdata.apps_property import gdata.data class AppsPropertyEntry(gdata.data.GDEntry): """Represents a generic entry in object form.""" property = [gdata.apps_property.AppsProperty] def _GetProperty(self, name): """Get the apps:property value with the given name. Args: name: string Name of the apps:property value to get. Returns: The apps:property value with the given name, or None if the name was invalid. """ value = None for p in self.property: if p.name == name: value = p.value break return value def _SetProperty(self, name, value): """Set the apps:property value with the given name to the given value. Args: name: string Name of the apps:property value to set. value: string Value to give the apps:property value with the given name. """ found = False for i in range(len(self.property)): if self.property[i].name == name: self.property[i].value = value found = True break if not found: self.property.append( gdata.apps_property.AppsProperty(name=name, value=value)) gdata-2.0.18/samples/apps/marketplace_sample/gdata/apps/groups/0000750036466300116100000000000012156625015024534 5ustar afshareng00000000000000gdata-2.0.18/samples/apps/marketplace_sample/gdata/apps/groups/data.py0000640036466300116100000001566612156622362026040 0ustar afshareng00000000000000#!/usr/bin/python2.4 # # Copyright 2011 Google Inc. All Rights Reserved. # # 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. """Data model classes for the Groups Provisioning API.""" __author__ = 'Shraddha gupta ' import atom.data import gdata.apps import gdata.apps.apps_property_entry import gdata.apps_property import gdata.data # This is required to work around a naming conflict between the Google # Spreadsheets API and Python's built-in property function pyproperty = property # The apps:property groupId of a group entry GROUP_ID = 'groupId' # The apps:property groupName of a group entry GROUP_NAME = 'groupName' # The apps:property description of a group entry DESCRIPTION = 'description' # The apps:property emailPermission of a group entry EMAIL_PERMISSION = 'emailPermission' # The apps:property memberId of a group member entry MEMBER_ID = 'memberId' # The apps:property memberType of a group member entry MEMBER_TYPE = 'memberType' # The apps:property directMember of a group member entry DIRECT_MEMBER = 'directMember' class GroupEntry(gdata.apps.apps_property_entry.AppsPropertyEntry): """Represents a group entry in object form.""" def GetGroupId(self): """Get groupId of the GroupEntry object. Returns: The groupId this GroupEntry object as a string or None. """ return self._GetProperty(GROUP_ID) def SetGroupId(self, value): """Set the groupId of this GroupEntry object. Args: value: string The new groupId to give this object. """ self._SetProperty(GROUP_ID, value) group_id = pyproperty(GetGroupId, SetGroupId) def GetGroupName(self): """Get the groupName of the GroupEntry object. Returns: The groupName of this GroupEntry object as a string or None. """ return self._GetProperty(GROUP_NAME) def SetGroupName(self, value): """Set the groupName of this GroupEntry object. Args: value: string The new groupName to give this object. """ self._SetProperty(GROUP_NAME, value) group_name = pyproperty(GetGroupName, SetGroupName) def GetDescription(self): """Get the description of the GroupEntry object. Returns: The description of this GroupEntry object as a string or None. """ return self._GetProperty(DESCRIPTION) def SetDescription(self, value): """Set the description of this GroupEntry object. Args: value: string The new description to give this object. """ self._SetProperty(DESCRIPTION, value) description = pyproperty(GetDescription, SetDescription) def GetEmailPermission(self): """Get the emailPermission of the GroupEntry object. Returns: The emailPermission of this GroupEntry object as a string or None. """ return self._GetProperty(EMAIL_PERMISSION) def SetEmailPermission(self, value): """Set the emailPermission of this GroupEntry object. Args: value: string The new emailPermission to give this object. """ self._SetProperty(EMAIL_PERMISSION, value) email_permission = pyproperty(GetEmailPermission, SetEmailPermission) def __init__(self, group_id=None, group_name=None, description=None, email_permission=None, *args, **kwargs): """Constructs a new GroupEntry object with the given arguments. Args: group_id: string identifier of the group. group_name: string name of the group. description: string (optional) the group description. email_permisison: string (optional) permission level of the group. """ super(GroupEntry, self).__init__(*args, **kwargs) if group_id: self.group_id = group_id if group_name: self.group_name = group_name if description: self.description = description if email_permission: self.email_permission = email_permission class GroupFeed(gdata.data.GDFeed): """Represents a feed of GroupEntry objects.""" # Override entry so that this feed knows how to type its list of entries. entry = [GroupEntry] class GroupMemberEntry(gdata.apps.apps_property_entry.AppsPropertyEntry): """Represents a group member in object form.""" def GetMemberId(self): """Get the memberId of the GroupMember object. Returns: The memberId of this GroupMember object as a string. """ return self._GetProperty(MEMBER_ID) def SetMemberId(self, value): """Set the memberId of this GroupMember object. Args: value: string The new memberId to give this object. """ self._SetProperty(MEMBER_ID, value) member_id = pyproperty(GetMemberId, SetMemberId) def GetMemberType(self): """Get the memberType(User, Group) of the GroupMember object. Returns: The memberType of this GroupMember object as a string or None. """ return self._GetProperty(MEMBER_TYPE) def SetMemberType(self, value): """Set the memberType of this GroupMember object. Args: value: string The new memberType to give this object. """ self._SetProperty(MEMBER_TYPE, value) member_type = pyproperty(GetMemberType, SetMemberType) def GetDirectMember(self): """Get the directMember of the GroupMember object. Returns: The directMember of this GroupMember object as a bool or None. """ return self._GetProperty(DIRECT_MEMBER) def SetDirectMember(self, value): """Set the memberType of this GroupMember object. Args: value: string The new memberType to give this object. """ self._SetProperty(DIRECT_MEMBER, value) direct_member = pyproperty(GetDirectMember, SetDirectMember) def __init__(self, member_id=None, member_type=None, direct_member=None, *args, **kwargs): """Constructs a new GroupMemberEntry object with the given arguments. Args: member_id: string identifier of group member object. member_type: string (optional) member type of group member object. direct_member: bool (optional) if group member object is direct member. args: The other parameters to pass to gdata.entry.GDEntry constructor. kwargs: The other parameters to pass to gdata.entry.GDEntry constructor. """ super(GroupMemberEntry, self).__init__(*args, **kwargs) if member_id: self.member_id = member_id if member_type: self.member_type = member_type if direct_member: self.direct_member = direct_member class GroupMemberFeed(gdata.data.GDFeed): """Represents a feed of GroupMemberEntry objects.""" # Override entry so that this feed knows how to type its list of entries. entry = [GroupMemberEntry] gdata-2.0.18/samples/apps/marketplace_sample/gdata/apps/groups/__init__.py0000640036466300116100000000000012156622362026636 0ustar afshareng00000000000000gdata-2.0.18/samples/apps/marketplace_sample/gdata/apps/groups/client.py0000640036466300116100000002715412156622362026400 0ustar afshareng00000000000000#!/usr/bin/python2.4 # # Copyright 2011 Google Inc. All Rights Reserved. # # 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. """GroupsClient simplifies Groups Provisioning API calls. GroupsClient extends gdata.client.GDClient to ease interaction with the Group Provisioning API. These interactions include the ability to create, retrieve, update and delete groups. """ __author__ = 'Shraddha gupta ' import urllib import gdata.apps.groups.data import gdata.client # Multidomain URI templates # The strings in this template are eventually replaced with the API version, # and Google Apps domain name respectively. GROUP_URI_TEMPLATE = '/a/feeds/group/%s/%s' GROUP_MEMBER = 'member' class GroupsProvisioningClient(gdata.client.GDClient): """Client extension for the Google Group Provisioning API service. Attributes: host: string The hostname for the Group Provisioning API service. api_version: string The version of the MultiDomain Provisioning API. """ host = 'apps-apis.google.com' api_version = '2.0' auth_service = 'apps' auth_scopes = gdata.gauth.AUTH_SCOPES['apps'] ssl = True def __init__(self, domain, auth_token=None, **kwargs): """Constructs a new client for the Groups Provisioning API. Args: domain: string The Google Apps domain with Group Provisioning. auth_token: (optional) gdata.gauth.ClientLoginToken, AuthSubToken, or OAuthToken which authorizes this client to edit the email settings. kwargs: The other parameters to pass to the gdata.client.GDClient constructor. """ gdata.client.GDClient.__init__(self, auth_token=auth_token, **kwargs) self.domain = domain def make_group_provisioning_uri( self, feed_type=None, group_id=None, member_id=None, params=None): """Creates a resource feed URI for the Groups Provisioning API. Using this client's Google Apps domain, create a feed URI for group provisioning in that domain. If an email address is provided, return a URI for that specific resource. If params are provided, append them as GET params. Args: feed_type: string groupmember for groupmember feed else None group_id: string (optional) The identifier of group for which to make a feed URI. member_id: string (optional) The identifier of group member for which to make a feed URI. params: dict (optional) key -> value params to append as GET vars to the URI. Example: params={'start': 'my-resource-id'} Returns: A string giving the URI for group provisioning for this client's Google Apps domain. """ uri = GROUP_URI_TEMPLATE % (self.api_version, self.domain) if group_id: uri += '/' + group_id if feed_type is GROUP_MEMBER: uri += '/' + feed_type if member_id: uri += '/' + member_id if params: uri += '?' + urllib.urlencode(params) return uri MakeGroupProvisioningUri = make_group_provisioning_uri def make_group_member_uri(self, group_id, member_id=None, params=None): """Creates a resource feed URI for the Group Member Provisioning API.""" return self.make_group_provisioning_uri(GROUP_MEMBER, group_id=group_id, member_id=member_id, params=params) MakeGroupMembersUri = make_group_member_uri def RetrieveAllPages(self, feed, desired_class=gdata.data.GDFeed): """Retrieve all pages and add all elements. Args: feed: gdata.data.GDFeed object with linked elements. desired_class: type of Feed to be returned. Returns: desired_class: subclass of gdata.data.GDFeed. """ next = feed.GetNextLink() while next is not None: next_feed = self.GetFeed(next.href, desired_class=desired_class) for a_entry in next_feed.entry: feed.entry.append(a_entry) next = next_feed.GetNextLink() return feed def retrieve_page_of_groups(self, **kwargs): """Retrieves first page of groups for the given domain. Args: kwargs: The other parameters to pass to gdata.client.GDClient.GetFeed() Returns: A gdata.apps.groups.data.GroupFeed of the groups """ uri = self.MakeGroupProvisioningUri() return self.GetFeed(uri, desired_class=gdata.apps.groups.data.GroupFeed, **kwargs) RetrievePageOfGroups = retrieve_page_of_groups def retrieve_all_groups(self): """Retrieve all groups in this domain. Returns: gdata.apps.groups.data.GroupFeed of the groups """ groups_feed = self.RetrievePageOfGroups() # pagination return self.RetrieveAllPages(groups_feed, gdata.apps.groups.data.GroupFeed) RetrieveAllGroups = retrieve_all_groups def retrieve_group(self, group_id, **kwargs): """Retrieves a single group in the domain. Args: group_id: string groupId of the group to be retrieved kwargs: other parameters to pass to gdata.client.GDClient.GetEntry() Returns: A gdata.apps.groups.data.GroupEntry representing the group """ uri = self.MakeGroupProvisioningUri(group_id=group_id) return self.GetEntry(uri, desired_class=gdata.apps.groups.data.GroupEntry, **kwargs) RetrieveGroup = retrieve_group def retrieve_page_of_member_groups(self, member_id, direct_only=False, **kwargs): """Retrieve one page of groups that belong to the given member_id. Args: member_id: The member's email address (e.g. member@example.com). direct_only: Boolean whether only return groups that this member directly belongs to. Returns: gdata.apps.groups.data.GroupFeed of the groups. """ uri = self.MakeGroupProvisioningUri(params={'member':member_id, 'directOnly':direct_only}) return self.GetFeed(uri, desired_class=gdata.apps.groups.data.GroupFeed, **kwargs) RetrievePageOfMemberGroups = retrieve_page_of_member_groups def retrieve_groups(self, member_id, direct_only=False, **kwargs): """Retrieve all groups that belong to the given member_id. Args: member_id: The member's email address (e.g. member@example.com). direct_only: Boolean whether only return groups that this member directly belongs to. Returns: gdata.apps.groups.data.GroupFeed of the groups """ groups_feed = self.RetrievePageOfMemberGroups(member_id=member_id, direct_only=direct_only) # pagination return self.RetrieveAllPages(groups_feed, gdata.apps.groups.data.GroupFeed) RetrieveGroups = retrieve_groups def create_group(self, group_id, group_name, description=None, email_permission=None, **kwargs): """Creates a group in the domain with the given properties. Args: group_id: string identifier of the group. group_name: string name of the group. description: string (optional) description of the group. email_permission: string (optional) email permission level for the group. kwargs: other parameters to pass to gdata.client.GDClient.post(). Returns: A gdata.apps.groups.data.GroupEntry of the new group """ new_group = gdata.apps.groups.data.GroupEntry(group_id=group_id, group_name=group_name, description=description, email_permission=email_permission) return self.post(new_group, self.MakeGroupProvisioningUri(), **kwargs) CreateGroup = create_group def update_group(self, group_id, group_entry, **kwargs): """Updates the group with the given groupID. Args: group_id: string identifier of the group. group_entry: GroupEntry The group entry with updated values. kwargs: The other parameters to pass to gdata.client.GDClient.put() Returns: A gdata.apps.groups.data.GroupEntry representing the group """ return self.update(group_entry, uri=self.MakeGroupProvisioningUri(group_id=group_id), **kwargs) UpdateGroup = update_group def delete_group(self, group_id, **kwargs): """Deletes the group with the given groupId. Args: group_id: string groupId of the group to delete. kwargs: The other parameters to pass to gdata.client.GDClient.delete() """ self.delete(self.MakeGroupProvisioningUri(group_id=group_id), **kwargs) DeleteGroup = delete_group def retrieve_page_of_members(self, group_id, **kwargs): """Retrieves first page of group members of the group. Args: group_id: string groupId of the group whose members are retrieved kwargs: The other parameters to pass to gdata.client.GDClient.GetFeed() Returns: A gdata.apps.groups.data.GroupMemberFeed of the GroupMember entries """ uri = self.MakeGroupMembersUri(group_id=group_id) return self.GetFeed(uri, desired_class=gdata.apps.groups.data.GroupMemberFeed, **kwargs) RetrievePageOfMembers = retrieve_page_of_members def retrieve_all_members(self, group_id, **kwargs): """Retrieve all members of the group. Returns: gdata.apps.groups.data.GroupMemberFeed """ group_member_feed = self.RetrievePageOfMembers(group_id=group_id) # pagination return self.RetrieveAllPages(group_member_feed, gdata.apps.groups.data.GroupMemberFeed) RetrieveAllMembers = retrieve_all_members def retrieve_group_member(self, group_id, member_id, **kwargs): """Retrieves a group member with the given id from given group. Args: group_id: string groupId of the group whose member is retrieved member_id: string memberId of the group member retrieved kwargs: The other parameters to pass to gdata.client.GDClient.GetEntry() Returns: A gdata.apps.groups.data.GroupEntry representing the group member """ uri = self.MakeGroupMembersUri(group_id=group_id, member_id=member_id) return self.GetEntry(uri, desired_class=gdata.apps.groups.data.GroupMemberEntry, **kwargs) RetrieveGroupMember = retrieve_group_member def add_member_to_group(self, group_id, member_id, member_type=None, direct_member=None, **kwargs): """Adds a member with the given id to the group. Args: group_id: string groupId of the group where member is added member_id: string memberId of the member added member_type: string (optional) type of member(user or group) direct_member: bool (optional) if member is a direct member kwargs: The other parameters to pass to gdata.client.GDClient.post(). Returns: A gdata.apps.groups.data.GroupMemberEntry of the group member """ member = gdata.apps.groups.data.GroupMemberEntry(member_id=member_id, member_type=member_type, direct_member=direct_member) return self.post(member, self.MakeGroupMembersUri(group_id=group_id), **kwargs) AddMemberToGroup = add_member_to_group def remove_member_from_group(self, group_id, member_id, **kwargs): """Remove member from the given group. Args: group_id: string groupId of the group member_id: string memberId of the member to be removed kwargs: The other parameters to pass to gdata.client.GDClient.delete() """ self.delete( self.MakeGroupMembersUri(group_id=group_id, member_id=member_id), **kwargs) RemoveMemberFromGroup = remove_member_from_group gdata-2.0.18/samples/apps/marketplace_sample/gdata/apps/groups/service.py0000640036466300116100000003117412156622362026557 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright (C) 2008 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. """Allow Google Apps domain administrators to manage groups, group members and group owners. GroupsService: Provides methods to manage groups, members and owners. """ __author__ = 'google-apps-apis@googlegroups.com' import urllib import gdata.apps import gdata.apps.service import gdata.service API_VER = '2.0' BASE_URL = '/a/feeds/group/' + API_VER + '/%s' GROUP_MEMBER_URL = BASE_URL + '?member=%s' GROUP_MEMBER_DIRECT_URL = GROUP_MEMBER_URL + '&directOnly=%s' GROUP_ID_URL = BASE_URL + '/%s' MEMBER_URL = BASE_URL + '/%s/member' MEMBER_WITH_SUSPENDED_URL = MEMBER_URL + '?includeSuspendedUsers=%s' MEMBER_ID_URL = MEMBER_URL + '/%s' OWNER_URL = BASE_URL + '/%s/owner' OWNER_WITH_SUSPENDED_URL = OWNER_URL + '?includeSuspendedUsers=%s' OWNER_ID_URL = OWNER_URL + '/%s' PERMISSION_OWNER = 'Owner' PERMISSION_MEMBER = 'Member' PERMISSION_DOMAIN = 'Domain' PERMISSION_ANYONE = 'Anyone' class GroupsService(gdata.apps.service.PropertyService): """Client for the Google Apps Groups service.""" def _ServiceUrl(self, service_type, is_existed, group_id, member_id, owner_email, direct_only=False, domain=None, suspended_users=False): if domain is None: domain = self.domain if service_type == 'group': if group_id != '' and is_existed: return GROUP_ID_URL % (domain, group_id) elif member_id != '': if direct_only: return GROUP_MEMBER_DIRECT_URL % (domain, urllib.quote_plus(member_id), self._Bool2Str(direct_only)) else: return GROUP_MEMBER_URL % (domain, urllib.quote_plus(member_id)) else: return BASE_URL % (domain) if service_type == 'member': if member_id != '' and is_existed: return MEMBER_ID_URL % (domain, group_id, urllib.quote_plus(member_id)) elif suspended_users: return MEMBER_WITH_SUSPENDED_URL % (domain, group_id, self._Bool2Str(suspended_users)) else: return MEMBER_URL % (domain, group_id) if service_type == 'owner': if owner_email != '' and is_existed: return OWNER_ID_URL % (domain, group_id, urllib.quote_plus(owner_email)) elif suspended_users: return OWNER_WITH_SUSPENDED_URL % (domain, group_id, self._Bool2Str(suspended_users)) else: return OWNER_URL % (domain, group_id) def _Bool2Str(self, b): if b is None: return None return str(b is True).lower() def _IsExisted(self, uri): try: self._GetProperties(uri) return True except gdata.apps.service.AppsForYourDomainException, e: if e.error_code == gdata.apps.service.ENTITY_DOES_NOT_EXIST: return False else: raise e def CreateGroup(self, group_id, group_name, description, email_permission): """Create a group. Args: group_id: The ID of the group (e.g. us-sales). group_name: The name of the group. description: A description of the group email_permission: The subscription permission of the group. Returns: A dict containing the result of the create operation. """ uri = self._ServiceUrl('group', False, group_id, '', '') properties = {} properties['groupId'] = group_id properties['groupName'] = group_name properties['description'] = description properties['emailPermission'] = email_permission return self._PostProperties(uri, properties) def UpdateGroup(self, group_id, group_name, description, email_permission): """Update a group's name, description and/or permission. Args: group_id: The ID of the group (e.g. us-sales). group_name: The name of the group. description: A description of the group email_permission: The subscription permission of the group. Returns: A dict containing the result of the update operation. """ uri = self._ServiceUrl('group', True, group_id, '', '') properties = {} properties['groupId'] = group_id properties['groupName'] = group_name properties['description'] = description properties['emailPermission'] = email_permission return self._PutProperties(uri, properties) def RetrieveGroup(self, group_id): """Retrieve a group based on its ID. Args: group_id: The ID of the group (e.g. us-sales). Returns: A dict containing the result of the retrieve operation. """ uri = self._ServiceUrl('group', True, group_id, '', '') return self._GetProperties(uri) def RetrieveAllGroups(self): """Retrieve all groups in the domain. Args: None Returns: A list containing the result of the retrieve operation. """ uri = self._ServiceUrl('group', True, '', '', '') return self._GetPropertiesList(uri) def RetrievePageOfGroups(self, start_group=None): """Retrieve one page of groups in the domain. Args: start_group: The key to continue for pagination through all groups. Returns: A feed object containing the result of the retrieve operation. """ uri = self._ServiceUrl('group', True, '', '', '') if start_group is not None: uri += "?start="+start_group property_feed = self._GetPropertyFeed(uri) return property_feed def RetrieveGroups(self, member_id, direct_only=False): """Retrieve all groups that belong to the given member_id. Args: member_id: The member's email address (e.g. member@example.com). direct_only: Boolean whether only return groups that this member directly belongs to. Returns: A list containing the result of the retrieve operation. """ uri = self._ServiceUrl('group', True, '', member_id, '', direct_only=direct_only) return self._GetPropertiesList(uri) def DeleteGroup(self, group_id): """Delete a group based on its ID. Args: group_id: The ID of the group (e.g. us-sales). Returns: A dict containing the result of the delete operation. """ uri = self._ServiceUrl('group', True, group_id, '', '') return self._DeleteProperties(uri) def AddMemberToGroup(self, member_id, group_id): """Add a member to a group. Args: member_id: The member's email address (e.g. member@example.com). group_id: The ID of the group (e.g. us-sales). Returns: A dict containing the result of the add operation. """ uri = self._ServiceUrl('member', False, group_id, member_id, '') properties = {} properties['memberId'] = member_id return self._PostProperties(uri, properties) def IsMember(self, member_id, group_id): """Check whether the given member already exists in the given group. Args: member_id: The member's email address (e.g. member@example.com). group_id: The ID of the group (e.g. us-sales). Returns: True if the member exists in the group. False otherwise. """ uri = self._ServiceUrl('member', True, group_id, member_id, '') return self._IsExisted(uri) def RetrieveMember(self, member_id, group_id): """Retrieve the given member in the given group. Args: member_id: The member's email address (e.g. member@example.com). group_id: The ID of the group (e.g. us-sales). Returns: A dict containing the result of the retrieve operation. """ uri = self._ServiceUrl('member', True, group_id, member_id, '') return self._GetProperties(uri) def RetrieveAllMembers(self, group_id, suspended_users=False): """Retrieve all members in the given group. Args: group_id: The ID of the group (e.g. us-sales). suspended_users: A boolean; should we include any suspended users in the membership list returned? Returns: A list containing the result of the retrieve operation. """ uri = self._ServiceUrl('member', True, group_id, '', '', suspended_users=suspended_users) return self._GetPropertiesList(uri) def RetrievePageOfMembers(self, group_id, suspended_users=False, start=None): """Retrieve one page of members of a given group. Args: group_id: The ID of the group (e.g. us-sales). suspended_users: A boolean; should we include any suspended users in the membership list returned? start: The key to continue for pagination through all members. Returns: A feed object containing the result of the retrieve operation. """ uri = self._ServiceUrl('member', True, group_id, '', '', suspended_users=suspended_users) if start is not None: if suspended_users: uri += "&start="+start else: uri += "?start="+start property_feed = self._GetPropertyFeed(uri) return property_feed def RemoveMemberFromGroup(self, member_id, group_id): """Remove the given member from the given group. Args: member_id: The member's email address (e.g. member@example.com). group_id: The ID of the group (e.g. us-sales). Returns: A dict containing the result of the remove operation. """ uri = self._ServiceUrl('member', True, group_id, member_id, '') return self._DeleteProperties(uri) def AddOwnerToGroup(self, owner_email, group_id): """Add an owner to a group. Args: owner_email: The email address of a group owner. group_id: The ID of the group (e.g. us-sales). Returns: A dict containing the result of the add operation. """ uri = self._ServiceUrl('owner', False, group_id, '', owner_email) properties = {} properties['email'] = owner_email return self._PostProperties(uri, properties) def IsOwner(self, owner_email, group_id): """Check whether the given member an owner of the given group. Args: owner_email: The email address of a group owner. group_id: The ID of the group (e.g. us-sales). Returns: True if the member is an owner of the given group. False otherwise. """ uri = self._ServiceUrl('owner', True, group_id, '', owner_email) return self._IsExisted(uri) def RetrieveOwner(self, owner_email, group_id): """Retrieve the given owner in the given group. Args: owner_email: The email address of a group owner. group_id: The ID of the group (e.g. us-sales). Returns: A dict containing the result of the retrieve operation. """ uri = self._ServiceUrl('owner', True, group_id, '', owner_email) return self._GetProperties(uri) def RetrieveAllOwners(self, group_id, suspended_users=False): """Retrieve all owners of the given group. Args: group_id: The ID of the group (e.g. us-sales). suspended_users: A boolean; should we include any suspended users in the ownership list returned? Returns: A list containing the result of the retrieve operation. """ uri = self._ServiceUrl('owner', True, group_id, '', '', suspended_users=suspended_users) return self._GetPropertiesList(uri) def RetrievePageOfOwners(self, group_id, suspended_users=False, start=None): """Retrieve one page of owners of the given group. Args: group_id: The ID of the group (e.g. us-sales). suspended_users: A boolean; should we include any suspended users in the ownership list returned? start: The key to continue for pagination through all owners. Returns: A feed object containing the result of the retrieve operation. """ uri = self._ServiceUrl('owner', True, group_id, '', '', suspended_users=suspended_users) if start is not None: if suspended_users: uri += "&start="+start else: uri += "?start="+start property_feed = self._GetPropertyFeed(uri) return property_feed def RemoveOwnerFromGroup(self, owner_email, group_id): """Remove the given owner from the given group. Args: owner_email: The email address of a group owner. group_id: The ID of the group (e.g. us-sales). Returns: A dict containing the result of the remove operation. """ uri = self._ServiceUrl('owner', True, group_id, '', owner_email) return self._DeleteProperties(uri) gdata-2.0.18/samples/apps/marketplace_sample/gdata/apps/adminsettings/0000750036466300116100000000000012156625015026066 5ustar afshareng00000000000000gdata-2.0.18/samples/apps/marketplace_sample/gdata/apps/adminsettings/__init__.py0000640036466300116100000000112412156622362030200 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright (C) 2008 Google # # 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. gdata-2.0.18/samples/apps/marketplace_sample/gdata/apps/adminsettings/service.py0000640036466300116100000003250012156622362030103 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright (C) 2008 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. """Allow Google Apps domain administrators to set domain admin settings. AdminSettingsService: Set admin settings.""" __author__ = 'jlee@pbu.edu' import gdata.apps import gdata.apps.service import gdata.service API_VER='2.0' class AdminSettingsService(gdata.apps.service.PropertyService): """Client for the Google Apps Admin Settings service.""" def _serviceUrl(self, setting_id, domain=None): if domain is None: domain = self.domain return '/a/feeds/domain/%s/%s/%s' % (API_VER, domain, setting_id) def genericGet(self, location): """Generic HTTP Get Wrapper Args: location: relative uri to Get Returns: A dict containing the result of the get operation.""" uri = self._serviceUrl(location) try: return self._GetProperties(uri) except gdata.service.RequestError, e: raise AppsForYourDomainException(e.args[0]) def GetDefaultLanguage(self): """Gets Domain Default Language Args: None Returns: Default Language as a string. All possible values are listed at: http://code.google.com/apis/apps/email_settings/developers_guide_protocol.html#GA_email_language_tags""" result = self.genericGet('general/defaultLanguage') return result['defaultLanguage'] def UpdateDefaultLanguage(self, defaultLanguage): """Updates Domain Default Language Args: defaultLanguage: Domain Language to set possible values are at: http://code.google.com/apis/apps/email_settings/developers_guide_protocol.html#GA_email_language_tags Returns: A dict containing the result of the put operation""" uri = self._serviceUrl('general/defaultLanguage') properties = {'defaultLanguage': defaultLanguage} return self._PutProperties(uri, properties) def GetOrganizationName(self): """Gets Domain Default Language Args: None Returns: Organization Name as a string.""" result = self.genericGet('general/organizationName') return result['organizationName'] def UpdateOrganizationName(self, organizationName): """Updates Organization Name Args: organizationName: Name of organization Returns: A dict containing the result of the put operation""" uri = self._serviceUrl('general/organizationName') properties = {'organizationName': organizationName} return self._PutProperties(uri, properties) def GetMaximumNumberOfUsers(self): """Gets Maximum Number of Users Allowed Args: None Returns: An integer, the maximum number of users""" result = self.genericGet('general/maximumNumberOfUsers') return int(result['maximumNumberOfUsers']) def GetCurrentNumberOfUsers(self): """Gets Current Number of Users Args: None Returns: An integer, the current number of users""" result = self.genericGet('general/currentNumberOfUsers') return int(result['currentNumberOfUsers']) def IsDomainVerified(self): """Is the domain verified Args: None Returns: Boolean, is domain verified""" result = self.genericGet('accountInformation/isVerified') if result['isVerified'] == 'true': return True else: return False def GetSupportPIN(self): """Gets Support PIN Args: None Returns: A string, the Support PIN""" result = self.genericGet('accountInformation/supportPIN') return result['supportPIN'] def GetEdition(self): """Gets Google Apps Domain Edition Args: None Returns: A string, the domain's edition (premier, education, partner)""" result = self.genericGet('accountInformation/edition') return result['edition'] def GetCustomerPIN(self): """Gets Customer PIN Args: None Returns: A string, the customer PIN""" result = self.genericGet('accountInformation/customerPIN') return result['customerPIN'] def GetCreationTime(self): """Gets Domain Creation Time Args: None Returns: A string, the domain's creation time""" result = self.genericGet('accountInformation/creationTime') return result['creationTime'] def GetCountryCode(self): """Gets Domain Country Code Args: None Returns: A string, the domain's country code. Possible values at: http://www.iso.org/iso/country_codes/iso_3166_code_lists/english_country_names_and_code_elements.htm""" result = self.genericGet('accountInformation/countryCode') return result['countryCode'] def GetAdminSecondaryEmail(self): """Gets Domain Admin Secondary Email Address Args: None Returns: A string, the secondary email address for domain admin""" result = self.genericGet('accountInformation/adminSecondaryEmail') return result['adminSecondaryEmail'] def UpdateAdminSecondaryEmail(self, adminSecondaryEmail): """Gets Domain Creation Time Args: adminSecondaryEmail: string, secondary email address of admin Returns: A dict containing the result of the put operation""" uri = self._serviceUrl('accountInformation/adminSecondaryEmail') properties = {'adminSecondaryEmail': adminSecondaryEmail} return self._PutProperties(uri, properties) def GetDomainLogo(self): """Gets Domain Logo This function does not make use of the Google Apps Admin Settings API, it does an HTTP Get of a url specific to the Google Apps domain. It is included for completeness sake. Args: None Returns: binary image file""" import urllib url = 'http://www.google.com/a/cpanel/'+self.domain+'/images/logo.gif' response = urllib.urlopen(url) return response.read() def UpdateDomainLogo(self, logoImage): """Update Domain's Custom Logo Args: logoImage: binary image data Returns: A dict containing the result of the put operation""" from base64 import b64encode uri = self._serviceUrl('appearance/customLogo') properties = {'logoImage': b64encode(logoImage)} return self._PutProperties(uri, properties) def GetCNAMEVerificationStatus(self): """Gets Domain CNAME Verification Status Args: None Returns: A dict {recordName, verified, verifiedMethod}""" return self.genericGet('verification/cname') def UpdateCNAMEVerificationStatus(self, verified): """Updates CNAME Verification Status Args: verified: boolean, True will retry verification process Returns: A dict containing the result of the put operation""" uri = self._serviceUrl('verification/cname') properties = self.GetCNAMEVerificationStatus() properties['verified'] = verified return self._PutProperties(uri, properties) def GetMXVerificationStatus(self): """Gets Domain MX Verification Status Args: None Returns: A dict {verified, verifiedMethod}""" return self.genericGet('verification/mx') def UpdateMXVerificationStatus(self, verified): """Updates MX Verification Status Args: verified: boolean, True will retry verification process Returns: A dict containing the result of the put operation""" uri = self._serviceUrl('verification/mx') properties = self.GetMXVerificationStatus() properties['verified'] = verified return self._PutProperties(uri, properties) def GetSSOSettings(self): """Gets Domain Single Sign-On Settings Args: None Returns: A dict {samlSignonUri, samlLogoutUri, changePasswordUri, enableSSO, ssoWhitelist, useDomainSpecificIssuer}""" return self.genericGet('sso/general') def UpdateSSOSettings(self, enableSSO=None, samlSignonUri=None, samlLogoutUri=None, changePasswordUri=None, ssoWhitelist=None, useDomainSpecificIssuer=None): """Update SSO Settings. Args: enableSSO: boolean, SSO Master on/off switch samlSignonUri: string, SSO Login Page samlLogoutUri: string, SSO Logout Page samlPasswordUri: string, SSO Password Change Page ssoWhitelist: string, Range of IP Addresses which will see SSO useDomainSpecificIssuer: boolean, Include Google Apps Domain in Issuer Returns: A dict containing the result of the update operation. """ uri = self._serviceUrl('sso/general') #Get current settings, replace Nones with '' properties = self.GetSSOSettings() if properties['samlSignonUri'] == None: properties['samlSignonUri'] = '' if properties['samlLogoutUri'] == None: properties['samlLogoutUri'] = '' if properties['changePasswordUri'] == None: properties['changePasswordUri'] = '' if properties['ssoWhitelist'] == None: properties['ssoWhitelist'] = '' #update only the values we were passed if enableSSO != None: properties['enableSSO'] = gdata.apps.service._bool2str(enableSSO) if samlSignonUri != None: properties['samlSignonUri'] = samlSignonUri if samlLogoutUri != None: properties['samlLogoutUri'] = samlLogoutUri if changePasswordUri != None: properties['changePasswordUri'] = changePasswordUri if ssoWhitelist != None: properties['ssoWhitelist'] = ssoWhitelist if useDomainSpecificIssuer != None: properties['useDomainSpecificIssuer'] = gdata.apps.service._bool2str(useDomainSpecificIssuer) return self._PutProperties(uri, properties) def GetSSOKey(self): """Gets Domain Single Sign-On Signing Key Args: None Returns: A dict {modulus, exponent, algorithm, format}""" return self.genericGet('sso/signingkey') def UpdateSSOKey(self, signingKey): """Update SSO Settings. Args: signingKey: string, public key to be uploaded Returns: A dict containing the result of the update operation.""" uri = self._serviceUrl('sso/signingkey') properties = {'signingKey': signingKey} return self._PutProperties(uri, properties) def IsUserMigrationEnabled(self): """Is User Migration Enabled Args: None Returns: boolean, is user migration enabled""" result = self.genericGet('email/migration') if result['enableUserMigration'] == 'true': return True else: return False def UpdateUserMigrationStatus(self, enableUserMigration): """Update User Migration Status Args: enableUserMigration: boolean, user migration enable/disable Returns: A dict containing the result of the update operation.""" uri = self._serviceUrl('email/migration') properties = {'enableUserMigration': enableUserMigration} return self._PutProperties(uri, properties) def GetOutboundGatewaySettings(self): """Get Outbound Gateway Settings Args: None Returns: A dict {smartHost, smtpMode}""" uri = self._serviceUrl('email/gateway') try: return self._GetProperties(uri) except gdata.service.RequestError, e: raise AppsForYourDomainException(e.args[0]) except TypeError: #if no outbound gateway is set, we get a TypeError, #catch it and return nothing... return {'smartHost': None, 'smtpMode': None} def UpdateOutboundGatewaySettings(self, smartHost=None, smtpMode=None): """Update Outbound Gateway Settings Args: smartHost: string, ip address or hostname of outbound gateway smtpMode: string, SMTP or SMTP_TLS Returns: A dict containing the result of the update operation.""" uri = self._serviceUrl('email/gateway') #Get current settings, replace Nones with '' properties = GetOutboundGatewaySettings() if properties['smartHost'] == None: properties['smartHost'] = '' if properties['smtpMode'] == None: properties['smtpMode'] = '' #If we were passed new values for smartHost or smtpMode, update them if smartHost != None: properties['smartHost'] = smartHost if smtpMode != None: properties['smtpMode'] = smtpMode return self._PutProperties(uri, properties) def AddEmailRoute(self, routeDestination, routeRewriteTo, routeEnabled, bounceNotifications, accountHandling): """Adds Domain Email Route Args: routeDestination: string, destination ip address or hostname routeRewriteTo: boolean, rewrite smtp envelop To: routeEnabled: boolean, enable disable email routing bounceNotifications: boolean, send bound notificiations to sender accountHandling: string, which to route, "allAccounts", "provisionedAccounts", "unknownAccounts" Returns: A dict containing the result of the update operation.""" uri = self._serviceUrl('emailrouting') properties = {} properties['routeDestination'] = routeDestination properties['routeRewriteTo'] = gdata.apps.service._bool2str(routeRewriteTo) properties['routeEnabled'] = gdata.apps.service._bool2str(routeEnabled) properties['bounceNotifications'] = gdata.apps.service._bool2str(bounceNotifications) properties['accountHandling'] = accountHandling return self._PostProperties(uri, properties) gdata-2.0.18/samples/apps/marketplace_sample/gdata/apps/service.py0000640036466300116100000005014512156622362025237 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright (C) 2007 SIOS Technology, 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. __author__ = 'tmatsuo@sios.com (Takashi MATSUO)' try: from xml.etree import cElementTree as ElementTree except ImportError: try: import cElementTree as ElementTree except ImportError: try: from xml.etree import ElementTree except ImportError: from elementtree import ElementTree import urllib import gdata import atom.service import gdata.service import gdata.apps import atom API_VER="2.0" HTTP_OK=200 UNKOWN_ERROR=1000 USER_DELETED_RECENTLY=1100 USER_SUSPENDED=1101 DOMAIN_USER_LIMIT_EXCEEDED=1200 DOMAIN_ALIAS_LIMIT_EXCEEDED=1201 DOMAIN_SUSPENDED=1202 DOMAIN_FEATURE_UNAVAILABLE=1203 ENTITY_EXISTS=1300 ENTITY_DOES_NOT_EXIST=1301 ENTITY_NAME_IS_RESERVED=1302 ENTITY_NAME_NOT_VALID=1303 INVALID_GIVEN_NAME=1400 INVALID_FAMILY_NAME=1401 INVALID_PASSWORD=1402 INVALID_USERNAME=1403 INVALID_HASH_FUNCTION_NAME=1404 INVALID_HASH_DIGGEST_LENGTH=1405 INVALID_EMAIL_ADDRESS=1406 INVALID_QUERY_PARAMETER_VALUE=1407 TOO_MANY_RECIPIENTS_ON_EMAIL_LIST=1500 DEFAULT_QUOTA_LIMIT='2048' class Error(Exception): pass class AppsForYourDomainException(Error): def __init__(self, response): Error.__init__(self, response) try: self.element_tree = ElementTree.fromstring(response['body']) self.error_code = int(self.element_tree[0].attrib['errorCode']) self.reason = self.element_tree[0].attrib['reason'] self.invalidInput = self.element_tree[0].attrib['invalidInput'] except: self.error_code = UNKOWN_ERROR class AppsService(gdata.service.GDataService): """Client for the Google Apps Provisioning service.""" def __init__(self, email=None, password=None, domain=None, source=None, server='apps-apis.google.com', additional_headers=None, **kwargs): """Creates a client for the Google Apps Provisioning service. Args: email: string (optional) The user's email address, used for authentication. password: string (optional) The user's password. domain: string (optional) The Google Apps domain name. source: string (optional) The name of the user's application. server: string (optional) The name of the server to which a connection will be opened. Default value: 'apps-apis.google.com'. **kwargs: The other parameters to pass to gdata.service.GDataService constructor. """ gdata.service.GDataService.__init__( self, email=email, password=password, service='apps', source=source, server=server, additional_headers=additional_headers, **kwargs) self.ssl = True self.port = 443 self.domain = domain def _baseURL(self): return "/a/feeds/%s" % self.domain def AddAllElementsFromAllPages(self, link_finder, func): """retrieve all pages and add all elements""" next = link_finder.GetNextLink() while next is not None: next_feed = self.Get(next.href, converter=func) for a_entry in next_feed.entry: link_finder.entry.append(a_entry) next = next_feed.GetNextLink() return link_finder def RetrievePageOfEmailLists(self, start_email_list_name=None, num_retries=gdata.service.DEFAULT_NUM_RETRIES, delay=gdata.service.DEFAULT_DELAY, backoff=gdata.service.DEFAULT_BACKOFF): """Retrieve one page of email list""" uri = "%s/emailList/%s" % (self._baseURL(), API_VER) if start_email_list_name is not None: uri += "?startEmailListName=%s" % start_email_list_name try: return gdata.apps.EmailListFeedFromString(str(self.GetWithRetries( uri, num_retries=num_retries, delay=delay, backoff=backoff))) except gdata.service.RequestError, e: raise AppsForYourDomainException(e.args[0]) def GetGeneratorForAllEmailLists( self, num_retries=gdata.service.DEFAULT_NUM_RETRIES, delay=gdata.service.DEFAULT_DELAY, backoff=gdata.service.DEFAULT_BACKOFF): """Retrieve a generator for all emaillists in this domain.""" first_page = self.RetrievePageOfEmailLists(num_retries=num_retries, delay=delay, backoff=backoff) return self.GetGeneratorFromLinkFinder( first_page, gdata.apps.EmailListRecipientFeedFromString, num_retries=num_retries, delay=delay, backoff=backoff) def RetrieveAllEmailLists(self): """Retrieve all email list of a domain.""" ret = self.RetrievePageOfEmailLists() # pagination return self.AddAllElementsFromAllPages( ret, gdata.apps.EmailListFeedFromString) def RetrieveEmailList(self, list_name): """Retreive a single email list by the list's name.""" uri = "%s/emailList/%s/%s" % ( self._baseURL(), API_VER, list_name) try: return self.Get(uri, converter=gdata.apps.EmailListEntryFromString) except gdata.service.RequestError, e: raise AppsForYourDomainException(e.args[0]) def RetrieveEmailLists(self, recipient): """Retrieve All Email List Subscriptions for an Email Address.""" uri = "%s/emailList/%s?recipient=%s" % ( self._baseURL(), API_VER, recipient) try: ret = gdata.apps.EmailListFeedFromString(str(self.Get(uri))) except gdata.service.RequestError, e: raise AppsForYourDomainException(e.args[0]) # pagination return self.AddAllElementsFromAllPages( ret, gdata.apps.EmailListFeedFromString) def RemoveRecipientFromEmailList(self, recipient, list_name): """Remove recipient from email list.""" uri = "%s/emailList/%s/%s/recipient/%s" % ( self._baseURL(), API_VER, list_name, recipient) try: self.Delete(uri) except gdata.service.RequestError, e: raise AppsForYourDomainException(e.args[0]) def RetrievePageOfRecipients(self, list_name, start_recipient=None, num_retries=gdata.service.DEFAULT_NUM_RETRIES, delay=gdata.service.DEFAULT_DELAY, backoff=gdata.service.DEFAULT_BACKOFF): """Retrieve one page of recipient of an email list. """ uri = "%s/emailList/%s/%s/recipient" % ( self._baseURL(), API_VER, list_name) if start_recipient is not None: uri += "?startRecipient=%s" % start_recipient try: return gdata.apps.EmailListRecipientFeedFromString(str( self.GetWithRetries( uri, num_retries=num_retries, delay=delay, backoff=backoff))) except gdata.service.RequestError, e: raise AppsForYourDomainException(e.args[0]) def GetGeneratorForAllRecipients( self, list_name, num_retries=gdata.service.DEFAULT_NUM_RETRIES, delay=gdata.service.DEFAULT_DELAY, backoff=gdata.service.DEFAULT_BACKOFF): """Retrieve a generator for all recipients of a particular emaillist.""" first_page = self.RetrievePageOfRecipients(list_name, num_retries=num_retries, delay=delay, backoff=backoff) return self.GetGeneratorFromLinkFinder( first_page, gdata.apps.EmailListRecipientFeedFromString, num_retries=num_retries, delay=delay, backoff=backoff) def RetrieveAllRecipients(self, list_name): """Retrieve all recipient of an email list.""" ret = self.RetrievePageOfRecipients(list_name) # pagination return self.AddAllElementsFromAllPages( ret, gdata.apps.EmailListRecipientFeedFromString) def AddRecipientToEmailList(self, recipient, list_name): """Add a recipient to a email list.""" uri = "%s/emailList/%s/%s/recipient" % ( self._baseURL(), API_VER, list_name) recipient_entry = gdata.apps.EmailListRecipientEntry() recipient_entry.who = gdata.apps.Who(email=recipient) try: return gdata.apps.EmailListRecipientEntryFromString( str(self.Post(recipient_entry, uri))) except gdata.service.RequestError, e: raise AppsForYourDomainException(e.args[0]) def DeleteEmailList(self, list_name): """Delete a email list""" uri = "%s/emailList/%s/%s" % (self._baseURL(), API_VER, list_name) try: self.Delete(uri) except gdata.service.RequestError, e: raise AppsForYourDomainException(e.args[0]) def CreateEmailList(self, list_name): """Create a email list. """ uri = "%s/emailList/%s" % (self._baseURL(), API_VER) email_list_entry = gdata.apps.EmailListEntry() email_list_entry.email_list = gdata.apps.EmailList(name=list_name) try: return gdata.apps.EmailListEntryFromString( str(self.Post(email_list_entry, uri))) except gdata.service.RequestError, e: raise AppsForYourDomainException(e.args[0]) def DeleteNickname(self, nickname): """Delete a nickname""" uri = "%s/nickname/%s/%s" % (self._baseURL(), API_VER, nickname) try: self.Delete(uri) except gdata.service.RequestError, e: raise AppsForYourDomainException(e.args[0]) def RetrievePageOfNicknames(self, start_nickname=None, num_retries=gdata.service.DEFAULT_NUM_RETRIES, delay=gdata.service.DEFAULT_DELAY, backoff=gdata.service.DEFAULT_BACKOFF): """Retrieve one page of nicknames in the domain""" uri = "%s/nickname/%s" % (self._baseURL(), API_VER) if start_nickname is not None: uri += "?startNickname=%s" % start_nickname try: return gdata.apps.NicknameFeedFromString(str(self.GetWithRetries( uri, num_retries=num_retries, delay=delay, backoff=backoff))) except gdata.service.RequestError, e: raise AppsForYourDomainException(e.args[0]) def GetGeneratorForAllNicknames( self, num_retries=gdata.service.DEFAULT_NUM_RETRIES, delay=gdata.service.DEFAULT_DELAY, backoff=gdata.service.DEFAULT_BACKOFF): """Retrieve a generator for all nicknames in this domain.""" first_page = self.RetrievePageOfNicknames(num_retries=num_retries, delay=delay, backoff=backoff) return self.GetGeneratorFromLinkFinder( first_page, gdata.apps.NicknameFeedFromString, num_retries=num_retries, delay=delay, backoff=backoff) def RetrieveAllNicknames(self): """Retrieve all nicknames in the domain""" ret = self.RetrievePageOfNicknames() # pagination return self.AddAllElementsFromAllPages( ret, gdata.apps.NicknameFeedFromString) def GetGeneratorForAllNicknamesOfAUser( self, user_name, num_retries=gdata.service.DEFAULT_NUM_RETRIES, delay=gdata.service.DEFAULT_DELAY, backoff=gdata.service.DEFAULT_BACKOFF): """Retrieve a generator for all nicknames of a particular user.""" uri = "%s/nickname/%s?username=%s" % (self._baseURL(), API_VER, user_name) try: first_page = gdata.apps.NicknameFeedFromString(str(self.GetWithRetries( uri, num_retries=num_retries, delay=delay, backoff=backoff))) except gdata.service.RequestError, e: raise AppsForYourDomainException(e.args[0]) return self.GetGeneratorFromLinkFinder( first_page, gdata.apps.NicknameFeedFromString, num_retries=num_retries, delay=delay, backoff=backoff) def RetrieveNicknames(self, user_name): """Retrieve nicknames of the user""" uri = "%s/nickname/%s?username=%s" % (self._baseURL(), API_VER, user_name) try: ret = gdata.apps.NicknameFeedFromString(str(self.Get(uri))) except gdata.service.RequestError, e: raise AppsForYourDomainException(e.args[0]) # pagination return self.AddAllElementsFromAllPages( ret, gdata.apps.NicknameFeedFromString) def RetrieveNickname(self, nickname): """Retrieve a nickname. Args: nickname: string The nickname to retrieve Returns: gdata.apps.NicknameEntry """ uri = "%s/nickname/%s/%s" % (self._baseURL(), API_VER, nickname) try: return gdata.apps.NicknameEntryFromString(str(self.Get(uri))) except gdata.service.RequestError, e: raise AppsForYourDomainException(e.args[0]) def CreateNickname(self, user_name, nickname): """Create a nickname""" uri = "%s/nickname/%s" % (self._baseURL(), API_VER) nickname_entry = gdata.apps.NicknameEntry() nickname_entry.login = gdata.apps.Login(user_name=user_name) nickname_entry.nickname = gdata.apps.Nickname(name=nickname) try: return gdata.apps.NicknameEntryFromString( str(self.Post(nickname_entry, uri))) except gdata.service.RequestError, e: raise AppsForYourDomainException(e.args[0]) def DeleteUser(self, user_name): """Delete a user account""" uri = "%s/user/%s/%s" % (self._baseURL(), API_VER, user_name) try: return self.Delete(uri) except gdata.service.RequestError, e: raise AppsForYourDomainException(e.args[0]) def UpdateUser(self, user_name, user_entry): """Update a user account.""" uri = "%s/user/%s/%s" % (self._baseURL(), API_VER, user_name) try: return gdata.apps.UserEntryFromString(str(self.Put(user_entry, uri))) except gdata.service.RequestError, e: raise AppsForYourDomainException(e.args[0]) def CreateUser(self, user_name, family_name, given_name, password, suspended='false', quota_limit=None, password_hash_function=None, change_password=None): """Create a user account. """ uri = "%s/user/%s" % (self._baseURL(), API_VER) user_entry = gdata.apps.UserEntry() user_entry.login = gdata.apps.Login( user_name=user_name, password=password, suspended=suspended, hash_function_name=password_hash_function, change_password=change_password) user_entry.name = gdata.apps.Name(family_name=family_name, given_name=given_name) if quota_limit is not None: user_entry.quota = gdata.apps.Quota(limit=str(quota_limit)) try: return gdata.apps.UserEntryFromString(str(self.Post(user_entry, uri))) except gdata.service.RequestError, e: raise AppsForYourDomainException(e.args[0]) def SuspendUser(self, user_name): user_entry = self.RetrieveUser(user_name) if user_entry.login.suspended != 'true': user_entry.login.suspended = 'true' user_entry = self.UpdateUser(user_name, user_entry) return user_entry def RestoreUser(self, user_name): user_entry = self.RetrieveUser(user_name) if user_entry.login.suspended != 'false': user_entry.login.suspended = 'false' user_entry = self.UpdateUser(user_name, user_entry) return user_entry def RetrieveUser(self, user_name): """Retrieve an user account. Args: user_name: string The user name to retrieve Returns: gdata.apps.UserEntry """ uri = "%s/user/%s/%s" % (self._baseURL(), API_VER, user_name) try: return gdata.apps.UserEntryFromString(str(self.Get(uri))) except gdata.service.RequestError, e: raise AppsForYourDomainException(e.args[0]) def RetrievePageOfUsers(self, start_username=None, num_retries=gdata.service.DEFAULT_NUM_RETRIES, delay=gdata.service.DEFAULT_DELAY, backoff=gdata.service.DEFAULT_BACKOFF): """Retrieve one page of users in this domain.""" uri = "%s/user/%s" % (self._baseURL(), API_VER) if start_username is not None: uri += "?startUsername=%s" % start_username try: return gdata.apps.UserFeedFromString(str(self.GetWithRetries( uri, num_retries=num_retries, delay=delay, backoff=backoff))) except gdata.service.RequestError, e: raise AppsForYourDomainException(e.args[0]) def GetGeneratorForAllUsers(self, num_retries=gdata.service.DEFAULT_NUM_RETRIES, delay=gdata.service.DEFAULT_DELAY, backoff=gdata.service.DEFAULT_BACKOFF): """Retrieve a generator for all users in this domain.""" first_page = self.RetrievePageOfUsers(num_retries=num_retries, delay=delay, backoff=backoff) return self.GetGeneratorFromLinkFinder( first_page, gdata.apps.UserFeedFromString, num_retries=num_retries, delay=delay, backoff=backoff) def RetrieveAllUsers(self): """Retrieve all users in this domain. OBSOLETE""" ret = self.RetrievePageOfUsers() # pagination return self.AddAllElementsFromAllPages( ret, gdata.apps.UserFeedFromString) class PropertyService(gdata.service.GDataService): """Client for the Google Apps Property service.""" def __init__(self, email=None, password=None, domain=None, source=None, server='apps-apis.google.com', additional_headers=None): gdata.service.GDataService.__init__(self, email=email, password=password, service='apps', source=source, server=server, additional_headers=additional_headers) self.ssl = True self.port = 443 self.domain = domain def AddAllElementsFromAllPages(self, link_finder, func): """retrieve all pages and add all elements""" next = link_finder.GetNextLink() while next is not None: next_feed = self.Get(next.href, converter=func) for a_entry in next_feed.entry: link_finder.entry.append(a_entry) next = next_feed.GetNextLink() return link_finder def _GetPropertyEntry(self, properties): property_entry = gdata.apps.PropertyEntry() property = [] for name, value in properties.iteritems(): if name is not None and value is not None: property.append(gdata.apps.Property(name=name, value=value)) property_entry.property = property return property_entry def _PropertyEntry2Dict(self, property_entry): properties = {} for i, property in enumerate(property_entry.property): properties[property.name] = property.value return properties def _GetPropertyFeed(self, uri): try: return gdata.apps.PropertyFeedFromString(str(self.Get(uri))) except gdata.service.RequestError, e: raise gdata.apps.service.AppsForYourDomainException(e.args[0]) def _GetPropertiesList(self, uri): property_feed = self._GetPropertyFeed(uri) # pagination property_feed = self.AddAllElementsFromAllPages( property_feed, gdata.apps.PropertyFeedFromString) properties_list = [] for property_entry in property_feed.entry: properties_list.append(self._PropertyEntry2Dict(property_entry)) return properties_list def _GetProperties(self, uri): try: return self._PropertyEntry2Dict(gdata.apps.PropertyEntryFromString( str(self.Get(uri)))) except gdata.service.RequestError, e: raise gdata.apps.service.AppsForYourDomainException(e.args[0]) def _PostProperties(self, uri, properties): property_entry = self._GetPropertyEntry(properties) try: return self._PropertyEntry2Dict(gdata.apps.PropertyEntryFromString( str(self.Post(property_entry, uri)))) except gdata.service.RequestError, e: raise gdata.apps.service.AppsForYourDomainException(e.args[0]) def _PutProperties(self, uri, properties): property_entry = self._GetPropertyEntry(properties) try: return self._PropertyEntry2Dict(gdata.apps.PropertyEntryFromString( str(self.Put(property_entry, uri)))) except gdata.service.RequestError, e: raise gdata.apps.service.AppsForYourDomainException(e.args[0]) def _DeleteProperties(self, uri): try: self.Delete(uri) except gdata.service.RequestError, e: raise gdata.apps.service.AppsForYourDomainException(e.args[0]) def _bool2str(b): if b is None: return None return str(b is True).lower() gdata-2.0.18/samples/apps/marketplace_sample/gdata/urlfetch.py0000640036466300116100000002216212156622362024446 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright (C) 2008 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. """Provides HTTP functions for gdata.service to use on Google App Engine AppEngineHttpClient: Provides an HTTP request method which uses App Engine's urlfetch API. Set the http_client member of a GDataService object to an instance of an AppEngineHttpClient to allow the gdata library to run on Google App Engine. run_on_appengine: Function which will modify an existing GDataService object to allow it to run on App Engine. It works by creating a new instance of the AppEngineHttpClient and replacing the GDataService object's http_client. HttpRequest: Function that wraps google.appengine.api.urlfetch.Fetch in a common interface which is used by gdata.service.GDataService. In other words, this module can be used as the gdata service request handler so that all HTTP requests will be performed by the hosting Google App Engine server. """ __author__ = 'api.jscudder (Jeff Scudder)' import StringIO import atom.service import atom.http_interface from google.appengine.api import urlfetch def run_on_appengine(gdata_service): """Modifies a GDataService object to allow it to run on App Engine. Args: gdata_service: An instance of AtomService, GDataService, or any of their subclasses which has an http_client member. """ gdata_service.http_client = AppEngineHttpClient() class AppEngineHttpClient(atom.http_interface.GenericHttpClient): def __init__(self, headers=None): self.debug = False self.headers = headers or {} def request(self, operation, url, data=None, headers=None): """Performs an HTTP call to the server, supports GET, POST, PUT, and DELETE. Usage example, perform and HTTP GET on http://www.google.com/: import atom.http client = atom.http.HttpClient() http_response = client.request('GET', 'http://www.google.com/') Args: operation: str The HTTP operation to be performed. This is usually one of 'GET', 'POST', 'PUT', or 'DELETE' data: filestream, list of parts, or other object which can be converted to a string. Should be set to None when performing a GET or DELETE. If data is a file-like object which can be read, this method will read a chunk of 100K bytes at a time and send them. If the data is a list of parts to be sent, each part will be evaluated and sent. url: The full URL to which the request should be sent. Can be a string or atom.url.Url. headers: dict of strings. HTTP headers which should be sent in the request. """ all_headers = self.headers.copy() if headers: all_headers.update(headers) # Construct the full payload. # Assume that data is None or a string. data_str = data if data: if isinstance(data, list): # If data is a list of different objects, convert them all to strings # and join them together. converted_parts = [__ConvertDataPart(x) for x in data] data_str = ''.join(converted_parts) else: data_str = __ConvertDataPart(data) # If the list of headers does not include a Content-Length, attempt to # calculate it based on the data object. if data and 'Content-Length' not in all_headers: all_headers['Content-Length'] = len(data_str) # Set the content type to the default value if none was set. if 'Content-Type' not in all_headers: all_headers['Content-Type'] = 'application/atom+xml' # Lookup the urlfetch operation which corresponds to the desired HTTP verb. if operation == 'GET': method = urlfetch.GET elif operation == 'POST': method = urlfetch.POST elif operation == 'PUT': method = urlfetch.PUT elif operation == 'DELETE': method = urlfetch.DELETE else: method = None return HttpResponse(urlfetch.Fetch(url=str(url), payload=data_str, method=method, headers=all_headers)) def HttpRequest(service, operation, data, uri, extra_headers=None, url_params=None, escape_params=True, content_type='application/atom+xml'): """Performs an HTTP call to the server, supports GET, POST, PUT, and DELETE. This function is deprecated, use AppEngineHttpClient.request instead. To use this module with gdata.service, you can set this module to be the http_request_handler so that HTTP requests use Google App Engine's urlfetch. import gdata.service import gdata.urlfetch gdata.service.http_request_handler = gdata.urlfetch Args: service: atom.AtomService object which contains some of the parameters needed to make the request. The following members are used to construct the HTTP call: server (str), additional_headers (dict), port (int), and ssl (bool). operation: str The HTTP operation to be performed. This is usually one of 'GET', 'POST', 'PUT', or 'DELETE' data: filestream, list of parts, or other object which can be converted to a string. Should be set to None when performing a GET or PUT. If data is a file-like object which can be read, this method will read a chunk of 100K bytes at a time and send them. If the data is a list of parts to be sent, each part will be evaluated and sent. uri: The beginning of the URL to which the request should be sent. Examples: '/', '/base/feeds/snippets', '/m8/feeds/contacts/default/base' extra_headers: dict of strings. HTTP headers which should be sent in the request. These headers are in addition to those stored in service.additional_headers. url_params: dict of strings. Key value pairs to be added to the URL as URL parameters. For example {'foo':'bar', 'test':'param'} will become ?foo=bar&test=param. escape_params: bool default True. If true, the keys and values in url_params will be URL escaped when the form is constructed (Special characters converted to %XX form.) content_type: str The MIME type for the data being sent. Defaults to 'application/atom+xml', this is only used if data is set. """ full_uri = atom.service.BuildUri(uri, url_params, escape_params) (server, port, ssl, partial_uri) = atom.service.ProcessUrl(service, full_uri) # Construct the full URL for the request. if ssl: full_url = 'https://%s%s' % (server, partial_uri) else: full_url = 'http://%s%s' % (server, partial_uri) # Construct the full payload. # Assume that data is None or a string. data_str = data if data: if isinstance(data, list): # If data is a list of different objects, convert them all to strings # and join them together. converted_parts = [__ConvertDataPart(x) for x in data] data_str = ''.join(converted_parts) else: data_str = __ConvertDataPart(data) # Construct the dictionary of HTTP headers. headers = {} if isinstance(service.additional_headers, dict): headers = service.additional_headers.copy() if isinstance(extra_headers, dict): for header, value in extra_headers.iteritems(): headers[header] = value # Add the content type header (we don't need to calculate content length, # since urlfetch.Fetch will calculate for us). if content_type: headers['Content-Type'] = content_type # Lookup the urlfetch operation which corresponds to the desired HTTP verb. if operation == 'GET': method = urlfetch.GET elif operation == 'POST': method = urlfetch.POST elif operation == 'PUT': method = urlfetch.PUT elif operation == 'DELETE': method = urlfetch.DELETE else: method = None return HttpResponse(urlfetch.Fetch(url=full_url, payload=data_str, method=method, headers=headers)) def __ConvertDataPart(data): if not data or isinstance(data, str): return data elif hasattr(data, 'read'): # data is a file like object, so read it completely. return data.read() # The data object was not a file. # Try to convert to a string and send the data. return str(data) class HttpResponse(object): """Translates a urlfetch resoinse to look like an hhtplib resoinse. Used to allow the resoinse from HttpRequest to be usable by gdata.service methods. """ def __init__(self, urlfetch_response): self.body = StringIO.StringIO(urlfetch_response.content) self.headers = urlfetch_response.headers self.status = urlfetch_response.status_code self.reason = '' def read(self, length=None): if not length: return self.body.read() else: return self.body.read(length) def getheader(self, name): if not self.headers.has_key(name): return self.headers[name.lower()] return self.headers[name] gdata-2.0.18/samples/apps/marketplace_sample/gdata/calendar/0000750036466300116100000000000012156625015024023 5ustar afshareng00000000000000gdata-2.0.18/samples/apps/marketplace_sample/gdata/calendar/data.py0000640036466300116100000002317412156622362025320 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright (C) 2009 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. """Contains the data classes of the Google Calendar Data API""" __author__ = 'j.s@google.com (Jeff Scudder)' import atom.core import atom.data import gdata.acl.data import gdata.data import gdata.geo.data import gdata.opensearch.data GCAL_NAMESPACE = 'http://schemas.google.com/gCal/2005' GCAL_TEMPLATE = '{%s}%%s' % GCAL_NAMESPACE WEB_CONTENT_LINK_REL = '%s/%s' % (GCAL_NAMESPACE, 'webContent') class AccessLevelProperty(atom.core.XmlElement): """Describes how much a given user may do with an event or calendar""" _qname = GCAL_TEMPLATE % 'accesslevel' value = 'value' class AllowGSync2Property(atom.core.XmlElement): """Whether the user is permitted to run Google Apps Sync""" _qname = GCAL_TEMPLATE % 'allowGSync2' value = 'value' class AllowGSyncProperty(atom.core.XmlElement): """Whether the user is permitted to run Google Apps Sync""" _qname = GCAL_TEMPLATE % 'allowGSync' value = 'value' class AnyoneCanAddSelfProperty(atom.core.XmlElement): """Whether anyone can add self as attendee""" _qname = GCAL_TEMPLATE % 'anyoneCanAddSelf' value = 'value' class CalendarAclRole(gdata.acl.data.AclRole): """Describes the Calendar roles of an entry in the Calendar access control list""" _qname = gdata.acl.data.GACL_TEMPLATE % 'role' class CalendarCommentEntry(gdata.data.GDEntry): """Describes an entry in a feed of a Calendar event's comments""" class CalendarCommentFeed(gdata.data.GDFeed): """Describes feed of a Calendar event's comments""" entry = [CalendarCommentEntry] class CalendarComments(gdata.data.Comments): """Describes a container of a feed link for Calendar comment entries""" _qname = gdata.data.GD_TEMPLATE % 'comments' class CalendarExtendedProperty(gdata.data.ExtendedProperty): """Defines a value for the realm attribute that is used only in the calendar API""" _qname = gdata.data.GD_TEMPLATE % 'extendedProperty' class CalendarWhere(gdata.data.Where): """Extends the base Where class with Calendar extensions""" _qname = gdata.data.GD_TEMPLATE % 'where' class ColorProperty(atom.core.XmlElement): """Describes the color of a calendar""" _qname = GCAL_TEMPLATE % 'color' value = 'value' class GuestsCanInviteOthersProperty(atom.core.XmlElement): """Whether guests can invite others to the event""" _qname = GCAL_TEMPLATE % 'guestsCanInviteOthers' value = 'value' class GuestsCanModifyProperty(atom.core.XmlElement): """Whether guests can modify event""" _qname = GCAL_TEMPLATE % 'guestsCanModify' value = 'value' class GuestsCanSeeGuestsProperty(atom.core.XmlElement): """Whether guests can see other attendees""" _qname = GCAL_TEMPLATE % 'guestsCanSeeGuests' value = 'value' class HiddenProperty(atom.core.XmlElement): """Describes whether a calendar is hidden""" _qname = GCAL_TEMPLATE % 'hidden' value = 'value' class IcalUIDProperty(atom.core.XmlElement): """Describes the UID in the ical export of the event""" _qname = GCAL_TEMPLATE % 'uid' value = 'value' class OverrideNameProperty(atom.core.XmlElement): """Describes the override name property of a calendar""" _qname = GCAL_TEMPLATE % 'overridename' value = 'value' class PrivateCopyProperty(atom.core.XmlElement): """Indicates whether this is a private copy of the event, changes to which should not be sent to other calendars""" _qname = GCAL_TEMPLATE % 'privateCopy' value = 'value' class QuickAddProperty(atom.core.XmlElement): """Describes whether gd:content is for quick-add processing""" _qname = GCAL_TEMPLATE % 'quickadd' value = 'value' class ResourceProperty(atom.core.XmlElement): """Describes whether gd:who is a resource such as a conference room""" _qname = GCAL_TEMPLATE % 'resource' value = 'value' id = 'id' class EventWho(gdata.data.Who): """Extends the base Who class with Calendar extensions""" _qname = gdata.data.GD_TEMPLATE % 'who' resource = ResourceProperty class SelectedProperty(atom.core.XmlElement): """Describes whether a calendar is selected""" _qname = GCAL_TEMPLATE % 'selected' value = 'value' class SendAclNotificationsProperty(atom.core.XmlElement): """Describes whether to send ACL notifications to grantees""" _qname = GCAL_TEMPLATE % 'sendAclNotifications' value = 'value' class CalendarAclEntry(gdata.acl.data.AclEntry): """Describes an entry in a feed of a Calendar access control list (ACL)""" send_acl_notifications = SendAclNotificationsProperty class CalendarAclFeed(gdata.data.GDFeed): """Describes a Calendar access contorl list (ACL) feed""" entry = [CalendarAclEntry] class SendEventNotificationsProperty(atom.core.XmlElement): """Describes whether to send event notifications to other participants of the event""" _qname = GCAL_TEMPLATE % 'sendEventNotifications' value = 'value' class SequenceNumberProperty(atom.core.XmlElement): """Describes sequence number of an event""" _qname = GCAL_TEMPLATE % 'sequence' value = 'value' class CalendarRecurrenceExceptionEntry(gdata.data.GDEntry): """Describes an entry used by a Calendar recurrence exception entry link""" uid = IcalUIDProperty sequence = SequenceNumberProperty class CalendarRecurrenceException(gdata.data.RecurrenceException): """Describes an exception to a recurring Calendar event""" _qname = gdata.data.GD_TEMPLATE % 'recurrenceException' class SettingsProperty(atom.core.XmlElement): """User preference name-value pair""" _qname = GCAL_TEMPLATE % 'settingsProperty' name = 'name' value = 'value' class SettingsEntry(gdata.data.GDEntry): """Describes a Calendar Settings property entry""" settings_property = SettingsProperty class CalendarSettingsFeed(gdata.data.GDFeed): """Personal settings for Calendar application""" entry = [SettingsEntry] class SuppressReplyNotificationsProperty(atom.core.XmlElement): """Lists notification methods to be suppressed for this reply""" _qname = GCAL_TEMPLATE % 'suppressReplyNotifications' methods = 'methods' class SyncEventProperty(atom.core.XmlElement): """Describes whether this is a sync scenario where the Ical UID and Sequence number are honored during inserts and updates""" _qname = GCAL_TEMPLATE % 'syncEvent' value = 'value' class When(gdata.data.When): """Extends the gd:when element to add reminders""" reminder = [gdata.data.Reminder] class CalendarEventEntry(gdata.data.BatchEntry): """Describes a Calendar event entry""" quick_add = QuickAddProperty send_event_notifications = SendEventNotificationsProperty sync_event = SyncEventProperty anyone_can_add_self = AnyoneCanAddSelfProperty extended_property = [CalendarExtendedProperty] sequence = SequenceNumberProperty guests_can_invite_others = GuestsCanInviteOthersProperty guests_can_modify = GuestsCanModifyProperty guests_can_see_guests = GuestsCanSeeGuestsProperty georss_where = gdata.geo.data.GeoRssWhere private_copy = PrivateCopyProperty suppress_reply_notifications = SuppressReplyNotificationsProperty uid = IcalUIDProperty where = [gdata.data.Where] when = [When] who = [gdata.data.Who] transparency = gdata.data.Transparency comments = gdata.data.Comments event_status = gdata.data.EventStatus visibility = gdata.data.Visibility recurrence = gdata.data.Recurrence recurrence_exception = [gdata.data.RecurrenceException] original_event = gdata.data.OriginalEvent reminder = [gdata.data.Reminder] class TimeZoneProperty(atom.core.XmlElement): """Describes the time zone of a calendar""" _qname = GCAL_TEMPLATE % 'timezone' value = 'value' class TimesCleanedProperty(atom.core.XmlElement): """Describes how many times calendar was cleaned via Manage Calendars""" _qname = GCAL_TEMPLATE % 'timesCleaned' value = 'value' class CalendarEntry(gdata.data.GDEntry): """Describes a Calendar entry in the feed of a user's calendars""" timezone = TimeZoneProperty overridename = OverrideNameProperty hidden = HiddenProperty selected = SelectedProperty times_cleaned = TimesCleanedProperty color = ColorProperty where = [CalendarWhere] accesslevel = AccessLevelProperty class CalendarEventFeed(gdata.data.BatchFeed): """Describes a Calendar event feed""" allow_g_sync2 = AllowGSync2Property timezone = TimeZoneProperty entry = [CalendarEventEntry] times_cleaned = TimesCleanedProperty allow_g_sync = AllowGSyncProperty class CalendarFeed(gdata.data.GDFeed): """Describes a feed of Calendars""" entry = [CalendarEntry] class WebContentGadgetPref(atom.core.XmlElement): """Describes a single web content gadget preference""" _qname = GCAL_TEMPLATE % 'webContentGadgetPref' name = 'name' value = 'value' class WebContent(atom.core.XmlElement): """Describes a "web content" extension""" _qname = GCAL_TEMPLATE % 'webContent' height = 'height' width = 'width' web_content_gadget_pref = [WebContentGadgetPref] url = 'url' display = 'display' class WebContentLink(atom.data.Link): """Describes a "web content" link""" def __init__(self, title=None, href=None, link_type=None, web_content=None): atom.data.Link.__init__(self, rel=WEB_CONTENT_LINK_REL, title=title, href=href, link_type=link_type) web_content = WebContent gdata-2.0.18/samples/apps/marketplace_sample/gdata/calendar/__init__.py0000750036466300116100000011432612156622362026150 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright (C) 2006 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. """Contains extensions to ElementWrapper objects used with Google Calendar.""" __author__ = 'api.vli (Vivian Li), api.rboyd (Ryan Boyd)' try: from xml.etree import cElementTree as ElementTree except ImportError: try: import cElementTree as ElementTree except ImportError: try: from xml.etree import ElementTree except ImportError: from elementtree import ElementTree import atom import gdata # XML namespaces which are often used in Google Calendar entities. GCAL_NAMESPACE = 'http://schemas.google.com/gCal/2005' GCAL_TEMPLATE = '{http://schemas.google.com/gCal/2005}%s' WEB_CONTENT_LINK_REL = '%s/%s' % (GCAL_NAMESPACE, 'webContent') GACL_NAMESPACE = gdata.GACL_NAMESPACE GACL_TEMPLATE = gdata.GACL_TEMPLATE class ValueAttributeContainer(atom.AtomBase): """A parent class for all Calendar classes which have a value attribute. Children include Color, AccessLevel, Hidden """ _children = atom.AtomBase._children.copy() _attributes = atom.AtomBase._attributes.copy() _attributes['value'] = 'value' def __init__(self, value=None, extension_elements=None, extension_attributes=None, text=None): self.value = value self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} class Color(ValueAttributeContainer): """The Google Calendar color element""" _tag = 'color' _namespace = GCAL_NAMESPACE _children = ValueAttributeContainer._children.copy() _attributes = ValueAttributeContainer._attributes.copy() class AccessLevel(ValueAttributeContainer): """The Google Calendar accesslevel element""" _tag = 'accesslevel' _namespace = GCAL_NAMESPACE _children = ValueAttributeContainer._children.copy() _attributes = ValueAttributeContainer._attributes.copy() class Hidden(ValueAttributeContainer): """The Google Calendar hidden element""" _tag = 'hidden' _namespace = GCAL_NAMESPACE _children = ValueAttributeContainer._children.copy() _attributes = ValueAttributeContainer._attributes.copy() class Selected(ValueAttributeContainer): """The Google Calendar selected element""" _tag = 'selected' _namespace = GCAL_NAMESPACE _children = ValueAttributeContainer._children.copy() _attributes = ValueAttributeContainer._attributes.copy() class Timezone(ValueAttributeContainer): """The Google Calendar timezone element""" _tag = 'timezone' _namespace = GCAL_NAMESPACE _children = ValueAttributeContainer._children.copy() _attributes = ValueAttributeContainer._attributes.copy() class Where(atom.AtomBase): """The Google Calendar Where element""" _tag = 'where' _namespace = gdata.GDATA_NAMESPACE _children = atom.AtomBase._children.copy() _attributes = atom.AtomBase._attributes.copy() _attributes['valueString'] = 'value_string' def __init__(self, value_string=None, extension_elements=None, extension_attributes=None, text=None): self.value_string = value_string self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} class CalendarListEntry(gdata.GDataEntry, gdata.LinkFinder): """A Google Calendar meta Entry flavor of an Atom Entry """ _tag = gdata.GDataEntry._tag _namespace = gdata.GDataEntry._namespace _children = gdata.GDataEntry._children.copy() _attributes = gdata.GDataEntry._attributes.copy() _children['{%s}color' % GCAL_NAMESPACE] = ('color', Color) _children['{%s}accesslevel' % GCAL_NAMESPACE] = ('access_level', AccessLevel) _children['{%s}hidden' % GCAL_NAMESPACE] = ('hidden', Hidden) _children['{%s}selected' % GCAL_NAMESPACE] = ('selected', Selected) _children['{%s}timezone' % GCAL_NAMESPACE] = ('timezone', Timezone) _children['{%s}where' % gdata.GDATA_NAMESPACE] = ('where', Where) def __init__(self, author=None, category=None, content=None, atom_id=None, link=None, published=None, title=None, updated=None, color=None, access_level=None, hidden=None, timezone=None, selected=None, where=None, extension_elements=None, extension_attributes=None, text=None): gdata.GDataEntry.__init__(self, author=author, category=category, content=content, atom_id=atom_id, link=link, published=published, title=title, updated=updated, text=None) self.color = color self.access_level = access_level self.hidden = hidden self.selected = selected self.timezone = timezone self.where = where class CalendarListFeed(gdata.GDataFeed, gdata.LinkFinder): """A Google Calendar meta feed flavor of an Atom Feed""" _tag = gdata.GDataFeed._tag _namespace = gdata.GDataFeed._namespace _children = gdata.GDataFeed._children.copy() _attributes = gdata.GDataFeed._attributes.copy() _children['{%s}entry' % atom.ATOM_NAMESPACE] = ('entry', [CalendarListEntry]) class Scope(atom.AtomBase): """The Google ACL scope element""" _tag = 'scope' _namespace = GACL_NAMESPACE _children = atom.AtomBase._children.copy() _attributes = atom.AtomBase._attributes.copy() _attributes['value'] = 'value' _attributes['type'] = 'type' def __init__(self, extension_elements=None, value=None, scope_type=None, extension_attributes=None, text=None): self.value = value self.type = scope_type self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} class Role(ValueAttributeContainer): """The Google Calendar timezone element""" _tag = 'role' _namespace = GACL_NAMESPACE _children = ValueAttributeContainer._children.copy() _attributes = ValueAttributeContainer._attributes.copy() class CalendarAclEntry(gdata.GDataEntry, gdata.LinkFinder): """A Google Calendar ACL Entry flavor of an Atom Entry """ _tag = gdata.GDataEntry._tag _namespace = gdata.GDataEntry._namespace _children = gdata.GDataEntry._children.copy() _attributes = gdata.GDataEntry._attributes.copy() _children['{%s}scope' % GACL_NAMESPACE] = ('scope', Scope) _children['{%s}role' % GACL_NAMESPACE] = ('role', Role) def __init__(self, author=None, category=None, content=None, atom_id=None, link=None, published=None, title=None, updated=None, scope=None, role=None, extension_elements=None, extension_attributes=None, text=None): gdata.GDataEntry.__init__(self, author=author, category=category, content=content, atom_id=atom_id, link=link, published=published, title=title, updated=updated, text=None) self.scope = scope self.role = role class CalendarAclFeed(gdata.GDataFeed, gdata.LinkFinder): """A Google Calendar ACL feed flavor of an Atom Feed""" _tag = gdata.GDataFeed._tag _namespace = gdata.GDataFeed._namespace _children = gdata.GDataFeed._children.copy() _attributes = gdata.GDataFeed._attributes.copy() _children['{%s}entry' % atom.ATOM_NAMESPACE] = ('entry', [CalendarAclEntry]) class CalendarEventCommentEntry(gdata.GDataEntry, gdata.LinkFinder): """A Google Calendar event comments entry flavor of an Atom Entry""" _tag = gdata.GDataEntry._tag _namespace = gdata.GDataEntry._namespace _children = gdata.GDataEntry._children.copy() _attributes = gdata.GDataEntry._attributes.copy() class CalendarEventCommentFeed(gdata.GDataFeed, gdata.LinkFinder): """A Google Calendar event comments feed flavor of an Atom Feed""" _tag = gdata.GDataFeed._tag _namespace = gdata.GDataFeed._namespace _children = gdata.GDataFeed._children.copy() _attributes = gdata.GDataFeed._attributes.copy() _children['{%s}entry' % atom.ATOM_NAMESPACE] = ('entry', [CalendarEventCommentEntry]) class ExtendedProperty(gdata.ExtendedProperty): """A transparent subclass of gdata.ExtendedProperty added to this module for backwards compatibility.""" class Reminder(atom.AtomBase): """The Google Calendar reminder element""" _tag = 'reminder' _namespace = gdata.GDATA_NAMESPACE _children = atom.AtomBase._children.copy() _attributes = atom.AtomBase._attributes.copy() _attributes['absoluteTime'] = 'absolute_time' _attributes['days'] = 'days' _attributes['hours'] = 'hours' _attributes['minutes'] = 'minutes' _attributes['method'] = 'method' def __init__(self, absolute_time=None, days=None, hours=None, minutes=None, method=None, extension_elements=None, extension_attributes=None, text=None): self.absolute_time = absolute_time if days is not None: self.days = str(days) else: self.days = None if hours is not None: self.hours = str(hours) else: self.hours = None if minutes is not None: self.minutes = str(minutes) else: self.minutes = None self.method = method self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} class When(atom.AtomBase): """The Google Calendar When element""" _tag = 'when' _namespace = gdata.GDATA_NAMESPACE _children = atom.AtomBase._children.copy() _attributes = atom.AtomBase._attributes.copy() _children['{%s}reminder' % gdata.GDATA_NAMESPACE] = ('reminder', [Reminder]) _attributes['startTime'] = 'start_time' _attributes['endTime'] = 'end_time' def __init__(self, start_time=None, end_time=None, reminder=None, extension_elements=None, extension_attributes=None, text=None): self.start_time = start_time self.end_time = end_time self.reminder = reminder or [] self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} class Recurrence(atom.AtomBase): """The Google Calendar Recurrence element""" _tag = 'recurrence' _namespace = gdata.GDATA_NAMESPACE _children = atom.AtomBase._children.copy() _attributes = atom.AtomBase._attributes.copy() class UriEnumElement(atom.AtomBase): _children = atom.AtomBase._children.copy() _attributes = atom.AtomBase._attributes.copy() def __init__(self, tag, enum_map, attrib_name='value', extension_elements=None, extension_attributes=None, text=None): self.tag=tag self.enum_map=enum_map self.attrib_name=attrib_name self.value=None self.text=text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} def findKey(self, value): res=[item[0] for item in self.enum_map.items() if item[1] == value] if res is None or len(res) == 0: return None return res[0] def _ConvertElementAttributeToMember(self, attribute, value): # Special logic to use the enum_map to set the value of the object's value member. if attribute == self.attrib_name and value != '': self.value = self.enum_map[value] return # Find the attribute in this class's list of attributes. if self.__class__._attributes.has_key(attribute): # Find the member of this class which corresponds to the XML attribute # (lookup in current_class._attributes) and set this member to the # desired value (using self.__dict__). setattr(self, self.__class__._attributes[attribute], value) else: # The current class doesn't map this attribute, so try to parent class. atom.ExtensionContainer._ConvertElementAttributeToMember(self, attribute, value) def _AddMembersToElementTree(self, tree): # Convert the members of this class which are XML child nodes. # This uses the class's _children dictionary to find the members which # should become XML child nodes. member_node_names = [values[0] for tag, values in self.__class__._children.iteritems()] for member_name in member_node_names: member = getattr(self, member_name) if member is None: pass elif isinstance(member, list): for instance in member: instance._BecomeChildElement(tree) else: member._BecomeChildElement(tree) # Special logic to set the desired XML attribute. key = self.findKey(self.value) if key is not None: tree.attrib[self.attrib_name]=key # Convert the members of this class which are XML attributes. for xml_attribute, member_name in self.__class__._attributes.iteritems(): member = getattr(self, member_name) if member is not None: tree.attrib[xml_attribute] = member # Lastly, call the parent's _AddMembersToElementTree to get any # extension elements. atom.ExtensionContainer._AddMembersToElementTree(self, tree) class AttendeeStatus(UriEnumElement): """The Google Calendar attendeeStatus element""" _tag = 'attendeeStatus' _namespace = gdata.GDATA_NAMESPACE _children = UriEnumElement._children.copy() _attributes = UriEnumElement._attributes.copy() attendee_enum = { 'http://schemas.google.com/g/2005#event.accepted' : 'ACCEPTED', 'http://schemas.google.com/g/2005#event.declined' : 'DECLINED', 'http://schemas.google.com/g/2005#event.invited' : 'INVITED', 'http://schemas.google.com/g/2005#event.tentative' : 'TENTATIVE'} def __init__(self, extension_elements=None, extension_attributes=None, text=None): UriEnumElement.__init__(self, 'attendeeStatus', AttendeeStatus.attendee_enum, extension_elements=extension_elements, extension_attributes=extension_attributes, text=text) class AttendeeType(UriEnumElement): """The Google Calendar attendeeType element""" _tag = 'attendeeType' _namespace = gdata.GDATA_NAMESPACE _children = UriEnumElement._children.copy() _attributes = UriEnumElement._attributes.copy() attendee_type_enum = { 'http://schemas.google.com/g/2005#event.optional' : 'OPTIONAL', 'http://schemas.google.com/g/2005#event.required' : 'REQUIRED' } def __init__(self, extension_elements=None, extension_attributes=None, text=None): UriEnumElement.__init__(self, 'attendeeType', AttendeeType.attendee_type_enum, extension_elements=extension_elements, extension_attributes=extension_attributes,text=text) class Visibility(UriEnumElement): """The Google Calendar Visibility element""" _tag = 'visibility' _namespace = gdata.GDATA_NAMESPACE _children = UriEnumElement._children.copy() _attributes = UriEnumElement._attributes.copy() visibility_enum = { 'http://schemas.google.com/g/2005#event.confidential' : 'CONFIDENTIAL', 'http://schemas.google.com/g/2005#event.default' : 'DEFAULT', 'http://schemas.google.com/g/2005#event.private' : 'PRIVATE', 'http://schemas.google.com/g/2005#event.public' : 'PUBLIC' } def __init__(self, extension_elements=None, extension_attributes=None, text=None): UriEnumElement.__init__(self, 'visibility', Visibility.visibility_enum, extension_elements=extension_elements, extension_attributes=extension_attributes, text=text) class Transparency(UriEnumElement): """The Google Calendar Transparency element""" _tag = 'transparency' _namespace = gdata.GDATA_NAMESPACE _children = UriEnumElement._children.copy() _attributes = UriEnumElement._attributes.copy() transparency_enum = { 'http://schemas.google.com/g/2005#event.opaque' : 'OPAQUE', 'http://schemas.google.com/g/2005#event.transparent' : 'TRANSPARENT' } def __init__(self, extension_elements=None, extension_attributes=None, text=None): UriEnumElement.__init__(self, tag='transparency', enum_map=Transparency.transparency_enum, extension_elements=extension_elements, extension_attributes=extension_attributes, text=text) class Comments(atom.AtomBase): """The Google Calendar comments element""" _tag = 'comments' _namespace = gdata.GDATA_NAMESPACE _children = atom.AtomBase._children.copy() _attributes = atom.AtomBase._attributes.copy() _children['{%s}feedLink' % gdata.GDATA_NAMESPACE] = ('feed_link', gdata.FeedLink) _attributes['rel'] = 'rel' def __init__(self, rel=None, feed_link=None, extension_elements=None, extension_attributes=None, text=None): self.rel = rel self.feed_link = feed_link self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} class EventStatus(UriEnumElement): """The Google Calendar eventStatus element""" _tag = 'eventStatus' _namespace = gdata.GDATA_NAMESPACE _children = UriEnumElement._children.copy() _attributes = UriEnumElement._attributes.copy() status_enum = { 'http://schemas.google.com/g/2005#event.canceled' : 'CANCELED', 'http://schemas.google.com/g/2005#event.confirmed' : 'CONFIRMED', 'http://schemas.google.com/g/2005#event.tentative' : 'TENTATIVE'} def __init__(self, extension_elements=None, extension_attributes=None, text=None): UriEnumElement.__init__(self, tag='eventStatus', enum_map=EventStatus.status_enum, extension_elements=extension_elements, extension_attributes=extension_attributes, text=text) class Who(UriEnumElement): """The Google Calendar Who element""" _tag = 'who' _namespace = gdata.GDATA_NAMESPACE _children = UriEnumElement._children.copy() _attributes = UriEnumElement._attributes.copy() _children['{%s}attendeeStatus' % gdata.GDATA_NAMESPACE] = ( 'attendee_status', AttendeeStatus) _children['{%s}attendeeType' % gdata.GDATA_NAMESPACE] = ('attendee_type', AttendeeType) _attributes['valueString'] = 'name' _attributes['email'] = 'email' relEnum = { 'http://schemas.google.com/g/2005#event.attendee' : 'ATTENDEE', 'http://schemas.google.com/g/2005#event.organizer' : 'ORGANIZER', 'http://schemas.google.com/g/2005#event.performer' : 'PERFORMER', 'http://schemas.google.com/g/2005#event.speaker' : 'SPEAKER', 'http://schemas.google.com/g/2005#message.bcc' : 'BCC', 'http://schemas.google.com/g/2005#message.cc' : 'CC', 'http://schemas.google.com/g/2005#message.from' : 'FROM', 'http://schemas.google.com/g/2005#message.reply-to' : 'REPLY_TO', 'http://schemas.google.com/g/2005#message.to' : 'TO' } def __init__(self, name=None, email=None, attendee_status=None, attendee_type=None, rel=None, extension_elements=None, extension_attributes=None, text=None): UriEnumElement.__init__(self, 'who', Who.relEnum, attrib_name='rel', extension_elements=extension_elements, extension_attributes=extension_attributes, text=text) self.name = name self.email = email self.attendee_status = attendee_status self.attendee_type = attendee_type self.rel = rel class OriginalEvent(atom.AtomBase): """The Google Calendar OriginalEvent element""" _tag = 'originalEvent' _namespace = gdata.GDATA_NAMESPACE _children = atom.AtomBase._children.copy() _attributes = atom.AtomBase._attributes.copy() # TODO: The when tag used to map to a EntryLink, make sure it should really be a When. _children['{%s}when' % gdata.GDATA_NAMESPACE] = ('when', When) _attributes['id'] = 'id' _attributes['href'] = 'href' def __init__(self, id=None, href=None, when=None, extension_elements=None, extension_attributes=None, text=None): self.id = id self.href = href self.when = when self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} def GetCalendarEventEntryClass(): return CalendarEventEntry # This class is not completely defined here, because of a circular reference # in which CalendarEventEntryLink and CalendarEventEntry refer to one another. class CalendarEventEntryLink(gdata.EntryLink): """An entryLink which contains a calendar event entry Within an event's recurranceExceptions, an entry link points to a calendar event entry. This class exists to capture the calendar specific extensions in the entry. """ _tag = 'entryLink' _namespace = gdata.GDATA_NAMESPACE _children = gdata.EntryLink._children.copy() _attributes = gdata.EntryLink._attributes.copy() # The CalendarEventEntryLink should like CalendarEventEntry as a child but # that class hasn't been defined yet, so we will wait until after defining # CalendarEventEntry to list it in _children. class RecurrenceException(atom.AtomBase): """The Google Calendar RecurrenceException element""" _tag = 'recurrenceException' _namespace = gdata.GDATA_NAMESPACE _children = atom.AtomBase._children.copy() _attributes = atom.AtomBase._attributes.copy() _children['{%s}entryLink' % gdata.GDATA_NAMESPACE] = ('entry_link', CalendarEventEntryLink) _children['{%s}originalEvent' % gdata.GDATA_NAMESPACE] = ('original_event', OriginalEvent) _attributes['specialized'] = 'specialized' def __init__(self, specialized=None, entry_link=None, original_event=None, extension_elements=None, extension_attributes=None, text=None): self.specialized = specialized self.entry_link = entry_link self.original_event = original_event self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} class SendEventNotifications(atom.AtomBase): """The Google Calendar sendEventNotifications element""" _tag = 'sendEventNotifications' _namespace = GCAL_NAMESPACE _children = atom.AtomBase._children.copy() _attributes = atom.AtomBase._attributes.copy() _attributes['value'] = 'value' def __init__(self, extension_elements=None, value=None, extension_attributes=None, text=None): self.value = value self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} class QuickAdd(atom.AtomBase): """The Google Calendar quickadd element""" _tag = 'quickadd' _namespace = GCAL_NAMESPACE _children = atom.AtomBase._children.copy() _attributes = atom.AtomBase._attributes.copy() _attributes['value'] = 'value' def __init__(self, extension_elements=None, value=None, extension_attributes=None, text=None): self.value = value self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} def _TransferToElementTree(self, element_tree): if self.value: element_tree.attrib['value'] = self.value element_tree.tag = GCAL_TEMPLATE % 'quickadd' atom.AtomBase._TransferToElementTree(self, element_tree) return element_tree def _TakeAttributeFromElementTree(self, attribute, element_tree): if attribute == 'value': self.value = element_tree.attrib[attribute] del element_tree.attrib[attribute] else: atom.AtomBase._TakeAttributeFromElementTree(self, attribute, element_tree) class SyncEvent(atom.AtomBase): _tag = 'syncEvent' _namespace = GCAL_NAMESPACE _children = atom.AtomBase._children.copy() _attributes = atom.AtomBase._attributes.copy() _attributes['value'] = 'value' def __init__(self, value='false', extension_elements=None, extension_attributes=None, text=None): self.value = value self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} class UID(atom.AtomBase): _tag = 'uid' _namespace = GCAL_NAMESPACE _children = atom.AtomBase._children.copy() _attributes = atom.AtomBase._attributes.copy() _attributes['value'] = 'value' def __init__(self, value=None, extension_elements=None, extension_attributes=None, text=None): self.value = value self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} class Sequence(atom.AtomBase): _tag = 'sequence' _namespace = GCAL_NAMESPACE _children = atom.AtomBase._children.copy() _attributes = atom.AtomBase._attributes.copy() _attributes['value'] = 'value' def __init__(self, value=None, extension_elements=None, extension_attributes=None, text=None): self.value = value self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} class WebContentGadgetPref(atom.AtomBase): _tag = 'webContentGadgetPref' _namespace = GCAL_NAMESPACE _children = atom.AtomBase._children.copy() _attributes = atom.AtomBase._attributes.copy() _attributes['name'] = 'name' _attributes['value'] = 'value' """The Google Calendar Web Content Gadget Preferences element""" def __init__(self, name=None, value=None, extension_elements=None, extension_attributes=None, text=None): self.name = name self.value = value self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} class WebContent(atom.AtomBase): _tag = 'webContent' _namespace = GCAL_NAMESPACE _children = atom.AtomBase._children.copy() _attributes = atom.AtomBase._attributes.copy() _children['{%s}webContentGadgetPref' % GCAL_NAMESPACE] = ('gadget_pref', [WebContentGadgetPref]) _attributes['url'] = 'url' _attributes['width'] = 'width' _attributes['height'] = 'height' def __init__(self, url=None, width=None, height=None, text=None, gadget_pref=None, extension_elements=None, extension_attributes=None): self.url = url self.width = width self.height = height self.text = text self.gadget_pref = gadget_pref or [] self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} class WebContentLink(atom.Link): _tag = 'link' _namespace = atom.ATOM_NAMESPACE _children = atom.Link._children.copy() _attributes = atom.Link._attributes.copy() _children['{%s}webContent' % GCAL_NAMESPACE] = ('web_content', WebContent) def __init__(self, title=None, href=None, link_type=None, web_content=None): atom.Link.__init__(self, rel=WEB_CONTENT_LINK_REL, title=title, href=href, link_type=link_type) self.web_content = web_content class GuestsCanInviteOthers(atom.AtomBase): """Indicates whether event attendees may invite others to the event. This element may only be changed by the organizer of the event. If not included as part of the event entry, this element will default to true during a POST request, and will inherit its previous value during a PUT request. """ _tag = 'guestsCanInviteOthers' _namespace = GCAL_NAMESPACE _attributes = atom.AtomBase._attributes.copy() _attributes['value'] = 'value' def __init__(self, value='true', *args, **kwargs): atom.AtomBase.__init__(self, *args, **kwargs) self.value = value class GuestsCanSeeGuests(atom.AtomBase): """Indicates whether attendees can see other people invited to the event. The organizer always sees all attendees. Guests always see themselves. This property affects what attendees see in the event's guest list via both the Calendar UI and API feeds. This element may only be changed by the organizer of the event. If not included as part of the event entry, this element will default to true during a POST request, and will inherit its previous value during a PUT request. """ _tag = 'guestsCanSeeGuests' _namespace = GCAL_NAMESPACE _attributes = atom.AtomBase._attributes.copy() _attributes['value'] = 'value' def __init__(self, value='true', *args, **kwargs): atom.AtomBase.__init__(self, *args, **kwargs) self.value = value class GuestsCanModify(atom.AtomBase): """Indicates whether event attendees may modify the original event. If yes, changes are visible to organizer and other attendees. Otherwise, any changes made by attendees will be restricted to that attendee's calendar. This element may only be changed by the organizer of the event, and may be set to 'true' only if both gCal:guestsCanInviteOthers and gCal:guestsCanSeeGuests are set to true in the same PUT/POST request. Otherwise, request fails with HTTP error code 400 (Bad Request). If not included as part of the event entry, this element will default to false during a POST request, and will inherit its previous value during a PUT request.""" _tag = 'guestsCanModify' _namespace = GCAL_NAMESPACE _attributes = atom.AtomBase._attributes.copy() _attributes['value'] = 'value' def __init__(self, value='false', *args, **kwargs): atom.AtomBase.__init__(self, *args, **kwargs) self.value = value class CalendarEventEntry(gdata.BatchEntry): """A Google Calendar flavor of an Atom Entry """ _tag = gdata.BatchEntry._tag _namespace = gdata.BatchEntry._namespace _children = gdata.BatchEntry._children.copy() _attributes = gdata.BatchEntry._attributes.copy() # This class also contains WebContentLinks but converting those members # is handled in a special version of _ConvertElementTreeToMember. _children['{%s}where' % gdata.GDATA_NAMESPACE] = ('where', [Where]) _children['{%s}when' % gdata.GDATA_NAMESPACE] = ('when', [When]) _children['{%s}who' % gdata.GDATA_NAMESPACE] = ('who', [Who]) _children['{%s}extendedProperty' % gdata.GDATA_NAMESPACE] = ( 'extended_property', [ExtendedProperty]) _children['{%s}visibility' % gdata.GDATA_NAMESPACE] = ('visibility', Visibility) _children['{%s}transparency' % gdata.GDATA_NAMESPACE] = ('transparency', Transparency) _children['{%s}eventStatus' % gdata.GDATA_NAMESPACE] = ('event_status', EventStatus) _children['{%s}recurrence' % gdata.GDATA_NAMESPACE] = ('recurrence', Recurrence) _children['{%s}recurrenceException' % gdata.GDATA_NAMESPACE] = ( 'recurrence_exception', [RecurrenceException]) _children['{%s}sendEventNotifications' % GCAL_NAMESPACE] = ( 'send_event_notifications', SendEventNotifications) _children['{%s}quickadd' % GCAL_NAMESPACE] = ('quick_add', QuickAdd) _children['{%s}comments' % gdata.GDATA_NAMESPACE] = ('comments', Comments) _children['{%s}originalEvent' % gdata.GDATA_NAMESPACE] = ('original_event', OriginalEvent) _children['{%s}sequence' % GCAL_NAMESPACE] = ('sequence', Sequence) _children['{%s}reminder' % gdata.GDATA_NAMESPACE] = ('reminder', [Reminder]) _children['{%s}syncEvent' % GCAL_NAMESPACE] = ('sync_event', SyncEvent) _children['{%s}uid' % GCAL_NAMESPACE] = ('uid', UID) _children['{%s}guestsCanInviteOthers' % GCAL_NAMESPACE] = ( 'guests_can_invite_others', GuestsCanInviteOthers) _children['{%s}guestsCanModify' % GCAL_NAMESPACE] = ( 'guests_can_modify', GuestsCanModify) _children['{%s}guestsCanSeeGuests' % GCAL_NAMESPACE] = ( 'guests_can_see_guests', GuestsCanSeeGuests) def __init__(self, author=None, category=None, content=None, atom_id=None, link=None, published=None, title=None, updated=None, transparency=None, comments=None, event_status=None, send_event_notifications=None, visibility=None, recurrence=None, recurrence_exception=None, where=None, when=None, who=None, quick_add=None, extended_property=None, original_event=None, batch_operation=None, batch_id=None, batch_status=None, sequence=None, reminder=None, sync_event=None, uid=None, guests_can_invite_others=None, guests_can_modify=None, guests_can_see_guests=None, extension_elements=None, extension_attributes=None, text=None): gdata.BatchEntry.__init__(self, author=author, category=category, content=content, atom_id=atom_id, link=link, published=published, batch_operation=batch_operation, batch_id=batch_id, batch_status=batch_status, title=title, updated=updated) self.transparency = transparency self.comments = comments self.event_status = event_status self.send_event_notifications = send_event_notifications self.visibility = visibility self.recurrence = recurrence self.recurrence_exception = recurrence_exception or [] self.where = where or [] self.when = when or [] self.who = who or [] self.quick_add = quick_add self.extended_property = extended_property or [] self.original_event = original_event self.sequence = sequence self.reminder = reminder or [] self.sync_event = sync_event self.uid = uid self.text = text self.guests_can_invite_others = guests_can_invite_others self.guests_can_modify = guests_can_modify self.guests_can_see_guests = guests_can_see_guests self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} # We needed to add special logic to _ConvertElementTreeToMember because we # want to make links with a rel of WEB_CONTENT_LINK_REL into a # WebContentLink def _ConvertElementTreeToMember(self, child_tree): # Special logic to handle Web Content links if (child_tree.tag == '{%s}link' % atom.ATOM_NAMESPACE and child_tree.attrib['rel'] == WEB_CONTENT_LINK_REL): if self.link is None: self.link = [] self.link.append(atom._CreateClassFromElementTree(WebContentLink, child_tree)) return # Find the element's tag in this class's list of child members if self.__class__._children.has_key(child_tree.tag): member_name = self.__class__._children[child_tree.tag][0] member_class = self.__class__._children[child_tree.tag][1] # If the class member is supposed to contain a list, make sure the # matching member is set to a list, then append the new member # instance to the list. if isinstance(member_class, list): if getattr(self, member_name) is None: setattr(self, member_name, []) getattr(self, member_name).append(atom._CreateClassFromElementTree( member_class[0], child_tree)) else: setattr(self, member_name, atom._CreateClassFromElementTree(member_class, child_tree)) else: atom.ExtensionContainer._ConvertElementTreeToMember(self, child_tree) def GetWebContentLink(self): """Finds the first link with rel set to WEB_CONTENT_REL Returns: A gdata.calendar.WebContentLink or none if none of the links had rel equal to WEB_CONTENT_REL """ for a_link in self.link: if a_link.rel == WEB_CONTENT_LINK_REL: return a_link return None def CalendarEventEntryFromString(xml_string): return atom.CreateClassFromXMLString(CalendarEventEntry, xml_string) def CalendarEventCommentEntryFromString(xml_string): return atom.CreateClassFromXMLString(CalendarEventCommentEntry, xml_string) CalendarEventEntryLink._children = {'{%s}entry' % atom.ATOM_NAMESPACE: ('entry', CalendarEventEntry)} def CalendarEventEntryLinkFromString(xml_string): return atom.CreateClassFromXMLString(CalendarEventEntryLink, xml_string) class CalendarEventFeed(gdata.BatchFeed, gdata.LinkFinder): """A Google Calendar event feed flavor of an Atom Feed""" _tag = gdata.BatchFeed._tag _namespace = gdata.BatchFeed._namespace _children = gdata.BatchFeed._children.copy() _attributes = gdata.BatchFeed._attributes.copy() _children['{%s}entry' % atom.ATOM_NAMESPACE] = ('entry', [CalendarEventEntry]) _children['{%s}timezone' % GCAL_NAMESPACE] = ('timezone', Timezone) def __init__(self, author=None, category=None, contributor=None, generator=None, icon=None, atom_id=None, link=None, logo=None, rights=None, subtitle=None, title=None, updated=None, entry=None, total_results=None, start_index=None, items_per_page=None, interrupted=None, timezone=None, extension_elements=None, extension_attributes=None, text=None): gdata.BatchFeed.__init__(self, author=author, category=category, contributor=contributor, generator=generator, icon=icon, atom_id=atom_id, link=link, logo=logo, rights=rights, subtitle=subtitle, title=title, updated=updated, entry=entry, total_results=total_results, start_index=start_index, items_per_page=items_per_page, interrupted=interrupted, extension_elements=extension_elements, extension_attributes=extension_attributes, text=text) self.timezone = timezone def CalendarListEntryFromString(xml_string): return atom.CreateClassFromXMLString(CalendarListEntry, xml_string) def CalendarAclEntryFromString(xml_string): return atom.CreateClassFromXMLString(CalendarAclEntry, xml_string) def CalendarListFeedFromString(xml_string): return atom.CreateClassFromXMLString(CalendarListFeed, xml_string) def CalendarAclFeedFromString(xml_string): return atom.CreateClassFromXMLString(CalendarAclFeed, xml_string) def CalendarEventFeedFromString(xml_string): return atom.CreateClassFromXMLString(CalendarEventFeed, xml_string) def CalendarEventCommentFeedFromString(xml_string): return atom.CreateClassFromXMLString(CalendarEventCommentFeed, xml_string) gdata-2.0.18/samples/apps/marketplace_sample/gdata/calendar/client.py0000750036466300116100000006062412156622362025670 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright (C) 2011 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. """CalendarClient extends the GDataService to streamline Google Calendar operations. CalendarService: Provides methods to query feeds and manipulate items. Extends GDataService. DictionaryToParamList: Function which converts a dictionary into a list of URL arguments (represented as strings). This is a utility function used in CRUD operations. """ __author__ = 'alainv (Alain Vongsouvanh)' import urllib import gdata.client import gdata.calendar.data import atom.data import atom.http_core import gdata.gauth DEFAULT_BATCH_URL = ('https://www.google.com/calendar/feeds/default/private' '/full/batch') class CalendarClient(gdata.client.GDClient): """Client for the Google Calendar service.""" api_version = '2' auth_service = 'cl' server = "www.google.com" contact_list = "default" auth_scopes = gdata.gauth.AUTH_SCOPES['cl'] def __init__(self, domain=None, auth_token=None, **kwargs): """Constructs a new client for the Calendar API. Args: domain: string The Google Apps domain (if any). kwargs: The other parameters to pass to the gdata.client.GDClient constructor. """ gdata.client.GDClient.__init__(self, auth_token=auth_token, **kwargs) self.domain = domain def get_calendar_feed_uri(self, feed='', projection='full', scheme="https"): """Builds a feed URI. Args: projection: The projection to apply to the feed contents, for example 'full', 'base', 'base/12345', 'full/batch'. Default value: 'full'. scheme: The URL scheme such as 'http' or 'https', None to return a relative URI without hostname. Returns: A feed URI using the given scheme and projection. Example: '/calendar/feeds/default/owncalendars/full'. """ prefix = scheme and '%s://%s' % (scheme, self.server) or '' suffix = feed and '/%s/%s' % (feed, projection) or '' return '%s/calendar/feeds/default%s' % (prefix, suffix) GetCalendarFeedUri = get_calendar_feed_uri def get_calendar_event_feed_uri(self, calendar='default', visibility='private', projection='full', scheme="https"): """Builds a feed URI. Args: projection: The projection to apply to the feed contents, for example 'full', 'base', 'base/12345', 'full/batch'. Default value: 'full'. scheme: The URL scheme such as 'http' or 'https', None to return a relative URI without hostname. Returns: A feed URI using the given scheme and projection. Example: '/calendar/feeds/default/private/full'. """ prefix = scheme and '%s://%s' % (scheme, self.server) or '' return '%s/calendar/feeds/%s/%s/%s' % (prefix, calendar, visibility, projection) GetCalendarEventFeedUri = get_calendar_event_feed_uri def get_calendars_feed(self, uri, desired_class=gdata.calendar.data.CalendarFeed, auth_token=None, **kwargs): """Obtains a calendar feed. Args: uri: The uri of the calendar feed to request. desired_class: class descended from atom.core.XmlElement to which a successful response should be converted. If there is no converter function specified (desired_class=None) then the desired_class will be used in calling the atom.core.parse function. If neither the desired_class nor the converter is specified, an HTTP reponse object will be returned. Defaults to gdata.calendar.data.CalendarFeed. auth_token: An object which sets the Authorization HTTP header in its modify_request method. Recommended classes include gdata.gauth.ClientLoginToken and gdata.gauth.AuthSubToken among others. Represents the current user. Defaults to None and if None, this method will look for a value in the auth_token member of SpreadsheetsClient. """ return self.get_feed(uri, auth_token=auth_token, desired_class=desired_class, **kwargs) GetCalendarsFeed = get_calendars_feed def get_own_calendars_feed(self, desired_class=gdata.calendar.data.CalendarFeed, auth_token=None, **kwargs): """Obtains a feed containing the calendars owned by the current user. Args: desired_class: class descended from atom.core.XmlElement to which a successful response should be converted. If there is no converter function specified (desired_class=None) then the desired_class will be used in calling the atom.core.parse function. If neither the desired_class nor the converter is specified, an HTTP reponse object will be returned. Defaults to gdata.calendar.data.CalendarFeed. auth_token: An object which sets the Authorization HTTP header in its modify_request method. Recommended classes include gdata.gauth.ClientLoginToken and gdata.gauth.AuthSubToken among others. Represents the current user. Defaults to None and if None, this method will look for a value in the auth_token member of SpreadsheetsClient. """ return self.GetCalendarsFeed(uri=self.GetCalendarFeedUri(feed='owncalendars'), desired_class=desired_class, auth_token=auth_token, **kwargs) GetOwnCalendarsFeed = get_own_calendars_feed def get_all_calendars_feed(self, desired_class=gdata.calendar.data.CalendarFeed, auth_token=None, **kwargs): """Obtains a feed containing all the ccalendars the current user has access to. Args: desired_class: class descended from atom.core.XmlElement to which a successful response should be converted. If there is no converter function specified (desired_class=None) then the desired_class will be used in calling the atom.core.parse function. If neither the desired_class nor the converter is specified, an HTTP reponse object will be returned. Defaults to gdata.calendar.data.CalendarFeed. auth_token: An object which sets the Authorization HTTP header in its modify_request method. Recommended classes include gdata.gauth.ClientLoginToken and gdata.gauth.AuthSubToken among others. Represents the current user. Defaults to None and if None, this method will look for a value in the auth_token member of SpreadsheetsClient. """ return self.GetCalendarsFeed(uri=self.GetCalendarFeedUri(feed='allcalendars'), desired_class=desired_class, auth_token=auth_token, **kwargs) GetAllCalendarsFeed = get_all_calendars_feed def get_calendar_entry(self, uri, desired_class=gdata.calendar.data.CalendarEntry, auth_token=None, **kwargs): """Obtains a single calendar entry. Args: uri: The uri of the desired calendar entry. desired_class: class descended from atom.core.XmlElement to which a successful response should be converted. If there is no converter function specified (desired_class=None) then the desired_class will be used in calling the atom.core.parse function. If neither the desired_class nor the converter is specified, an HTTP reponse object will be returned. Defaults to gdata.calendar.data.CalendarEntry. auth_token: An object which sets the Authorization HTTP header in its modify_request method. Recommended classes include gdata.gauth.ClientLoginToken and gdata.gauth.AuthSubToken among others. Represents the current user. Defaults to None and if None, this method will look for a value in the auth_token member of SpreadsheetsClient. """ return self.get_entry(uri, auth_token=auth_token, desired_class=desired_class, **kwargs) GetCalendarEntry = get_calendar_entry def get_calendar_event_feed(self, uri=None, desired_class=gdata.calendar.data.CalendarEventFeed, auth_token=None, **kwargs): """Obtains a feed of events for the desired calendar. Args: uri: The uri of the desired calendar entry. Defaults to https://www.google.com/calendar/feeds/default/private/full. desired_class: class descended from atom.core.XmlElement to which a successful response should be converted. If there is no converter function specified (desired_class=None) then the desired_class will be used in calling the atom.core.parse function. If neither the desired_class nor the converter is specified, an HTTP reponse object will be returned. Defaults to gdata.calendar.data.CalendarEventFeed. auth_token: An object which sets the Authorization HTTP header in its modify_request method. Recommended classes include gdata.gauth.ClientLoginToken and gdata.gauth.AuthSubToken among others. Represents the current user. Defaults to None and if None, this method will look for a value in the auth_token member of SpreadsheetsClient. """ uri = uri or self.GetCalendarEventFeedUri() return self.get_feed(uri, auth_token=auth_token, desired_class=desired_class, **kwargs) GetCalendarEventFeed = get_calendar_event_feed def get_event_entry(self, uri, desired_class=gdata.calendar.data.CalendarEventEntry, auth_token=None, **kwargs): """Obtains a single event entry. Args: uri: The uri of the desired calendar event entry. desired_class: class descended from atom.core.XmlElement to which a successful response should be converted. If there is no converter function specified (desired_class=None) then the desired_class will be used in calling the atom.core.parse function. If neither the desired_class nor the converter is specified, an HTTP reponse object will be returned. Defaults to gdata.calendar.data.CalendarEventEntry. auth_token: An object which sets the Authorization HTTP header in its modify_request method. Recommended classes include gdata.gauth.ClientLoginToken and gdata.gauth.AuthSubToken among others. Represents the current user. Defaults to None and if None, this method will look for a value in the auth_token member of SpreadsheetsClient. """ return self.get_entry(uri, auth_token=auth_token, desired_class=desired_class, **kwargs) GetEventEntry = get_event_entry def get_calendar_acl_feed(self, uri='https://www.google.com/calendar/feeds/default/acl/full', desired_class=gdata.calendar.data.CalendarAclFeed, auth_token=None, **kwargs): """Obtains an Access Control List feed. Args: uri: The uri of the desired Acl feed. Defaults to https://www.google.com/calendar/feeds/default/acl/full. desired_class: class descended from atom.core.XmlElement to which a successful response should be converted. If there is no converter function specified (desired_class=None) then the desired_class will be used in calling the atom.core.parse function. If neither the desired_class nor the converter is specified, an HTTP reponse object will be returned. Defaults to gdata.calendar.data.CalendarAclFeed. auth_token: An object which sets the Authorization HTTP header in its modify_request method. Recommended classes include gdata.gauth.ClientLoginToken and gdata.gauth.AuthSubToken among others. Represents the current user. Defaults to None and if None, this method will look for a value in the auth_token member of SpreadsheetsClient. """ return self.get_feed(uri, auth_token=auth_token, desired_class=desired_class, **kwargs) GetCalendarAclFeed = get_calendar_acl_feed def get_calendar_acl_entry(self, uri, desired_class=gdata.calendar.data.CalendarAclEntry, auth_token=None, **kwargs): """Obtains a single Access Control List entry. Args: uri: The uri of the desired Acl feed. desired_class: class descended from atom.core.XmlElement to which a successful response should be converted. If there is no converter function specified (desired_class=None) then the desired_class will be used in calling the atom.core.parse function. If neither the desired_class nor the converter is specified, an HTTP reponse object will be returned. Defaults to gdata.calendar.data.CalendarAclEntry. auth_token: An object which sets the Authorization HTTP header in its modify_request method. Recommended classes include gdata.gauth.ClientLoginToken and gdata.gauth.AuthSubToken among others. Represents the current user. Defaults to None and if None, this method will look for a value in the auth_token member of SpreadsheetsClient. """ return self.get_entry(uri, auth_token=auth_token, desired_class=desired_class, **kwargs) GetCalendarAclEntry = get_calendar_acl_entry def insert_calendar(self, new_calendar, insert_uri=None, auth_token=None, **kwargs): """Adds an new calendar to Google Calendar. Args: new_calendar: atom.Entry or subclass A new calendar which is to be added to Google Calendar. insert_uri: the URL to post new contacts to the feed url_params: dict (optional) Additional URL parameters to be included in the insertion request. escape_params: boolean (optional) If true, the url_parameters will be escaped before they are included in the request. Returns: On successful insert, an entry containing the contact created On failure, a RequestError is raised of the form: {'status': HTTP status code from server, 'reason': HTTP reason from the server, 'body': HTTP body of the server's response} """ insert_uri = insert_uri or self.GetCalendarFeedUri(feed='owncalendars') return self.Post(new_calendar, insert_uri, auth_token=auth_token, **kwargs) InsertCalendar = insert_calendar def insert_calendar_subscription(self, calendar, insert_uri=None, auth_token=None, **kwargs): """Subscribes the authenticated user to the provided calendar. Args: calendar: The calendar to which the user should be subscribed. url_params: dict (optional) Additional URL parameters to be included in the insertion request. escape_params: boolean (optional) If true, the url_parameters will be escaped before they are included in the request. Returns: On successful insert, an entry containing the subscription created On failure, a RequestError is raised of the form: {'status': HTTP status code from server, 'reason': HTTP reason from the server, 'body': HTTP body of the server's response} """ insert_uri = insert_uri or self.GetCalendarFeedUri(feed='allcalendars') return self.Post(calendar, insert_uri, auth_token=auth_token, **kwargs) InsertCalendarSubscription = insert_calendar_subscription def insert_event(self, new_event, insert_uri=None, auth_token=None, **kwargs): """Adds an new event to Google Calendar. Args: new_event: atom.Entry or subclass A new event which is to be added to Google Calendar. insert_uri: the URL to post new contacts to the feed url_params: dict (optional) Additional URL parameters to be included in the insertion request. escape_params: boolean (optional) If true, the url_parameters will be escaped before they are included in the request. Returns: On successful insert, an entry containing the contact created On failure, a RequestError is raised of the form: {'status': HTTP status code from server, 'reason': HTTP reason from the server, 'body': HTTP body of the server's response} """ insert_uri = insert_uri or self.GetCalendarEventFeedUri() return self.Post(new_event, insert_uri, auth_token=auth_token, **kwargs) InsertEvent = insert_event def insert_acl_entry(self, new_acl_entry, insert_uri = 'https://www.google.com/calendar/feeds/default/acl/full', auth_token=None, **kwargs): """Adds an new Acl entry to Google Calendar. Args: new_acl_event: atom.Entry or subclass A new acl which is to be added to Google Calendar. insert_uri: the URL to post new contacts to the feed url_params: dict (optional) Additional URL parameters to be included in the insertion request. escape_params: boolean (optional) If true, the url_parameters will be escaped before they are included in the request. Returns: On successful insert, an entry containing the contact created On failure, a RequestError is raised of the form: {'status': HTTP status code from server, 'reason': HTTP reason from the server, 'body': HTTP body of the server's response} """ return self.Post(new_acl_entry, insert_uri, auth_token=auth_token, **kwargs) InsertAclEntry = insert_acl_entry def execute_batch(self, batch_feed, url, desired_class=None): """Sends a batch request feed to the server. Args: batch_feed: gdata.contacts.CalendarEventFeed A feed containing batch request entries. Each entry contains the operation to be performed on the data contained in the entry. For example an entry with an operation type of insert will be used as if the individual entry had been inserted. url: str The batch URL to which these operations should be applied. converter: Function (optional) The function used to convert the server's response to an object. Returns: The results of the batch request's execution on the server. If the default converter is used, this is stored in a ContactsFeed. """ return self.Post(batch_feed, url, desired_class=desired_class) ExecuteBatch = execute_batch def update(self, entry, auth_token=None, **kwargs): """Edits the entry on the server by sending the XML for this entry. Performs a PUT and converts the response to a new entry object with a matching class to the entry passed in. Args: entry: auth_token: Returns: A new Entry object of a matching type to the entry which was passed in. """ return gdata.client.GDClient.Update(self, entry, auth_token=auth_token, force=True, **kwargs) Update = update class CalendarEventQuery(gdata.client.Query): """ Create a custom Calendar Query Full specs can be found at: U{Calendar query parameters reference } """ def __init__(self, feed=None, ctz=None, fields=None, futureevents=None, max_attendees=None, orderby=None, recurrence_expansion_start=None, recurrence_expansion_end=None, singleevents=None, showdeleted=None, showhidden=None, sortorder=None, start_min=None, start_max=None, updated_min=None, **kwargs): """ @param max_results: The maximum number of entries to return. If you want to receive all of the contacts, rather than only the default maximum, you can specify a very large number for max-results. @param start-index: The 1-based index of the first result to be retrieved. @param updated-min: The lower bound on entry update dates. @param group: Constrains the results to only the contacts belonging to the group specified. Value of this parameter specifies group ID @param orderby: Sorting criterion. The only supported value is lastmodified. @param showdeleted: Include deleted contacts in the returned contacts feed @pram sortorder: Sorting order direction. Can be either ascending or descending. @param requirealldeleted: Only relevant if showdeleted and updated-min are also provided. It dictates the behavior of the server in case it detects that placeholders of some entries deleted since the point in time specified as updated-min may have been lost. """ gdata.client.Query.__init__(self, **kwargs) self.ctz = ctz self.fields = fields self.futureevents = futureevents self.max_attendees = max_attendees self.orderby = orderby self.recurrence_expansion_start = recurrence_expansion_start self.recurrence_expansion_end = recurrence_expansion_end self.singleevents = singleevents self.showdeleted = showdeleted self.showhidden = showhidden self.sortorder = sortorder self.start_min = start_min self.start_max = start_max self.updated_min = updated_min def modify_request(self, http_request): if self.ctz: gdata.client._add_query_param('ctz', self.ctz, http_request) if self.fields: gdata.client._add_query_param('fields', self.fields, http_request) if self.futureevents: gdata.client._add_query_param('futureevents', self.futureevents, http_request) if self.max_attendees: gdata.client._add_query_param('max-attendees', self.max_attendees, http_request) if self.orderby: gdata.client._add_query_param('orderby', self.orderby, http_request) if self.recurrence_expansion_start: gdata.client._add_query_param('recurrence-expansion-start', self.recurrence_expansion_start, http_request) if self.recurrence_expansion_end: gdata.client._add_query_param('recurrence-expansion-end', self.recurrence_expansion_end, http_request) if self.singleevents: gdata.client._add_query_param('singleevents', self.singleevents, http_request) if self.showdeleted: gdata.client._add_query_param('showdeleted', self.showdeleted, http_request) if self.showhidden: gdata.client._add_query_param('showhidden', self.showhidden, http_request) if self.sortorder: gdata.client._add_query_param('sortorder', self.sortorder, http_request) if self.start_min: gdata.client._add_query_param('start-min', self.start_min, http_request) if self.start_max: gdata.client._add_query_param('start-max', self.start_max, http_request) if self.updated_min: gdata.client._add_query_param('updated-min', self.updated_min, http_request) gdata.client.Query.modify_request(self, http_request) ModifyRequest = modify_request gdata-2.0.18/samples/apps/marketplace_sample/gdata/calendar/service.py0000750036466300116100000005475412156622362026061 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright (C) 2006 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. """CalendarService extends the GDataService to streamline Google Calendar operations. CalendarService: Provides methods to query feeds and manipulate items. Extends GDataService. DictionaryToParamList: Function which converts a dictionary into a list of URL arguments (represented as strings). This is a utility function used in CRUD operations. """ __author__ = 'api.vli (Vivian Li)' import urllib import gdata import atom.service import gdata.service import gdata.calendar import atom DEFAULT_BATCH_URL = ('http://www.google.com/calendar/feeds/default/private' '/full/batch') class Error(Exception): pass class RequestError(Error): pass class CalendarService(gdata.service.GDataService): """Client for the Google Calendar service.""" def __init__(self, email=None, password=None, source=None, server='www.google.com', additional_headers=None, **kwargs): """Creates a client for the Google Calendar service. Args: email: string (optional) The user's email address, used for authentication. password: string (optional) The user's password. source: string (optional) The name of the user's application. server: string (optional) The name of the server to which a connection will be opened. Default value: 'www.google.com'. **kwargs: The other parameters to pass to gdata.service.GDataService constructor. """ gdata.service.GDataService.__init__( self, email=email, password=password, service='cl', source=source, server=server, additional_headers=additional_headers, **kwargs) def GetCalendarEventFeed(self, uri='/calendar/feeds/default/private/full'): return self.Get(uri, converter=gdata.calendar.CalendarEventFeedFromString) def GetCalendarEventEntry(self, uri): return self.Get(uri, converter=gdata.calendar.CalendarEventEntryFromString) def GetCalendarListFeed(self, uri='/calendar/feeds/default/allcalendars/full'): return self.Get(uri, converter=gdata.calendar.CalendarListFeedFromString) def GetAllCalendarsFeed(self, uri='/calendar/feeds/default/allcalendars/full'): return self.Get(uri, converter=gdata.calendar.CalendarListFeedFromString) def GetOwnCalendarsFeed(self, uri='/calendar/feeds/default/owncalendars/full'): return self.Get(uri, converter=gdata.calendar.CalendarListFeedFromString) def GetCalendarListEntry(self, uri): return self.Get(uri, converter=gdata.calendar.CalendarListEntryFromString) def GetCalendarAclFeed(self, uri='/calendar/feeds/default/acl/full'): return self.Get(uri, converter=gdata.calendar.CalendarAclFeedFromString) def GetCalendarAclEntry(self, uri): return self.Get(uri, converter=gdata.calendar.CalendarAclEntryFromString) def GetCalendarEventCommentFeed(self, uri): return self.Get(uri, converter=gdata.calendar.CalendarEventCommentFeedFromString) def GetCalendarEventCommentEntry(self, uri): return self.Get(uri, converter=gdata.calendar.CalendarEventCommentEntryFromString) def Query(self, uri, converter=None): """Performs a query and returns a resulting feed or entry. Args: feed: string The feed which is to be queried Returns: On success, a GDataFeed or Entry depending on which is sent from the server. On failure, a RequestError is raised of the form: {'status': HTTP status code from server, 'reason': HTTP reason from the server, 'body': HTTP body of the server's response} """ if converter: result = self.Get(uri, converter=converter) else: result = self.Get(uri) return result def CalendarQuery(self, query): if isinstance(query, CalendarEventQuery): return self.Query(query.ToUri(), converter=gdata.calendar.CalendarEventFeedFromString) elif isinstance(query, CalendarListQuery): return self.Query(query.ToUri(), converter=gdata.calendar.CalendarListFeedFromString) elif isinstance(query, CalendarEventCommentQuery): return self.Query(query.ToUri(), converter=gdata.calendar.CalendarEventCommentFeedFromString) else: return self.Query(query.ToUri()) def InsertEvent(self, new_event, insert_uri, url_params=None, escape_params=True): """Adds an event to Google Calendar. Args: new_event: atom.Entry or subclass A new event which is to be added to Google Calendar. insert_uri: the URL to post new events to the feed url_params: dict (optional) Additional URL parameters to be included in the insertion request. escape_params: boolean (optional) If true, the url_parameters will be escaped before they are included in the request. Returns: On successful insert, an entry containing the event created On failure, a RequestError is raised of the form: {'status': HTTP status code from server, 'reason': HTTP reason from the server, 'body': HTTP body of the server's response} """ return self.Post(new_event, insert_uri, url_params=url_params, escape_params=escape_params, converter=gdata.calendar.CalendarEventEntryFromString) def InsertCalendarSubscription(self, calendar, url_params=None, escape_params=True): """Subscribes the authenticated user to the provided calendar. Args: calendar: The calendar to which the user should be subscribed. url_params: dict (optional) Additional URL parameters to be included in the insertion request. escape_params: boolean (optional) If true, the url_parameters will be escaped before they are included in the request. Returns: On successful insert, an entry containing the subscription created On failure, a RequestError is raised of the form: {'status': HTTP status code from server, 'reason': HTTP reason from the server, 'body': HTTP body of the server's response} """ insert_uri = '/calendar/feeds/default/allcalendars/full' return self.Post(calendar, insert_uri, url_params=url_params, escape_params=escape_params, converter=gdata.calendar.CalendarListEntryFromString) def InsertCalendar(self, new_calendar, url_params=None, escape_params=True): """Creates a new calendar. Args: new_calendar: The calendar to be created url_params: dict (optional) Additional URL parameters to be included in the insertion request. escape_params: boolean (optional) If true, the url_parameters will be escaped before they are included in the request. Returns: On successful insert, an entry containing the calendar created On failure, a RequestError is raised of the form: {'status': HTTP status code from server, 'reason': HTTP reason from the server, 'body': HTTP body of the server's response} """ insert_uri = '/calendar/feeds/default/owncalendars/full' response = self.Post(new_calendar, insert_uri, url_params=url_params, escape_params=escape_params, converter=gdata.calendar.CalendarListEntryFromString) return response def UpdateCalendar(self, calendar, url_params=None, escape_params=True): """Updates a calendar. Args: calendar: The calendar which should be updated url_params: dict (optional) Additional URL parameters to be included in the insertion request. escape_params: boolean (optional) If true, the url_parameters will be escaped before they are included in the request. Returns: On successful insert, an entry containing the calendar created On failure, a RequestError is raised of the form: {'status': HTTP status code from server, 'reason': HTTP reason from the server, 'body': HTTP body of the server's response} """ update_uri = calendar.GetEditLink().href response = self.Put(data=calendar, uri=update_uri, url_params=url_params, escape_params=escape_params, converter=gdata.calendar.CalendarListEntryFromString) return response def InsertAclEntry(self, new_entry, insert_uri, url_params=None, escape_params=True): """Adds an ACL entry (rule) to Google Calendar. Args: new_entry: atom.Entry or subclass A new ACL entry which is to be added to Google Calendar. insert_uri: the URL to post new entries to the ACL feed url_params: dict (optional) Additional URL parameters to be included in the insertion request. escape_params: boolean (optional) If true, the url_parameters will be escaped before they are included in the request. Returns: On successful insert, an entry containing the ACL entry created On failure, a RequestError is raised of the form: {'status': HTTP status code from server, 'reason': HTTP reason from the server, 'body': HTTP body of the server's response} """ return self.Post(new_entry, insert_uri, url_params=url_params, escape_params=escape_params, converter=gdata.calendar.CalendarAclEntryFromString) def InsertEventComment(self, new_entry, insert_uri, url_params=None, escape_params=True): """Adds an entry to Google Calendar. Args: new_entry: atom.Entry or subclass A new entry which is to be added to Google Calendar. insert_uri: the URL to post new entrys to the feed url_params: dict (optional) Additional URL parameters to be included in the insertion request. escape_params: boolean (optional) If true, the url_parameters will be escaped before they are included in the request. Returns: On successful insert, an entry containing the comment created On failure, a RequestError is raised of the form: {'status': HTTP status code from server, 'reason': HTTP reason from the server, 'body': HTTP body of the server's response} """ return self.Post(new_entry, insert_uri, url_params=url_params, escape_params=escape_params, converter=gdata.calendar.CalendarEventCommentEntryFromString) def _RemoveStandardUrlPrefix(self, url): url_prefix = 'http://%s/' % self.server if url.startswith(url_prefix): return url[len(url_prefix) - 1:] return url def DeleteEvent(self, edit_uri, extra_headers=None, url_params=None, escape_params=True): """Removes an event with the specified ID from Google Calendar. Args: edit_uri: string The edit URL of the entry to be deleted. Example: 'http://www.google.com/calendar/feeds/default/private/full/abx' url_params: dict (optional) Additional URL parameters to be included in the deletion request. escape_params: boolean (optional) If true, the url_parameters will be escaped before they are included in the request. Returns: On successful delete, a httplib.HTTPResponse containing the server's response to the DELETE request. On failure, a RequestError is raised of the form: {'status': HTTP status code from server, 'reason': HTTP reason from the server, 'body': HTTP body of the server's response} """ edit_uri = self._RemoveStandardUrlPrefix(edit_uri) return self.Delete('%s' % edit_uri, url_params=url_params, escape_params=escape_params) def DeleteAclEntry(self, edit_uri, extra_headers=None, url_params=None, escape_params=True): """Removes an ACL entry at the given edit_uri from Google Calendar. Args: edit_uri: string The edit URL of the entry to be deleted. Example: 'http://www.google.com/calendar/feeds/liz%40gmail.com/acl/full/default' url_params: dict (optional) Additional URL parameters to be included in the deletion request. escape_params: boolean (optional) If true, the url_parameters will be escaped before they are included in the request. Returns: On successful delete, a httplib.HTTPResponse containing the server's response to the DELETE request. On failure, a RequestError is raised of the form: {'status': HTTP status code from server, 'reason': HTTP reason from the server, 'body': HTTP body of the server's response} """ edit_uri = self._RemoveStandardUrlPrefix(edit_uri) return self.Delete('%s' % edit_uri, url_params=url_params, escape_params=escape_params) def DeleteCalendarEntry(self, edit_uri, extra_headers=None, url_params=None, escape_params=True): """Removes a calendar entry at the given edit_uri from Google Calendar. Args: edit_uri: string The edit URL of the entry to be deleted. Example: 'http://www.google.com/calendar/feeds/default/allcalendars/abcdef@group.calendar.google.com' url_params: dict (optional) Additional URL parameters to be included in the deletion request. escape_params: boolean (optional) If true, the url_parameters will be escaped before they are included in the request. Returns: On successful delete, True is returned On failure, a RequestError is raised of the form: {'status': HTTP status code from server, 'reason': HTTP reason from the server, 'body': HTTP body of the server's response} """ return self.Delete(edit_uri, url_params=url_params, escape_params=escape_params) def UpdateEvent(self, edit_uri, updated_event, url_params=None, escape_params=True): """Updates an existing event. Args: edit_uri: string The edit link URI for the element being updated updated_event: string, atom.Entry, or subclass containing the Atom Entry which will replace the event which is stored at the edit_url url_params: dict (optional) Additional URL parameters to be included in the update request. escape_params: boolean (optional) If true, the url_parameters will be escaped before they are included in the request. Returns: On successful update, a httplib.HTTPResponse containing the server's response to the PUT request. On failure, a RequestError is raised of the form: {'status': HTTP status code from server, 'reason': HTTP reason from the server, 'body': HTTP body of the server's response} """ edit_uri = self._RemoveStandardUrlPrefix(edit_uri) return self.Put(updated_event, '%s' % edit_uri, url_params=url_params, escape_params=escape_params, converter=gdata.calendar.CalendarEventEntryFromString) def UpdateAclEntry(self, edit_uri, updated_rule, url_params=None, escape_params=True): """Updates an existing ACL rule. Args: edit_uri: string The edit link URI for the element being updated updated_rule: string, atom.Entry, or subclass containing the Atom Entry which will replace the event which is stored at the edit_url url_params: dict (optional) Additional URL parameters to be included in the update request. escape_params: boolean (optional) If true, the url_parameters will be escaped before they are included in the request. Returns: On successful update, a httplib.HTTPResponse containing the server's response to the PUT request. On failure, a RequestError is raised of the form: {'status': HTTP status code from server, 'reason': HTTP reason from the server, 'body': HTTP body of the server's response} """ edit_uri = self._RemoveStandardUrlPrefix(edit_uri) return self.Put(updated_rule, '%s' % edit_uri, url_params=url_params, escape_params=escape_params, converter=gdata.calendar.CalendarAclEntryFromString) def ExecuteBatch(self, batch_feed, url, converter=gdata.calendar.CalendarEventFeedFromString): """Sends a batch request feed to the server. The batch request needs to be sent to the batch URL for a particular calendar. You can find the URL by calling GetBatchLink().href on the CalendarEventFeed. Args: batch_feed: gdata.calendar.CalendarEventFeed A feed containing batch request entries. Each entry contains the operation to be performed on the data contained in the entry. For example an entry with an operation type of insert will be used as if the individual entry had been inserted. url: str The batch URL for the Calendar to which these operations should be applied. converter: Function (optional) The function used to convert the server's response to an object. The default value is CalendarEventFeedFromString. Returns: The results of the batch request's execution on the server. If the default converter is used, this is stored in a CalendarEventFeed. """ return self.Post(batch_feed, url, converter=converter) class CalendarEventQuery(gdata.service.Query): def __init__(self, user='default', visibility='private', projection='full', text_query=None, params=None, categories=None): gdata.service.Query.__init__(self, feed='http://www.google.com/calendar/feeds/%s/%s/%s' % ( urllib.quote(user), urllib.quote(visibility), urllib.quote(projection)), text_query=text_query, params=params, categories=categories) def _GetStartMin(self): if 'start-min' in self.keys(): return self['start-min'] else: return None def _SetStartMin(self, val): self['start-min'] = val start_min = property(_GetStartMin, _SetStartMin, doc="""The start-min query parameter""") def _GetStartMax(self): if 'start-max' in self.keys(): return self['start-max'] else: return None def _SetStartMax(self, val): self['start-max'] = val start_max = property(_GetStartMax, _SetStartMax, doc="""The start-max query parameter""") def _GetOrderBy(self): if 'orderby' in self.keys(): return self['orderby'] else: return None def _SetOrderBy(self, val): if val is not 'lastmodified' and val is not 'starttime': raise Error, "Order By must be either 'lastmodified' or 'starttime'" self['orderby'] = val orderby = property(_GetOrderBy, _SetOrderBy, doc="""The orderby query parameter""") def _GetSortOrder(self): if 'sortorder' in self.keys(): return self['sortorder'] else: return None def _SetSortOrder(self, val): if (val is not 'ascending' and val is not 'descending' and val is not 'a' and val is not 'd' and val is not 'ascend' and val is not 'descend'): raise Error, "Sort order must be either ascending, ascend, " + ( "a or descending, descend, or d") self['sortorder'] = val sortorder = property(_GetSortOrder, _SetSortOrder, doc="""The sortorder query parameter""") def _GetSingleEvents(self): if 'singleevents' in self.keys(): return self['singleevents'] else: return None def _SetSingleEvents(self, val): self['singleevents'] = val singleevents = property(_GetSingleEvents, _SetSingleEvents, doc="""The singleevents query parameter""") def _GetFutureEvents(self): if 'futureevents' in self.keys(): return self['futureevents'] else: return None def _SetFutureEvents(self, val): self['futureevents'] = val futureevents = property(_GetFutureEvents, _SetFutureEvents, doc="""The futureevents query parameter""") def _GetRecurrenceExpansionStart(self): if 'recurrence-expansion-start' in self.keys(): return self['recurrence-expansion-start'] else: return None def _SetRecurrenceExpansionStart(self, val): self['recurrence-expansion-start'] = val recurrence_expansion_start = property(_GetRecurrenceExpansionStart, _SetRecurrenceExpansionStart, doc="""The recurrence-expansion-start query parameter""") def _GetRecurrenceExpansionEnd(self): if 'recurrence-expansion-end' in self.keys(): return self['recurrence-expansion-end'] else: return None def _SetRecurrenceExpansionEnd(self, val): self['recurrence-expansion-end'] = val recurrence_expansion_end = property(_GetRecurrenceExpansionEnd, _SetRecurrenceExpansionEnd, doc="""The recurrence-expansion-end query parameter""") def _SetTimezone(self, val): self['ctz'] = val def _GetTimezone(self): if 'ctz' in self.keys(): return self['ctz'] else: return None ctz = property(_GetTimezone, _SetTimezone, doc="""The ctz query parameter which sets report time on the server.""") class CalendarListQuery(gdata.service.Query): """Queries the Google Calendar meta feed""" def __init__(self, userId=None, text_query=None, params=None, categories=None): if userId is None: userId = 'default' gdata.service.Query.__init__(self, feed='http://www.google.com/calendar/feeds/' +userId, text_query=text_query, params=params, categories=categories) class CalendarEventCommentQuery(gdata.service.Query): """Queries the Google Calendar event comments feed""" def __init__(self, feed=None): gdata.service.Query.__init__(self, feed=feed) gdata-2.0.18/samples/apps/marketplace_sample/gdata/test_config.py0000640036466300116100000004267612156622362025152 0ustar afshareng00000000000000#!/usr/bin/env python # Copyright (C) 2009 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. import sys import unittest import getpass import inspect import atom.mock_http_core import gdata.gauth """Loads configuration for tests which connect to Google servers. Settings used in tests are stored in a ConfigCollection instance in this module called options. If your test needs to get a test related setting, use import gdata.test_config option_value = gdata.test_config.options.get_value('x') The above will check the command line for an '--x' argument, and if not found will either use the default value for 'x' or prompt the user to enter one. Your test can override the value specified by the user by performing: gdata.test_config.options.set_value('x', 'y') If your test uses a new option which you would like to allow the user to specify on the command line or via a prompt, you can use the register_option method as follows: gdata.test_config.options.register( 'option_name', 'Prompt shown to the user', secret=False #As for password. 'This is the description of the option, shown when help is requested.', 'default value, provide only if you do not want the user to be prompted') """ class Option(object): def __init__(self, name, prompt, secret=False, description=None, default=None): self.name = name self.prompt = prompt self.secret = secret self.description = description self.default = default def get(self): value = self.default # Check for a command line parameter. for i in xrange(len(sys.argv)): if sys.argv[i].startswith('--%s=' % self.name): value = sys.argv[i].split('=')[1] elif sys.argv[i] == '--%s' % self.name: value = sys.argv[i + 1] # If the param was not on the command line, ask the user to input the # value. # In order for this to prompt the user, the default value for the option # must be None. if value is None: prompt = '%s: ' % self.prompt if self.secret: value = getpass.getpass(prompt) else: print 'You can specify this on the command line using --%s' % self.name value = raw_input(prompt) return value class ConfigCollection(object): def __init__(self, options=None): self.options = options or {} self.values = {} def register_option(self, option): self.options[option.name] = option def register(self, *args, **kwargs): self.register_option(Option(*args, **kwargs)) def get_value(self, option_name): if option_name in self.values: return self.values[option_name] value = self.options[option_name].get() if value is not None: self.values[option_name] = value return value def set_value(self, option_name, value): self.values[option_name] = value def render_usage(self): message_parts = [] for opt_name, option in self.options.iteritems(): message_parts.append('--%s: %s' % (opt_name, option.description)) return '\n'.join(message_parts) options = ConfigCollection() # Register the default options. options.register( 'username', 'Please enter the email address of your test account', description=('The email address you want to sign in with. ' 'Make sure this is a test account as these tests may edit' ' or delete data.')) options.register( 'password', 'Please enter the password for your test account', secret=True, description='The test account password.') options.register( 'clearcache', 'Delete cached data? (enter true or false)', description=('If set to true, any temporary files which cache test' ' requests and responses will be deleted.'), default='true') options.register( 'savecache', 'Save requests and responses in a temporary file? (enter true or false)', description=('If set to true, requests to the server and responses will' ' be saved in temporary files.'), default='false') options.register( 'runlive', 'Run the live tests which contact the server? (enter true or false)', description=('If set to true, the tests will make real HTTP requests to' ' the servers. This slows down test execution and may' ' modify the users data, be sure to use a test account.'), default='true') options.register( 'host', 'Run the live tests against the given host', description='Examples: docs.google.com, spreadsheets.google.com, etc.', default='') options.register( 'ssl', 'Run the live tests over SSL (enter true or false)', description='If set to true, all tests will be performed over HTTPS (SSL)', default='false') options.register( 'clean', 'Clean ALL data first before and after each test (enter true or false)', description='If set to true, all tests will remove all data (DANGEROUS)', default='false') options.register( 'appsusername', 'Please enter the email address of your test Apps domain account', description=('The email address you want to sign in with. ' 'Make sure this is a test account on your Apps domain as ' 'these tests may edit or delete data.')) options.register( 'appspassword', 'Please enter the password for your test Apps domain account', secret=True, description='The test Apps account password.') # Other options which may be used if needed. BLOG_ID_OPTION = Option( 'blogid', 'Please enter the ID of your test blog', description=('The blog ID for the blog which should have test posts added' ' to it. Example 7682659670455539811')) TEST_IMAGE_LOCATION_OPTION = Option( 'imgpath', 'Please enter the full path to a test image to upload', description=('This test image will be uploaded to a service which' ' accepts a media file, it must be a jpeg.')) SPREADSHEET_ID_OPTION = Option( 'spreadsheetid', 'Please enter the ID of a spreadsheet to use in these tests', description=('The spreadsheet ID for the spreadsheet which should be' ' modified by theses tests.')) APPS_DOMAIN_OPTION = Option( 'appsdomain', 'Please enter your Google Apps domain', description=('The domain the Google Apps is hosted on or leave blank' ' if n/a')) SITES_NAME_OPTION = Option( 'sitename', 'Please enter name of your Google Site', description='The webspace name of the Site found in its URL.') PROJECT_NAME_OPTION = Option( 'project_name', 'Please enter the name of your project hosting project', description=('The name of the project which should have test issues added' ' to it. Example gdata-python-client')) ISSUE_ASSIGNEE_OPTION = Option( 'issue_assignee', 'Enter the email address of the target owner of the updated issue.', description=('The email address of the user a created issue\'s owner will ' ' become. Example testuser2@gmail.com')) GA_TABLE_ID = Option( 'table_id', 'Enter the Table ID of the Google Analytics profile to test', description=('The Table ID of the Google Analytics profile to test.' ' Example ga:1174')) TARGET_USERNAME_OPTION = Option( 'targetusername', 'Please enter the username (without domain) of the user which will be' ' affected by the tests', description=('The username of the user to be tested')) YT_DEVELOPER_KEY_OPTION = Option( 'developerkey', 'Please enter your YouTube developer key', description=('The YouTube developer key for your account')) YT_CLIENT_ID_OPTION = Option( 'clientid', 'Please enter your YouTube client ID', description=('The YouTube client ID for your account')) YT_VIDEO_ID_OPTION= Option( 'videoid', 'Please enter the ID of a YouTube video you uploaded', description=('The video ID of a YouTube video uploaded to your account')) # Functions to inject a cachable HTTP client into a service client. def configure_client(client, case_name, service_name, use_apps_auth=False): """Sets up a mock client which will reuse a saved session. Should be called during setUp of each unit test. Handles authentication to allow the GDClient to make requests which require an auth header. Args: client: a gdata.GDClient whose http_client member should be replaced with a atom.mock_http_core.MockHttpClient so that repeated executions can used cached responses instead of contacting the server. case_name: str The name of the test case class. Examples: 'BloggerTest', 'ContactsTest'. Used to save a session for the ClientLogin auth token request, so the case_name should be reused if and only if the same username, password, and service are being used. service_name: str The service name as used for ClientLogin to identify the Google Data API being accessed. Example: 'blogger', 'wise', etc. use_apps_auth: bool (optional) If set to True, use appsusername and appspassword command-line args instead of username and password respectively. """ # Use a mock HTTP client which will record and replay the HTTP traffic # from these tests. client.http_client = atom.mock_http_core.MockHttpClient() client.http_client.cache_case_name = case_name # Getting the auth token only needs to be done once in the course of test # runs. auth_token_key = '%s_auth_token' % service_name if (auth_token_key not in options.values and options.get_value('runlive') == 'true'): client.http_client.cache_test_name = 'client_login' cache_name = client.http_client.get_cache_file_name() if options.get_value('clearcache') == 'true': client.http_client.delete_session(cache_name) client.http_client.use_cached_session(cache_name) if not use_apps_auth: username = options.get_value('username') password = options.get_value('password') else: username = options.get_value('appsusername') password = options.get_value('appspassword') auth_token = client.client_login(username, password, case_name, service=service_name) options.values[auth_token_key] = gdata.gauth.token_to_blob(auth_token) if client.alt_auth_service is not None: options.values[client.alt_auth_service] = gdata.gauth.token_to_blob( client.alt_auth_token) client.http_client.close_session() # Allow a config auth_token of False to prevent the client's auth header # from being modified. if auth_token_key in options.values: client.auth_token = gdata.gauth.token_from_blob( options.values[auth_token_key]) if client.alt_auth_service is not None: client.alt_auth_token = gdata.gauth.token_from_blob( options.values[client.alt_auth_service]) if options.get_value('host'): client.host = options.get_value('host') def configure_cache(client, test_name): """Loads or begins a cached session to record HTTP traffic. Should be called at the beginning of each test method. Args: client: a gdata.GDClient whose http_client member has been replaced with a atom.mock_http_core.MockHttpClient so that repeated executions can used cached responses instead of contacting the server. test_name: str The name of this test method. Examples: 'TestClass.test_x_works', 'TestClass.test_crud_operations'. This is used to name the recording of the HTTP requests and responses, so it should be unique to each test method in the test case. """ # Auth token is obtained in configure_client which is called as part of # setUp. client.http_client.cache_test_name = test_name cache_name = client.http_client.get_cache_file_name() if options.get_value('clearcache') == 'true': client.http_client.delete_session(cache_name) client.http_client.use_cached_session(cache_name) def close_client(client): """Saves the recoded responses to a temp file if the config file allows. This should be called in the unit test's tearDown method. Checks to see if the 'savecache' option is set to 'true', to make sure we only save sessions to repeat if the user desires. """ if client and options.get_value('savecache') == 'true': # If this was a live request, save the recording. client.http_client.close_session() def configure_service(service, case_name, service_name): """Sets up a mock GDataService v1 client to reuse recorded sessions. Should be called during setUp of each unit test. This is a duplicate of configure_client, modified to handle old v1 service classes. """ service.http_client.v2_http_client = atom.mock_http_core.MockHttpClient() service.http_client.v2_http_client.cache_case_name = case_name # Getting the auth token only needs to be done once in the course of test # runs. auth_token_key = 'service_%s_auth_token' % service_name if (auth_token_key not in options.values and options.get_value('runlive') == 'true'): service.http_client.v2_http_client.cache_test_name = 'client_login' cache_name = service.http_client.v2_http_client.get_cache_file_name() if options.get_value('clearcache') == 'true': service.http_client.v2_http_client.delete_session(cache_name) service.http_client.v2_http_client.use_cached_session(cache_name) service.ClientLogin(options.get_value('username'), options.get_value('password'), service=service_name, source=case_name) options.values[auth_token_key] = service.GetClientLoginToken() service.http_client.v2_http_client.close_session() if auth_token_key in options.values: service.SetClientLoginToken(options.values[auth_token_key]) def configure_service_cache(service, test_name): """Loads or starts a session recording for a v1 Service object. Duplicates the behavior of configure_cache, but the target for this function is a v1 Service object instead of a v2 Client. """ service.http_client.v2_http_client.cache_test_name = test_name cache_name = service.http_client.v2_http_client.get_cache_file_name() if options.get_value('clearcache') == 'true': service.http_client.v2_http_client.delete_session(cache_name) service.http_client.v2_http_client.use_cached_session(cache_name) def close_service(service): if service and options.get_value('savecache') == 'true': # If this was a live request, save the recording. service.http_client.v2_http_client.close_session() def build_suite(classes): """Creates a TestSuite for all unit test classes in the list. Assumes that each of the classes in the list has unit test methods which begin with 'test'. Calls unittest.makeSuite. Returns: A new unittest.TestSuite containing a test suite for all classes. """ suites = [unittest.makeSuite(a_class, 'test') for a_class in classes] return unittest.TestSuite(suites) def check_data_classes(test, classes): import inspect for data_class in classes: test.assert_(data_class.__doc__ is not None, 'The class %s should have a docstring' % data_class) if hasattr(data_class, '_qname'): qname_versions = None if isinstance(data_class._qname, tuple): qname_versions = data_class._qname else: qname_versions = (data_class._qname,) for versioned_qname in qname_versions: test.assert_(isinstance(versioned_qname, str), 'The class %s has a non-string _qname' % data_class) test.assert_(not versioned_qname.endswith('}'), 'The _qname for class %s is only a namespace' % ( data_class)) for attribute_name, value in data_class.__dict__.iteritems(): # Ignore all elements that start with _ (private members) if not attribute_name.startswith('_'): try: if not (isinstance(value, str) or inspect.isfunction(value) or (isinstance(value, list) and issubclass(value[0], atom.core.XmlElement)) or type(value) == property # Allow properties. or inspect.ismethod(value) # Allow methods. or inspect.ismethoddescriptor(value) # Allow method descriptors. # staticmethod et al. or issubclass(value, atom.core.XmlElement)): test.fail( 'XmlElement member should have an attribute, XML class,' ' or list of XML classes as attributes.') except TypeError: test.fail('Element %s in %s was of type %s' % ( attribute_name, data_class._qname, type(value))) def check_clients_with_auth(test, classes): for client_class in classes: test.assert_(hasattr(client_class, 'api_version')) test.assert_(isinstance(client_class.auth_service, (str, unicode, int))) test.assert_(hasattr(client_class, 'auth_service')) test.assert_(isinstance(client_class.auth_service, (str, unicode))) test.assert_(hasattr(client_class, 'auth_scopes')) test.assert_(isinstance(client_class.auth_scopes, (list, tuple))) gdata-2.0.18/samples/apps/marketplace_sample/gdata/gauth.py0000640036466300116100000016136312156622362023751 0ustar afshareng00000000000000#!/usr/bin/env python # # Copyright (C) 2009 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. # This module is used for version 2 of the Google Data APIs. """Provides auth related token classes and functions for Google Data APIs. Token classes represent a user's authorization of this app to access their data. Usually these are not created directly but by a GDClient object. ClientLoginToken AuthSubToken SecureAuthSubToken OAuthHmacToken OAuthRsaToken TwoLeggedOAuthHmacToken TwoLeggedOAuthRsaToken Functions which are often used in application code (as opposed to just within the gdata-python-client library) are the following: generate_auth_sub_url authorize_request_token The following are helper functions which are used to save and load auth token objects in the App Engine datastore. These should only be used if you are using this library within App Engine: ae_load ae_save """ import datetime import time import random import urllib import urlparse import atom.http_core try: import simplejson except ImportError: try: # Try to import from django, should work on App Engine from django.utils import simplejson except ImportError: # Should work for Python2.6 and higher. import json as simplejson try: from urlparse import parse_qsl except ImportError: from cgi import parse_qsl __author__ = 'j.s@google.com (Jeff Scudder)' PROGRAMMATIC_AUTH_LABEL = 'GoogleLogin auth=' AUTHSUB_AUTH_LABEL = 'AuthSub token=' OAUTH2_AUTH_LABEL = 'OAuth ' # This dict provides the AuthSub and OAuth scopes for all services by service # name. The service name (key) is used in ClientLogin requests. AUTH_SCOPES = { 'cl': ( # Google Calendar API 'https://www.google.com/calendar/feeds/', 'http://www.google.com/calendar/feeds/'), 'gbase': ( # Google Base API 'http://base.google.com/base/feeds/', 'http://www.google.com/base/feeds/'), 'blogger': ( # Blogger API 'http://www.blogger.com/feeds/',), 'codesearch': ( # Google Code Search API 'http://www.google.com/codesearch/feeds/',), 'cp': ( # Contacts API 'https://www.google.com/m8/feeds/', 'http://www.google.com/m8/feeds/'), 'finance': ( # Google Finance API 'http://finance.google.com/finance/feeds/',), 'health': ( # Google Health API 'https://www.google.com/health/feeds/',), 'writely': ( # Documents List API 'https://docs.google.com/feeds/', 'https://spreadsheets.google.com/feeds/', 'https://docs.googleusercontent.com/'), 'lh2': ( # Picasa Web Albums API 'http://picasaweb.google.com/data/',), 'apps': ( # Google Apps Provisioning API 'https://apps-apis.google.com/a/feeds/user/', 'https://apps-apis.google.com/a/feeds/policies/', 'https://apps-apis.google.com/a/feeds/alias/', 'https://apps-apis.google.com/a/feeds/groups/'), 'weaver': ( # Health H9 Sandbox 'https://www.google.com/h9/feeds/',), 'wise': ( # Spreadsheets Data API 'https://spreadsheets.google.com/feeds/',), 'sitemaps': ( # Google Webmaster Tools API 'https://www.google.com/webmasters/tools/feeds/',), 'youtube': ( # YouTube API 'http://gdata.youtube.com/feeds/api/', 'http://uploads.gdata.youtube.com/feeds/api', 'http://gdata.youtube.com/action/GetUploadToken'), 'books': ( # Google Books API 'http://www.google.com/books/feeds/',), 'analytics': ( # Google Analytics API 'https://www.google.com/analytics/feeds/',), 'jotspot': ( # Google Sites API 'http://sites.google.com/feeds/', 'https://sites.google.com/feeds/'), 'local': ( # Google Maps Data API 'http://maps.google.com/maps/feeds/',), 'code': ( # Project Hosting Data API 'http://code.google.com/feeds/issues',)} class Error(Exception): pass class UnsupportedTokenType(Error): """Raised when token to or from blob is unable to convert the token.""" pass class OAuth2AccessTokenError(Error): """Raised when an OAuth2 error occurs.""" def __init__(self, error_message): self.error_message = error_message # ClientLogin functions and classes. def generate_client_login_request_body(email, password, service, source, account_type='HOSTED_OR_GOOGLE', captcha_token=None, captcha_response=None): """Creates the body of the autentication request See http://code.google.com/apis/accounts/AuthForInstalledApps.html#Request for more details. Args: email: str password: str service: str source: str account_type: str (optional) Defaul is 'HOSTED_OR_GOOGLE', other valid values are 'GOOGLE' and 'HOSTED' captcha_token: str (optional) captcha_response: str (optional) Returns: The HTTP body to send in a request for a client login token. """ # Create a POST body containing the user's credentials. request_fields = {'Email': email, 'Passwd': password, 'accountType': account_type, 'service': service, 'source': source} if captcha_token and captcha_response: # Send the captcha token and response as part of the POST body if the # user is responding to a captch challenge. request_fields['logintoken'] = captcha_token request_fields['logincaptcha'] = captcha_response return urllib.urlencode(request_fields) GenerateClientLoginRequestBody = generate_client_login_request_body def get_client_login_token_string(http_body): """Returns the token value for a ClientLoginToken. Reads the token from the server's response to a Client Login request and creates the token value string to use in requests. Args: http_body: str The body of the server's HTTP response to a Client Login request Returns: The token value string for a ClientLoginToken. """ for response_line in http_body.splitlines(): if response_line.startswith('Auth='): # Strip off the leading Auth= and return the Authorization value. return response_line[5:] return None GetClientLoginTokenString = get_client_login_token_string def get_captcha_challenge(http_body, captcha_base_url='http://www.google.com/accounts/'): """Returns the URL and token for a CAPTCHA challenge issued by the server. Args: http_body: str The body of the HTTP response from the server which contains the CAPTCHA challenge. captcha_base_url: str This function returns a full URL for viewing the challenge image which is built from the server's response. This base_url is used as the beginning of the URL because the server only provides the end of the URL. For example the server provides 'Captcha?ctoken=Hi...N' and the URL for the image is 'http://www.google.com/accounts/Captcha?ctoken=Hi...N' Returns: A dictionary containing the information needed to repond to the CAPTCHA challenge, the image URL and the ID token of the challenge. The dictionary is in the form: {'token': string identifying the CAPTCHA image, 'url': string containing the URL of the image} Returns None if there was no CAPTCHA challenge in the response. """ contains_captcha_challenge = False captcha_parameters = {} for response_line in http_body.splitlines(): if response_line.startswith('Error=CaptchaRequired'): contains_captcha_challenge = True elif response_line.startswith('CaptchaToken='): # Strip off the leading CaptchaToken= captcha_parameters['token'] = response_line[13:] elif response_line.startswith('CaptchaUrl='): captcha_parameters['url'] = '%s%s' % (captcha_base_url, response_line[11:]) if contains_captcha_challenge: return captcha_parameters else: return None GetCaptchaChallenge = get_captcha_challenge class ClientLoginToken(object): def __init__(self, token_string): self.token_string = token_string def modify_request(self, http_request): http_request.headers['Authorization'] = '%s%s' % (PROGRAMMATIC_AUTH_LABEL, self.token_string) ModifyRequest = modify_request # AuthSub functions and classes. def _to_uri(str_or_uri): if isinstance(str_or_uri, (str, unicode)): return atom.http_core.Uri.parse_uri(str_or_uri) return str_or_uri def generate_auth_sub_url(next, scopes, secure=False, session=True, request_url=atom.http_core.parse_uri( 'https://www.google.com/accounts/AuthSubRequest'), domain='default', scopes_param_prefix='auth_sub_scopes'): """Constructs a URI for requesting a multiscope AuthSub token. The generated token will contain a URL parameter to pass along the requested scopes to the next URL. When the Google Accounts page redirects the broswser to the 'next' URL, it appends the single use AuthSub token value to the URL as a URL parameter with the key 'token'. However, the information about which scopes were requested is not included by Google Accounts. This method adds the scopes to the next URL before making the request so that the redirect will be sent to a page, and both the token value and the list of scopes for which the token was requested. Args: next: atom.http_core.Uri or string The URL user will be sent to after authorizing this web application to access their data. scopes: list containint strings or atom.http_core.Uri objects. The URLs of the services to be accessed. Could also be a single string or single atom.http_core.Uri for requesting just one scope. secure: boolean (optional) Determines whether or not the issued token is a secure token. session: boolean (optional) Determines whether or not the issued token can be upgraded to a session token. request_url: atom.http_core.Uri or str The beginning of the request URL. This is normally 'http://www.google.com/accounts/AuthSubRequest' or '/accounts/AuthSubRequest' domain: The domain which the account is part of. This is used for Google Apps accounts, the default value is 'default' which means that the requested account is a Google Account (@gmail.com for example) scopes_param_prefix: str (optional) The requested scopes are added as a URL parameter to the next URL so that the page at the 'next' URL can extract the token value and the valid scopes from the URL. The key for the URL parameter defaults to 'auth_sub_scopes' Returns: An atom.http_core.Uri which the user's browser should be directed to in order to authorize this application to access their information. """ if isinstance(next, (str, unicode)): next = atom.http_core.Uri.parse_uri(next) # If the user passed in a string instead of a list for scopes, convert to # a single item tuple. if isinstance(scopes, (str, unicode, atom.http_core.Uri)): scopes = (scopes,) scopes_string = ' '.join([str(scope) for scope in scopes]) next.query[scopes_param_prefix] = scopes_string if isinstance(request_url, (str, unicode)): request_url = atom.http_core.Uri.parse_uri(request_url) request_url.query['next'] = str(next) request_url.query['scope'] = scopes_string if session: request_url.query['session'] = '1' else: request_url.query['session'] = '0' if secure: request_url.query['secure'] = '1' else: request_url.query['secure'] = '0' request_url.query['hd'] = domain return request_url def auth_sub_string_from_url(url, scopes_param_prefix='auth_sub_scopes'): """Finds the token string (and scopes) after the browser is redirected. After the Google Accounts AuthSub pages redirect the user's broswer back to the web application (using the 'next' URL from the request) the web app must extract the token from the current page's URL. The token is provided as a URL parameter named 'token' and if generate_auth_sub_url was used to create the request, the token's valid scopes are included in a URL parameter whose name is specified in scopes_param_prefix. Args: url: atom.url.Url or str representing the current URL. The token value and valid scopes should be included as URL parameters. scopes_param_prefix: str (optional) The URL parameter key which maps to the list of valid scopes for the token. Returns: A tuple containing the token value as a string, and a tuple of scopes (as atom.http_core.Uri objects) which are URL prefixes under which this token grants permission to read and write user data. (token_string, (scope_uri, scope_uri, scope_uri, ...)) If no scopes were included in the URL, the second value in the tuple is None. If there was no token param in the url, the tuple returned is (None, None) """ if isinstance(url, (str, unicode)): url = atom.http_core.Uri.parse_uri(url) if 'token' not in url.query: return (None, None) token = url.query['token'] # TODO: decide whether no scopes should be None or (). scopes = None # Default to None for no scopes. if scopes_param_prefix in url.query: scopes = tuple(url.query[scopes_param_prefix].split(' ')) return (token, scopes) AuthSubStringFromUrl = auth_sub_string_from_url def auth_sub_string_from_body(http_body): """Extracts the AuthSub token from an HTTP body string. Used to find the new session token after making a request to upgrade a single use AuthSub token. Args: http_body: str The repsonse from the server which contains the AuthSub key. For example, this function would find the new session token from the server's response to an upgrade token request. Returns: The raw token value string to use in an AuthSubToken object. """ for response_line in http_body.splitlines(): if response_line.startswith('Token='): # Strip off Token= and return the token value string. return response_line[6:] return None class AuthSubToken(object): def __init__(self, token_string, scopes=None): self.token_string = token_string self.scopes = scopes or [] def modify_request(self, http_request): """Sets Authorization header, allows app to act on the user's behalf.""" http_request.headers['Authorization'] = '%s%s' % (AUTHSUB_AUTH_LABEL, self.token_string) ModifyRequest = modify_request def from_url(str_or_uri): """Creates a new AuthSubToken using information in the URL. Uses auth_sub_string_from_url. Args: str_or_uri: The current page's URL (as a str or atom.http_core.Uri) which should contain a token query parameter since the Google auth server redirected the user's browser to this URL. """ token_and_scopes = auth_sub_string_from_url(str_or_uri) return AuthSubToken(token_and_scopes[0], token_and_scopes[1]) from_url = staticmethod(from_url) FromUrl = from_url def _upgrade_token(self, http_body): """Replaces the token value with a session token from the auth server. Uses the response of a token upgrade request to modify this token. Uses auth_sub_string_from_body. """ self.token_string = auth_sub_string_from_body(http_body) # Functions and classes for Secure-mode AuthSub def build_auth_sub_data(http_request, timestamp, nonce): """Creates the data string which must be RSA-signed in secure requests. For more details see the documenation on secure AuthSub requests: http://code.google.com/apis/accounts/docs/AuthSub.html#signingrequests Args: http_request: The request being made to the server. The Request's URL must be complete before this signature is calculated as any changes to the URL will invalidate the signature. nonce: str Random 64-bit, unsigned number encoded as an ASCII string in decimal format. The nonce/timestamp pair should always be unique to prevent replay attacks. timestamp: Integer representing the time the request is sent. The timestamp should be expressed in number of seconds after January 1, 1970 00:00:00 GMT. """ return '%s %s %s %s' % (http_request.method, str(http_request.uri), str(timestamp), nonce) def generate_signature(data, rsa_key): """Signs the data string for a secure AuthSub request.""" import base64 try: from tlslite.utils import keyfactory except ImportError: try: from gdata.tlslite.utils import keyfactory except ImportError: from tlslite.tlslite.utils import keyfactory private_key = keyfactory.parsePrivateKey(rsa_key) signed = private_key.hashAndSign(data) # Python2.3 and lower does not have the base64.b64encode function. if hasattr(base64, 'b64encode'): return base64.b64encode(signed) else: return base64.encodestring(signed).replace('\n', '') class SecureAuthSubToken(AuthSubToken): def __init__(self, token_string, rsa_private_key, scopes=None): self.token_string = token_string self.scopes = scopes or [] self.rsa_private_key = rsa_private_key def from_url(str_or_uri, rsa_private_key): """Creates a new SecureAuthSubToken using information in the URL. Uses auth_sub_string_from_url. Args: str_or_uri: The current page's URL (as a str or atom.http_core.Uri) which should contain a token query parameter since the Google auth server redirected the user's browser to this URL. rsa_private_key: str the private RSA key cert used to sign all requests made with this token. """ token_and_scopes = auth_sub_string_from_url(str_or_uri) return SecureAuthSubToken(token_and_scopes[0], rsa_private_key, token_and_scopes[1]) from_url = staticmethod(from_url) FromUrl = from_url def modify_request(self, http_request): """Sets the Authorization header and includes a digital signature. Calculates a digital signature using the private RSA key, a timestamp (uses now at the time this method is called) and a random nonce. Args: http_request: The atom.http_core.HttpRequest which contains all of the information needed to send a request to the remote server. The URL and the method of the request must be already set and cannot be changed after this token signs the request, or the signature will not be valid. """ timestamp = str(int(time.time())) nonce = ''.join([str(random.randint(0, 9)) for i in xrange(15)]) data = build_auth_sub_data(http_request, timestamp, nonce) signature = generate_signature(data, self.rsa_private_key) http_request.headers['Authorization'] = ( '%s%s sigalg="rsa-sha1" data="%s" sig="%s"' % (AUTHSUB_AUTH_LABEL, self.token_string, data, signature)) ModifyRequest = modify_request # OAuth functions and classes. RSA_SHA1 = 'RSA-SHA1' HMAC_SHA1 = 'HMAC-SHA1' def build_oauth_base_string(http_request, consumer_key, nonce, signaure_type, timestamp, version, next='oob', token=None, verifier=None): """Generates the base string to be signed in the OAuth request. Args: http_request: The request being made to the server. The Request's URL must be complete before this signature is calculated as any changes to the URL will invalidate the signature. consumer_key: Domain identifying the third-party web application. This is the domain used when registering the application with Google. It identifies who is making the request on behalf of the user. nonce: Random 64-bit, unsigned number encoded as an ASCII string in decimal format. The nonce/timestamp pair should always be unique to prevent replay attacks. signaure_type: either RSA_SHA1 or HMAC_SHA1 timestamp: Integer representing the time the request is sent. The timestamp should be expressed in number of seconds after January 1, 1970 00:00:00 GMT. version: The OAuth version used by the requesting web application. This value must be '1.0' or '1.0a'. If not provided, Google assumes version 1.0 is in use. next: The URL the user should be redirected to after granting access to a Google service(s). It can include url-encoded query parameters. The default value is 'oob'. (This is the oauth_callback.) token: The string for the OAuth request token or OAuth access token. verifier: str Sent as the oauth_verifier and required when upgrading a request token to an access token. """ # First we must build the canonical base string for the request. params = http_request.uri.query.copy() params['oauth_consumer_key'] = consumer_key params['oauth_nonce'] = nonce params['oauth_signature_method'] = signaure_type params['oauth_timestamp'] = str(timestamp) if next is not None: params['oauth_callback'] = str(next) if token is not None: params['oauth_token'] = token if version is not None: params['oauth_version'] = version if verifier is not None: params['oauth_verifier'] = verifier # We need to get the key value pairs in lexigraphically sorted order. sorted_keys = None try: sorted_keys = sorted(params.keys()) # The sorted function is not available in Python2.3 and lower except NameError: sorted_keys = params.keys() sorted_keys.sort() pairs = [] for key in sorted_keys: pairs.append('%s=%s' % (urllib.quote(key, safe='~'), urllib.quote(params[key], safe='~'))) # We want to escape /'s too, so use safe='~' all_parameters = urllib.quote('&'.join(pairs), safe='~') normailzed_host = http_request.uri.host.lower() normalized_scheme = (http_request.uri.scheme or 'http').lower() non_default_port = None if (http_request.uri.port is not None and ((normalized_scheme == 'https' and http_request.uri.port != 443) or (normalized_scheme == 'http' and http_request.uri.port != 80))): non_default_port = http_request.uri.port path = http_request.uri.path or '/' request_path = None if not path.startswith('/'): path = '/%s' % path if non_default_port is not None: # Set the only safe char in url encoding to ~ since we want to escape / # as well. request_path = urllib.quote('%s://%s:%s%s' % ( normalized_scheme, normailzed_host, non_default_port, path), safe='~') else: # Set the only safe char in url encoding to ~ since we want to escape / # as well. request_path = urllib.quote('%s://%s%s' % ( normalized_scheme, normailzed_host, path), safe='~') # TODO: ensure that token escaping logic is correct, not sure if the token # value should be double escaped instead of single. base_string = '&'.join((http_request.method.upper(), request_path, all_parameters)) # Now we have the base string, we can calculate the oauth_signature. return base_string def generate_hmac_signature(http_request, consumer_key, consumer_secret, timestamp, nonce, version, next='oob', token=None, token_secret=None, verifier=None): import hmac import base64 base_string = build_oauth_base_string( http_request, consumer_key, nonce, HMAC_SHA1, timestamp, version, next, token, verifier=verifier) hash_key = None hashed = None if token_secret is not None: hash_key = '%s&%s' % (urllib.quote(consumer_secret, safe='~'), urllib.quote(token_secret, safe='~')) else: hash_key = '%s&' % urllib.quote(consumer_secret, safe='~') try: import hashlib hashed = hmac.new(hash_key, base_string, hashlib.sha1) except ImportError: import sha hashed = hmac.new(hash_key, base_string, sha) # Python2.3 does not have base64.b64encode. if hasattr(base64, 'b64encode'): return base64.b64encode(hashed.digest()) else: return base64.encodestring(hashed.digest()).replace('\n', '') def generate_rsa_signature(http_request, consumer_key, rsa_key, timestamp, nonce, version, next='oob', token=None, token_secret=None, verifier=None): import base64 try: from tlslite.utils import keyfactory except ImportError: try: from gdata.tlslite.utils import keyfactory except ImportError: from tlslite.tlslite.utils import keyfactory base_string = build_oauth_base_string( http_request, consumer_key, nonce, RSA_SHA1, timestamp, version, next, token, verifier=verifier) private_key = keyfactory.parsePrivateKey(rsa_key) # Sign using the key signed = private_key.hashAndSign(base_string) # Python2.3 does not have base64.b64encode. if hasattr(base64, 'b64encode'): return base64.b64encode(signed) else: return base64.encodestring(signed).replace('\n', '') def generate_auth_header(consumer_key, timestamp, nonce, signature_type, signature, version='1.0', next=None, token=None, verifier=None): """Builds the Authorization header to be sent in the request. Args: consumer_key: Identifies the application making the request (str). timestamp: nonce: signature_type: One of either HMAC_SHA1 or RSA_SHA1 signature: The HMAC or RSA signature for the request as a base64 encoded string. version: The version of the OAuth protocol that this request is using. Default is '1.0' next: The URL of the page that the user's browser should be sent to after they authorize the token. (Optional) token: str The OAuth token value to be used in the oauth_token parameter of the header. verifier: str The OAuth verifier which must be included when you are upgrading a request token to an access token. """ params = { 'oauth_consumer_key': consumer_key, 'oauth_version': version, 'oauth_nonce': nonce, 'oauth_timestamp': str(timestamp), 'oauth_signature_method': signature_type, 'oauth_signature': signature} if next is not None: params['oauth_callback'] = str(next) if token is not None: params['oauth_token'] = token if verifier is not None: params['oauth_verifier'] = verifier pairs = [ '%s="%s"' % ( k, urllib.quote(v, safe='~')) for k, v in params.iteritems()] return 'OAuth %s' % (', '.join(pairs)) REQUEST_TOKEN_URL = 'https://www.google.com/accounts/OAuthGetRequestToken' ACCESS_TOKEN_URL = 'https://www.google.com/accounts/OAuthGetAccessToken' def generate_request_for_request_token( consumer_key, signature_type, scopes, rsa_key=None, consumer_secret=None, auth_server_url=REQUEST_TOKEN_URL, next='oob', version='1.0'): """Creates request to be sent to auth server to get an OAuth request token. Args: consumer_key: signature_type: either RSA_SHA1 or HMAC_SHA1. The rsa_key must be provided if the signature type is RSA but if the signature method is HMAC, the consumer_secret must be used. scopes: List of URL prefixes for the data which we want to access. For example, to request access to the user's Blogger and Google Calendar data, we would request ['http://www.blogger.com/feeds/', 'https://www.google.com/calendar/feeds/', 'http://www.google.com/calendar/feeds/'] rsa_key: Only used if the signature method is RSA_SHA1. consumer_secret: Only used if the signature method is HMAC_SHA1. auth_server_url: The URL to which the token request should be directed. Defaults to 'https://www.google.com/accounts/OAuthGetRequestToken'. next: The URL of the page that the user's browser should be sent to after they authorize the token. (Optional) version: The OAuth version used by the requesting web application. Defaults to '1.0a' Returns: An atom.http_core.HttpRequest object with the URL, Authorization header and body filled in. """ request = atom.http_core.HttpRequest(auth_server_url, 'POST') # Add the requested auth scopes to the Auth request URL. if scopes: request.uri.query['scope'] = ' '.join(scopes) timestamp = str(int(time.time())) nonce = ''.join([str(random.randint(0, 9)) for i in xrange(15)]) signature = None if signature_type == HMAC_SHA1: signature = generate_hmac_signature( request, consumer_key, consumer_secret, timestamp, nonce, version, next=next) elif signature_type == RSA_SHA1: signature = generate_rsa_signature( request, consumer_key, rsa_key, timestamp, nonce, version, next=next) else: return None request.headers['Authorization'] = generate_auth_header( consumer_key, timestamp, nonce, signature_type, signature, version, next) request.headers['Content-Length'] = '0' return request def generate_request_for_access_token( request_token, auth_server_url=ACCESS_TOKEN_URL): """Creates a request to ask the OAuth server for an access token. Requires a request token which the user has authorized. See the documentation on OAuth with Google Data for more details: http://code.google.com/apis/accounts/docs/OAuth.html#AccessToken Args: request_token: An OAuthHmacToken or OAuthRsaToken which the user has approved using their browser. auth_server_url: (optional) The URL at which the OAuth access token is requested. Defaults to https://www.google.com/accounts/OAuthGetAccessToken Returns: A new HttpRequest object which can be sent to the OAuth server to request an OAuth Access Token. """ http_request = atom.http_core.HttpRequest(auth_server_url, 'POST') http_request.headers['Content-Length'] = '0' return request_token.modify_request(http_request) def oauth_token_info_from_body(http_body): """Exracts an OAuth request token from the server's response. Returns: A tuple of strings containing the OAuth token and token secret. If neither of these are present in the body, returns (None, None) """ token = None token_secret = None for pair in http_body.split('&'): if pair.startswith('oauth_token='): token = urllib.unquote(pair[len('oauth_token='):]) if pair.startswith('oauth_token_secret='): token_secret = urllib.unquote(pair[len('oauth_token_secret='):]) return (token, token_secret) def hmac_token_from_body(http_body, consumer_key, consumer_secret, auth_state): token_value, token_secret = oauth_token_info_from_body(http_body) token = OAuthHmacToken(consumer_key, consumer_secret, token_value, token_secret, auth_state) return token def rsa_token_from_body(http_body, consumer_key, rsa_private_key, auth_state): token_value, token_secret = oauth_token_info_from_body(http_body) token = OAuthRsaToken(consumer_key, rsa_private_key, token_value, token_secret, auth_state) return token DEFAULT_DOMAIN = 'default' OAUTH_AUTHORIZE_URL = 'https://www.google.com/accounts/OAuthAuthorizeToken' def generate_oauth_authorization_url( token, next=None, hd=DEFAULT_DOMAIN, hl=None, btmpl=None, auth_server=OAUTH_AUTHORIZE_URL): """Creates a URL for the page where the request token can be authorized. Args: token: str The request token from the OAuth server. next: str (optional) URL the user should be redirected to after granting access to a Google service(s). It can include url-encoded query parameters. hd: str (optional) Identifies a particular hosted domain account to be accessed (for example, 'mycollege.edu'). Uses 'default' to specify a regular Google account ('username@gmail.com'). hl: str (optional) An ISO 639 country code identifying what language the approval page should be translated in (for example, 'hl=en' for English). The default is the user's selected language. btmpl: str (optional) Forces a mobile version of the approval page. The only accepted value is 'mobile'. auth_server: str (optional) The start of the token authorization web page. Defaults to 'https://www.google.com/accounts/OAuthAuthorizeToken' Returns: An atom.http_core.Uri pointing to the token authorization page where the user may allow or deny this app to access their Google data. """ uri = atom.http_core.Uri.parse_uri(auth_server) uri.query['oauth_token'] = token uri.query['hd'] = hd if next is not None: uri.query['oauth_callback'] = str(next) if hl is not None: uri.query['hl'] = hl if btmpl is not None: uri.query['btmpl'] = btmpl return uri def oauth_token_info_from_url(url): """Exracts an OAuth access token from the redirected page's URL. Returns: A tuple of strings containing the OAuth token and the OAuth verifier which need to sent when upgrading a request token to an access token. """ if isinstance(url, (str, unicode)): url = atom.http_core.Uri.parse_uri(url) token = None verifier = None if 'oauth_token' in url.query: token = urllib.unquote(url.query['oauth_token']) if 'oauth_verifier' in url.query: verifier = urllib.unquote(url.query['oauth_verifier']) return (token, verifier) def authorize_request_token(request_token, url): """Adds information to request token to allow it to become an access token. Modifies the request_token object passed in by setting and unsetting the necessary fields to allow this token to form a valid upgrade request. Args: request_token: The OAuth request token which has been authorized by the user. In order for this token to be upgraded to an access token, certain fields must be extracted from the URL and added to the token so that they can be passed in an upgrade-token request. url: The URL of the current page which the user's browser was redirected to after they authorized access for the app. This function extracts information from the URL which is needed to upgraded the token from a request token to an access token. Returns: The same token object which was passed in. """ token, verifier = oauth_token_info_from_url(url) request_token.token = token request_token.verifier = verifier request_token.auth_state = AUTHORIZED_REQUEST_TOKEN return request_token AuthorizeRequestToken = authorize_request_token def upgrade_to_access_token(request_token, server_response_body): """Extracts access token information from response to an upgrade request. Once the server has responded with the new token info for the OAuth access token, this method modifies the request_token to set and unset necessary fields to create valid OAuth authorization headers for requests. Args: request_token: An OAuth token which this function modifies to allow it to be used as an access token. server_response_body: str The server's response to an OAuthAuthorizeToken request. This should contain the new token and token_secret which are used to generate the signature and parameters of the Authorization header in subsequent requests to Google Data APIs. Returns: The same token object which was passed in. """ token, token_secret = oauth_token_info_from_body(server_response_body) request_token.token = token request_token.token_secret = token_secret request_token.auth_state = ACCESS_TOKEN request_token.next = None request_token.verifier = None return request_token UpgradeToAccessToken = upgrade_to_access_token REQUEST_TOKEN = 1 AUTHORIZED_REQUEST_TOKEN = 2 ACCESS_TOKEN = 3 class OAuthHmacToken(object): SIGNATURE_METHOD = HMAC_SHA1 def __init__(self, consumer_key, consumer_secret, token, token_secret, auth_state, next=None, verifier=None): self.consumer_key = consumer_key self.consumer_secret = consumer_secret self.token = token self.token_secret = token_secret self.auth_state = auth_state self.next = next self.verifier = verifier # Used to convert request token to access token. def generate_authorization_url( self, google_apps_domain=DEFAULT_DOMAIN, language=None, btmpl=None, auth_server=OAUTH_AUTHORIZE_URL): """Creates the URL at which the user can authorize this app to access. Args: google_apps_domain: str (optional) If the user should be signing in using an account under a known Google Apps domain, provide the domain name ('example.com') here. If not provided, 'default' will be used, and the user will be prompted to select an account if they are signed in with a Google Account and Google Apps accounts. language: str (optional) An ISO 639 country code identifying what language the approval page should be translated in (for example, 'en' for English). The default is the user's selected language. btmpl: str (optional) Forces a mobile version of the approval page. The only accepted value is 'mobile'. auth_server: str (optional) The start of the token authorization web page. Defaults to 'https://www.google.com/accounts/OAuthAuthorizeToken' """ return generate_oauth_authorization_url( self.token, hd=google_apps_domain, hl=language, btmpl=btmpl, auth_server=auth_server) GenerateAuthorizationUrl = generate_authorization_url def modify_request(self, http_request): """Sets the Authorization header in the HTTP request using the token. Calculates an HMAC signature using the information in the token to indicate that the request came from this application and that this application has permission to access a particular user's data. Returns: The same HTTP request object which was passed in. """ timestamp = str(int(time.time())) nonce = ''.join([str(random.randint(0, 9)) for i in xrange(15)]) signature = generate_hmac_signature( http_request, self.consumer_key, self.consumer_secret, timestamp, nonce, version='1.0', next=self.next, token=self.token, token_secret=self.token_secret, verifier=self.verifier) http_request.headers['Authorization'] = generate_auth_header( self.consumer_key, timestamp, nonce, HMAC_SHA1, signature, version='1.0', next=self.next, token=self.token, verifier=self.verifier) return http_request ModifyRequest = modify_request class OAuthRsaToken(OAuthHmacToken): SIGNATURE_METHOD = RSA_SHA1 def __init__(self, consumer_key, rsa_private_key, token, token_secret, auth_state, next=None, verifier=None): self.consumer_key = consumer_key self.rsa_private_key = rsa_private_key self.token = token self.token_secret = token_secret self.auth_state = auth_state self.next = next self.verifier = verifier # Used to convert request token to access token. def modify_request(self, http_request): """Sets the Authorization header in the HTTP request using the token. Calculates an RSA signature using the information in the token to indicate that the request came from this application and that this application has permission to access a particular user's data. Returns: The same HTTP request object which was passed in. """ timestamp = str(int(time.time())) nonce = ''.join([str(random.randint(0, 9)) for i in xrange(15)]) signature = generate_rsa_signature( http_request, self.consumer_key, self.rsa_private_key, timestamp, nonce, version='1.0', next=self.next, token=self.token, token_secret=self.token_secret, verifier=self.verifier) http_request.headers['Authorization'] = generate_auth_header( self.consumer_key, timestamp, nonce, RSA_SHA1, signature, version='1.0', next=self.next, token=self.token, verifier=self.verifier) return http_request ModifyRequest = modify_request class TwoLeggedOAuthHmacToken(OAuthHmacToken): def __init__(self, consumer_key, consumer_secret, requestor_id): self.requestor_id = requestor_id OAuthHmacToken.__init__( self, consumer_key, consumer_secret, None, None, ACCESS_TOKEN, next=None, verifier=None) def modify_request(self, http_request): """Sets the Authorization header in the HTTP request using the token. Calculates an HMAC signature using the information in the token to indicate that the request came from this application and that this application has permission to access a particular user's data using 2LO. Returns: The same HTTP request object which was passed in. """ http_request.uri.query['xoauth_requestor_id'] = self.requestor_id return OAuthHmacToken.modify_request(self, http_request) ModifyRequest = modify_request class TwoLeggedOAuthRsaToken(OAuthRsaToken): def __init__(self, consumer_key, rsa_private_key, requestor_id): self.requestor_id = requestor_id OAuthRsaToken.__init__( self, consumer_key, rsa_private_key, None, None, ACCESS_TOKEN, next=None, verifier=None) def modify_request(self, http_request): """Sets the Authorization header in the HTTP request using the token. Calculates an RSA signature using the information in the token to indicate that the request came from this application and that this application has permission to access a particular user's data using 2LO. Returns: The same HTTP request object which was passed in. """ http_request.uri.query['xoauth_requestor_id'] = self.requestor_id return OAuthRsaToken.modify_request(self, http_request) ModifyRequest = modify_request class OAuth2Token(object): """Token object for OAuth 2.0 as described on . Token can be applied to a gdata.client.GDClient object using the authorize() method, which then signs each request from that object with the OAuth 2.0 access token. This class supports 3 flows of OAuth 2.0: Client-side web flow: call generate_authorize_url with `response_type='token'' and the registered `redirect_uri'. Server-side web flow: call generate_authorize_url with the registered `redirect_url'. Native applications flow: call generate_authorize_url as it is. You will have to ask the user to go to the generated url and pass in the authorization code to your application. """ def __init__(self, client_id, client_secret, scope, user_agent, auth_uri='https://accounts.google.com/o/oauth2/auth', token_uri='https://accounts.google.com/o/oauth2/token', access_token=None, refresh_token=None): """Create an instance of OAuth2Token This constructor is not usually called by the user, instead OAuth2Credentials objects are instantiated by the OAuth2WebServerFlow. Args: client_id: string, client identifier. client_secret: string client secret. scope: string, scope of the credentials being requested. user_agent: string, HTTP User-Agent to provide for this application. auth_uri: string, URI for authorization endpoint. For convenience defaults to Google's endpoints but any OAuth 2.0 provider can be used. token_uri: string, URI for token endpoint. For convenience defaults to Google's endpoints but any OAuth 2.0 provider can be used. access_token: string, access token. refresh_token: string, refresh token. """ self.client_id = client_id self.client_secret = client_secret self.scope = scope self.user_agent = user_agent self.auth_uri = auth_uri self.token_uri = token_uri self.access_token = access_token self.refresh_token = refresh_token # True if the credentials have been revoked or expired and can't be # refreshed. self._invalid = False @property def invalid(self): """True if the credentials are invalid, such as being revoked.""" return getattr(self, '_invalid', False) def _refresh(self, request): """Refresh the access_token using the refresh_token. Args: http: An instance of httplib2.Http.request or something that acts like it. """ body = urllib.urlencode({ 'grant_type': 'refresh_token', 'client_id': self.client_id, 'client_secret': self.client_secret, 'refresh_token' : self.refresh_token }) headers = { 'user-agent': self.user_agent, } http_request = atom.http_core.HttpRequest(uri=self.token_uri, method='POST', headers=headers) http_request.add_body_part(body, mime_type='application/x-www-form-urlencoded') response = request(http_request) body = response.read() if response.status == 200: self._extract_tokens(body) else: self._invalid = True return response def _extract_tokens(self, body): d = simplejson.loads(body) self.access_token = d['access_token'] self.refresh_token = d.get('refresh_token', self.refresh_token) if 'expires_in' in d: self.token_expiry = datetime.timedelta( seconds = int(d['expires_in'])) + datetime.datetime.now() else: self.token_expiry = None def generate_authorize_url(self, redirect_uri='oob', response_type='code', **kwargs): """Returns a URI to redirect to the provider. Args: redirect_uri: string, Either the string 'oob' for a non-web-based application, or a URI that handles the callback from the authorization server. response_type: string, Either the string 'code' for server-side or native application, or the string 'token' for client- side application. If redirect_uri is 'oob' then pass in the generated verification code to get_access_token, otherwise pass in the query parameters received at the callback uri to get_access_token. If the response_type is 'token', no need to call get_access_token as the API will return it within the query parameters received at the callback: oauth2_token.access_token = YOUR_ACCESS_TOKEN """ self.redirect_uri = redirect_uri query = { 'response_type': response_type, 'client_id': self.client_id, 'redirect_uri': redirect_uri, 'scope': self.scope, } query.update(kwargs) parts = list(urlparse.urlparse(self.auth_uri)) query.update(dict(parse_qsl(parts[4]))) # 4 is the index of the query part parts[4] = urllib.urlencode(query) return urlparse.urlunparse(parts) def get_access_token(self, code): """Exhanges a code for an access token. Args: code: string or dict, either the code as a string, or a dictionary of the query parameters to the redirect_uri, which contains the code. """ if not (isinstance(code, str) or isinstance(code, unicode)): code = code['code'] body = urllib.urlencode({ 'grant_type': 'authorization_code', 'client_id': self.client_id, 'client_secret': self.client_secret, 'code': code, 'redirect_uri': self.redirect_uri, 'scope': self.scope }) headers = { 'user-agent': self.user_agent, } http_client = atom.http_core.HttpClient() http_request = atom.http_core.HttpRequest(uri=self.token_uri, method='POST', headers=headers) http_request.add_body_part(data=body, mime_type='application/x-www-form-urlencoded') response = http_client.request(http_request) body = response.read() if response.status == 200: self._extract_tokens(body) return self else: error_msg = 'Invalid response %s.' % response.status try: d = simplejson.loads(body) if 'error' in d: error_msg = d['error'] except: pass raise OAuth2AccessTokenError(error_msg) def authorize(self, client): """Authorize a gdata.client.GDClient instance with these credentials. Args: client: An instance of gdata.client.GDClient or something that acts like it. Returns: A modified instance of client that was passed in. Example: c = gdata.client.GDClient(source='user-agent') c = token.authorize(c) """ client.auth_token = self request_orig = client.http_client.request def new_request(http_request): response = request_orig(http_request) if response.status == 401: refresh_response = self._refresh(request_orig) if self._invalid: return refresh_response else: self.modify_request(http_request) return request_orig(http_request) else: return response client.http_client.request = new_request return client def modify_request(self, http_request): """Sets the Authorization header in the HTTP request using the token. Returns: The same HTTP request object which was passed in. """ http_request.headers['Authorization'] = '%s%s' % (OAUTH2_AUTH_LABEL, self.access_token) return http_request ModifyRequest = modify_request def _join_token_parts(*args): """"Escapes and combines all strings passed in. Used to convert a token object's members into a string instead of using pickle. Note: A None value will be converted to an empty string. Returns: A string in the form 1x|member1|member2|member3... """ return '|'.join([urllib.quote_plus(a or '') for a in args]) def _split_token_parts(blob): """Extracts and unescapes fields from the provided binary string. Reverses the packing performed by _join_token_parts. Used to extract the members of a token object. Note: An empty string from the blob will be interpreted as None. Args: blob: str A string of the form 1x|member1|member2|member3 as created by _join_token_parts Returns: A list of unescaped strings. """ return [urllib.unquote_plus(part) or None for part in blob.split('|')] def token_to_blob(token): """Serializes the token data as a string for storage in a datastore. Supported token classes: ClientLoginToken, AuthSubToken, SecureAuthSubToken, OAuthRsaToken, and OAuthHmacToken, TwoLeggedOAuthRsaToken, TwoLeggedOAuthHmacToken and OAuth2Token. Args: token: A token object which must be of one of the supported token classes. Raises: UnsupportedTokenType if the token is not one of the supported token classes listed above. Returns: A string represenging this token. The string can be converted back into an equivalent token object using token_from_blob. Note that any members which are set to '' will be set to None when the token is deserialized by token_from_blob. """ if isinstance(token, ClientLoginToken): return _join_token_parts('1c', token.token_string) # Check for secure auth sub type first since it is a subclass of # AuthSubToken. elif isinstance(token, SecureAuthSubToken): return _join_token_parts('1s', token.token_string, token.rsa_private_key, *token.scopes) elif isinstance(token, AuthSubToken): return _join_token_parts('1a', token.token_string, *token.scopes) elif isinstance(token, TwoLeggedOAuthRsaToken): return _join_token_parts( '1rtl', token.consumer_key, token.rsa_private_key, token.requestor_id) elif isinstance(token, TwoLeggedOAuthHmacToken): return _join_token_parts( '1htl', token.consumer_key, token.consumer_secret, token.requestor_id) # Check RSA OAuth token first since the OAuthRsaToken is a subclass of # OAuthHmacToken. elif isinstance(token, OAuthRsaToken): return _join_token_parts( '1r', token.consumer_key, token.rsa_private_key, token.token, token.token_secret, str(token.auth_state), token.next, token.verifier) elif isinstance(token, OAuthHmacToken): return _join_token_parts( '1h', token.consumer_key, token.consumer_secret, token.token, token.token_secret, str(token.auth_state), token.next, token.verifier) elif isinstance(token, OAuth2Token): return _join_token_parts( '2o', token.client_id, token.client_secret, token.scope, token.user_agent, token.auth_uri, token.token_uri, token.access_token, token.refresh_token) else: raise UnsupportedTokenType( 'Unable to serialize token of type %s' % type(token)) TokenToBlob = token_to_blob def token_from_blob(blob): """Deserializes a token string from the datastore back into a token object. Supported token classes: ClientLoginToken, AuthSubToken, SecureAuthSubToken, OAuthRsaToken, and OAuthHmacToken, TwoLeggedOAuthRsaToken, TwoLeggedOAuthHmacToken and OAuth2Token. Args: blob: string created by token_to_blob. Raises: UnsupportedTokenType if the token is not one of the supported token classes listed above. Returns: A new token object with members set to the values serialized in the blob string. Note that any members which were set to '' in the original token will now be None. """ parts = _split_token_parts(blob) if parts[0] == '1c': return ClientLoginToken(parts[1]) elif parts[0] == '1a': return AuthSubToken(parts[1], parts[2:]) elif parts[0] == '1s': return SecureAuthSubToken(parts[1], parts[2], parts[3:]) elif parts[0] == '1rtl': return TwoLeggedOAuthRsaToken(parts[1], parts[2], parts[3]) elif parts[0] == '1htl': return TwoLeggedOAuthHmacToken(parts[1], parts[2], parts[3]) elif parts[0] == '1r': auth_state = int(parts[5]) return OAuthRsaToken(parts[1], parts[2], parts[3], parts[4], auth_state, parts[6], parts[7]) elif parts[0] == '1h': auth_state = int(parts[5]) return OAuthHmacToken(parts[1], parts[2], parts[3], parts[4], auth_state, parts[6], parts[7]) elif parts[0] == '2o': return OAuth2Token(parts[1], parts[2], parts[3], parts[4], parts[5], parts[6], parts[7], parts[8]) else: raise UnsupportedTokenType( 'Unable to deserialize token with type marker of %s' % parts[0]) TokenFromBlob = token_from_blob def dump_tokens(tokens): return ','.join([token_to_blob(t) for t in tokens]) def load_tokens(blob): return [token_from_blob(s) for s in blob.split(',')] def find_scopes_for_services(service_names=None): """Creates a combined list of scope URLs for the desired services. This method searches the AUTH_SCOPES dictionary. Args: service_names: list of strings (optional) Each name must be a key in the AUTH_SCOPES dictionary. If no list is provided (None) then the resulting list will contain all scope URLs in the AUTH_SCOPES dict. Returns: A list of URL strings which are the scopes needed to access these services when requesting a token using AuthSub or OAuth. """ result_scopes = [] if service_names is None: for service_name, scopes in AUTH_SCOPES.iteritems(): result_scopes.extend(scopes) else: for service_name in service_names: result_scopes.extend(AUTH_SCOPES[service_name]) return result_scopes FindScopesForServices = find_scopes_for_services def ae_save(token, token_key): """Stores an auth token in the App Engine datastore. This is a convenience method for using the library with App Engine. Recommended usage is to associate the auth token with the current_user. If a user is signed in to the app using the App Engine users API, you can use gdata.gauth.ae_save(some_token, users.get_current_user().user_id()) If you are not using the Users API you are free to choose whatever string you would like for a token_string. Args: token: an auth token object. Must be one of ClientLoginToken, AuthSubToken, SecureAuthSubToken, OAuthRsaToken, or OAuthHmacToken (see token_to_blob). token_key: str A unique identified to be used when you want to retrieve the token. If the user is signed in to App Engine using the users API, I recommend using the user ID for the token_key: users.get_current_user().user_id() """ import gdata.alt.app_engine key_name = ''.join(('gd_auth_token', token_key)) return gdata.alt.app_engine.set_token(key_name, token_to_blob(token)) AeSave = ae_save def ae_load(token_key): """Retrieves a token object from the App Engine datastore. This is a convenience method for using the library with App Engine. See also ae_save. Args: token_key: str The unique key associated with the desired token when it was saved using ae_save. Returns: A token object if there was a token associated with the token_key or None if the key could not be found. """ import gdata.alt.app_engine key_name = ''.join(('gd_auth_token', token_key)) token_string = gdata.alt.app_engine.get_token(key_name) if token_string is not None: return token_from_blob(token_string) else: return None AeLoad = ae_load def ae_delete(token_key): """Removes the token object from the App Engine datastore.""" import gdata.alt.app_engine key_name = ''.join(('gd_auth_token', token_key)) gdata.alt.app_engine.delete_token(key_name) AeDelete = ae_delete gdata-2.0.18/samples/apps/marketplace_sample/gdata/tlslite/0000750036466300116100000000000012156625015023732 5ustar afshareng00000000000000gdata-2.0.18/samples/apps/marketplace_sample/gdata/tlslite/api.py0000750036466300116100000000562512156622362025072 0ustar afshareng00000000000000"""Import this module for easy access to TLS Lite objects. The TLS Lite API consists of classes, functions, and variables spread throughout this package. Instead of importing them individually with:: from tlslite.TLSConnection import TLSConnection from tlslite.HandshakeSettings import HandshakeSettings from tlslite.errors import * . . It's easier to do:: from tlslite.api import * This imports all the important objects (TLSConnection, Checker, HandshakeSettings, etc.) into the global namespace. In particular, it imports:: from constants import AlertLevel, AlertDescription, Fault from errors import * from Checker import Checker from HandshakeSettings import HandshakeSettings from Session import Session from SessionCache import SessionCache from SharedKeyDB import SharedKeyDB from TLSConnection import TLSConnection from VerifierDB import VerifierDB from X509 import X509 from X509CertChain import X509CertChain from integration.HTTPTLSConnection import HTTPTLSConnection from integration.POP3_TLS import POP3_TLS from integration.IMAP4_TLS import IMAP4_TLS from integration.SMTP_TLS import SMTP_TLS from integration.XMLRPCTransport import XMLRPCTransport from integration.TLSSocketServerMixIn import TLSSocketServerMixIn from integration.TLSAsyncDispatcherMixIn import TLSAsyncDispatcherMixIn from integration.TLSTwistedProtocolWrapper import TLSTwistedProtocolWrapper from utils.cryptomath import cryptlibpyLoaded, m2cryptoLoaded, gmpyLoaded, pycryptoLoaded, prngName from utils.keyfactory import generateRSAKey, parsePEMKey, parseXMLKey, parseAsPublicKey, parsePrivateKey """ from constants import AlertLevel, AlertDescription, Fault from errors import * from Checker import Checker from HandshakeSettings import HandshakeSettings from Session import Session from SessionCache import SessionCache from SharedKeyDB import SharedKeyDB from TLSConnection import TLSConnection from VerifierDB import VerifierDB from X509 import X509 from X509CertChain import X509CertChain from integration.HTTPTLSConnection import HTTPTLSConnection from integration.TLSSocketServerMixIn import TLSSocketServerMixIn from integration.TLSAsyncDispatcherMixIn import TLSAsyncDispatcherMixIn from integration.POP3_TLS import POP3_TLS from integration.IMAP4_TLS import IMAP4_TLS from integration.SMTP_TLS import SMTP_TLS from integration.XMLRPCTransport import XMLRPCTransport try: import twisted del(twisted) from integration.TLSTwistedProtocolWrapper import TLSTwistedProtocolWrapper except ImportError: pass from utils.cryptomath import cryptlibpyLoaded, m2cryptoLoaded, gmpyLoaded, \ pycryptoLoaded, prngName from utils.keyfactory import generateRSAKey, parsePEMKey, parseXMLKey, \ parseAsPublicKey, parsePrivateKey gdata-2.0.18/samples/apps/marketplace_sample/gdata/tlslite/mathtls.py0000750036466300116100000002657712156622362026006 0ustar afshareng00000000000000"""Miscellaneous helper functions.""" from utils.compat import * from utils.cryptomath import * import hmac import md5 import sha #1024, 1536, 2048, 3072, 4096, 6144, and 8192 bit groups] goodGroupParameters = [(2,0xEEAF0AB9ADB38DD69C33F80AFA8FC5E86072618775FF3C0B9EA2314C9C256576D674DF7496EA81D3383B4813D692C6E0E0D5D8E250B98BE48E495C1D6089DAD15DC7D7B46154D6B6CE8EF4AD69B15D4982559B297BCF1885C529F566660E57EC68EDBC3C05726CC02FD4CBF4976EAA9AFD5138FE8376435B9FC61D2FC0EB06E3),\ (2,0x9DEF3CAFB939277AB1F12A8617A47BBBDBA51DF499AC4C80BEEEA9614B19CC4D5F4F5F556E27CBDE51C6A94BE4607A291558903BA0D0F84380B655BB9A22E8DCDF028A7CEC67F0D08134B1C8B97989149B609E0BE3BAB63D47548381DBC5B1FC764E3F4B53DD9DA1158BFD3E2B9C8CF56EDF019539349627DB2FD53D24B7C48665772E437D6C7F8CE442734AF7CCB7AE837C264AE3A9BEB87F8A2FE9B8B5292E5A021FFF5E91479E8CE7A28C2442C6F315180F93499A234DCF76E3FED135F9BB),\ (2,0xAC6BDB41324A9A9BF166DE5E1389582FAF72B6651987EE07FC3192943DB56050A37329CBB4A099ED8193E0757767A13DD52312AB4B03310DCD7F48A9DA04FD50E8083969EDB767B0CF6095179A163AB3661A05FBD5FAAAE82918A9962F0B93B855F97993EC975EEAA80D740ADBF4FF747359D041D5C33EA71D281E446B14773BCA97B43A23FB801676BD207A436C6481F1D2B9078717461A5B9D32E688F87748544523B524B0D57D5EA77A2775D2ECFA032CFBDBF52FB3786160279004E57AE6AF874E7303CE53299CCC041C7BC308D82A5698F3A8D0C38271AE35F8E9DBFBB694B5C803D89F7AE435DE236D525F54759B65E372FCD68EF20FA7111F9E4AFF73),\ (2,0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A93AD2CAFFFFFFFFFFFFFFFF),\ (5,0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934063199FFFFFFFFFFFFFFFF),\ (5,0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C93402849236C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BDF8FF9406AD9E530EE5DB382F413001AEB06A53ED9027D831179727B0865A8918DA3EDBEBCF9B14ED44CE6CBACED4BB1BDB7F1447E6CC254B332051512BD7AF426FB8F401378CD2BF5983CA01C64B92ECF032EA15D1721D03F482D7CE6E74FEF6D55E702F46980C82B5A84031900B1C9E59E7C97FBEC7E8F323A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AACC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE32806A1D58BB7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55CDA56C9EC2EF29632387FE8D76E3C0468043E8F663F4860EE12BF2D5B0B7474D6E694F91E6DCC4024FFFFFFFFFFFFFFFF),\ (5,0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C93402849236C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BDF8FF9406AD9E530EE5DB382F413001AEB06A53ED9027D831179727B0865A8918DA3EDBEBCF9B14ED44CE6CBACED4BB1BDB7F1447E6CC254B332051512BD7AF426FB8F401378CD2BF5983CA01C64B92ECF032EA15D1721D03F482D7CE6E74FEF6D55E702F46980C82B5A84031900B1C9E59E7C97FBEC7E8F323A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AACC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE32806A1D58BB7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55CDA56C9EC2EF29632387FE8D76E3C0468043E8F663F4860EE12BF2D5B0B7474D6E694F91E6DBE115974A3926F12FEE5E438777CB6A932DF8CD8BEC4D073B931BA3BC832B68D9DD300741FA7BF8AFC47ED2576F6936BA424663AAB639C5AE4F5683423B4742BF1C978238F16CBE39D652DE3FDB8BEFC848AD922222E04A4037C0713EB57A81A23F0C73473FC646CEA306B4BCBC8862F8385DDFA9D4B7FA2C087E879683303ED5BDD3A062B3CF5B3A278A66D2A13F83F44F82DDF310EE074AB6A364597E899A0255DC164F31CC50846851DF9AB48195DED7EA1B1D510BD7EE74D73FAF36BC31ECFA268359046F4EB879F924009438B481C6CD7889A002ED5EE382BC9190DA6FC026E479558E4475677E9AA9E3050E2765694DFC81F56E880B96E7160C980DD98EDD3DFFFFFFFFFFFFFFFFF)] def P_hash(hashModule, secret, seed, length): bytes = createByteArrayZeros(length) secret = bytesToString(secret) seed = bytesToString(seed) A = seed index = 0 while 1: A = hmac.HMAC(secret, A, hashModule).digest() output = hmac.HMAC(secret, A+seed, hashModule).digest() for c in output: if index >= length: return bytes bytes[index] = ord(c) index += 1 return bytes def PRF(secret, label, seed, length): #Split the secret into left and right halves S1 = secret[ : int(math.ceil(len(secret)/2.0))] S2 = secret[ int(math.floor(len(secret)/2.0)) : ] #Run the left half through P_MD5 and the right half through P_SHA1 p_md5 = P_hash(md5, S1, concatArrays(stringToBytes(label), seed), length) p_sha1 = P_hash(sha, S2, concatArrays(stringToBytes(label), seed), length) #XOR the output values and return the result for x in range(length): p_md5[x] ^= p_sha1[x] return p_md5 def PRF_SSL(secret, seed, length): secretStr = bytesToString(secret) seedStr = bytesToString(seed) bytes = createByteArrayZeros(length) index = 0 for x in range(26): A = chr(ord('A')+x) * (x+1) # 'A', 'BB', 'CCC', etc.. input = secretStr + sha.sha(A + secretStr + seedStr).digest() output = md5.md5(input).digest() for c in output: if index >= length: return bytes bytes[index] = ord(c) index += 1 return bytes def makeX(salt, username, password): if len(username)>=256: raise ValueError("username too long") if len(salt)>=256: raise ValueError("salt too long") return stringToNumber(sha.sha(salt + sha.sha(username + ":" + password)\ .digest()).digest()) #This function is used by VerifierDB.makeVerifier def makeVerifier(username, password, bits): bitsIndex = {1024:0, 1536:1, 2048:2, 3072:3, 4096:4, 6144:5, 8192:6}[bits] g,N = goodGroupParameters[bitsIndex] salt = bytesToString(getRandomBytes(16)) x = makeX(salt, username, password) verifier = powMod(g, x, N) return N, g, salt, verifier def PAD(n, x): nLength = len(numberToString(n)) s = numberToString(x) if len(s) < nLength: s = ("\0" * (nLength-len(s))) + s return s def makeU(N, A, B): return stringToNumber(sha.sha(PAD(N, A) + PAD(N, B)).digest()) def makeK(N, g): return stringToNumber(sha.sha(numberToString(N) + PAD(N, g)).digest()) """ MAC_SSL Modified from Python HMAC by Trevor """ class MAC_SSL: """MAC_SSL class. This supports the API for Cryptographic Hash Functions (PEP 247). """ def __init__(self, key, msg = None, digestmod = None): """Create a new MAC_SSL object. key: key for the keyed hash object. msg: Initial input for the hash, if provided. digestmod: A module supporting PEP 247. Defaults to the md5 module. """ if digestmod is None: import md5 digestmod = md5 if key == None: #TREVNEW - for faster copying return #TREVNEW self.digestmod = digestmod self.outer = digestmod.new() self.inner = digestmod.new() self.digest_size = digestmod.digest_size ipad = "\x36" * 40 opad = "\x5C" * 40 self.inner.update(key) self.inner.update(ipad) self.outer.update(key) self.outer.update(opad) if msg is not None: self.update(msg) def update(self, msg): """Update this hashing object with the string msg. """ self.inner.update(msg) def copy(self): """Return a separate copy of this hashing object. An update to this copy won't affect the original object. """ other = MAC_SSL(None) #TREVNEW - for faster copying other.digest_size = self.digest_size #TREVNEW other.digestmod = self.digestmod other.inner = self.inner.copy() other.outer = self.outer.copy() return other def digest(self): """Return the hash value of this hashing object. This returns a string containing 8-bit data. The object is not altered in any way by this function; you can continue updating the object after calling this function. """ h = self.outer.copy() h.update(self.inner.digest()) return h.digest() def hexdigest(self): """Like digest(), but returns a string of hexadecimal digits instead. """ return "".join([hex(ord(x))[2:].zfill(2) for x in tuple(self.digest())]) gdata-2.0.18/samples/apps/marketplace_sample/gdata/tlslite/SharedKeyDB.py0000750036466300116100000000357212156622362026405 0ustar afshareng00000000000000"""Class for storing shared keys.""" from utils.cryptomath import * from utils.compat import * from mathtls import * from Session import Session from BaseDB import BaseDB class SharedKeyDB(BaseDB): """This class represent an in-memory or on-disk database of shared keys. A SharedKeyDB can be passed to a server handshake function to authenticate a client based on one of the shared keys. This class is thread-safe. """ def __init__(self, filename=None): """Create a new SharedKeyDB. @type filename: str @param filename: Filename for an on-disk database, or None for an in-memory database. If the filename already exists, follow this with a call to open(). To create a new on-disk database, follow this with a call to create(). """ BaseDB.__init__(self, filename, "shared key") def _getItem(self, username, valueStr): session = Session() session._createSharedKey(username, valueStr) return session def __setitem__(self, username, sharedKey): """Add a shared key to the database. @type username: str @param username: The username to associate the shared key with. Must be less than or equal to 16 characters in length, and must not already be in the database. @type sharedKey: str @param sharedKey: The shared key to add. Must be less than 48 characters in length. """ BaseDB.__setitem__(self, username, sharedKey) def _setItem(self, username, value): if len(username)>16: raise ValueError("username too long") if len(value)>=48: raise ValueError("shared key too long") return value def _checkItem(self, value, username, param): newSession = self._getItem(username, param) return value.masterSecret == newSession.masterSecretgdata-2.0.18/samples/apps/marketplace_sample/gdata/tlslite/VerifierDB.py0000750036466300116100000000604012156622362026272 0ustar afshareng00000000000000"""Class for storing SRP password verifiers.""" from utils.cryptomath import * from utils.compat import * import mathtls from BaseDB import BaseDB class VerifierDB(BaseDB): """This class represent an in-memory or on-disk database of SRP password verifiers. A VerifierDB can be passed to a server handshake to authenticate a client based on one of the verifiers. This class is thread-safe. """ def __init__(self, filename=None): """Create a new VerifierDB instance. @type filename: str @param filename: Filename for an on-disk database, or None for an in-memory database. If the filename already exists, follow this with a call to open(). To create a new on-disk database, follow this with a call to create(). """ BaseDB.__init__(self, filename, "verifier") def _getItem(self, username, valueStr): (N, g, salt, verifier) = valueStr.split(" ") N = base64ToNumber(N) g = base64ToNumber(g) salt = base64ToString(salt) verifier = base64ToNumber(verifier) return (N, g, salt, verifier) def __setitem__(self, username, verifierEntry): """Add a verifier entry to the database. @type username: str @param username: The username to associate the verifier with. Must be less than 256 characters in length. Must not already be in the database. @type verifierEntry: tuple @param verifierEntry: The verifier entry to add. Use L{tlslite.VerifierDB.VerifierDB.makeVerifier} to create a verifier entry. """ BaseDB.__setitem__(self, username, verifierEntry) def _setItem(self, username, value): if len(username)>=256: raise ValueError("username too long") N, g, salt, verifier = value N = numberToBase64(N) g = numberToBase64(g) salt = stringToBase64(salt) verifier = numberToBase64(verifier) valueStr = " ".join( (N, g, salt, verifier) ) return valueStr def _checkItem(self, value, username, param): (N, g, salt, verifier) = value x = mathtls.makeX(salt, username, param) v = powMod(g, x, N) return (verifier == v) def makeVerifier(username, password, bits): """Create a verifier entry which can be stored in a VerifierDB. @type username: str @param username: The username for this verifier. Must be less than 256 characters in length. @type password: str @param password: The password for this verifier. @type bits: int @param bits: This values specifies which SRP group parameters to use. It must be one of (1024, 1536, 2048, 3072, 4096, 6144, 8192). Larger values are more secure but slower. 2048 is a good compromise between safety and speed. @rtype: tuple @return: A tuple which may be stored in a VerifierDB. """ return mathtls.makeVerifier(username, password, bits) makeVerifier = staticmethod(makeVerifier)gdata-2.0.18/samples/apps/marketplace_sample/gdata/tlslite/integration/0000750036466300116100000000000012156625015026255 5ustar afshareng00000000000000gdata-2.0.18/samples/apps/marketplace_sample/gdata/tlslite/integration/HTTPTLSConnection.py0000750036466300116100000001501412156622362032017 0ustar afshareng00000000000000"""TLS Lite + httplib.""" import socket import httplib from gdata.tlslite.TLSConnection import TLSConnection from gdata.tlslite.integration.ClientHelper import ClientHelper class HTTPBaseTLSConnection(httplib.HTTPConnection): """This abstract class provides a framework for adding TLS support to httplib.""" default_port = 443 def __init__(self, host, port=None, strict=None): if strict == None: #Python 2.2 doesn't support strict httplib.HTTPConnection.__init__(self, host, port) else: httplib.HTTPConnection.__init__(self, host, port, strict) def connect(self): sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) if hasattr(sock, 'settimeout'): sock.settimeout(10) sock.connect((self.host, self.port)) #Use a TLSConnection to emulate a socket self.sock = TLSConnection(sock) #When httplib closes this, close the socket self.sock.closeSocket = True self._handshake(self.sock) def _handshake(self, tlsConnection): """Called to perform some sort of handshake. This method must be overridden in a subclass to do some type of handshake. This method will be called after the socket has been connected but before any data has been sent. If this method does not raise an exception, the TLS connection will be considered valid. This method may (or may not) be called every time an HTTP request is performed, depending on whether the underlying HTTP connection is persistent. @type tlsConnection: L{tlslite.TLSConnection.TLSConnection} @param tlsConnection: The connection to perform the handshake on. """ raise NotImplementedError() class HTTPTLSConnection(HTTPBaseTLSConnection, ClientHelper): """This class extends L{HTTPBaseTLSConnection} to support the common types of handshaking.""" def __init__(self, host, port=None, username=None, password=None, sharedKey=None, certChain=None, privateKey=None, cryptoID=None, protocol=None, x509Fingerprint=None, x509TrustList=None, x509CommonName=None, settings = None): """Create a new HTTPTLSConnection. For client authentication, use one of these argument combinations: - username, password (SRP) - username, sharedKey (shared-key) - certChain, privateKey (certificate) For server authentication, you can either rely on the implicit mutual authentication performed by SRP or shared-keys, or you can do certificate-based server authentication with one of these argument combinations: - cryptoID[, protocol] (requires cryptoIDlib) - x509Fingerprint - x509TrustList[, x509CommonName] (requires cryptlib_py) Certificate-based server authentication is compatible with SRP or certificate-based client authentication. It is not compatible with shared-keys. The constructor does not perform the TLS handshake itself, but simply stores these arguments for later. The handshake is performed only when this class needs to connect with the server. Thus you should be prepared to handle TLS-specific exceptions when calling methods inherited from L{httplib.HTTPConnection} such as request(), connect(), and send(). See the client handshake functions in L{tlslite.TLSConnection.TLSConnection} for details on which exceptions might be raised. @type host: str @param host: Server to connect to. @type port: int @param port: Port to connect to. @type username: str @param username: SRP or shared-key username. Requires the 'password' or 'sharedKey' argument. @type password: str @param password: SRP password for mutual authentication. Requires the 'username' argument. @type sharedKey: str @param sharedKey: Shared key for mutual authentication. Requires the 'username' argument. @type certChain: L{tlslite.X509CertChain.X509CertChain} or L{cryptoIDlib.CertChain.CertChain} @param certChain: Certificate chain for client authentication. Requires the 'privateKey' argument. Excludes the SRP or shared-key related arguments. @type privateKey: L{tlslite.utils.RSAKey.RSAKey} @param privateKey: Private key for client authentication. Requires the 'certChain' argument. Excludes the SRP or shared-key related arguments. @type cryptoID: str @param cryptoID: cryptoID for server authentication. Mutually exclusive with the 'x509...' arguments. @type protocol: str @param protocol: cryptoID protocol URI for server authentication. Requires the 'cryptoID' argument. @type x509Fingerprint: str @param x509Fingerprint: Hex-encoded X.509 fingerprint for server authentication. Mutually exclusive with the 'cryptoID' and 'x509TrustList' arguments. @type x509TrustList: list of L{tlslite.X509.X509} @param x509TrustList: A list of trusted root certificates. The other party must present a certificate chain which extends to one of these root certificates. The cryptlib_py module must be installed to use this parameter. Mutually exclusive with the 'cryptoID' and 'x509Fingerprint' arguments. @type x509CommonName: str @param x509CommonName: The end-entity certificate's 'CN' field must match this value. For a web server, this is typically a server name such as 'www.amazon.com'. Mutually exclusive with the 'cryptoID' and 'x509Fingerprint' arguments. Requires the 'x509TrustList' argument. @type settings: L{tlslite.HandshakeSettings.HandshakeSettings} @param settings: Various settings which can be used to control the ciphersuites, certificate types, and SSL/TLS versions offered by the client. """ HTTPBaseTLSConnection.__init__(self, host, port) ClientHelper.__init__(self, username, password, sharedKey, certChain, privateKey, cryptoID, protocol, x509Fingerprint, x509TrustList, x509CommonName, settings) def _handshake(self, tlsConnection): ClientHelper._handshake(self, tlsConnection) gdata-2.0.18/samples/apps/marketplace_sample/gdata/tlslite/integration/ClientHelper.py0000750036466300116100000001555512156622362031225 0ustar afshareng00000000000000""" A helper class for using TLS Lite with stdlib clients (httplib, xmlrpclib, imaplib, poplib). """ from gdata.tlslite.Checker import Checker class ClientHelper: """This is a helper class used to integrate TLS Lite with various TLS clients (e.g. poplib, smtplib, httplib, etc.)""" def __init__(self, username=None, password=None, sharedKey=None, certChain=None, privateKey=None, cryptoID=None, protocol=None, x509Fingerprint=None, x509TrustList=None, x509CommonName=None, settings = None): """ For client authentication, use one of these argument combinations: - username, password (SRP) - username, sharedKey (shared-key) - certChain, privateKey (certificate) For server authentication, you can either rely on the implicit mutual authentication performed by SRP or shared-keys, or you can do certificate-based server authentication with one of these argument combinations: - cryptoID[, protocol] (requires cryptoIDlib) - x509Fingerprint - x509TrustList[, x509CommonName] (requires cryptlib_py) Certificate-based server authentication is compatible with SRP or certificate-based client authentication. It is not compatible with shared-keys. The constructor does not perform the TLS handshake itself, but simply stores these arguments for later. The handshake is performed only when this class needs to connect with the server. Then you should be prepared to handle TLS-specific exceptions. See the client handshake functions in L{tlslite.TLSConnection.TLSConnection} for details on which exceptions might be raised. @type username: str @param username: SRP or shared-key username. Requires the 'password' or 'sharedKey' argument. @type password: str @param password: SRP password for mutual authentication. Requires the 'username' argument. @type sharedKey: str @param sharedKey: Shared key for mutual authentication. Requires the 'username' argument. @type certChain: L{tlslite.X509CertChain.X509CertChain} or L{cryptoIDlib.CertChain.CertChain} @param certChain: Certificate chain for client authentication. Requires the 'privateKey' argument. Excludes the SRP or shared-key related arguments. @type privateKey: L{tlslite.utils.RSAKey.RSAKey} @param privateKey: Private key for client authentication. Requires the 'certChain' argument. Excludes the SRP or shared-key related arguments. @type cryptoID: str @param cryptoID: cryptoID for server authentication. Mutually exclusive with the 'x509...' arguments. @type protocol: str @param protocol: cryptoID protocol URI for server authentication. Requires the 'cryptoID' argument. @type x509Fingerprint: str @param x509Fingerprint: Hex-encoded X.509 fingerprint for server authentication. Mutually exclusive with the 'cryptoID' and 'x509TrustList' arguments. @type x509TrustList: list of L{tlslite.X509.X509} @param x509TrustList: A list of trusted root certificates. The other party must present a certificate chain which extends to one of these root certificates. The cryptlib_py module must be installed to use this parameter. Mutually exclusive with the 'cryptoID' and 'x509Fingerprint' arguments. @type x509CommonName: str @param x509CommonName: The end-entity certificate's 'CN' field must match this value. For a web server, this is typically a server name such as 'www.amazon.com'. Mutually exclusive with the 'cryptoID' and 'x509Fingerprint' arguments. Requires the 'x509TrustList' argument. @type settings: L{tlslite.HandshakeSettings.HandshakeSettings} @param settings: Various settings which can be used to control the ciphersuites, certificate types, and SSL/TLS versions offered by the client. """ self.username = None self.password = None self.sharedKey = None self.certChain = None self.privateKey = None self.checker = None #SRP Authentication if username and password and not \ (sharedKey or certChain or privateKey): self.username = username self.password = password #Shared Key Authentication elif username and sharedKey and not \ (password or certChain or privateKey): self.username = username self.sharedKey = sharedKey #Certificate Chain Authentication elif certChain and privateKey and not \ (username or password or sharedKey): self.certChain = certChain self.privateKey = privateKey #No Authentication elif not password and not username and not \ sharedKey and not certChain and not privateKey: pass else: raise ValueError("Bad parameters") #Authenticate the server based on its cryptoID or fingerprint if sharedKey and (cryptoID or protocol or x509Fingerprint): raise ValueError("Can't use shared keys with other forms of"\ "authentication") self.checker = Checker(cryptoID, protocol, x509Fingerprint, x509TrustList, x509CommonName) self.settings = settings self.tlsSession = None def _handshake(self, tlsConnection): if self.username and self.password: tlsConnection.handshakeClientSRP(username=self.username, password=self.password, checker=self.checker, settings=self.settings, session=self.tlsSession) elif self.username and self.sharedKey: tlsConnection.handshakeClientSharedKey(username=self.username, sharedKey=self.sharedKey, settings=self.settings) else: tlsConnection.handshakeClientCert(certChain=self.certChain, privateKey=self.privateKey, checker=self.checker, settings=self.settings, session=self.tlsSession) self.tlsSession = tlsConnection.session gdata-2.0.18/samples/apps/marketplace_sample/gdata/tlslite/integration/TLSSocketServerMixIn.py0000750036466300116100000000423312156622362032605 0ustar afshareng00000000000000"""TLS Lite + SocketServer.""" from gdata.tlslite.TLSConnection import TLSConnection class TLSSocketServerMixIn: """ This class can be mixed in with any L{SocketServer.TCPServer} to add TLS support. To use this class, define a new class that inherits from it and some L{SocketServer.TCPServer} (with the mix-in first). Then implement the handshake() method, doing some sort of server handshake on the connection argument. If the handshake method returns True, the RequestHandler will be triggered. Below is a complete example of a threaded HTTPS server:: from SocketServer import * from BaseHTTPServer import * from SimpleHTTPServer import * from tlslite.api import * s = open("./serverX509Cert.pem").read() x509 = X509() x509.parse(s) certChain = X509CertChain([x509]) s = open("./serverX509Key.pem").read() privateKey = parsePEMKey(s, private=True) sessionCache = SessionCache() class MyHTTPServer(ThreadingMixIn, TLSSocketServerMixIn, HTTPServer): def handshake(self, tlsConnection): try: tlsConnection.handshakeServer(certChain=certChain, privateKey=privateKey, sessionCache=sessionCache) tlsConnection.ignoreAbruptClose = True return True except TLSError, error: print "Handshake failure:", str(error) return False httpd = MyHTTPServer(('localhost', 443), SimpleHTTPRequestHandler) httpd.serve_forever() """ def finish_request(self, sock, client_address): tlsConnection = TLSConnection(sock) if self.handshake(tlsConnection) == True: self.RequestHandlerClass(tlsConnection, client_address, self) tlsConnection.close() #Implement this method to do some form of handshaking. Return True #if the handshake finishes properly and the request is authorized. def handshake(self, tlsConnection): raise NotImplementedError() gdata-2.0.18/samples/apps/marketplace_sample/gdata/tlslite/integration/POP3_TLS.py0000750036466300116100000001253212156622362030102 0ustar afshareng00000000000000"""TLS Lite + poplib.""" import socket from poplib import POP3 from gdata.tlslite.TLSConnection import TLSConnection from gdata.tlslite.integration.ClientHelper import ClientHelper # POP TLS PORT POP3_TLS_PORT = 995 class POP3_TLS(POP3, ClientHelper): """This class extends L{poplib.POP3} with TLS support.""" def __init__(self, host, port = POP3_TLS_PORT, username=None, password=None, sharedKey=None, certChain=None, privateKey=None, cryptoID=None, protocol=None, x509Fingerprint=None, x509TrustList=None, x509CommonName=None, settings=None): """Create a new POP3_TLS. For client authentication, use one of these argument combinations: - username, password (SRP) - username, sharedKey (shared-key) - certChain, privateKey (certificate) For server authentication, you can either rely on the implicit mutual authentication performed by SRP or shared-keys, or you can do certificate-based server authentication with one of these argument combinations: - cryptoID[, protocol] (requires cryptoIDlib) - x509Fingerprint - x509TrustList[, x509CommonName] (requires cryptlib_py) Certificate-based server authentication is compatible with SRP or certificate-based client authentication. It is not compatible with shared-keys. The caller should be prepared to handle TLS-specific exceptions. See the client handshake functions in L{tlslite.TLSConnection.TLSConnection} for details on which exceptions might be raised. @type host: str @param host: Server to connect to. @type port: int @param port: Port to connect to. @type username: str @param username: SRP or shared-key username. Requires the 'password' or 'sharedKey' argument. @type password: str @param password: SRP password for mutual authentication. Requires the 'username' argument. @type sharedKey: str @param sharedKey: Shared key for mutual authentication. Requires the 'username' argument. @type certChain: L{tlslite.X509CertChain.X509CertChain} or L{cryptoIDlib.CertChain.CertChain} @param certChain: Certificate chain for client authentication. Requires the 'privateKey' argument. Excludes the SRP or shared-key related arguments. @type privateKey: L{tlslite.utils.RSAKey.RSAKey} @param privateKey: Private key for client authentication. Requires the 'certChain' argument. Excludes the SRP or shared-key related arguments. @type cryptoID: str @param cryptoID: cryptoID for server authentication. Mutually exclusive with the 'x509...' arguments. @type protocol: str @param protocol: cryptoID protocol URI for server authentication. Requires the 'cryptoID' argument. @type x509Fingerprint: str @param x509Fingerprint: Hex-encoded X.509 fingerprint for server authentication. Mutually exclusive with the 'cryptoID' and 'x509TrustList' arguments. @type x509TrustList: list of L{tlslite.X509.X509} @param x509TrustList: A list of trusted root certificates. The other party must present a certificate chain which extends to one of these root certificates. The cryptlib_py module must be installed to use this parameter. Mutually exclusive with the 'cryptoID' and 'x509Fingerprint' arguments. @type x509CommonName: str @param x509CommonName: The end-entity certificate's 'CN' field must match this value. For a web server, this is typically a server name such as 'www.amazon.com'. Mutually exclusive with the 'cryptoID' and 'x509Fingerprint' arguments. Requires the 'x509TrustList' argument. @type settings: L{tlslite.HandshakeSettings.HandshakeSettings} @param settings: Various settings which can be used to control the ciphersuites, certificate types, and SSL/TLS versions offered by the client. """ self.host = host self.port = port msg = "getaddrinfo returns an empty list" self.sock = None for res in socket.getaddrinfo(self.host, self.port, 0, socket.SOCK_STREAM): af, socktype, proto, canonname, sa = res try: self.sock = socket.socket(af, socktype, proto) self.sock.connect(sa) except socket.error, msg: if self.sock: self.sock.close() self.sock = None continue break if not self.sock: raise socket.error, msg ### New code below (all else copied from poplib) ClientHelper.__init__(self, username, password, sharedKey, certChain, privateKey, cryptoID, protocol, x509Fingerprint, x509TrustList, x509CommonName, settings) self.sock = TLSConnection(self.sock) self.sock.closeSocket = True ClientHelper._handshake(self, self.sock) ### self.file = self.sock.makefile('rb') self._debugging = 0 self.welcome = self._getresp() gdata-2.0.18/samples/apps/marketplace_sample/gdata/tlslite/integration/__init__.py0000750036466300116100000000071212156622362030373 0ustar afshareng00000000000000"""Classes for integrating TLS Lite with other packages.""" __all__ = ["AsyncStateMachine", "HTTPTLSConnection", "POP3_TLS", "IMAP4_TLS", "SMTP_TLS", "XMLRPCTransport", "TLSSocketServerMixIn", "TLSAsyncDispatcherMixIn", "TLSTwistedProtocolWrapper"] try: import twisted del twisted except ImportError: del __all__[__all__.index("TLSTwistedProtocolWrapper")] gdata-2.0.18/samples/apps/marketplace_sample/gdata/tlslite/integration/XMLRPCTransport.py0000750036466300116100000001326412156622362031564 0ustar afshareng00000000000000"""TLS Lite + xmlrpclib.""" import xmlrpclib import httplib from gdata.tlslite.integration.HTTPTLSConnection import HTTPTLSConnection from gdata.tlslite.integration.ClientHelper import ClientHelper class XMLRPCTransport(xmlrpclib.Transport, ClientHelper): """Handles an HTTPS transaction to an XML-RPC server.""" def __init__(self, username=None, password=None, sharedKey=None, certChain=None, privateKey=None, cryptoID=None, protocol=None, x509Fingerprint=None, x509TrustList=None, x509CommonName=None, settings=None): """Create a new XMLRPCTransport. An instance of this class can be passed to L{xmlrpclib.ServerProxy} to use TLS with XML-RPC calls:: from tlslite.api import XMLRPCTransport from xmlrpclib import ServerProxy transport = XMLRPCTransport(user="alice", password="abra123") server = ServerProxy("https://localhost", transport) For client authentication, use one of these argument combinations: - username, password (SRP) - username, sharedKey (shared-key) - certChain, privateKey (certificate) For server authentication, you can either rely on the implicit mutual authentication performed by SRP or shared-keys, or you can do certificate-based server authentication with one of these argument combinations: - cryptoID[, protocol] (requires cryptoIDlib) - x509Fingerprint - x509TrustList[, x509CommonName] (requires cryptlib_py) Certificate-based server authentication is compatible with SRP or certificate-based client authentication. It is not compatible with shared-keys. The constructor does not perform the TLS handshake itself, but simply stores these arguments for later. The handshake is performed only when this class needs to connect with the server. Thus you should be prepared to handle TLS-specific exceptions when calling methods of L{xmlrpclib.ServerProxy}. See the client handshake functions in L{tlslite.TLSConnection.TLSConnection} for details on which exceptions might be raised. @type username: str @param username: SRP or shared-key username. Requires the 'password' or 'sharedKey' argument. @type password: str @param password: SRP password for mutual authentication. Requires the 'username' argument. @type sharedKey: str @param sharedKey: Shared key for mutual authentication. Requires the 'username' argument. @type certChain: L{tlslite.X509CertChain.X509CertChain} or L{cryptoIDlib.CertChain.CertChain} @param certChain: Certificate chain for client authentication. Requires the 'privateKey' argument. Excludes the SRP or shared-key related arguments. @type privateKey: L{tlslite.utils.RSAKey.RSAKey} @param privateKey: Private key for client authentication. Requires the 'certChain' argument. Excludes the SRP or shared-key related arguments. @type cryptoID: str @param cryptoID: cryptoID for server authentication. Mutually exclusive with the 'x509...' arguments. @type protocol: str @param protocol: cryptoID protocol URI for server authentication. Requires the 'cryptoID' argument. @type x509Fingerprint: str @param x509Fingerprint: Hex-encoded X.509 fingerprint for server authentication. Mutually exclusive with the 'cryptoID' and 'x509TrustList' arguments. @type x509TrustList: list of L{tlslite.X509.X509} @param x509TrustList: A list of trusted root certificates. The other party must present a certificate chain which extends to one of these root certificates. The cryptlib_py module must be installed to use this parameter. Mutually exclusive with the 'cryptoID' and 'x509Fingerprint' arguments. @type x509CommonName: str @param x509CommonName: The end-entity certificate's 'CN' field must match this value. For a web server, this is typically a server name such as 'www.amazon.com'. Mutually exclusive with the 'cryptoID' and 'x509Fingerprint' arguments. Requires the 'x509TrustList' argument. @type settings: L{tlslite.HandshakeSettings.HandshakeSettings} @param settings: Various settings which can be used to control the ciphersuites, certificate types, and SSL/TLS versions offered by the client. """ ClientHelper.__init__(self, username, password, sharedKey, certChain, privateKey, cryptoID, protocol, x509Fingerprint, x509TrustList, x509CommonName, settings) def make_connection(self, host): # create a HTTPS connection object from a host descriptor host, extra_headers, x509 = self.get_host_info(host) http = HTTPTLSConnection(host, None, self.username, self.password, self.sharedKey, self.certChain, self.privateKey, self.checker.cryptoID, self.checker.protocol, self.checker.x509Fingerprint, self.checker.x509TrustList, self.checker.x509CommonName, self.settings) http2 = httplib.HTTP() http2._setup(http) return http2 gdata-2.0.18/samples/apps/marketplace_sample/gdata/tlslite/integration/SMTP_TLS.py0000750036466300116100000001120312156622362030136 0ustar afshareng00000000000000"""TLS Lite + smtplib.""" from smtplib import SMTP from gdata.tlslite.TLSConnection import TLSConnection from gdata.tlslite.integration.ClientHelper import ClientHelper class SMTP_TLS(SMTP): """This class extends L{smtplib.SMTP} with TLS support.""" def starttls(self, username=None, password=None, sharedKey=None, certChain=None, privateKey=None, cryptoID=None, protocol=None, x509Fingerprint=None, x509TrustList=None, x509CommonName=None, settings=None): """Puts the connection to the SMTP server into TLS mode. If the server supports TLS, this will encrypt the rest of the SMTP session. For client authentication, use one of these argument combinations: - username, password (SRP) - username, sharedKey (shared-key) - certChain, privateKey (certificate) For server authentication, you can either rely on the implicit mutual authentication performed by SRP or shared-keys, or you can do certificate-based server authentication with one of these argument combinations: - cryptoID[, protocol] (requires cryptoIDlib) - x509Fingerprint - x509TrustList[, x509CommonName] (requires cryptlib_py) Certificate-based server authentication is compatible with SRP or certificate-based client authentication. It is not compatible with shared-keys. The caller should be prepared to handle TLS-specific exceptions. See the client handshake functions in L{tlslite.TLSConnection.TLSConnection} for details on which exceptions might be raised. @type username: str @param username: SRP or shared-key username. Requires the 'password' or 'sharedKey' argument. @type password: str @param password: SRP password for mutual authentication. Requires the 'username' argument. @type sharedKey: str @param sharedKey: Shared key for mutual authentication. Requires the 'username' argument. @type certChain: L{tlslite.X509CertChain.X509CertChain} or L{cryptoIDlib.CertChain.CertChain} @param certChain: Certificate chain for client authentication. Requires the 'privateKey' argument. Excludes the SRP or shared-key related arguments. @type privateKey: L{tlslite.utils.RSAKey.RSAKey} @param privateKey: Private key for client authentication. Requires the 'certChain' argument. Excludes the SRP or shared-key related arguments. @type cryptoID: str @param cryptoID: cryptoID for server authentication. Mutually exclusive with the 'x509...' arguments. @type protocol: str @param protocol: cryptoID protocol URI for server authentication. Requires the 'cryptoID' argument. @type x509Fingerprint: str @param x509Fingerprint: Hex-encoded X.509 fingerprint for server authentication. Mutually exclusive with the 'cryptoID' and 'x509TrustList' arguments. @type x509TrustList: list of L{tlslite.X509.X509} @param x509TrustList: A list of trusted root certificates. The other party must present a certificate chain which extends to one of these root certificates. The cryptlib_py module must be installed to use this parameter. Mutually exclusive with the 'cryptoID' and 'x509Fingerprint' arguments. @type x509CommonName: str @param x509CommonName: The end-entity certificate's 'CN' field must match this value. For a web server, this is typically a server name such as 'www.amazon.com'. Mutually exclusive with the 'cryptoID' and 'x509Fingerprint' arguments. Requires the 'x509TrustList' argument. @type settings: L{tlslite.HandshakeSettings.HandshakeSettings} @param settings: Various settings which can be used to control the ciphersuites, certificate types, and SSL/TLS versions offered by the client. """ (resp, reply) = self.docmd("STARTTLS") if resp == 220: helper = ClientHelper( username, password, sharedKey, certChain, privateKey, cryptoID, protocol, x509Fingerprint, x509TrustList, x509CommonName, settings) conn = TLSConnection(self.sock) conn.closeSocket = True helper._handshake(conn) self.sock = conn self.file = conn.makefile('rb') return (resp, reply) gdata-2.0.18/samples/apps/marketplace_sample/gdata/tlslite/integration/IntegrationHelper.py0000750036466300116100000000345712156622362032270 0ustar afshareng00000000000000 class IntegrationHelper: def __init__(self, username=None, password=None, sharedKey=None, certChain=None, privateKey=None, cryptoID=None, protocol=None, x509Fingerprint=None, x509TrustList=None, x509CommonName=None, settings = None): self.username = None self.password = None self.sharedKey = None self.certChain = None self.privateKey = None self.checker = None #SRP Authentication if username and password and not \ (sharedKey or certChain or privateKey): self.username = username self.password = password #Shared Key Authentication elif username and sharedKey and not \ (password or certChain or privateKey): self.username = username self.sharedKey = sharedKey #Certificate Chain Authentication elif certChain and privateKey and not \ (username or password or sharedKey): self.certChain = certChain self.privateKey = privateKey #No Authentication elif not password and not username and not \ sharedKey and not certChain and not privateKey: pass else: raise ValueError("Bad parameters") #Authenticate the server based on its cryptoID or fingerprint if sharedKey and (cryptoID or protocol or x509Fingerprint): raise ValueError("Can't use shared keys with other forms of"\ "authentication") self.checker = Checker(cryptoID, protocol, x509Fingerprint, x509TrustList, x509CommonName) self.settings = settingsgdata-2.0.18/samples/apps/marketplace_sample/gdata/tlslite/integration/AsyncStateMachine.py0000750036466300116100000001603612156622362032205 0ustar afshareng00000000000000""" A state machine for using TLS Lite with asynchronous I/O. """ class AsyncStateMachine: """ This is an abstract class that's used to integrate TLS Lite with asyncore and Twisted. This class signals wantsReadsEvent() and wantsWriteEvent(). When the underlying socket has become readable or writeable, the event should be passed to this class by calling inReadEvent() or inWriteEvent(). This class will then try to read or write through the socket, and will update its state appropriately. This class will forward higher-level events to its subclass. For example, when a complete TLS record has been received, outReadEvent() will be called with the decrypted data. """ def __init__(self): self._clear() def _clear(self): #These store the various asynchronous operations (i.e. #generators). Only one of them, at most, is ever active at a #time. self.handshaker = None self.closer = None self.reader = None self.writer = None #This stores the result from the last call to the #currently active operation. If 0 it indicates that the #operation wants to read, if 1 it indicates that the #operation wants to write. If None, there is no active #operation. self.result = None def _checkAssert(self, maxActive=1): #This checks that only one operation, at most, is #active, and that self.result is set appropriately. activeOps = 0 if self.handshaker: activeOps += 1 if self.closer: activeOps += 1 if self.reader: activeOps += 1 if self.writer: activeOps += 1 if self.result == None: if activeOps != 0: raise AssertionError() elif self.result in (0,1): if activeOps != 1: raise AssertionError() else: raise AssertionError() if activeOps > maxActive: raise AssertionError() def wantsReadEvent(self): """If the state machine wants to read. If an operation is active, this returns whether or not the operation wants to read from the socket. If an operation is not active, this returns None. @rtype: bool or None @return: If the state machine wants to read. """ if self.result != None: return self.result == 0 return None def wantsWriteEvent(self): """If the state machine wants to write. If an operation is active, this returns whether or not the operation wants to write to the socket. If an operation is not active, this returns None. @rtype: bool or None @return: If the state machine wants to write. """ if self.result != None: return self.result == 1 return None def outConnectEvent(self): """Called when a handshake operation completes. May be overridden in subclass. """ pass def outCloseEvent(self): """Called when a close operation completes. May be overridden in subclass. """ pass def outReadEvent(self, readBuffer): """Called when a read operation completes. May be overridden in subclass.""" pass def outWriteEvent(self): """Called when a write operation completes. May be overridden in subclass.""" pass def inReadEvent(self): """Tell the state machine it can read from the socket.""" try: self._checkAssert() if self.handshaker: self._doHandshakeOp() elif self.closer: self._doCloseOp() elif self.reader: self._doReadOp() elif self.writer: self._doWriteOp() else: self.reader = self.tlsConnection.readAsync(16384) self._doReadOp() except: self._clear() raise def inWriteEvent(self): """Tell the state machine it can write to the socket.""" try: self._checkAssert() if self.handshaker: self._doHandshakeOp() elif self.closer: self._doCloseOp() elif self.reader: self._doReadOp() elif self.writer: self._doWriteOp() else: self.outWriteEvent() except: self._clear() raise def _doHandshakeOp(self): try: self.result = self.handshaker.next() except StopIteration: self.handshaker = None self.result = None self.outConnectEvent() def _doCloseOp(self): try: self.result = self.closer.next() except StopIteration: self.closer = None self.result = None self.outCloseEvent() def _doReadOp(self): self.result = self.reader.next() if not self.result in (0,1): readBuffer = self.result self.reader = None self.result = None self.outReadEvent(readBuffer) def _doWriteOp(self): try: self.result = self.writer.next() except StopIteration: self.writer = None self.result = None def setHandshakeOp(self, handshaker): """Start a handshake operation. @type handshaker: generator @param handshaker: A generator created by using one of the asynchronous handshake functions (i.e. handshakeServerAsync, or handshakeClientxxx(..., async=True). """ try: self._checkAssert(0) self.handshaker = handshaker self._doHandshakeOp() except: self._clear() raise def setServerHandshakeOp(self, **args): """Start a handshake operation. The arguments passed to this function will be forwarded to L{tlslite.TLSConnection.TLSConnection.handshakeServerAsync}. """ handshaker = self.tlsConnection.handshakeServerAsync(**args) self.setHandshakeOp(handshaker) def setCloseOp(self): """Start a close operation. """ try: self._checkAssert(0) self.closer = self.tlsConnection.closeAsync() self._doCloseOp() except: self._clear() raise def setWriteOp(self, writeBuffer): """Start a write operation. @type writeBuffer: str @param writeBuffer: The string to transmit. """ try: self._checkAssert(0) self.writer = self.tlsConnection.writeAsync(writeBuffer) self._doWriteOp() except: self._clear() raise gdata-2.0.18/samples/apps/marketplace_sample/gdata/tlslite/integration/TLSTwistedProtocolWrapper.py0000750036466300116100000001557112156622362033736 0ustar afshareng00000000000000"""TLS Lite + Twisted.""" from twisted.protocols.policies import ProtocolWrapper, WrappingFactory from twisted.python.failure import Failure from AsyncStateMachine import AsyncStateMachine from gdata.tlslite.TLSConnection import TLSConnection from gdata.tlslite.errors import * import socket import errno #The TLSConnection is created around a "fake socket" that #plugs it into the underlying Twisted transport class _FakeSocket: def __init__(self, wrapper): self.wrapper = wrapper self.data = "" def send(self, data): ProtocolWrapper.write(self.wrapper, data) return len(data) def recv(self, numBytes): if self.data == "": raise socket.error, (errno.EWOULDBLOCK, "") returnData = self.data[:numBytes] self.data = self.data[numBytes:] return returnData class TLSTwistedProtocolWrapper(ProtocolWrapper, AsyncStateMachine): """This class can wrap Twisted protocols to add TLS support. Below is a complete example of using TLS Lite with a Twisted echo server. There are two server implementations below. Echo is the original protocol, which is oblivious to TLS. Echo1 subclasses Echo and negotiates TLS when the client connects. Echo2 subclasses Echo and negotiates TLS when the client sends "STARTTLS":: from twisted.internet.protocol import Protocol, Factory from twisted.internet import reactor from twisted.protocols.policies import WrappingFactory from twisted.protocols.basic import LineReceiver from twisted.python import log from twisted.python.failure import Failure import sys from tlslite.api import * s = open("./serverX509Cert.pem").read() x509 = X509() x509.parse(s) certChain = X509CertChain([x509]) s = open("./serverX509Key.pem").read() privateKey = parsePEMKey(s, private=True) verifierDB = VerifierDB("verifierDB") verifierDB.open() class Echo(LineReceiver): def connectionMade(self): self.transport.write("Welcome to the echo server!\\r\\n") def lineReceived(self, line): self.transport.write(line + "\\r\\n") class Echo1(Echo): def connectionMade(self): if not self.transport.tlsStarted: self.transport.setServerHandshakeOp(certChain=certChain, privateKey=privateKey, verifierDB=verifierDB) else: Echo.connectionMade(self) def connectionLost(self, reason): pass #Handle any TLS exceptions here class Echo2(Echo): def lineReceived(self, data): if data == "STARTTLS": self.transport.setServerHandshakeOp(certChain=certChain, privateKey=privateKey, verifierDB=verifierDB) else: Echo.lineReceived(self, data) def connectionLost(self, reason): pass #Handle any TLS exceptions here factory = Factory() factory.protocol = Echo1 #factory.protocol = Echo2 wrappingFactory = WrappingFactory(factory) wrappingFactory.protocol = TLSTwistedProtocolWrapper log.startLogging(sys.stdout) reactor.listenTCP(1079, wrappingFactory) reactor.run() This class works as follows: Data comes in and is given to the AsyncStateMachine for handling. AsyncStateMachine will forward events to this class, and we'll pass them on to the ProtocolHandler, which will proxy them to the wrapped protocol. The wrapped protocol may then call back into this class, and these calls will be proxied into the AsyncStateMachine. The call graph looks like this: - self.dataReceived - AsyncStateMachine.inReadEvent - self.out(Connect|Close|Read)Event - ProtocolWrapper.(connectionMade|loseConnection|dataReceived) - self.(loseConnection|write|writeSequence) - AsyncStateMachine.(setCloseOp|setWriteOp) """ #WARNING: IF YOU COPY-AND-PASTE THE ABOVE CODE, BE SURE TO REMOVE #THE EXTRA ESCAPING AROUND "\\r\\n" def __init__(self, factory, wrappedProtocol): ProtocolWrapper.__init__(self, factory, wrappedProtocol) AsyncStateMachine.__init__(self) self.fakeSocket = _FakeSocket(self) self.tlsConnection = TLSConnection(self.fakeSocket) self.tlsStarted = False self.connectionLostCalled = False def connectionMade(self): try: ProtocolWrapper.connectionMade(self) except TLSError, e: self.connectionLost(Failure(e)) ProtocolWrapper.loseConnection(self) def dataReceived(self, data): try: if not self.tlsStarted: ProtocolWrapper.dataReceived(self, data) else: self.fakeSocket.data += data while self.fakeSocket.data: AsyncStateMachine.inReadEvent(self) except TLSError, e: self.connectionLost(Failure(e)) ProtocolWrapper.loseConnection(self) def connectionLost(self, reason): if not self.connectionLostCalled: ProtocolWrapper.connectionLost(self, reason) self.connectionLostCalled = True def outConnectEvent(self): ProtocolWrapper.connectionMade(self) def outCloseEvent(self): ProtocolWrapper.loseConnection(self) def outReadEvent(self, data): if data == "": ProtocolWrapper.loseConnection(self) else: ProtocolWrapper.dataReceived(self, data) def setServerHandshakeOp(self, **args): self.tlsStarted = True AsyncStateMachine.setServerHandshakeOp(self, **args) def loseConnection(self): if not self.tlsStarted: ProtocolWrapper.loseConnection(self) else: AsyncStateMachine.setCloseOp(self) def write(self, data): if not self.tlsStarted: ProtocolWrapper.write(self, data) else: #Because of the FakeSocket, write operations are guaranteed to #terminate immediately. AsyncStateMachine.setWriteOp(self, data) def writeSequence(self, seq): if not self.tlsStarted: ProtocolWrapper.writeSequence(self, seq) else: #Because of the FakeSocket, write operations are guaranteed to #terminate immediately. AsyncStateMachine.setWriteOp(self, "".join(seq)) gdata-2.0.18/samples/apps/marketplace_sample/gdata/tlslite/integration/IMAP4_TLS.py0000750036466300116100000001203112156622362030165 0ustar afshareng00000000000000"""TLS Lite + imaplib.""" import socket from imaplib import IMAP4 from gdata.tlslite.TLSConnection import TLSConnection from gdata.tlslite.integration.ClientHelper import ClientHelper # IMAP TLS PORT IMAP4_TLS_PORT = 993 class IMAP4_TLS(IMAP4, ClientHelper): """This class extends L{imaplib.IMAP4} with TLS support.""" def __init__(self, host = '', port = IMAP4_TLS_PORT, username=None, password=None, sharedKey=None, certChain=None, privateKey=None, cryptoID=None, protocol=None, x509Fingerprint=None, x509TrustList=None, x509CommonName=None, settings=None): """Create a new IMAP4_TLS. For client authentication, use one of these argument combinations: - username, password (SRP) - username, sharedKey (shared-key) - certChain, privateKey (certificate) For server authentication, you can either rely on the implicit mutual authentication performed by SRP or shared-keys, or you can do certificate-based server authentication with one of these argument combinations: - cryptoID[, protocol] (requires cryptoIDlib) - x509Fingerprint - x509TrustList[, x509CommonName] (requires cryptlib_py) Certificate-based server authentication is compatible with SRP or certificate-based client authentication. It is not compatible with shared-keys. The caller should be prepared to handle TLS-specific exceptions. See the client handshake functions in L{tlslite.TLSConnection.TLSConnection} for details on which exceptions might be raised. @type host: str @param host: Server to connect to. @type port: int @param port: Port to connect to. @type username: str @param username: SRP or shared-key username. Requires the 'password' or 'sharedKey' argument. @type password: str @param password: SRP password for mutual authentication. Requires the 'username' argument. @type sharedKey: str @param sharedKey: Shared key for mutual authentication. Requires the 'username' argument. @type certChain: L{tlslite.X509CertChain.X509CertChain} or L{cryptoIDlib.CertChain.CertChain} @param certChain: Certificate chain for client authentication. Requires the 'privateKey' argument. Excludes the SRP or shared-key related arguments. @type privateKey: L{tlslite.utils.RSAKey.RSAKey} @param privateKey: Private key for client authentication. Requires the 'certChain' argument. Excludes the SRP or shared-key related arguments. @type cryptoID: str @param cryptoID: cryptoID for server authentication. Mutually exclusive with the 'x509...' arguments. @type protocol: str @param protocol: cryptoID protocol URI for server authentication. Requires the 'cryptoID' argument. @type x509Fingerprint: str @param x509Fingerprint: Hex-encoded X.509 fingerprint for server authentication. Mutually exclusive with the 'cryptoID' and 'x509TrustList' arguments. @type x509TrustList: list of L{tlslite.X509.X509} @param x509TrustList: A list of trusted root certificates. The other party must present a certificate chain which extends to one of these root certificates. The cryptlib_py module must be installed to use this parameter. Mutually exclusive with the 'cryptoID' and 'x509Fingerprint' arguments. @type x509CommonName: str @param x509CommonName: The end-entity certificate's 'CN' field must match this value. For a web server, this is typically a server name such as 'www.amazon.com'. Mutually exclusive with the 'cryptoID' and 'x509Fingerprint' arguments. Requires the 'x509TrustList' argument. @type settings: L{tlslite.HandshakeSettings.HandshakeSettings} @param settings: Various settings which can be used to control the ciphersuites, certificate types, and SSL/TLS versions offered by the client. """ ClientHelper.__init__(self, username, password, sharedKey, certChain, privateKey, cryptoID, protocol, x509Fingerprint, x509TrustList, x509CommonName, settings) IMAP4.__init__(self, host, port) def open(self, host = '', port = IMAP4_TLS_PORT): """Setup connection to remote server on "host:port". This connection will be used by the routines: read, readline, send, shutdown. """ self.host = host self.port = port self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.sock.connect((host, port)) self.sock = TLSConnection(self.sock) self.sock.closeSocket = True ClientHelper._handshake(self, self.sock) self.file = self.sock.makefile('rb') gdata-2.0.18/samples/apps/marketplace_sample/gdata/tlslite/integration/TLSAsyncDispatcherMixIn.py0000750036466300116100000001141612156622362033253 0ustar afshareng00000000000000"""TLS Lite + asyncore.""" import asyncore from gdata.tlslite.TLSConnection import TLSConnection from AsyncStateMachine import AsyncStateMachine class TLSAsyncDispatcherMixIn(AsyncStateMachine): """This class can be "mixed in" with an L{asyncore.dispatcher} to add TLS support. This class essentially sits between the dispatcher and the select loop, intercepting events and only calling the dispatcher when applicable. In the case of handle_read(), a read operation will be activated, and when it completes, the bytes will be placed in a buffer where the dispatcher can retrieve them by calling recv(), and the dispatcher's handle_read() will be called. In the case of handle_write(), the dispatcher's handle_write() will be called, and when it calls send(), a write operation will be activated. To use this class, you must combine it with an asyncore.dispatcher, and pass in a handshake operation with setServerHandshakeOp(). Below is an example of using this class with medusa. This class is mixed in with http_channel to create http_tls_channel. Note: 1. the mix-in is listed first in the inheritance list 2. the input buffer size must be at least 16K, otherwise the dispatcher might not read all the bytes from the TLS layer, leaving some bytes in limbo. 3. IE seems to have a problem receiving a whole HTTP response in a single TLS record, so HTML pages containing '\\r\\n\\r\\n' won't be displayed on IE. Add the following text into 'start_medusa.py', in the 'HTTP Server' section:: from tlslite.api import * s = open("./serverX509Cert.pem").read() x509 = X509() x509.parse(s) certChain = X509CertChain([x509]) s = open("./serverX509Key.pem").read() privateKey = parsePEMKey(s, private=True) class http_tls_channel(TLSAsyncDispatcherMixIn, http_server.http_channel): ac_in_buffer_size = 16384 def __init__ (self, server, conn, addr): http_server.http_channel.__init__(self, server, conn, addr) TLSAsyncDispatcherMixIn.__init__(self, conn) self.tlsConnection.ignoreAbruptClose = True self.setServerHandshakeOp(certChain=certChain, privateKey=privateKey) hs.channel_class = http_tls_channel If the TLS layer raises an exception, the exception will be caught in asyncore.dispatcher, which will call close() on this class. The TLS layer always closes the TLS connection before raising an exception, so the close operation will complete right away, causing asyncore.dispatcher.close() to be called, which closes the socket and removes this instance from the asyncore loop. """ def __init__(self, sock=None): AsyncStateMachine.__init__(self) if sock: self.tlsConnection = TLSConnection(sock) #Calculate the sibling I'm being mixed in with. #This is necessary since we override functions #like readable(), handle_read(), etc., but we #also want to call the sibling's versions. for cl in self.__class__.__bases__: if cl != TLSAsyncDispatcherMixIn and cl != AsyncStateMachine: self.siblingClass = cl break else: raise AssertionError() def readable(self): result = self.wantsReadEvent() if result != None: return result return self.siblingClass.readable(self) def writable(self): result = self.wantsWriteEvent() if result != None: return result return self.siblingClass.writable(self) def handle_read(self): self.inReadEvent() def handle_write(self): self.inWriteEvent() def outConnectEvent(self): self.siblingClass.handle_connect(self) def outCloseEvent(self): asyncore.dispatcher.close(self) def outReadEvent(self, readBuffer): self.readBuffer = readBuffer self.siblingClass.handle_read(self) def outWriteEvent(self): self.siblingClass.handle_write(self) def recv(self, bufferSize=16384): if bufferSize < 16384 or self.readBuffer == None: raise AssertionError() returnValue = self.readBuffer self.readBuffer = None return returnValue def send(self, writeBuffer): self.setWriteOp(writeBuffer) return len(writeBuffer) def close(self): if hasattr(self, "tlsConnection"): self.setCloseOp() else: asyncore.dispatcher.close(self) gdata-2.0.18/samples/apps/marketplace_sample/gdata/tlslite/__init__.py0000750036466300116100000000215112156622362026047 0ustar afshareng00000000000000""" TLS Lite is a free python library that implements SSL v3, TLS v1, and TLS v1.1. TLS Lite supports non-traditional authentication methods such as SRP, shared keys, and cryptoIDs, in addition to X.509 certificates. TLS Lite is pure python, however it can access OpenSSL, cryptlib, pycrypto, and GMPY for faster crypto operations. TLS Lite integrates with httplib, xmlrpclib, poplib, imaplib, smtplib, SocketServer, asyncore, and Twisted. To use, do:: from tlslite.api import * Then use the L{tlslite.TLSConnection.TLSConnection} class with a socket, or use one of the integration classes in L{tlslite.integration}. @version: 0.3.8 """ __version__ = "0.3.8" __all__ = ["api", "BaseDB", "Checker", "constants", "errors", "FileObject", "HandshakeSettings", "mathtls", "messages", "Session", "SessionCache", "SharedKeyDB", "TLSConnection", "TLSRecordLayer", "VerifierDB", "X509", "X509CertChain", "integration", "utils"] gdata-2.0.18/samples/apps/marketplace_sample/gdata/tlslite/constants.py0000750036466300116100000001646412156622362026340 0ustar afshareng00000000000000"""Constants used in various places.""" class CertificateType: x509 = 0 openpgp = 1 cryptoID = 2 class HandshakeType: hello_request = 0 client_hello = 1 server_hello = 2 certificate = 11 server_key_exchange = 12 certificate_request = 13 server_hello_done = 14 certificate_verify = 15 client_key_exchange = 16 finished = 20 class ContentType: change_cipher_spec = 20 alert = 21 handshake = 22 application_data = 23 all = (20,21,22,23) class AlertLevel: warning = 1 fatal = 2 class AlertDescription: """ @cvar bad_record_mac: A TLS record failed to decrypt properly. If this occurs during a shared-key or SRP handshake it most likely indicates a bad password. It may also indicate an implementation error, or some tampering with the data in transit. This alert will be signalled by the server if the SRP password is bad. It may also be signalled by the server if the SRP username is unknown to the server, but it doesn't wish to reveal that fact. This alert will be signalled by the client if the shared-key username is bad. @cvar handshake_failure: A problem occurred while handshaking. This typically indicates a lack of common ciphersuites between client and server, or some other disagreement (about SRP parameters or key sizes, for example). @cvar protocol_version: The other party's SSL/TLS version was unacceptable. This indicates that the client and server couldn't agree on which version of SSL or TLS to use. @cvar user_canceled: The handshake is being cancelled for some reason. """ close_notify = 0 unexpected_message = 10 bad_record_mac = 20 decryption_failed = 21 record_overflow = 22 decompression_failure = 30 handshake_failure = 40 no_certificate = 41 #SSLv3 bad_certificate = 42 unsupported_certificate = 43 certificate_revoked = 44 certificate_expired = 45 certificate_unknown = 46 illegal_parameter = 47 unknown_ca = 48 access_denied = 49 decode_error = 50 decrypt_error = 51 export_restriction = 60 protocol_version = 70 insufficient_security = 71 internal_error = 80 user_canceled = 90 no_renegotiation = 100 unknown_srp_username = 120 missing_srp_username = 121 untrusted_srp_parameters = 122 class CipherSuite: TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA = 0x0050 TLS_SRP_SHA_WITH_AES_128_CBC_SHA = 0x0053 TLS_SRP_SHA_WITH_AES_256_CBC_SHA = 0x0056 TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA = 0x0051 TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA = 0x0054 TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA = 0x0057 TLS_RSA_WITH_3DES_EDE_CBC_SHA = 0x000A TLS_RSA_WITH_AES_128_CBC_SHA = 0x002F TLS_RSA_WITH_AES_256_CBC_SHA = 0x0035 TLS_RSA_WITH_RC4_128_SHA = 0x0005 srpSuites = [] srpSuites.append(TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA) srpSuites.append(TLS_SRP_SHA_WITH_AES_128_CBC_SHA) srpSuites.append(TLS_SRP_SHA_WITH_AES_256_CBC_SHA) def getSrpSuites(ciphers): suites = [] for cipher in ciphers: if cipher == "aes128": suites.append(CipherSuite.TLS_SRP_SHA_WITH_AES_128_CBC_SHA) elif cipher == "aes256": suites.append(CipherSuite.TLS_SRP_SHA_WITH_AES_256_CBC_SHA) elif cipher == "3des": suites.append(CipherSuite.TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA) return suites getSrpSuites = staticmethod(getSrpSuites) srpRsaSuites = [] srpRsaSuites.append(TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA) srpRsaSuites.append(TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA) srpRsaSuites.append(TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA) def getSrpRsaSuites(ciphers): suites = [] for cipher in ciphers: if cipher == "aes128": suites.append(CipherSuite.TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA) elif cipher == "aes256": suites.append(CipherSuite.TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA) elif cipher == "3des": suites.append(CipherSuite.TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA) return suites getSrpRsaSuites = staticmethod(getSrpRsaSuites) rsaSuites = [] rsaSuites.append(TLS_RSA_WITH_3DES_EDE_CBC_SHA) rsaSuites.append(TLS_RSA_WITH_AES_128_CBC_SHA) rsaSuites.append(TLS_RSA_WITH_AES_256_CBC_SHA) rsaSuites.append(TLS_RSA_WITH_RC4_128_SHA) def getRsaSuites(ciphers): suites = [] for cipher in ciphers: if cipher == "aes128": suites.append(CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA) elif cipher == "aes256": suites.append(CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA) elif cipher == "rc4": suites.append(CipherSuite.TLS_RSA_WITH_RC4_128_SHA) elif cipher == "3des": suites.append(CipherSuite.TLS_RSA_WITH_3DES_EDE_CBC_SHA) return suites getRsaSuites = staticmethod(getRsaSuites) tripleDESSuites = [] tripleDESSuites.append(TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA) tripleDESSuites.append(TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA) tripleDESSuites.append(TLS_RSA_WITH_3DES_EDE_CBC_SHA) aes128Suites = [] aes128Suites.append(TLS_SRP_SHA_WITH_AES_128_CBC_SHA) aes128Suites.append(TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA) aes128Suites.append(TLS_RSA_WITH_AES_128_CBC_SHA) aes256Suites = [] aes256Suites.append(TLS_SRP_SHA_WITH_AES_256_CBC_SHA) aes256Suites.append(TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA) aes256Suites.append(TLS_RSA_WITH_AES_256_CBC_SHA) rc4Suites = [] rc4Suites.append(TLS_RSA_WITH_RC4_128_SHA) class Fault: badUsername = 101 badPassword = 102 badA = 103 clientSrpFaults = range(101,104) badVerifyMessage = 601 clientCertFaults = range(601,602) badPremasterPadding = 501 shortPremasterSecret = 502 clientNoAuthFaults = range(501,503) badIdentifier = 401 badSharedKey = 402 clientSharedKeyFaults = range(401,403) badB = 201 serverFaults = range(201,202) badFinished = 300 badMAC = 301 badPadding = 302 genericFaults = range(300,303) faultAlerts = {\ badUsername: (AlertDescription.unknown_srp_username, \ AlertDescription.bad_record_mac),\ badPassword: (AlertDescription.bad_record_mac,),\ badA: (AlertDescription.illegal_parameter,),\ badIdentifier: (AlertDescription.handshake_failure,),\ badSharedKey: (AlertDescription.bad_record_mac,),\ badPremasterPadding: (AlertDescription.bad_record_mac,),\ shortPremasterSecret: (AlertDescription.bad_record_mac,),\ badVerifyMessage: (AlertDescription.decrypt_error,),\ badFinished: (AlertDescription.decrypt_error,),\ badMAC: (AlertDescription.bad_record_mac,),\ badPadding: (AlertDescription.bad_record_mac,) } faultNames = {\ badUsername: "bad username",\ badPassword: "bad password",\ badA: "bad A",\ badIdentifier: "bad identifier",\ badSharedKey: "bad sharedkey",\ badPremasterPadding: "bad premaster padding",\ shortPremasterSecret: "short premaster secret",\ badVerifyMessage: "bad verify message",\ badFinished: "bad finished message",\ badMAC: "bad MAC",\ badPadding: "bad padding" } gdata-2.0.18/samples/apps/marketplace_sample/gdata/tlslite/HandshakeSettings.py0000750036466300116100000001433412156622362027725 0ustar afshareng00000000000000"""Class for setting handshake parameters.""" from constants import CertificateType from utils import cryptomath from utils import cipherfactory class HandshakeSettings: """This class encapsulates various parameters that can be used with a TLS handshake. @sort: minKeySize, maxKeySize, cipherNames, certificateTypes, minVersion, maxVersion @type minKeySize: int @ivar minKeySize: The minimum bit length for asymmetric keys. If the other party tries to use SRP, RSA, or Diffie-Hellman parameters smaller than this length, an alert will be signalled. The default is 1023. @type maxKeySize: int @ivar maxKeySize: The maximum bit length for asymmetric keys. If the other party tries to use SRP, RSA, or Diffie-Hellman parameters larger than this length, an alert will be signalled. The default is 8193. @type cipherNames: list @ivar cipherNames: The allowed ciphers, in order of preference. The allowed values in this list are 'aes256', 'aes128', '3des', and 'rc4'. If these settings are used with a client handshake, they determine the order of the ciphersuites offered in the ClientHello message. If these settings are used with a server handshake, the server will choose whichever ciphersuite matches the earliest entry in this list. NOTE: If '3des' is used in this list, but TLS Lite can't find an add-on library that supports 3DES, then '3des' will be silently removed. The default value is ['aes256', 'aes128', '3des', 'rc4']. @type certificateTypes: list @ivar certificateTypes: The allowed certificate types, in order of preference. The allowed values in this list are 'x509' and 'cryptoID'. This list is only used with a client handshake. The client will advertise to the server which certificate types are supported, and will check that the server uses one of the appropriate types. NOTE: If 'cryptoID' is used in this list, but cryptoIDlib is not installed, then 'cryptoID' will be silently removed. @type minVersion: tuple @ivar minVersion: The minimum allowed SSL/TLS version. This variable can be set to (3,0) for SSL 3.0, (3,1) for TLS 1.0, or (3,2) for TLS 1.1. If the other party wishes to use a lower version, a protocol_version alert will be signalled. The default is (3,0). @type maxVersion: tuple @ivar maxVersion: The maximum allowed SSL/TLS version. This variable can be set to (3,0) for SSL 3.0, (3,1) for TLS 1.0, or (3,2) for TLS 1.1. If the other party wishes to use a higher version, a protocol_version alert will be signalled. The default is (3,2). (WARNING: Some servers may (improperly) reject clients which offer support for TLS 1.1. In this case, try lowering maxVersion to (3,1)). """ def __init__(self): self.minKeySize = 1023 self.maxKeySize = 8193 self.cipherNames = ["aes256", "aes128", "3des", "rc4"] self.cipherImplementations = ["cryptlib", "openssl", "pycrypto", "python"] self.certificateTypes = ["x509", "cryptoID"] self.minVersion = (3,0) self.maxVersion = (3,2) #Filters out options that are not supported def _filter(self): other = HandshakeSettings() other.minKeySize = self.minKeySize other.maxKeySize = self.maxKeySize other.cipherNames = self.cipherNames other.cipherImplementations = self.cipherImplementations other.certificateTypes = self.certificateTypes other.minVersion = self.minVersion other.maxVersion = self.maxVersion if not cipherfactory.tripleDESPresent: other.cipherNames = [e for e in self.cipherNames if e != "3des"] if len(other.cipherNames)==0: raise ValueError("No supported ciphers") try: import cryptoIDlib except ImportError: other.certificateTypes = [e for e in self.certificateTypes \ if e != "cryptoID"] if len(other.certificateTypes)==0: raise ValueError("No supported certificate types") if not cryptomath.cryptlibpyLoaded: other.cipherImplementations = [e for e in \ self.cipherImplementations if e != "cryptlib"] if not cryptomath.m2cryptoLoaded: other.cipherImplementations = [e for e in \ other.cipherImplementations if e != "openssl"] if not cryptomath.pycryptoLoaded: other.cipherImplementations = [e for e in \ other.cipherImplementations if e != "pycrypto"] if len(other.cipherImplementations)==0: raise ValueError("No supported cipher implementations") if other.minKeySize<512: raise ValueError("minKeySize too small") if other.minKeySize>16384: raise ValueError("minKeySize too large") if other.maxKeySize<512: raise ValueError("maxKeySize too small") if other.maxKeySize>16384: raise ValueError("maxKeySize too large") for s in other.cipherNames: if s not in ("aes256", "aes128", "rc4", "3des"): raise ValueError("Unknown cipher name: '%s'" % s) for s in other.cipherImplementations: if s not in ("cryptlib", "openssl", "python", "pycrypto"): raise ValueError("Unknown cipher implementation: '%s'" % s) for s in other.certificateTypes: if s not in ("x509", "cryptoID"): raise ValueError("Unknown certificate type: '%s'" % s) if other.minVersion > other.maxVersion: raise ValueError("Versions set incorrectly") if not other.minVersion in ((3,0), (3,1), (3,2)): raise ValueError("minVersion set incorrectly") if not other.maxVersion in ((3,0), (3,1), (3,2)): raise ValueError("maxVersion set incorrectly") return other def _getCertificateTypes(self): l = [] for ct in self.certificateTypes: if ct == "x509": l.append(CertificateType.x509) elif ct == "cryptoID": l.append(CertificateType.cryptoID) else: raise AssertionError() return l gdata-2.0.18/samples/apps/marketplace_sample/gdata/tlslite/messages.py0000750036466300116100000004377512156622362026140 0ustar afshareng00000000000000"""Classes representing TLS messages.""" from utils.compat import * from utils.cryptomath import * from errors import * from utils.codec import * from constants import * from X509 import X509 from X509CertChain import X509CertChain import sha import md5 class RecordHeader3: def __init__(self): self.type = 0 self.version = (0,0) self.length = 0 self.ssl2 = False def create(self, version, type, length): self.type = type self.version = version self.length = length return self def write(self): w = Writer(5) w.add(self.type, 1) w.add(self.version[0], 1) w.add(self.version[1], 1) w.add(self.length, 2) return w.bytes def parse(self, p): self.type = p.get(1) self.version = (p.get(1), p.get(1)) self.length = p.get(2) self.ssl2 = False return self class RecordHeader2: def __init__(self): self.type = 0 self.version = (0,0) self.length = 0 self.ssl2 = True def parse(self, p): if p.get(1)!=128: raise SyntaxError() self.type = ContentType.handshake self.version = (2,0) #We don't support 2-byte-length-headers; could be a problem self.length = p.get(1) return self class Msg: def preWrite(self, trial): if trial: w = Writer() else: length = self.write(True) w = Writer(length) return w def postWrite(self, w, trial): if trial: return w.index else: return w.bytes class Alert(Msg): def __init__(self): self.contentType = ContentType.alert self.level = 0 self.description = 0 def create(self, description, level=AlertLevel.fatal): self.level = level self.description = description return self def parse(self, p): p.setLengthCheck(2) self.level = p.get(1) self.description = p.get(1) p.stopLengthCheck() return self def write(self): w = Writer(2) w.add(self.level, 1) w.add(self.description, 1) return w.bytes class HandshakeMsg(Msg): def preWrite(self, handshakeType, trial): if trial: w = Writer() w.add(handshakeType, 1) w.add(0, 3) else: length = self.write(True) w = Writer(length) w.add(handshakeType, 1) w.add(length-4, 3) return w class ClientHello(HandshakeMsg): def __init__(self, ssl2=False): self.contentType = ContentType.handshake self.ssl2 = ssl2 self.client_version = (0,0) self.random = createByteArrayZeros(32) self.session_id = createByteArraySequence([]) self.cipher_suites = [] # a list of 16-bit values self.certificate_types = [CertificateType.x509] self.compression_methods = [] # a list of 8-bit values self.srp_username = None # a string def create(self, version, random, session_id, cipher_suites, certificate_types=None, srp_username=None): self.client_version = version self.random = random self.session_id = session_id self.cipher_suites = cipher_suites self.certificate_types = certificate_types self.compression_methods = [0] self.srp_username = srp_username return self def parse(self, p): if self.ssl2: self.client_version = (p.get(1), p.get(1)) cipherSpecsLength = p.get(2) sessionIDLength = p.get(2) randomLength = p.get(2) self.cipher_suites = p.getFixList(3, int(cipherSpecsLength/3)) self.session_id = p.getFixBytes(sessionIDLength) self.random = p.getFixBytes(randomLength) if len(self.random) < 32: zeroBytes = 32-len(self.random) self.random = createByteArrayZeros(zeroBytes) + self.random self.compression_methods = [0]#Fake this value #We're not doing a stopLengthCheck() for SSLv2, oh well.. else: p.startLengthCheck(3) self.client_version = (p.get(1), p.get(1)) self.random = p.getFixBytes(32) self.session_id = p.getVarBytes(1) self.cipher_suites = p.getVarList(2, 2) self.compression_methods = p.getVarList(1, 1) if not p.atLengthCheck(): totalExtLength = p.get(2) soFar = 0 while soFar != totalExtLength: extType = p.get(2) extLength = p.get(2) if extType == 6: self.srp_username = bytesToString(p.getVarBytes(1)) elif extType == 7: self.certificate_types = p.getVarList(1, 1) else: p.getFixBytes(extLength) soFar += 4 + extLength p.stopLengthCheck() return self def write(self, trial=False): w = HandshakeMsg.preWrite(self, HandshakeType.client_hello, trial) w.add(self.client_version[0], 1) w.add(self.client_version[1], 1) w.addFixSeq(self.random, 1) w.addVarSeq(self.session_id, 1, 1) w.addVarSeq(self.cipher_suites, 2, 2) w.addVarSeq(self.compression_methods, 1, 1) extLength = 0 if self.certificate_types and self.certificate_types != \ [CertificateType.x509]: extLength += 5 + len(self.certificate_types) if self.srp_username: extLength += 5 + len(self.srp_username) if extLength > 0: w.add(extLength, 2) if self.certificate_types and self.certificate_types != \ [CertificateType.x509]: w.add(7, 2) w.add(len(self.certificate_types)+1, 2) w.addVarSeq(self.certificate_types, 1, 1) if self.srp_username: w.add(6, 2) w.add(len(self.srp_username)+1, 2) w.addVarSeq(stringToBytes(self.srp_username), 1, 1) return HandshakeMsg.postWrite(self, w, trial) class ServerHello(HandshakeMsg): def __init__(self): self.contentType = ContentType.handshake self.server_version = (0,0) self.random = createByteArrayZeros(32) self.session_id = createByteArraySequence([]) self.cipher_suite = 0 self.certificate_type = CertificateType.x509 self.compression_method = 0 def create(self, version, random, session_id, cipher_suite, certificate_type): self.server_version = version self.random = random self.session_id = session_id self.cipher_suite = cipher_suite self.certificate_type = certificate_type self.compression_method = 0 return self def parse(self, p): p.startLengthCheck(3) self.server_version = (p.get(1), p.get(1)) self.random = p.getFixBytes(32) self.session_id = p.getVarBytes(1) self.cipher_suite = p.get(2) self.compression_method = p.get(1) if not p.atLengthCheck(): totalExtLength = p.get(2) soFar = 0 while soFar != totalExtLength: extType = p.get(2) extLength = p.get(2) if extType == 7: self.certificate_type = p.get(1) else: p.getFixBytes(extLength) soFar += 4 + extLength p.stopLengthCheck() return self def write(self, trial=False): w = HandshakeMsg.preWrite(self, HandshakeType.server_hello, trial) w.add(self.server_version[0], 1) w.add(self.server_version[1], 1) w.addFixSeq(self.random, 1) w.addVarSeq(self.session_id, 1, 1) w.add(self.cipher_suite, 2) w.add(self.compression_method, 1) extLength = 0 if self.certificate_type and self.certificate_type != \ CertificateType.x509: extLength += 5 if extLength != 0: w.add(extLength, 2) if self.certificate_type and self.certificate_type != \ CertificateType.x509: w.add(7, 2) w.add(1, 2) w.add(self.certificate_type, 1) return HandshakeMsg.postWrite(self, w, trial) class Certificate(HandshakeMsg): def __init__(self, certificateType): self.certificateType = certificateType self.contentType = ContentType.handshake self.certChain = None def create(self, certChain): self.certChain = certChain return self def parse(self, p): p.startLengthCheck(3) if self.certificateType == CertificateType.x509: chainLength = p.get(3) index = 0 certificate_list = [] while index != chainLength: certBytes = p.getVarBytes(3) x509 = X509() x509.parseBinary(certBytes) certificate_list.append(x509) index += len(certBytes)+3 if certificate_list: self.certChain = X509CertChain(certificate_list) elif self.certificateType == CertificateType.cryptoID: s = bytesToString(p.getVarBytes(2)) if s: try: import cryptoIDlib.CertChain except ImportError: raise SyntaxError(\ "cryptoID cert chain received, cryptoIDlib not present") self.certChain = cryptoIDlib.CertChain.CertChain().parse(s) else: raise AssertionError() p.stopLengthCheck() return self def write(self, trial=False): w = HandshakeMsg.preWrite(self, HandshakeType.certificate, trial) if self.certificateType == CertificateType.x509: chainLength = 0 if self.certChain: certificate_list = self.certChain.x509List else: certificate_list = [] #determine length for cert in certificate_list: bytes = cert.writeBytes() chainLength += len(bytes)+3 #add bytes w.add(chainLength, 3) for cert in certificate_list: bytes = cert.writeBytes() w.addVarSeq(bytes, 1, 3) elif self.certificateType == CertificateType.cryptoID: if self.certChain: bytes = stringToBytes(self.certChain.write()) else: bytes = createByteArraySequence([]) w.addVarSeq(bytes, 1, 2) else: raise AssertionError() return HandshakeMsg.postWrite(self, w, trial) class CertificateRequest(HandshakeMsg): def __init__(self): self.contentType = ContentType.handshake self.certificate_types = [] #treat as opaque bytes for now self.certificate_authorities = createByteArraySequence([]) def create(self, certificate_types, certificate_authorities): self.certificate_types = certificate_types self.certificate_authorities = certificate_authorities return self def parse(self, p): p.startLengthCheck(3) self.certificate_types = p.getVarList(1, 1) self.certificate_authorities = p.getVarBytes(2) p.stopLengthCheck() return self def write(self, trial=False): w = HandshakeMsg.preWrite(self, HandshakeType.certificate_request, trial) w.addVarSeq(self.certificate_types, 1, 1) w.addVarSeq(self.certificate_authorities, 1, 2) return HandshakeMsg.postWrite(self, w, trial) class ServerKeyExchange(HandshakeMsg): def __init__(self, cipherSuite): self.cipherSuite = cipherSuite self.contentType = ContentType.handshake self.srp_N = 0L self.srp_g = 0L self.srp_s = createByteArraySequence([]) self.srp_B = 0L self.signature = createByteArraySequence([]) def createSRP(self, srp_N, srp_g, srp_s, srp_B): self.srp_N = srp_N self.srp_g = srp_g self.srp_s = srp_s self.srp_B = srp_B return self def parse(self, p): p.startLengthCheck(3) self.srp_N = bytesToNumber(p.getVarBytes(2)) self.srp_g = bytesToNumber(p.getVarBytes(2)) self.srp_s = p.getVarBytes(1) self.srp_B = bytesToNumber(p.getVarBytes(2)) if self.cipherSuite in CipherSuite.srpRsaSuites: self.signature = p.getVarBytes(2) p.stopLengthCheck() return self def write(self, trial=False): w = HandshakeMsg.preWrite(self, HandshakeType.server_key_exchange, trial) w.addVarSeq(numberToBytes(self.srp_N), 1, 2) w.addVarSeq(numberToBytes(self.srp_g), 1, 2) w.addVarSeq(self.srp_s, 1, 1) w.addVarSeq(numberToBytes(self.srp_B), 1, 2) if self.cipherSuite in CipherSuite.srpRsaSuites: w.addVarSeq(self.signature, 1, 2) return HandshakeMsg.postWrite(self, w, trial) def hash(self, clientRandom, serverRandom): oldCipherSuite = self.cipherSuite self.cipherSuite = None try: bytes = clientRandom + serverRandom + self.write()[4:] s = bytesToString(bytes) return stringToBytes(md5.md5(s).digest() + sha.sha(s).digest()) finally: self.cipherSuite = oldCipherSuite class ServerHelloDone(HandshakeMsg): def __init__(self): self.contentType = ContentType.handshake def create(self): return self def parse(self, p): p.startLengthCheck(3) p.stopLengthCheck() return self def write(self, trial=False): w = HandshakeMsg.preWrite(self, HandshakeType.server_hello_done, trial) return HandshakeMsg.postWrite(self, w, trial) class ClientKeyExchange(HandshakeMsg): def __init__(self, cipherSuite, version=None): self.cipherSuite = cipherSuite self.version = version self.contentType = ContentType.handshake self.srp_A = 0 self.encryptedPreMasterSecret = createByteArraySequence([]) def createSRP(self, srp_A): self.srp_A = srp_A return self def createRSA(self, encryptedPreMasterSecret): self.encryptedPreMasterSecret = encryptedPreMasterSecret return self def parse(self, p): p.startLengthCheck(3) if self.cipherSuite in CipherSuite.srpSuites + \ CipherSuite.srpRsaSuites: self.srp_A = bytesToNumber(p.getVarBytes(2)) elif self.cipherSuite in CipherSuite.rsaSuites: if self.version in ((3,1), (3,2)): self.encryptedPreMasterSecret = p.getVarBytes(2) elif self.version == (3,0): self.encryptedPreMasterSecret = \ p.getFixBytes(len(p.bytes)-p.index) else: raise AssertionError() else: raise AssertionError() p.stopLengthCheck() return self def write(self, trial=False): w = HandshakeMsg.preWrite(self, HandshakeType.client_key_exchange, trial) if self.cipherSuite in CipherSuite.srpSuites + \ CipherSuite.srpRsaSuites: w.addVarSeq(numberToBytes(self.srp_A), 1, 2) elif self.cipherSuite in CipherSuite.rsaSuites: if self.version in ((3,1), (3,2)): w.addVarSeq(self.encryptedPreMasterSecret, 1, 2) elif self.version == (3,0): w.addFixSeq(self.encryptedPreMasterSecret, 1) else: raise AssertionError() else: raise AssertionError() return HandshakeMsg.postWrite(self, w, trial) class CertificateVerify(HandshakeMsg): def __init__(self): self.contentType = ContentType.handshake self.signature = createByteArraySequence([]) def create(self, signature): self.signature = signature return self def parse(self, p): p.startLengthCheck(3) self.signature = p.getVarBytes(2) p.stopLengthCheck() return self def write(self, trial=False): w = HandshakeMsg.preWrite(self, HandshakeType.certificate_verify, trial) w.addVarSeq(self.signature, 1, 2) return HandshakeMsg.postWrite(self, w, trial) class ChangeCipherSpec(Msg): def __init__(self): self.contentType = ContentType.change_cipher_spec self.type = 1 def create(self): self.type = 1 return self def parse(self, p): p.setLengthCheck(1) self.type = p.get(1) p.stopLengthCheck() return self def write(self, trial=False): w = Msg.preWrite(self, trial) w.add(self.type,1) return Msg.postWrite(self, w, trial) class Finished(HandshakeMsg): def __init__(self, version): self.contentType = ContentType.handshake self.version = version self.verify_data = createByteArraySequence([]) def create(self, verify_data): self.verify_data = verify_data return self def parse(self, p): p.startLengthCheck(3) if self.version == (3,0): self.verify_data = p.getFixBytes(36) elif self.version in ((3,1), (3,2)): self.verify_data = p.getFixBytes(12) else: raise AssertionError() p.stopLengthCheck() return self def write(self, trial=False): w = HandshakeMsg.preWrite(self, HandshakeType.finished, trial) w.addFixSeq(self.verify_data, 1) return HandshakeMsg.postWrite(self, w, trial) class ApplicationData(Msg): def __init__(self): self.contentType = ContentType.application_data self.bytes = createByteArraySequence([]) def create(self, bytes): self.bytes = bytes return self def parse(self, p): self.bytes = p.bytes return self def write(self): return self.bytesgdata-2.0.18/samples/apps/marketplace_sample/gdata/tlslite/FileObject.py0000750036466300116100000001522712156622362026326 0ustar afshareng00000000000000"""Class returned by TLSConnection.makefile().""" class FileObject: """This class provides a file object interface to a L{tlslite.TLSConnection.TLSConnection}. Call makefile() on a TLSConnection to create a FileObject instance. This class was copied, with minor modifications, from the _fileobject class in socket.py. Note that fileno() is not implemented.""" default_bufsize = 16384 #TREV: changed from 8192 def __init__(self, sock, mode='rb', bufsize=-1): self._sock = sock self.mode = mode # Not actually used in this version if bufsize < 0: bufsize = self.default_bufsize self.bufsize = bufsize self.softspace = False if bufsize == 0: self._rbufsize = 1 elif bufsize == 1: self._rbufsize = self.default_bufsize else: self._rbufsize = bufsize self._wbufsize = bufsize self._rbuf = "" # A string self._wbuf = [] # A list of strings def _getclosed(self): return self._sock is not None closed = property(_getclosed, doc="True if the file is closed") def close(self): try: if self._sock: for result in self._sock._decrefAsync(): #TREV pass finally: self._sock = None def __del__(self): try: self.close() except: # close() may fail if __init__ didn't complete pass def flush(self): if self._wbuf: buffer = "".join(self._wbuf) self._wbuf = [] self._sock.sendall(buffer) #def fileno(self): # raise NotImplementedError() #TREV def write(self, data): data = str(data) # XXX Should really reject non-string non-buffers if not data: return self._wbuf.append(data) if (self._wbufsize == 0 or self._wbufsize == 1 and '\n' in data or self._get_wbuf_len() >= self._wbufsize): self.flush() def writelines(self, list): # XXX We could do better here for very long lists # XXX Should really reject non-string non-buffers self._wbuf.extend(filter(None, map(str, list))) if (self._wbufsize <= 1 or self._get_wbuf_len() >= self._wbufsize): self.flush() def _get_wbuf_len(self): buf_len = 0 for x in self._wbuf: buf_len += len(x) return buf_len def read(self, size=-1): data = self._rbuf if size < 0: # Read until EOF buffers = [] if data: buffers.append(data) self._rbuf = "" if self._rbufsize <= 1: recv_size = self.default_bufsize else: recv_size = self._rbufsize while True: data = self._sock.recv(recv_size) if not data: break buffers.append(data) return "".join(buffers) else: # Read until size bytes or EOF seen, whichever comes first buf_len = len(data) if buf_len >= size: self._rbuf = data[size:] return data[:size] buffers = [] if data: buffers.append(data) self._rbuf = "" while True: left = size - buf_len recv_size = max(self._rbufsize, left) data = self._sock.recv(recv_size) if not data: break buffers.append(data) n = len(data) if n >= left: self._rbuf = data[left:] buffers[-1] = data[:left] break buf_len += n return "".join(buffers) def readline(self, size=-1): data = self._rbuf if size < 0: # Read until \n or EOF, whichever comes first if self._rbufsize <= 1: # Speed up unbuffered case assert data == "" buffers = [] recv = self._sock.recv while data != "\n": data = recv(1) if not data: break buffers.append(data) return "".join(buffers) nl = data.find('\n') if nl >= 0: nl += 1 self._rbuf = data[nl:] return data[:nl] buffers = [] if data: buffers.append(data) self._rbuf = "" while True: data = self._sock.recv(self._rbufsize) if not data: break buffers.append(data) nl = data.find('\n') if nl >= 0: nl += 1 self._rbuf = data[nl:] buffers[-1] = data[:nl] break return "".join(buffers) else: # Read until size bytes or \n or EOF seen, whichever comes first nl = data.find('\n', 0, size) if nl >= 0: nl += 1 self._rbuf = data[nl:] return data[:nl] buf_len = len(data) if buf_len >= size: self._rbuf = data[size:] return data[:size] buffers = [] if data: buffers.append(data) self._rbuf = "" while True: data = self._sock.recv(self._rbufsize) if not data: break buffers.append(data) left = size - buf_len nl = data.find('\n', 0, left) if nl >= 0: nl += 1 self._rbuf = data[nl:] buffers[-1] = data[:nl] break n = len(data) if n >= left: self._rbuf = data[left:] buffers[-1] = data[:left] break buf_len += n return "".join(buffers) def readlines(self, sizehint=0): total = 0 list = [] while True: line = self.readline() if not line: break list.append(line) total += len(line) if sizehint and total >= sizehint: break return list # Iterator protocols def __iter__(self): return self def next(self): line = self.readline() if not line: raise StopIteration return line gdata-2.0.18/samples/apps/marketplace_sample/gdata/tlslite/X509.py0000750036466300116100000001027712156622362024765 0ustar afshareng00000000000000"""Class representing an X.509 certificate.""" from utils.ASN1Parser import ASN1Parser from utils.cryptomath import * from utils.keyfactory import _createPublicRSAKey class X509: """This class represents an X.509 certificate. @type bytes: L{array.array} of unsigned bytes @ivar bytes: The DER-encoded ASN.1 certificate @type publicKey: L{tlslite.utils.RSAKey.RSAKey} @ivar publicKey: The subject public key from the certificate. """ def __init__(self): self.bytes = createByteArraySequence([]) self.publicKey = None def parse(self, s): """Parse a PEM-encoded X.509 certificate. @type s: str @param s: A PEM-encoded X.509 certificate (i.e. a base64-encoded certificate wrapped with "-----BEGIN CERTIFICATE-----" and "-----END CERTIFICATE-----" tags). """ start = s.find("-----BEGIN CERTIFICATE-----") end = s.find("-----END CERTIFICATE-----") if start == -1: raise SyntaxError("Missing PEM prefix") if end == -1: raise SyntaxError("Missing PEM postfix") s = s[start+len("-----BEGIN CERTIFICATE-----") : end] bytes = base64ToBytes(s) self.parseBinary(bytes) return self def parseBinary(self, bytes): """Parse a DER-encoded X.509 certificate. @type bytes: str or L{array.array} of unsigned bytes @param bytes: A DER-encoded X.509 certificate. """ if isinstance(bytes, type("")): bytes = stringToBytes(bytes) self.bytes = bytes p = ASN1Parser(bytes) #Get the tbsCertificate tbsCertificateP = p.getChild(0) #Is the optional version field present? #This determines which index the key is at. if tbsCertificateP.value[0]==0xA0: subjectPublicKeyInfoIndex = 6 else: subjectPublicKeyInfoIndex = 5 #Get the subjectPublicKeyInfo subjectPublicKeyInfoP = tbsCertificateP.getChild(\ subjectPublicKeyInfoIndex) #Get the algorithm algorithmP = subjectPublicKeyInfoP.getChild(0) rsaOID = algorithmP.value if list(rsaOID) != [6, 9, 42, 134, 72, 134, 247, 13, 1, 1, 1, 5, 0]: raise SyntaxError("Unrecognized AlgorithmIdentifier") #Get the subjectPublicKey subjectPublicKeyP = subjectPublicKeyInfoP.getChild(1) #Adjust for BIT STRING encapsulation if (subjectPublicKeyP.value[0] !=0): raise SyntaxError() subjectPublicKeyP = ASN1Parser(subjectPublicKeyP.value[1:]) #Get the modulus and exponent modulusP = subjectPublicKeyP.getChild(0) publicExponentP = subjectPublicKeyP.getChild(1) #Decode them into numbers n = bytesToNumber(modulusP.value) e = bytesToNumber(publicExponentP.value) #Create a public key instance self.publicKey = _createPublicRSAKey(n, e) def getFingerprint(self): """Get the hex-encoded fingerprint of this certificate. @rtype: str @return: A hex-encoded fingerprint. """ return sha.sha(self.bytes).hexdigest() def getCommonName(self): """Get the Subject's Common Name from the certificate. The cryptlib_py module must be installed in order to use this function. @rtype: str or None @return: The CN component of the certificate's subject DN, if present. """ import cryptlib_py import array c = cryptlib_py.cryptImportCert(self.bytes, cryptlib_py.CRYPT_UNUSED) name = cryptlib_py.CRYPT_CERTINFO_COMMONNAME try: try: length = cryptlib_py.cryptGetAttributeString(c, name, None) returnVal = array.array('B', [0] * length) cryptlib_py.cryptGetAttributeString(c, name, returnVal) returnVal = returnVal.tostring() except cryptlib_py.CryptException, e: if e[0] == cryptlib_py.CRYPT_ERROR_NOTFOUND: returnVal = None return returnVal finally: cryptlib_py.cryptDestroyCert(c) def writeBytes(self): return self.bytes gdata-2.0.18/samples/apps/marketplace_sample/gdata/tlslite/TLSConnection.py0000750036466300116100000021131312156622362026774 0ustar afshareng00000000000000""" MAIN CLASS FOR TLS LITE (START HERE!). """ from __future__ import generators import socket from utils.compat import formatExceptionTrace from TLSRecordLayer import TLSRecordLayer from Session import Session from constants import * from utils.cryptomath import getRandomBytes from errors import * from messages import * from mathtls import * from HandshakeSettings import HandshakeSettings class TLSConnection(TLSRecordLayer): """ This class wraps a socket and provides TLS handshaking and data transfer. To use this class, create a new instance, passing a connected socket into the constructor. Then call some handshake function. If the handshake completes without raising an exception, then a TLS connection has been negotiated. You can transfer data over this connection as if it were a socket. This class provides both synchronous and asynchronous versions of its key functions. The synchronous versions should be used when writing single-or multi-threaded code using blocking sockets. The asynchronous versions should be used when performing asynchronous, event-based I/O with non-blocking sockets. Asynchronous I/O is a complicated subject; typically, you should not use the asynchronous functions directly, but should use some framework like asyncore or Twisted which TLS Lite integrates with (see L{tlslite.integration.TLSAsyncDispatcherMixIn.TLSAsyncDispatcherMixIn} or L{tlslite.integration.TLSTwistedProtocolWrapper.TLSTwistedProtocolWrapper}). """ def __init__(self, sock): """Create a new TLSConnection instance. @param sock: The socket data will be transmitted on. The socket should already be connected. It may be in blocking or non-blocking mode. @type sock: L{socket.socket} """ TLSRecordLayer.__init__(self, sock) def handshakeClientSRP(self, username, password, session=None, settings=None, checker=None, async=False): """Perform an SRP handshake in the role of client. This function performs a TLS/SRP handshake. SRP mutually authenticates both parties to each other using only a username and password. This function may also perform a combined SRP and server-certificate handshake, if the server chooses to authenticate itself with a certificate chain in addition to doing SRP. TLS/SRP is non-standard. Most TLS implementations don't support it. See U{http://www.ietf.org/html.charters/tls-charter.html} or U{http://trevp.net/tlssrp/} for the latest information on TLS/SRP. Like any handshake function, this can be called on a closed TLS connection, or on a TLS connection that is already open. If called on an open connection it performs a re-handshake. If the function completes without raising an exception, the TLS connection will be open and available for data transfer. If an exception is raised, the connection will have been automatically closed (if it was ever open). @type username: str @param username: The SRP username. @type password: str @param password: The SRP password. @type session: L{tlslite.Session.Session} @param session: A TLS session to attempt to resume. This session must be an SRP session performed with the same username and password as were passed in. If the resumption does not succeed, a full SRP handshake will be performed. @type settings: L{tlslite.HandshakeSettings.HandshakeSettings} @param settings: Various settings which can be used to control the ciphersuites, certificate types, and SSL/TLS versions offered by the client. @type checker: L{tlslite.Checker.Checker} @param checker: A Checker instance. This instance will be invoked to examine the other party's authentication credentials, if the handshake completes succesfully. @type async: bool @param async: If False, this function will block until the handshake is completed. If True, this function will return a generator. Successive invocations of the generator will return 0 if it is waiting to read from the socket, 1 if it is waiting to write to the socket, or will raise StopIteration if the handshake operation is completed. @rtype: None or an iterable @return: If 'async' is True, a generator object will be returned. @raise socket.error: If a socket error occurs. @raise tlslite.errors.TLSAbruptCloseError: If the socket is closed without a preceding alert. @raise tlslite.errors.TLSAlert: If a TLS alert is signalled. @raise tlslite.errors.TLSAuthenticationError: If the checker doesn't like the other party's authentication credentials. """ handshaker = self._handshakeClientAsync(srpParams=(username, password), session=session, settings=settings, checker=checker) if async: return handshaker for result in handshaker: pass def handshakeClientCert(self, certChain=None, privateKey=None, session=None, settings=None, checker=None, async=False): """Perform a certificate-based handshake in the role of client. This function performs an SSL or TLS handshake. The server will authenticate itself using an X.509 or cryptoID certificate chain. If the handshake succeeds, the server's certificate chain will be stored in the session's serverCertChain attribute. Unless a checker object is passed in, this function does no validation or checking of the server's certificate chain. If the server requests client authentication, the client will send the passed-in certificate chain, and use the passed-in private key to authenticate itself. If no certificate chain and private key were passed in, the client will attempt to proceed without client authentication. The server may or may not allow this. Like any handshake function, this can be called on a closed TLS connection, or on a TLS connection that is already open. If called on an open connection it performs a re-handshake. If the function completes without raising an exception, the TLS connection will be open and available for data transfer. If an exception is raised, the connection will have been automatically closed (if it was ever open). @type certChain: L{tlslite.X509CertChain.X509CertChain} or L{cryptoIDlib.CertChain.CertChain} @param certChain: The certificate chain to be used if the server requests client authentication. @type privateKey: L{tlslite.utils.RSAKey.RSAKey} @param privateKey: The private key to be used if the server requests client authentication. @type session: L{tlslite.Session.Session} @param session: A TLS session to attempt to resume. If the resumption does not succeed, a full handshake will be performed. @type settings: L{tlslite.HandshakeSettings.HandshakeSettings} @param settings: Various settings which can be used to control the ciphersuites, certificate types, and SSL/TLS versions offered by the client. @type checker: L{tlslite.Checker.Checker} @param checker: A Checker instance. This instance will be invoked to examine the other party's authentication credentials, if the handshake completes succesfully. @type async: bool @param async: If False, this function will block until the handshake is completed. If True, this function will return a generator. Successive invocations of the generator will return 0 if it is waiting to read from the socket, 1 if it is waiting to write to the socket, or will raise StopIteration if the handshake operation is completed. @rtype: None or an iterable @return: If 'async' is True, a generator object will be returned. @raise socket.error: If a socket error occurs. @raise tlslite.errors.TLSAbruptCloseError: If the socket is closed without a preceding alert. @raise tlslite.errors.TLSAlert: If a TLS alert is signalled. @raise tlslite.errors.TLSAuthenticationError: If the checker doesn't like the other party's authentication credentials. """ handshaker = self._handshakeClientAsync(certParams=(certChain, privateKey), session=session, settings=settings, checker=checker) if async: return handshaker for result in handshaker: pass def handshakeClientUnknown(self, srpCallback=None, certCallback=None, session=None, settings=None, checker=None, async=False): """Perform a to-be-determined type of handshake in the role of client. This function performs an SSL or TLS handshake. If the server requests client certificate authentication, the certCallback will be invoked and should return a (certChain, privateKey) pair. If the callback returns None, the library will attempt to proceed without client authentication. The server may or may not allow this. If the server requests SRP authentication, the srpCallback will be invoked and should return a (username, password) pair. If the callback returns None, the local implementation will signal a user_canceled error alert. After the handshake completes, the client can inspect the connection's session attribute to determine what type of authentication was performed. Like any handshake function, this can be called on a closed TLS connection, or on a TLS connection that is already open. If called on an open connection it performs a re-handshake. If the function completes without raising an exception, the TLS connection will be open and available for data transfer. If an exception is raised, the connection will have been automatically closed (if it was ever open). @type srpCallback: callable @param srpCallback: The callback to be used if the server requests SRP authentication. If None, the client will not offer support for SRP ciphersuites. @type certCallback: callable @param certCallback: The callback to be used if the server requests client certificate authentication. @type session: L{tlslite.Session.Session} @param session: A TLS session to attempt to resume. If the resumption does not succeed, a full handshake will be performed. @type settings: L{tlslite.HandshakeSettings.HandshakeSettings} @param settings: Various settings which can be used to control the ciphersuites, certificate types, and SSL/TLS versions offered by the client. @type checker: L{tlslite.Checker.Checker} @param checker: A Checker instance. This instance will be invoked to examine the other party's authentication credentials, if the handshake completes succesfully. @type async: bool @param async: If False, this function will block until the handshake is completed. If True, this function will return a generator. Successive invocations of the generator will return 0 if it is waiting to read from the socket, 1 if it is waiting to write to the socket, or will raise StopIteration if the handshake operation is completed. @rtype: None or an iterable @return: If 'async' is True, a generator object will be returned. @raise socket.error: If a socket error occurs. @raise tlslite.errors.TLSAbruptCloseError: If the socket is closed without a preceding alert. @raise tlslite.errors.TLSAlert: If a TLS alert is signalled. @raise tlslite.errors.TLSAuthenticationError: If the checker doesn't like the other party's authentication credentials. """ handshaker = self._handshakeClientAsync(unknownParams=(srpCallback, certCallback), session=session, settings=settings, checker=checker) if async: return handshaker for result in handshaker: pass def handshakeClientSharedKey(self, username, sharedKey, settings=None, checker=None, async=False): """Perform a shared-key handshake in the role of client. This function performs a shared-key handshake. Using shared symmetric keys of high entropy (128 bits or greater) mutually authenticates both parties to each other. TLS with shared-keys is non-standard. Most TLS implementations don't support it. See U{http://www.ietf.org/html.charters/tls-charter.html} for the latest information on TLS with shared-keys. If the shared-keys Internet-Draft changes or is superceded, TLS Lite will track those changes, so the shared-key support in later versions of TLS Lite may become incompatible with this version. Like any handshake function, this can be called on a closed TLS connection, or on a TLS connection that is already open. If called on an open connection it performs a re-handshake. If the function completes without raising an exception, the TLS connection will be open and available for data transfer. If an exception is raised, the connection will have been automatically closed (if it was ever open). @type username: str @param username: The shared-key username. @type sharedKey: str @param sharedKey: The shared key. @type settings: L{tlslite.HandshakeSettings.HandshakeSettings} @param settings: Various settings which can be used to control the ciphersuites, certificate types, and SSL/TLS versions offered by the client. @type checker: L{tlslite.Checker.Checker} @param checker: A Checker instance. This instance will be invoked to examine the other party's authentication credentials, if the handshake completes succesfully. @type async: bool @param async: If False, this function will block until the handshake is completed. If True, this function will return a generator. Successive invocations of the generator will return 0 if it is waiting to read from the socket, 1 if it is waiting to write to the socket, or will raise StopIteration if the handshake operation is completed. @rtype: None or an iterable @return: If 'async' is True, a generator object will be returned. @raise socket.error: If a socket error occurs. @raise tlslite.errors.TLSAbruptCloseError: If the socket is closed without a preceding alert. @raise tlslite.errors.TLSAlert: If a TLS alert is signalled. @raise tlslite.errors.TLSAuthenticationError: If the checker doesn't like the other party's authentication credentials. """ handshaker = self._handshakeClientAsync(sharedKeyParams=(username, sharedKey), settings=settings, checker=checker) if async: return handshaker for result in handshaker: pass def _handshakeClientAsync(self, srpParams=(), certParams=(), unknownParams=(), sharedKeyParams=(), session=None, settings=None, checker=None, recursive=False): handshaker = self._handshakeClientAsyncHelper(srpParams=srpParams, certParams=certParams, unknownParams=unknownParams, sharedKeyParams=sharedKeyParams, session=session, settings=settings, recursive=recursive) for result in self._handshakeWrapperAsync(handshaker, checker): yield result def _handshakeClientAsyncHelper(self, srpParams, certParams, unknownParams, sharedKeyParams, session, settings, recursive): if not recursive: self._handshakeStart(client=True) #Unpack parameters srpUsername = None # srpParams password = None # srpParams clientCertChain = None # certParams privateKey = None # certParams srpCallback = None # unknownParams certCallback = None # unknownParams #session # sharedKeyParams (or session) #settings # settings if srpParams: srpUsername, password = srpParams elif certParams: clientCertChain, privateKey = certParams elif unknownParams: srpCallback, certCallback = unknownParams elif sharedKeyParams: session = Session()._createSharedKey(*sharedKeyParams) if not settings: settings = HandshakeSettings() settings = settings._filter() #Validate parameters if srpUsername and not password: raise ValueError("Caller passed a username but no password") if password and not srpUsername: raise ValueError("Caller passed a password but no username") if clientCertChain and not privateKey: raise ValueError("Caller passed a certChain but no privateKey") if privateKey and not clientCertChain: raise ValueError("Caller passed a privateKey but no certChain") if clientCertChain: foundType = False try: import cryptoIDlib.CertChain if isinstance(clientCertChain, cryptoIDlib.CertChain.CertChain): if "cryptoID" not in settings.certificateTypes: raise ValueError("Client certificate doesn't "\ "match Handshake Settings") settings.certificateTypes = ["cryptoID"] foundType = True except ImportError: pass if not foundType and isinstance(clientCertChain, X509CertChain): if "x509" not in settings.certificateTypes: raise ValueError("Client certificate doesn't match "\ "Handshake Settings") settings.certificateTypes = ["x509"] foundType = True if not foundType: raise ValueError("Unrecognized certificate type") if session: if not session.valid(): session = None #ignore non-resumable sessions... elif session.resumable and \ (session.srpUsername != srpUsername): raise ValueError("Session username doesn't match") #Add Faults to parameters if srpUsername and self.fault == Fault.badUsername: srpUsername += "GARBAGE" if password and self.fault == Fault.badPassword: password += "GARBAGE" if sharedKeyParams: identifier = sharedKeyParams[0] sharedKey = sharedKeyParams[1] if self.fault == Fault.badIdentifier: identifier += "GARBAGE" session = Session()._createSharedKey(identifier, sharedKey) elif self.fault == Fault.badSharedKey: sharedKey += "GARBAGE" session = Session()._createSharedKey(identifier, sharedKey) #Initialize locals serverCertChain = None cipherSuite = 0 certificateType = CertificateType.x509 premasterSecret = None #Get client nonce clientRandom = getRandomBytes(32) #Initialize acceptable ciphersuites cipherSuites = [] if srpParams: cipherSuites += CipherSuite.getSrpRsaSuites(settings.cipherNames) cipherSuites += CipherSuite.getSrpSuites(settings.cipherNames) elif certParams: cipherSuites += CipherSuite.getRsaSuites(settings.cipherNames) elif unknownParams: if srpCallback: cipherSuites += \ CipherSuite.getSrpRsaSuites(settings.cipherNames) cipherSuites += \ CipherSuite.getSrpSuites(settings.cipherNames) cipherSuites += CipherSuite.getRsaSuites(settings.cipherNames) elif sharedKeyParams: cipherSuites += CipherSuite.getRsaSuites(settings.cipherNames) else: cipherSuites += CipherSuite.getRsaSuites(settings.cipherNames) #Initialize acceptable certificate types certificateTypes = settings._getCertificateTypes() #Tentatively set the version to the client's minimum version. #We'll use this for the ClientHello, and if an error occurs #parsing the Server Hello, we'll use this version for the response self.version = settings.maxVersion #Either send ClientHello (with a resumable session)... if session: #If it's a resumable (i.e. not a shared-key session), then its #ciphersuite must be one of the acceptable ciphersuites if (not sharedKeyParams) and \ session.cipherSuite not in cipherSuites: raise ValueError("Session's cipher suite not consistent "\ "with parameters") else: clientHello = ClientHello() clientHello.create(settings.maxVersion, clientRandom, session.sessionID, cipherSuites, certificateTypes, session.srpUsername) #Or send ClientHello (without) else: clientHello = ClientHello() clientHello.create(settings.maxVersion, clientRandom, createByteArraySequence([]), cipherSuites, certificateTypes, srpUsername) for result in self._sendMsg(clientHello): yield result #Get ServerHello (or missing_srp_username) for result in self._getMsg((ContentType.handshake, ContentType.alert), HandshakeType.server_hello): if result in (0,1): yield result else: break msg = result if isinstance(msg, ServerHello): serverHello = msg elif isinstance(msg, Alert): alert = msg #If it's not a missing_srp_username, re-raise if alert.description != AlertDescription.missing_srp_username: self._shutdown(False) raise TLSRemoteAlert(alert) #If we're not in SRP callback mode, we won't have offered SRP #without a username, so we shouldn't get this alert if not srpCallback: for result in self._sendError(\ AlertDescription.unexpected_message): yield result srpParams = srpCallback() #If the callback returns None, cancel the handshake if srpParams == None: for result in self._sendError(AlertDescription.user_canceled): yield result #Recursively perform handshake for result in self._handshakeClientAsyncHelper(srpParams, None, None, None, None, settings, True): yield result return #Get the server version. Do this before anything else, so any #error alerts will use the server's version self.version = serverHello.server_version #Future responses from server must use this version self._versionCheck = True #Check ServerHello if serverHello.server_version < settings.minVersion: for result in self._sendError(\ AlertDescription.protocol_version, "Too old version: %s" % str(serverHello.server_version)): yield result if serverHello.server_version > settings.maxVersion: for result in self._sendError(\ AlertDescription.protocol_version, "Too new version: %s" % str(serverHello.server_version)): yield result if serverHello.cipher_suite not in cipherSuites: for result in self._sendError(\ AlertDescription.illegal_parameter, "Server responded with incorrect ciphersuite"): yield result if serverHello.certificate_type not in certificateTypes: for result in self._sendError(\ AlertDescription.illegal_parameter, "Server responded with incorrect certificate type"): yield result if serverHello.compression_method != 0: for result in self._sendError(\ AlertDescription.illegal_parameter, "Server responded with incorrect compression method"): yield result #Get the server nonce serverRandom = serverHello.random #If the server agrees to resume if session and session.sessionID and \ serverHello.session_id == session.sessionID: #If a shared-key, we're flexible about suites; otherwise the #server-chosen suite has to match the session's suite if sharedKeyParams: session.cipherSuite = serverHello.cipher_suite elif serverHello.cipher_suite != session.cipherSuite: for result in self._sendError(\ AlertDescription.illegal_parameter,\ "Server's ciphersuite doesn't match session"): yield result #Set the session for this connection self.session = session #Calculate pending connection states self._calcPendingStates(clientRandom, serverRandom, settings.cipherImplementations) #Exchange ChangeCipherSpec and Finished messages for result in self._getFinished(): yield result for result in self._sendFinished(): yield result #Mark the connection as open self._handshakeDone(resumed=True) #If server DOES NOT agree to resume else: if sharedKeyParams: for result in self._sendError(\ AlertDescription.user_canceled, "Was expecting a shared-key resumption"): yield result #We've already validated these cipherSuite = serverHello.cipher_suite certificateType = serverHello.certificate_type #If the server chose an SRP suite... if cipherSuite in CipherSuite.srpSuites: #Get ServerKeyExchange, ServerHelloDone for result in self._getMsg(ContentType.handshake, HandshakeType.server_key_exchange, cipherSuite): if result in (0,1): yield result else: break serverKeyExchange = result for result in self._getMsg(ContentType.handshake, HandshakeType.server_hello_done): if result in (0,1): yield result else: break serverHelloDone = result #If the server chose an SRP+RSA suite... elif cipherSuite in CipherSuite.srpRsaSuites: #Get Certificate, ServerKeyExchange, ServerHelloDone for result in self._getMsg(ContentType.handshake, HandshakeType.certificate, certificateType): if result in (0,1): yield result else: break serverCertificate = result for result in self._getMsg(ContentType.handshake, HandshakeType.server_key_exchange, cipherSuite): if result in (0,1): yield result else: break serverKeyExchange = result for result in self._getMsg(ContentType.handshake, HandshakeType.server_hello_done): if result in (0,1): yield result else: break serverHelloDone = result #If the server chose an RSA suite... elif cipherSuite in CipherSuite.rsaSuites: #Get Certificate[, CertificateRequest], ServerHelloDone for result in self._getMsg(ContentType.handshake, HandshakeType.certificate, certificateType): if result in (0,1): yield result else: break serverCertificate = result for result in self._getMsg(ContentType.handshake, (HandshakeType.server_hello_done, HandshakeType.certificate_request)): if result in (0,1): yield result else: break msg = result certificateRequest = None if isinstance(msg, CertificateRequest): certificateRequest = msg for result in self._getMsg(ContentType.handshake, HandshakeType.server_hello_done): if result in (0,1): yield result else: break serverHelloDone = result elif isinstance(msg, ServerHelloDone): serverHelloDone = msg else: raise AssertionError() #Calculate SRP premaster secret, if server chose an SRP or #SRP+RSA suite if cipherSuite in CipherSuite.srpSuites + \ CipherSuite.srpRsaSuites: #Get and check the server's group parameters and B value N = serverKeyExchange.srp_N g = serverKeyExchange.srp_g s = serverKeyExchange.srp_s B = serverKeyExchange.srp_B if (g,N) not in goodGroupParameters: for result in self._sendError(\ AlertDescription.untrusted_srp_parameters, "Unknown group parameters"): yield result if numBits(N) < settings.minKeySize: for result in self._sendError(\ AlertDescription.untrusted_srp_parameters, "N value is too small: %d" % numBits(N)): yield result if numBits(N) > settings.maxKeySize: for result in self._sendError(\ AlertDescription.untrusted_srp_parameters, "N value is too large: %d" % numBits(N)): yield result if B % N == 0: for result in self._sendError(\ AlertDescription.illegal_parameter, "Suspicious B value"): yield result #Check the server's signature, if server chose an #SRP+RSA suite if cipherSuite in CipherSuite.srpRsaSuites: #Hash ServerKeyExchange/ServerSRPParams hashBytes = serverKeyExchange.hash(clientRandom, serverRandom) #Extract signature bytes from ServerKeyExchange sigBytes = serverKeyExchange.signature if len(sigBytes) == 0: for result in self._sendError(\ AlertDescription.illegal_parameter, "Server sent an SRP ServerKeyExchange "\ "message without a signature"): yield result #Get server's public key from the Certificate message for result in self._getKeyFromChain(serverCertificate, settings): if result in (0,1): yield result else: break publicKey, serverCertChain = result #Verify signature if not publicKey.verify(sigBytes, hashBytes): for result in self._sendError(\ AlertDescription.decrypt_error, "Signature failed to verify"): yield result #Calculate client's ephemeral DH values (a, A) a = bytesToNumber(getRandomBytes(32)) A = powMod(g, a, N) #Calculate client's static DH values (x, v) x = makeX(bytesToString(s), srpUsername, password) v = powMod(g, x, N) #Calculate u u = makeU(N, A, B) #Calculate premaster secret k = makeK(N, g) S = powMod((B - (k*v)) % N, a+(u*x), N) if self.fault == Fault.badA: A = N S = 0 premasterSecret = numberToBytes(S) #Send ClientKeyExchange for result in self._sendMsg(\ ClientKeyExchange(cipherSuite).createSRP(A)): yield result #Calculate RSA premaster secret, if server chose an RSA suite elif cipherSuite in CipherSuite.rsaSuites: #Handle the presence of a CertificateRequest if certificateRequest: if unknownParams and certCallback: certParamsNew = certCallback() if certParamsNew: clientCertChain, privateKey = certParamsNew #Get server's public key from the Certificate message for result in self._getKeyFromChain(serverCertificate, settings): if result in (0,1): yield result else: break publicKey, serverCertChain = result #Calculate premaster secret premasterSecret = getRandomBytes(48) premasterSecret[0] = settings.maxVersion[0] premasterSecret[1] = settings.maxVersion[1] if self.fault == Fault.badPremasterPadding: premasterSecret[0] = 5 if self.fault == Fault.shortPremasterSecret: premasterSecret = premasterSecret[:-1] #Encrypt premaster secret to server's public key encryptedPreMasterSecret = publicKey.encrypt(premasterSecret) #If client authentication was requested, send Certificate #message, either with certificates or empty if certificateRequest: clientCertificate = Certificate(certificateType) if clientCertChain: #Check to make sure we have the same type of #certificates the server requested wrongType = False if certificateType == CertificateType.x509: if not isinstance(clientCertChain, X509CertChain): wrongType = True elif certificateType == CertificateType.cryptoID: if not isinstance(clientCertChain, cryptoIDlib.CertChain.CertChain): wrongType = True if wrongType: for result in self._sendError(\ AlertDescription.handshake_failure, "Client certificate is of wrong type"): yield result clientCertificate.create(clientCertChain) for result in self._sendMsg(clientCertificate): yield result else: #The server didn't request client auth, so we #zeroize these so the clientCertChain won't be #stored in the session. privateKey = None clientCertChain = None #Send ClientKeyExchange clientKeyExchange = ClientKeyExchange(cipherSuite, self.version) clientKeyExchange.createRSA(encryptedPreMasterSecret) for result in self._sendMsg(clientKeyExchange): yield result #If client authentication was requested and we have a #private key, send CertificateVerify if certificateRequest and privateKey: if self.version == (3,0): #Create a temporary session object, just for the #purpose of creating the CertificateVerify session = Session() session._calcMasterSecret(self.version, premasterSecret, clientRandom, serverRandom) verifyBytes = self._calcSSLHandshakeHash(\ session.masterSecret, "") elif self.version in ((3,1), (3,2)): verifyBytes = stringToBytes(\ self._handshake_md5.digest() + \ self._handshake_sha.digest()) if self.fault == Fault.badVerifyMessage: verifyBytes[0] = ((verifyBytes[0]+1) % 256) signedBytes = privateKey.sign(verifyBytes) certificateVerify = CertificateVerify() certificateVerify.create(signedBytes) for result in self._sendMsg(certificateVerify): yield result #Create the session object self.session = Session() self.session._calcMasterSecret(self.version, premasterSecret, clientRandom, serverRandom) self.session.sessionID = serverHello.session_id self.session.cipherSuite = cipherSuite self.session.srpUsername = srpUsername self.session.clientCertChain = clientCertChain self.session.serverCertChain = serverCertChain #Calculate pending connection states self._calcPendingStates(clientRandom, serverRandom, settings.cipherImplementations) #Exchange ChangeCipherSpec and Finished messages for result in self._sendFinished(): yield result for result in self._getFinished(): yield result #Mark the connection as open self.session._setResumable(True) self._handshakeDone(resumed=False) def handshakeServer(self, sharedKeyDB=None, verifierDB=None, certChain=None, privateKey=None, reqCert=False, sessionCache=None, settings=None, checker=None): """Perform a handshake in the role of server. This function performs an SSL or TLS handshake. Depending on the arguments and the behavior of the client, this function can perform a shared-key, SRP, or certificate-based handshake. It can also perform a combined SRP and server-certificate handshake. Like any handshake function, this can be called on a closed TLS connection, or on a TLS connection that is already open. If called on an open connection it performs a re-handshake. This function does not send a Hello Request message before performing the handshake, so if re-handshaking is required, the server must signal the client to begin the re-handshake through some other means. If the function completes without raising an exception, the TLS connection will be open and available for data transfer. If an exception is raised, the connection will have been automatically closed (if it was ever open). @type sharedKeyDB: L{tlslite.SharedKeyDB.SharedKeyDB} @param sharedKeyDB: A database of shared symmetric keys associated with usernames. If the client performs a shared-key handshake, the session's sharedKeyUsername attribute will be set. @type verifierDB: L{tlslite.VerifierDB.VerifierDB} @param verifierDB: A database of SRP password verifiers associated with usernames. If the client performs an SRP handshake, the session's srpUsername attribute will be set. @type certChain: L{tlslite.X509CertChain.X509CertChain} or L{cryptoIDlib.CertChain.CertChain} @param certChain: The certificate chain to be used if the client requests server certificate authentication. @type privateKey: L{tlslite.utils.RSAKey.RSAKey} @param privateKey: The private key to be used if the client requests server certificate authentication. @type reqCert: bool @param reqCert: Whether to request client certificate authentication. This only applies if the client chooses server certificate authentication; if the client chooses SRP or shared-key authentication, this will be ignored. If the client performs a client certificate authentication, the sessions's clientCertChain attribute will be set. @type sessionCache: L{tlslite.SessionCache.SessionCache} @param sessionCache: An in-memory cache of resumable sessions. The client can resume sessions from this cache. Alternatively, if the client performs a full handshake, a new session will be added to the cache. @type settings: L{tlslite.HandshakeSettings.HandshakeSettings} @param settings: Various settings which can be used to control the ciphersuites and SSL/TLS version chosen by the server. @type checker: L{tlslite.Checker.Checker} @param checker: A Checker instance. This instance will be invoked to examine the other party's authentication credentials, if the handshake completes succesfully. @raise socket.error: If a socket error occurs. @raise tlslite.errors.TLSAbruptCloseError: If the socket is closed without a preceding alert. @raise tlslite.errors.TLSAlert: If a TLS alert is signalled. @raise tlslite.errors.TLSAuthenticationError: If the checker doesn't like the other party's authentication credentials. """ for result in self.handshakeServerAsync(sharedKeyDB, verifierDB, certChain, privateKey, reqCert, sessionCache, settings, checker): pass def handshakeServerAsync(self, sharedKeyDB=None, verifierDB=None, certChain=None, privateKey=None, reqCert=False, sessionCache=None, settings=None, checker=None): """Start a server handshake operation on the TLS connection. This function returns a generator which behaves similarly to handshakeServer(). Successive invocations of the generator will return 0 if it is waiting to read from the socket, 1 if it is waiting to write to the socket, or it will raise StopIteration if the handshake operation is complete. @rtype: iterable @return: A generator; see above for details. """ handshaker = self._handshakeServerAsyncHelper(\ sharedKeyDB=sharedKeyDB, verifierDB=verifierDB, certChain=certChain, privateKey=privateKey, reqCert=reqCert, sessionCache=sessionCache, settings=settings) for result in self._handshakeWrapperAsync(handshaker, checker): yield result def _handshakeServerAsyncHelper(self, sharedKeyDB, verifierDB, certChain, privateKey, reqCert, sessionCache, settings): self._handshakeStart(client=False) if (not sharedKeyDB) and (not verifierDB) and (not certChain): raise ValueError("Caller passed no authentication credentials") if certChain and not privateKey: raise ValueError("Caller passed a certChain but no privateKey") if privateKey and not certChain: raise ValueError("Caller passed a privateKey but no certChain") if not settings: settings = HandshakeSettings() settings = settings._filter() #Initialize acceptable cipher suites cipherSuites = [] if verifierDB: if certChain: cipherSuites += \ CipherSuite.getSrpRsaSuites(settings.cipherNames) cipherSuites += CipherSuite.getSrpSuites(settings.cipherNames) if sharedKeyDB or certChain: cipherSuites += CipherSuite.getRsaSuites(settings.cipherNames) #Initialize acceptable certificate type certificateType = None if certChain: try: import cryptoIDlib.CertChain if isinstance(certChain, cryptoIDlib.CertChain.CertChain): certificateType = CertificateType.cryptoID except ImportError: pass if isinstance(certChain, X509CertChain): certificateType = CertificateType.x509 if certificateType == None: raise ValueError("Unrecognized certificate type") #Initialize locals clientCertChain = None serverCertChain = None #We may set certChain to this later postFinishedError = None #Tentatively set version to most-desirable version, so if an error #occurs parsing the ClientHello, this is what we'll use for the #error alert self.version = settings.maxVersion #Get ClientHello for result in self._getMsg(ContentType.handshake, HandshakeType.client_hello): if result in (0,1): yield result else: break clientHello = result #If client's version is too low, reject it if clientHello.client_version < settings.minVersion: self.version = settings.minVersion for result in self._sendError(\ AlertDescription.protocol_version, "Too old version: %s" % str(clientHello.client_version)): yield result #If client's version is too high, propose my highest version elif clientHello.client_version > settings.maxVersion: self.version = settings.maxVersion else: #Set the version to the client's version self.version = clientHello.client_version #Get the client nonce; create server nonce clientRandom = clientHello.random serverRandom = getRandomBytes(32) #Calculate the first cipher suite intersection. #This is the 'privileged' ciphersuite. We'll use it if we're #doing a shared-key resumption or a new negotiation. In fact, #the only time we won't use it is if we're resuming a non-sharedkey #session, in which case we use the ciphersuite from the session. # #Given the current ciphersuite ordering, this means we prefer SRP #over non-SRP. for cipherSuite in cipherSuites: if cipherSuite in clientHello.cipher_suites: break else: for result in self._sendError(\ AlertDescription.handshake_failure): yield result #If resumption was requested... if clientHello.session_id and (sharedKeyDB or sessionCache): session = None #Check in the sharedKeys container if sharedKeyDB and len(clientHello.session_id)==16: try: #Trim off zero padding, if any for x in range(16): if clientHello.session_id[x]==0: break self.allegedSharedKeyUsername = bytesToString(\ clientHello.session_id[:x]) session = sharedKeyDB[self.allegedSharedKeyUsername] if not session.sharedKey: raise AssertionError() #use privileged ciphersuite session.cipherSuite = cipherSuite except KeyError: pass #Then check in the session cache if sessionCache and not session: try: session = sessionCache[bytesToString(\ clientHello.session_id)] if session.sharedKey: raise AssertionError() if not session.resumable: raise AssertionError() #Check for consistency with ClientHello if session.cipherSuite not in cipherSuites: for result in self._sendError(\ AlertDescription.handshake_failure): yield result if session.cipherSuite not in clientHello.cipher_suites: for result in self._sendError(\ AlertDescription.handshake_failure): yield result if clientHello.srp_username: if clientHello.srp_username != session.srpUsername: for result in self._sendError(\ AlertDescription.handshake_failure): yield result except KeyError: pass #If a session is found.. if session: #Set the session self.session = session #Send ServerHello serverHello = ServerHello() serverHello.create(self.version, serverRandom, session.sessionID, session.cipherSuite, certificateType) for result in self._sendMsg(serverHello): yield result #From here on, the client's messages must have the right version self._versionCheck = True #Calculate pending connection states self._calcPendingStates(clientRandom, serverRandom, settings.cipherImplementations) #Exchange ChangeCipherSpec and Finished messages for result in self._sendFinished(): yield result for result in self._getFinished(): yield result #Mark the connection as open self._handshakeDone(resumed=True) return #If not a resumption... #TRICKY: we might have chosen an RSA suite that was only deemed #acceptable because of the shared-key resumption. If the shared- #key resumption failed, because the identifier wasn't recognized, #we might fall through to here, where we have an RSA suite #chosen, but no certificate. if cipherSuite in CipherSuite.rsaSuites and not certChain: for result in self._sendError(\ AlertDescription.handshake_failure): yield result #If an RSA suite is chosen, check for certificate type intersection #(We do this check down here because if the mismatch occurs but the # client is using a shared-key session, it's okay) if cipherSuite in CipherSuite.rsaSuites + \ CipherSuite.srpRsaSuites: if certificateType not in clientHello.certificate_types: for result in self._sendError(\ AlertDescription.handshake_failure, "the client doesn't support my certificate type"): yield result #Move certChain -> serverCertChain, now that we're using it serverCertChain = certChain #Create sessionID if sessionCache: sessionID = getRandomBytes(32) else: sessionID = createByteArraySequence([]) #If we've selected an SRP suite, exchange keys and calculate #premaster secret: if cipherSuite in CipherSuite.srpSuites + CipherSuite.srpRsaSuites: #If there's no SRP username... if not clientHello.srp_username: #Ask the client to re-send ClientHello with one for result in self._sendMsg(Alert().create(\ AlertDescription.missing_srp_username, AlertLevel.warning)): yield result #Get ClientHello for result in self._getMsg(ContentType.handshake, HandshakeType.client_hello): if result in (0,1): yield result else: break clientHello = result #Check ClientHello #If client's version is too low, reject it (COPIED CODE; BAD!) if clientHello.client_version < settings.minVersion: self.version = settings.minVersion for result in self._sendError(\ AlertDescription.protocol_version, "Too old version: %s" % str(clientHello.client_version)): yield result #If client's version is too high, propose my highest version elif clientHello.client_version > settings.maxVersion: self.version = settings.maxVersion else: #Set the version to the client's version self.version = clientHello.client_version #Recalculate the privileged cipher suite, making sure to #pick an SRP suite cipherSuites = [c for c in cipherSuites if c in \ CipherSuite.srpSuites + \ CipherSuite.srpRsaSuites] for cipherSuite in cipherSuites: if cipherSuite in clientHello.cipher_suites: break else: for result in self._sendError(\ AlertDescription.handshake_failure): yield result #Get the client nonce; create server nonce clientRandom = clientHello.random serverRandom = getRandomBytes(32) #The username better be there, this time if not clientHello.srp_username: for result in self._sendError(\ AlertDescription.illegal_parameter, "Client resent a hello, but without the SRP"\ " username"): yield result #Get username self.allegedSrpUsername = clientHello.srp_username #Get parameters from username try: entry = verifierDB[self.allegedSrpUsername] except KeyError: for result in self._sendError(\ AlertDescription.unknown_srp_username): yield result (N, g, s, v) = entry #Calculate server's ephemeral DH values (b, B) b = bytesToNumber(getRandomBytes(32)) k = makeK(N, g) B = (powMod(g, b, N) + (k*v)) % N #Create ServerKeyExchange, signing it if necessary serverKeyExchange = ServerKeyExchange(cipherSuite) serverKeyExchange.createSRP(N, g, stringToBytes(s), B) if cipherSuite in CipherSuite.srpRsaSuites: hashBytes = serverKeyExchange.hash(clientRandom, serverRandom) serverKeyExchange.signature = privateKey.sign(hashBytes) #Send ServerHello[, Certificate], ServerKeyExchange, #ServerHelloDone msgs = [] serverHello = ServerHello() serverHello.create(self.version, serverRandom, sessionID, cipherSuite, certificateType) msgs.append(serverHello) if cipherSuite in CipherSuite.srpRsaSuites: certificateMsg = Certificate(certificateType) certificateMsg.create(serverCertChain) msgs.append(certificateMsg) msgs.append(serverKeyExchange) msgs.append(ServerHelloDone()) for result in self._sendMsgs(msgs): yield result #From here on, the client's messages must have the right version self._versionCheck = True #Get and check ClientKeyExchange for result in self._getMsg(ContentType.handshake, HandshakeType.client_key_exchange, cipherSuite): if result in (0,1): yield result else: break clientKeyExchange = result A = clientKeyExchange.srp_A if A % N == 0: postFinishedError = (AlertDescription.illegal_parameter, "Suspicious A value") #Calculate u u = makeU(N, A, B) #Calculate premaster secret S = powMod((A * powMod(v,u,N)) % N, b, N) premasterSecret = numberToBytes(S) #If we've selected an RSA suite, exchange keys and calculate #premaster secret: elif cipherSuite in CipherSuite.rsaSuites: #Send ServerHello, Certificate[, CertificateRequest], #ServerHelloDone msgs = [] msgs.append(ServerHello().create(self.version, serverRandom, sessionID, cipherSuite, certificateType)) msgs.append(Certificate(certificateType).create(serverCertChain)) if reqCert: msgs.append(CertificateRequest()) msgs.append(ServerHelloDone()) for result in self._sendMsgs(msgs): yield result #From here on, the client's messages must have the right version self._versionCheck = True #Get [Certificate,] (if was requested) if reqCert: if self.version == (3,0): for result in self._getMsg((ContentType.handshake, ContentType.alert), HandshakeType.certificate, certificateType): if result in (0,1): yield result else: break msg = result if isinstance(msg, Alert): #If it's not a no_certificate alert, re-raise alert = msg if alert.description != \ AlertDescription.no_certificate: self._shutdown(False) raise TLSRemoteAlert(alert) elif isinstance(msg, Certificate): clientCertificate = msg if clientCertificate.certChain and \ clientCertificate.certChain.getNumCerts()!=0: clientCertChain = clientCertificate.certChain else: raise AssertionError() elif self.version in ((3,1), (3,2)): for result in self._getMsg(ContentType.handshake, HandshakeType.certificate, certificateType): if result in (0,1): yield result else: break clientCertificate = result if clientCertificate.certChain and \ clientCertificate.certChain.getNumCerts()!=0: clientCertChain = clientCertificate.certChain else: raise AssertionError() #Get ClientKeyExchange for result in self._getMsg(ContentType.handshake, HandshakeType.client_key_exchange, cipherSuite): if result in (0,1): yield result else: break clientKeyExchange = result #Decrypt ClientKeyExchange premasterSecret = privateKey.decrypt(\ clientKeyExchange.encryptedPreMasterSecret) randomPreMasterSecret = getRandomBytes(48) versionCheck = (premasterSecret[0], premasterSecret[1]) if not premasterSecret: premasterSecret = randomPreMasterSecret elif len(premasterSecret)!=48: premasterSecret = randomPreMasterSecret elif versionCheck != clientHello.client_version: if versionCheck != self.version: #Tolerate buggy IE clients premasterSecret = randomPreMasterSecret #Get and check CertificateVerify, if relevant if clientCertChain: if self.version == (3,0): #Create a temporary session object, just for the purpose #of checking the CertificateVerify session = Session() session._calcMasterSecret(self.version, premasterSecret, clientRandom, serverRandom) verifyBytes = self._calcSSLHandshakeHash(\ session.masterSecret, "") elif self.version in ((3,1), (3,2)): verifyBytes = stringToBytes(self._handshake_md5.digest() +\ self._handshake_sha.digest()) for result in self._getMsg(ContentType.handshake, HandshakeType.certificate_verify): if result in (0,1): yield result else: break certificateVerify = result publicKey = clientCertChain.getEndEntityPublicKey() if len(publicKey) < settings.minKeySize: postFinishedError = (AlertDescription.handshake_failure, "Client's public key too small: %d" % len(publicKey)) if len(publicKey) > settings.maxKeySize: postFinishedError = (AlertDescription.handshake_failure, "Client's public key too large: %d" % len(publicKey)) if not publicKey.verify(certificateVerify.signature, verifyBytes): postFinishedError = (AlertDescription.decrypt_error, "Signature failed to verify") #Create the session object self.session = Session() self.session._calcMasterSecret(self.version, premasterSecret, clientRandom, serverRandom) self.session.sessionID = sessionID self.session.cipherSuite = cipherSuite self.session.srpUsername = self.allegedSrpUsername self.session.clientCertChain = clientCertChain self.session.serverCertChain = serverCertChain #Calculate pending connection states self._calcPendingStates(clientRandom, serverRandom, settings.cipherImplementations) #Exchange ChangeCipherSpec and Finished messages for result in self._getFinished(): yield result #If we were holding a post-finished error until receiving the client #finished message, send it now. We delay the call until this point #because calling sendError() throws an exception, and our caller might #shut down the socket upon receiving the exception. If he did, and the #client was still sending its ChangeCipherSpec or Finished messages, it #would cause a socket error on the client side. This is a lot of #consideration to show to misbehaving clients, but this would also #cause problems with fault-testing. if postFinishedError: for result in self._sendError(*postFinishedError): yield result for result in self._sendFinished(): yield result #Add the session object to the session cache if sessionCache and sessionID: sessionCache[bytesToString(sessionID)] = self.session #Mark the connection as open self.session._setResumable(True) self._handshakeDone(resumed=False) def _handshakeWrapperAsync(self, handshaker, checker): if not self.fault: try: for result in handshaker: yield result if checker: try: checker(self) except TLSAuthenticationError: alert = Alert().create(AlertDescription.close_notify, AlertLevel.fatal) for result in self._sendMsg(alert): yield result raise except: self._shutdown(False) raise else: try: for result in handshaker: yield result if checker: try: checker(self) except TLSAuthenticationError: alert = Alert().create(AlertDescription.close_notify, AlertLevel.fatal) for result in self._sendMsg(alert): yield result raise except socket.error, e: raise TLSFaultError("socket error!") except TLSAbruptCloseError, e: raise TLSFaultError("abrupt close error!") except TLSAlert, alert: if alert.description not in Fault.faultAlerts[self.fault]: raise TLSFaultError(str(alert)) else: pass except: self._shutdown(False) raise else: raise TLSFaultError("No error!") def _getKeyFromChain(self, certificate, settings): #Get and check cert chain from the Certificate message certChain = certificate.certChain if not certChain or certChain.getNumCerts() == 0: for result in self._sendError(AlertDescription.illegal_parameter, "Other party sent a Certificate message without "\ "certificates"): yield result #Get and check public key from the cert chain publicKey = certChain.getEndEntityPublicKey() if len(publicKey) < settings.minKeySize: for result in self._sendError(AlertDescription.handshake_failure, "Other party's public key too small: %d" % len(publicKey)): yield result if len(publicKey) > settings.maxKeySize: for result in self._sendError(AlertDescription.handshake_failure, "Other party's public key too large: %d" % len(publicKey)): yield result yield publicKey, certChain gdata-2.0.18/samples/apps/marketplace_sample/gdata/tlslite/SessionCache.py0000750036466300116100000000661612156622362026671 0ustar afshareng00000000000000"""Class for caching TLS sessions.""" import thread import time class SessionCache: """This class is used by the server to cache TLS sessions. Caching sessions allows the client to use TLS session resumption and avoid the expense of a full handshake. To use this class, simply pass a SessionCache instance into the server handshake function. This class is thread-safe. """ #References to these instances #are also held by the caller, who may change the 'resumable' #flag, so the SessionCache must return the same instances #it was passed in. def __init__(self, maxEntries=10000, maxAge=14400): """Create a new SessionCache. @type maxEntries: int @param maxEntries: The maximum size of the cache. When this limit is reached, the oldest sessions will be deleted as necessary to make room for new ones. The default is 10000. @type maxAge: int @param maxAge: The number of seconds before a session expires from the cache. The default is 14400 (i.e. 4 hours).""" self.lock = thread.allocate_lock() # Maps sessionIDs to sessions self.entriesDict = {} #Circular list of (sessionID, timestamp) pairs self.entriesList = [(None,None)] * maxEntries self.firstIndex = 0 self.lastIndex = 0 self.maxAge = maxAge def __getitem__(self, sessionID): self.lock.acquire() try: self._purge() #Delete old items, so we're assured of a new one session = self.entriesDict[sessionID] #When we add sessions they're resumable, but it's possible #for the session to be invalidated later on (if a fatal alert #is returned), so we have to check for resumability before #returning the session. if session.valid(): return session else: raise KeyError() finally: self.lock.release() def __setitem__(self, sessionID, session): self.lock.acquire() try: #Add the new element self.entriesDict[sessionID] = session self.entriesList[self.lastIndex] = (sessionID, time.time()) self.lastIndex = (self.lastIndex+1) % len(self.entriesList) #If the cache is full, we delete the oldest element to make an #empty space if self.lastIndex == self.firstIndex: del(self.entriesDict[self.entriesList[self.firstIndex][0]]) self.firstIndex = (self.firstIndex+1) % len(self.entriesList) finally: self.lock.release() #Delete expired items def _purge(self): currentTime = time.time() #Search through the circular list, deleting expired elements until #we reach a non-expired element. Since elements in list are #ordered in time, we can break once we reach the first non-expired #element index = self.firstIndex while index != self.lastIndex: if currentTime - self.entriesList[index][1] > self.maxAge: del(self.entriesDict[self.entriesList[index][0]]) index = (index+1) % len(self.entriesList) else: break self.firstIndex = index def _test(): import doctest, SessionCache return doctest.testmod(SessionCache) if __name__ == "__main__": _test() gdata-2.0.18/samples/apps/marketplace_sample/gdata/tlslite/Session.py0000750036466300116100000001117512156622362025741 0ustar afshareng00000000000000"""Class representing a TLS session.""" from utils.compat import * from mathtls import * from constants import * class Session: """ This class represents a TLS session. TLS distinguishes between connections and sessions. A new handshake creates both a connection and a session. Data is transmitted over the connection. The session contains a more permanent record of the handshake. The session can be inspected to determine handshake results. The session can also be used to create a new connection through "session resumption". If the client and server both support this, they can create a new connection based on an old session without the overhead of a full handshake. The session for a L{tlslite.TLSConnection.TLSConnection} can be retrieved from the connection's 'session' attribute. @type srpUsername: str @ivar srpUsername: The client's SRP username (or None). @type sharedKeyUsername: str @ivar sharedKeyUsername: The client's shared-key username (or None). @type clientCertChain: L{tlslite.X509CertChain.X509CertChain} or L{cryptoIDlib.CertChain.CertChain} @ivar clientCertChain: The client's certificate chain (or None). @type serverCertChain: L{tlslite.X509CertChain.X509CertChain} or L{cryptoIDlib.CertChain.CertChain} @ivar serverCertChain: The server's certificate chain (or None). """ def __init__(self): self.masterSecret = createByteArraySequence([]) self.sessionID = createByteArraySequence([]) self.cipherSuite = 0 self.srpUsername = None self.sharedKeyUsername = None self.clientCertChain = None self.serverCertChain = None self.resumable = False self.sharedKey = False def _clone(self): other = Session() other.masterSecret = self.masterSecret other.sessionID = self.sessionID other.cipherSuite = self.cipherSuite other.srpUsername = self.srpUsername other.sharedKeyUsername = self.sharedKeyUsername other.clientCertChain = self.clientCertChain other.serverCertChain = self.serverCertChain other.resumable = self.resumable other.sharedKey = self.sharedKey return other def _calcMasterSecret(self, version, premasterSecret, clientRandom, serverRandom): if version == (3,0): self.masterSecret = PRF_SSL(premasterSecret, concatArrays(clientRandom, serverRandom), 48) elif version in ((3,1), (3,2)): self.masterSecret = PRF(premasterSecret, "master secret", concatArrays(clientRandom, serverRandom), 48) else: raise AssertionError() def valid(self): """If this session can be used for session resumption. @rtype: bool @return: If this session can be used for session resumption. """ return self.resumable or self.sharedKey def _setResumable(self, boolean): #Only let it be set if this isn't a shared key if not self.sharedKey: #Only let it be set to True if the sessionID is non-null if (not boolean) or (boolean and self.sessionID): self.resumable = boolean def getCipherName(self): """Get the name of the cipher used with this connection. @rtype: str @return: The name of the cipher used with this connection. Either 'aes128', 'aes256', 'rc4', or '3des'. """ if self.cipherSuite in CipherSuite.aes128Suites: return "aes128" elif self.cipherSuite in CipherSuite.aes256Suites: return "aes256" elif self.cipherSuite in CipherSuite.rc4Suites: return "rc4" elif self.cipherSuite in CipherSuite.tripleDESSuites: return "3des" else: return None def _createSharedKey(self, sharedKeyUsername, sharedKey): if len(sharedKeyUsername)>16: raise ValueError() if len(sharedKey)>47: raise ValueError() self.sharedKeyUsername = sharedKeyUsername self.sessionID = createByteArrayZeros(16) for x in range(len(sharedKeyUsername)): self.sessionID[x] = ord(sharedKeyUsername[x]) premasterSecret = createByteArrayZeros(48) sharedKey = chr(len(sharedKey)) + sharedKey for x in range(48): premasterSecret[x] = ord(sharedKey[x % len(sharedKey)]) self.masterSecret = PRF(premasterSecret, "shared secret", createByteArraySequence([]), 48) self.sharedKey = True return self gdata-2.0.18/samples/apps/marketplace_sample/gdata/tlslite/utils/0000750036466300116100000000000012156625015025072 5ustar afshareng00000000000000gdata-2.0.18/samples/apps/marketplace_sample/gdata/tlslite/utils/jython_compat.py0000750036466300116100000001222612156622362030332 0ustar afshareng00000000000000"""Miscellaneous functions to mask Python/Jython differences.""" import os import sha if os.name != "java": BaseException = Exception from sets import Set import array import math def createByteArraySequence(seq): return array.array('B', seq) def createByteArrayZeros(howMany): return array.array('B', [0] * howMany) def concatArrays(a1, a2): return a1+a2 def bytesToString(bytes): return bytes.tostring() def stringToBytes(s): bytes = createByteArrayZeros(0) bytes.fromstring(s) return bytes def numBits(n): if n==0: return 0 return int(math.floor(math.log(n, 2))+1) class CertChainBase: pass class SelfTestBase: pass class ReportFuncBase: pass #Helper functions for working with sets (from Python 2.3) def iterSet(set): return iter(set) def getListFromSet(set): return list(set) #Factory function for getting a SHA1 object def getSHA1(s): return sha.sha(s) import sys import traceback def formatExceptionTrace(e): newStr = "".join(traceback.format_exception(sys.exc_type, sys.exc_value, sys.exc_traceback)) return newStr else: #Jython 2.1 is missing lots of python 2.3 stuff, #which we have to emulate here: import java import jarray BaseException = java.lang.Exception def createByteArraySequence(seq): if isinstance(seq, type("")): #If it's a string, convert seq = [ord(c) for c in seq] return jarray.array(seq, 'h') #use short instead of bytes, cause bytes are signed def createByteArrayZeros(howMany): return jarray.zeros(howMany, 'h') #use short instead of bytes, cause bytes are signed def concatArrays(a1, a2): l = list(a1)+list(a2) return createByteArraySequence(l) #WAY TOO SLOW - MUST BE REPLACED------------ def bytesToString(bytes): return "".join([chr(b) for b in bytes]) def stringToBytes(s): bytes = createByteArrayZeros(len(s)) for count, c in enumerate(s): bytes[count] = ord(c) return bytes #WAY TOO SLOW - MUST BE REPLACED------------ def numBits(n): if n==0: return 0 n= 1L * n; #convert to long, if it isn't already return n.__tojava__(java.math.BigInteger).bitLength() #This properly creates static methods for Jython class staticmethod: def __init__(self, anycallable): self.__call__ = anycallable #Properties are not supported for Jython class property: def __init__(self, anycallable): pass #True and False have to be specially defined False = 0 True = 1 class StopIteration(Exception): pass def enumerate(collection): return zip(range(len(collection)), collection) class Set: def __init__(self, seq=None): self.values = {} if seq: for e in seq: self.values[e] = None def add(self, e): self.values[e] = None def discard(self, e): if e in self.values.keys(): del(self.values[e]) def union(self, s): ret = Set() for e in self.values.keys(): ret.values[e] = None for e in s.values.keys(): ret.values[e] = None return ret def issubset(self, other): for e in self.values.keys(): if e not in other.values.keys(): return False return True def __nonzero__( self): return len(self.values.keys()) def __contains__(self, e): return e in self.values.keys() def iterSet(set): return set.values.keys() def getListFromSet(set): return set.values.keys() """ class JCE_SHA1: def __init__(self, s=None): self.md = java.security.MessageDigest.getInstance("SHA1") if s: self.update(s) def update(self, s): self.md.update(s) def copy(self): sha1 = JCE_SHA1() sha1.md = self.md.clone() return sha1 def digest(self): digest = self.md.digest() bytes = jarray.zeros(20, 'h') for count in xrange(20): x = digest[count] if x < 0: x += 256 bytes[count] = x return bytes """ #Factory function for getting a SHA1 object #The JCE_SHA1 class is way too slow... #the sha.sha object we use instead is broken in the jython 2.1 #release, and needs to be patched def getSHA1(s): #return JCE_SHA1(s) return sha.sha(s) #Adjust the string to an array of bytes def stringToJavaByteArray(s): bytes = jarray.zeros(len(s), 'b') for count, c in enumerate(s): x = ord(c) if x >= 128: x -= 256 bytes[count] = x return bytes import sys import traceback def formatExceptionTrace(e): newStr = "".join(traceback.format_exception(sys.exc_type, sys.exc_value, sys.exc_traceback)) return newStr gdata-2.0.18/samples/apps/marketplace_sample/gdata/tlslite/utils/rijndael.py0000750036466300116100000002611512156622362027246 0ustar afshareng00000000000000""" A pure python (slow) implementation of rijndael with a decent interface To include - from rijndael import rijndael To do a key setup - r = rijndael(key, block_size = 16) key must be a string of length 16, 24, or 32 blocksize must be 16, 24, or 32. Default is 16 To use - ciphertext = r.encrypt(plaintext) plaintext = r.decrypt(ciphertext) If any strings are of the wrong length a ValueError is thrown """ # ported from the Java reference code by Bram Cohen, bram@gawth.com, April 2001 # this code is public domain, unless someone makes # an intellectual property claim against the reference # code, in which case it can be made public domain by # deleting all the comments and renaming all the variables import copy import string #----------------------- #TREV - ADDED BECAUSE THERE'S WARNINGS ABOUT INT OVERFLOW BEHAVIOR CHANGING IN #2.4..... import os if os.name != "java": import exceptions if hasattr(exceptions, "FutureWarning"): import warnings warnings.filterwarnings("ignore", category=FutureWarning, append=1) #----------------------- shifts = [[[0, 0], [1, 3], [2, 2], [3, 1]], [[0, 0], [1, 5], [2, 4], [3, 3]], [[0, 0], [1, 7], [3, 5], [4, 4]]] # [keysize][block_size] num_rounds = {16: {16: 10, 24: 12, 32: 14}, 24: {16: 12, 24: 12, 32: 14}, 32: {16: 14, 24: 14, 32: 14}} A = [[1, 1, 1, 1, 1, 0, 0, 0], [0, 1, 1, 1, 1, 1, 0, 0], [0, 0, 1, 1, 1, 1, 1, 0], [0, 0, 0, 1, 1, 1, 1, 1], [1, 0, 0, 0, 1, 1, 1, 1], [1, 1, 0, 0, 0, 1, 1, 1], [1, 1, 1, 0, 0, 0, 1, 1], [1, 1, 1, 1, 0, 0, 0, 1]] # produce log and alog tables, needed for multiplying in the # field GF(2^m) (generator = 3) alog = [1] for i in xrange(255): j = (alog[-1] << 1) ^ alog[-1] if j & 0x100 != 0: j ^= 0x11B alog.append(j) log = [0] * 256 for i in xrange(1, 255): log[alog[i]] = i # multiply two elements of GF(2^m) def mul(a, b): if a == 0 or b == 0: return 0 return alog[(log[a & 0xFF] + log[b & 0xFF]) % 255] # substitution box based on F^{-1}(x) box = [[0] * 8 for i in xrange(256)] box[1][7] = 1 for i in xrange(2, 256): j = alog[255 - log[i]] for t in xrange(8): box[i][t] = (j >> (7 - t)) & 0x01 B = [0, 1, 1, 0, 0, 0, 1, 1] # affine transform: box[i] <- B + A*box[i] cox = [[0] * 8 for i in xrange(256)] for i in xrange(256): for t in xrange(8): cox[i][t] = B[t] for j in xrange(8): cox[i][t] ^= A[t][j] * box[i][j] # S-boxes and inverse S-boxes S = [0] * 256 Si = [0] * 256 for i in xrange(256): S[i] = cox[i][0] << 7 for t in xrange(1, 8): S[i] ^= cox[i][t] << (7-t) Si[S[i] & 0xFF] = i # T-boxes G = [[2, 1, 1, 3], [3, 2, 1, 1], [1, 3, 2, 1], [1, 1, 3, 2]] AA = [[0] * 8 for i in xrange(4)] for i in xrange(4): for j in xrange(4): AA[i][j] = G[i][j] AA[i][i+4] = 1 for i in xrange(4): pivot = AA[i][i] if pivot == 0: t = i + 1 while AA[t][i] == 0 and t < 4: t += 1 assert t != 4, 'G matrix must be invertible' for j in xrange(8): AA[i][j], AA[t][j] = AA[t][j], AA[i][j] pivot = AA[i][i] for j in xrange(8): if AA[i][j] != 0: AA[i][j] = alog[(255 + log[AA[i][j] & 0xFF] - log[pivot & 0xFF]) % 255] for t in xrange(4): if i != t: for j in xrange(i+1, 8): AA[t][j] ^= mul(AA[i][j], AA[t][i]) AA[t][i] = 0 iG = [[0] * 4 for i in xrange(4)] for i in xrange(4): for j in xrange(4): iG[i][j] = AA[i][j + 4] def mul4(a, bs): if a == 0: return 0 r = 0 for b in bs: r <<= 8 if b != 0: r = r | mul(a, b) return r T1 = [] T2 = [] T3 = [] T4 = [] T5 = [] T6 = [] T7 = [] T8 = [] U1 = [] U2 = [] U3 = [] U4 = [] for t in xrange(256): s = S[t] T1.append(mul4(s, G[0])) T2.append(mul4(s, G[1])) T3.append(mul4(s, G[2])) T4.append(mul4(s, G[3])) s = Si[t] T5.append(mul4(s, iG[0])) T6.append(mul4(s, iG[1])) T7.append(mul4(s, iG[2])) T8.append(mul4(s, iG[3])) U1.append(mul4(t, iG[0])) U2.append(mul4(t, iG[1])) U3.append(mul4(t, iG[2])) U4.append(mul4(t, iG[3])) # round constants rcon = [1] r = 1 for t in xrange(1, 30): r = mul(2, r) rcon.append(r) del A del AA del pivot del B del G del box del log del alog del i del j del r del s del t del mul del mul4 del cox del iG class rijndael: def __init__(self, key, block_size = 16): if block_size != 16 and block_size != 24 and block_size != 32: raise ValueError('Invalid block size: ' + str(block_size)) if len(key) != 16 and len(key) != 24 and len(key) != 32: raise ValueError('Invalid key size: ' + str(len(key))) self.block_size = block_size ROUNDS = num_rounds[len(key)][block_size] BC = block_size / 4 # encryption round keys Ke = [[0] * BC for i in xrange(ROUNDS + 1)] # decryption round keys Kd = [[0] * BC for i in xrange(ROUNDS + 1)] ROUND_KEY_COUNT = (ROUNDS + 1) * BC KC = len(key) / 4 # copy user material bytes into temporary ints tk = [] for i in xrange(0, KC): tk.append((ord(key[i * 4]) << 24) | (ord(key[i * 4 + 1]) << 16) | (ord(key[i * 4 + 2]) << 8) | ord(key[i * 4 + 3])) # copy values into round key arrays t = 0 j = 0 while j < KC and t < ROUND_KEY_COUNT: Ke[t / BC][t % BC] = tk[j] Kd[ROUNDS - (t / BC)][t % BC] = tk[j] j += 1 t += 1 tt = 0 rconpointer = 0 while t < ROUND_KEY_COUNT: # extrapolate using phi (the round key evolution function) tt = tk[KC - 1] tk[0] ^= (S[(tt >> 16) & 0xFF] & 0xFF) << 24 ^ \ (S[(tt >> 8) & 0xFF] & 0xFF) << 16 ^ \ (S[ tt & 0xFF] & 0xFF) << 8 ^ \ (S[(tt >> 24) & 0xFF] & 0xFF) ^ \ (rcon[rconpointer] & 0xFF) << 24 rconpointer += 1 if KC != 8: for i in xrange(1, KC): tk[i] ^= tk[i-1] else: for i in xrange(1, KC / 2): tk[i] ^= tk[i-1] tt = tk[KC / 2 - 1] tk[KC / 2] ^= (S[ tt & 0xFF] & 0xFF) ^ \ (S[(tt >> 8) & 0xFF] & 0xFF) << 8 ^ \ (S[(tt >> 16) & 0xFF] & 0xFF) << 16 ^ \ (S[(tt >> 24) & 0xFF] & 0xFF) << 24 for i in xrange(KC / 2 + 1, KC): tk[i] ^= tk[i-1] # copy values into round key arrays j = 0 while j < KC and t < ROUND_KEY_COUNT: Ke[t / BC][t % BC] = tk[j] Kd[ROUNDS - (t / BC)][t % BC] = tk[j] j += 1 t += 1 # inverse MixColumn where needed for r in xrange(1, ROUNDS): for j in xrange(BC): tt = Kd[r][j] Kd[r][j] = U1[(tt >> 24) & 0xFF] ^ \ U2[(tt >> 16) & 0xFF] ^ \ U3[(tt >> 8) & 0xFF] ^ \ U4[ tt & 0xFF] self.Ke = Ke self.Kd = Kd def encrypt(self, plaintext): if len(plaintext) != self.block_size: raise ValueError('wrong block length, expected ' + str(self.block_size) + ' got ' + str(len(plaintext))) Ke = self.Ke BC = self.block_size / 4 ROUNDS = len(Ke) - 1 if BC == 4: SC = 0 elif BC == 6: SC = 1 else: SC = 2 s1 = shifts[SC][1][0] s2 = shifts[SC][2][0] s3 = shifts[SC][3][0] a = [0] * BC # temporary work array t = [] # plaintext to ints + key for i in xrange(BC): t.append((ord(plaintext[i * 4 ]) << 24 | ord(plaintext[i * 4 + 1]) << 16 | ord(plaintext[i * 4 + 2]) << 8 | ord(plaintext[i * 4 + 3]) ) ^ Ke[0][i]) # apply round transforms for r in xrange(1, ROUNDS): for i in xrange(BC): a[i] = (T1[(t[ i ] >> 24) & 0xFF] ^ T2[(t[(i + s1) % BC] >> 16) & 0xFF] ^ T3[(t[(i + s2) % BC] >> 8) & 0xFF] ^ T4[ t[(i + s3) % BC] & 0xFF] ) ^ Ke[r][i] t = copy.copy(a) # last round is special result = [] for i in xrange(BC): tt = Ke[ROUNDS][i] result.append((S[(t[ i ] >> 24) & 0xFF] ^ (tt >> 24)) & 0xFF) result.append((S[(t[(i + s1) % BC] >> 16) & 0xFF] ^ (tt >> 16)) & 0xFF) result.append((S[(t[(i + s2) % BC] >> 8) & 0xFF] ^ (tt >> 8)) & 0xFF) result.append((S[ t[(i + s3) % BC] & 0xFF] ^ tt ) & 0xFF) return string.join(map(chr, result), '') def decrypt(self, ciphertext): if len(ciphertext) != self.block_size: raise ValueError('wrong block length, expected ' + str(self.block_size) + ' got ' + str(len(plaintext))) Kd = self.Kd BC = self.block_size / 4 ROUNDS = len(Kd) - 1 if BC == 4: SC = 0 elif BC == 6: SC = 1 else: SC = 2 s1 = shifts[SC][1][1] s2 = shifts[SC][2][1] s3 = shifts[SC][3][1] a = [0] * BC # temporary work array t = [0] * BC # ciphertext to ints + key for i in xrange(BC): t[i] = (ord(ciphertext[i * 4 ]) << 24 | ord(ciphertext[i * 4 + 1]) << 16 | ord(ciphertext[i * 4 + 2]) << 8 | ord(ciphertext[i * 4 + 3]) ) ^ Kd[0][i] # apply round transforms for r in xrange(1, ROUNDS): for i in xrange(BC): a[i] = (T5[(t[ i ] >> 24) & 0xFF] ^ T6[(t[(i + s1) % BC] >> 16) & 0xFF] ^ T7[(t[(i + s2) % BC] >> 8) & 0xFF] ^ T8[ t[(i + s3) % BC] & 0xFF] ) ^ Kd[r][i] t = copy.copy(a) # last round is special result = [] for i in xrange(BC): tt = Kd[ROUNDS][i] result.append((Si[(t[ i ] >> 24) & 0xFF] ^ (tt >> 24)) & 0xFF) result.append((Si[(t[(i + s1) % BC] >> 16) & 0xFF] ^ (tt >> 16)) & 0xFF) result.append((Si[(t[(i + s2) % BC] >> 8) & 0xFF] ^ (tt >> 8)) & 0xFF) result.append((Si[ t[(i + s3) % BC] & 0xFF] ^ tt ) & 0xFF) return string.join(map(chr, result), '') def encrypt(key, block): return rijndael(key, len(block)).encrypt(block) def decrypt(key, block): return rijndael(key, len(block)).decrypt(block) def test(): def t(kl, bl): b = 'b' * bl r = rijndael('a' * kl, bl) assert r.decrypt(r.encrypt(b)) == b t(16, 16) t(16, 24) t(16, 32) t(24, 16) t(24, 24) t(24, 32) t(32, 16) t(32, 24) t(32, 32) gdata-2.0.18/samples/apps/marketplace_sample/gdata/tlslite/utils/cryptomath.py0000750036466300116100000002660612156622362027655 0ustar afshareng00000000000000"""cryptomath module This module has basic math/crypto code.""" import os import sys import math import base64 import binascii if sys.version_info[:2] <= (2, 4): from sha import sha as sha1 else: from hashlib import sha1 from compat import * # ************************************************************************** # Load Optional Modules # ************************************************************************** # Try to load M2Crypto/OpenSSL try: from M2Crypto import m2 m2cryptoLoaded = True except ImportError: m2cryptoLoaded = False # Try to load cryptlib try: import cryptlib_py try: cryptlib_py.cryptInit() except cryptlib_py.CryptException, e: #If tlslite and cryptoIDlib are both present, #they might each try to re-initialize this, #so we're tolerant of that. if e[0] != cryptlib_py.CRYPT_ERROR_INITED: raise cryptlibpyLoaded = True except ImportError: cryptlibpyLoaded = False #Try to load GMPY try: import gmpy gmpyLoaded = True except ImportError: gmpyLoaded = False #Try to load pycrypto try: import Crypto.Cipher.AES pycryptoLoaded = True except ImportError: pycryptoLoaded = False # ************************************************************************** # PRNG Functions # ************************************************************************** # Get os.urandom PRNG try: os.urandom(1) def getRandomBytes(howMany): return stringToBytes(os.urandom(howMany)) prngName = "os.urandom" except: # Else get cryptlib PRNG if cryptlibpyLoaded: def getRandomBytes(howMany): randomKey = cryptlib_py.cryptCreateContext(cryptlib_py.CRYPT_UNUSED, cryptlib_py.CRYPT_ALGO_AES) cryptlib_py.cryptSetAttribute(randomKey, cryptlib_py.CRYPT_CTXINFO_MODE, cryptlib_py.CRYPT_MODE_OFB) cryptlib_py.cryptGenerateKey(randomKey) bytes = createByteArrayZeros(howMany) cryptlib_py.cryptEncrypt(randomKey, bytes) return bytes prngName = "cryptlib" else: #Else get UNIX /dev/urandom PRNG try: devRandomFile = open("/dev/urandom", "rb") def getRandomBytes(howMany): return stringToBytes(devRandomFile.read(howMany)) prngName = "/dev/urandom" except IOError: #Else get Win32 CryptoAPI PRNG try: import win32prng def getRandomBytes(howMany): s = win32prng.getRandomBytes(howMany) if len(s) != howMany: raise AssertionError() return stringToBytes(s) prngName ="CryptoAPI" except ImportError: #Else no PRNG :-( def getRandomBytes(howMany): raise NotImplementedError("No Random Number Generator "\ "available.") prngName = "None" # ************************************************************************** # Converter Functions # ************************************************************************** def bytesToNumber(bytes): total = 0L multiplier = 1L for count in range(len(bytes)-1, -1, -1): byte = bytes[count] total += multiplier * byte multiplier *= 256 return total def numberToBytes(n): howManyBytes = numBytes(n) bytes = createByteArrayZeros(howManyBytes) for count in range(howManyBytes-1, -1, -1): bytes[count] = int(n % 256) n >>= 8 return bytes def bytesToBase64(bytes): s = bytesToString(bytes) return stringToBase64(s) def base64ToBytes(s): s = base64ToString(s) return stringToBytes(s) def numberToBase64(n): bytes = numberToBytes(n) return bytesToBase64(bytes) def base64ToNumber(s): bytes = base64ToBytes(s) return bytesToNumber(bytes) def stringToNumber(s): bytes = stringToBytes(s) return bytesToNumber(bytes) def numberToString(s): bytes = numberToBytes(s) return bytesToString(bytes) def base64ToString(s): try: return base64.decodestring(s) except binascii.Error, e: raise SyntaxError(e) except binascii.Incomplete, e: raise SyntaxError(e) def stringToBase64(s): return base64.encodestring(s).replace("\n", "") def mpiToNumber(mpi): #mpi is an openssl-format bignum string if (ord(mpi[4]) & 0x80) !=0: #Make sure this is a positive number raise AssertionError() bytes = stringToBytes(mpi[4:]) return bytesToNumber(bytes) def numberToMPI(n): bytes = numberToBytes(n) ext = 0 #If the high-order bit is going to be set, #add an extra byte of zeros if (numBits(n) & 0x7)==0: ext = 1 length = numBytes(n) + ext bytes = concatArrays(createByteArrayZeros(4+ext), bytes) bytes[0] = (length >> 24) & 0xFF bytes[1] = (length >> 16) & 0xFF bytes[2] = (length >> 8) & 0xFF bytes[3] = length & 0xFF return bytesToString(bytes) # ************************************************************************** # Misc. Utility Functions # ************************************************************************** def numBytes(n): if n==0: return 0 bits = numBits(n) return int(math.ceil(bits / 8.0)) def hashAndBase64(s): return stringToBase64(sha1(s).digest()) def getBase64Nonce(numChars=22): #defaults to an 132 bit nonce bytes = getRandomBytes(numChars) bytesStr = "".join([chr(b) for b in bytes]) return stringToBase64(bytesStr)[:numChars] # ************************************************************************** # Big Number Math # ************************************************************************** def getRandomNumber(low, high): if low >= high: raise AssertionError() howManyBits = numBits(high) howManyBytes = numBytes(high) lastBits = howManyBits % 8 while 1: bytes = getRandomBytes(howManyBytes) if lastBits: bytes[0] = bytes[0] % (1 << lastBits) n = bytesToNumber(bytes) if n >= low and n < high: return n def gcd(a,b): a, b = max(a,b), min(a,b) while b: a, b = b, a % b return a def lcm(a, b): #This will break when python division changes, but we can't use // cause #of Jython return (a * b) / gcd(a, b) #Returns inverse of a mod b, zero if none #Uses Extended Euclidean Algorithm def invMod(a, b): c, d = a, b uc, ud = 1, 0 while c != 0: #This will break when python division changes, but we can't use // #cause of Jython q = d / c c, d = d-(q*c), c uc, ud = ud - (q * uc), uc if d == 1: return ud % b return 0 if gmpyLoaded: def powMod(base, power, modulus): base = gmpy.mpz(base) power = gmpy.mpz(power) modulus = gmpy.mpz(modulus) result = pow(base, power, modulus) return long(result) else: #Copied from Bryan G. Olson's post to comp.lang.python #Does left-to-right instead of pow()'s right-to-left, #thus about 30% faster than the python built-in with small bases def powMod(base, power, modulus): nBitScan = 5 """ Return base**power mod modulus, using multi bit scanning with nBitScan bits at a time.""" #TREV - Added support for negative exponents negativeResult = False if (power < 0): power *= -1 negativeResult = True exp2 = 2**nBitScan mask = exp2 - 1 # Break power into a list of digits of nBitScan bits. # The list is recursive so easy to read in reverse direction. nibbles = None while power: nibbles = int(power & mask), nibbles power = power >> nBitScan # Make a table of powers of base up to 2**nBitScan - 1 lowPowers = [1] for i in xrange(1, exp2): lowPowers.append((lowPowers[i-1] * base) % modulus) # To exponentiate by the first nibble, look it up in the table nib, nibbles = nibbles prod = lowPowers[nib] # For the rest, square nBitScan times, then multiply by # base^nibble while nibbles: nib, nibbles = nibbles for i in xrange(nBitScan): prod = (prod * prod) % modulus if nib: prod = (prod * lowPowers[nib]) % modulus #TREV - Added support for negative exponents if negativeResult: prodInv = invMod(prod, modulus) #Check to make sure the inverse is correct if (prod * prodInv) % modulus != 1: raise AssertionError() return prodInv return prod #Pre-calculate a sieve of the ~100 primes < 1000: def makeSieve(n): sieve = range(n) for count in range(2, int(math.sqrt(n))): if sieve[count] == 0: continue x = sieve[count] * 2 while x < len(sieve): sieve[x] = 0 x += sieve[count] sieve = [x for x in sieve[2:] if x] return sieve sieve = makeSieve(1000) def isPrime(n, iterations=5, display=False): #Trial division with sieve for x in sieve: if x >= n: return True if n % x == 0: return False #Passed trial division, proceed to Rabin-Miller #Rabin-Miller implemented per Ferguson & Schneier #Compute s, t for Rabin-Miller if display: print "*", s, t = n-1, 0 while s % 2 == 0: s, t = s/2, t+1 #Repeat Rabin-Miller x times a = 2 #Use 2 as a base for first iteration speedup, per HAC for count in range(iterations): v = powMod(a, s, n) if v==1: continue i = 0 while v != n-1: if i == t-1: return False else: v, i = powMod(v, 2, n), i+1 a = getRandomNumber(2, n) return True def getRandomPrime(bits, display=False): if bits < 10: raise AssertionError() #The 1.5 ensures the 2 MSBs are set #Thus, when used for p,q in RSA, n will have its MSB set # #Since 30 is lcm(2,3,5), we'll set our test numbers to #29 % 30 and keep them there low = (2L ** (bits-1)) * 3/2 high = 2L ** bits - 30 p = getRandomNumber(low, high) p += 29 - (p % 30) while 1: if display: print ".", p += 30 if p >= high: p = getRandomNumber(low, high) p += 29 - (p % 30) if isPrime(p, display=display): return p #Unused at the moment... def getRandomSafePrime(bits, display=False): if bits < 10: raise AssertionError() #The 1.5 ensures the 2 MSBs are set #Thus, when used for p,q in RSA, n will have its MSB set # #Since 30 is lcm(2,3,5), we'll set our test numbers to #29 % 30 and keep them there low = (2 ** (bits-2)) * 3/2 high = (2 ** (bits-1)) - 30 q = getRandomNumber(low, high) q += 29 - (q % 30) while 1: if display: print ".", q += 30 if (q >= high): q = getRandomNumber(low, high) q += 29 - (q % 30) #Ideas from Tom Wu's SRP code #Do trial division on p and q before Rabin-Miller if isPrime(q, 0, display=display): p = (2 * q) + 1 if isPrime(p, display=display): if isPrime(q, display=display): return p gdata-2.0.18/samples/apps/marketplace_sample/gdata/tlslite/utils/dateFuncs.py0000750036466300116100000000420512156622362027366 0ustar afshareng00000000000000 import os #Functions for manipulating datetime objects #CCYY-MM-DDThh:mm:ssZ def parseDateClass(s): year, month, day = s.split("-") day, tail = day[:2], day[2:] hour, minute, second = tail[1:].split(":") second = second[:2] year, month, day = int(year), int(month), int(day) hour, minute, second = int(hour), int(minute), int(second) return createDateClass(year, month, day, hour, minute, second) if os.name != "java": from datetime import datetime, timedelta #Helper functions for working with a date/time class def createDateClass(year, month, day, hour, minute, second): return datetime(year, month, day, hour, minute, second) def printDateClass(d): #Split off fractional seconds, append 'Z' return d.isoformat().split(".")[0]+"Z" def getNow(): return datetime.utcnow() def getHoursFromNow(hours): return datetime.utcnow() + timedelta(hours=hours) def getMinutesFromNow(minutes): return datetime.utcnow() + timedelta(minutes=minutes) def isDateClassExpired(d): return d < datetime.utcnow() def isDateClassBefore(d1, d2): return d1 < d2 else: #Jython 2.1 is missing lots of python 2.3 stuff, #which we have to emulate here: import java import jarray def createDateClass(year, month, day, hour, minute, second): c = java.util.Calendar.getInstance() c.setTimeZone(java.util.TimeZone.getTimeZone("UTC")) c.set(year, month-1, day, hour, minute, second) return c def printDateClass(d): return "%04d-%02d-%02dT%02d:%02d:%02dZ" % \ (d.get(d.YEAR), d.get(d.MONTH)+1, d.get(d.DATE), \ d.get(d.HOUR_OF_DAY), d.get(d.MINUTE), d.get(d.SECOND)) def getNow(): c = java.util.Calendar.getInstance() c.setTimeZone(java.util.TimeZone.getTimeZone("UTC")) c.get(c.HOUR) #force refresh? return c def getHoursFromNow(hours): d = getNow() d.add(d.HOUR, hours) return d def isDateClassExpired(d): n = getNow() return d.before(n) def isDateClassBefore(d1, d2): return d1.before(d2) gdata-2.0.18/samples/apps/marketplace_sample/gdata/tlslite/utils/Cryptlib_TripleDES.py0000750036466300116100000000260012156622362031112 0ustar afshareng00000000000000"""Cryptlib 3DES implementation.""" from cryptomath import * from TripleDES import * if cryptlibpyLoaded: def new(key, mode, IV): return Cryptlib_TripleDES(key, mode, IV) class Cryptlib_TripleDES(TripleDES): def __init__(self, key, mode, IV): TripleDES.__init__(self, key, mode, IV, "cryptlib") self.context = cryptlib_py.cryptCreateContext(cryptlib_py.CRYPT_UNUSED, cryptlib_py.CRYPT_ALGO_3DES) cryptlib_py.cryptSetAttribute(self.context, cryptlib_py.CRYPT_CTXINFO_MODE, cryptlib_py.CRYPT_MODE_CBC) cryptlib_py.cryptSetAttribute(self.context, cryptlib_py.CRYPT_CTXINFO_KEYSIZE, len(key)) cryptlib_py.cryptSetAttributeString(self.context, cryptlib_py.CRYPT_CTXINFO_KEY, key) cryptlib_py.cryptSetAttributeString(self.context, cryptlib_py.CRYPT_CTXINFO_IV, IV) def __del__(self): cryptlib_py.cryptDestroyContext(self.context) def encrypt(self, plaintext): TripleDES.encrypt(self, plaintext) bytes = stringToBytes(plaintext) cryptlib_py.cryptEncrypt(self.context, bytes) return bytesToString(bytes) def decrypt(self, ciphertext): TripleDES.decrypt(self, ciphertext) bytes = stringToBytes(ciphertext) cryptlib_py.cryptDecrypt(self.context, bytes) return bytesToString(bytes)gdata-2.0.18/samples/apps/marketplace_sample/gdata/tlslite/utils/win32prng.c0000750036466300116100000000225612156622362027101 0ustar afshareng00000000000000 #include "Python.h" #define _WIN32_WINNT 0x0400 /* Needed for CryptoAPI on some systems */ #include static PyObject* getRandomBytes(PyObject *self, PyObject *args) { int howMany; HCRYPTPROV hCryptProv; unsigned char* bytes = NULL; PyObject* returnVal = NULL; /* Read Arguments */ if (!PyArg_ParseTuple(args, "i", &howMany)) return(NULL); /* Get Context */ if(CryptAcquireContext( &hCryptProv, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT) == 0) return Py_BuildValue("s#", NULL, 0); /* Allocate bytes */ bytes = malloc(howMany); /* Get random data */ if(CryptGenRandom( hCryptProv, howMany, bytes) == 0) returnVal = Py_BuildValue("s#", NULL, 0); else returnVal = Py_BuildValue("s#", bytes, howMany); free(bytes); CryptReleaseContext(hCryptProv, 0); return returnVal; } /* List of functions exported by this module */ static struct PyMethodDef win32prng_functions[] = { {"getRandomBytes", (PyCFunction)getRandomBytes, METH_VARARGS}, {NULL, NULL} /* Sentinel */ }; /* Initialize this module. */ DL_EXPORT(void) initwin32prng(void) { Py_InitModule("win32prng", win32prng_functions); } gdata-2.0.18/samples/apps/marketplace_sample/gdata/tlslite/utils/ASN1Parser.py0000750036466300116100000000170012156622362027326 0ustar afshareng00000000000000"""Class for parsing ASN.1""" from compat import * from codec import * #Takes a byte array which has a DER TLV field at its head class ASN1Parser: def __init__(self, bytes): p = Parser(bytes) p.get(1) #skip Type #Get Length self.length = self._getASN1Length(p) #Get Value self.value = p.getFixBytes(self.length) #Assuming this is a sequence... def getChild(self, which): p = Parser(self.value) for x in range(which+1): markIndex = p.index p.get(1) #skip Type length = self._getASN1Length(p) p.getFixBytes(length) return ASN1Parser(p.bytes[markIndex : p.index]) #Decode the ASN.1 DER length field def _getASN1Length(self, p): firstLength = p.get(1) if firstLength<=127: return firstLength else: lengthLength = firstLength & 0x7F return p.get(lengthLength) gdata-2.0.18/samples/apps/marketplace_sample/gdata/tlslite/utils/xmltools.py0000750036466300116100000001632412156622362027340 0ustar afshareng00000000000000"""Helper functions for XML. This module has misc. helper functions for working with XML DOM nodes.""" from compat import * import os import re if os.name == "java": # Only for Jython from javax.xml.parsers import * import java builder = DocumentBuilderFactory.newInstance().newDocumentBuilder() def parseDocument(s): stream = java.io.ByteArrayInputStream(java.lang.String(s).getBytes()) return builder.parse(stream) else: from xml.dom import minidom from xml.sax import saxutils def parseDocument(s): return minidom.parseString(s) def parseAndStripWhitespace(s): try: element = parseDocument(s).documentElement except BaseException, e: raise SyntaxError(str(e)) stripWhitespace(element) return element #Goes through a DOM tree and removes whitespace besides child elements, #as long as this whitespace is correctly tab-ified def stripWhitespace(element, tab=0): element.normalize() lastSpacer = "\n" + ("\t"*tab) spacer = lastSpacer + "\t" #Zero children aren't allowed (i.e. ) #This makes writing output simpler, and matches Canonical XML if element.childNodes.length==0: #DON'T DO len(element.childNodes) - doesn't work in Jython raise SyntaxError("Empty XML elements not allowed") #If there's a single child, it must be text context if element.childNodes.length==1: if element.firstChild.nodeType == element.firstChild.TEXT_NODE: #If it's an empty element, remove if element.firstChild.data == lastSpacer: element.removeChild(element.firstChild) return #If not text content, give an error elif element.firstChild.nodeType == element.firstChild.ELEMENT_NODE: raise SyntaxError("Bad whitespace under '%s'" % element.tagName) else: raise SyntaxError("Unexpected node type in XML document") #Otherwise there's multiple child element child = element.firstChild while child: if child.nodeType == child.ELEMENT_NODE: stripWhitespace(child, tab+1) child = child.nextSibling elif child.nodeType == child.TEXT_NODE: if child == element.lastChild: if child.data != lastSpacer: raise SyntaxError("Bad whitespace under '%s'" % element.tagName) elif child.data != spacer: raise SyntaxError("Bad whitespace under '%s'" % element.tagName) next = child.nextSibling element.removeChild(child) child = next else: raise SyntaxError("Unexpected node type in XML document") def checkName(element, name): if element.nodeType != element.ELEMENT_NODE: raise SyntaxError("Missing element: '%s'" % name) if name == None: return if element.tagName != name: raise SyntaxError("Wrong element name: should be '%s', is '%s'" % (name, element.tagName)) def getChild(element, index, name=None): if element.nodeType != element.ELEMENT_NODE: raise SyntaxError("Wrong node type in getChild()") child = element.childNodes.item(index) if child == None: raise SyntaxError("Missing child: '%s'" % name) checkName(child, name) return child def getChildIter(element, index): class ChildIter: def __init__(self, element, index): self.element = element self.index = index def next(self): if self.index < len(self.element.childNodes): retVal = self.element.childNodes.item(self.index) self.index += 1 else: retVal = None return retVal def checkEnd(self): if self.index != len(self.element.childNodes): raise SyntaxError("Too many elements under: '%s'" % self.element.tagName) return ChildIter(element, index) def getChildOrNone(element, index): if element.nodeType != element.ELEMENT_NODE: raise SyntaxError("Wrong node type in getChild()") child = element.childNodes.item(index) return child def getLastChild(element, index, name=None): if element.nodeType != element.ELEMENT_NODE: raise SyntaxError("Wrong node type in getLastChild()") child = element.childNodes.item(index) if child == None: raise SyntaxError("Missing child: '%s'" % name) if child != element.lastChild: raise SyntaxError("Too many elements under: '%s'" % element.tagName) checkName(child, name) return child #Regular expressions for syntax-checking attribute and element content nsRegEx = "http://trevp.net/cryptoID\Z" cryptoIDRegEx = "([a-km-z3-9]{5}\.){3}[a-km-z3-9]{5}\Z" urlRegEx = "http(s)?://.{1,100}\Z" sha1Base64RegEx = "[A-Za-z0-9+/]{27}=\Z" base64RegEx = "[A-Za-z0-9+/]+={0,4}\Z" certsListRegEx = "(0)?(1)?(2)?(3)?(4)?(5)?(6)?(7)?(8)?(9)?\Z" keyRegEx = "[A-Z]\Z" keysListRegEx = "(A)?(B)?(C)?(D)?(E)?(F)?(G)?(H)?(I)?(J)?(K)?(L)?(M)?(N)?(O)?(P)?(Q)?(R)?(S)?(T)?(U)?(V)?(W)?(X)?(Y)?(Z)?\Z" dateTimeRegEx = "\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\dZ\Z" shortStringRegEx = ".{1,100}\Z" exprRegEx = "[a-zA-Z0-9 ,()]{1,200}\Z" notAfterDeltaRegEx = "0|([1-9][0-9]{0,8})\Z" #A number from 0 to (1 billion)-1 booleanRegEx = "(true)|(false)" def getReqAttribute(element, attrName, regEx=""): if element.nodeType != element.ELEMENT_NODE: raise SyntaxError("Wrong node type in getReqAttribute()") value = element.getAttribute(attrName) if not value: raise SyntaxError("Missing Attribute: " + attrName) if not re.match(regEx, value): raise SyntaxError("Bad Attribute Value for '%s': '%s' " % (attrName, value)) element.removeAttribute(attrName) return str(value) #de-unicode it; this is needed for bsddb, for example def getAttribute(element, attrName, regEx=""): if element.nodeType != element.ELEMENT_NODE: raise SyntaxError("Wrong node type in getAttribute()") value = element.getAttribute(attrName) if value: if not re.match(regEx, value): raise SyntaxError("Bad Attribute Value for '%s': '%s' " % (attrName, value)) element.removeAttribute(attrName) return str(value) #de-unicode it; this is needed for bsddb, for example def checkNoMoreAttributes(element): if element.nodeType != element.ELEMENT_NODE: raise SyntaxError("Wrong node type in checkNoMoreAttributes()") if element.attributes.length!=0: raise SyntaxError("Extra attributes on '%s'" % element.tagName) def getText(element, regEx=""): textNode = element.firstChild if textNode == None: raise SyntaxError("Empty element '%s'" % element.tagName) if textNode.nodeType != textNode.TEXT_NODE: raise SyntaxError("Non-text node: '%s'" % element.tagName) if not re.match(regEx, textNode.data): raise SyntaxError("Bad Text Value for '%s': '%s' " % (element.tagName, textNode.data)) return str(textNode.data) #de-unicode it; this is needed for bsddb, for example #Function for adding tabs to a string def indent(s, steps, ch="\t"): tabs = ch*steps if s[-1] != "\n": s = tabs + s.replace("\n", "\n"+tabs) else: s = tabs + s.replace("\n", "\n"+tabs) s = s[ : -len(tabs)] return s def escape(s): return saxutils.escape(s) gdata-2.0.18/samples/apps/marketplace_sample/gdata/tlslite/utils/__init__.py0000750036466300116100000000143412156622362027212 0ustar afshareng00000000000000"""Toolkit for crypto and other stuff.""" __all__ = ["AES", "ASN1Parser", "cipherfactory", "codec", "Cryptlib_AES", "Cryptlib_RC4", "Cryptlib_TripleDES", "cryptomath: cryptomath module", "dateFuncs", "hmac", "JCE_RSAKey", "compat", "keyfactory", "OpenSSL_AES", "OpenSSL_RC4", "OpenSSL_RSAKey", "OpenSSL_TripleDES", "PyCrypto_AES", "PyCrypto_RC4", "PyCrypto_RSAKey", "PyCrypto_TripleDES", "Python_AES", "Python_RC4", "Python_RSAKey", "RC4", "rijndael", "RSAKey", "TripleDES", "xmltools"] gdata-2.0.18/samples/apps/marketplace_sample/gdata/tlslite/utils/Cryptlib_AES.py0000750036466300116100000000252412156622362027734 0ustar afshareng00000000000000"""Cryptlib AES implementation.""" from cryptomath import * from AES import * if cryptlibpyLoaded: def new(key, mode, IV): return Cryptlib_AES(key, mode, IV) class Cryptlib_AES(AES): def __init__(self, key, mode, IV): AES.__init__(self, key, mode, IV, "cryptlib") self.context = cryptlib_py.cryptCreateContext(cryptlib_py.CRYPT_UNUSED, cryptlib_py.CRYPT_ALGO_AES) cryptlib_py.cryptSetAttribute(self.context, cryptlib_py.CRYPT_CTXINFO_MODE, cryptlib_py.CRYPT_MODE_CBC) cryptlib_py.cryptSetAttribute(self.context, cryptlib_py.CRYPT_CTXINFO_KEYSIZE, len(key)) cryptlib_py.cryptSetAttributeString(self.context, cryptlib_py.CRYPT_CTXINFO_KEY, key) cryptlib_py.cryptSetAttributeString(self.context, cryptlib_py.CRYPT_CTXINFO_IV, IV) def __del__(self): cryptlib_py.cryptDestroyContext(self.context) def encrypt(self, plaintext): AES.encrypt(self, plaintext) bytes = stringToBytes(plaintext) cryptlib_py.cryptEncrypt(self.context, bytes) return bytesToString(bytes) def decrypt(self, ciphertext): AES.decrypt(self, ciphertext) bytes = stringToBytes(ciphertext) cryptlib_py.cryptDecrypt(self.context, bytes) return bytesToString(bytes) gdata-2.0.18/samples/apps/marketplace_sample/gdata/tlslite/utils/Cryptlib_RC4.py0000750036466300116100000000164212156622362027714 0ustar afshareng00000000000000"""Cryptlib RC4 implementation.""" from cryptomath import * from RC4 import RC4 if cryptlibpyLoaded: def new(key): return Cryptlib_RC4(key) class Cryptlib_RC4(RC4): def __init__(self, key): RC4.__init__(self, key, "cryptlib") self.context = cryptlib_py.cryptCreateContext(cryptlib_py.CRYPT_UNUSED, cryptlib_py.CRYPT_ALGO_RC4) cryptlib_py.cryptSetAttribute(self.context, cryptlib_py.CRYPT_CTXINFO_KEYSIZE, len(key)) cryptlib_py.cryptSetAttributeString(self.context, cryptlib_py.CRYPT_CTXINFO_KEY, key) def __del__(self): cryptlib_py.cryptDestroyContext(self.context) def encrypt(self, plaintext): bytes = stringToBytes(plaintext) cryptlib_py.cryptEncrypt(self.context, bytes) return bytesToString(bytes) def decrypt(self, ciphertext): return self.encrypt(ciphertext)gdata-2.0.18/samples/apps/marketplace_sample/gdata/tlslite/utils/cipherfactory.py0000750036466300116100000000615112156622362030316 0ustar afshareng00000000000000"""Factory functions for symmetric cryptography.""" import os import Python_AES import Python_RC4 import cryptomath tripleDESPresent = False if cryptomath.m2cryptoLoaded: import OpenSSL_AES import OpenSSL_RC4 import OpenSSL_TripleDES tripleDESPresent = True if cryptomath.cryptlibpyLoaded: import Cryptlib_AES import Cryptlib_RC4 import Cryptlib_TripleDES tripleDESPresent = True if cryptomath.pycryptoLoaded: import PyCrypto_AES import PyCrypto_RC4 import PyCrypto_TripleDES tripleDESPresent = True # ************************************************************************** # Factory Functions for AES # ************************************************************************** def createAES(key, IV, implList=None): """Create a new AES object. @type key: str @param key: A 16, 24, or 32 byte string. @type IV: str @param IV: A 16 byte string @rtype: L{tlslite.utils.AES} @return: An AES object. """ if implList == None: implList = ["cryptlib", "openssl", "pycrypto", "python"] for impl in implList: if impl == "cryptlib" and cryptomath.cryptlibpyLoaded: return Cryptlib_AES.new(key, 2, IV) elif impl == "openssl" and cryptomath.m2cryptoLoaded: return OpenSSL_AES.new(key, 2, IV) elif impl == "pycrypto" and cryptomath.pycryptoLoaded: return PyCrypto_AES.new(key, 2, IV) elif impl == "python": return Python_AES.new(key, 2, IV) raise NotImplementedError() def createRC4(key, IV, implList=None): """Create a new RC4 object. @type key: str @param key: A 16 to 32 byte string. @type IV: object @param IV: Ignored, whatever it is. @rtype: L{tlslite.utils.RC4} @return: An RC4 object. """ if implList == None: implList = ["cryptlib", "openssl", "pycrypto", "python"] if len(IV) != 0: raise AssertionError() for impl in implList: if impl == "cryptlib" and cryptomath.cryptlibpyLoaded: return Cryptlib_RC4.new(key) elif impl == "openssl" and cryptomath.m2cryptoLoaded: return OpenSSL_RC4.new(key) elif impl == "pycrypto" and cryptomath.pycryptoLoaded: return PyCrypto_RC4.new(key) elif impl == "python": return Python_RC4.new(key) raise NotImplementedError() #Create a new TripleDES instance def createTripleDES(key, IV, implList=None): """Create a new 3DES object. @type key: str @param key: A 24 byte string. @type IV: str @param IV: An 8 byte string @rtype: L{tlslite.utils.TripleDES} @return: A 3DES object. """ if implList == None: implList = ["cryptlib", "openssl", "pycrypto"] for impl in implList: if impl == "cryptlib" and cryptomath.cryptlibpyLoaded: return Cryptlib_TripleDES.new(key, 2, IV) elif impl == "openssl" and cryptomath.m2cryptoLoaded: return OpenSSL_TripleDES.new(key, 2, IV) elif impl == "pycrypto" and cryptomath.pycryptoLoaded: return PyCrypto_TripleDES.new(key, 2, IV) raise NotImplementedError()gdata-2.0.18/samples/apps/marketplace_sample/gdata/tlslite/utils/PyCrypto_RC4.py0000750036466300116100000000102112156622362027704 0ustar afshareng00000000000000"""PyCrypto RC4 implementation.""" from cryptomath import * from RC4 import * if pycryptoLoaded: import Crypto.Cipher.ARC4 def new(key): return PyCrypto_RC4(key) class PyCrypto_RC4(RC4): def __init__(self, key): RC4.__init__(self, key, "pycrypto") self.context = Crypto.Cipher.ARC4.new(key) def encrypt(self, plaintext): return self.context.encrypt(plaintext) def decrypt(self, ciphertext): return self.context.decrypt(ciphertext)gdata-2.0.18/samples/apps/marketplace_sample/gdata/tlslite/utils/keyfactory.py0000750036466300116100000002112712156622362027634 0ustar afshareng00000000000000"""Factory functions for asymmetric cryptography. @sort: generateRSAKey, parseXMLKey, parsePEMKey, parseAsPublicKey, parseAsPrivateKey """ from compat import * from RSAKey import RSAKey from Python_RSAKey import Python_RSAKey import cryptomath if cryptomath.m2cryptoLoaded: from OpenSSL_RSAKey import OpenSSL_RSAKey if cryptomath.pycryptoLoaded: from PyCrypto_RSAKey import PyCrypto_RSAKey # ************************************************************************** # Factory Functions for RSA Keys # ************************************************************************** def generateRSAKey(bits, implementations=["openssl", "python"]): """Generate an RSA key with the specified bit length. @type bits: int @param bits: Desired bit length of the new key's modulus. @rtype: L{tlslite.utils.RSAKey.RSAKey} @return: A new RSA private key. """ for implementation in implementations: if implementation == "openssl" and cryptomath.m2cryptoLoaded: return OpenSSL_RSAKey.generate(bits) elif implementation == "python": return Python_RSAKey.generate(bits) raise ValueError("No acceptable implementations") def parseXMLKey(s, private=False, public=False, implementations=["python"]): """Parse an XML-format key. The XML format used here is specific to tlslite and cryptoIDlib. The format can store the public component of a key, or the public and private components. For example:: 4a5yzB8oGNlHo866CAspAC47M4Fvx58zwK8pou... Aw== 4a5yzB8oGNlHo866CAspAC47M4Fvx58zwK8pou... Aw== JZ0TIgUxWXmL8KJ0VqyG1V0J3ern9pqIoB0xmy...

    5PreIj6z6ldIGL1V4+1C36dQFHNCQHJvW52GXc... /E/wDit8YXPCxx126zTq2ilQ3IcW54NJYyNjiZ... mKc+wX8inDowEH45Qp4slRo1YveBgExKPROu6... qDVKtBz9lk0shL5PR3ickXDgkwS576zbl2ztB... j6E8EA7dNsTImaXexAmLA1DoeArsYeFAInr... @type s: str @param s: A string containing an XML public or private key. @type private: bool @param private: If True, a L{SyntaxError} will be raised if the private key component is not present. @type public: bool @param public: If True, the private key component (if present) will be discarded, so this function will always return a public key. @rtype: L{tlslite.utils.RSAKey.RSAKey} @return: An RSA key. @raise SyntaxError: If the key is not properly formatted. """ for implementation in implementations: if implementation == "python": key = Python_RSAKey.parseXML(s) break else: raise ValueError("No acceptable implementations") return _parseKeyHelper(key, private, public) #Parse as an OpenSSL or Python key def parsePEMKey(s, private=False, public=False, passwordCallback=None, implementations=["openssl", "python"]): """Parse a PEM-format key. The PEM format is used by OpenSSL and other tools. The format is typically used to store both the public and private components of a key. For example:: -----BEGIN RSA PRIVATE KEY----- MIICXQIBAAKBgQDYscuoMzsGmW0pAYsmyHltxB2TdwHS0dImfjCMfaSDkfLdZY5+ dOWORVns9etWnr194mSGA1F0Pls/VJW8+cX9+3vtJV8zSdANPYUoQf0TP7VlJxkH dSRkUbEoz5bAAs/+970uos7n7iXQIni+3erUTdYEk2iWnMBjTljfgbK/dQIDAQAB AoGAJHoJZk75aKr7DSQNYIHuruOMdv5ZeDuJvKERWxTrVJqE32/xBKh42/IgqRrc esBN9ZregRCd7YtxoL+EVUNWaJNVx2mNmezEznrc9zhcYUrgeaVdFO2yBF1889zO gCOVwrO8uDgeyj6IKa25H6c1N13ih/o7ZzEgWbGG+ylU1yECQQDv4ZSJ4EjSh/Fl aHdz3wbBa/HKGTjC8iRy476Cyg2Fm8MZUe9Yy3udOrb5ZnS2MTpIXt5AF3h2TfYV VoFXIorjAkEA50FcJmzT8sNMrPaV8vn+9W2Lu4U7C+K/O2g1iXMaZms5PC5zV5aV CKXZWUX1fq2RaOzlbQrpgiolhXpeh8FjxwJBAOFHzSQfSsTNfttp3KUpU0LbiVvv i+spVSnA0O4rq79KpVNmK44Mq67hsW1P11QzrzTAQ6GVaUBRv0YS061td1kCQHnP wtN2tboFR6lABkJDjxoGRvlSt4SOPr7zKGgrWjeiuTZLHXSAnCY+/hr5L9Q3ZwXG 6x6iBdgLjVIe4BZQNtcCQQDXGv/gWinCNTN3MPWfTW/RGzuMYVmyBFais0/VrgdH h1dLpztmpQqfyH/zrBXQ9qL/zR4ojS6XYneO/U18WpEe -----END RSA PRIVATE KEY----- To generate a key like this with OpenSSL, run:: openssl genrsa 2048 > key.pem This format also supports password-encrypted private keys. TLS Lite can only handle password-encrypted private keys when OpenSSL and M2Crypto are installed. In this case, passwordCallback will be invoked to query the user for the password. @type s: str @param s: A string containing a PEM-encoded public or private key. @type private: bool @param private: If True, a L{SyntaxError} will be raised if the private key component is not present. @type public: bool @param public: If True, the private key component (if present) will be discarded, so this function will always return a public key. @type passwordCallback: callable @param passwordCallback: This function will be called, with no arguments, if the PEM-encoded private key is password-encrypted. The callback should return the password string. If the password is incorrect, SyntaxError will be raised. If no callback is passed and the key is password-encrypted, a prompt will be displayed at the console. @rtype: L{tlslite.utils.RSAKey.RSAKey} @return: An RSA key. @raise SyntaxError: If the key is not properly formatted. """ for implementation in implementations: if implementation == "openssl" and cryptomath.m2cryptoLoaded: key = OpenSSL_RSAKey.parse(s, passwordCallback) break elif implementation == "python": key = Python_RSAKey.parsePEM(s) break else: raise ValueError("No acceptable implementations") return _parseKeyHelper(key, private, public) def _parseKeyHelper(key, private, public): if private: if not key.hasPrivateKey(): raise SyntaxError("Not a private key!") if public: return _createPublicKey(key) if private: if hasattr(key, "d"): return _createPrivateKey(key) else: return key return key def parseAsPublicKey(s): """Parse an XML or PEM-formatted public key. @type s: str @param s: A string containing an XML or PEM-encoded public or private key. @rtype: L{tlslite.utils.RSAKey.RSAKey} @return: An RSA public key. @raise SyntaxError: If the key is not properly formatted. """ try: return parsePEMKey(s, public=True) except: return parseXMLKey(s, public=True) def parsePrivateKey(s): """Parse an XML or PEM-formatted private key. @type s: str @param s: A string containing an XML or PEM-encoded private key. @rtype: L{tlslite.utils.RSAKey.RSAKey} @return: An RSA private key. @raise SyntaxError: If the key is not properly formatted. """ try: return parsePEMKey(s, private=True) except: return parseXMLKey(s, private=True) def _createPublicKey(key): """ Create a new public key. Discard any private component, and return the most efficient key possible. """ if not isinstance(key, RSAKey): raise AssertionError() return _createPublicRSAKey(key.n, key.e) def _createPrivateKey(key): """ Create a new private key. Return the most efficient key possible. """ if not isinstance(key, RSAKey): raise AssertionError() if not key.hasPrivateKey(): raise AssertionError() return _createPrivateRSAKey(key.n, key.e, key.d, key.p, key.q, key.dP, key.dQ, key.qInv) def _createPublicRSAKey(n, e, implementations = ["openssl", "pycrypto", "python"]): for implementation in implementations: if implementation == "openssl" and cryptomath.m2cryptoLoaded: return OpenSSL_RSAKey(n, e) elif implementation == "pycrypto" and cryptomath.pycryptoLoaded: return PyCrypto_RSAKey(n, e) elif implementation == "python": return Python_RSAKey(n, e) raise ValueError("No acceptable implementations") def _createPrivateRSAKey(n, e, d, p, q, dP, dQ, qInv, implementations = ["pycrypto", "python"]): for implementation in implementations: if implementation == "pycrypto" and cryptomath.pycryptoLoaded: return PyCrypto_RSAKey(n, e, d, p, q, dP, dQ, qInv) elif implementation == "python": return Python_RSAKey(n, e, d, p, q, dP, dQ, qInv) raise ValueError("No acceptable implementations") gdata-2.0.18/samples/apps/marketplace_sample/gdata/tlslite/utils/OpenSSL_RSAKey.py0000750036466300116100000001162612156622362030120 0ustar afshareng00000000000000"""OpenSSL/M2Crypto RSA implementation.""" from cryptomath import * from RSAKey import * from Python_RSAKey import Python_RSAKey #copied from M2Crypto.util.py, so when we load the local copy of m2 #we can still use it def password_callback(v, prompt1='Enter private key passphrase:', prompt2='Verify passphrase:'): from getpass import getpass while 1: try: p1=getpass(prompt1) if v: p2=getpass(prompt2) if p1==p2: break else: break except KeyboardInterrupt: return None return p1 if m2cryptoLoaded: class OpenSSL_RSAKey(RSAKey): def __init__(self, n=0, e=0): self.rsa = None self._hasPrivateKey = False if (n and not e) or (e and not n): raise AssertionError() if n and e: self.rsa = m2.rsa_new() m2.rsa_set_n(self.rsa, numberToMPI(n)) m2.rsa_set_e(self.rsa, numberToMPI(e)) def __del__(self): if self.rsa: m2.rsa_free(self.rsa) def __getattr__(self, name): if name == 'e': if not self.rsa: return 0 return mpiToNumber(m2.rsa_get_e(self.rsa)) elif name == 'n': if not self.rsa: return 0 return mpiToNumber(m2.rsa_get_n(self.rsa)) else: raise AttributeError def hasPrivateKey(self): return self._hasPrivateKey def hash(self): return Python_RSAKey(self.n, self.e).hash() def _rawPrivateKeyOp(self, m): s = numberToString(m) byteLength = numBytes(self.n) if len(s)== byteLength: pass elif len(s) == byteLength-1: s = '\0' + s else: raise AssertionError() c = stringToNumber(m2.rsa_private_encrypt(self.rsa, s, m2.no_padding)) return c def _rawPublicKeyOp(self, c): s = numberToString(c) byteLength = numBytes(self.n) if len(s)== byteLength: pass elif len(s) == byteLength-1: s = '\0' + s else: raise AssertionError() m = stringToNumber(m2.rsa_public_decrypt(self.rsa, s, m2.no_padding)) return m def acceptsPassword(self): return True def write(self, password=None): bio = m2.bio_new(m2.bio_s_mem()) if self._hasPrivateKey: if password: def f(v): return password m2.rsa_write_key(self.rsa, bio, m2.des_ede_cbc(), f) else: def f(): pass m2.rsa_write_key_no_cipher(self.rsa, bio, f) else: if password: raise AssertionError() m2.rsa_write_pub_key(self.rsa, bio) s = m2.bio_read(bio, m2.bio_ctrl_pending(bio)) m2.bio_free(bio) return s def writeXMLPublicKey(self, indent=''): return Python_RSAKey(self.n, self.e).write(indent) def generate(bits): key = OpenSSL_RSAKey() def f():pass key.rsa = m2.rsa_generate_key(bits, 3, f) key._hasPrivateKey = True return key generate = staticmethod(generate) def parse(s, passwordCallback=None): if s.startswith("-----BEGIN "): if passwordCallback==None: callback = password_callback else: def f(v, prompt1=None, prompt2=None): return passwordCallback() callback = f bio = m2.bio_new(m2.bio_s_mem()) try: m2.bio_write(bio, s) key = OpenSSL_RSAKey() if s.startswith("-----BEGIN RSA PRIVATE KEY-----"): def f():pass key.rsa = m2.rsa_read_key(bio, callback) if key.rsa == None: raise SyntaxError() key._hasPrivateKey = True elif s.startswith("-----BEGIN PUBLIC KEY-----"): key.rsa = m2.rsa_read_pub_key(bio) if key.rsa == None: raise SyntaxError() key._hasPrivateKey = False else: raise SyntaxError() return key finally: m2.bio_free(bio) else: raise SyntaxError() parse = staticmethod(parse) gdata-2.0.18/samples/apps/marketplace_sample/gdata/tlslite/utils/Python_RC4.py0000750036466300116100000000176612156622362027414 0ustar afshareng00000000000000"""Pure-Python RC4 implementation.""" from RC4 import RC4 from cryptomath import * def new(key): return Python_RC4(key) class Python_RC4(RC4): def __init__(self, key): RC4.__init__(self, key, "python") keyBytes = stringToBytes(key) S = [i for i in range(256)] j = 0 for i in range(256): j = (j + S[i] + keyBytes[i % len(keyBytes)]) % 256 S[i], S[j] = S[j], S[i] self.S = S self.i = 0 self.j = 0 def encrypt(self, plaintext): plaintextBytes = stringToBytes(plaintext) S = self.S i = self.i j = self.j for x in range(len(plaintextBytes)): i = (i + 1) % 256 j = (j + S[i]) % 256 S[i], S[j] = S[j], S[i] t = (S[i] + S[j]) % 256 plaintextBytes[x] ^= S[t] self.i = i self.j = j return bytesToString(plaintextBytes) def decrypt(self, ciphertext): return self.encrypt(ciphertext) gdata-2.0.18/samples/apps/marketplace_sample/gdata/tlslite/utils/hmac.py0000750036466300116100000000632612156622362026370 0ustar afshareng00000000000000"""HMAC (Keyed-Hashing for Message Authentication) Python module. Implements the HMAC algorithm as described by RFC 2104. (This file is modified from the standard library version to do faster copying) """ def _strxor(s1, s2): """Utility method. XOR the two strings s1 and s2 (must have same length). """ return "".join(map(lambda x, y: chr(ord(x) ^ ord(y)), s1, s2)) # The size of the digests returned by HMAC depends on the underlying # hashing module used. digest_size = None class HMAC: """RFC2104 HMAC class. This supports the API for Cryptographic Hash Functions (PEP 247). """ def __init__(self, key, msg = None, digestmod = None): """Create a new HMAC object. key: key for the keyed hash object. msg: Initial input for the hash, if provided. digestmod: A module supporting PEP 247. Defaults to the md5 module. """ if digestmod is None: import md5 digestmod = md5 if key == None: #TREVNEW - for faster copying return #TREVNEW self.digestmod = digestmod self.outer = digestmod.new() self.inner = digestmod.new() self.digest_size = digestmod.digest_size blocksize = 64 ipad = "\x36" * blocksize opad = "\x5C" * blocksize if len(key) > blocksize: key = digestmod.new(key).digest() key = key + chr(0) * (blocksize - len(key)) self.outer.update(_strxor(key, opad)) self.inner.update(_strxor(key, ipad)) if msg is not None: self.update(msg) ## def clear(self): ## raise NotImplementedError, "clear() method not available in HMAC." def update(self, msg): """Update this hashing object with the string msg. """ self.inner.update(msg) def copy(self): """Return a separate copy of this hashing object. An update to this copy won't affect the original object. """ other = HMAC(None) #TREVNEW - for faster copying other.digest_size = self.digest_size #TREVNEW other.digestmod = self.digestmod other.inner = self.inner.copy() other.outer = self.outer.copy() return other def digest(self): """Return the hash value of this hashing object. This returns a string containing 8-bit data. The object is not altered in any way by this function; you can continue updating the object after calling this function. """ h = self.outer.copy() h.update(self.inner.digest()) return h.digest() def hexdigest(self): """Like digest(), but returns a string of hexadecimal digits instead. """ return "".join([hex(ord(x))[2:].zfill(2) for x in tuple(self.digest())]) def new(key, msg = None, digestmod = None): """Create a new hashing object and return it. key: The starting key for the hash. msg: if available, will immediately be hashed into the object's starting state. You can now feed arbitrary strings into the object using its update() method, and can ask for the hash value at any time by calling its digest() method. """ return HMAC(key, msg, digestmod) gdata-2.0.18/samples/apps/marketplace_sample/gdata/tlslite/utils/entropy.c0000750036466300116100000001127512156622362026751 0ustar afshareng00000000000000 #include "Python.h" #ifdef MS_WINDOWS /* The following #define is not needed on VC6 with the Platform SDK, and it may not be needed on VC7, I'm not sure. I don't think it hurts anything.*/ #define _WIN32_WINNT 0x0400 #include typedef BOOL (WINAPI *CRYPTACQUIRECONTEXTA)(HCRYPTPROV *phProv,\ LPCSTR pszContainer, LPCSTR pszProvider, DWORD dwProvType,\ DWORD dwFlags ); typedef BOOL (WINAPI *CRYPTGENRANDOM)(HCRYPTPROV hProv, DWORD dwLen,\ BYTE *pbBuffer ); typedef BOOL (WINAPI *CRYPTRELEASECONTEXT)(HCRYPTPROV hProv,\ DWORD dwFlags); static PyObject* entropy(PyObject *self, PyObject *args) { int howMany = 0; HINSTANCE hAdvAPI32 = NULL; CRYPTACQUIRECONTEXTA pCryptAcquireContextA = NULL; CRYPTGENRANDOM pCryptGenRandom = NULL; CRYPTRELEASECONTEXT pCryptReleaseContext = NULL; HCRYPTPROV hCryptProv = 0; unsigned char* bytes = NULL; PyObject* returnVal = NULL; /* Read arguments */ if (!PyArg_ParseTuple(args, "i", &howMany)) return(NULL); /* Obtain handle to the DLL containing CryptoAPI This should not fail */ if( (hAdvAPI32 = GetModuleHandle("advapi32.dll")) == NULL) { PyErr_Format(PyExc_SystemError, "Advapi32.dll not found"); return NULL; } /* Obtain pointers to the CryptoAPI functions This will fail on some early version of Win95 */ pCryptAcquireContextA = (CRYPTACQUIRECONTEXTA)GetProcAddress(hAdvAPI32,\ "CryptAcquireContextA"); pCryptGenRandom = (CRYPTGENRANDOM)GetProcAddress(hAdvAPI32,\ "CryptGenRandom"); pCryptReleaseContext = (CRYPTRELEASECONTEXT) GetProcAddress(hAdvAPI32,\ "CryptReleaseContext"); if (pCryptAcquireContextA == NULL || pCryptGenRandom == NULL || pCryptReleaseContext == NULL) { PyErr_Format(PyExc_NotImplementedError, "CryptoAPI not available on this version of Windows"); return NULL; } /* Allocate bytes */ if ((bytes = (unsigned char*)PyMem_Malloc(howMany)) == NULL) return PyErr_NoMemory(); /* Acquire context */ if(!pCryptAcquireContextA(&hCryptProv, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) { PyErr_Format(PyExc_SystemError, "CryptAcquireContext failed, error %d", GetLastError()); PyMem_Free(bytes); return NULL; } /* Get random data */ if(!pCryptGenRandom(hCryptProv, howMany, bytes)) { PyErr_Format(PyExc_SystemError, "CryptGenRandom failed, error %d", GetLastError()); PyMem_Free(bytes); CryptReleaseContext(hCryptProv, 0); return NULL; } /* Build return value */ returnVal = Py_BuildValue("s#", bytes, howMany); PyMem_Free(bytes); /* Release context */ if (!pCryptReleaseContext(hCryptProv, 0)) { PyErr_Format(PyExc_SystemError, "CryptReleaseContext failed, error %d", GetLastError()); return NULL; } return returnVal; } #elif defined(HAVE_UNISTD_H) && defined(HAVE_FCNTL_H) #include #include static PyObject* entropy(PyObject *self, PyObject *args) { int howMany; int fd; unsigned char* bytes = NULL; PyObject* returnVal = NULL; /* Read arguments */ if (!PyArg_ParseTuple(args, "i", &howMany)) return(NULL); /* Allocate bytes */ if ((bytes = (unsigned char*)PyMem_Malloc(howMany)) == NULL) return PyErr_NoMemory(); /* Open device */ if ((fd = open("/dev/urandom", O_RDONLY, 0)) == -1) { PyErr_Format(PyExc_NotImplementedError, "No entropy source found"); PyMem_Free(bytes); return NULL; } /* Get random data */ if (read(fd, bytes, howMany) < howMany) { PyErr_Format(PyExc_SystemError, "Reading from /dev/urandom failed"); PyMem_Free(bytes); close(fd); return NULL; } /* Build return value */ returnVal = Py_BuildValue("s#", bytes, howMany); PyMem_Free(bytes); /* Close device */ close(fd); return returnVal; } #else static PyObject* entropy(PyObject *self, PyObject *args) { PyErr_Format(PyExc_NotImplementedError, "Function not supported"); return NULL; } #endif /* List of functions exported by this module */ static struct PyMethodDef entropy_functions[] = { {"entropy", (PyCFunction)entropy, METH_VARARGS, "Return a string of random bytes produced by a platform-specific\nentropy source."}, {NULL, NULL} /* Sentinel */ }; /* Initialize this module. */ PyMODINIT_FUNC initentropy(void) { Py_InitModule("entropy", entropy_functions); }gdata-2.0.18/samples/apps/marketplace_sample/gdata/tlslite/utils/OpenSSL_TripleDES.py0000750036466300116100000000320212156622362030604 0ustar afshareng00000000000000"""OpenSSL/M2Crypto 3DES implementation.""" from cryptomath import * from TripleDES import * if m2cryptoLoaded: def new(key, mode, IV): return OpenSSL_TripleDES(key, mode, IV) class OpenSSL_TripleDES(TripleDES): def __init__(self, key, mode, IV): TripleDES.__init__(self, key, mode, IV, "openssl") self.key = key self.IV = IV def _createContext(self, encrypt): context = m2.cipher_ctx_new() cipherType = m2.des_ede3_cbc() m2.cipher_init(context, cipherType, self.key, self.IV, encrypt) return context def encrypt(self, plaintext): TripleDES.encrypt(self, plaintext) context = self._createContext(1) ciphertext = m2.cipher_update(context, plaintext) m2.cipher_ctx_free(context) self.IV = ciphertext[-self.block_size:] return ciphertext def decrypt(self, ciphertext): TripleDES.decrypt(self, ciphertext) context = self._createContext(0) #I think M2Crypto has a bug - it fails to decrypt and return the last block passed in. #To work around this, we append sixteen zeros to the string, below: plaintext = m2.cipher_update(context, ciphertext+('\0'*16)) #If this bug is ever fixed, then plaintext will end up having a garbage #plaintext block on the end. That's okay - the below code will ignore it. plaintext = plaintext[:len(ciphertext)] m2.cipher_ctx_free(context) self.IV = ciphertext[-self.block_size:] return plaintextgdata-2.0.18/samples/apps/marketplace_sample/gdata/tlslite/utils/Python_AES.py0000750036466300116100000000411112156622362027417 0ustar afshareng00000000000000"""Pure-Python AES implementation.""" from cryptomath import * from AES import * from rijndael import rijndael def new(key, mode, IV): return Python_AES(key, mode, IV) class Python_AES(AES): def __init__(self, key, mode, IV): AES.__init__(self, key, mode, IV, "python") self.rijndael = rijndael(key, 16) self.IV = IV def encrypt(self, plaintext): AES.encrypt(self, plaintext) plaintextBytes = stringToBytes(plaintext) chainBytes = stringToBytes(self.IV) #CBC Mode: For each block... for x in range(len(plaintextBytes)/16): #XOR with the chaining block blockBytes = plaintextBytes[x*16 : (x*16)+16] for y in range(16): blockBytes[y] ^= chainBytes[y] blockString = bytesToString(blockBytes) #Encrypt it encryptedBytes = stringToBytes(self.rijndael.encrypt(blockString)) #Overwrite the input with the output for y in range(16): plaintextBytes[(x*16)+y] = encryptedBytes[y] #Set the next chaining block chainBytes = encryptedBytes self.IV = bytesToString(chainBytes) return bytesToString(plaintextBytes) def decrypt(self, ciphertext): AES.decrypt(self, ciphertext) ciphertextBytes = stringToBytes(ciphertext) chainBytes = stringToBytes(self.IV) #CBC Mode: For each block... for x in range(len(ciphertextBytes)/16): #Decrypt it blockBytes = ciphertextBytes[x*16 : (x*16)+16] blockString = bytesToString(blockBytes) decryptedBytes = stringToBytes(self.rijndael.decrypt(blockString)) #XOR with the chaining block and overwrite the input with output for y in range(16): decryptedBytes[y] ^= chainBytes[y] ciphertextBytes[(x*16)+y] = decryptedBytes[y] #Set the next chaining block chainBytes = blockBytes self.IV = bytesToString(chainBytes) return bytesToString(ciphertextBytes) gdata-2.0.18/samples/apps/marketplace_sample/gdata/tlslite/utils/Python_RSAKey.py0000750036466300116100000001703312156622362030114 0ustar afshareng00000000000000"""Pure-Python RSA implementation.""" from cryptomath import * import xmltools from ASN1Parser import ASN1Parser from RSAKey import * class Python_RSAKey(RSAKey): def __init__(self, n=0, e=0, d=0, p=0, q=0, dP=0, dQ=0, qInv=0): if (n and not e) or (e and not n): raise AssertionError() self.n = n self.e = e self.d = d self.p = p self.q = q self.dP = dP self.dQ = dQ self.qInv = qInv self.blinder = 0 self.unblinder = 0 def hasPrivateKey(self): return self.d != 0 def hash(self): s = self.writeXMLPublicKey('\t\t') return hashAndBase64(s.strip()) def _rawPrivateKeyOp(self, m): #Create blinding values, on the first pass: if not self.blinder: self.unblinder = getRandomNumber(2, self.n) self.blinder = powMod(invMod(self.unblinder, self.n), self.e, self.n) #Blind the input m = (m * self.blinder) % self.n #Perform the RSA operation c = self._rawPrivateKeyOpHelper(m) #Unblind the output c = (c * self.unblinder) % self.n #Update blinding values self.blinder = (self.blinder * self.blinder) % self.n self.unblinder = (self.unblinder * self.unblinder) % self.n #Return the output return c def _rawPrivateKeyOpHelper(self, m): #Non-CRT version #c = powMod(m, self.d, self.n) #CRT version (~3x faster) s1 = powMod(m, self.dP, self.p) s2 = powMod(m, self.dQ, self.q) h = ((s1 - s2) * self.qInv) % self.p c = s2 + self.q * h return c def _rawPublicKeyOp(self, c): m = powMod(c, self.e, self.n) return m def acceptsPassword(self): return False def write(self, indent=''): if self.d: s = indent+'\n' else: s = indent+'\n' s += indent+'\t%s\n' % numberToBase64(self.n) s += indent+'\t%s\n' % numberToBase64(self.e) if self.d: s += indent+'\t%s\n' % numberToBase64(self.d) s += indent+'\t

    %s

    \n' % numberToBase64(self.p) s += indent+'\t%s\n' % numberToBase64(self.q) s += indent+'\t%s\n' % numberToBase64(self.dP) s += indent+'\t%s\n' % numberToBase64(self.dQ) s += indent+'\t%s\n' % numberToBase64(self.qInv) s += indent+'
    ' else: s += indent+'' #Only add \n if part of a larger structure if indent != '': s += '\n' return s def writeXMLPublicKey(self, indent=''): return Python_RSAKey(self.n, self.e).write(indent) def generate(bits): key = Python_RSAKey() p = getRandomPrime(bits/2, False) q = getRandomPrime(bits/2, False) t = lcm(p-1, q-1) key.n = p * q key.e = 3L #Needed to be long, for Java key.d = invMod(key.e, t) key.p = p key.q = q key.dP = key.d % (p-1) key.dQ = key.d % (q-1) key.qInv = invMod(q, p) return key generate = staticmethod(generate) def parsePEM(s, passwordCallback=None): """Parse a string containing a or , or PEM-encoded key.""" start = s.find("-----BEGIN PRIVATE KEY-----") if start != -1: end = s.find("-----END PRIVATE KEY-----") if end == -1: raise SyntaxError("Missing PEM Postfix") s = s[start+len("-----BEGIN PRIVATE KEY -----") : end] bytes = base64ToBytes(s) return Python_RSAKey._parsePKCS8(bytes) else: start = s.find("-----BEGIN RSA PRIVATE KEY-----") if start != -1: end = s.find("-----END RSA PRIVATE KEY-----") if end == -1: raise SyntaxError("Missing PEM Postfix") s = s[start+len("-----BEGIN RSA PRIVATE KEY -----") : end] bytes = base64ToBytes(s) return Python_RSAKey._parseSSLeay(bytes) raise SyntaxError("Missing PEM Prefix") parsePEM = staticmethod(parsePEM) def parseXML(s): element = xmltools.parseAndStripWhitespace(s) return Python_RSAKey._parseXML(element) parseXML = staticmethod(parseXML) def _parsePKCS8(bytes): p = ASN1Parser(bytes) version = p.getChild(0).value[0] if version != 0: raise SyntaxError("Unrecognized PKCS8 version") rsaOID = p.getChild(1).value if list(rsaOID) != [6, 9, 42, 134, 72, 134, 247, 13, 1, 1, 1, 5, 0]: raise SyntaxError("Unrecognized AlgorithmIdentifier") #Get the privateKey privateKeyP = p.getChild(2) #Adjust for OCTET STRING encapsulation privateKeyP = ASN1Parser(privateKeyP.value) return Python_RSAKey._parseASN1PrivateKey(privateKeyP) _parsePKCS8 = staticmethod(_parsePKCS8) def _parseSSLeay(bytes): privateKeyP = ASN1Parser(bytes) return Python_RSAKey._parseASN1PrivateKey(privateKeyP) _parseSSLeay = staticmethod(_parseSSLeay) def _parseASN1PrivateKey(privateKeyP): version = privateKeyP.getChild(0).value[0] if version != 0: raise SyntaxError("Unrecognized RSAPrivateKey version") n = bytesToNumber(privateKeyP.getChild(1).value) e = bytesToNumber(privateKeyP.getChild(2).value) d = bytesToNumber(privateKeyP.getChild(3).value) p = bytesToNumber(privateKeyP.getChild(4).value) q = bytesToNumber(privateKeyP.getChild(5).value) dP = bytesToNumber(privateKeyP.getChild(6).value) dQ = bytesToNumber(privateKeyP.getChild(7).value) qInv = bytesToNumber(privateKeyP.getChild(8).value) return Python_RSAKey(n, e, d, p, q, dP, dQ, qInv) _parseASN1PrivateKey = staticmethod(_parseASN1PrivateKey) def _parseXML(element): try: xmltools.checkName(element, "privateKey") except SyntaxError: xmltools.checkName(element, "publicKey") #Parse attributes xmltools.getReqAttribute(element, "xmlns", "http://trevp.net/rsa\Z") xmltools.checkNoMoreAttributes(element) #Parse public values ( and ) n = base64ToNumber(xmltools.getText(xmltools.getChild(element, 0, "n"), xmltools.base64RegEx)) e = base64ToNumber(xmltools.getText(xmltools.getChild(element, 1, "e"), xmltools.base64RegEx)) d = 0 p = 0 q = 0 dP = 0 dQ = 0 qInv = 0 #Parse private values, if present if element.childNodes.length>=3: d = base64ToNumber(xmltools.getText(xmltools.getChild(element, 2, "d"), xmltools.base64RegEx)) p = base64ToNumber(xmltools.getText(xmltools.getChild(element, 3, "p"), xmltools.base64RegEx)) q = base64ToNumber(xmltools.getText(xmltools.getChild(element, 4, "q"), xmltools.base64RegEx)) dP = base64ToNumber(xmltools.getText(xmltools.getChild(element, 5, "dP"), xmltools.base64RegEx)) dQ = base64ToNumber(xmltools.getText(xmltools.getChild(element, 6, "dQ"), xmltools.base64RegEx)) qInv = base64ToNumber(xmltools.getText(xmltools.getLastChild(element, 7, "qInv"), xmltools.base64RegEx)) return Python_RSAKey(n, e, d, p, q, dP, dQ, qInv) _parseXML = staticmethod(_parseXML) gdata-2.0.18/samples/apps/marketplace_sample/gdata/tlslite/utils/codec.py0000750036466300116100000000532312156622362026531 0ustar afshareng00000000000000"""Classes for reading/writing binary data (such as TLS records).""" from compat import * class Writer: def __init__(self, length=0): #If length is zero, then this is just a "trial run" to determine length self.index = 0 self.bytes = createByteArrayZeros(length) def add(self, x, length): if self.bytes: newIndex = self.index+length-1 while newIndex >= self.index: self.bytes[newIndex] = x & 0xFF x >>= 8 newIndex -= 1 self.index += length def addFixSeq(self, seq, length): if self.bytes: for e in seq: self.add(e, length) else: self.index += len(seq)*length def addVarSeq(self, seq, length, lengthLength): if self.bytes: self.add(len(seq)*length, lengthLength) for e in seq: self.add(e, length) else: self.index += lengthLength + (len(seq)*length) class Parser: def __init__(self, bytes): self.bytes = bytes self.index = 0 def get(self, length): if self.index + length > len(self.bytes): raise SyntaxError() x = 0 for count in range(length): x <<= 8 x |= self.bytes[self.index] self.index += 1 return x def getFixBytes(self, lengthBytes): bytes = self.bytes[self.index : self.index+lengthBytes] self.index += lengthBytes return bytes def getVarBytes(self, lengthLength): lengthBytes = self.get(lengthLength) return self.getFixBytes(lengthBytes) def getFixList(self, length, lengthList): l = [0] * lengthList for x in range(lengthList): l[x] = self.get(length) return l def getVarList(self, length, lengthLength): lengthList = self.get(lengthLength) if lengthList % length != 0: raise SyntaxError() lengthList = int(lengthList/length) l = [0] * lengthList for x in range(lengthList): l[x] = self.get(length) return l def startLengthCheck(self, lengthLength): self.lengthCheck = self.get(lengthLength) self.indexCheck = self.index def setLengthCheck(self, length): self.lengthCheck = length self.indexCheck = self.index def stopLengthCheck(self): if (self.index - self.indexCheck) != self.lengthCheck: raise SyntaxError() def atLengthCheck(self): if (self.index - self.indexCheck) < self.lengthCheck: return False elif (self.index - self.indexCheck) == self.lengthCheck: return True else: raise SyntaxError()gdata-2.0.18/samples/apps/marketplace_sample/gdata/tlslite/utils/compat.py0000750036466300116100000000773412156622362026747 0ustar afshareng00000000000000"""Miscellaneous functions to mask Python version differences.""" import sys import os if sys.version_info < (2,2): raise AssertionError("Python 2.2 or later required") if sys.version_info < (2,3): def enumerate(collection): return zip(range(len(collection)), collection) class Set: def __init__(self, seq=None): self.values = {} if seq: for e in seq: self.values[e] = None def add(self, e): self.values[e] = None def discard(self, e): if e in self.values.keys(): del(self.values[e]) def union(self, s): ret = Set() for e in self.values.keys(): ret.values[e] = None for e in s.values.keys(): ret.values[e] = None return ret def issubset(self, other): for e in self.values.keys(): if e not in other.values.keys(): return False return True def __nonzero__( self): return len(self.values.keys()) def __contains__(self, e): return e in self.values.keys() def __iter__(self): return iter(set.values.keys()) if os.name != "java": import array def createByteArraySequence(seq): return array.array('B', seq) def createByteArrayZeros(howMany): return array.array('B', [0] * howMany) def concatArrays(a1, a2): return a1+a2 def bytesToString(bytes): return bytes.tostring() def stringToBytes(s): bytes = createByteArrayZeros(0) bytes.fromstring(s) return bytes import math def numBits(n): if n==0: return 0 s = "%x" % n return ((len(s)-1)*4) + \ {'0':0, '1':1, '2':2, '3':2, '4':3, '5':3, '6':3, '7':3, '8':4, '9':4, 'a':4, 'b':4, 'c':4, 'd':4, 'e':4, 'f':4, }[s[0]] return int(math.floor(math.log(n, 2))+1) BaseException = Exception import sys import traceback def formatExceptionTrace(e): newStr = "".join(traceback.format_exception(sys.exc_type, sys.exc_value, sys.exc_traceback)) return newStr else: #Jython 2.1 is missing lots of python 2.3 stuff, #which we have to emulate here: #NOTE: JYTHON SUPPORT NO LONGER WORKS, DUE TO USE OF GENERATORS. #THIS CODE IS LEFT IN SO THAT ONE JYTHON UPDATES TO 2.2, IT HAS A #CHANCE OF WORKING AGAIN. import java import jarray def createByteArraySequence(seq): if isinstance(seq, type("")): #If it's a string, convert seq = [ord(c) for c in seq] return jarray.array(seq, 'h') #use short instead of bytes, cause bytes are signed def createByteArrayZeros(howMany): return jarray.zeros(howMany, 'h') #use short instead of bytes, cause bytes are signed def concatArrays(a1, a2): l = list(a1)+list(a2) return createByteArraySequence(l) #WAY TOO SLOW - MUST BE REPLACED------------ def bytesToString(bytes): return "".join([chr(b) for b in bytes]) def stringToBytes(s): bytes = createByteArrayZeros(len(s)) for count, c in enumerate(s): bytes[count] = ord(c) return bytes #WAY TOO SLOW - MUST BE REPLACED------------ def numBits(n): if n==0: return 0 n= 1L * n; #convert to long, if it isn't already return n.__tojava__(java.math.BigInteger).bitLength() #Adjust the string to an array of bytes def stringToJavaByteArray(s): bytes = jarray.zeros(len(s), 'b') for count, c in enumerate(s): x = ord(c) if x >= 128: x -= 256 bytes[count] = x return bytes BaseException = java.lang.Exception import sys import traceback def formatExceptionTrace(e): newStr = "".join(traceback.format_exception(sys.exc_type, sys.exc_value, sys.exc_traceback)) return newStrgdata-2.0.18/samples/apps/marketplace_sample/gdata/tlslite/utils/PyCrypto_TripleDES.py0000750036466300116100000000114212156622362031113 0ustar afshareng00000000000000"""PyCrypto 3DES implementation.""" from cryptomath import * from TripleDES import * if pycryptoLoaded: import Crypto.Cipher.DES3 def new(key, mode, IV): return PyCrypto_TripleDES(key, mode, IV) class PyCrypto_TripleDES(TripleDES): def __init__(self, key, mode, IV): TripleDES.__init__(self, key, mode, IV, "pycrypto") self.context = Crypto.Cipher.DES3.new(key, mode, IV) def encrypt(self, plaintext): return self.context.encrypt(plaintext) def decrypt(self, ciphertext): return self.context.decrypt(ciphertext)gdata-2.0.18/samples/apps/marketplace_sample/gdata/tlslite/utils/RC4.py0000750036466300116100000000071212156622362026041 0ustar afshareng00000000000000"""Abstract class for RC4.""" from compat import * #For False class RC4: def __init__(self, keyBytes, implementation): if len(keyBytes) < 16 or len(keyBytes) > 256: raise ValueError() self.isBlockCipher = False self.name = "rc4" self.implementation = implementation def encrypt(self, plaintext): raise NotImplementedError() def decrypt(self, ciphertext): raise NotImplementedError()gdata-2.0.18/samples/apps/marketplace_sample/gdata/tlslite/utils/OpenSSL_AES.py0000750036466300116100000000343612156622362027432 0ustar afshareng00000000000000"""OpenSSL/M2Crypto AES implementation.""" from cryptomath import * from AES import * if m2cryptoLoaded: def new(key, mode, IV): return OpenSSL_AES(key, mode, IV) class OpenSSL_AES(AES): def __init__(self, key, mode, IV): AES.__init__(self, key, mode, IV, "openssl") self.key = key self.IV = IV def _createContext(self, encrypt): context = m2.cipher_ctx_new() if len(self.key)==16: cipherType = m2.aes_128_cbc() if len(self.key)==24: cipherType = m2.aes_192_cbc() if len(self.key)==32: cipherType = m2.aes_256_cbc() m2.cipher_init(context, cipherType, self.key, self.IV, encrypt) return context def encrypt(self, plaintext): AES.encrypt(self, plaintext) context = self._createContext(1) ciphertext = m2.cipher_update(context, plaintext) m2.cipher_ctx_free(context) self.IV = ciphertext[-self.block_size:] return ciphertext def decrypt(self, ciphertext): AES.decrypt(self, ciphertext) context = self._createContext(0) #I think M2Crypto has a bug - it fails to decrypt and return the last block passed in. #To work around this, we append sixteen zeros to the string, below: plaintext = m2.cipher_update(context, ciphertext+('\0'*16)) #If this bug is ever fixed, then plaintext will end up having a garbage #plaintext block on the end. That's okay - the below code will discard it. plaintext = plaintext[:len(ciphertext)] m2.cipher_ctx_free(context) self.IV = ciphertext[-self.block_size:] return plaintext gdata-2.0.18/samples/apps/marketplace_sample/gdata/tlslite/utils/PyCrypto_RSAKey.py0000750036466300116100000000342612156622362030425 0ustar afshareng00000000000000"""PyCrypto RSA implementation.""" from cryptomath import * from RSAKey import * from Python_RSAKey import Python_RSAKey if pycryptoLoaded: from Crypto.PublicKey import RSA class PyCrypto_RSAKey(RSAKey): def __init__(self, n=0, e=0, d=0, p=0, q=0, dP=0, dQ=0, qInv=0): if not d: self.rsa = RSA.construct( (n, e) ) else: self.rsa = RSA.construct( (n, e, d, p, q) ) def __getattr__(self, name): return getattr(self.rsa, name) def hasPrivateKey(self): return self.rsa.has_private() def hash(self): return Python_RSAKey(self.n, self.e).hash() def _rawPrivateKeyOp(self, m): s = numberToString(m) byteLength = numBytes(self.n) if len(s)== byteLength: pass elif len(s) == byteLength-1: s = '\0' + s else: raise AssertionError() c = stringToNumber(self.rsa.decrypt((s,))) return c def _rawPublicKeyOp(self, c): s = numberToString(c) byteLength = numBytes(self.n) if len(s)== byteLength: pass elif len(s) == byteLength-1: s = '\0' + s else: raise AssertionError() m = stringToNumber(self.rsa.encrypt(s, None)[0]) return m def writeXMLPublicKey(self, indent=''): return Python_RSAKey(self.n, self.e).write(indent) def generate(bits): key = PyCrypto_RSAKey() def f(numBytes): return bytesToString(getRandomBytes(numBytes)) key.rsa = RSA.generate(bits, f) return key generate = staticmethod(generate) gdata-2.0.18/samples/apps/marketplace_sample/gdata/tlslite/utils/OpenSSL_RC4.py0000750036466300116100000000111312156622362027400 0ustar afshareng00000000000000"""OpenSSL/M2Crypto RC4 implementation.""" from cryptomath import * from RC4 import RC4 if m2cryptoLoaded: def new(key): return OpenSSL_RC4(key) class OpenSSL_RC4(RC4): def __init__(self, key): RC4.__init__(self, key, "openssl") self.rc4 = m2.rc4_new() m2.rc4_set_key(self.rc4, key) def __del__(self): m2.rc4_free(self.rc4) def encrypt(self, plaintext): return m2.rc4_update(self.rc4, plaintext) def decrypt(self, ciphertext): return self.encrypt(ciphertext) gdata-2.0.18/samples/apps/marketplace_sample/gdata/tlslite/utils/TripleDES.py0000750036466300116100000000140012156622362027237 0ustar afshareng00000000000000"""Abstract class for 3DES.""" from compat import * #For True class TripleDES: def __init__(self, key, mode, IV, implementation): if len(key) != 24: raise ValueError() if mode != 2: raise ValueError() if len(IV) != 8: raise ValueError() self.isBlockCipher = True self.block_size = 8 self.implementation = implementation self.name = "3des" #CBC-Mode encryption, returns ciphertext #WARNING: *MAY* modify the input as well def encrypt(self, plaintext): assert(len(plaintext) % 8 == 0) #CBC-Mode decryption, returns plaintext #WARNING: *MAY* modify the input as well def decrypt(self, ciphertext): assert(len(ciphertext) % 8 == 0) gdata-2.0.18/samples/apps/marketplace_sample/gdata/tlslite/utils/RSAKey.py0000750036466300116100000002057112156622362026554 0ustar afshareng00000000000000"""Abstract class for RSA.""" from cryptomath import * class RSAKey: """This is an abstract base class for RSA keys. Particular implementations of RSA keys, such as L{OpenSSL_RSAKey.OpenSSL_RSAKey}, L{Python_RSAKey.Python_RSAKey}, and L{PyCrypto_RSAKey.PyCrypto_RSAKey}, inherit from this. To create or parse an RSA key, don't use one of these classes directly. Instead, use the factory functions in L{tlslite.utils.keyfactory}. """ def __init__(self, n=0, e=0): """Create a new RSA key. If n and e are passed in, the new key will be initialized. @type n: int @param n: RSA modulus. @type e: int @param e: RSA public exponent. """ raise NotImplementedError() def __len__(self): """Return the length of this key in bits. @rtype: int """ return numBits(self.n) def hasPrivateKey(self): """Return whether or not this key has a private component. @rtype: bool """ raise NotImplementedError() def hash(self): """Return the cryptoID value corresponding to this key. @rtype: str """ raise NotImplementedError() def getSigningAlgorithm(self): """Return the cryptoID sigAlgo value corresponding to this key. @rtype: str """ return "pkcs1-sha1" def hashAndSign(self, bytes): """Hash and sign the passed-in bytes. This requires the key to have a private component. It performs a PKCS1-SHA1 signature on the passed-in data. @type bytes: str or L{array.array} of unsigned bytes @param bytes: The value which will be hashed and signed. @rtype: L{array.array} of unsigned bytes. @return: A PKCS1-SHA1 signature on the passed-in data. """ if not isinstance(bytes, type("")): bytes = bytesToString(bytes) hashBytes = stringToBytes(sha1(bytes).digest()) prefixedHashBytes = self._addPKCS1SHA1Prefix(hashBytes) sigBytes = self.sign(prefixedHashBytes) return sigBytes def hashAndVerify(self, sigBytes, bytes): """Hash and verify the passed-in bytes with the signature. This verifies a PKCS1-SHA1 signature on the passed-in data. @type sigBytes: L{array.array} of unsigned bytes @param sigBytes: A PKCS1-SHA1 signature. @type bytes: str or L{array.array} of unsigned bytes @param bytes: The value which will be hashed and verified. @rtype: bool @return: Whether the signature matches the passed-in data. """ if not isinstance(bytes, type("")): bytes = bytesToString(bytes) hashBytes = stringToBytes(sha1(bytes).digest()) prefixedHashBytes = self._addPKCS1SHA1Prefix(hashBytes) return self.verify(sigBytes, prefixedHashBytes) def sign(self, bytes): """Sign the passed-in bytes. This requires the key to have a private component. It performs a PKCS1 signature on the passed-in data. @type bytes: L{array.array} of unsigned bytes @param bytes: The value which will be signed. @rtype: L{array.array} of unsigned bytes. @return: A PKCS1 signature on the passed-in data. """ if not self.hasPrivateKey(): raise AssertionError() paddedBytes = self._addPKCS1Padding(bytes, 1) m = bytesToNumber(paddedBytes) if m >= self.n: raise ValueError() c = self._rawPrivateKeyOp(m) sigBytes = numberToBytes(c) return sigBytes def verify(self, sigBytes, bytes): """Verify the passed-in bytes with the signature. This verifies a PKCS1 signature on the passed-in data. @type sigBytes: L{array.array} of unsigned bytes @param sigBytes: A PKCS1 signature. @type bytes: L{array.array} of unsigned bytes @param bytes: The value which will be verified. @rtype: bool @return: Whether the signature matches the passed-in data. """ paddedBytes = self._addPKCS1Padding(bytes, 1) c = bytesToNumber(sigBytes) if c >= self.n: return False m = self._rawPublicKeyOp(c) checkBytes = numberToBytes(m) return checkBytes == paddedBytes def encrypt(self, bytes): """Encrypt the passed-in bytes. This performs PKCS1 encryption of the passed-in data. @type bytes: L{array.array} of unsigned bytes @param bytes: The value which will be encrypted. @rtype: L{array.array} of unsigned bytes. @return: A PKCS1 encryption of the passed-in data. """ paddedBytes = self._addPKCS1Padding(bytes, 2) m = bytesToNumber(paddedBytes) if m >= self.n: raise ValueError() c = self._rawPublicKeyOp(m) encBytes = numberToBytes(c) return encBytes def decrypt(self, encBytes): """Decrypt the passed-in bytes. This requires the key to have a private component. It performs PKCS1 decryption of the passed-in data. @type encBytes: L{array.array} of unsigned bytes @param encBytes: The value which will be decrypted. @rtype: L{array.array} of unsigned bytes or None. @return: A PKCS1 decryption of the passed-in data or None if the data is not properly formatted. """ if not self.hasPrivateKey(): raise AssertionError() c = bytesToNumber(encBytes) if c >= self.n: return None m = self._rawPrivateKeyOp(c) decBytes = numberToBytes(m) if (len(decBytes) != numBytes(self.n)-1): #Check first byte return None if decBytes[0] != 2: #Check second byte return None for x in range(len(decBytes)-1): #Scan through for zero separator if decBytes[x]== 0: break else: return None return decBytes[x+1:] #Return everything after the separator def _rawPrivateKeyOp(self, m): raise NotImplementedError() def _rawPublicKeyOp(self, c): raise NotImplementedError() def acceptsPassword(self): """Return True if the write() method accepts a password for use in encrypting the private key. @rtype: bool """ raise NotImplementedError() def write(self, password=None): """Return a string containing the key. @rtype: str @return: A string describing the key, in whichever format (PEM or XML) is native to the implementation. """ raise NotImplementedError() def writeXMLPublicKey(self, indent=''): """Return a string containing the key. @rtype: str @return: A string describing the public key, in XML format. """ return Python_RSAKey(self.n, self.e).write(indent) def generate(bits): """Generate a new key with the specified bit length. @rtype: L{tlslite.utils.RSAKey.RSAKey} """ raise NotImplementedError() generate = staticmethod(generate) # ************************************************************************** # Helper Functions for RSA Keys # ************************************************************************** def _addPKCS1SHA1Prefix(self, bytes): prefixBytes = createByteArraySequence(\ [48,33,48,9,6,5,43,14,3,2,26,5,0,4,20]) prefixedBytes = prefixBytes + bytes return prefixedBytes def _addPKCS1Padding(self, bytes, blockType): padLength = (numBytes(self.n) - (len(bytes)+3)) if blockType == 1: #Signature padding pad = [0xFF] * padLength elif blockType == 2: #Encryption padding pad = createByteArraySequence([]) while len(pad) < padLength: padBytes = getRandomBytes(padLength * 2) pad = [b for b in padBytes if b != 0] pad = pad[:padLength] else: raise AssertionError() #NOTE: To be proper, we should add [0,blockType]. However, #the zero is lost when the returned padding is converted #to a number, so we don't even bother with it. Also, #adding it would cause a misalignment in verify() padding = createByteArraySequence([blockType] + pad + [0]) paddedBytes = padding + bytes return paddedBytes gdata-2.0.18/samples/apps/marketplace_sample/gdata/tlslite/utils/AES.py0000750036466300116100000000167612156622362026073 0ustar afshareng00000000000000"""Abstract class for AES.""" class AES: def __init__(self, key, mode, IV, implementation): if len(key) not in (16, 24, 32): raise AssertionError() if mode != 2: raise AssertionError() if len(IV) != 16: raise AssertionError() self.isBlockCipher = True self.block_size = 16 self.implementation = implementation if len(key)==16: self.name = "aes128" elif len(key)==24: self.name = "aes192" elif len(key)==32: self.name = "aes256" else: raise AssertionError() #CBC-Mode encryption, returns ciphertext #WARNING: *MAY* modify the input as well def encrypt(self, plaintext): assert(len(plaintext) % 16 == 0) #CBC-Mode decryption, returns plaintext #WARNING: *MAY* modify the input as well def decrypt(self, ciphertext): assert(len(ciphertext) % 16 == 0)gdata-2.0.18/samples/apps/marketplace_sample/gdata/tlslite/utils/PyCrypto_AES.py0000750036466300116100000000110112156622362027723 0ustar afshareng00000000000000"""PyCrypto AES implementation.""" from cryptomath import * from AES import * if pycryptoLoaded: import Crypto.Cipher.AES def new(key, mode, IV): return PyCrypto_AES(key, mode, IV) class PyCrypto_AES(AES): def __init__(self, key, mode, IV): AES.__init__(self, key, mode, IV, "pycrypto") self.context = Crypto.Cipher.AES.new(key, mode, IV) def encrypt(self, plaintext): return self.context.encrypt(plaintext) def decrypt(self, ciphertext): return self.context.decrypt(ciphertext)gdata-2.0.18/samples/apps/marketplace_sample/gdata/tlslite/BaseDB.py0000750036466300116100000000666412156622362025405 0ustar afshareng00000000000000"""Base class for SharedKeyDB and VerifierDB.""" import anydbm import thread class BaseDB: def __init__(self, filename, type): self.type = type self.filename = filename if self.filename: self.db = None else: self.db = {} self.lock = thread.allocate_lock() def create(self): """Create a new on-disk database. @raise anydbm.error: If there's a problem creating the database. """ if self.filename: self.db = anydbm.open(self.filename, "n") #raises anydbm.error self.db["--Reserved--type"] = self.type self.db.sync() else: self.db = {} def open(self): """Open a pre-existing on-disk database. @raise anydbm.error: If there's a problem opening the database. @raise ValueError: If the database is not of the right type. """ if not self.filename: raise ValueError("Can only open on-disk databases") self.db = anydbm.open(self.filename, "w") #raises anydbm.error try: if self.db["--Reserved--type"] != self.type: raise ValueError("Not a %s database" % self.type) except KeyError: raise ValueError("Not a recognized database") def __getitem__(self, username): if self.db == None: raise AssertionError("DB not open") self.lock.acquire() try: valueStr = self.db[username] finally: self.lock.release() return self._getItem(username, valueStr) def __setitem__(self, username, value): if self.db == None: raise AssertionError("DB not open") valueStr = self._setItem(username, value) self.lock.acquire() try: self.db[username] = valueStr if self.filename: self.db.sync() finally: self.lock.release() def __delitem__(self, username): if self.db == None: raise AssertionError("DB not open") self.lock.acquire() try: del(self.db[username]) if self.filename: self.db.sync() finally: self.lock.release() def __contains__(self, username): """Check if the database contains the specified username. @type username: str @param username: The username to check for. @rtype: bool @return: True if the database contains the username, False otherwise. """ if self.db == None: raise AssertionError("DB not open") self.lock.acquire() try: return self.db.has_key(username) finally: self.lock.release() def check(self, username, param): value = self.__getitem__(username) return self._checkItem(value, username, param) def keys(self): """Return a list of usernames in the database. @rtype: list @return: The usernames in the database. """ if self.db == None: raise AssertionError("DB not open") self.lock.acquire() try: usernames = self.db.keys() finally: self.lock.release() usernames = [u for u in usernames if not u.startswith("--Reserved--")] return usernamesgdata-2.0.18/samples/apps/marketplace_sample/gdata/tlslite/X509CertChain.py0000750036466300116100000001531512156622362026544 0ustar afshareng00000000000000"""Class representing an X.509 certificate chain.""" from utils import cryptomath class X509CertChain: """This class represents a chain of X.509 certificates. @type x509List: list @ivar x509List: A list of L{tlslite.X509.X509} instances, starting with the end-entity certificate and with every subsequent certificate certifying the previous. """ def __init__(self, x509List=None): """Create a new X509CertChain. @type x509List: list @param x509List: A list of L{tlslite.X509.X509} instances, starting with the end-entity certificate and with every subsequent certificate certifying the previous. """ if x509List: self.x509List = x509List else: self.x509List = [] def getNumCerts(self): """Get the number of certificates in this chain. @rtype: int """ return len(self.x509List) def getEndEntityPublicKey(self): """Get the public key from the end-entity certificate. @rtype: L{tlslite.utils.RSAKey.RSAKey} """ if self.getNumCerts() == 0: raise AssertionError() return self.x509List[0].publicKey def getFingerprint(self): """Get the hex-encoded fingerprint of the end-entity certificate. @rtype: str @return: A hex-encoded fingerprint. """ if self.getNumCerts() == 0: raise AssertionError() return self.x509List[0].getFingerprint() def getCommonName(self): """Get the Subject's Common Name from the end-entity certificate. The cryptlib_py module must be installed in order to use this function. @rtype: str or None @return: The CN component of the certificate's subject DN, if present. """ if self.getNumCerts() == 0: raise AssertionError() return self.x509List[0].getCommonName() def validate(self, x509TrustList): """Check the validity of the certificate chain. This checks that every certificate in the chain validates with the subsequent one, until some certificate validates with (or is identical to) one of the passed-in root certificates. The cryptlib_py module must be installed in order to use this function. @type x509TrustList: list of L{tlslite.X509.X509} @param x509TrustList: A list of trusted root certificates. The certificate chain must extend to one of these certificates to be considered valid. """ import cryptlib_py c1 = None c2 = None lastC = None rootC = None try: rootFingerprints = [c.getFingerprint() for c in x509TrustList] #Check that every certificate in the chain validates with the #next one for cert1, cert2 in zip(self.x509List, self.x509List[1:]): #If we come upon a root certificate, we're done. if cert1.getFingerprint() in rootFingerprints: return True c1 = cryptlib_py.cryptImportCert(cert1.writeBytes(), cryptlib_py.CRYPT_UNUSED) c2 = cryptlib_py.cryptImportCert(cert2.writeBytes(), cryptlib_py.CRYPT_UNUSED) try: cryptlib_py.cryptCheckCert(c1, c2) except: return False cryptlib_py.cryptDestroyCert(c1) c1 = None cryptlib_py.cryptDestroyCert(c2) c2 = None #If the last certificate is one of the root certificates, we're #done. if self.x509List[-1].getFingerprint() in rootFingerprints: return True #Otherwise, find a root certificate that the last certificate #chains to, and validate them. lastC = cryptlib_py.cryptImportCert(self.x509List[-1].writeBytes(), cryptlib_py.CRYPT_UNUSED) for rootCert in x509TrustList: rootC = cryptlib_py.cryptImportCert(rootCert.writeBytes(), cryptlib_py.CRYPT_UNUSED) if self._checkChaining(lastC, rootC): try: cryptlib_py.cryptCheckCert(lastC, rootC) return True except: return False return False finally: if not (c1 is None): cryptlib_py.cryptDestroyCert(c1) if not (c2 is None): cryptlib_py.cryptDestroyCert(c2) if not (lastC is None): cryptlib_py.cryptDestroyCert(lastC) if not (rootC is None): cryptlib_py.cryptDestroyCert(rootC) def _checkChaining(self, lastC, rootC): import cryptlib_py import array def compareNames(name): try: length = cryptlib_py.cryptGetAttributeString(lastC, name, None) lastName = array.array('B', [0] * length) cryptlib_py.cryptGetAttributeString(lastC, name, lastName) lastName = lastName.tostring() except cryptlib_py.CryptException, e: if e[0] == cryptlib_py.CRYPT_ERROR_NOTFOUND: lastName = None try: length = cryptlib_py.cryptGetAttributeString(rootC, name, None) rootName = array.array('B', [0] * length) cryptlib_py.cryptGetAttributeString(rootC, name, rootName) rootName = rootName.tostring() except cryptlib_py.CryptException, e: if e[0] == cryptlib_py.CRYPT_ERROR_NOTFOUND: rootName = None return lastName == rootName cryptlib_py.cryptSetAttribute(lastC, cryptlib_py.CRYPT_CERTINFO_ISSUERNAME, cryptlib_py.CRYPT_UNUSED) if not compareNames(cryptlib_py.CRYPT_CERTINFO_COUNTRYNAME): return False if not compareNames(cryptlib_py.CRYPT_CERTINFO_LOCALITYNAME): return False if not compareNames(cryptlib_py.CRYPT_CERTINFO_ORGANIZATIONNAME): return False if not compareNames(cryptlib_py.CRYPT_CERTINFO_ORGANIZATIONALUNITNAME): return False if not compareNames(cryptlib_py.CRYPT_CERTINFO_COMMONNAME): return False return Truegdata-2.0.18/samples/apps/marketplace_sample/gdata/tlslite/TLSRecordLayer.py0000750036466300116100000012576412156622362027126 0ustar afshareng00000000000000"""Helper class for TLSConnection.""" from __future__ import generators from utils.compat import * from utils.cryptomath import * from utils.cipherfactory import createAES, createRC4, createTripleDES from utils.codec import * from errors import * from messages import * from mathtls import * from constants import * from utils.cryptomath import getRandomBytes from utils import hmac from FileObject import FileObject import sha import md5 import socket import errno import traceback class _ConnectionState: def __init__(self): self.macContext = None self.encContext = None self.seqnum = 0 def getSeqNumStr(self): w = Writer(8) w.add(self.seqnum, 8) seqnumStr = bytesToString(w.bytes) self.seqnum += 1 return seqnumStr class TLSRecordLayer: """ This class handles data transmission for a TLS connection. Its only subclass is L{tlslite.TLSConnection.TLSConnection}. We've separated the code in this class from TLSConnection to make things more readable. @type sock: socket.socket @ivar sock: The underlying socket object. @type session: L{tlslite.Session.Session} @ivar session: The session corresponding to this connection. Due to TLS session resumption, multiple connections can correspond to the same underlying session. @type version: tuple @ivar version: The TLS version being used for this connection. (3,0) means SSL 3.0, and (3,1) means TLS 1.0. @type closed: bool @ivar closed: If this connection is closed. @type resumed: bool @ivar resumed: If this connection is based on a resumed session. @type allegedSharedKeyUsername: str or None @ivar allegedSharedKeyUsername: This is set to the shared-key username asserted by the client, whether the handshake succeeded or not. If the handshake fails, this can be inspected to determine if a guessing attack is in progress against a particular user account. @type allegedSrpUsername: str or None @ivar allegedSrpUsername: This is set to the SRP username asserted by the client, whether the handshake succeeded or not. If the handshake fails, this can be inspected to determine if a guessing attack is in progress against a particular user account. @type closeSocket: bool @ivar closeSocket: If the socket should be closed when the connection is closed (writable). If you set this to True, TLS Lite will assume the responsibility of closing the socket when the TLS Connection is shutdown (either through an error or through the user calling close()). The default is False. @type ignoreAbruptClose: bool @ivar ignoreAbruptClose: If an abrupt close of the socket should raise an error (writable). If you set this to True, TLS Lite will not raise a L{tlslite.errors.TLSAbruptCloseError} exception if the underlying socket is unexpectedly closed. Such an unexpected closure could be caused by an attacker. However, it also occurs with some incorrect TLS implementations. You should set this to True only if you're not worried about an attacker truncating the connection, and only if necessary to avoid spurious errors. The default is False. @sort: __init__, read, readAsync, write, writeAsync, close, closeAsync, getCipherImplementation, getCipherName """ def __init__(self, sock): self.sock = sock #My session object (Session instance; read-only) self.session = None #Am I a client or server? self._client = None #Buffers for processing messages self._handshakeBuffer = [] self._readBuffer = "" #Handshake digests self._handshake_md5 = md5.md5() self._handshake_sha = sha.sha() #TLS Protocol Version self.version = (0,0) #read-only self._versionCheck = False #Once we choose a version, this is True #Current and Pending connection states self._writeState = _ConnectionState() self._readState = _ConnectionState() self._pendingWriteState = _ConnectionState() self._pendingReadState = _ConnectionState() #Is the connection open? self.closed = True #read-only self._refCount = 0 #Used to trigger closure #Is this a resumed (or shared-key) session? self.resumed = False #read-only #What username did the client claim in his handshake? self.allegedSharedKeyUsername = None self.allegedSrpUsername = None #On a call to close(), do we close the socket? (writeable) self.closeSocket = False #If the socket is abruptly closed, do we ignore it #and pretend the connection was shut down properly? (writeable) self.ignoreAbruptClose = False #Fault we will induce, for testing purposes self.fault = None #********************************************************* # Public Functions START #********************************************************* def read(self, max=None, min=1): """Read some data from the TLS connection. This function will block until at least 'min' bytes are available (or the connection is closed). If an exception is raised, the connection will have been automatically closed. @type max: int @param max: The maximum number of bytes to return. @type min: int @param min: The minimum number of bytes to return @rtype: str @return: A string of no more than 'max' bytes, and no fewer than 'min' (unless the connection has been closed, in which case fewer than 'min' bytes may be returned). @raise socket.error: If a socket error occurs. @raise tlslite.errors.TLSAbruptCloseError: If the socket is closed without a preceding alert. @raise tlslite.errors.TLSAlert: If a TLS alert is signalled. """ for result in self.readAsync(max, min): pass return result def readAsync(self, max=None, min=1): """Start a read operation on the TLS connection. This function returns a generator which behaves similarly to read(). Successive invocations of the generator will return 0 if it is waiting to read from the socket, 1 if it is waiting to write to the socket, or a string if the read operation has completed. @rtype: iterable @return: A generator; see above for details. """ try: while len(self._readBuffer)= len(s): break if endIndex > len(s): endIndex = len(s) block = stringToBytes(s[startIndex : endIndex]) applicationData = ApplicationData().create(block) for result in self._sendMsg(applicationData, skipEmptyFrag): yield result skipEmptyFrag = True #only send an empy fragment on 1st message index += 1 except: self._shutdown(False) raise def close(self): """Close the TLS connection. This function will block until it has exchanged close_notify alerts with the other party. After doing so, it will shut down the TLS connection. Further attempts to read through this connection will return "". Further attempts to write through this connection will raise ValueError. If makefile() has been called on this connection, the connection will be not be closed until the connection object and all file objects have been closed. Even if an exception is raised, the connection will have been closed. @raise socket.error: If a socket error occurs. @raise tlslite.errors.TLSAbruptCloseError: If the socket is closed without a preceding alert. @raise tlslite.errors.TLSAlert: If a TLS alert is signalled. """ if not self.closed: for result in self._decrefAsync(): pass def closeAsync(self): """Start a close operation on the TLS connection. This function returns a generator which behaves similarly to close(). Successive invocations of the generator will return 0 if it is waiting to read from the socket, 1 if it is waiting to write to the socket, or will raise StopIteration if the close operation has completed. @rtype: iterable @return: A generator; see above for details. """ if not self.closed: for result in self._decrefAsync(): yield result def _decrefAsync(self): self._refCount -= 1 if self._refCount == 0 and not self.closed: try: for result in self._sendMsg(Alert().create(\ AlertDescription.close_notify, AlertLevel.warning)): yield result alert = None while not alert: for result in self._getMsg((ContentType.alert, \ ContentType.application_data)): if result in (0,1): yield result if result.contentType == ContentType.alert: alert = result if alert.description == AlertDescription.close_notify: self._shutdown(True) else: raise TLSRemoteAlert(alert) except (socket.error, TLSAbruptCloseError): #If the other side closes the socket, that's okay self._shutdown(True) except: self._shutdown(False) raise def getCipherName(self): """Get the name of the cipher used with this connection. @rtype: str @return: The name of the cipher used with this connection. Either 'aes128', 'aes256', 'rc4', or '3des'. """ if not self._writeState.encContext: return None return self._writeState.encContext.name def getCipherImplementation(self): """Get the name of the cipher implementation used with this connection. @rtype: str @return: The name of the cipher implementation used with this connection. Either 'python', 'cryptlib', 'openssl', or 'pycrypto'. """ if not self._writeState.encContext: return None return self._writeState.encContext.implementation #Emulate a socket, somewhat - def send(self, s): """Send data to the TLS connection (socket emulation). @raise socket.error: If a socket error occurs. """ self.write(s) return len(s) def sendall(self, s): """Send data to the TLS connection (socket emulation). @raise socket.error: If a socket error occurs. """ self.write(s) def recv(self, bufsize): """Get some data from the TLS connection (socket emulation). @raise socket.error: If a socket error occurs. @raise tlslite.errors.TLSAbruptCloseError: If the socket is closed without a preceding alert. @raise tlslite.errors.TLSAlert: If a TLS alert is signalled. """ return self.read(bufsize) def makefile(self, mode='r', bufsize=-1): """Create a file object for the TLS connection (socket emulation). @rtype: L{tlslite.FileObject.FileObject} """ self._refCount += 1 return FileObject(self, mode, bufsize) def getsockname(self): """Return the socket's own address (socket emulation).""" return self.sock.getsockname() def getpeername(self): """Return the remote address to which the socket is connected (socket emulation).""" return self.sock.getpeername() def settimeout(self, value): """Set a timeout on blocking socket operations (socket emulation).""" return self.sock.settimeout(value) def gettimeout(self): """Return the timeout associated with socket operations (socket emulation).""" return self.sock.gettimeout() def setsockopt(self, level, optname, value): """Set the value of the given socket option (socket emulation).""" return self.sock.setsockopt(level, optname, value) #********************************************************* # Public Functions END #********************************************************* def _shutdown(self, resumable): self._writeState = _ConnectionState() self._readState = _ConnectionState() #Don't do this: self._readBuffer = "" self.version = (0,0) self._versionCheck = False self.closed = True if self.closeSocket: self.sock.close() #Even if resumable is False, we'll never toggle this on if not resumable and self.session: self.session.resumable = False def _sendError(self, alertDescription, errorStr=None): alert = Alert().create(alertDescription, AlertLevel.fatal) for result in self._sendMsg(alert): yield result self._shutdown(False) raise TLSLocalAlert(alert, errorStr) def _sendMsgs(self, msgs): skipEmptyFrag = False for msg in msgs: for result in self._sendMsg(msg, skipEmptyFrag): yield result skipEmptyFrag = True def _sendMsg(self, msg, skipEmptyFrag=False): bytes = msg.write() contentType = msg.contentType #Whenever we're connected and asked to send a message, #we first send an empty Application Data message. This prevents #an attacker from launching a chosen-plaintext attack based on #knowing the next IV. if not self.closed and not skipEmptyFrag and self.version == (3,1): if self._writeState.encContext: if self._writeState.encContext.isBlockCipher: for result in self._sendMsg(ApplicationData(), skipEmptyFrag=True): yield result #Update handshake hashes if contentType == ContentType.handshake: bytesStr = bytesToString(bytes) self._handshake_md5.update(bytesStr) self._handshake_sha.update(bytesStr) #Calculate MAC if self._writeState.macContext: seqnumStr = self._writeState.getSeqNumStr() bytesStr = bytesToString(bytes) mac = self._writeState.macContext.copy() mac.update(seqnumStr) mac.update(chr(contentType)) if self.version == (3,0): mac.update( chr( int(len(bytes)/256) ) ) mac.update( chr( int(len(bytes)%256) ) ) elif self.version in ((3,1), (3,2)): mac.update(chr(self.version[0])) mac.update(chr(self.version[1])) mac.update( chr( int(len(bytes)/256) ) ) mac.update( chr( int(len(bytes)%256) ) ) else: raise AssertionError() mac.update(bytesStr) macString = mac.digest() macBytes = stringToBytes(macString) if self.fault == Fault.badMAC: macBytes[0] = (macBytes[0]+1) % 256 #Encrypt for Block or Stream Cipher if self._writeState.encContext: #Add padding and encrypt (for Block Cipher): if self._writeState.encContext.isBlockCipher: #Add TLS 1.1 fixed block if self.version == (3,2): bytes = self.fixedIVBlock + bytes #Add padding: bytes = bytes + (macBytes + paddingBytes) currentLength = len(bytes) + len(macBytes) + 1 blockLength = self._writeState.encContext.block_size paddingLength = blockLength-(currentLength % blockLength) paddingBytes = createByteArraySequence([paddingLength] * \ (paddingLength+1)) if self.fault == Fault.badPadding: paddingBytes[0] = (paddingBytes[0]+1) % 256 endBytes = concatArrays(macBytes, paddingBytes) bytes = concatArrays(bytes, endBytes) #Encrypt plaintext = stringToBytes(bytes) ciphertext = self._writeState.encContext.encrypt(plaintext) bytes = stringToBytes(ciphertext) #Encrypt (for Stream Cipher) else: bytes = concatArrays(bytes, macBytes) plaintext = bytesToString(bytes) ciphertext = self._writeState.encContext.encrypt(plaintext) bytes = stringToBytes(ciphertext) #Add record header and send r = RecordHeader3().create(self.version, contentType, len(bytes)) s = bytesToString(concatArrays(r.write(), bytes)) while 1: try: bytesSent = self.sock.send(s) #Might raise socket.error except socket.error, why: if why[0] == errno.EWOULDBLOCK: yield 1 continue else: raise if bytesSent == len(s): return s = s[bytesSent:] yield 1 def _getMsg(self, expectedType, secondaryType=None, constructorType=None): try: if not isinstance(expectedType, tuple): expectedType = (expectedType,) #Spin in a loop, until we've got a non-empty record of a type we #expect. The loop will be repeated if: # - we receive a renegotiation attempt; we send no_renegotiation, # then try again # - we receive an empty application-data fragment; we try again while 1: for result in self._getNextRecord(): if result in (0,1): yield result recordHeader, p = result #If this is an empty application-data fragment, try again if recordHeader.type == ContentType.application_data: if p.index == len(p.bytes): continue #If we received an unexpected record type... if recordHeader.type not in expectedType: #If we received an alert... if recordHeader.type == ContentType.alert: alert = Alert().parse(p) #We either received a fatal error, a warning, or a #close_notify. In any case, we're going to close the #connection. In the latter two cases we respond with #a close_notify, but ignore any socket errors, since #the other side might have already closed the socket. if alert.level == AlertLevel.warning or \ alert.description == AlertDescription.close_notify: #If the sendMsg() call fails because the socket has #already been closed, we will be forgiving and not #report the error nor invalidate the "resumability" #of the session. try: alertMsg = Alert() alertMsg.create(AlertDescription.close_notify, AlertLevel.warning) for result in self._sendMsg(alertMsg): yield result except socket.error: pass if alert.description == \ AlertDescription.close_notify: self._shutdown(True) elif alert.level == AlertLevel.warning: self._shutdown(False) else: #Fatal alert: self._shutdown(False) #Raise the alert as an exception raise TLSRemoteAlert(alert) #If we received a renegotiation attempt... if recordHeader.type == ContentType.handshake: subType = p.get(1) reneg = False if self._client: if subType == HandshakeType.hello_request: reneg = True else: if subType == HandshakeType.client_hello: reneg = True #Send no_renegotiation, then try again if reneg: alertMsg = Alert() alertMsg.create(AlertDescription.no_renegotiation, AlertLevel.warning) for result in self._sendMsg(alertMsg): yield result continue #Otherwise: this is an unexpected record, but neither an #alert nor renegotiation for result in self._sendError(\ AlertDescription.unexpected_message, "received type=%d" % recordHeader.type): yield result break #Parse based on content_type if recordHeader.type == ContentType.change_cipher_spec: yield ChangeCipherSpec().parse(p) elif recordHeader.type == ContentType.alert: yield Alert().parse(p) elif recordHeader.type == ContentType.application_data: yield ApplicationData().parse(p) elif recordHeader.type == ContentType.handshake: #Convert secondaryType to tuple, if it isn't already if not isinstance(secondaryType, tuple): secondaryType = (secondaryType,) #If it's a handshake message, check handshake header if recordHeader.ssl2: subType = p.get(1) if subType != HandshakeType.client_hello: for result in self._sendError(\ AlertDescription.unexpected_message, "Can only handle SSLv2 ClientHello messages"): yield result if HandshakeType.client_hello not in secondaryType: for result in self._sendError(\ AlertDescription.unexpected_message): yield result subType = HandshakeType.client_hello else: subType = p.get(1) if subType not in secondaryType: for result in self._sendError(\ AlertDescription.unexpected_message, "Expecting %s, got %s" % (str(secondaryType), subType)): yield result #Update handshake hashes sToHash = bytesToString(p.bytes) self._handshake_md5.update(sToHash) self._handshake_sha.update(sToHash) #Parse based on handshake type if subType == HandshakeType.client_hello: yield ClientHello(recordHeader.ssl2).parse(p) elif subType == HandshakeType.server_hello: yield ServerHello().parse(p) elif subType == HandshakeType.certificate: yield Certificate(constructorType).parse(p) elif subType == HandshakeType.certificate_request: yield CertificateRequest().parse(p) elif subType == HandshakeType.certificate_verify: yield CertificateVerify().parse(p) elif subType == HandshakeType.server_key_exchange: yield ServerKeyExchange(constructorType).parse(p) elif subType == HandshakeType.server_hello_done: yield ServerHelloDone().parse(p) elif subType == HandshakeType.client_key_exchange: yield ClientKeyExchange(constructorType, \ self.version).parse(p) elif subType == HandshakeType.finished: yield Finished(self.version).parse(p) else: raise AssertionError() #If an exception was raised by a Parser or Message instance: except SyntaxError, e: for result in self._sendError(AlertDescription.decode_error, formatExceptionTrace(e)): yield result #Returns next record or next handshake message def _getNextRecord(self): #If there's a handshake message waiting, return it if self._handshakeBuffer: recordHeader, bytes = self._handshakeBuffer[0] self._handshakeBuffer = self._handshakeBuffer[1:] yield (recordHeader, Parser(bytes)) return #Otherwise... #Read the next record header bytes = createByteArraySequence([]) recordHeaderLength = 1 ssl2 = False while 1: try: s = self.sock.recv(recordHeaderLength-len(bytes)) except socket.error, why: if why[0] == errno.EWOULDBLOCK: yield 0 continue else: raise #If the connection was abruptly closed, raise an error if len(s)==0: raise TLSAbruptCloseError() bytes += stringToBytes(s) if len(bytes)==1: if bytes[0] in ContentType.all: ssl2 = False recordHeaderLength = 5 elif bytes[0] == 128: ssl2 = True recordHeaderLength = 2 else: raise SyntaxError() if len(bytes) == recordHeaderLength: break #Parse the record header if ssl2: r = RecordHeader2().parse(Parser(bytes)) else: r = RecordHeader3().parse(Parser(bytes)) #Check the record header fields if r.length > 18432: for result in self._sendError(AlertDescription.record_overflow): yield result #Read the record contents bytes = createByteArraySequence([]) while 1: try: s = self.sock.recv(r.length - len(bytes)) except socket.error, why: if why[0] == errno.EWOULDBLOCK: yield 0 continue else: raise #If the connection is closed, raise a socket error if len(s)==0: raise TLSAbruptCloseError() bytes += stringToBytes(s) if len(bytes) == r.length: break #Check the record header fields (2) #We do this after reading the contents from the socket, so that #if there's an error, we at least don't leave extra bytes in the #socket.. # # THIS CHECK HAS NO SECURITY RELEVANCE (?), BUT COULD HURT INTEROP. # SO WE LEAVE IT OUT FOR NOW. # #if self._versionCheck and r.version != self.version: # for result in self._sendError(AlertDescription.protocol_version, # "Version in header field: %s, should be %s" % (str(r.version), # str(self.version))): # yield result #Decrypt the record for result in self._decryptRecord(r.type, bytes): if result in (0,1): yield result else: break bytes = result p = Parser(bytes) #If it doesn't contain handshake messages, we can just return it if r.type != ContentType.handshake: yield (r, p) #If it's an SSLv2 ClientHello, we can return it as well elif r.ssl2: yield (r, p) else: #Otherwise, we loop through and add the handshake messages to the #handshake buffer while 1: if p.index == len(bytes): #If we're at the end if not self._handshakeBuffer: for result in self._sendError(\ AlertDescription.decode_error, \ "Received empty handshake record"): yield result break #There needs to be at least 4 bytes to get a header if p.index+4 > len(bytes): for result in self._sendError(\ AlertDescription.decode_error, "A record has a partial handshake message (1)"): yield result p.get(1) # skip handshake type msgLength = p.get(3) if p.index+msgLength > len(bytes): for result in self._sendError(\ AlertDescription.decode_error, "A record has a partial handshake message (2)"): yield result handshakePair = (r, bytes[p.index-4 : p.index+msgLength]) self._handshakeBuffer.append(handshakePair) p.index += msgLength #We've moved at least one handshake message into the #handshakeBuffer, return the first one recordHeader, bytes = self._handshakeBuffer[0] self._handshakeBuffer = self._handshakeBuffer[1:] yield (recordHeader, Parser(bytes)) def _decryptRecord(self, recordType, bytes): if self._readState.encContext: #Decrypt if it's a block cipher if self._readState.encContext.isBlockCipher: blockLength = self._readState.encContext.block_size if len(bytes) % blockLength != 0: for result in self._sendError(\ AlertDescription.decryption_failed, "Encrypted data not a multiple of blocksize"): yield result ciphertext = bytesToString(bytes) plaintext = self._readState.encContext.decrypt(ciphertext) if self.version == (3,2): #For TLS 1.1, remove explicit IV plaintext = plaintext[self._readState.encContext.block_size : ] bytes = stringToBytes(plaintext) #Check padding paddingGood = True paddingLength = bytes[-1] if (paddingLength+1) > len(bytes): paddingGood=False totalPaddingLength = 0 else: if self.version == (3,0): totalPaddingLength = paddingLength+1 elif self.version in ((3,1), (3,2)): totalPaddingLength = paddingLength+1 paddingBytes = bytes[-totalPaddingLength:-1] for byte in paddingBytes: if byte != paddingLength: paddingGood = False totalPaddingLength = 0 else: raise AssertionError() #Decrypt if it's a stream cipher else: paddingGood = True ciphertext = bytesToString(bytes) plaintext = self._readState.encContext.decrypt(ciphertext) bytes = stringToBytes(plaintext) totalPaddingLength = 0 #Check MAC macGood = True macLength = self._readState.macContext.digest_size endLength = macLength + totalPaddingLength if endLength > len(bytes): macGood = False else: #Read MAC startIndex = len(bytes) - endLength endIndex = startIndex + macLength checkBytes = bytes[startIndex : endIndex] #Calculate MAC seqnumStr = self._readState.getSeqNumStr() bytes = bytes[:-endLength] bytesStr = bytesToString(bytes) mac = self._readState.macContext.copy() mac.update(seqnumStr) mac.update(chr(recordType)) if self.version == (3,0): mac.update( chr( int(len(bytes)/256) ) ) mac.update( chr( int(len(bytes)%256) ) ) elif self.version in ((3,1), (3,2)): mac.update(chr(self.version[0])) mac.update(chr(self.version[1])) mac.update( chr( int(len(bytes)/256) ) ) mac.update( chr( int(len(bytes)%256) ) ) else: raise AssertionError() mac.update(bytesStr) macString = mac.digest() macBytes = stringToBytes(macString) #Compare MACs if macBytes != checkBytes: macGood = False if not (paddingGood and macGood): for result in self._sendError(AlertDescription.bad_record_mac, "MAC failure (or padding failure)"): yield result yield bytes def _handshakeStart(self, client): self._client = client self._handshake_md5 = md5.md5() self._handshake_sha = sha.sha() self._handshakeBuffer = [] self.allegedSharedKeyUsername = None self.allegedSrpUsername = None self._refCount = 1 def _handshakeDone(self, resumed): self.resumed = resumed self.closed = False def _calcPendingStates(self, clientRandom, serverRandom, implementations): if self.session.cipherSuite in CipherSuite.aes128Suites: macLength = 20 keyLength = 16 ivLength = 16 createCipherFunc = createAES elif self.session.cipherSuite in CipherSuite.aes256Suites: macLength = 20 keyLength = 32 ivLength = 16 createCipherFunc = createAES elif self.session.cipherSuite in CipherSuite.rc4Suites: macLength = 20 keyLength = 16 ivLength = 0 createCipherFunc = createRC4 elif self.session.cipherSuite in CipherSuite.tripleDESSuites: macLength = 20 keyLength = 24 ivLength = 8 createCipherFunc = createTripleDES else: raise AssertionError() if self.version == (3,0): createMACFunc = MAC_SSL elif self.version in ((3,1), (3,2)): createMACFunc = hmac.HMAC outputLength = (macLength*2) + (keyLength*2) + (ivLength*2) #Calculate Keying Material from Master Secret if self.version == (3,0): keyBlock = PRF_SSL(self.session.masterSecret, concatArrays(serverRandom, clientRandom), outputLength) elif self.version in ((3,1), (3,2)): keyBlock = PRF(self.session.masterSecret, "key expansion", concatArrays(serverRandom,clientRandom), outputLength) else: raise AssertionError() #Slice up Keying Material clientPendingState = _ConnectionState() serverPendingState = _ConnectionState() p = Parser(keyBlock) clientMACBlock = bytesToString(p.getFixBytes(macLength)) serverMACBlock = bytesToString(p.getFixBytes(macLength)) clientKeyBlock = bytesToString(p.getFixBytes(keyLength)) serverKeyBlock = bytesToString(p.getFixBytes(keyLength)) clientIVBlock = bytesToString(p.getFixBytes(ivLength)) serverIVBlock = bytesToString(p.getFixBytes(ivLength)) clientPendingState.macContext = createMACFunc(clientMACBlock, digestmod=sha) serverPendingState.macContext = createMACFunc(serverMACBlock, digestmod=sha) clientPendingState.encContext = createCipherFunc(clientKeyBlock, clientIVBlock, implementations) serverPendingState.encContext = createCipherFunc(serverKeyBlock, serverIVBlock, implementations) #Assign new connection states to pending states if self._client: self._pendingWriteState = clientPendingState self._pendingReadState = serverPendingState else: self._pendingWriteState = serverPendingState self._pendingReadState = clientPendingState if self.version == (3,2) and ivLength: #Choose fixedIVBlock for TLS 1.1 (this is encrypted with the CBC #residue to create the IV for each sent block) self.fixedIVBlock = getRandomBytes(ivLength) def _changeWriteState(self): self._writeState = self._pendingWriteState self._pendingWriteState = _ConnectionState() def _changeReadState(self): self._readState = self._pendingReadState self._pendingReadState = _ConnectionState() def _sendFinished(self): #Send ChangeCipherSpec for result in self._sendMsg(ChangeCipherSpec()): yield result #Switch to pending write state self._changeWriteState() #Calculate verification data verifyData = self._calcFinished(True) if self.fault == Fault.badFinished: verifyData[0] = (verifyData[0]+1)%256 #Send Finished message under new state finished = Finished(self.version).create(verifyData) for result in self._sendMsg(finished): yield result def _getFinished(self): #Get and check ChangeCipherSpec for result in self._getMsg(ContentType.change_cipher_spec): if result in (0,1): yield result changeCipherSpec = result if changeCipherSpec.type != 1: for result in self._sendError(AlertDescription.illegal_parameter, "ChangeCipherSpec type incorrect"): yield result #Switch to pending read state self._changeReadState() #Calculate verification data verifyData = self._calcFinished(False) #Get and check Finished message under new state for result in self._getMsg(ContentType.handshake, HandshakeType.finished): if result in (0,1): yield result finished = result if finished.verify_data != verifyData: for result in self._sendError(AlertDescription.decrypt_error, "Finished message is incorrect"): yield result def _calcFinished(self, send=True): if self.version == (3,0): if (self._client and send) or (not self._client and not send): senderStr = "\x43\x4C\x4E\x54" else: senderStr = "\x53\x52\x56\x52" verifyData = self._calcSSLHandshakeHash(self.session.masterSecret, senderStr) return verifyData elif self.version in ((3,1), (3,2)): if (self._client and send) or (not self._client and not send): label = "client finished" else: label = "server finished" handshakeHashes = stringToBytes(self._handshake_md5.digest() + \ self._handshake_sha.digest()) verifyData = PRF(self.session.masterSecret, label, handshakeHashes, 12) return verifyData else: raise AssertionError() #Used for Finished messages and CertificateVerify messages in SSL v3 def _calcSSLHandshakeHash(self, masterSecret, label): masterSecretStr = bytesToString(masterSecret) imac_md5 = self._handshake_md5.copy() imac_sha = self._handshake_sha.copy() imac_md5.update(label + masterSecretStr + '\x36'*48) imac_sha.update(label + masterSecretStr + '\x36'*40) md5Str = md5.md5(masterSecretStr + ('\x5c'*48) + \ imac_md5.digest()).digest() shaStr = sha.sha(masterSecretStr + ('\x5c'*40) + \ imac_sha.digest()).digest() return stringToBytes(md5Str + shaStr) gdata-2.0.18/samples/apps/marketplace_sample/gdata/tlslite/errors.py0000750036466300116100000001324312156622362025630 0ustar afshareng00000000000000"""Exception classes. @sort: TLSError, TLSAbruptCloseError, TLSAlert, TLSLocalAlert, TLSRemoteAlert, TLSAuthenticationError, TLSNoAuthenticationError, TLSAuthenticationTypeError, TLSFingerprintError, TLSAuthorizationError, TLSValidationError, TLSFaultError """ from constants import AlertDescription, AlertLevel class TLSError(Exception): """Base class for all TLS Lite exceptions.""" pass class TLSAbruptCloseError(TLSError): """The socket was closed without a proper TLS shutdown. The TLS specification mandates that an alert of some sort must be sent before the underlying socket is closed. If the socket is closed without this, it could signify that an attacker is trying to truncate the connection. It could also signify a misbehaving TLS implementation, or a random network failure. """ pass class TLSAlert(TLSError): """A TLS alert has been signalled.""" pass _descriptionStr = {\ AlertDescription.close_notify: "close_notify",\ AlertDescription.unexpected_message: "unexpected_message",\ AlertDescription.bad_record_mac: "bad_record_mac",\ AlertDescription.decryption_failed: "decryption_failed",\ AlertDescription.record_overflow: "record_overflow",\ AlertDescription.decompression_failure: "decompression_failure",\ AlertDescription.handshake_failure: "handshake_failure",\ AlertDescription.no_certificate: "no certificate",\ AlertDescription.bad_certificate: "bad_certificate",\ AlertDescription.unsupported_certificate: "unsupported_certificate",\ AlertDescription.certificate_revoked: "certificate_revoked",\ AlertDescription.certificate_expired: "certificate_expired",\ AlertDescription.certificate_unknown: "certificate_unknown",\ AlertDescription.illegal_parameter: "illegal_parameter",\ AlertDescription.unknown_ca: "unknown_ca",\ AlertDescription.access_denied: "access_denied",\ AlertDescription.decode_error: "decode_error",\ AlertDescription.decrypt_error: "decrypt_error",\ AlertDescription.export_restriction: "export_restriction",\ AlertDescription.protocol_version: "protocol_version",\ AlertDescription.insufficient_security: "insufficient_security",\ AlertDescription.internal_error: "internal_error",\ AlertDescription.user_canceled: "user_canceled",\ AlertDescription.no_renegotiation: "no_renegotiation",\ AlertDescription.unknown_srp_username: "unknown_srp_username",\ AlertDescription.missing_srp_username: "missing_srp_username"} class TLSLocalAlert(TLSAlert): """A TLS alert has been signalled by the local implementation. @type description: int @ivar description: Set to one of the constants in L{tlslite.constants.AlertDescription} @type level: int @ivar level: Set to one of the constants in L{tlslite.constants.AlertLevel} @type message: str @ivar message: Description of what went wrong. """ def __init__(self, alert, message=None): self.description = alert.description self.level = alert.level self.message = message def __str__(self): alertStr = TLSAlert._descriptionStr.get(self.description) if alertStr == None: alertStr = str(self.description) if self.message: return alertStr + ": " + self.message else: return alertStr class TLSRemoteAlert(TLSAlert): """A TLS alert has been signalled by the remote implementation. @type description: int @ivar description: Set to one of the constants in L{tlslite.constants.AlertDescription} @type level: int @ivar level: Set to one of the constants in L{tlslite.constants.AlertLevel} """ def __init__(self, alert): self.description = alert.description self.level = alert.level def __str__(self): alertStr = TLSAlert._descriptionStr.get(self.description) if alertStr == None: alertStr = str(self.description) return alertStr class TLSAuthenticationError(TLSError): """The handshake succeeded, but the other party's authentication was inadequate. This exception will only be raised when a L{tlslite.Checker.Checker} has been passed to a handshake function. The Checker will be invoked once the handshake completes, and if the Checker objects to how the other party authenticated, a subclass of this exception will be raised. """ pass class TLSNoAuthenticationError(TLSAuthenticationError): """The Checker was expecting the other party to authenticate with a certificate chain, but this did not occur.""" pass class TLSAuthenticationTypeError(TLSAuthenticationError): """The Checker was expecting the other party to authenticate with a different type of certificate chain.""" pass class TLSFingerprintError(TLSAuthenticationError): """The Checker was expecting the other party to authenticate with a certificate chain that matches a different fingerprint.""" pass class TLSAuthorizationError(TLSAuthenticationError): """The Checker was expecting the other party to authenticate with a certificate chain that has a different authorization.""" pass class TLSValidationError(TLSAuthenticationError): """The Checker has determined that the other party's certificate chain is invalid.""" pass class TLSFaultError(TLSError): """The other party responded incorrectly to an induced fault. This exception will only occur during fault testing, when a TLSConnection's fault variable is set to induce some sort of faulty behavior, and the other party doesn't respond appropriately. """ pass gdata-2.0.18/samples/apps/marketplace_sample/gdata/tlslite/Checker.py0000750036466300116100000001423512156622362025662 0ustar afshareng00000000000000"""Class for post-handshake certificate checking.""" from utils.cryptomath import hashAndBase64 from X509 import X509 from X509CertChain import X509CertChain from errors import * class Checker: """This class is passed to a handshake function to check the other party's certificate chain. If a handshake function completes successfully, but the Checker judges the other party's certificate chain to be missing or inadequate, a subclass of L{tlslite.errors.TLSAuthenticationError} will be raised. Currently, the Checker can check either an X.509 or a cryptoID chain (for the latter, cryptoIDlib must be installed). """ def __init__(self, cryptoID=None, protocol=None, x509Fingerprint=None, x509TrustList=None, x509CommonName=None, checkResumedSession=False): """Create a new Checker instance. You must pass in one of these argument combinations: - cryptoID[, protocol] (requires cryptoIDlib) - x509Fingerprint - x509TrustList[, x509CommonName] (requires cryptlib_py) @type cryptoID: str @param cryptoID: A cryptoID which the other party's certificate chain must match. The cryptoIDlib module must be installed. Mutually exclusive with all of the 'x509...' arguments. @type protocol: str @param protocol: A cryptoID protocol URI which the other party's certificate chain must match. Requires the 'cryptoID' argument. @type x509Fingerprint: str @param x509Fingerprint: A hex-encoded X.509 end-entity fingerprint which the other party's end-entity certificate must match. Mutually exclusive with the 'cryptoID' and 'x509TrustList' arguments. @type x509TrustList: list of L{tlslite.X509.X509} @param x509TrustList: A list of trusted root certificates. The other party must present a certificate chain which extends to one of these root certificates. The cryptlib_py module must be installed. Mutually exclusive with the 'cryptoID' and 'x509Fingerprint' arguments. @type x509CommonName: str @param x509CommonName: The end-entity certificate's 'CN' field must match this value. For a web server, this is typically a server name such as 'www.amazon.com'. Mutually exclusive with the 'cryptoID' and 'x509Fingerprint' arguments. Requires the 'x509TrustList' argument. @type checkResumedSession: bool @param checkResumedSession: If resumed sessions should be checked. This defaults to False, on the theory that if the session was checked once, we don't need to bother re-checking it. """ if cryptoID and (x509Fingerprint or x509TrustList): raise ValueError() if x509Fingerprint and x509TrustList: raise ValueError() if x509CommonName and not x509TrustList: raise ValueError() if protocol and not cryptoID: raise ValueError() if cryptoID: import cryptoIDlib #So we raise an error here if x509TrustList: import cryptlib_py #So we raise an error here self.cryptoID = cryptoID self.protocol = protocol self.x509Fingerprint = x509Fingerprint self.x509TrustList = x509TrustList self.x509CommonName = x509CommonName self.checkResumedSession = checkResumedSession def __call__(self, connection): """Check a TLSConnection. When a Checker is passed to a handshake function, this will be called at the end of the function. @type connection: L{tlslite.TLSConnection.TLSConnection} @param connection: The TLSConnection to examine. @raise tlslite.errors.TLSAuthenticationError: If the other party's certificate chain is missing or bad. """ if not self.checkResumedSession and connection.resumed: return if self.cryptoID or self.x509Fingerprint or self.x509TrustList: if connection._client: chain = connection.session.serverCertChain else: chain = connection.session.clientCertChain if self.x509Fingerprint or self.x509TrustList: if isinstance(chain, X509CertChain): if self.x509Fingerprint: if chain.getFingerprint() != self.x509Fingerprint: raise TLSFingerprintError(\ "X.509 fingerprint mismatch: %s, %s" % \ (chain.getFingerprint(), self.x509Fingerprint)) else: #self.x509TrustList if not chain.validate(self.x509TrustList): raise TLSValidationError("X.509 validation failure") if self.x509CommonName and \ (chain.getCommonName() != self.x509CommonName): raise TLSAuthorizationError(\ "X.509 Common Name mismatch: %s, %s" % \ (chain.getCommonName(), self.x509CommonName)) elif chain: raise TLSAuthenticationTypeError() else: raise TLSNoAuthenticationError() elif self.cryptoID: import cryptoIDlib.CertChain if isinstance(chain, cryptoIDlib.CertChain.CertChain): if chain.cryptoID != self.cryptoID: raise TLSFingerprintError(\ "cryptoID mismatch: %s, %s" % \ (chain.cryptoID, self.cryptoID)) if self.protocol: if not chain.checkProtocol(self.protocol): raise TLSAuthorizationError(\ "cryptoID protocol mismatch") if not chain.validate(): raise TLSValidationError("cryptoID validation failure") elif chain: raise TLSAuthenticationTypeError() else: raise TLSNoAuthenticationError() gdata-2.0.18/samples/apps/marketplace_sample/gdata/service.py0000750036466300116100000020775112156622362024305 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright (C) 2006,2008 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. """GDataService provides CRUD ops. and programmatic login for GData services. Error: A base exception class for all exceptions in the gdata_client module. CaptchaRequired: This exception is thrown when a login attempt results in a captcha challenge from the ClientLogin service. When this exception is thrown, the captcha_token and captcha_url are set to the values provided in the server's response. BadAuthentication: Raised when a login attempt is made with an incorrect username or password. NotAuthenticated: Raised if an operation requiring authentication is called before a user has authenticated. NonAuthSubToken: Raised if a method to modify an AuthSub token is used when the user is either not authenticated or is authenticated through another authentication mechanism. NonOAuthToken: Raised if a method to modify an OAuth token is used when the user is either not authenticated or is authenticated through another authentication mechanism. RequestError: Raised if a CRUD request returned a non-success code. UnexpectedReturnType: Raised if the response from the server was not of the desired type. For example, this would be raised if the server sent a feed when the client requested an entry. GDataService: Encapsulates user credentials needed to perform insert, update and delete operations with the GData API. An instance can perform user authentication, query, insertion, deletion, and update. Query: Eases query URI creation by allowing URI parameters to be set as dictionary attributes. For example a query with a feed of '/base/feeds/snippets' and ['bq'] set to 'digital camera' will produce '/base/feeds/snippets?bq=digital+camera' when .ToUri() is called on it. """ __author__ = 'api.jscudder (Jeffrey Scudder)' import re import urllib import urlparse try: from xml.etree import cElementTree as ElementTree except ImportError: try: import cElementTree as ElementTree except ImportError: try: from xml.etree import ElementTree except ImportError: from elementtree import ElementTree import atom.service import gdata import atom import atom.http_interface import atom.token_store import gdata.auth import gdata.gauth AUTH_SERVER_HOST = 'https://www.google.com' # When requesting an AuthSub token, it is often helpful to track the scope # which is being requested. One way to accomplish this is to add a URL # parameter to the 'next' URL which contains the requested scope. This # constant is the default name (AKA key) for the URL parameter. SCOPE_URL_PARAM_NAME = 'authsub_token_scope' # When requesting an OAuth access token or authorization of an existing OAuth # request token, it is often helpful to track the scope(s) which is/are being # requested. One way to accomplish this is to add a URL parameter to the # 'callback' URL which contains the requested scope. This constant is the # default name (AKA key) for the URL parameter. OAUTH_SCOPE_URL_PARAM_NAME = 'oauth_token_scope' # Maps the service names used in ClientLogin to scope URLs. CLIENT_LOGIN_SCOPES = gdata.gauth.AUTH_SCOPES # Default parameters for GDataService.GetWithRetries method DEFAULT_NUM_RETRIES = 3 DEFAULT_DELAY = 1 DEFAULT_BACKOFF = 2 def lookup_scopes(service_name): """Finds the scope URLs for the desired service. In some cases, an unknown service may be used, and in those cases this function will return None. """ if service_name in CLIENT_LOGIN_SCOPES: return CLIENT_LOGIN_SCOPES[service_name] return None # Module level variable specifies which module should be used by GDataService # objects to make HttpRequests. This setting can be overridden on each # instance of GDataService. # This module level variable is deprecated. Reassign the http_client member # of a GDataService object instead. http_request_handler = atom.service class Error(Exception): pass class CaptchaRequired(Error): pass class BadAuthentication(Error): pass class NotAuthenticated(Error): pass class NonAuthSubToken(Error): pass class NonOAuthToken(Error): pass class RequestError(Error): pass class UnexpectedReturnType(Error): pass class BadAuthenticationServiceURL(Error): pass class FetchingOAuthRequestTokenFailed(RequestError): pass class TokenUpgradeFailed(RequestError): pass class RevokingOAuthTokenFailed(RequestError): pass class AuthorizationRequired(Error): pass class TokenHadNoScope(Error): pass class RanOutOfTries(Error): pass class GDataService(atom.service.AtomService): """Contains elements needed for GData login and CRUD request headers. Maintains additional headers (tokens for example) needed for the GData services to allow a user to perform inserts, updates, and deletes. """ # The hander member is deprecated, use http_client instead. handler = None # The auth_token member is deprecated, use the token_store instead. auth_token = None # The tokens dict is deprecated in favor of the token_store. tokens = None def __init__(self, email=None, password=None, account_type='HOSTED_OR_GOOGLE', service=None, auth_service_url=None, source=None, server=None, additional_headers=None, handler=None, tokens=None, http_client=None, token_store=None): """Creates an object of type GDataService. Args: email: string (optional) The user's email address, used for authentication. password: string (optional) The user's password. account_type: string (optional) The type of account to use. Use 'GOOGLE' for regular Google accounts or 'HOSTED' for Google Apps accounts, or 'HOSTED_OR_GOOGLE' to try finding a HOSTED account first and, if it doesn't exist, try finding a regular GOOGLE account. Default value: 'HOSTED_OR_GOOGLE'. service: string (optional) The desired service for which credentials will be obtained. auth_service_url: string (optional) User-defined auth token request URL allows users to explicitly specify where to send auth token requests. source: string (optional) The name of the user's application. server: string (optional) The name of the server to which a connection will be opened. Default value: 'base.google.com'. additional_headers: dictionary (optional) Any additional headers which should be included with CRUD operations. handler: module (optional) This parameter is deprecated and has been replaced by http_client. tokens: This parameter is deprecated, calls should be made to token_store instead. http_client: An object responsible for making HTTP requests using a request method. If none is provided, a new instance of atom.http.ProxiedHttpClient will be used. token_store: Keeps a collection of authorization tokens which can be applied to requests for a specific URLs. Critical methods are find_token based on a URL (atom.url.Url or a string), add_token, and remove_token. """ atom.service.AtomService.__init__(self, http_client=http_client, token_store=token_store) self.email = email self.password = password self.account_type = account_type self.service = service self.auth_service_url = auth_service_url self.server = server self.additional_headers = additional_headers or {} self._oauth_input_params = None self.__SetSource(source) self.__captcha_token = None self.__captcha_url = None self.__gsessionid = None if http_request_handler.__name__ == 'gdata.urlfetch': import gdata.alt.appengine self.http_client = gdata.alt.appengine.AppEngineHttpClient() def _SetSessionId(self, session_id): """Used in unit tests to simulate a 302 which sets a gsessionid.""" self.__gsessionid = session_id # Define properties for GDataService def _SetAuthSubToken(self, auth_token, scopes=None): """Deprecated, use SetAuthSubToken instead.""" self.SetAuthSubToken(auth_token, scopes=scopes) def __SetAuthSubToken(self, auth_token, scopes=None): """Deprecated, use SetAuthSubToken instead.""" self._SetAuthSubToken(auth_token, scopes=scopes) def _GetAuthToken(self): """Returns the auth token used for authenticating requests. Returns: string """ current_scopes = lookup_scopes(self.service) if current_scopes: token = self.token_store.find_token(current_scopes[0]) if hasattr(token, 'auth_header'): return token.auth_header return None def _GetCaptchaToken(self): """Returns a captcha token if the most recent login attempt generated one. The captcha token is only set if the Programmatic Login attempt failed because the Google service issued a captcha challenge. Returns: string """ return self.__captcha_token def __GetCaptchaToken(self): return self._GetCaptchaToken() captcha_token = property(__GetCaptchaToken, doc="""Get the captcha token for a login request.""") def _GetCaptchaURL(self): """Returns the URL of the captcha image if a login attempt generated one. The captcha URL is only set if the Programmatic Login attempt failed because the Google service issued a captcha challenge. Returns: string """ return self.__captcha_url def __GetCaptchaURL(self): return self._GetCaptchaURL() captcha_url = property(__GetCaptchaURL, doc="""Get the captcha URL for a login request.""") def GetGeneratorFromLinkFinder(self, link_finder, func, num_retries=DEFAULT_NUM_RETRIES, delay=DEFAULT_DELAY, backoff=DEFAULT_BACKOFF): """returns a generator for pagination""" yield link_finder next = link_finder.GetNextLink() while next is not None: next_feed = func(str(self.GetWithRetries( next.href, num_retries=num_retries, delay=delay, backoff=backoff))) yield next_feed next = next_feed.GetNextLink() def _GetElementGeneratorFromLinkFinder(self, link_finder, func, num_retries=DEFAULT_NUM_RETRIES, delay=DEFAULT_DELAY, backoff=DEFAULT_BACKOFF): for element in self.GetGeneratorFromLinkFinder(link_finder, func, num_retries=num_retries, delay=delay, backoff=backoff).entry: yield element def GetOAuthInputParameters(self): return self._oauth_input_params def SetOAuthInputParameters(self, signature_method, consumer_key, consumer_secret=None, rsa_key=None, two_legged_oauth=False, requestor_id=None): """Sets parameters required for using OAuth authentication mechanism. NOTE: Though consumer_secret and rsa_key are optional, either of the two is required depending on the value of the signature_method. Args: signature_method: class which provides implementation for strategy class oauth.oauth.OAuthSignatureMethod. Signature method to be used for signing each request. Valid implementations are provided as the constants defined by gdata.auth.OAuthSignatureMethod. Currently they are gdata.auth.OAuthSignatureMethod.RSA_SHA1 and gdata.auth.OAuthSignatureMethod.HMAC_SHA1 consumer_key: string Domain identifying third_party web application. consumer_secret: string (optional) Secret generated during registration. Required only for HMAC_SHA1 signature method. rsa_key: string (optional) Private key required for RSA_SHA1 signature method. two_legged_oauth: boolean (optional) Enables two-legged OAuth process. requestor_id: string (optional) User email adress to make requests on their behalf. This parameter should only be set when two_legged_oauth is True. """ self._oauth_input_params = gdata.auth.OAuthInputParams( signature_method, consumer_key, consumer_secret=consumer_secret, rsa_key=rsa_key, requestor_id=requestor_id) if two_legged_oauth: oauth_token = gdata.auth.OAuthToken( oauth_input_params=self._oauth_input_params) self.SetOAuthToken(oauth_token) def FetchOAuthRequestToken(self, scopes=None, extra_parameters=None, request_url='%s/accounts/OAuthGetRequestToken' % \ AUTH_SERVER_HOST, oauth_callback=None): """Fetches and sets the OAuth request token and returns it. Args: scopes: string or list of string base URL(s) of the service(s) to be accessed. If None, then this method tries to determine the scope(s) from the current service. extra_parameters: dict (optional) key-value pairs as any additional parameters to be included in the URL and signature while making a request for fetching an OAuth request token. All the OAuth parameters are added by default. But if provided through this argument, any default parameters will be overwritten. For e.g. a default parameter oauth_version 1.0 can be overwritten if extra_parameters = {'oauth_version': '2.0'} request_url: Request token URL. The default is 'https://www.google.com/accounts/OAuthGetRequestToken'. oauth_callback: str (optional) If set, it is assume the client is using the OAuth v1.0a protocol where the callback url is sent in the request token step. If the oauth_callback is also set in extra_params, this value will override that one. Returns: The fetched request token as a gdata.auth.OAuthToken object. Raises: FetchingOAuthRequestTokenFailed if the server responded to the request with an error. """ if scopes is None: scopes = lookup_scopes(self.service) if not isinstance(scopes, (list, tuple)): scopes = [scopes,] if oauth_callback: if extra_parameters is not None: extra_parameters['oauth_callback'] = oauth_callback else: extra_parameters = {'oauth_callback': oauth_callback} request_token_url = gdata.auth.GenerateOAuthRequestTokenUrl( self._oauth_input_params, scopes, request_token_url=request_url, extra_parameters=extra_parameters) response = self.http_client.request('GET', str(request_token_url)) if response.status == 200: token = gdata.auth.OAuthToken() token.set_token_string(response.read()) token.scopes = scopes token.oauth_input_params = self._oauth_input_params self.SetOAuthToken(token) return token error = { 'status': response.status, 'reason': 'Non 200 response on fetch request token', 'body': response.read() } raise FetchingOAuthRequestTokenFailed(error) def SetOAuthToken(self, oauth_token): """Attempts to set the current token and add it to the token store. The oauth_token can be any OAuth token i.e. unauthorized request token, authorized request token or access token. This method also attempts to add the token to the token store. Use this method any time you want the current token to point to the oauth_token passed. For e.g. call this method with the request token you receive from FetchOAuthRequestToken. Args: request_token: gdata.auth.OAuthToken OAuth request token. """ if self.auto_set_current_token: self.current_token = oauth_token if self.auto_store_tokens: self.token_store.add_token(oauth_token) def GenerateOAuthAuthorizationURL( self, request_token=None, callback_url=None, extra_params=None, include_scopes_in_callback=False, scopes_param_prefix=OAUTH_SCOPE_URL_PARAM_NAME, request_url='%s/accounts/OAuthAuthorizeToken' % AUTH_SERVER_HOST): """Generates URL at which user will login to authorize the request token. Args: request_token: gdata.auth.OAuthToken (optional) OAuth request token. If not specified, then the current token will be used if it is of type , else it is found by looking in the token_store by looking for a token for the current scope. callback_url: string (optional) The URL user will be sent to after logging in and granting access. extra_params: dict (optional) Additional parameters to be sent. include_scopes_in_callback: Boolean (default=False) if set to True, and if 'callback_url' is present, the 'callback_url' will be modified to include the scope(s) from the request token as a URL parameter. The key for the 'callback' URL's scope parameter will be OAUTH_SCOPE_URL_PARAM_NAME. The benefit of including the scope URL as a parameter to the 'callback' URL, is that the page which receives the OAuth token will be able to tell which URLs the token grants access to. scopes_param_prefix: string (default='oauth_token_scope') The URL parameter key which maps to the list of valid scopes for the token. This URL parameter will be included in the callback URL along with the scopes of the token as value if include_scopes_in_callback=True. request_url: Authorization URL. The default is 'https://www.google.com/accounts/OAuthAuthorizeToken'. Returns: A string URL at which the user is required to login. Raises: NonOAuthToken if the user's request token is not an OAuth token or if a request token was not available. """ if request_token and not isinstance(request_token, gdata.auth.OAuthToken): raise NonOAuthToken if not request_token: if isinstance(self.current_token, gdata.auth.OAuthToken): request_token = self.current_token else: current_scopes = lookup_scopes(self.service) if current_scopes: token = self.token_store.find_token(current_scopes[0]) if isinstance(token, gdata.auth.OAuthToken): request_token = token if not request_token: raise NonOAuthToken return str(gdata.auth.GenerateOAuthAuthorizationUrl( request_token, authorization_url=request_url, callback_url=callback_url, extra_params=extra_params, include_scopes_in_callback=include_scopes_in_callback, scopes_param_prefix=scopes_param_prefix)) def UpgradeToOAuthAccessToken(self, authorized_request_token=None, request_url='%s/accounts/OAuthGetAccessToken' \ % AUTH_SERVER_HOST, oauth_version='1.0', oauth_verifier=None): """Upgrades the authorized request token to an access token and returns it Args: authorized_request_token: gdata.auth.OAuthToken (optional) OAuth request token. If not specified, then the current token will be used if it is of type , else it is found by looking in the token_store by looking for a token for the current scope. request_url: Access token URL. The default is 'https://www.google.com/accounts/OAuthGetAccessToken'. oauth_version: str (default='1.0') oauth_version parameter. All other 'oauth_' parameters are added by default. This parameter too, is added by default but here you can override it's value. oauth_verifier: str (optional) If present, it is assumed that the client will use the OAuth v1.0a protocol which includes passing the oauth_verifier (as returned by the SP) in the access token step. Returns: Access token Raises: NonOAuthToken if the user's authorized request token is not an OAuth token or if an authorized request token was not available. TokenUpgradeFailed if the server responded to the request with an error. """ if (authorized_request_token and not isinstance(authorized_request_token, gdata.auth.OAuthToken)): raise NonOAuthToken if not authorized_request_token: if isinstance(self.current_token, gdata.auth.OAuthToken): authorized_request_token = self.current_token else: current_scopes = lookup_scopes(self.service) if current_scopes: token = self.token_store.find_token(current_scopes[0]) if isinstance(token, gdata.auth.OAuthToken): authorized_request_token = token if not authorized_request_token: raise NonOAuthToken access_token_url = gdata.auth.GenerateOAuthAccessTokenUrl( authorized_request_token, self._oauth_input_params, access_token_url=request_url, oauth_version=oauth_version, oauth_verifier=oauth_verifier) response = self.http_client.request('GET', str(access_token_url)) if response.status == 200: token = gdata.auth.OAuthTokenFromHttpBody(response.read()) token.scopes = authorized_request_token.scopes token.oauth_input_params = authorized_request_token.oauth_input_params self.SetOAuthToken(token) return token else: raise TokenUpgradeFailed({'status': response.status, 'reason': 'Non 200 response on upgrade', 'body': response.read()}) def RevokeOAuthToken(self, request_url='%s/accounts/AuthSubRevokeToken' % \ AUTH_SERVER_HOST): """Revokes an existing OAuth token. request_url: Token revoke URL. The default is 'https://www.google.com/accounts/AuthSubRevokeToken'. Raises: NonOAuthToken if the user's auth token is not an OAuth token. RevokingOAuthTokenFailed if request for revoking an OAuth token failed. """ scopes = lookup_scopes(self.service) token = self.token_store.find_token(scopes[0]) if not isinstance(token, gdata.auth.OAuthToken): raise NonOAuthToken response = token.perform_request(self.http_client, 'GET', request_url, headers={'Content-Type':'application/x-www-form-urlencoded'}) if response.status == 200: self.token_store.remove_token(token) else: raise RevokingOAuthTokenFailed def GetAuthSubToken(self): """Returns the AuthSub token as a string. If the token is an gdta.auth.AuthSubToken, the Authorization Label ("AuthSub token") is removed. This method examines the current_token to see if it is an AuthSubToken or SecureAuthSubToken. If not, it searches the token_store for a token which matches the current scope. The current scope is determined by the service name string member. Returns: If the current_token is set to an AuthSubToken/SecureAuthSubToken, return the token string. If there is no current_token, a token string for a token which matches the service object's default scope is returned. If there are no tokens valid for the scope, returns None. """ if isinstance(self.current_token, gdata.auth.AuthSubToken): return self.current_token.get_token_string() current_scopes = lookup_scopes(self.service) if current_scopes: token = self.token_store.find_token(current_scopes[0]) if isinstance(token, gdata.auth.AuthSubToken): return token.get_token_string() else: token = self.token_store.find_token(atom.token_store.SCOPE_ALL) if isinstance(token, gdata.auth.ClientLoginToken): return token.get_token_string() return None def SetAuthSubToken(self, token, scopes=None, rsa_key=None): """Sets the token sent in requests to an AuthSub token. Sets the current_token and attempts to add the token to the token_store. Only use this method if you have received a token from the AuthSub service. The auth token is set automatically when UpgradeToSessionToken() is used. See documentation for Google AuthSub here: http://code.google.com/apis/accounts/AuthForWebApps.html Args: token: gdata.auth.AuthSubToken or gdata.auth.SecureAuthSubToken or string The token returned by the AuthSub service. If the token is an AuthSubToken or SecureAuthSubToken, the scope information stored in the token is used. If the token is a string, the scopes parameter is used to determine the valid scopes. scopes: list of URLs for which the token is valid. This is only used if the token parameter is a string. rsa_key: string (optional) Private key required for RSA_SHA1 signature method. This parameter is necessary if the token is a string representing a secure token. """ if not isinstance(token, gdata.auth.AuthSubToken): token_string = token if rsa_key: token = gdata.auth.SecureAuthSubToken(rsa_key) else: token = gdata.auth.AuthSubToken() token.set_token_string(token_string) # If no scopes were set for the token, use the scopes passed in, or # try to determine the scopes based on the current service name. If # all else fails, set the token to match all requests. if not token.scopes: if scopes is None: scopes = lookup_scopes(self.service) if scopes is None: scopes = [atom.token_store.SCOPE_ALL] token.scopes = scopes if self.auto_set_current_token: self.current_token = token if self.auto_store_tokens: self.token_store.add_token(token) def GetClientLoginToken(self): """Returns the token string for the current token or a token matching the service scope. If the current_token is a ClientLoginToken, the token string for the current token is returned. If the current_token is not set, this method searches for a token in the token_store which is valid for the service object's current scope. The current scope is determined by the service name string member. The token string is the end of the Authorization header, it doesn not include the ClientLogin label. """ if isinstance(self.current_token, gdata.auth.ClientLoginToken): return self.current_token.get_token_string() current_scopes = lookup_scopes(self.service) if current_scopes: token = self.token_store.find_token(current_scopes[0]) if isinstance(token, gdata.auth.ClientLoginToken): return token.get_token_string() else: token = self.token_store.find_token(atom.token_store.SCOPE_ALL) if isinstance(token, gdata.auth.ClientLoginToken): return token.get_token_string() return None def SetClientLoginToken(self, token, scopes=None): """Sets the token sent in requests to a ClientLogin token. This method sets the current_token to a new ClientLoginToken and it also attempts to add the ClientLoginToken to the token_store. Only use this method if you have received a token from the ClientLogin service. The auth_token is set automatically when ProgrammaticLogin() is used. See documentation for Google ClientLogin here: http://code.google.com/apis/accounts/docs/AuthForInstalledApps.html Args: token: string or instance of a ClientLoginToken. """ if not isinstance(token, gdata.auth.ClientLoginToken): token_string = token token = gdata.auth.ClientLoginToken() token.set_token_string(token_string) if not token.scopes: if scopes is None: scopes = lookup_scopes(self.service) if scopes is None: scopes = [atom.token_store.SCOPE_ALL] token.scopes = scopes if self.auto_set_current_token: self.current_token = token if self.auto_store_tokens: self.token_store.add_token(token) # Private methods to create the source property. def __GetSource(self): return self.__source def __SetSource(self, new_source): self.__source = new_source # Update the UserAgent header to include the new application name. self.additional_headers['User-Agent'] = atom.http_interface.USER_AGENT % ( self.__source,) source = property(__GetSource, __SetSource, doc="""The source is the name of the application making the request. It should be in the form company_id-app_name-app_version""") # Authentication operations def ProgrammaticLogin(self, captcha_token=None, captcha_response=None): """Authenticates the user and sets the GData Auth token. Login retreives a temporary auth token which must be used with all requests to GData services. The auth token is stored in the GData client object. Login is also used to respond to a captcha challenge. If the user's login attempt failed with a CaptchaRequired error, the user can respond by calling Login with the captcha token and the answer to the challenge. Args: captcha_token: string (optional) The identifier for the captcha challenge which was presented to the user. captcha_response: string (optional) The user's answer to the captch challenge. Raises: CaptchaRequired if the login service will require a captcha response BadAuthentication if the login service rejected the username or password Error if the login service responded with a 403 different from the above """ request_body = gdata.auth.generate_client_login_request_body(self.email, self.password, self.service, self.source, self.account_type, captcha_token, captcha_response) # If the user has defined their own authentication service URL, # send the ClientLogin requests to this URL: if not self.auth_service_url: auth_request_url = AUTH_SERVER_HOST + '/accounts/ClientLogin' else: auth_request_url = self.auth_service_url auth_response = self.http_client.request('POST', auth_request_url, data=request_body, headers={'Content-Type':'application/x-www-form-urlencoded'}) response_body = auth_response.read() if auth_response.status == 200: # TODO: insert the token into the token_store directly. self.SetClientLoginToken( gdata.auth.get_client_login_token(response_body)) self.__captcha_token = None self.__captcha_url = None elif auth_response.status == 403: # Examine each line to find the error type and the captcha token and # captch URL if they are present. captcha_parameters = gdata.auth.get_captcha_challenge(response_body, captcha_base_url='%s/accounts/' % AUTH_SERVER_HOST) if captcha_parameters: self.__captcha_token = captcha_parameters['token'] self.__captcha_url = captcha_parameters['url'] raise CaptchaRequired, 'Captcha Required' elif response_body.splitlines()[0] == 'Error=BadAuthentication': self.__captcha_token = None self.__captcha_url = None raise BadAuthentication, 'Incorrect username or password' else: self.__captcha_token = None self.__captcha_url = None raise Error, 'Server responded with a 403 code' elif auth_response.status == 302: self.__captcha_token = None self.__captcha_url = None # Google tries to redirect all bad URLs back to # http://www.google.. If a redirect # attempt is made, assume the user has supplied an incorrect authentication URL raise BadAuthenticationServiceURL, 'Server responded with a 302 code.' def ClientLogin(self, username, password, account_type=None, service=None, auth_service_url=None, source=None, captcha_token=None, captcha_response=None): """Convenience method for authenticating using ProgrammaticLogin. Sets values for email, password, and other optional members. Args: username: password: account_type: string (optional) service: string (optional) auth_service_url: string (optional) captcha_token: string (optional) captcha_response: string (optional) """ self.email = username self.password = password if account_type: self.account_type = account_type if service: self.service = service if source: self.source = source if auth_service_url: self.auth_service_url = auth_service_url self.ProgrammaticLogin(captcha_token, captcha_response) def GenerateAuthSubURL(self, next, scope, secure=False, session=True, domain='default'): """Generate a URL at which the user will login and be redirected back. Users enter their credentials on a Google login page and a token is sent to the URL specified in next. See documentation for AuthSub login at: http://code.google.com/apis/accounts/docs/AuthSub.html Args: next: string The URL user will be sent to after logging in. scope: string or list of strings. The URLs of the services to be accessed. secure: boolean (optional) Determines whether or not the issued token is a secure token. session: boolean (optional) Determines whether or not the issued token can be upgraded to a session token. """ if not isinstance(scope, (list, tuple)): scope = (scope,) return gdata.auth.generate_auth_sub_url(next, scope, secure=secure, session=session, request_url='%s/accounts/AuthSubRequest' % AUTH_SERVER_HOST, domain=domain) def UpgradeToSessionToken(self, token=None): """Upgrades a single use AuthSub token to a session token. Args: token: A gdata.auth.AuthSubToken or gdata.auth.SecureAuthSubToken (optional) which is good for a single use but can be upgraded to a session token. If no token is passed in, the token is found by looking in the token_store by looking for a token for the current scope. Raises: NonAuthSubToken if the user's auth token is not an AuthSub token TokenUpgradeFailed if the server responded to the request with an error. """ if token is None: scopes = lookup_scopes(self.service) if scopes: token = self.token_store.find_token(scopes[0]) else: token = self.token_store.find_token(atom.token_store.SCOPE_ALL) if not isinstance(token, gdata.auth.AuthSubToken): raise NonAuthSubToken self.SetAuthSubToken(self.upgrade_to_session_token(token)) def upgrade_to_session_token(self, token): """Upgrades a single use AuthSub token to a session token. Args: token: A gdata.auth.AuthSubToken or gdata.auth.SecureAuthSubToken which is good for a single use but can be upgraded to a session token. Returns: The upgraded token as a gdata.auth.AuthSubToken object. Raises: TokenUpgradeFailed if the server responded to the request with an error. """ response = token.perform_request(self.http_client, 'GET', AUTH_SERVER_HOST + '/accounts/AuthSubSessionToken', headers={'Content-Type':'application/x-www-form-urlencoded'}) response_body = response.read() if response.status == 200: token.set_token_string( gdata.auth.token_from_http_body(response_body)) return token else: raise TokenUpgradeFailed({'status': response.status, 'reason': 'Non 200 response on upgrade', 'body': response_body}) def RevokeAuthSubToken(self): """Revokes an existing AuthSub token. Raises: NonAuthSubToken if the user's auth token is not an AuthSub token """ scopes = lookup_scopes(self.service) token = self.token_store.find_token(scopes[0]) if not isinstance(token, gdata.auth.AuthSubToken): raise NonAuthSubToken response = token.perform_request(self.http_client, 'GET', AUTH_SERVER_HOST + '/accounts/AuthSubRevokeToken', headers={'Content-Type':'application/x-www-form-urlencoded'}) if response.status == 200: self.token_store.remove_token(token) def AuthSubTokenInfo(self): """Fetches the AuthSub token's metadata from the server. Raises: NonAuthSubToken if the user's auth token is not an AuthSub token """ scopes = lookup_scopes(self.service) token = self.token_store.find_token(scopes[0]) if not isinstance(token, gdata.auth.AuthSubToken): raise NonAuthSubToken response = token.perform_request(self.http_client, 'GET', AUTH_SERVER_HOST + '/accounts/AuthSubTokenInfo', headers={'Content-Type':'application/x-www-form-urlencoded'}) result_body = response.read() if response.status == 200: return result_body else: raise RequestError, {'status': response.status, 'body': result_body} def GetWithRetries(self, uri, extra_headers=None, redirects_remaining=4, encoding='UTF-8', converter=None, num_retries=DEFAULT_NUM_RETRIES, delay=DEFAULT_DELAY, backoff=DEFAULT_BACKOFF, logger=None): """This is a wrapper method for Get with retrying capability. To avoid various errors while retrieving bulk entities by retrying specified times. Note this method relies on the time module and so may not be usable by default in Python2.2. Args: num_retries: Integer; the retry count. delay: Integer; the initial delay for retrying. backoff: Integer; how much the delay should lengthen after each failure. logger: An object which has a debug(str) method to receive logging messages. Recommended that you pass in the logging module. Raises: ValueError if any of the parameters has an invalid value. RanOutOfTries on failure after number of retries. """ # Moved import for time module inside this method since time is not a # default module in Python2.2. This method will not be usable in # Python2.2. import time if backoff <= 1: raise ValueError("backoff must be greater than 1") num_retries = int(num_retries) if num_retries < 0: raise ValueError("num_retries must be 0 or greater") if delay <= 0: raise ValueError("delay must be greater than 0") # Let's start mtries, mdelay = num_retries, delay while mtries > 0: if mtries != num_retries: if logger: logger.debug("Retrying: %s" % uri) try: rv = self.Get(uri, extra_headers=extra_headers, redirects_remaining=redirects_remaining, encoding=encoding, converter=converter) except SystemExit: # Allow this error raise except RequestError, e: # Error 500 is 'internal server error' and warrants a retry # Error 503 is 'service unavailable' and warrants a retry if e[0]['status'] not in [500, 503]: raise e # Else, fall through to the retry code... except Exception, e: if logger: logger.debug(e) # Fall through to the retry code... else: # This is the right path. return rv mtries -= 1 time.sleep(mdelay) mdelay *= backoff raise RanOutOfTries('Ran out of tries.') # CRUD operations def Get(self, uri, extra_headers=None, redirects_remaining=4, encoding='UTF-8', converter=None): """Query the GData API with the given URI The uri is the portion of the URI after the server value (ex: www.google.com). To perform a query against Google Base, set the server to 'base.google.com' and set the uri to '/base/feeds/...', where ... is your query. For example, to find snippets for all digital cameras uri should be set to: '/base/feeds/snippets?bq=digital+camera' Args: uri: string The query in the form of a URI. Example: '/base/feeds/snippets?bq=digital+camera'. extra_headers: dictionary (optional) Extra HTTP headers to be included in the GET request. These headers are in addition to those stored in the client's additional_headers property. The client automatically sets the Content-Type and Authorization headers. redirects_remaining: int (optional) Tracks the number of additional redirects this method will allow. If the service object receives a redirect and remaining is 0, it will not follow the redirect. This was added to avoid infinite redirect loops. encoding: string (optional) The character encoding for the server's response. Default is UTF-8 converter: func (optional) A function which will transform the server's results before it is returned. Example: use GDataFeedFromString to parse the server response as if it were a GDataFeed. Returns: If there is no ResultsTransformer specified in the call, a GDataFeed or GDataEntry depending on which is sent from the server. If the response is niether a feed or entry and there is no ResultsTransformer, return a string. If there is a ResultsTransformer, the returned value will be that of the ResultsTransformer function. """ if extra_headers is None: extra_headers = {} if self.__gsessionid is not None: if uri.find('gsessionid=') < 0: if uri.find('?') > -1: uri += '&gsessionid=%s' % (self.__gsessionid,) else: uri += '?gsessionid=%s' % (self.__gsessionid,) server_response = self.request('GET', uri, headers=extra_headers) result_body = server_response.read() if server_response.status == 200: if converter: return converter(result_body) # There was no ResultsTransformer specified, so try to convert the # server's response into a GDataFeed. feed = gdata.GDataFeedFromString(result_body) if not feed: # If conversion to a GDataFeed failed, try to convert the server's # response to a GDataEntry. entry = gdata.GDataEntryFromString(result_body) if not entry: # The server's response wasn't a feed, or an entry, so return the # response body as a string. return result_body return entry return feed elif server_response.status == 302: if redirects_remaining > 0: location = (server_response.getheader('Location') or server_response.getheader('location')) if location is not None: m = re.compile('[\?\&]gsessionid=(\w*\-)').search(location) if m is not None: self.__gsessionid = m.group(1) return GDataService.Get(self, location, extra_headers, redirects_remaining - 1, encoding=encoding, converter=converter) else: raise RequestError, {'status': server_response.status, 'reason': '302 received without Location header', 'body': result_body} else: raise RequestError, {'status': server_response.status, 'reason': 'Redirect received, but redirects_remaining <= 0', 'body': result_body} else: raise RequestError, {'status': server_response.status, 'reason': server_response.reason, 'body': result_body} def GetMedia(self, uri, extra_headers=None): """Returns a MediaSource containing media and its metadata from the given URI string. """ response_handle = self.request('GET', uri, headers=extra_headers) return gdata.MediaSource(response_handle, response_handle.getheader( 'Content-Type'), response_handle.getheader('Content-Length')) def GetEntry(self, uri, extra_headers=None): """Query the GData API with the given URI and receive an Entry. See also documentation for gdata.service.Get Args: uri: string The query in the form of a URI. Example: '/base/feeds/snippets?bq=digital+camera'. extra_headers: dictionary (optional) Extra HTTP headers to be included in the GET request. These headers are in addition to those stored in the client's additional_headers property. The client automatically sets the Content-Type and Authorization headers. Returns: A GDataEntry built from the XML in the server's response. """ result = GDataService.Get(self, uri, extra_headers, converter=atom.EntryFromString) if isinstance(result, atom.Entry): return result else: raise UnexpectedReturnType, 'Server did not send an entry' def GetFeed(self, uri, extra_headers=None, converter=gdata.GDataFeedFromString): """Query the GData API with the given URI and receive a Feed. See also documentation for gdata.service.Get Args: uri: string The query in the form of a URI. Example: '/base/feeds/snippets?bq=digital+camera'. extra_headers: dictionary (optional) Extra HTTP headers to be included in the GET request. These headers are in addition to those stored in the client's additional_headers property. The client automatically sets the Content-Type and Authorization headers. Returns: A GDataFeed built from the XML in the server's response. """ result = GDataService.Get(self, uri, extra_headers, converter=converter) if isinstance(result, atom.Feed): return result else: raise UnexpectedReturnType, 'Server did not send a feed' def GetNext(self, feed): """Requests the next 'page' of results in the feed. This method uses the feed's next link to request an additional feed and uses the class of the feed to convert the results of the GET request. Args: feed: atom.Feed or a subclass. The feed should contain a next link and the type of the feed will be applied to the results from the server. The new feed which is returned will be of the same class as this feed which was passed in. Returns: A new feed representing the next set of results in the server's feed. The type of this feed will match that of the feed argument. """ next_link = feed.GetNextLink() # Create a closure which will convert an XML string to the class of # the feed object passed in. def ConvertToFeedClass(xml_string): return atom.CreateClassFromXMLString(feed.__class__, xml_string) # Make a GET request on the next link and use the above closure for the # converted which processes the XML string from the server. if next_link and next_link.href: return GDataService.Get(self, next_link.href, converter=ConvertToFeedClass) else: return None def Post(self, data, uri, extra_headers=None, url_params=None, escape_params=True, redirects_remaining=4, media_source=None, converter=None): """Insert or update data into a GData service at the given URI. Args: data: string, ElementTree._Element, atom.Entry, or gdata.GDataEntry The XML to be sent to the uri. uri: string The location (feed) to which the data should be inserted. Example: '/base/feeds/items'. extra_headers: dict (optional) HTTP headers which are to be included. The client automatically sets the Content-Type, Authorization, and Content-Length headers. url_params: dict (optional) Additional URL parameters to be included in the URI. These are translated into query arguments in the form '&dict_key=value&...'. Example: {'max-results': '250'} becomes &max-results=250 escape_params: boolean (optional) If false, the calling code has already ensured that the query will form a valid URL (all reserved characters have been escaped). If true, this method will escape the query and any URL parameters provided. media_source: MediaSource (optional) Container for the media to be sent along with the entry, if provided. converter: func (optional) A function which will be executed on the server's response. Often this is a function like GDataEntryFromString which will parse the body of the server's response and return a GDataEntry. Returns: If the post succeeded, this method will return a GDataFeed, GDataEntry, or the results of running converter on the server's result body (if converter was specified). """ return GDataService.PostOrPut(self, 'POST', data, uri, extra_headers=extra_headers, url_params=url_params, escape_params=escape_params, redirects_remaining=redirects_remaining, media_source=media_source, converter=converter) def PostOrPut(self, verb, data, uri, extra_headers=None, url_params=None, escape_params=True, redirects_remaining=4, media_source=None, converter=None): """Insert data into a GData service at the given URI. Args: verb: string, either 'POST' or 'PUT' data: string, ElementTree._Element, atom.Entry, or gdata.GDataEntry The XML to be sent to the uri. uri: string The location (feed) to which the data should be inserted. Example: '/base/feeds/items'. extra_headers: dict (optional) HTTP headers which are to be included. The client automatically sets the Content-Type, Authorization, and Content-Length headers. url_params: dict (optional) Additional URL parameters to be included in the URI. These are translated into query arguments in the form '&dict_key=value&...'. Example: {'max-results': '250'} becomes &max-results=250 escape_params: boolean (optional) If false, the calling code has already ensured that the query will form a valid URL (all reserved characters have been escaped). If true, this method will escape the query and any URL parameters provided. media_source: MediaSource (optional) Container for the media to be sent along with the entry, if provided. converter: func (optional) A function which will be executed on the server's response. Often this is a function like GDataEntryFromString which will parse the body of the server's response and return a GDataEntry. Returns: If the post succeeded, this method will return a GDataFeed, GDataEntry, or the results of running converter on the server's result body (if converter was specified). """ if extra_headers is None: extra_headers = {} if self.__gsessionid is not None: if uri.find('gsessionid=') < 0: if url_params is None: url_params = {} url_params['gsessionid'] = self.__gsessionid if data and media_source: if ElementTree.iselement(data): data_str = ElementTree.tostring(data) else: data_str = str(data) multipart = [] multipart.append('Media multipart posting\r\n--END_OF_PART\r\n' + \ 'Content-Type: application/atom+xml\r\n\r\n') multipart.append('\r\n--END_OF_PART\r\nContent-Type: ' + \ media_source.content_type+'\r\n\r\n') multipart.append('\r\n--END_OF_PART--\r\n') extra_headers['MIME-version'] = '1.0' extra_headers['Content-Length'] = str(len(multipart[0]) + len(multipart[1]) + len(multipart[2]) + len(data_str) + media_source.content_length) extra_headers['Content-Type'] = 'multipart/related; boundary=END_OF_PART' server_response = self.request(verb, uri, data=[multipart[0], data_str, multipart[1], media_source.file_handle, multipart[2]], headers=extra_headers, url_params=url_params) result_body = server_response.read() elif media_source or isinstance(data, gdata.MediaSource): if isinstance(data, gdata.MediaSource): media_source = data extra_headers['Content-Length'] = str(media_source.content_length) extra_headers['Content-Type'] = media_source.content_type server_response = self.request(verb, uri, data=media_source.file_handle, headers=extra_headers, url_params=url_params) result_body = server_response.read() else: http_data = data if 'Content-Type' not in extra_headers: content_type = 'application/atom+xml' extra_headers['Content-Type'] = content_type server_response = self.request(verb, uri, data=http_data, headers=extra_headers, url_params=url_params) result_body = server_response.read() # Server returns 201 for most post requests, but when performing a batch # request the server responds with a 200 on success. if server_response.status == 201 or server_response.status == 200: if converter: return converter(result_body) feed = gdata.GDataFeedFromString(result_body) if not feed: entry = gdata.GDataEntryFromString(result_body) if not entry: return result_body return entry return feed elif server_response.status == 302: if redirects_remaining > 0: location = (server_response.getheader('Location') or server_response.getheader('location')) if location is not None: m = re.compile('[\?\&]gsessionid=(\w*\-)').search(location) if m is not None: self.__gsessionid = m.group(1) return GDataService.PostOrPut(self, verb, data, location, extra_headers, url_params, escape_params, redirects_remaining - 1, media_source, converter=converter) else: raise RequestError, {'status': server_response.status, 'reason': '302 received without Location header', 'body': result_body} else: raise RequestError, {'status': server_response.status, 'reason': 'Redirect received, but redirects_remaining <= 0', 'body': result_body} else: raise RequestError, {'status': server_response.status, 'reason': server_response.reason, 'body': result_body} def Put(self, data, uri, extra_headers=None, url_params=None, escape_params=True, redirects_remaining=3, media_source=None, converter=None): """Updates an entry at the given URI. Args: data: string, ElementTree._Element, or xml_wrapper.ElementWrapper The XML containing the updated data. uri: string A URI indicating entry to which the update will be applied. Example: '/base/feeds/items/ITEM-ID' extra_headers: dict (optional) HTTP headers which are to be included. The client automatically sets the Content-Type, Authorization, and Content-Length headers. url_params: dict (optional) Additional URL parameters to be included in the URI. These are translated into query arguments in the form '&dict_key=value&...'. Example: {'max-results': '250'} becomes &max-results=250 escape_params: boolean (optional) If false, the calling code has already ensured that the query will form a valid URL (all reserved characters have been escaped). If true, this method will escape the query and any URL parameters provided. converter: func (optional) A function which will be executed on the server's response. Often this is a function like GDataEntryFromString which will parse the body of the server's response and return a GDataEntry. Returns: If the put succeeded, this method will return a GDataFeed, GDataEntry, or the results of running converter on the server's result body (if converter was specified). """ return GDataService.PostOrPut(self, 'PUT', data, uri, extra_headers=extra_headers, url_params=url_params, escape_params=escape_params, redirects_remaining=redirects_remaining, media_source=media_source, converter=converter) def Delete(self, uri, extra_headers=None, url_params=None, escape_params=True, redirects_remaining=4): """Deletes the entry at the given URI. Args: uri: string The URI of the entry to be deleted. Example: '/base/feeds/items/ITEM-ID' extra_headers: dict (optional) HTTP headers which are to be included. The client automatically sets the Content-Type and Authorization headers. url_params: dict (optional) Additional URL parameters to be included in the URI. These are translated into query arguments in the form '&dict_key=value&...'. Example: {'max-results': '250'} becomes &max-results=250 escape_params: boolean (optional) If false, the calling code has already ensured that the query will form a valid URL (all reserved characters have been escaped). If true, this method will escape the query and any URL parameters provided. Returns: True if the entry was deleted. """ if extra_headers is None: extra_headers = {} if self.__gsessionid is not None: if uri.find('gsessionid=') < 0: if url_params is None: url_params = {} url_params['gsessionid'] = self.__gsessionid server_response = self.request('DELETE', uri, headers=extra_headers, url_params=url_params) result_body = server_response.read() if server_response.status == 200: return True elif server_response.status == 302: if redirects_remaining > 0: location = (server_response.getheader('Location') or server_response.getheader('location')) if location is not None: m = re.compile('[\?\&]gsessionid=(\w*\-)').search(location) if m is not None: self.__gsessionid = m.group(1) return GDataService.Delete(self, location, extra_headers, url_params, escape_params, redirects_remaining - 1) else: raise RequestError, {'status': server_response.status, 'reason': '302 received without Location header', 'body': result_body} else: raise RequestError, {'status': server_response.status, 'reason': 'Redirect received, but redirects_remaining <= 0', 'body': result_body} else: raise RequestError, {'status': server_response.status, 'reason': server_response.reason, 'body': result_body} def ExtractToken(url, scopes_included_in_next=True): """Gets the AuthSub token from the current page's URL. Designed to be used on the URL that the browser is sent to after the user authorizes this application at the page given by GenerateAuthSubRequestUrl. Args: url: The current page's URL. It should contain the token as a URL parameter. Example: 'http://example.com/?...&token=abcd435' scopes_included_in_next: If True, this function looks for a scope value associated with the token. The scope is a URL parameter with the key set to SCOPE_URL_PARAM_NAME. This parameter should be present if the AuthSub request URL was generated using GenerateAuthSubRequestUrl with include_scope_in_next set to True. Returns: A tuple containing the token string and a list of scope strings for which this token should be valid. If the scope was not included in the URL, the tuple will contain (token, None). """ parsed = urlparse.urlparse(url) token = gdata.auth.AuthSubTokenFromUrl(parsed[4]) scopes = '' if scopes_included_in_next: for pair in parsed[4].split('&'): if pair.startswith('%s=' % SCOPE_URL_PARAM_NAME): scopes = urllib.unquote_plus(pair.split('=')[1]) return (token, scopes.split(' ')) def GenerateAuthSubRequestUrl(next, scopes, hd='default', secure=False, session=True, request_url='https://www.google.com/accounts/AuthSubRequest', include_scopes_in_next=True): """Creates a URL to request an AuthSub token to access Google services. For more details on AuthSub, see the documentation here: http://code.google.com/apis/accounts/docs/AuthSub.html Args: next: The URL where the browser should be sent after the user authorizes the application. This page is responsible for receiving the token which is embeded in the URL as a parameter. scopes: The base URL to which access will be granted. Example: 'http://www.google.com/calendar/feeds' will grant access to all URLs in the Google Calendar data API. If you would like a token for multiple scopes, pass in a list of URL strings. hd: The domain to which the user's account belongs. This is set to the domain name if you are using Google Apps. Example: 'example.org' Defaults to 'default' secure: If set to True, all requests should be signed. The default is False. session: If set to True, the token received by the 'next' URL can be upgraded to a multiuse session token. If session is set to False, the token may only be used once and cannot be upgraded. Default is True. request_url: The base of the URL to which the user will be sent to authorize this application to access their data. The default is 'https://www.google.com/accounts/AuthSubRequest'. include_scopes_in_next: Boolean if set to true, the 'next' parameter will be modified to include the requested scope as a URL parameter. The key for the next's scope parameter will be SCOPE_URL_PARAM_NAME. The benefit of including the scope URL as a parameter to the next URL, is that the page which receives the AuthSub token will be able to tell which URLs the token grants access to. Returns: A URL string to which the browser should be sent. """ if isinstance(scopes, list): scope = ' '.join(scopes) else: scope = scopes if include_scopes_in_next: if next.find('?') > -1: next += '&%s' % urllib.urlencode({SCOPE_URL_PARAM_NAME:scope}) else: next += '?%s' % urllib.urlencode({SCOPE_URL_PARAM_NAME:scope}) return gdata.auth.GenerateAuthSubUrl(next=next, scope=scope, secure=secure, session=session, request_url=request_url, domain=hd) class Query(dict): """Constructs a query URL to be used in GET requests Url parameters are created by adding key-value pairs to this object as a dict. For example, to add &max-results=25 to the URL do my_query['max-results'] = 25 Category queries are created by adding category strings to the categories member. All items in the categories list will be concatenated with the / symbol (symbolizing a category x AND y restriction). If you would like to OR 2 categories, append them as one string with a | between the categories. For example, do query.categories.append('Fritz|Laurie') to create a query like this feed/-/Fritz%7CLaurie . This query will look for results in both categories. """ def __init__(self, feed=None, text_query=None, params=None, categories=None): """Constructor for Query Args: feed: str (optional) The path for the feed (Examples: '/base/feeds/snippets' or 'calendar/feeds/jo@gmail.com/private/full' text_query: str (optional) The contents of the q query parameter. The contents of the text_query are URL escaped upon conversion to a URI. params: dict (optional) Parameter value string pairs which become URL params when translated to a URI. These parameters are added to the query's items (key-value pairs). categories: list (optional) List of category strings which should be included as query categories. See http://code.google.com/apis/gdata/reference.html#Queries for details. If you want to get results from category A or B (both categories), specify a single list item 'A|B'. """ self.feed = feed self.categories = [] if text_query: self.text_query = text_query if isinstance(params, dict): for param in params: self[param] = params[param] if isinstance(categories, list): for category in categories: self.categories.append(category) def _GetTextQuery(self): if 'q' in self.keys(): return self['q'] else: return None def _SetTextQuery(self, query): self['q'] = query text_query = property(_GetTextQuery, _SetTextQuery, doc="""The feed query's q parameter""") def _GetAuthor(self): if 'author' in self.keys(): return self['author'] else: return None def _SetAuthor(self, query): self['author'] = query author = property(_GetAuthor, _SetAuthor, doc="""The feed query's author parameter""") def _GetAlt(self): if 'alt' in self.keys(): return self['alt'] else: return None def _SetAlt(self, query): self['alt'] = query alt = property(_GetAlt, _SetAlt, doc="""The feed query's alt parameter""") def _GetUpdatedMin(self): if 'updated-min' in self.keys(): return self['updated-min'] else: return None def _SetUpdatedMin(self, query): self['updated-min'] = query updated_min = property(_GetUpdatedMin, _SetUpdatedMin, doc="""The feed query's updated-min parameter""") def _GetUpdatedMax(self): if 'updated-max' in self.keys(): return self['updated-max'] else: return None def _SetUpdatedMax(self, query): self['updated-max'] = query updated_max = property(_GetUpdatedMax, _SetUpdatedMax, doc="""The feed query's updated-max parameter""") def _GetPublishedMin(self): if 'published-min' in self.keys(): return self['published-min'] else: return None def _SetPublishedMin(self, query): self['published-min'] = query published_min = property(_GetPublishedMin, _SetPublishedMin, doc="""The feed query's published-min parameter""") def _GetPublishedMax(self): if 'published-max' in self.keys(): return self['published-max'] else: return None def _SetPublishedMax(self, query): self['published-max'] = query published_max = property(_GetPublishedMax, _SetPublishedMax, doc="""The feed query's published-max parameter""") def _GetStartIndex(self): if 'start-index' in self.keys(): return self['start-index'] else: return None def _SetStartIndex(self, query): if not isinstance(query, str): query = str(query) self['start-index'] = query start_index = property(_GetStartIndex, _SetStartIndex, doc="""The feed query's start-index parameter""") def _GetMaxResults(self): if 'max-results' in self.keys(): return self['max-results'] else: return None def _SetMaxResults(self, query): if not isinstance(query, str): query = str(query) self['max-results'] = query max_results = property(_GetMaxResults, _SetMaxResults, doc="""The feed query's max-results parameter""") def _GetOrderBy(self): if 'orderby' in self.keys(): return self['orderby'] else: return None def _SetOrderBy(self, query): self['orderby'] = query orderby = property(_GetOrderBy, _SetOrderBy, doc="""The feed query's orderby parameter""") def ToUri(self): q_feed = self.feed or '' category_string = '/'.join( [urllib.quote_plus(c) for c in self.categories]) # Add categories to the feed if there are any. if len(self.categories) > 0: q_feed = q_feed + '/-/' + category_string return atom.service.BuildUri(q_feed, self) def __str__(self): return self.ToUri() gdata-2.0.18/samples/apps/marketplace_sample/images/0000750036466300116100000000000012156625015022437 5ustar afshareng00000000000000gdata-2.0.18/samples/apps/marketplace_sample/images/google.png0000640036466300116100000002654312156622362024436 0ustar afshareng00000000000000‰PNG  IHDRuG9¶ pHYs  šœ OiCCPPhotoshop ICC profilexÚSgTSé=÷ÞôBKˆ€”KoR RB‹€‘&*! Jˆ!¡ÙQÁEEÈ ˆŽŽ€ŒQ, Š Øä!¢Žƒ£ˆŠÊûá{£kÖ¼÷æÍþµ×>ç¬ó³ÏÀ –H3Q5€ ©BàƒÇÄÆáä.@ $p³d!sý#ø~<<+"À¾xÓ ÀM›À0‡ÿêB™\€„Àt‘8K€@zŽB¦@F€˜&S `ËcbãP-`'æÓ€ø™{[”! ‘ eˆDh;¬ÏVŠEX0fKÄ9Ø-0IWfH°·ÀÎ ² 0Qˆ…){`È##x„™FòW<ñ+®ç*x™²<¹$9E[-qWW.(ÎI+6aaš@.Ây™24àóÌ ‘àƒóýxήÎÎ6޶_-ê¿ÿ"bbãþåÏ«p@át~Ñþ,/³€;€mþ¢%îh^  u÷‹f²@µ éÚWópø~<ß5°j>{‘-¨]cöK'XtÀâ÷ò»oÁÔ(€hƒáÏwÿï?ýG %€fI’q^D$.Tʳ?ÇD *°AôÁ,ÀÁÜÁ ü`6„B$ÄÂBB d€r`)¬‚B(†Í°*`/Ô@4ÀQh†“p.ÂU¸=púažÁ(¼ AÈa!ÚˆbŠX#Ž™…ø!ÁH‹$ ɈQ"K‘5H1RŠT UHò=r9‡\Fº‘;È2‚ü†¼G1”²Q=Ô µC¹¨7„F¢ Ðdt1š ›Ðr´=Œ6¡çЫhÚ>CÇ0Àè3Äl0.ÆÃB±8, “c˱"¬ «Æ°V¬»‰õcϱwEÀ 6wB aAHXLXNØH¨ $4Ú 7 „QÂ'"“¨K´&ºùÄb21‡XH,#Ö/{ˆCÄ7$‰C2'¹I±¤TÒÒFÒnR#é,©›4H#“ÉÚdk²9”, +È…ääÃä3ää!ò[ b@q¤øSâ(RÊjJåå4åe˜2AU£šRݨ¡T5ZB­¡¶R¯Q‡¨4uš9̓IK¥­¢•Óhh÷i¯ètºÝ•N—ÐWÒËéGè—èôw †ƒÇˆg(›gw¯˜L¦Ó‹ÇT071ë˜ç™™oUX*¶*|‘Ê •J•&•*/T©ª¦ªÞª UóUËT©^S}®FU3Sã© Ô–«UªPëSSg©;¨‡ªg¨oT?¤~Yý‰YÃLÃOC¤Q ±_ã¼Æ c³x,!k «†u5Ä&±ÍÙ|v*»˜ý»‹=ª©¡9C3J3W³Ró”f?ã˜qøœtN ç(§—ó~ŠÞï)â)¦4L¹1e\kª–—–X«H«Q«Gë½6®í§¦½E»YûAÇJ'\'GgÎçSÙSݧ §M=:õ®.ªk¥¡»Dw¿n§î˜ž¾^€žLo§Þy½çú}/ýTýmú§õG X³ $Û Î<Å5qo</ÇÛñQC]Ã@C¥a•a—á„‘¹Ñ<£ÕFFŒiÆ\ã$ãmÆmÆ£&&!&KMêMîšRM¹¦)¦;L;LÇÍÌÍ¢ÍÖ™5›=1×2ç›ç›×›ß·`ZxZ,¶¨¶¸eI²äZ¦Yî¶¼n…Z9Y¥XUZ]³F­­%Ö»­»§§¹N“N«žÖgðñ¶É¶©·°åØÛ®¶m¶}agbg·Å®Ã“}º}ý= ‡Ù«Z~s´r:V:ޚΜî?}Åô–é/gXÏÏØ3ã¶Ë)ÄiS›ÓGgg¹sƒóˆ‹‰K‚Ë.—>.›ÆÝȽäJtõq]ázÒõ›³›Âí¨Û¯î6îiî‡ÜŸÌ4Ÿ)žY3sÐÃÈCàQåÑ? Ÿ•0k߬~OCOgµç#/c/‘W­×°·¥wª÷aï>ö>rŸã>ã<7Þ2ÞY_Ì7À·È·ËOÃož_…ßC#ÿdÿzÿѧ€%g‰A[ûøz|!¿Ž?:Ûeö²ÙíAŒ ¹AA‚­‚åÁ­!hÈì­!÷ç˜Î‘Îi…P~èÖÐaæa‹Ã~ '…‡…W†?ŽpˆXÑ1—5wÑÜCsßDúD–DÞ›g1O9¯-J5*>ª.j<Ú7º4º?Æ.fYÌÕXXIlK9.*®6nl¾ßüíó‡ââ ã{˜/È]py¡ÎÂô…§©.,:–@LˆN8”ðA*¨Œ%òw%Ž yÂÂg"/Ñ6шØC\*NòH*Mz’쑼5y$Å3¥,幄'©¼L LÝ›:žšv m2=:½1ƒ’‘qBª!M“¶gêgæfvˬe…²þÅn‹·/•Ék³¬Y- ¶B¦èTZ(×*²geWf¿Í‰Ê9–«ž+Íí̳ÊÛ7œïŸÿíÂá’¶¥†KW-X潬j9²‰Š®Û—Ø(Üxå‡oÊ¿™Ü”´©«Ä¹dÏfÒféæÞ-ž[–ª—æ—n ÙÚ´ ßV´íõöEÛ/—Í(Û»ƒ¶C¹£¿<¸¼e§ÉÎÍ;?T¤TôTúT6îÒݵa×ønÑî{¼ö4ìÕÛ[¼÷ý>ɾÛUUMÕfÕeûIû³÷?®‰ªéø–ûm]­NmqíÇÒý#¶×¹ÔÕÒ=TRÖ+ëGǾþïw- 6 UœÆâ#pDyäé÷ ß÷ :ÚvŒ{¬áÓvg/jBšòšF›Sšû[b[ºOÌ>ÑÖêÞzüGÛœ499â?rýéü§CÏdÏ&žþ¢þË®/~øÕë×Îјѡ—ò—“¿m|¥ýêÀë¯ÛÆÂƾÉx31^ôVûíÁwÜwï£ßOä| (ÿhù±õSЧû“““ÿ˜óüc3-Û cHRMz%€ƒùÿ€éu0ê`:˜o’_ÅF"ŽIDATxÚì}w|\Źö33çœ-ÒJ²d[¶länŒ;Å0%¡wH¨ $ÔB€nz!`jÂ…äK€!@(BI0ÝØC „â†mIVo«ÕöSgæýþس¶l _’{óÝßû³¬=;gÎûÌYÂ+&¨€Z‘ ¨©€Z‘ ¨©€Zµ"P+Rµ"P+RµjE* V¤jE* V¤jEv)ÆÅ·oo’+bÐL("h &(" ˆõÜb­+™iRA™œò|obñ 7ÛMp= "\^c åÃs` ù‚2qýŒ•øâ˜7Ạ¤5€ö :<(@mÈlÒ+@#P Ñxñ|D÷¨|UT;)€@PéÞä$´ (¯ô¹*A ¯t-8®kù=Z‹s!˜†3ÀvAiB„›HM½ö˜?€ÉÄ.«¡$ª#‘êÏhƒ_"‰ñ”¢LÃÖÞ-ªØïÜæ_“|€úý þÛYzäw%4|HœRØ ·e¡Îð¹tÁ†Ê Ói4>òì÷$"‡8 8àøÎ„b`<œÃ±…æz ‚X5EP$B™¥ÿ^èÌÊ­î¾­ýâkƒ­¯®YóÈÙÅO_™þ„­ {: â QS±ˆ1Dº†»ŽH®#¢61±a7-ô·÷À^“~4øY~€(‰B0PB” üÌYŒ ³“ Žêoß©ÉË ÊßsÈ6¿ÑÕ?mhÀ·¼b¡“Éä*íöoÈç•bDíXûh>j_3™[;ªúøºújħœØzȽ¿Ïô¿wÛún §Ÿ¶õ0q‰Hß™Ù}àŽ^ýË nƒé(@<„¼uǽÿ€§¯j>±?Ñû28¼Ž¨…V‡öW[óáwŸ$n€‹Ì™ð™ ÅØßG¿»Ü¡ ‘œ¯ÐÙ¹²³Í³‚\ÿ 9ô̽Coã]-‹>€€ çÀˆÄäÏ5aÂçHïyîðÀ¨¦º±Ö4!j¾n{ó¿´Ûq› ¿ò-Lœ~ÚV5’ |ô/êÚyÞŽê–ˆŸwÜ£P ¯‚ \ÇÊ{Kð"“«™gå¡À f!àð«¯Ûmõà`>ç³ákúñ©;ÓmàÛMù öÛ-íüÀÞ֬Ͳ/~¯oթχ(°CPU*ð|ûc©|ûc›k§õŸpÙuʼnG S p¼QŒp¾á < ÛòÈg÷³Z ŠÊÄÜÚ$jè‚Ô"|þ½ "¿'ÌÔžÐÖ(̸ªî}è †YX¢1m{yŸ¨ä󈌗Bš¶ù`‚ Y‹a‘€‹à ”FJ@c^ÇžmïÂüÞÍ™¢¼çkƒo]ù™P]ræÝ‰(> 51‚’-M³³­Ñúàç~õf;=êÁó]jèWßÊ´Ý B&@A™Ø£:…»÷xÍÑ Üâ¿&¨eÿ î‚€Ì,«o@~Áe@Ãz0] @•âÄPiÛKÁ'à À)b*Mðç~•*™Ѥ«íäó{¶ä!òOߨÿÖ•k Hp÷8«‡˜ È], Æ™§oÑŒkwÓC»Š¥/¶Èþk/+t.ïà•#ÞÙ‰aÜ=ûyìfæài³üÑ¿¾˜D¾ôÇ[¡üØØ àAšJYD\¶St¡†Gð½øÞ`z;þC^Á—@ Á¢¾n(Ç—öux~뫽¯žó,€á2 »ŸÙC»XŸ¥•a1Db¬˜3Åœs:…úÙÓq…Îåwz0áôNÚ31„{æ¿€ÝbyxÊøäÆÂÙcá>3Tf$÷ ¨ h Ô‘ÆÔðµÏEfb9Éÿ\žê׿g<ë+ÃI‚›-j =ñùoÏ/´‘&M»ŠÃg!oãr¿ë¹}žÃu”i{â%@ïž÷"&D p=lç5ÉX)ôanÉÀþ“Ffå@,¤xÆÃß±°† 6<ææ3áíµ *Ü¥šJ¹îHO#µFœEpI|ˆÔ‡í!xézÁÿ&{n@¶ÅV.åÃ äæµ?y@€»çÙ[u)ÅQ`Ä@ô7B†A5û¾ö£u˜“Háîù! ÊØÑ‡òÒäÛÅ ÞþªLÒÉX¤+íxuÇ}ïÐ}gœð72˜T;F»ù%Œ¼&MñXë³u}Cîs{œ²² €¦U»Óß0â¶õÁ9À &Q‰I‹©V&%Á÷?¯¡¼ZK!oæžy7›:O«Èšm…Òe/Š|œŸ‹V#\Ï.}Â9ÀHè¢s¥ h_‘†‘÷”ÞØšN­8à¾ÇûN;ZïTŸ‹.;Á±ßñ!Tß{Z:EN ˜ 5Ó`Øy…SXÉ`™NMÿÜfòí¢²" ö‰yUÃøå¼—FºÃÎ4³ÏE{ÿ—uÏлà$¥ÒQbJ$75Ôbðs§ý5 ï®ý—¿ôë­ùbÐqòRýqÕOÛuÇGiàÛ”k;ËË¥³…|°IJíE 5¹¶Æ8¡6QÄØêL¯Ùh_Ç–nê iå$úȼ]:»åmóÌL‡i…I†ÅêâUÚ‹F¡¬º) Älñœ'™ï¹[,cè§Ù¡µ¿üÒsn̓“JÔ«éC…µ‹àÒªù PÉ´œ1Jÿ -½_ó†klÏë'_×U1kRbT-ö¬Š%·ž|üý×¾¿þÚ0VÙÁF®¨v]£Æµ5H*×±¡œ?Í9»s…=çÛñ€00A+ M´ t"‚#¹u)üzñjLŠåv”3øN0;Ò—º[¾³y¿Ö|öΆ‡úqKg:¬¾®îâ1Ç/Lf¯¬©µhLUìöWæïwêr·ïüIO¬êì8å ‰sÆ!çð˜j»'jýÎzç['-x¶/­døðìî‹kö:nQñÚÑM¹‰XÕyí®Žxë—ÏÛë¢î?±ƒ:Z9•vb@žsé»[º£W¤‡òoË|ûÓE̦Ø5ö¤Ú±8G>ãöåûÖþByy×O¾øjzÃ-m!ç3ÈH» ÍÍvpª'Çf`±5$@l4ºSл[ŽØ”¾ñ‡]¿}º7™Àn›5}ÿ#sùkêGOeW\YÓ´¯=Í=õîÖždÈŒ%³Š8Ð÷ékÒîïàPR ìJ‰4 ˜ÑÌhšbUÇØ1ÕˆD#´&Ã@õ¨z\³×FLN¤?hàs#}Ã/ùïlÞoE6yåü5½åÇ-í!õ¿4œi=iÓ¦Ÿ?ÍÒçô$™Ê¡ª 9ÊMüîËÓ&LœôøJCûH«^H/sˆ!ÛŸ ’-M¿P¼pɽÏ÷¥•|CÏ¿-÷â­ÏªSZóÝ:ŸÁ¼‰óÇ~軣°ØAmŒAƒ1€1X9~³¹ƒ]=ÜÛù@ûoë/ê~fÑ“Ý/øûžåÓ¾J¹W¿ÑßžÓ=í2â‘HLXyÔ“é ·l Ý@+Mк”R0•å¢è€– ߯cÝ©å´nëoä’W-zý¯÷>Ý›wcòâZž~‚ç¿TH¥ó:S€ÙïðsÔUªÊé"pÛÖSd(Uzï…¨kÎv­̰J¼_N¼˜¦êDêhj…é{NÀè¦ñè êO‚d¨J<¿ÞêÏýŽZ{šÖ¥þpâ[ë^ Èmد€ôW[¶.ßs¯Od ±-¼€êo˜úü‚ ¶n~çC”ëŽoÜûÝoé7 øR²©ßN-¤œX»þ‰â†MÆ•¹î"‘ëÀ€¬?vŽ{Ç‘{›cEÏ‚/”­oìg¦zó=Ïî{Wh›L8æPÏŠ£ïˆ=÷øŽpiÌ>«ùˆçŽP ‹5~üá¹$·Q/(7”d FÁóŠÐ=©›yGÿ¾=ÉõG¬yÿ©ÐÈôÿÙ€ì•ëZV'¹|¦¯7ƒ¶œ Õ¯.¼°qÌ\‘2EòÀgãePæypKlKŠÃÀh¤jb`B”†¸¹¼ “£º& Ó2À´ÂŠô žó]À÷éz‡¿Í“é™…Î$^Êg_  ÀþàÓKԆÖ‰?ï?[pï/$6Ìe«“s@¤P“åGÝ>¥ùX±ªïePEDuïuL¦Æ9C)¬Z¼Ž™à&žªÁHK Ý×@‚C–åžÊ»üu?ë¤UrÞNbŸ»öâÙ<•õç&sü’ᤆ²û7IwH…,’Ÿ}Π·ç¹½€"ì5×\eJ±GÀ÷8ODêªðšÛN£€ùP´=HÚY"RÃKçæƒÙ/Ê4Z|÷M©)çnç.ÿ’ŠÕÕ"}éÙÔ}ü¡ãYQïÑRðÐâyŵºðL뙬m Ê@G”Ô%@À`ÄÇÕ—?&ûšáŸ—m§™¨XS˜Œçû§!ê§A®{0=‘÷gÎש ²y×}»PèW¶ûú§ꢒPZ!Ú½çÐïÒy;®v}-”¦Ø‘³T0ÜÂÀ$î~~NÖ‘ô}á˜^çmãÉËy€ô!Daé€ï0ãþÀÕ€TКЕ'H0’Y~R¾Ã.h0rœJ½ùç÷jÃÐ`L`îyº{åE–å¾¢¤4AæäDóqSDr?Â$$´.}¶K»òàýé3·¹,8HrÝÆ5^îëçhÍ*ïî¯7 >¬Ë¾ß›s#"…›îCÿ—庾ûL6×6¢Î © |32³ìÜ Rö÷ß ‚–µöd ¦“ƒ•ÌŽd®N àÚï—A¾ìÇYœÃ·Wœ`@àíýæèbLý^1RJ4QWìµo,6€)dòÈ|œ|§dÞ–åµFàCÙ.”gÃ0 Â(iîÑzÊHö2sµÔ€,©gá@1 €•ʰ}í@dö o2CsX*^WV—ª#¡FÁ™˜’W )-ñ> (—4}¥oKÞæÏ’T ‹êÚË‹œ Z}B7-B4 À¨Œ ¢Û”³Š)Baú…i'Ó —"ûþ)¨žµ±7ûã–u¤ðÞ¼*¹ú¤GÂbA €ƒ¦”µ}g~Dôä\t’ó‡,éBÎQgWäŸzjÑÜÓD'ÿn%ãF†i‚ "‚ÚV’Ýå¹_‰¾7¾•‰š[.£À‘Ò-BÂàÔ~Çô#îÝ€µâÇ áÙ‹ˆÉÐ8ŠaT¢”nÌ<«OOû\‡ß|B›³ÛIö¤“;ð¨âåcåãô`÷°ß÷®NyW‚ià¿ê+QÁ«˜†2JÛ’"«Ž51HaÎÔZØ#t‹ÂŠÂê°jÐkûùwóC:›ë E˜?1øÒ…ÇVM`YÖˆ$šY€ˆF â )ï/Jµ›‹øÍOÿŒwËcNà1õì¤òs­Ù׎ùÉЫ‹ŽðZ¯8ÂÝòÃc{Ûº^ìèdc»»4ìlnp`ÕIËCª`Wß|¶éÒŽ,ªGþ¼„•u>ûÖÆîXp÷P1@ÎUãØ»»ú±Å m~øIFD»õ•Õ|‡ö¬6À‰ÃìW?ì+¸Ñ.fpŸ¾1d†Ùw×(:á'ÙM=¾qE¶KÑp—üÆegð›ÆÕóût·øPxÇ!Œbî'C›•Ñ3„?÷ @ÕŽªA}­…9gµÉðwÉBçãï§ÖݲZÑX—DfXƒ`Õ4Ìýú”reɿٺ¡pÀ @p0!ÃP0Â~h,¶Í —¡@¯tÓ7$E°®;ïcØöÀ|TÊ©þõê¥}õ¤É»%šŠƒ—ûÍ%Ý´ç‚ÆwfÌšYŽôêËË&è}団fûõÊÉÁË¥agxž1&ˆ/yt¯ó6]3û¨[Ç>{ÝhQnŒ”{Ø– pÎP_Ç1y7D5`šä«@B¹hiz{7ÿU±äi=mßè0œ;Û]=n€1ýüßþ2cÞu—On;õ¥?ŠòÄ9¡žZ½_¶çÏü€åßxP^Ö"ew¸+œw¿^K¤5¨¥7ÙnvÄÅC[Uf`£B,ï¾éçÑÇ_ÿ? KØ¡[úgd°H&_N­Ïº¥žý¾•u1ŽéÞ¾l¥ÆŸŽ3#X´p,NþQV÷ýŒwè7²y#b¬WªTö3"‘hlÊe·Î8þ™oÜg£“G"îÎ7$¸àà&M@V=> 5Ø¢èñu_µ{ý &Úy¿®ïú‹ÈžÖ­Ýõ›ó.:2ES…xs‡wã-uÓÞØ¸ôðeùaû ;ï$½ ‚nÿ¾Â wí»n^‡é#6ëÌuÛŒýÁC{r±ÑûÜF@»_­©j<ÄaUU!–0QS£:jc¹û™L=þÚû¬ úö Fð%Á2J¥Æ¡awj{‹º=?T<AZ0ئŸ6~ùfs÷{È»!¸Ǽµ‘¨úa]ã©ýÈeˆÎmä& +¥k­yÔஞ䧽¥É|^¾Ã2wþÈú]ž´?GMM°ä|†±»Êvûj–ˆ~ï0±`éXº´©GÖE]Í8žˆ´2Ëêå?ÖÏzS}òÝU›p÷Ù`͈âyá¶eתWãßA„{Œ@ ƒ" „tްå­'ǺâÐÕ2ÓDÄB¬Ú€iißTÝË6><ç'¼ºŸ¯3õÏAu#:žE—3.•ò†Zaò†hïk/ú=ü¬çÐÇT'&|†Õ\]ë[§'˜0ë„.À¥áÈ€†-û»‚ÔuË‹…¡ró€ˆô Ž6Z;㙼ö˜¯*}¬¦ê±ÌŒÁˆÆ©²¯â:“k-C·p¦{¹@AˆkMãí"›>œ æ9ÙŒ†ÜÂtªA×Z™[µÊí{v€TÝa¯ù?Ê߀¹©wqè–n VÍyÝ9±ºC'0kñDnîY§1‚#oCmè‚÷Înfõ_}¯F¹0˜QwÔÔÐHP‰Í—æX…W}~›¾ÿ,lÍ&…Q¦¥íœî·:±î[O¡5¤)'ìæ¸w.û¾úcüjXä€ @p!_” ³vh&‡²i5>;ìTùÅâ¨Q`&̪âµ0F0¨ó'-ͽ €#~>_«ôPoe±ÙOÀ¡‚BŽR”©ðþå÷> UçVÕ-™óðjm̳”‘ Ò\§s<øë;°_{Ò.¶†”Ÿ+W•ˆˆ>ªgkl}j «þU±q‡OŒŽ;ú`bÍhÔÌ  «!fÆœ òA: ’)"¯—Qz#ó»Öû©›í®GGDÆv8q§á¨Uê™›0wè]¤ŽSº;Y˜SFÃä>þaŤ9»åh÷®†šßÀΠúùÓ¿ë°ÐÏDÂq­cÒNczä/®¹ž^«úV Ph0ƒÃWúŒÁ´¸´½]-ɧíAí¯$¯÷øý½A±=MÒ‰‹šƒÏ 0ñ3F¼Ñê(¤$Dhý7ºž;ð¶ÐØe ¢»8@Oá<|òåiS)íølw—(/ÌÐ&Öˆž© ¯wB»zá󀈰KP•”è\¾¨lk„±£":6.ªfÖµÀW${в°Õ&åáª/ƒP¾qù4Y©áÈUôƒìvP]¥ ¥Æ9É>NÜØvb`{¬(¨»kGkn”h}¨²¨ <…97«r9Œ#æªÛ®½Pu5Vú>"ÚƒÌ|ÀnïçmÝä@åÖÿ,ûÞ—ïõÒk #ž«\¢3Æìÿô¿û4órM€ œì!¿íò¥¹­O´…×¼Ü5t*Üüƒ´ÀöÎì-ÏÛØiî4ÂÆzd²ô±/H5ó&‘ÖJ)rz_\â…œm(wP(wPŒŒ¶F^ï¬@£\AÂbàféÊzÂ{Æ‘àLZ-Mâ0 €ûJ|Òpµ‚'ÿ¾±þ]#•`ÚŒB “ÀT©-¨<‚p§ïàåêïà T:,qpkº(ÎkÿÀά¼iðÕ i®|~y¤AYrõq?l<ð‰NÏŸw#L˜ÂèȸcÅÖ'úã?Ÿ¬ú&²€Žl_V&Õírî÷4Œ#))é˜Ó¦‚ŸÁ°ŽìîýÛ_)»õº|h€Æ}+>Åv<ØŒ;ìM˜Ð€–PAxÌ„•¦õ?Qb XÎ.Âïh¬œ  Œž'÷ʸы3à ^n°mxõé‡`°›OlÕÐtàC+B$ΰõ©jàÕ“ïwè»;AÓEоi Y®€ÞOëšØ-H¿àE€¬ÞéÔäKc–þ™ü"7A±Z€4Ç¿ÚëlqNxÅ9?ÏÝ <<·ÅÏãh_j³ éΰ8Ÿ`Ï8­]G#¤€R˜vZ»n}d²Ãyæ^Æ&\ ¥ä‚²nhc.bI$ôÞ0Ó7 Y%rä±¢ÿ~PÿÕÅâmîühë/’‹y;´¢l"ЊÀŒºÑ!Õs¿Ø­G7DQç ªc¥É™¬Æô3zµt²9'Íí2³r]™¢…Fµ‚ÂùH'~ þÏyï  M·ö]‡¬ºø0bQg›Æc,–ëð‹.´"h1z¦ƒîÞ€X{ïDfš|ÛQ­J‡ÈG Lh3†!žH,’žÏë}9ßþH[èu9ÜP,‡„}" 9 `~Ôÿ ‰r׳ŸÂŸ3"†Ahå‚´³MµrÀôÖ'!såAÄ«;nÙîÇ<°€õâ õlç|„HĸVÓ†ÙLeºƒþŸß—.ÍXéí@Æ% Ô îﰠꉿaÀºÂ¸>)J;¨”Ú—¶%jv~S»9Rvn‘5¹Ñ£žXtîæk÷;ÿͽÚß¼#öìµ L€ kLgé‚¶ö¶“rXîž‹ Ë»Êï"M9ùu*¿¾SVA5À?)ù_éSË{ìcº÷åCï½ï9éOþvà×ÎNÛUñb6qE¬zÔU£/ì›söùCmí <$ì¢_ÌÛ6‚Î_{mß¿ßM®Î‡U¾\ØÅ-þgE¿Ÿ¿úq2óÌ´ù¡=ü¡¿|þqûÇš_;˜ä”¥[?ÝËTÕ§aEgœAå8 ï#غÚí¹ÿMwpU6,4äÃòœròëôÿÿù*òÿ²ÏÈòet§r]¹ØR.ó¹aé.øèúOæ¢ ¨¸;—/ÙNÀÊrÙñ¿Õ©Uþ©OžTþ» ¨©€Z‘ ¨©€Z‘ ¨P+Rµ"P+Rµ"P+ VLðÉ“ÿ;=}’êRÖ¸CIEND®B`‚gdata-2.0.18/samples/apps/marketplace_sample/css/0000750036466300116100000000000012156625015021762 5ustar afshareng00000000000000gdata-2.0.18/samples/apps/marketplace_sample/css/style.css0000640036466300116100000000060312156622362023636 0ustar afshareng00000000000000#kd-googlebar { position: relative; background: #F1F1F1; height: 70px; line-height: 70px; padding-left: 44px; border-bottom: 1px solid #E5E5E5; z-index: 40; vertical-align: top; } #users { text-align: center; top:200px; right:10px; } #input_details { margin-left: auto; margin-right: auto; border-width: 4px; border-style: outset; border-color: gray; } gdata-2.0.18/samples/apps/marketplace_sample/atom/0000750036466300116100000000000012156625015022132 5ustar afshareng00000000000000gdata-2.0.18/samples/apps/marketplace_sample/atom/mock_http_core.py0000640036466300116100000002735012156622362025516 0ustar afshareng00000000000000#!/usr/bin/env python # # Copyright (C) 2009 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. # This module is used for version 2 of the Google Data APIs. __author__ = 'j.s@google.com (Jeff Scudder)' import StringIO import pickle import os.path import tempfile import atom.http_core class Error(Exception): pass class NoRecordingFound(Error): pass class MockHttpClient(object): debug = None real_client = None last_request_was_live = False # The following members are used to construct the session cache temp file # name. # These are combined to form the file name # /tmp/cache_prefix.cache_case_name.cache_test_name cache_name_prefix = 'gdata_live_test' cache_case_name = '' cache_test_name = '' def __init__(self, recordings=None, real_client=None): self._recordings = recordings or [] if real_client is not None: self.real_client = real_client def add_response(self, http_request, status, reason, headers=None, body=None): response = MockHttpResponse(status, reason, headers, body) # TODO Scrub the request and the response. self._recordings.append((http_request._copy(), response)) AddResponse = add_response def request(self, http_request): """Provide a recorded response, or record a response for replay. If the real_client is set, the request will be made using the real_client, and the response from the server will be recorded. If the real_client is None (the default), this method will examine the recordings and find the first which matches. """ request = http_request._copy() _scrub_request(request) if self.real_client is None: self.last_request_was_live = False for recording in self._recordings: if _match_request(recording[0], request): return recording[1] else: # Pass along the debug settings to the real client. self.real_client.debug = self.debug # Make an actual request since we can use the real HTTP client. self.last_request_was_live = True response = self.real_client.request(http_request) scrubbed_response = _scrub_response(response) self.add_response(request, scrubbed_response.status, scrubbed_response.reason, dict(atom.http_core.get_headers(scrubbed_response)), scrubbed_response.read()) # Return the recording which we just added. return self._recordings[-1][1] raise NoRecordingFound('No recoding was found for request: %s %s' % ( request.method, str(request.uri))) Request = request def _save_recordings(self, filename): recording_file = open(os.path.join(tempfile.gettempdir(), filename), 'wb') pickle.dump(self._recordings, recording_file) recording_file.close() def _load_recordings(self, filename): recording_file = open(os.path.join(tempfile.gettempdir(), filename), 'rb') self._recordings = pickle.load(recording_file) recording_file.close() def _delete_recordings(self, filename): full_path = os.path.join(tempfile.gettempdir(), filename) if os.path.exists(full_path): os.remove(full_path) def _load_or_use_client(self, filename, http_client): if os.path.exists(os.path.join(tempfile.gettempdir(), filename)): self._load_recordings(filename) else: self.real_client = http_client def use_cached_session(self, name=None, real_http_client=None): """Attempts to load recordings from a previous live request. If a temp file with the recordings exists, then it is used to fulfill requests. If the file does not exist, then a real client is used to actually make the desired HTTP requests. Requests and responses are recorded and will be written to the desired temprary cache file when close_session is called. Args: name: str (optional) The file name of session file to be used. The file is loaded from the temporary directory of this machine. If no name is passed in, a default name will be constructed using the cache_name_prefix, cache_case_name, and cache_test_name of this object. real_http_client: atom.http_core.HttpClient the real client to be used if the cached recordings are not found. If the default value is used, this will be an atom.http_core.HttpClient. """ if real_http_client is None: real_http_client = atom.http_core.HttpClient() if name is None: self._recordings_cache_name = self.get_cache_file_name() else: self._recordings_cache_name = name self._load_or_use_client(self._recordings_cache_name, real_http_client) def close_session(self): """Saves recordings in the temporary file named in use_cached_session.""" if self.real_client is not None: self._save_recordings(self._recordings_cache_name) def delete_session(self, name=None): """Removes recordings from a previous live request.""" if name is None: self._delete_recordings(self._recordings_cache_name) else: self._delete_recordings(name) def get_cache_file_name(self): return '%s.%s.%s' % (self.cache_name_prefix, self.cache_case_name, self.cache_test_name) def _dump(self): """Provides debug information in a string.""" output = 'MockHttpClient\n real_client: %s\n cache file name: %s\n' % ( self.real_client, self.get_cache_file_name()) output += ' recordings:\n' i = 0 for recording in self._recordings: output += ' recording %i is for: %s %s\n' % ( i, recording[0].method, str(recording[0].uri)) i += 1 return output def _match_request(http_request, stored_request): """Determines whether a request is similar enough to a stored request to cause the stored response to be returned.""" # Check to see if the host names match. if (http_request.uri.host is not None and http_request.uri.host != stored_request.uri.host): return False # Check the request path in the URL (/feeds/private/full/x) elif http_request.uri.path != stored_request.uri.path: return False # Check the method used in the request (GET, POST, etc.) elif http_request.method != stored_request.method: return False # If there is a gsession ID in either request, make sure that it is matched # exactly. elif ('gsessionid' in http_request.uri.query or 'gsessionid' in stored_request.uri.query): if 'gsessionid' not in stored_request.uri.query: return False elif 'gsessionid' not in http_request.uri.query: return False elif (http_request.uri.query['gsessionid'] != stored_request.uri.query['gsessionid']): return False # Ignores differences in the query params (?start-index=5&max-results=20), # the body of the request, the port number, HTTP headers, just to name a # few. return True def _scrub_request(http_request): """ Removes email address and password from a client login request. Since the mock server saves the request and response in plantext, sensitive information like the password should be removed before saving the recordings. At the moment only requests sent to a ClientLogin url are scrubbed. """ if (http_request and http_request.uri and http_request.uri.path and http_request.uri.path.endswith('ClientLogin')): # Remove the email and password from a ClientLogin request. http_request._body_parts = [] http_request.add_form_inputs( {'form_data': 'client login request has been scrubbed'}) else: # We can remove the body of the post from the recorded request, since # the request body is not used when finding a matching recording. http_request._body_parts = [] return http_request def _scrub_response(http_response): return http_response class EchoHttpClient(object): """Sends the request data back in the response. Used to check the formatting of the request as it was sent. Always responds with a 200 OK, and some information from the HTTP request is returned in special Echo-X headers in the response. The following headers are added in the response: 'Echo-Host': The host name and port number to which the HTTP connection is made. If no port was passed in, the header will contain host:None. 'Echo-Uri': The path portion of the URL being requested. /example?x=1&y=2 'Echo-Scheme': The beginning of the URL, usually 'http' or 'https' 'Echo-Method': The HTTP method being used, 'GET', 'POST', 'PUT', etc. """ def request(self, http_request): return self._http_request(http_request.uri, http_request.method, http_request.headers, http_request._body_parts) def _http_request(self, uri, method, headers=None, body_parts=None): body = StringIO.StringIO() response = atom.http_core.HttpResponse(status=200, reason='OK', body=body) if headers is None: response._headers = {} else: # Copy headers from the request to the response but convert values to # strings. Server response headers always come in as strings, so an int # should be converted to a corresponding string when echoing. for header, value in headers.iteritems(): response._headers[header] = str(value) response._headers['Echo-Host'] = '%s:%s' % (uri.host, str(uri.port)) response._headers['Echo-Uri'] = uri._get_relative_path() response._headers['Echo-Scheme'] = uri.scheme response._headers['Echo-Method'] = method for part in body_parts: if isinstance(part, str): body.write(part) elif hasattr(part, 'read'): body.write(part.read()) body.seek(0) return response class SettableHttpClient(object): """An HTTP Client which responds with the data given in set_response.""" def __init__(self, status, reason, body, headers): """Configures the response for the server. See set_response for details on the arguments to the constructor. """ self.set_response(status, reason, body, headers) self.last_request = None def set_response(self, status, reason, body, headers): """Determines the response which will be sent for each request. Args: status: An int for the HTTP status code, example: 200, 404, etc. reason: String for the HTTP reason, example: OK, NOT FOUND, etc. body: The body of the HTTP response as a string or a file-like object (something with a read method). headers: dict of strings containing the HTTP headers in the response. """ self.response = atom.http_core.HttpResponse(status=status, reason=reason, body=body) self.response._headers = headers.copy() def request(self, http_request): self.last_request = http_request return self.response class MockHttpResponse(atom.http_core.HttpResponse): def __init__(self, status=None, reason=None, headers=None, body=None): self._headers = headers or {} if status is not None: self.status = status if reason is not None: self.reason = reason if body is not None: # Instead of using a file-like object for the body, store as a string # so that reads can be repeated. if hasattr(body, 'read'): self._body = body.read() else: self._body = body def read(self): return self._body gdata-2.0.18/samples/apps/marketplace_sample/atom/mock_http.py0000640036466300116100000001057212156622362024504 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright (C) 2008 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. __author__ = 'api.jscudder (Jeff Scudder)' import atom.http_interface import atom.url class Error(Exception): pass class NoRecordingFound(Error): pass class MockRequest(object): """Holds parameters of an HTTP request for matching against future requests. """ def __init__(self, operation, url, data=None, headers=None): self.operation = operation if isinstance(url, (str, unicode)): url = atom.url.parse_url(url) self.url = url self.data = data self.headers = headers class MockResponse(atom.http_interface.HttpResponse): """Simulates an httplib.HTTPResponse object.""" def __init__(self, body=None, status=None, reason=None, headers=None): if body and hasattr(body, 'read'): self.body = body.read() else: self.body = body if status is not None: self.status = int(status) else: self.status = None self.reason = reason self._headers = headers or {} def read(self): return self.body class MockHttpClient(atom.http_interface.GenericHttpClient): def __init__(self, headers=None, recordings=None, real_client=None): """An HttpClient which responds to request with stored data. The request-response pairs are stored as tuples in a member list named recordings. The MockHttpClient can be switched from replay mode to record mode by setting the real_client member to an instance of an HttpClient which will make real HTTP requests and store the server's response in list of recordings. Args: headers: dict containing HTTP headers which should be included in all HTTP requests. recordings: The initial recordings to be used for responses. This list contains tuples in the form: (MockRequest, MockResponse) real_client: An HttpClient which will make a real HTTP request. The response will be converted into a MockResponse and stored in recordings. """ self.recordings = recordings or [] self.real_client = real_client self.headers = headers or {} def add_response(self, response, operation, url, data=None, headers=None): """Adds a request-response pair to the recordings list. After the recording is added, future matching requests will receive the response. Args: response: MockResponse operation: str url: str data: str, Currently the data is ignored when looking for matching requests. headers: dict of strings: Currently the headers are ignored when looking for matching requests. """ request = MockRequest(operation, url, data=data, headers=headers) self.recordings.append((request, response)) def request(self, operation, url, data=None, headers=None): """Returns a matching MockResponse from the recordings. If the real_client is set, the request will be passed along and the server's response will be added to the recordings and also returned. If there is no match, a NoRecordingFound error will be raised. """ if self.real_client is None: if isinstance(url, (str, unicode)): url = atom.url.parse_url(url) for recording in self.recordings: if recording[0].operation == operation and recording[0].url == url: return recording[1] raise NoRecordingFound('No recodings found for %s %s' % ( operation, url)) else: # There is a real HTTP client, so make the request, and record the # response. response = self.real_client.request(operation, url, data=data, headers=headers) # TODO: copy the headers stored_response = MockResponse(body=response, status=response.status, reason=response.reason) self.add_response(stored_response, operation, url, data=data, headers=headers) return stored_response gdata-2.0.18/samples/apps/marketplace_sample/atom/core.py0000640036466300116100000005042712156622362023447 0ustar afshareng00000000000000#!/usr/bin/env python # # Copyright (C) 2008 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. # This module is used for version 2 of the Google Data APIs. __author__ = 'j.s@google.com (Jeff Scudder)' import inspect try: from xml.etree import cElementTree as ElementTree except ImportError: try: import cElementTree as ElementTree except ImportError: try: from xml.etree import ElementTree except ImportError: from elementtree import ElementTree try: from xml.dom.minidom import parseString as xmlString except ImportError: xmlString = None STRING_ENCODING = 'utf-8' class XmlElement(object): """Represents an element node in an XML document. The text member is a UTF-8 encoded str or unicode. """ _qname = None _other_elements = None _other_attributes = None # The rule set contains mappings for XML qnames to child members and the # appropriate member classes. _rule_set = None _members = None text = None def __init__(self, text=None, *args, **kwargs): if ('_members' not in self.__class__.__dict__ or self.__class__._members is None): self.__class__._members = tuple(self.__class__._list_xml_members()) for member_name, member_type in self.__class__._members: if member_name in kwargs: setattr(self, member_name, kwargs[member_name]) else: if isinstance(member_type, list): setattr(self, member_name, []) else: setattr(self, member_name, None) self._other_elements = [] self._other_attributes = {} if text is not None: self.text = text def _list_xml_members(cls): """Generator listing all members which are XML elements or attributes. The following members would be considered XML members: foo = 'abc' - indicates an XML attribute with the qname abc foo = SomeElement - indicates an XML child element foo = [AnElement] - indicates a repeating XML child element, each instance will be stored in a list in this member foo = ('att1', '{http://example.com/namespace}att2') - indicates an XML attribute which has different parsing rules in different versions of the protocol. Version 1 of the XML parsing rules will look for an attribute with the qname 'att1' but verion 2 of the parsing rules will look for a namespaced attribute with the local name of 'att2' and an XML namespace of 'http://example.com/namespace'. """ members = [] for pair in inspect.getmembers(cls): if not pair[0].startswith('_') and pair[0] != 'text': member_type = pair[1] if (isinstance(member_type, tuple) or isinstance(member_type, list) or isinstance(member_type, (str, unicode)) or (inspect.isclass(member_type) and issubclass(member_type, XmlElement))): members.append(pair) return members _list_xml_members = classmethod(_list_xml_members) def _get_rules(cls, version): """Initializes the _rule_set for the class which is used when parsing XML. This method is used internally for parsing and generating XML for an XmlElement. It is not recommended that you call this method directly. Returns: A tuple containing the XML parsing rules for the appropriate version. The tuple looks like: (qname, {sub_element_qname: (member_name, member_class, repeating), ..}, {attribute_qname: member_name}) To give a couple of concrete example, the atom.data.Control _get_rules with version of 2 will return: ('{http://www.w3.org/2007/app}control', {'{http://www.w3.org/2007/app}draft': ('draft', , False)}, {}) Calling _get_rules with version 1 on gdata.data.FeedLink will produce: ('{http://schemas.google.com/g/2005}feedLink', {'{http://www.w3.org/2005/Atom}feed': ('feed', , False)}, {'href': 'href', 'readOnly': 'read_only', 'countHint': 'count_hint', 'rel': 'rel'}) """ # Initialize the _rule_set to make sure there is a slot available to store # the parsing rules for this version of the XML schema. # Look for rule set in the class __dict__ proxy so that only the # _rule_set for this class will be found. By using the dict proxy # we avoid finding rule_sets defined in superclasses. # The four lines below provide support for any number of versions, but it # runs a bit slower then hard coding slots for two versions, so I'm using # the below two lines. #if '_rule_set' not in cls.__dict__ or cls._rule_set is None: # cls._rule_set = [] #while len(cls.__dict__['_rule_set']) < version: # cls._rule_set.append(None) # If there is no rule set cache in the class, provide slots for two XML # versions. If and when there is a version 3, this list will need to be # expanded. if '_rule_set' not in cls.__dict__ or cls._rule_set is None: cls._rule_set = [None, None] # If a version higher than 2 is requested, fall back to version 2 because # 2 is currently the highest supported version. if version > 2: return cls._get_rules(2) # Check the dict proxy for the rule set to avoid finding any rule sets # which belong to the superclass. We only want rule sets for this class. if cls._rule_set[version-1] is None: # The rule set for each version consists of the qname for this element # ('{namespace}tag'), a dictionary (elements) for looking up the # corresponding class member when given a child element's qname, and a # dictionary (attributes) for looking up the corresponding class member # when given an XML attribute's qname. elements = {} attributes = {} if ('_members' not in cls.__dict__ or cls._members is None): cls._members = tuple(cls._list_xml_members()) for member_name, target in cls._members: if isinstance(target, list): # This member points to a repeating element. elements[_get_qname(target[0], version)] = (member_name, target[0], True) elif isinstance(target, tuple): # This member points to a versioned XML attribute. if version <= len(target): attributes[target[version-1]] = member_name else: attributes[target[-1]] = member_name elif isinstance(target, (str, unicode)): # This member points to an XML attribute. attributes[target] = member_name elif issubclass(target, XmlElement): # This member points to a single occurance element. elements[_get_qname(target, version)] = (member_name, target, False) version_rules = (_get_qname(cls, version), elements, attributes) cls._rule_set[version-1] = version_rules return version_rules else: return cls._rule_set[version-1] _get_rules = classmethod(_get_rules) def get_elements(self, tag=None, namespace=None, version=1): """Find all sub elements which match the tag and namespace. To find all elements in this object, call get_elements with the tag and namespace both set to None (the default). This method searches through the object's members and the elements stored in _other_elements which did not match any of the XML parsing rules for this class. Args: tag: str namespace: str version: int Specifies the version of the XML rules to be used when searching for matching elements. Returns: A list of the matching XmlElements. """ matches = [] ignored1, elements, ignored2 = self.__class__._get_rules(version) if elements: for qname, element_def in elements.iteritems(): member = getattr(self, element_def[0]) if member: if _qname_matches(tag, namespace, qname): if element_def[2]: # If this is a repeating element, copy all instances into the # result list. matches.extend(member) else: matches.append(member) for element in self._other_elements: if _qname_matches(tag, namespace, element._qname): matches.append(element) return matches GetElements = get_elements # FindExtensions and FindChildren are provided for backwards compatibility # to the atom.AtomBase class. # However, FindExtensions may return more results than the v1 atom.AtomBase # method does, because get_elements searches both the expected children # and the unexpected "other elements". The old AtomBase.FindExtensions # method searched only "other elements" AKA extension_elements. FindExtensions = get_elements FindChildren = get_elements def get_attributes(self, tag=None, namespace=None, version=1): """Find all attributes which match the tag and namespace. To find all attributes in this object, call get_attributes with the tag and namespace both set to None (the default). This method searches through the object's members and the attributes stored in _other_attributes which did not fit any of the XML parsing rules for this class. Args: tag: str namespace: str version: int Specifies the version of the XML rules to be used when searching for matching attributes. Returns: A list of XmlAttribute objects for the matching attributes. """ matches = [] ignored1, ignored2, attributes = self.__class__._get_rules(version) if attributes: for qname, attribute_def in attributes.iteritems(): if isinstance(attribute_def, (list, tuple)): attribute_def = attribute_def[0] member = getattr(self, attribute_def) # TODO: ensure this hasn't broken existing behavior. #member = getattr(self, attribute_def[0]) if member: if _qname_matches(tag, namespace, qname): matches.append(XmlAttribute(qname, member)) for qname, value in self._other_attributes.iteritems(): if _qname_matches(tag, namespace, qname): matches.append(XmlAttribute(qname, value)) return matches GetAttributes = get_attributes def _harvest_tree(self, tree, version=1): """Populates object members from the data in the tree Element.""" qname, elements, attributes = self.__class__._get_rules(version) for element in tree: if elements and element.tag in elements: definition = elements[element.tag] # If this is a repeating element, make sure the member is set to a # list. if definition[2]: if getattr(self, definition[0]) is None: setattr(self, definition[0], []) getattr(self, definition[0]).append(_xml_element_from_tree(element, definition[1], version)) else: setattr(self, definition[0], _xml_element_from_tree(element, definition[1], version)) else: self._other_elements.append(_xml_element_from_tree(element, XmlElement, version)) for attrib, value in tree.attrib.iteritems(): if attributes and attrib in attributes: setattr(self, attributes[attrib], value) else: self._other_attributes[attrib] = value if tree.text: self.text = tree.text def _to_tree(self, version=1, encoding=None): new_tree = ElementTree.Element(_get_qname(self, version)) self._attach_members(new_tree, version, encoding) return new_tree def _attach_members(self, tree, version=1, encoding=None): """Convert members to XML elements/attributes and add them to the tree. Args: tree: An ElementTree.Element which will be modified. The members of this object will be added as child elements or attributes according to the rules described in _expected_elements and _expected_attributes. The elements and attributes stored in other_attributes and other_elements are also added a children of this tree. version: int Ingnored in this method but used by VersionedElement. encoding: str (optional) """ qname, elements, attributes = self.__class__._get_rules(version) encoding = encoding or STRING_ENCODING # Add the expected elements and attributes to the tree. if elements: for tag, element_def in elements.iteritems(): member = getattr(self, element_def[0]) # If this is a repeating element and there are members in the list. if member and element_def[2]: for instance in member: instance._become_child(tree, version) elif member: member._become_child(tree, version) if attributes: for attribute_tag, member_name in attributes.iteritems(): value = getattr(self, member_name) if value: tree.attrib[attribute_tag] = value # Add the unexpected (other) elements and attributes to the tree. for element in self._other_elements: element._become_child(tree, version) for key, value in self._other_attributes.iteritems(): # I'm not sure if unicode can be used in the attribute name, so for now # we assume the encoding is correct for the attribute name. if not isinstance(value, unicode): value = value.decode(encoding) tree.attrib[key] = value if self.text: if isinstance(self.text, unicode): tree.text = self.text else: tree.text = self.text.decode(encoding) def to_string(self, version=1, encoding=None, pretty_print=None): """Converts this object to XML.""" tree_string = ElementTree.tostring(self._to_tree(version, encoding)) if pretty_print and xmlString is not None: return xmlString(tree_string).toprettyxml() return tree_string ToString = to_string def __str__(self): return self.to_string() def _become_child(self, tree, version=1): """Adds a child element to tree with the XML data in self.""" new_child = ElementTree.Element('') tree.append(new_child) new_child.tag = _get_qname(self, version) self._attach_members(new_child, version) def __get_extension_elements(self): return self._other_elements def __set_extension_elements(self, elements): self._other_elements = elements extension_elements = property(__get_extension_elements, __set_extension_elements, """Provides backwards compatibility for v1 atom.AtomBase classes.""") def __get_extension_attributes(self): return self._other_attributes def __set_extension_attributes(self, attributes): self._other_attributes = attributes extension_attributes = property(__get_extension_attributes, __set_extension_attributes, """Provides backwards compatibility for v1 atom.AtomBase classes.""") def _get_tag(self, version=1): qname = _get_qname(self, version) if qname: return qname[qname.find('}')+1:] return None def _get_namespace(self, version=1): qname = _get_qname(self, version) if qname.startswith('{'): return qname[1:qname.find('}')] else: return None def _set_tag(self, tag): if isinstance(self._qname, tuple): self._qname = self._qname.copy() if self._qname[0].startswith('{'): self._qname[0] = '{%s}%s' % (self._get_namespace(1), tag) else: self._qname[0] = tag else: if self._qname is not None and self._qname.startswith('{'): self._qname = '{%s}%s' % (self._get_namespace(), tag) else: self._qname = tag def _set_namespace(self, namespace): tag = self._get_tag(1) if tag is None: tag = '' if isinstance(self._qname, tuple): self._qname = self._qname.copy() if namespace: self._qname[0] = '{%s}%s' % (namespace, tag) else: self._qname[0] = tag else: if namespace: self._qname = '{%s}%s' % (namespace, tag) else: self._qname = tag tag = property(_get_tag, _set_tag, """Provides backwards compatibility for v1 atom.AtomBase classes.""") namespace = property(_get_namespace, _set_namespace, """Provides backwards compatibility for v1 atom.AtomBase classes.""") # Provided for backwards compatibility to atom.ExtensionElement children = extension_elements attributes = extension_attributes def _get_qname(element, version): if isinstance(element._qname, tuple): if version <= len(element._qname): return element._qname[version-1] else: return element._qname[-1] else: return element._qname def _qname_matches(tag, namespace, qname): """Logic determines if a QName matches the desired local tag and namespace. This is used in XmlElement.get_elements and XmlElement.get_attributes to find matches in the element's members (among all expected-and-unexpected elements-and-attributes). Args: expected_tag: string expected_namespace: string qname: string in the form '{xml_namespace}localtag' or 'tag' if there is no namespace. Returns: boolean True if the member's tag and namespace fit the expected tag and namespace. """ # If there is no expected namespace or tag, then everything will match. if qname is None: member_tag = None member_namespace = None else: if qname.startswith('{'): member_namespace = qname[1:qname.index('}')] member_tag = qname[qname.index('}') + 1:] else: member_namespace = None member_tag = qname return ((tag is None and namespace is None) # If there is a tag, but no namespace, see if the local tag matches. or (namespace is None and member_tag == tag) # There was no tag, but there was a namespace so see if the namespaces # match. or (tag is None and member_namespace == namespace) # There was no tag, and the desired elements have no namespace, so check # to see that the member's namespace is None. or (tag is None and namespace == '' and member_namespace is None) # The tag and the namespace both match. or (tag == member_tag and namespace == member_namespace) # The tag matches, and the expected namespace is the empty namespace, # check to make sure the member's namespace is None. or (tag == member_tag and namespace == '' and member_namespace is None)) def parse(xml_string, target_class=None, version=1, encoding=None): """Parses the XML string according to the rules for the target_class. Args: xml_string: str or unicode target_class: XmlElement or a subclass. If None is specified, the XmlElement class is used. version: int (optional) The version of the schema which should be used when converting the XML into an object. The default is 1. encoding: str (optional) The character encoding of the bytes in the xml_string. Default is 'UTF-8'. """ if target_class is None: target_class = XmlElement if isinstance(xml_string, unicode): if encoding is None: xml_string = xml_string.encode(STRING_ENCODING) else: xml_string = xml_string.encode(encoding) tree = ElementTree.fromstring(xml_string) return _xml_element_from_tree(tree, target_class, version) Parse = parse xml_element_from_string = parse XmlElementFromString = xml_element_from_string def _xml_element_from_tree(tree, target_class, version=1): if target_class._qname is None: instance = target_class() instance._qname = tree.tag instance._harvest_tree(tree, version) return instance # TODO handle the namespace-only case # Namespace only will be used with Google Spreadsheets rows and # Google Base item attributes. elif tree.tag == _get_qname(target_class, version): instance = target_class() instance._harvest_tree(tree, version) return instance return None class XmlAttribute(object): def __init__(self, qname, value): self._qname = qname self.value = value gdata-2.0.18/samples/apps/marketplace_sample/atom/data.py0000640036466300116100000001770612156622362023433 0ustar afshareng00000000000000#!/usr/bin/env python # # Copyright (C) 2009 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. # This module is used for version 2 of the Google Data APIs. __author__ = 'j.s@google.com (Jeff Scudder)' import atom.core XML_TEMPLATE = '{http://www.w3.org/XML/1998/namespace}%s' ATOM_TEMPLATE = '{http://www.w3.org/2005/Atom}%s' APP_TEMPLATE_V1 = '{http://purl.org/atom/app#}%s' APP_TEMPLATE_V2 = '{http://www.w3.org/2007/app}%s' class Name(atom.core.XmlElement): """The atom:name element.""" _qname = ATOM_TEMPLATE % 'name' class Email(atom.core.XmlElement): """The atom:email element.""" _qname = ATOM_TEMPLATE % 'email' class Uri(atom.core.XmlElement): """The atom:uri element.""" _qname = ATOM_TEMPLATE % 'uri' class Person(atom.core.XmlElement): """A foundation class which atom:author and atom:contributor extend. A person contains information like name, email address, and web page URI for an author or contributor to an Atom feed. """ name = Name email = Email uri = Uri class Author(Person): """The atom:author element. An author is a required element in Feed unless each Entry contains an Author. """ _qname = ATOM_TEMPLATE % 'author' class Contributor(Person): """The atom:contributor element.""" _qname = ATOM_TEMPLATE % 'contributor' class Link(atom.core.XmlElement): """The atom:link element.""" _qname = ATOM_TEMPLATE % 'link' href = 'href' rel = 'rel' type = 'type' hreflang = 'hreflang' title = 'title' length = 'length' class Generator(atom.core.XmlElement): """The atom:generator element.""" _qname = ATOM_TEMPLATE % 'generator' uri = 'uri' version = 'version' class Text(atom.core.XmlElement): """A foundation class from which atom:title, summary, etc. extend. This class should never be instantiated. """ type = 'type' class Title(Text): """The atom:title element.""" _qname = ATOM_TEMPLATE % 'title' class Subtitle(Text): """The atom:subtitle element.""" _qname = ATOM_TEMPLATE % 'subtitle' class Rights(Text): """The atom:rights element.""" _qname = ATOM_TEMPLATE % 'rights' class Summary(Text): """The atom:summary element.""" _qname = ATOM_TEMPLATE % 'summary' class Content(Text): """The atom:content element.""" _qname = ATOM_TEMPLATE % 'content' src = 'src' class Category(atom.core.XmlElement): """The atom:category element.""" _qname = ATOM_TEMPLATE % 'category' term = 'term' scheme = 'scheme' label = 'label' class Id(atom.core.XmlElement): """The atom:id element.""" _qname = ATOM_TEMPLATE % 'id' class Icon(atom.core.XmlElement): """The atom:icon element.""" _qname = ATOM_TEMPLATE % 'icon' class Logo(atom.core.XmlElement): """The atom:logo element.""" _qname = ATOM_TEMPLATE % 'logo' class Draft(atom.core.XmlElement): """The app:draft element which indicates if this entry should be public.""" _qname = (APP_TEMPLATE_V1 % 'draft', APP_TEMPLATE_V2 % 'draft') class Control(atom.core.XmlElement): """The app:control element indicating restrictions on publication. The APP control element may contain a draft element indicating whether or not this entry should be publicly available. """ _qname = (APP_TEMPLATE_V1 % 'control', APP_TEMPLATE_V2 % 'control') draft = Draft class Date(atom.core.XmlElement): """A parent class for atom:updated, published, etc.""" class Updated(Date): """The atom:updated element.""" _qname = ATOM_TEMPLATE % 'updated' class Published(Date): """The atom:published element.""" _qname = ATOM_TEMPLATE % 'published' class LinkFinder(object): """An "interface" providing methods to find link elements Entry elements often contain multiple links which differ in the rel attribute or content type. Often, developers are interested in a specific type of link so this class provides methods to find specific classes of links. This class is used as a mixin in Atom entries and feeds. """ def find_url(self, rel): """Returns the URL (as a string) in a link with the desired rel value.""" for link in self.link: if link.rel == rel and link.href: return link.href return None FindUrl = find_url def get_link(self, rel): """Returns a link object which has the desired rel value. If you are interested in the URL instead of the link object, consider using find_url instead. """ for link in self.link: if link.rel == rel and link.href: return link return None GetLink = get_link def find_self_link(self): """Find the first link with rel set to 'self' Returns: A str containing the link's href or None if none of the links had rel equal to 'self' """ return self.find_url('self') FindSelfLink = find_self_link def get_self_link(self): return self.get_link('self') GetSelfLink = get_self_link def find_edit_link(self): return self.find_url('edit') FindEditLink = find_edit_link def get_edit_link(self): return self.get_link('edit') GetEditLink = get_edit_link def find_edit_media_link(self): link = self.find_url('edit-media') # Search for media-edit as well since Picasa API used media-edit instead. if link is None: return self.find_url('media-edit') return link FindEditMediaLink = find_edit_media_link def get_edit_media_link(self): link = self.get_link('edit-media') if link is None: return self.get_link('media-edit') return link GetEditMediaLink = get_edit_media_link def find_next_link(self): return self.find_url('next') FindNextLink = find_next_link def get_next_link(self): return self.get_link('next') GetNextLink = get_next_link def find_license_link(self): return self.find_url('license') FindLicenseLink = find_license_link def get_license_link(self): return self.get_link('license') GetLicenseLink = get_license_link def find_alternate_link(self): return self.find_url('alternate') FindAlternateLink = find_alternate_link def get_alternate_link(self): return self.get_link('alternate') GetAlternateLink = get_alternate_link class FeedEntryParent(atom.core.XmlElement, LinkFinder): """A super class for atom:feed and entry, contains shared attributes""" author = [Author] category = [Category] contributor = [Contributor] id = Id link = [Link] rights = Rights title = Title updated = Updated def __init__(self, atom_id=None, text=None, *args, **kwargs): if atom_id is not None: self.id = atom_id atom.core.XmlElement.__init__(self, text=text, *args, **kwargs) class Source(FeedEntryParent): """The atom:source element.""" _qname = ATOM_TEMPLATE % 'source' generator = Generator icon = Icon logo = Logo subtitle = Subtitle class Entry(FeedEntryParent): """The atom:entry element.""" _qname = ATOM_TEMPLATE % 'entry' content = Content published = Published source = Source summary = Summary control = Control class Feed(Source): """The atom:feed element which contains entries.""" _qname = ATOM_TEMPLATE % 'feed' entry = [Entry] class ExtensionElement(atom.core.XmlElement): """Provided for backwards compatibility to the v1 atom.ExtensionElement.""" def __init__(self, tag=None, namespace=None, attributes=None, children=None, text=None, *args, **kwargs): if namespace: self._qname = '{%s}%s' % (namespace, tag) else: self._qname = tag self.children = children or [] self.attributes = attributes or {} self.text = text _BecomeChildElement = atom.core.XmlElement._become_child gdata-2.0.18/samples/apps/marketplace_sample/atom/__init__.py0000750036466300116100000014024612156622362024257 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright (C) 2006 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. """Contains classes representing Atom elements. Module objective: provide data classes for Atom constructs. These classes hide the XML-ness of Atom and provide a set of native Python classes to interact with. Conversions to and from XML should only be necessary when the Atom classes "touch the wire" and are sent over HTTP. For this reason this module provides methods and functions to convert Atom classes to and from strings. For more information on the Atom data model, see RFC 4287 (http://www.ietf.org/rfc/rfc4287.txt) AtomBase: A foundation class on which Atom classes are built. It handles the parsing of attributes and children which are common to all Atom classes. By default, the AtomBase class translates all XML child nodes into ExtensionElements. ExtensionElement: Atom allows Atom objects to contain XML which is not part of the Atom specification, these are called extension elements. If a classes parser encounters an unexpected XML construct, it is translated into an ExtensionElement instance. ExtensionElement is designed to fully capture the information in the XML. Child nodes in an XML extension are turned into ExtensionElements as well. """ __author__ = 'api.jscudder (Jeffrey Scudder)' try: from xml.etree import cElementTree as ElementTree except ImportError: try: import cElementTree as ElementTree except ImportError: try: from xml.etree import ElementTree except ImportError: from elementtree import ElementTree import warnings # XML namespaces which are often used in Atom entities. ATOM_NAMESPACE = 'http://www.w3.org/2005/Atom' ELEMENT_TEMPLATE = '{http://www.w3.org/2005/Atom}%s' APP_NAMESPACE = 'http://purl.org/atom/app#' APP_TEMPLATE = '{http://purl.org/atom/app#}%s' # This encoding is used for converting strings before translating the XML # into an object. XML_STRING_ENCODING = 'utf-8' # The desired string encoding for object members. set or monkey-patch to # unicode if you want object members to be Python unicode strings, instead of # encoded strings MEMBER_STRING_ENCODING = 'utf-8' #MEMBER_STRING_ENCODING = unicode # If True, all methods which are exclusive to v1 will raise a # DeprecationWarning ENABLE_V1_WARNINGS = False def v1_deprecated(warning=None): """Shows a warning if ENABLE_V1_WARNINGS is True. Function decorator used to mark methods used in v1 classes which may be removed in future versions of the library. """ warning = warning or '' # This closure is what is returned from the deprecated function. def mark_deprecated(f): # The deprecated_function wraps the actual call to f. def optional_warn_function(*args, **kwargs): if ENABLE_V1_WARNINGS: warnings.warn(warning, DeprecationWarning, stacklevel=2) return f(*args, **kwargs) # Preserve the original name to avoid masking all decorated functions as # 'deprecated_function' try: optional_warn_function.func_name = f.func_name except TypeError: pass # In Python2.3 we can't set the func_name return optional_warn_function return mark_deprecated def CreateClassFromXMLString(target_class, xml_string, string_encoding=None): """Creates an instance of the target class from the string contents. Args: target_class: class The class which will be instantiated and populated with the contents of the XML. This class must have a _tag and a _namespace class variable. xml_string: str A string which contains valid XML. The root element of the XML string should match the tag and namespace of the desired class. string_encoding: str The character encoding which the xml_string should be converted to before it is interpreted and translated into objects. The default is None in which case the string encoding is not changed. Returns: An instance of the target class with members assigned according to the contents of the XML - or None if the root XML tag and namespace did not match those of the target class. """ encoding = string_encoding or XML_STRING_ENCODING if encoding and isinstance(xml_string, unicode): xml_string = xml_string.encode(encoding) tree = ElementTree.fromstring(xml_string) return _CreateClassFromElementTree(target_class, tree) CreateClassFromXMLString = v1_deprecated( 'Please use atom.core.parse with atom.data classes instead.')( CreateClassFromXMLString) def _CreateClassFromElementTree(target_class, tree, namespace=None, tag=None): """Instantiates the class and populates members according to the tree. Note: Only use this function with classes that have _namespace and _tag class members. Args: target_class: class The class which will be instantiated and populated with the contents of the XML. tree: ElementTree An element tree whose contents will be converted into members of the new target_class instance. namespace: str (optional) The namespace which the XML tree's root node must match. If omitted, the namespace defaults to the _namespace of the target class. tag: str (optional) The tag which the XML tree's root node must match. If omitted, the tag defaults to the _tag class member of the target class. Returns: An instance of the target class - or None if the tag and namespace of the XML tree's root node did not match the desired namespace and tag. """ if namespace is None: namespace = target_class._namespace if tag is None: tag = target_class._tag if tree.tag == '{%s}%s' % (namespace, tag): target = target_class() target._HarvestElementTree(tree) return target else: return None class ExtensionContainer(object): def __init__(self, extension_elements=None, extension_attributes=None, text=None): self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} self.text = text __init__ = v1_deprecated( 'Please use data model classes in atom.data instead.')( __init__) # Three methods to create an object from an ElementTree def _HarvestElementTree(self, tree): # Fill in the instance members from the contents of the XML tree. for child in tree: self._ConvertElementTreeToMember(child) for attribute, value in tree.attrib.iteritems(): self._ConvertElementAttributeToMember(attribute, value) # Encode the text string according to the desired encoding type. (UTF-8) if tree.text: if MEMBER_STRING_ENCODING is unicode: self.text = tree.text else: self.text = tree.text.encode(MEMBER_STRING_ENCODING) def _ConvertElementTreeToMember(self, child_tree, current_class=None): self.extension_elements.append(_ExtensionElementFromElementTree( child_tree)) def _ConvertElementAttributeToMember(self, attribute, value): # Encode the attribute value's string with the desired type Default UTF-8 if value: if MEMBER_STRING_ENCODING is unicode: self.extension_attributes[attribute] = value else: self.extension_attributes[attribute] = value.encode( MEMBER_STRING_ENCODING) # One method to create an ElementTree from an object def _AddMembersToElementTree(self, tree): for child in self.extension_elements: child._BecomeChildElement(tree) for attribute, value in self.extension_attributes.iteritems(): if value: if isinstance(value, unicode) or MEMBER_STRING_ENCODING is unicode: tree.attrib[attribute] = value else: # Decode the value from the desired encoding (default UTF-8). tree.attrib[attribute] = value.decode(MEMBER_STRING_ENCODING) if self.text: if isinstance(self.text, unicode) or MEMBER_STRING_ENCODING is unicode: tree.text = self.text else: tree.text = self.text.decode(MEMBER_STRING_ENCODING) def FindExtensions(self, tag=None, namespace=None): """Searches extension elements for child nodes with the desired name. Returns a list of extension elements within this object whose tag and/or namespace match those passed in. To find all extensions in a particular namespace, specify the namespace but not the tag name. If you specify only the tag, the result list may contain extension elements in multiple namespaces. Args: tag: str (optional) The desired tag namespace: str (optional) The desired namespace Returns: A list of elements whose tag and/or namespace match the parameters values """ results = [] if tag and namespace: for element in self.extension_elements: if element.tag == tag and element.namespace == namespace: results.append(element) elif tag and not namespace: for element in self.extension_elements: if element.tag == tag: results.append(element) elif namespace and not tag: for element in self.extension_elements: if element.namespace == namespace: results.append(element) else: for element in self.extension_elements: results.append(element) return results class AtomBase(ExtensionContainer): _children = {} _attributes = {} def __init__(self, extension_elements=None, extension_attributes=None, text=None): self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} self.text = text __init__ = v1_deprecated( 'Please use data model classes in atom.data instead.')( __init__) def _ConvertElementTreeToMember(self, child_tree): # Find the element's tag in this class's list of child members if self.__class__._children.has_key(child_tree.tag): member_name = self.__class__._children[child_tree.tag][0] member_class = self.__class__._children[child_tree.tag][1] # If the class member is supposed to contain a list, make sure the # matching member is set to a list, then append the new member # instance to the list. if isinstance(member_class, list): if getattr(self, member_name) is None: setattr(self, member_name, []) getattr(self, member_name).append(_CreateClassFromElementTree( member_class[0], child_tree)) else: setattr(self, member_name, _CreateClassFromElementTree(member_class, child_tree)) else: ExtensionContainer._ConvertElementTreeToMember(self, child_tree) def _ConvertElementAttributeToMember(self, attribute, value): # Find the attribute in this class's list of attributes. if self.__class__._attributes.has_key(attribute): # Find the member of this class which corresponds to the XML attribute # (lookup in current_class._attributes) and set this member to the # desired value (using self.__dict__). if value: # Encode the string to capture non-ascii characters (default UTF-8) if MEMBER_STRING_ENCODING is unicode: setattr(self, self.__class__._attributes[attribute], value) else: setattr(self, self.__class__._attributes[attribute], value.encode(MEMBER_STRING_ENCODING)) else: ExtensionContainer._ConvertElementAttributeToMember( self, attribute, value) # Three methods to create an ElementTree from an object def _AddMembersToElementTree(self, tree): # Convert the members of this class which are XML child nodes. # This uses the class's _children dictionary to find the members which # should become XML child nodes. member_node_names = [values[0] for tag, values in self.__class__._children.iteritems()] for member_name in member_node_names: member = getattr(self, member_name) if member is None: pass elif isinstance(member, list): for instance in member: instance._BecomeChildElement(tree) else: member._BecomeChildElement(tree) # Convert the members of this class which are XML attributes. for xml_attribute, member_name in self.__class__._attributes.iteritems(): member = getattr(self, member_name) if member is not None: if isinstance(member, unicode) or MEMBER_STRING_ENCODING is unicode: tree.attrib[xml_attribute] = member else: tree.attrib[xml_attribute] = member.decode(MEMBER_STRING_ENCODING) # Lastly, call the ExtensionContainers's _AddMembersToElementTree to # convert any extension attributes. ExtensionContainer._AddMembersToElementTree(self, tree) def _BecomeChildElement(self, tree): """ Note: Only for use with classes that have a _tag and _namespace class member. It is in AtomBase so that it can be inherited but it should not be called on instances of AtomBase. """ new_child = ElementTree.Element('') tree.append(new_child) new_child.tag = '{%s}%s' % (self.__class__._namespace, self.__class__._tag) self._AddMembersToElementTree(new_child) def _ToElementTree(self): """ Note, this method is designed to be used only with classes that have a _tag and _namespace. It is placed in AtomBase for inheritance but should not be called on this class. """ new_tree = ElementTree.Element('{%s}%s' % (self.__class__._namespace, self.__class__._tag)) self._AddMembersToElementTree(new_tree) return new_tree def ToString(self, string_encoding='UTF-8'): """Converts the Atom object to a string containing XML.""" return ElementTree.tostring(self._ToElementTree(), encoding=string_encoding) def __str__(self): return self.ToString() class Name(AtomBase): """The atom:name element""" _tag = 'name' _namespace = ATOM_NAMESPACE _children = AtomBase._children.copy() _attributes = AtomBase._attributes.copy() def __init__(self, text=None, extension_elements=None, extension_attributes=None): """Constructor for Name Args: text: str The text data in the this element extension_elements: list A list of ExtensionElement instances extension_attributes: dict A dictionary of attribute value string pairs """ self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} def NameFromString(xml_string): return CreateClassFromXMLString(Name, xml_string) class Email(AtomBase): """The atom:email element""" _tag = 'email' _namespace = ATOM_NAMESPACE _children = AtomBase._children.copy() _attributes = AtomBase._attributes.copy() def __init__(self, text=None, extension_elements=None, extension_attributes=None): """Constructor for Email Args: extension_elements: list A list of ExtensionElement instances extension_attributes: dict A dictionary of attribute value string pairs text: str The text data in the this element """ self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} def EmailFromString(xml_string): return CreateClassFromXMLString(Email, xml_string) class Uri(AtomBase): """The atom:uri element""" _tag = 'uri' _namespace = ATOM_NAMESPACE _children = AtomBase._children.copy() _attributes = AtomBase._attributes.copy() def __init__(self, text=None, extension_elements=None, extension_attributes=None): """Constructor for Uri Args: extension_elements: list A list of ExtensionElement instances extension_attributes: dict A dictionary of attribute value string pairs text: str The text data in the this element """ self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} def UriFromString(xml_string): return CreateClassFromXMLString(Uri, xml_string) class Person(AtomBase): """A foundation class from which atom:author and atom:contributor extend. A person contains information like name, email address, and web page URI for an author or contributor to an Atom feed. """ _children = AtomBase._children.copy() _attributes = AtomBase._attributes.copy() _children['{%s}name' % (ATOM_NAMESPACE)] = ('name', Name) _children['{%s}email' % (ATOM_NAMESPACE)] = ('email', Email) _children['{%s}uri' % (ATOM_NAMESPACE)] = ('uri', Uri) def __init__(self, name=None, email=None, uri=None, extension_elements=None, extension_attributes=None, text=None): """Foundation from which author and contributor are derived. The constructor is provided for illustrative purposes, you should not need to instantiate a Person. Args: name: Name The person's name email: Email The person's email address uri: Uri The URI of the person's webpage extension_elements: list A list of ExtensionElement instances which are children of this element. extension_attributes: dict A dictionary of strings which are the values for additional XML attributes of this element. text: String The text contents of the element. This is the contents of the Entry's XML text node. (Example: This is the text) """ self.name = name self.email = email self.uri = uri self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} self.text = text class Author(Person): """The atom:author element An author is a required element in Feed. """ _tag = 'author' _namespace = ATOM_NAMESPACE _children = Person._children.copy() _attributes = Person._attributes.copy() #_children = {} #_attributes = {} def __init__(self, name=None, email=None, uri=None, extension_elements=None, extension_attributes=None, text=None): """Constructor for Author Args: name: Name email: Email uri: Uri extension_elements: list A list of ExtensionElement instances extension_attributes: dict A dictionary of attribute value string pairs text: str The text data in the this element """ self.name = name self.email = email self.uri = uri self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} self.text = text def AuthorFromString(xml_string): return CreateClassFromXMLString(Author, xml_string) class Contributor(Person): """The atom:contributor element""" _tag = 'contributor' _namespace = ATOM_NAMESPACE _children = Person._children.copy() _attributes = Person._attributes.copy() def __init__(self, name=None, email=None, uri=None, extension_elements=None, extension_attributes=None, text=None): """Constructor for Contributor Args: name: Name email: Email uri: Uri extension_elements: list A list of ExtensionElement instances extension_attributes: dict A dictionary of attribute value string pairs text: str The text data in the this element """ self.name = name self.email = email self.uri = uri self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} self.text = text def ContributorFromString(xml_string): return CreateClassFromXMLString(Contributor, xml_string) class Link(AtomBase): """The atom:link element""" _tag = 'link' _namespace = ATOM_NAMESPACE _children = AtomBase._children.copy() _attributes = AtomBase._attributes.copy() _attributes['rel'] = 'rel' _attributes['href'] = 'href' _attributes['type'] = 'type' _attributes['title'] = 'title' _attributes['length'] = 'length' _attributes['hreflang'] = 'hreflang' def __init__(self, href=None, rel=None, link_type=None, hreflang=None, title=None, length=None, text=None, extension_elements=None, extension_attributes=None): """Constructor for Link Args: href: string The href attribute of the link rel: string type: string hreflang: string The language for the href title: string length: string The length of the href's destination extension_elements: list A list of ExtensionElement instances extension_attributes: dict A dictionary of attribute value string pairs text: str The text data in the this element """ self.href = href self.rel = rel self.type = link_type self.hreflang = hreflang self.title = title self.length = length self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} def LinkFromString(xml_string): return CreateClassFromXMLString(Link, xml_string) class Generator(AtomBase): """The atom:generator element""" _tag = 'generator' _namespace = ATOM_NAMESPACE _children = AtomBase._children.copy() _attributes = AtomBase._attributes.copy() _attributes['uri'] = 'uri' _attributes['version'] = 'version' def __init__(self, uri=None, version=None, text=None, extension_elements=None, extension_attributes=None): """Constructor for Generator Args: uri: string version: string text: str The text data in the this element extension_elements: list A list of ExtensionElement instances extension_attributes: dict A dictionary of attribute value string pairs """ self.uri = uri self.version = version self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} def GeneratorFromString(xml_string): return CreateClassFromXMLString(Generator, xml_string) class Text(AtomBase): """A foundation class from which atom:title, summary, etc. extend. This class should never be instantiated. """ _children = AtomBase._children.copy() _attributes = AtomBase._attributes.copy() _attributes['type'] = 'type' def __init__(self, text_type=None, text=None, extension_elements=None, extension_attributes=None): """Constructor for Text Args: text_type: string text: str The text data in the this element extension_elements: list A list of ExtensionElement instances extension_attributes: dict A dictionary of attribute value string pairs """ self.type = text_type self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} class Title(Text): """The atom:title element""" _tag = 'title' _namespace = ATOM_NAMESPACE _children = Text._children.copy() _attributes = Text._attributes.copy() def __init__(self, title_type=None, text=None, extension_elements=None, extension_attributes=None): """Constructor for Title Args: title_type: string text: str The text data in the this element extension_elements: list A list of ExtensionElement instances extension_attributes: dict A dictionary of attribute value string pairs """ self.type = title_type self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} def TitleFromString(xml_string): return CreateClassFromXMLString(Title, xml_string) class Subtitle(Text): """The atom:subtitle element""" _tag = 'subtitle' _namespace = ATOM_NAMESPACE _children = Text._children.copy() _attributes = Text._attributes.copy() def __init__(self, subtitle_type=None, text=None, extension_elements=None, extension_attributes=None): """Constructor for Subtitle Args: subtitle_type: string text: str The text data in the this element extension_elements: list A list of ExtensionElement instances extension_attributes: dict A dictionary of attribute value string pairs """ self.type = subtitle_type self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} def SubtitleFromString(xml_string): return CreateClassFromXMLString(Subtitle, xml_string) class Rights(Text): """The atom:rights element""" _tag = 'rights' _namespace = ATOM_NAMESPACE _children = Text._children.copy() _attributes = Text._attributes.copy() def __init__(self, rights_type=None, text=None, extension_elements=None, extension_attributes=None): """Constructor for Rights Args: rights_type: string text: str The text data in the this element extension_elements: list A list of ExtensionElement instances extension_attributes: dict A dictionary of attribute value string pairs """ self.type = rights_type self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} def RightsFromString(xml_string): return CreateClassFromXMLString(Rights, xml_string) class Summary(Text): """The atom:summary element""" _tag = 'summary' _namespace = ATOM_NAMESPACE _children = Text._children.copy() _attributes = Text._attributes.copy() def __init__(self, summary_type=None, text=None, extension_elements=None, extension_attributes=None): """Constructor for Summary Args: summary_type: string text: str The text data in the this element extension_elements: list A list of ExtensionElement instances extension_attributes: dict A dictionary of attribute value string pairs """ self.type = summary_type self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} def SummaryFromString(xml_string): return CreateClassFromXMLString(Summary, xml_string) class Content(Text): """The atom:content element""" _tag = 'content' _namespace = ATOM_NAMESPACE _children = Text._children.copy() _attributes = Text._attributes.copy() _attributes['src'] = 'src' def __init__(self, content_type=None, src=None, text=None, extension_elements=None, extension_attributes=None): """Constructor for Content Args: content_type: string src: string text: str The text data in the this element extension_elements: list A list of ExtensionElement instances extension_attributes: dict A dictionary of attribute value string pairs """ self.type = content_type self.src = src self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} def ContentFromString(xml_string): return CreateClassFromXMLString(Content, xml_string) class Category(AtomBase): """The atom:category element""" _tag = 'category' _namespace = ATOM_NAMESPACE _children = AtomBase._children.copy() _attributes = AtomBase._attributes.copy() _attributes['term'] = 'term' _attributes['scheme'] = 'scheme' _attributes['label'] = 'label' def __init__(self, term=None, scheme=None, label=None, text=None, extension_elements=None, extension_attributes=None): """Constructor for Category Args: term: str scheme: str label: str text: str The text data in the this element extension_elements: list A list of ExtensionElement instances extension_attributes: dict A dictionary of attribute value string pairs """ self.term = term self.scheme = scheme self.label = label self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} def CategoryFromString(xml_string): return CreateClassFromXMLString(Category, xml_string) class Id(AtomBase): """The atom:id element.""" _tag = 'id' _namespace = ATOM_NAMESPACE _children = AtomBase._children.copy() _attributes = AtomBase._attributes.copy() def __init__(self, text=None, extension_elements=None, extension_attributes=None): """Constructor for Id Args: text: str The text data in the this element extension_elements: list A list of ExtensionElement instances extension_attributes: dict A dictionary of attribute value string pairs """ self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} def IdFromString(xml_string): return CreateClassFromXMLString(Id, xml_string) class Icon(AtomBase): """The atom:icon element.""" _tag = 'icon' _namespace = ATOM_NAMESPACE _children = AtomBase._children.copy() _attributes = AtomBase._attributes.copy() def __init__(self, text=None, extension_elements=None, extension_attributes=None): """Constructor for Icon Args: text: str The text data in the this element extension_elements: list A list of ExtensionElement instances extension_attributes: dict A dictionary of attribute value string pairs """ self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} def IconFromString(xml_string): return CreateClassFromXMLString(Icon, xml_string) class Logo(AtomBase): """The atom:logo element.""" _tag = 'logo' _namespace = ATOM_NAMESPACE _children = AtomBase._children.copy() _attributes = AtomBase._attributes.copy() def __init__(self, text=None, extension_elements=None, extension_attributes=None): """Constructor for Logo Args: text: str The text data in the this element extension_elements: list A list of ExtensionElement instances extension_attributes: dict A dictionary of attribute value string pairs """ self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} def LogoFromString(xml_string): return CreateClassFromXMLString(Logo, xml_string) class Draft(AtomBase): """The app:draft element which indicates if this entry should be public.""" _tag = 'draft' _namespace = APP_NAMESPACE _children = AtomBase._children.copy() _attributes = AtomBase._attributes.copy() def __init__(self, text=None, extension_elements=None, extension_attributes=None): """Constructor for app:draft Args: text: str The text data in the this element extension_elements: list A list of ExtensionElement instances extension_attributes: dict A dictionary of attribute value string pairs """ self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} def DraftFromString(xml_string): return CreateClassFromXMLString(Draft, xml_string) class Control(AtomBase): """The app:control element indicating restrictions on publication. The APP control element may contain a draft element indicating whether or not this entry should be publicly available. """ _tag = 'control' _namespace = APP_NAMESPACE _children = AtomBase._children.copy() _attributes = AtomBase._attributes.copy() _children['{%s}draft' % APP_NAMESPACE] = ('draft', Draft) def __init__(self, draft=None, text=None, extension_elements=None, extension_attributes=None): """Constructor for app:control""" self.draft = draft self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} def ControlFromString(xml_string): return CreateClassFromXMLString(Control, xml_string) class Date(AtomBase): """A parent class for atom:updated, published, etc.""" #TODO Add text to and from time conversion methods to allow users to set # the contents of a Date to a python DateTime object. _children = AtomBase._children.copy() _attributes = AtomBase._attributes.copy() def __init__(self, text=None, extension_elements=None, extension_attributes=None): self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} class Updated(Date): """The atom:updated element.""" _tag = 'updated' _namespace = ATOM_NAMESPACE _children = Date._children.copy() _attributes = Date._attributes.copy() def __init__(self, text=None, extension_elements=None, extension_attributes=None): """Constructor for Updated Args: text: str The text data in the this element extension_elements: list A list of ExtensionElement instances extension_attributes: dict A dictionary of attribute value string pairs """ self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} def UpdatedFromString(xml_string): return CreateClassFromXMLString(Updated, xml_string) class Published(Date): """The atom:published element.""" _tag = 'published' _namespace = ATOM_NAMESPACE _children = Date._children.copy() _attributes = Date._attributes.copy() def __init__(self, text=None, extension_elements=None, extension_attributes=None): """Constructor for Published Args: text: str The text data in the this element extension_elements: list A list of ExtensionElement instances extension_attributes: dict A dictionary of attribute value string pairs """ self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} def PublishedFromString(xml_string): return CreateClassFromXMLString(Published, xml_string) class LinkFinder(object): """An "interface" providing methods to find link elements Entry elements often contain multiple links which differ in the rel attribute or content type. Often, developers are interested in a specific type of link so this class provides methods to find specific classes of links. This class is used as a mixin in Atom entries and feeds. """ def GetSelfLink(self): """Find the first link with rel set to 'self' Returns: An atom.Link or none if none of the links had rel equal to 'self' """ for a_link in self.link: if a_link.rel == 'self': return a_link return None def GetEditLink(self): for a_link in self.link: if a_link.rel == 'edit': return a_link return None def GetEditMediaLink(self): for a_link in self.link: if a_link.rel == 'edit-media': return a_link return None def GetNextLink(self): for a_link in self.link: if a_link.rel == 'next': return a_link return None def GetLicenseLink(self): for a_link in self.link: if a_link.rel == 'license': return a_link return None def GetAlternateLink(self): for a_link in self.link: if a_link.rel == 'alternate': return a_link return None class FeedEntryParent(AtomBase, LinkFinder): """A super class for atom:feed and entry, contains shared attributes""" _children = AtomBase._children.copy() _attributes = AtomBase._attributes.copy() _children['{%s}author' % ATOM_NAMESPACE] = ('author', [Author]) _children['{%s}category' % ATOM_NAMESPACE] = ('category', [Category]) _children['{%s}contributor' % ATOM_NAMESPACE] = ('contributor', [Contributor]) _children['{%s}id' % ATOM_NAMESPACE] = ('id', Id) _children['{%s}link' % ATOM_NAMESPACE] = ('link', [Link]) _children['{%s}rights' % ATOM_NAMESPACE] = ('rights', Rights) _children['{%s}title' % ATOM_NAMESPACE] = ('title', Title) _children['{%s}updated' % ATOM_NAMESPACE] = ('updated', Updated) def __init__(self, author=None, category=None, contributor=None, atom_id=None, link=None, rights=None, title=None, updated=None, text=None, extension_elements=None, extension_attributes=None): self.author = author or [] self.category = category or [] self.contributor = contributor or [] self.id = atom_id self.link = link or [] self.rights = rights self.title = title self.updated = updated self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} class Source(FeedEntryParent): """The atom:source element""" _tag = 'source' _namespace = ATOM_NAMESPACE _children = FeedEntryParent._children.copy() _attributes = FeedEntryParent._attributes.copy() _children['{%s}generator' % ATOM_NAMESPACE] = ('generator', Generator) _children['{%s}icon' % ATOM_NAMESPACE] = ('icon', Icon) _children['{%s}logo' % ATOM_NAMESPACE] = ('logo', Logo) _children['{%s}subtitle' % ATOM_NAMESPACE] = ('subtitle', Subtitle) def __init__(self, author=None, category=None, contributor=None, generator=None, icon=None, atom_id=None, link=None, logo=None, rights=None, subtitle=None, title=None, updated=None, text=None, extension_elements=None, extension_attributes=None): """Constructor for Source Args: author: list (optional) A list of Author instances which belong to this class. category: list (optional) A list of Category instances contributor: list (optional) A list on Contributor instances generator: Generator (optional) icon: Icon (optional) id: Id (optional) The entry's Id element link: list (optional) A list of Link instances logo: Logo (optional) rights: Rights (optional) The entry's Rights element subtitle: Subtitle (optional) The entry's subtitle element title: Title (optional) the entry's title element updated: Updated (optional) the entry's updated element text: String (optional) The text contents of the element. This is the contents of the Entry's XML text node. (Example: This is the text) extension_elements: list (optional) A list of ExtensionElement instances which are children of this element. extension_attributes: dict (optional) A dictionary of strings which are the values for additional XML attributes of this element. """ self.author = author or [] self.category = category or [] self.contributor = contributor or [] self.generator = generator self.icon = icon self.id = atom_id self.link = link or [] self.logo = logo self.rights = rights self.subtitle = subtitle self.title = title self.updated = updated self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} def SourceFromString(xml_string): return CreateClassFromXMLString(Source, xml_string) class Entry(FeedEntryParent): """The atom:entry element""" _tag = 'entry' _namespace = ATOM_NAMESPACE _children = FeedEntryParent._children.copy() _attributes = FeedEntryParent._attributes.copy() _children['{%s}content' % ATOM_NAMESPACE] = ('content', Content) _children['{%s}published' % ATOM_NAMESPACE] = ('published', Published) _children['{%s}source' % ATOM_NAMESPACE] = ('source', Source) _children['{%s}summary' % ATOM_NAMESPACE] = ('summary', Summary) _children['{%s}control' % APP_NAMESPACE] = ('control', Control) def __init__(self, author=None, category=None, content=None, contributor=None, atom_id=None, link=None, published=None, rights=None, source=None, summary=None, control=None, title=None, updated=None, extension_elements=None, extension_attributes=None, text=None): """Constructor for atom:entry Args: author: list A list of Author instances which belong to this class. category: list A list of Category instances content: Content The entry's Content contributor: list A list on Contributor instances id: Id The entry's Id element link: list A list of Link instances published: Published The entry's Published element rights: Rights The entry's Rights element source: Source the entry's source element summary: Summary the entry's summary element title: Title the entry's title element updated: Updated the entry's updated element control: The entry's app:control element which can be used to mark an entry as a draft which should not be publicly viewable. text: String The text contents of the element. This is the contents of the Entry's XML text node. (Example: This is the text) extension_elements: list A list of ExtensionElement instances which are children of this element. extension_attributes: dict A dictionary of strings which are the values for additional XML attributes of this element. """ self.author = author or [] self.category = category or [] self.content = content self.contributor = contributor or [] self.id = atom_id self.link = link or [] self.published = published self.rights = rights self.source = source self.summary = summary self.title = title self.updated = updated self.control = control self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} __init__ = v1_deprecated('Please use atom.data.Entry instead.')(__init__) def EntryFromString(xml_string): return CreateClassFromXMLString(Entry, xml_string) class Feed(Source): """The atom:feed element""" _tag = 'feed' _namespace = ATOM_NAMESPACE _children = Source._children.copy() _attributes = Source._attributes.copy() _children['{%s}entry' % ATOM_NAMESPACE] = ('entry', [Entry]) def __init__(self, author=None, category=None, contributor=None, generator=None, icon=None, atom_id=None, link=None, logo=None, rights=None, subtitle=None, title=None, updated=None, entry=None, text=None, extension_elements=None, extension_attributes=None): """Constructor for Source Args: author: list (optional) A list of Author instances which belong to this class. category: list (optional) A list of Category instances contributor: list (optional) A list on Contributor instances generator: Generator (optional) icon: Icon (optional) id: Id (optional) The entry's Id element link: list (optional) A list of Link instances logo: Logo (optional) rights: Rights (optional) The entry's Rights element subtitle: Subtitle (optional) The entry's subtitle element title: Title (optional) the entry's title element updated: Updated (optional) the entry's updated element entry: list (optional) A list of the Entry instances contained in the feed. text: String (optional) The text contents of the element. This is the contents of the Entry's XML text node. (Example: This is the text) extension_elements: list (optional) A list of ExtensionElement instances which are children of this element. extension_attributes: dict (optional) A dictionary of strings which are the values for additional XML attributes of this element. """ self.author = author or [] self.category = category or [] self.contributor = contributor or [] self.generator = generator self.icon = icon self.id = atom_id self.link = link or [] self.logo = logo self.rights = rights self.subtitle = subtitle self.title = title self.updated = updated self.entry = entry or [] self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} __init__ = v1_deprecated('Please use atom.data.Feed instead.')(__init__) def FeedFromString(xml_string): return CreateClassFromXMLString(Feed, xml_string) class ExtensionElement(object): """Represents extra XML elements contained in Atom classes.""" def __init__(self, tag, namespace=None, attributes=None, children=None, text=None): """Constructor for EtensionElement Args: namespace: string (optional) The XML namespace for this element. tag: string (optional) The tag (without the namespace qualifier) for this element. To reconstruct the full qualified name of the element, combine this tag with the namespace. attributes: dict (optinal) The attribute value string pairs for the XML attributes of this element. children: list (optional) A list of ExtensionElements which represent the XML child nodes of this element. """ self.namespace = namespace self.tag = tag self.attributes = attributes or {} self.children = children or [] self.text = text def ToString(self): element_tree = self._TransferToElementTree(ElementTree.Element('')) return ElementTree.tostring(element_tree, encoding="UTF-8") def _TransferToElementTree(self, element_tree): if self.tag is None: return None if self.namespace is not None: element_tree.tag = '{%s}%s' % (self.namespace, self.tag) else: element_tree.tag = self.tag for key, value in self.attributes.iteritems(): element_tree.attrib[key] = value for child in self.children: child._BecomeChildElement(element_tree) element_tree.text = self.text return element_tree def _BecomeChildElement(self, element_tree): """Converts this object into an etree element and adds it as a child node. Adds self to the ElementTree. This method is required to avoid verbose XML which constantly redefines the namespace. Args: element_tree: ElementTree._Element The element to which this object's XML will be added. """ new_element = ElementTree.Element('') element_tree.append(new_element) self._TransferToElementTree(new_element) def FindChildren(self, tag=None, namespace=None): """Searches child nodes for objects with the desired tag/namespace. Returns a list of extension elements within this object whose tag and/or namespace match those passed in. To find all children in a particular namespace, specify the namespace but not the tag name. If you specify only the tag, the result list may contain extension elements in multiple namespaces. Args: tag: str (optional) The desired tag namespace: str (optional) The desired namespace Returns: A list of elements whose tag and/or namespace match the parameters values """ results = [] if tag and namespace: for element in self.children: if element.tag == tag and element.namespace == namespace: results.append(element) elif tag and not namespace: for element in self.children: if element.tag == tag: results.append(element) elif namespace and not tag: for element in self.children: if element.namespace == namespace: results.append(element) else: for element in self.children: results.append(element) return results def ExtensionElementFromString(xml_string): element_tree = ElementTree.fromstring(xml_string) return _ExtensionElementFromElementTree(element_tree) def _ExtensionElementFromElementTree(element_tree): element_tag = element_tree.tag if '}' in element_tag: namespace = element_tag[1:element_tag.index('}')] tag = element_tag[element_tag.index('}')+1:] else: namespace = None tag = element_tag extension = ExtensionElement(namespace=namespace, tag=tag) for key, value in element_tree.attrib.iteritems(): extension.attributes[key] = value for child in element_tree: extension.children.append(_ExtensionElementFromElementTree(child)) extension.text = element_tree.text return extension def deprecated(warning=None): """Decorator to raise warning each time the function is called. Args: warning: The warning message to be displayed as a string (optinoal). """ warning = warning or '' # This closure is what is returned from the deprecated function. def mark_deprecated(f): # The deprecated_function wraps the actual call to f. def deprecated_function(*args, **kwargs): warnings.warn(warning, DeprecationWarning, stacklevel=2) return f(*args, **kwargs) # Preserve the original name to avoid masking all decorated functions as # 'deprecated_function' try: deprecated_function.func_name = f.func_name except TypeError: # Setting the func_name is not allowed in Python2.3. pass return deprecated_function return mark_deprecated gdata-2.0.18/samples/apps/marketplace_sample/atom/client.py0000750036466300116100000001731412156622362023775 0ustar afshareng00000000000000#!/usr/bin/env python # # Copyright (C) 2009 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. """AtomPubClient provides CRUD ops. in line with the Atom Publishing Protocol. """ __author__ = 'j.s@google.com (Jeff Scudder)' import atom.http_core class Error(Exception): pass class MissingHost(Error): pass class AtomPubClient(object): host = None auth_token = None ssl = False # Whether to force all requests over https xoauth_requestor_id = None def __init__(self, http_client=None, host=None, auth_token=None, source=None, xoauth_requestor_id=None, **kwargs): """Creates a new AtomPubClient instance. Args: source: The name of your application. http_client: An object capable of performing HTTP requests through a request method. This object is used to perform the request when the AtomPubClient's request method is called. Used to allow HTTP requests to be directed to a mock server, or use an alternate library instead of the default of httplib to make HTTP requests. host: str The default host name to use if a host is not specified in the requested URI. auth_token: An object which sets the HTTP Authorization header when its modify_request method is called. """ self.http_client = http_client or atom.http_core.ProxiedHttpClient() if host is not None: self.host = host if auth_token is not None: self.auth_token = auth_token self.xoauth_requestor_id = xoauth_requestor_id self.source = source def request(self, method=None, uri=None, auth_token=None, http_request=None, **kwargs): """Performs an HTTP request to the server indicated. Uses the http_client instance to make the request. Args: method: The HTTP method as a string, usually one of 'GET', 'POST', 'PUT', or 'DELETE' uri: The URI desired as a string or atom.http_core.Uri. http_request: auth_token: An authorization token object whose modify_request method sets the HTTP Authorization header. Returns: The results of calling self.http_client.request. With the default http_client, this is an HTTP response object. """ # Modify the request based on the AtomPubClient settings and parameters # passed in to the request. http_request = self.modify_request(http_request) if isinstance(uri, (str, unicode)): uri = atom.http_core.Uri.parse_uri(uri) if uri is not None: uri.modify_request(http_request) if isinstance(method, (str, unicode)): http_request.method = method # Any unrecognized arguments are assumed to be capable of modifying the # HTTP request. for name, value in kwargs.iteritems(): if value is not None: value.modify_request(http_request) # Default to an http request if the protocol scheme is not set. if http_request.uri.scheme is None: http_request.uri.scheme = 'http' # Override scheme. Force requests over https. if self.ssl: http_request.uri.scheme = 'https' if http_request.uri.path is None: http_request.uri.path = '/' # Add the Authorization header at the very end. The Authorization header # value may need to be calculated using information in the request. if auth_token: auth_token.modify_request(http_request) elif self.auth_token: self.auth_token.modify_request(http_request) # Check to make sure there is a host in the http_request. if http_request.uri.host is None: raise MissingHost('No host provided in request %s %s' % ( http_request.method, str(http_request.uri))) # Perform the fully specified request using the http_client instance. # Sends the request to the server and returns the server's response. return self.http_client.request(http_request) Request = request def get(self, uri=None, auth_token=None, http_request=None, **kwargs): """Performs a request using the GET method, returns an HTTP response.""" return self.request(method='GET', uri=uri, auth_token=auth_token, http_request=http_request, **kwargs) Get = get def post(self, uri=None, data=None, auth_token=None, http_request=None, **kwargs): """Sends data using the POST method, returns an HTTP response.""" return self.request(method='POST', uri=uri, auth_token=auth_token, http_request=http_request, data=data, **kwargs) Post = post def put(self, uri=None, data=None, auth_token=None, http_request=None, **kwargs): """Sends data using the PUT method, returns an HTTP response.""" return self.request(method='PUT', uri=uri, auth_token=auth_token, http_request=http_request, data=data, **kwargs) Put = put def delete(self, uri=None, auth_token=None, http_request=None, **kwargs): """Performs a request using the DELETE method, returns an HTTP response.""" return self.request(method='DELETE', uri=uri, auth_token=auth_token, http_request=http_request, **kwargs) Delete = delete def modify_request(self, http_request): """Changes the HTTP request before sending it to the server. Sets the User-Agent HTTP header and fills in the HTTP host portion of the URL if one was not included in the request (for this it uses the self.host member if one is set). This method is called in self.request. Args: http_request: An atom.http_core.HttpRequest() (optional) If one is not provided, a new HttpRequest is instantiated. Returns: An atom.http_core.HttpRequest() with the User-Agent header set and if this client has a value in its host member, the host in the request URL is set. """ if http_request is None: http_request = atom.http_core.HttpRequest() if self.host is not None and http_request.uri.host is None: http_request.uri.host = self.host if self.xoauth_requestor_id is not None: http_request.uri.query['xoauth_requestor_id'] = self.xoauth_requestor_id # Set the user agent header for logging purposes. if self.source: http_request.headers['User-Agent'] = '%s gdata-py/2.0.15' % self.source else: http_request.headers['User-Agent'] = 'gdata-py/2.0.15' return http_request ModifyRequest = modify_request class CustomHeaders(object): """Add custom headers to an http_request. Usage: >>> custom_headers = atom.client.CustomHeaders(header1='value1', header2='value2') >>> client.get(uri, custom_headers=custom_headers) """ def __init__(self, **kwargs): """Creates a CustomHeaders instance. Initialize the headers dictionary with the arguments list. """ self.headers = kwargs def modify_request(self, http_request): """Changes the HTTP request before sending it to the server. Adds the custom headers to the HTTP request. Args: http_request: An atom.http_core.HttpRequest(). Returns: An atom.http_core.HttpRequest() with the added custom headers. """ for name, value in self.headers.iteritems(): if value is not None: http_request.headers[name] = value return http_request gdata-2.0.18/samples/apps/marketplace_sample/atom/mock_service.py0000750036466300116100000002415612156622362025172 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright (C) 2008 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. """MockService provides CRUD ops. for mocking calls to AtomPub services. MockService: Exposes the publicly used methods of AtomService to provide a mock interface which can be used in unit tests. """ import atom.service import pickle __author__ = 'api.jscudder (Jeffrey Scudder)' # Recordings contains pairings of HTTP MockRequest objects with MockHttpResponse objects. recordings = [] # If set, the mock service HttpRequest are actually made through this object. real_request_handler = None def ConcealValueWithSha(source): import sha return sha.new(source[:-5]).hexdigest() def DumpRecordings(conceal_func=ConcealValueWithSha): if conceal_func: for recording_pair in recordings: recording_pair[0].ConcealSecrets(conceal_func) return pickle.dumps(recordings) def LoadRecordings(recordings_file_or_string): if isinstance(recordings_file_or_string, str): atom.mock_service.recordings = pickle.loads(recordings_file_or_string) elif hasattr(recordings_file_or_string, 'read'): atom.mock_service.recordings = pickle.loads( recordings_file_or_string.read()) def HttpRequest(service, operation, data, uri, extra_headers=None, url_params=None, escape_params=True, content_type='application/atom+xml'): """Simulates an HTTP call to the server, makes an actual HTTP request if real_request_handler is set. This function operates in two different modes depending on if real_request_handler is set or not. If real_request_handler is not set, HttpRequest will look in this module's recordings list to find a response which matches the parameters in the function call. If real_request_handler is set, this function will call real_request_handler.HttpRequest, add the response to the recordings list, and respond with the actual response. Args: service: atom.AtomService object which contains some of the parameters needed to make the request. The following members are used to construct the HTTP call: server (str), additional_headers (dict), port (int), and ssl (bool). operation: str The HTTP operation to be performed. This is usually one of 'GET', 'POST', 'PUT', or 'DELETE' data: ElementTree, filestream, list of parts, or other object which can be converted to a string. Should be set to None when performing a GET or PUT. If data is a file-like object which can be read, this method will read a chunk of 100K bytes at a time and send them. If the data is a list of parts to be sent, each part will be evaluated and sent. uri: The beginning of the URL to which the request should be sent. Examples: '/', '/base/feeds/snippets', '/m8/feeds/contacts/default/base' extra_headers: dict of strings. HTTP headers which should be sent in the request. These headers are in addition to those stored in service.additional_headers. url_params: dict of strings. Key value pairs to be added to the URL as URL parameters. For example {'foo':'bar', 'test':'param'} will become ?foo=bar&test=param. escape_params: bool default True. If true, the keys and values in url_params will be URL escaped when the form is constructed (Special characters converted to %XX form.) content_type: str The MIME type for the data being sent. Defaults to 'application/atom+xml', this is only used if data is set. """ full_uri = atom.service.BuildUri(uri, url_params, escape_params) (server, port, ssl, uri) = atom.service.ProcessUrl(service, uri) current_request = MockRequest(operation, full_uri, host=server, ssl=ssl, data=data, extra_headers=extra_headers, url_params=url_params, escape_params=escape_params, content_type=content_type) # If the request handler is set, we should actually make the request using # the request handler and record the response to replay later. if real_request_handler: response = real_request_handler.HttpRequest(service, operation, data, uri, extra_headers=extra_headers, url_params=url_params, escape_params=escape_params, content_type=content_type) # TODO: need to copy the HTTP headers from the real response into the # recorded_response. recorded_response = MockHttpResponse(body=response.read(), status=response.status, reason=response.reason) # Insert a tuple which maps the request to the response object returned # when making an HTTP call using the real_request_handler. recordings.append((current_request, recorded_response)) return recorded_response else: # Look through available recordings to see if one matches the current # request. for request_response_pair in recordings: if request_response_pair[0].IsMatch(current_request): return request_response_pair[1] return None class MockRequest(object): """Represents a request made to an AtomPub server. These objects are used to determine if a client request matches a recorded HTTP request to determine what the mock server's response will be. """ def __init__(self, operation, uri, host=None, ssl=False, port=None, data=None, extra_headers=None, url_params=None, escape_params=True, content_type='application/atom+xml'): """Constructor for a MockRequest Args: operation: str One of 'GET', 'POST', 'PUT', or 'DELETE' this is the HTTP operation requested on the resource. uri: str The URL describing the resource to be modified or feed to be retrieved. This should include the protocol (http/https) and the host (aka domain). For example, these are some valud full_uris: 'http://example.com', 'https://www.google.com/accounts/ClientLogin' host: str (optional) The server name which will be placed at the beginning of the URL if the uri parameter does not begin with 'http'. Examples include 'example.com', 'www.google.com', 'www.blogger.com'. ssl: boolean (optional) If true, the request URL will begin with https instead of http. data: ElementTree, filestream, list of parts, or other object which can be converted to a string. (optional) Should be set to None when performing a GET or PUT. If data is a file-like object which can be read, the constructor will read the entire file into memory. If the data is a list of parts to be sent, each part will be evaluated and stored. extra_headers: dict (optional) HTTP headers included in the request. url_params: dict (optional) Key value pairs which should be added to the URL as URL parameters in the request. For example uri='/', url_parameters={'foo':'1','bar':'2'} could become '/?foo=1&bar=2'. escape_params: boolean (optional) Perform URL escaping on the keys and values specified in url_params. Defaults to True. content_type: str (optional) Provides the MIME type of the data being sent. """ self.operation = operation self.uri = _ConstructFullUrlBase(uri, host=host, ssl=ssl) self.data = data self.extra_headers = extra_headers self.url_params = url_params or {} self.escape_params = escape_params self.content_type = content_type def ConcealSecrets(self, conceal_func): """Conceal secret data in this request.""" if self.extra_headers.has_key('Authorization'): self.extra_headers['Authorization'] = conceal_func( self.extra_headers['Authorization']) def IsMatch(self, other_request): """Check to see if the other_request is equivalent to this request. Used to determine if a recording matches an incoming request so that a recorded response should be sent to the client. The matching is not exact, only the operation and URL are examined currently. Args: other_request: MockRequest The request which we want to check this (self) MockRequest against to see if they are equivalent. """ # More accurate matching logic will likely be required. return (self.operation == other_request.operation and self.uri == other_request.uri) def _ConstructFullUrlBase(uri, host=None, ssl=False): """Puts URL components into the form http(s)://full.host.strinf/uri/path Used to construct a roughly canonical URL so that URLs which begin with 'http://example.com/' can be compared to a uri of '/' when the host is set to 'example.com' If the uri contains 'http://host' already, the host and ssl parameters are ignored. Args: uri: str The path component of the URL, examples include '/' host: str (optional) The host name which should prepend the URL. Example: 'example.com' ssl: boolean (optional) If true, the returned URL will begin with https instead of http. Returns: String which has the form http(s)://example.com/uri/string/contents """ if uri.startswith('http'): return uri if ssl: return 'https://%s%s' % (host, uri) else: return 'http://%s%s' % (host, uri) class MockHttpResponse(object): """Returned from MockService crud methods as the server's response.""" def __init__(self, body=None, status=None, reason=None, headers=None): """Construct a mock HTTPResponse and set members. Args: body: str (optional) The HTTP body of the server's response. status: int (optional) reason: str (optional) headers: dict (optional) """ self.body = body self.status = status self.reason = reason self.headers = headers or {} def read(self): return self.body def getheader(self, header_name): return self.headers[header_name] gdata-2.0.18/samples/apps/marketplace_sample/atom/auth.py0000640036466300116100000000225712156622362023456 0ustar afshareng00000000000000#!/usr/bin/env python # # Copyright (C) 2009 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. # This module is used for version 2 of the Google Data APIs. __author__ = 'j.s@google.com (Jeff Scudder)' import base64 class BasicAuth(object): """Sets the Authorization header as defined in RFC1945""" def __init__(self, user_id, password): self.basic_cookie = base64.encodestring( '%s:%s' % (user_id, password)).strip() def modify_request(self, http_request): http_request.headers['Authorization'] = 'Basic %s' % self.basic_cookie ModifyRequest = modify_request class NoAuth(object): def modify_request(self, http_request): pass gdata-2.0.18/samples/apps/marketplace_sample/atom/http.py0000640036466300116100000003077712156622362023504 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright (C) 2008 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. """HttpClients in this module use httplib to make HTTP requests. This module make HTTP requests based on httplib, but there are environments in which an httplib based approach will not work (if running in Google App Engine for example). In those cases, higher level classes (like AtomService and GDataService) can swap out the HttpClient to transparently use a different mechanism for making HTTP requests. HttpClient: Contains a request method which performs an HTTP call to the server. ProxiedHttpClient: Contains a request method which connects to a proxy using settings stored in operating system environment variables then performs an HTTP call to the endpoint server. """ __author__ = 'api.jscudder (Jeff Scudder)' import types import os import httplib import atom.url import atom.http_interface import socket import base64 import atom.http_core ssl_imported = False ssl = None try: import ssl ssl_imported = True except ImportError: pass class ProxyError(atom.http_interface.Error): pass class TestConfigurationError(Exception): pass DEFAULT_CONTENT_TYPE = 'application/atom+xml' class HttpClient(atom.http_interface.GenericHttpClient): # Added to allow old v1 HttpClient objects to use the new # http_code.HttpClient. Used in unit tests to inject a mock client. v2_http_client = None def __init__(self, headers=None): self.debug = False self.headers = headers or {} def request(self, operation, url, data=None, headers=None): """Performs an HTTP call to the server, supports GET, POST, PUT, and DELETE. Usage example, perform and HTTP GET on http://www.google.com/: import atom.http client = atom.http.HttpClient() http_response = client.request('GET', 'http://www.google.com/') Args: operation: str The HTTP operation to be performed. This is usually one of 'GET', 'POST', 'PUT', or 'DELETE' data: filestream, list of parts, or other object which can be converted to a string. Should be set to None when performing a GET or DELETE. If data is a file-like object which can be read, this method will read a chunk of 100K bytes at a time and send them. If the data is a list of parts to be sent, each part will be evaluated and sent. url: The full URL to which the request should be sent. Can be a string or atom.url.Url. headers: dict of strings. HTTP headers which should be sent in the request. """ all_headers = self.headers.copy() if headers: all_headers.update(headers) # If the list of headers does not include a Content-Length, attempt to # calculate it based on the data object. if data and 'Content-Length' not in all_headers: if isinstance(data, types.StringTypes): all_headers['Content-Length'] = str(len(data)) else: raise atom.http_interface.ContentLengthRequired('Unable to calculate ' 'the length of the data parameter. Specify a value for ' 'Content-Length') # Set the content type to the default value if none was set. if 'Content-Type' not in all_headers: all_headers['Content-Type'] = DEFAULT_CONTENT_TYPE if self.v2_http_client is not None: http_request = atom.http_core.HttpRequest(method=operation) atom.http_core.Uri.parse_uri(str(url)).modify_request(http_request) http_request.headers = all_headers if data: http_request._body_parts.append(data) return self.v2_http_client.request(http_request=http_request) if not isinstance(url, atom.url.Url): if isinstance(url, types.StringTypes): url = atom.url.parse_url(url) else: raise atom.http_interface.UnparsableUrlObject('Unable to parse url ' 'parameter because it was not a string or atom.url.Url') connection = self._prepare_connection(url, all_headers) if self.debug: connection.debuglevel = 1 connection.putrequest(operation, self._get_access_url(url), skip_host=True) if url.port is not None: connection.putheader('Host', '%s:%s' % (url.host, url.port)) else: connection.putheader('Host', url.host) # Overcome a bug in Python 2.4 and 2.5 # httplib.HTTPConnection.putrequest adding # HTTP request header 'Host: www.google.com:443' instead of # 'Host: www.google.com', and thus resulting the error message # 'Token invalid - AuthSub token has wrong scope' in the HTTP response. if (url.protocol == 'https' and int(url.port or 443) == 443 and hasattr(connection, '_buffer') and isinstance(connection._buffer, list)): header_line = 'Host: %s:443' % url.host replacement_header_line = 'Host: %s' % url.host try: connection._buffer[connection._buffer.index(header_line)] = ( replacement_header_line) except ValueError: # header_line missing from connection._buffer pass # Send the HTTP headers. for header_name in all_headers: connection.putheader(header_name, all_headers[header_name]) connection.endheaders() # If there is data, send it in the request. if data: if isinstance(data, list): for data_part in data: _send_data_part(data_part, connection) else: _send_data_part(data, connection) # Return the HTTP Response from the server. return connection.getresponse() def _prepare_connection(self, url, headers): if not isinstance(url, atom.url.Url): if isinstance(url, types.StringTypes): url = atom.url.parse_url(url) else: raise atom.http_interface.UnparsableUrlObject('Unable to parse url ' 'parameter because it was not a string or atom.url.Url') if url.protocol == 'https': if not url.port: return httplib.HTTPSConnection(url.host) return httplib.HTTPSConnection(url.host, int(url.port)) else: if not url.port: return httplib.HTTPConnection(url.host) return httplib.HTTPConnection(url.host, int(url.port)) def _get_access_url(self, url): return url.to_string() class ProxiedHttpClient(HttpClient): """Performs an HTTP request through a proxy. The proxy settings are obtained from enviroment variables. The URL of the proxy server is assumed to be stored in the environment variables 'https_proxy' and 'http_proxy' respectively. If the proxy server requires a Basic Auth authorization header, the username and password are expected to be in the 'proxy-username' or 'proxy_username' variable and the 'proxy-password' or 'proxy_password' variable, or in 'http_proxy' or 'https_proxy' as "protocol://[username:password@]host:port". After connecting to the proxy server, the request is completed as in HttpClient.request. """ def _prepare_connection(self, url, headers): proxy_settings = os.environ.get('%s_proxy' % url.protocol) if not proxy_settings: # The request was HTTP or HTTPS, but there was no appropriate proxy set. return HttpClient._prepare_connection(self, url, headers) else: proxy_auth = _get_proxy_auth(proxy_settings) proxy_netloc = _get_proxy_net_location(proxy_settings) if url.protocol == 'https': # Set any proxy auth headers if proxy_auth: proxy_auth = 'Proxy-authorization: %s' % proxy_auth # Construct the proxy connect command. port = url.port if not port: port = '443' proxy_connect = 'CONNECT %s:%s HTTP/1.0\r\n' % (url.host, port) # Set the user agent to send to the proxy if headers and 'User-Agent' in headers: user_agent = 'User-Agent: %s\r\n' % (headers['User-Agent']) else: user_agent = 'User-Agent: python\r\n' proxy_pieces = '%s%s%s\r\n' % (proxy_connect, proxy_auth, user_agent) # Find the proxy host and port. proxy_url = atom.url.parse_url(proxy_netloc) if not proxy_url.port: proxy_url.port = '80' # Connect to the proxy server, very simple recv and error checking p_sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM) p_sock.connect((proxy_url.host, int(proxy_url.port))) p_sock.sendall(proxy_pieces) response = '' # Wait for the full response. while response.find("\r\n\r\n") == -1: response += p_sock.recv(8192) p_status = response.split()[1] if p_status != str(200): raise ProxyError('Error status=%s' % str(p_status)) # Trivial setup for ssl socket. sslobj = None if ssl_imported: sslobj = ssl.wrap_socket(p_sock, None, None) else: sock_ssl = socket.ssl(p_sock, None, None) sslobj = httplib.FakeSocket(p_sock, sock_ssl) # Initalize httplib and replace with the proxy socket. connection = httplib.HTTPConnection(proxy_url.host) connection.sock = sslobj return connection else: # If protocol was not https. # Find the proxy host and port. proxy_url = atom.url.parse_url(proxy_netloc) if not proxy_url.port: proxy_url.port = '80' if proxy_auth: headers['Proxy-Authorization'] = proxy_auth.strip() return httplib.HTTPConnection(proxy_url.host, int(proxy_url.port)) def _get_access_url(self, url): return url.to_string() def _get_proxy_auth(proxy_settings): """Returns proxy authentication string for header. Will check environment variables for proxy authentication info, starting with proxy(_/-)username and proxy(_/-)password before checking the given proxy_settings for a [protocol://]username:password@host[:port] string. Args: proxy_settings: String from http_proxy or https_proxy environment variable. Returns: Authentication string for proxy, or empty string if no proxy username was found. """ proxy_username = None proxy_password = None proxy_username = os.environ.get('proxy-username') if not proxy_username: proxy_username = os.environ.get('proxy_username') proxy_password = os.environ.get('proxy-password') if not proxy_password: proxy_password = os.environ.get('proxy_password') if not proxy_username: if '@' in proxy_settings: protocol_and_proxy_auth = proxy_settings.split('@')[0].split(':') if len(protocol_and_proxy_auth) == 3: # 3 elements means we have [, //, ] proxy_username = protocol_and_proxy_auth[1].lstrip('/') proxy_password = protocol_and_proxy_auth[2] elif len(protocol_and_proxy_auth) == 2: # 2 elements means we have [, ] proxy_username = protocol_and_proxy_auth[0] proxy_password = protocol_and_proxy_auth[1] if proxy_username: user_auth = base64.encodestring('%s:%s' % (proxy_username, proxy_password)) return 'Basic %s\r\n' % (user_auth.strip()) else: return '' def _get_proxy_net_location(proxy_settings): """Returns proxy host and port. Args: proxy_settings: String from http_proxy or https_proxy environment variable. Must be in the form of protocol://[username:password@]host:port Returns: String in the form of protocol://host:port """ if '@' in proxy_settings: protocol = proxy_settings.split(':')[0] netloc = proxy_settings.split('@')[1] return '%s://%s' % (protocol, netloc) else: return proxy_settings def _send_data_part(data, connection): if isinstance(data, types.StringTypes): connection.send(data) return # Check to see if data is a file-like object that has a read method. elif hasattr(data, 'read'): # Read the file and send it a chunk at a time. while 1: binarydata = data.read(100000) if binarydata == '': break connection.send(binarydata) return else: # The data object was not a file. # Try to convert to a string and send the data. connection.send(str(data)) return gdata-2.0.18/samples/apps/marketplace_sample/atom/url.py0000640036466300116100000001026512156622362023315 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright (C) 2008 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. __author__ = 'api.jscudder (Jeff Scudder)' import urlparse import urllib DEFAULT_PROTOCOL = 'http' DEFAULT_PORT = 80 def parse_url(url_string): """Creates a Url object which corresponds to the URL string. This method can accept partial URLs, but it will leave missing members of the Url unset. """ parts = urlparse.urlparse(url_string) url = Url() if parts[0]: url.protocol = parts[0] if parts[1]: host_parts = parts[1].split(':') if host_parts[0]: url.host = host_parts[0] if len(host_parts) > 1: url.port = host_parts[1] if parts[2]: url.path = parts[2] if parts[4]: param_pairs = parts[4].split('&') for pair in param_pairs: pair_parts = pair.split('=') if len(pair_parts) > 1: url.params[urllib.unquote_plus(pair_parts[0])] = ( urllib.unquote_plus(pair_parts[1])) elif len(pair_parts) == 1: url.params[urllib.unquote_plus(pair_parts[0])] = None return url class Url(object): """Represents a URL and implements comparison logic. URL strings which are not identical can still be equivalent, so this object provides a better interface for comparing and manipulating URLs than strings. URL parameters are represented as a dictionary of strings, and defaults are used for the protocol (http) and port (80) if not provided. """ def __init__(self, protocol=None, host=None, port=None, path=None, params=None): self.protocol = protocol self.host = host self.port = port self.path = path self.params = params or {} def to_string(self): url_parts = ['', '', '', '', '', ''] if self.protocol: url_parts[0] = self.protocol if self.host: if self.port: url_parts[1] = ':'.join((self.host, str(self.port))) else: url_parts[1] = self.host if self.path: url_parts[2] = self.path if self.params: url_parts[4] = self.get_param_string() return urlparse.urlunparse(url_parts) def get_param_string(self): param_pairs = [] for key, value in self.params.iteritems(): param_pairs.append('='.join((urllib.quote_plus(key), urllib.quote_plus(str(value))))) return '&'.join(param_pairs) def get_request_uri(self): """Returns the path with the parameters escaped and appended.""" param_string = self.get_param_string() if param_string: return '?'.join([self.path, param_string]) else: return self.path def __cmp__(self, other): if not isinstance(other, Url): return cmp(self.to_string(), str(other)) difference = 0 # Compare the protocol if self.protocol and other.protocol: difference = cmp(self.protocol, other.protocol) elif self.protocol and not other.protocol: difference = cmp(self.protocol, DEFAULT_PROTOCOL) elif not self.protocol and other.protocol: difference = cmp(DEFAULT_PROTOCOL, other.protocol) if difference != 0: return difference # Compare the host difference = cmp(self.host, other.host) if difference != 0: return difference # Compare the port if self.port and other.port: difference = cmp(self.port, other.port) elif self.port and not other.port: difference = cmp(self.port, DEFAULT_PORT) elif not self.port and other.port: difference = cmp(DEFAULT_PORT, other.port) if difference != 0: return difference # Compare the path difference = cmp(self.path, other.path) if difference != 0: return difference # Compare the parameters return cmp(self.params, other.params) def __str__(self): return self.to_string() gdata-2.0.18/samples/apps/marketplace_sample/atom/http_interface.py0000640036466300116100000001207612156622362025514 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright (C) 2008 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. """This module provides a common interface for all HTTP requests. HttpResponse: Represents the server's response to an HTTP request. Provides an interface identical to httplib.HTTPResponse which is the response expected from higher level classes which use HttpClient.request. GenericHttpClient: Provides an interface (superclass) for an object responsible for making HTTP requests. Subclasses of this object are used in AtomService and GDataService to make requests to the server. By changing the http_client member object, the AtomService is able to make HTTP requests using different logic (for example, when running on Google App Engine, the http_client makes requests using the App Engine urlfetch API). """ __author__ = 'api.jscudder (Jeff Scudder)' import StringIO USER_AGENT = '%s GData-Python/2.0.15' class Error(Exception): pass class UnparsableUrlObject(Error): pass class ContentLengthRequired(Error): pass class HttpResponse(object): def __init__(self, body=None, status=None, reason=None, headers=None): """Constructor for an HttpResponse object. HttpResponse represents the server's response to an HTTP request from the client. The HttpClient.request method returns a httplib.HTTPResponse object and this HttpResponse class is designed to mirror the interface exposed by httplib.HTTPResponse. Args: body: A file like object, with a read() method. The body could also be a string, and the constructor will wrap it so that HttpResponse.read(self) will return the full string. status: The HTTP status code as an int. Example: 200, 201, 404. reason: The HTTP status message which follows the code. Example: OK, Created, Not Found headers: A dictionary containing the HTTP headers in the server's response. A common header in the response is Content-Length. """ if body: if hasattr(body, 'read'): self._body = body else: self._body = StringIO.StringIO(body) else: self._body = None if status is not None: self.status = int(status) else: self.status = None self.reason = reason self._headers = headers or {} def getheader(self, name, default=None): if name in self._headers: return self._headers[name] else: return default def read(self, amt=None): if not amt: return self._body.read() else: return self._body.read(amt) class GenericHttpClient(object): debug = False def __init__(self, http_client, headers=None): """ Args: http_client: An object which provides a request method to make an HTTP request. The request method in GenericHttpClient performs a call-through to the contained HTTP client object. headers: A dictionary containing HTTP headers which should be included in every HTTP request. Common persistent headers include 'User-Agent'. """ self.http_client = http_client self.headers = headers or {} def request(self, operation, url, data=None, headers=None): all_headers = self.headers.copy() if headers: all_headers.update(headers) return self.http_client.request(operation, url, data=data, headers=all_headers) def get(self, url, headers=None): return self.request('GET', url, headers=headers) def post(self, url, data, headers=None): return self.request('POST', url, data=data, headers=headers) def put(self, url, data, headers=None): return self.request('PUT', url, data=data, headers=headers) def delete(self, url, headers=None): return self.request('DELETE', url, headers=headers) class GenericToken(object): """Represents an Authorization token to be added to HTTP requests. Some Authorization headers included calculated fields (digital signatures for example) which are based on the parameters of the HTTP request. Therefore the token is responsible for signing the request and adding the Authorization header. """ def perform_request(self, http_client, operation, url, data=None, headers=None): """For the GenericToken, no Authorization token is set.""" return http_client.request(operation, url, data=data, headers=headers) def valid_for_scope(self, url): """Tells the caller if the token authorizes access to the desired URL. Since the generic token doesn't add an auth header, it is not valid for any scope. """ return False gdata-2.0.18/samples/apps/marketplace_sample/atom/token_store.py0000640036466300116100000000772012156622362025051 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright (C) 2008 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. """This module provides a TokenStore class which is designed to manage auth tokens required for different services. Each token is valid for a set of scopes which is the start of a URL. An HTTP client will use a token store to find a valid Authorization header to send in requests to the specified URL. If the HTTP client determines that a token has expired or been revoked, it can remove the token from the store so that it will not be used in future requests. """ __author__ = 'api.jscudder (Jeff Scudder)' import atom.http_interface import atom.url SCOPE_ALL = 'http' class TokenStore(object): """Manages Authorization tokens which will be sent in HTTP headers.""" def __init__(self, scoped_tokens=None): self._tokens = scoped_tokens or {} def add_token(self, token): """Adds a new token to the store (replaces tokens with the same scope). Args: token: A subclass of http_interface.GenericToken. The token object is responsible for adding the Authorization header to the HTTP request. The scopes defined in the token are used to determine if the token is valid for a requested scope when find_token is called. Returns: True if the token was added, False if the token was not added becase no scopes were provided. """ if not hasattr(token, 'scopes') or not token.scopes: return False for scope in token.scopes: self._tokens[str(scope)] = token return True def find_token(self, url): """Selects an Authorization header token which can be used for the URL. Args: url: str or atom.url.Url or a list containing the same. The URL which is going to be requested. All tokens are examined to see if any scopes begin match the beginning of the URL. The first match found is returned. Returns: The token object which should execute the HTTP request. If there was no token for the url (the url did not begin with any of the token scopes available), then the atom.http_interface.GenericToken will be returned because the GenericToken calls through to the http client without adding an Authorization header. """ if url is None: return None if isinstance(url, (str, unicode)): url = atom.url.parse_url(url) if url in self._tokens: token = self._tokens[url] if token.valid_for_scope(url): return token else: del self._tokens[url] for scope, token in self._tokens.iteritems(): if token.valid_for_scope(url): return token return atom.http_interface.GenericToken() def remove_token(self, token): """Removes the token from the token_store. This method is used when a token is determined to be invalid. If the token was found by find_token, but resulted in a 401 or 403 error stating that the token was invlid, then the token should be removed to prevent future use. Returns: True if a token was found and then removed from the token store. False if the token was not in the TokenStore. """ token_found = False scopes_to_delete = [] for scope, stored_token in self._tokens.iteritems(): if stored_token == token: scopes_to_delete.append(scope) token_found = True for scope in scopes_to_delete: del self._tokens[scope] return token_found def remove_all_tokens(self): self._tokens = {} gdata-2.0.18/samples/apps/marketplace_sample/atom/http_core.py0000640036466300116100000004662612156622362024514 0ustar afshareng00000000000000#!/usr/bin/env python # # Copyright (C) 2009 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. # This module is used for version 2 of the Google Data APIs. # TODO: add proxy handling. __author__ = 'j.s@google.com (Jeff Scudder)' import os import StringIO import urlparse import urllib import httplib ssl = None try: import ssl except ImportError: pass class Error(Exception): pass class UnknownSize(Error): pass class ProxyError(Error): pass MIME_BOUNDARY = 'END_OF_PART' def get_headers(http_response): """Retrieves all HTTP headers from an HTTP response from the server. This method is provided for backwards compatibility for Python2.2 and 2.3. The httplib.HTTPResponse object in 2.2 and 2.3 does not have a getheaders method so this function will use getheaders if available, but if not it will retrieve a few using getheader. """ if hasattr(http_response, 'getheaders'): return http_response.getheaders() else: headers = [] for header in ( 'location', 'content-type', 'content-length', 'age', 'allow', 'cache-control', 'content-location', 'content-encoding', 'date', 'etag', 'expires', 'last-modified', 'pragma', 'server', 'set-cookie', 'transfer-encoding', 'vary', 'via', 'warning', 'www-authenticate', 'gdata-version'): value = http_response.getheader(header, None) if value is not None: headers.append((header, value)) return headers class HttpRequest(object): """Contains all of the parameters for an HTTP 1.1 request. The HTTP headers are represented by a dictionary, and it is the responsibility of the user to ensure that duplicate field names are combined into one header value according to the rules in section 4.2 of RFC 2616. """ method = None uri = None def __init__(self, uri=None, method=None, headers=None): """Construct an HTTP request. Args: uri: The full path or partial path as a Uri object or a string. method: The HTTP method for the request, examples include 'GET', 'POST', etc. headers: dict of strings The HTTP headers to include in the request. """ self.headers = headers or {} self._body_parts = [] if method is not None: self.method = method if isinstance(uri, (str, unicode)): uri = Uri.parse_uri(uri) self.uri = uri or Uri() def add_body_part(self, data, mime_type, size=None): """Adds data to the HTTP request body. If more than one part is added, this is assumed to be a mime-multipart request. This method is designed to create MIME 1.0 requests as specified in RFC 1341. Args: data: str or a file-like object containing a part of the request body. mime_type: str The MIME type describing the data size: int Required if the data is a file like object. If the data is a string, the size is calculated so this parameter is ignored. """ if isinstance(data, str): size = len(data) if size is None: # TODO: support chunked transfer if some of the body is of unknown size. raise UnknownSize('Each part of the body must have a known size.') if 'Content-Length' in self.headers: content_length = int(self.headers['Content-Length']) else: content_length = 0 # If this is the first part added to the body, then this is not a multipart # request. if len(self._body_parts) == 0: self.headers['Content-Type'] = mime_type content_length = size self._body_parts.append(data) elif len(self._body_parts) == 1: # This is the first member in a mime-multipart request, so change the # _body_parts list to indicate a multipart payload. self._body_parts.insert(0, 'Media multipart posting') boundary_string = '\r\n--%s\r\n' % (MIME_BOUNDARY,) content_length += len(boundary_string) + size self._body_parts.insert(1, boundary_string) content_length += len('Media multipart posting') # Put the content type of the first part of the body into the multipart # payload. original_type_string = 'Content-Type: %s\r\n\r\n' % ( self.headers['Content-Type'],) self._body_parts.insert(2, original_type_string) content_length += len(original_type_string) boundary_string = '\r\n--%s\r\n' % (MIME_BOUNDARY,) self._body_parts.append(boundary_string) content_length += len(boundary_string) # Change the headers to indicate this is now a mime multipart request. self.headers['Content-Type'] = 'multipart/related; boundary="%s"' % ( MIME_BOUNDARY,) self.headers['MIME-version'] = '1.0' # Include the mime type of this part. type_string = 'Content-Type: %s\r\n\r\n' % (mime_type) self._body_parts.append(type_string) content_length += len(type_string) self._body_parts.append(data) ending_boundary_string = '\r\n--%s--' % (MIME_BOUNDARY,) self._body_parts.append(ending_boundary_string) content_length += len(ending_boundary_string) else: # This is a mime multipart request. boundary_string = '\r\n--%s\r\n' % (MIME_BOUNDARY,) self._body_parts.insert(-1, boundary_string) content_length += len(boundary_string) + size # Include the mime type of this part. type_string = 'Content-Type: %s\r\n\r\n' % (mime_type) self._body_parts.insert(-1, type_string) content_length += len(type_string) self._body_parts.insert(-1, data) self.headers['Content-Length'] = str(content_length) # I could add an "append_to_body_part" method as well. AddBodyPart = add_body_part def add_form_inputs(self, form_data, mime_type='application/x-www-form-urlencoded'): """Form-encodes and adds data to the request body. Args: form_data: dict or sequnce or two member tuples which contains the form keys and values. mime_type: str The MIME type of the form data being sent. Defaults to 'application/x-www-form-urlencoded'. """ body = urllib.urlencode(form_data) self.add_body_part(body, mime_type) AddFormInputs = add_form_inputs def _copy(self): """Creates a deep copy of this request.""" copied_uri = Uri(self.uri.scheme, self.uri.host, self.uri.port, self.uri.path, self.uri.query.copy()) new_request = HttpRequest(uri=copied_uri, method=self.method, headers=self.headers.copy()) new_request._body_parts = self._body_parts[:] return new_request def _dump(self): """Converts to a printable string for debugging purposes. In order to preserve the request, it does not read from file-like objects in the body. """ output = 'HTTP Request\n method: %s\n url: %s\n headers:\n' % ( self.method, str(self.uri)) for header, value in self.headers.iteritems(): output += ' %s: %s\n' % (header, value) output += ' body sections:\n' i = 0 for part in self._body_parts: if isinstance(part, (str, unicode)): output += ' %s: %s\n' % (i, part) else: output += ' %s: \n' % i i += 1 return output def _apply_defaults(http_request): if http_request.uri.scheme is None: if http_request.uri.port == 443: http_request.uri.scheme = 'https' else: http_request.uri.scheme = 'http' class Uri(object): """A URI as used in HTTP 1.1""" scheme = None host = None port = None path = None def __init__(self, scheme=None, host=None, port=None, path=None, query=None): """Constructor for a URI. Args: scheme: str This is usually 'http' or 'https'. host: str The host name or IP address of the desired server. post: int The server's port number. path: str The path of the resource following the host. This begins with a /, example: '/calendar/feeds/default/allcalendars/full' query: dict of strings The URL query parameters. The keys and values are both escaped so this dict should contain the unescaped values. For example {'my key': 'val', 'second': '!!!'} will become '?my+key=val&second=%21%21%21' which is appended to the path. """ self.query = query or {} if scheme is not None: self.scheme = scheme if host is not None: self.host = host if port is not None: self.port = port if path: self.path = path def _get_query_string(self): param_pairs = [] for key, value in self.query.iteritems(): param_pairs.append('='.join((urllib.quote_plus(key), urllib.quote_plus(str(value))))) return '&'.join(param_pairs) def _get_relative_path(self): """Returns the path with the query parameters escaped and appended.""" param_string = self._get_query_string() if self.path is None: path = '/' else: path = self.path if param_string: return '?'.join([path, param_string]) else: return path def _to_string(self): if self.scheme is None and self.port == 443: scheme = 'https' elif self.scheme is None: scheme = 'http' else: scheme = self.scheme if self.path is None: path = '/' else: path = self.path if self.port is None: return '%s://%s%s' % (scheme, self.host, self._get_relative_path()) else: return '%s://%s:%s%s' % (scheme, self.host, str(self.port), self._get_relative_path()) def __str__(self): return self._to_string() def modify_request(self, http_request=None): """Sets HTTP request components based on the URI.""" if http_request is None: http_request = HttpRequest() if http_request.uri is None: http_request.uri = Uri() # Determine the correct scheme. if self.scheme: http_request.uri.scheme = self.scheme if self.port: http_request.uri.port = self.port if self.host: http_request.uri.host = self.host # Set the relative uri path if self.path: http_request.uri.path = self.path if self.query: http_request.uri.query = self.query.copy() return http_request ModifyRequest = modify_request def parse_uri(uri_string): """Creates a Uri object which corresponds to the URI string. This method can accept partial URIs, but it will leave missing members of the Uri unset. """ parts = urlparse.urlparse(uri_string) uri = Uri() if parts[0]: uri.scheme = parts[0] if parts[1]: host_parts = parts[1].split(':') if host_parts[0]: uri.host = host_parts[0] if len(host_parts) > 1: uri.port = int(host_parts[1]) if parts[2]: uri.path = parts[2] if parts[4]: param_pairs = parts[4].split('&') for pair in param_pairs: pair_parts = pair.split('=') if len(pair_parts) > 1: uri.query[urllib.unquote_plus(pair_parts[0])] = ( urllib.unquote_plus(pair_parts[1])) elif len(pair_parts) == 1: uri.query[urllib.unquote_plus(pair_parts[0])] = None return uri parse_uri = staticmethod(parse_uri) ParseUri = parse_uri parse_uri = Uri.parse_uri ParseUri = Uri.parse_uri class HttpResponse(object): status = None reason = None _body = None def __init__(self, status=None, reason=None, headers=None, body=None): self._headers = headers or {} if status is not None: self.status = status if reason is not None: self.reason = reason if body is not None: if hasattr(body, 'read'): self._body = body else: self._body = StringIO.StringIO(body) def getheader(self, name, default=None): if name in self._headers: return self._headers[name] else: return default def getheaders(self): return self._headers def read(self, amt=None): if self._body is None: return None if not amt: return self._body.read() else: return self._body.read(amt) def _dump_response(http_response): """Converts to a string for printing debug messages. Does not read the body since that may consume the content. """ output = 'HttpResponse\n status: %s\n reason: %s\n headers:' % ( http_response.status, http_response.reason) headers = get_headers(http_response) if isinstance(headers, dict): for header, value in headers.iteritems(): output += ' %s: %s\n' % (header, value) else: for pair in headers: output += ' %s: %s\n' % (pair[0], pair[1]) return output class HttpClient(object): """Performs HTTP requests using httplib.""" debug = None def request(self, http_request): return self._http_request(http_request.method, http_request.uri, http_request.headers, http_request._body_parts) Request = request def _get_connection(self, uri, headers=None): """Opens a socket connection to the server to set up an HTTP request. Args: uri: The full URL for the request as a Uri object. headers: A dict of string pairs containing the HTTP headers for the request. """ connection = None if uri.scheme == 'https': if not uri.port: connection = httplib.HTTPSConnection(uri.host) else: connection = httplib.HTTPSConnection(uri.host, int(uri.port)) else: if not uri.port: connection = httplib.HTTPConnection(uri.host) else: connection = httplib.HTTPConnection(uri.host, int(uri.port)) return connection def _http_request(self, method, uri, headers=None, body_parts=None): """Makes an HTTP request using httplib. Args: method: str example: 'GET', 'POST', 'PUT', 'DELETE', etc. uri: str or atom.http_core.Uri headers: dict of strings mapping to strings which will be sent as HTTP headers in the request. body_parts: list of strings, objects with a read method, or objects which can be converted to strings using str. Each of these will be sent in order as the body of the HTTP request. """ if isinstance(uri, (str, unicode)): uri = Uri.parse_uri(uri) connection = self._get_connection(uri, headers=headers) if self.debug: connection.debuglevel = 1 if connection.host != uri.host: connection.putrequest(method, str(uri)) else: connection.putrequest(method, uri._get_relative_path()) # Overcome a bug in Python 2.4 and 2.5 # httplib.HTTPConnection.putrequest adding # HTTP request header 'Host: www.google.com:443' instead of # 'Host: www.google.com', and thus resulting the error message # 'Token invalid - AuthSub token has wrong scope' in the HTTP response. if (uri.scheme == 'https' and int(uri.port or 443) == 443 and hasattr(connection, '_buffer') and isinstance(connection._buffer, list)): header_line = 'Host: %s:443' % uri.host replacement_header_line = 'Host: %s' % uri.host try: connection._buffer[connection._buffer.index(header_line)] = ( replacement_header_line) except ValueError: # header_line missing from connection._buffer pass # Send the HTTP headers. for header_name, value in headers.iteritems(): connection.putheader(header_name, value) connection.endheaders() # If there is data, send it in the request. if body_parts and filter(lambda x: x != '', body_parts): for part in body_parts: _send_data_part(part, connection) # Return the HTTP Response from the server. return connection.getresponse() def _send_data_part(data, connection): if isinstance(data, (str, unicode)): # I might want to just allow str, not unicode. connection.send(data) return # Check to see if data is a file-like object that has a read method. elif hasattr(data, 'read'): # Read the file and send it a chunk at a time. while 1: binarydata = data.read(100000) if binarydata == '': break connection.send(binarydata) return else: # The data object was not a file. # Try to convert to a string and send the data. connection.send(str(data)) return class ProxiedHttpClient(HttpClient): def _get_connection(self, uri, headers=None): # Check to see if there are proxy settings required for this request. proxy = None if uri.scheme == 'https': proxy = os.environ.get('https_proxy') elif uri.scheme == 'http': proxy = os.environ.get('http_proxy') if not proxy: return HttpClient._get_connection(self, uri, headers=headers) # Now we have the URL of the appropriate proxy server. # Get a username and password for the proxy if required. proxy_auth = _get_proxy_auth() if uri.scheme == 'https': import socket if proxy_auth: proxy_auth = 'Proxy-authorization: %s' % proxy_auth # Construct the proxy connect command. port = uri.port if not port: port = 443 proxy_connect = 'CONNECT %s:%s HTTP/1.0\r\n' % (uri.host, port) # Set the user agent to send to the proxy user_agent = '' if headers and 'User-Agent' in headers: user_agent = 'User-Agent: %s\r\n' % (headers['User-Agent']) proxy_pieces = '%s%s%s\r\n' % (proxy_connect, proxy_auth, user_agent) # Find the proxy host and port. proxy_uri = Uri.parse_uri(proxy) if not proxy_uri.port: proxy_uri.port = '80' # Connect to the proxy server, very simple recv and error checking p_sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM) p_sock.connect((proxy_uri.host, int(proxy_uri.port))) p_sock.sendall(proxy_pieces) response = '' # Wait for the full response. while response.find("\r\n\r\n") == -1: response += p_sock.recv(8192) p_status = response.split()[1] if p_status != str(200): raise ProxyError('Error status=%s' % str(p_status)) # Trivial setup for ssl socket. sslobj = None if ssl is not None: sslobj = ssl.wrap_socket(p_sock, None, None) else: sock_ssl = socket.ssl(p_sock, None, Nonesock_) sslobj = httplib.FakeSocket(p_sock, sock_ssl) # Initalize httplib and replace with the proxy socket. connection = httplib.HTTPConnection(proxy_uri.host) connection.sock = sslobj return connection elif uri.scheme == 'http': proxy_uri = Uri.parse_uri(proxy) if not proxy_uri.port: proxy_uri.port = '80' if proxy_auth: headers['Proxy-Authorization'] = proxy_auth.strip() return httplib.HTTPConnection(proxy_uri.host, int(proxy_uri.port)) return None def _get_proxy_auth(): import base64 proxy_username = os.environ.get('proxy-username') if not proxy_username: proxy_username = os.environ.get('proxy_username') proxy_password = os.environ.get('proxy-password') if not proxy_password: proxy_password = os.environ.get('proxy_password') if proxy_username: user_auth = base64.b64encode('%s:%s' % (proxy_username, proxy_password)) return 'Basic %s\r\n' % (user_auth.strip()) else: return '' gdata-2.0.18/samples/apps/marketplace_sample/atom/service.py0000750036466300116100000007037712156622362024167 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright (C) 2006, 2007, 2008 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. """AtomService provides CRUD ops. in line with the Atom Publishing Protocol. AtomService: Encapsulates the ability to perform insert, update and delete operations with the Atom Publishing Protocol on which GData is based. An instance can perform query, insertion, deletion, and update. HttpRequest: Function that performs a GET, POST, PUT, or DELETE HTTP request to the specified end point. An AtomService object or a subclass can be used to specify information about the request. """ __author__ = 'api.jscudder (Jeff Scudder)' import atom.http_interface import atom.url import atom.http import atom.token_store import os import httplib import urllib import re import base64 import socket import warnings try: from xml.etree import cElementTree as ElementTree except ImportError: try: import cElementTree as ElementTree except ImportError: try: from xml.etree import ElementTree except ImportError: from elementtree import ElementTree import atom class AtomService(object): """Performs Atom Publishing Protocol CRUD operations. The AtomService contains methods to perform HTTP CRUD operations. """ # Default values for members port = 80 ssl = False # Set the current_token to force the AtomService to use this token # instead of searching for an appropriate token in the token_store. current_token = None auto_store_tokens = True auto_set_current_token = True def _get_override_token(self): return self.current_token def _set_override_token(self, token): self.current_token = token override_token = property(_get_override_token, _set_override_token) #@atom.v1_deprecated('Please use atom.client.AtomPubClient instead.') def __init__(self, server=None, additional_headers=None, application_name='', http_client=None, token_store=None): """Creates a new AtomService client. Args: server: string (optional) The start of a URL for the server to which all operations should be directed. Example: 'www.google.com' additional_headers: dict (optional) Any additional HTTP headers which should be included with CRUD operations. http_client: An object responsible for making HTTP requests using a request method. If none is provided, a new instance of atom.http.ProxiedHttpClient will be used. token_store: Keeps a collection of authorization tokens which can be applied to requests for a specific URLs. Critical methods are find_token based on a URL (atom.url.Url or a string), add_token, and remove_token. """ self.http_client = http_client or atom.http.ProxiedHttpClient() self.token_store = token_store or atom.token_store.TokenStore() self.server = server self.additional_headers = additional_headers or {} self.additional_headers['User-Agent'] = atom.http_interface.USER_AGENT % ( application_name,) # If debug is True, the HTTPConnection will display debug information self._set_debug(False) __init__ = atom.v1_deprecated( 'Please use atom.client.AtomPubClient instead.')( __init__) def _get_debug(self): return self.http_client.debug def _set_debug(self, value): self.http_client.debug = value debug = property(_get_debug, _set_debug, doc='If True, HTTP debug information is printed.') def use_basic_auth(self, username, password, scopes=None): if username is not None and password is not None: if scopes is None: scopes = [atom.token_store.SCOPE_ALL] base_64_string = base64.encodestring('%s:%s' % (username, password)) token = BasicAuthToken('Basic %s' % base_64_string.strip(), scopes=[atom.token_store.SCOPE_ALL]) if self.auto_set_current_token: self.current_token = token if self.auto_store_tokens: return self.token_store.add_token(token) return True return False def UseBasicAuth(self, username, password, for_proxy=False): """Sets an Authenticaiton: Basic HTTP header containing plaintext. Deprecated, use use_basic_auth instead. The username and password are base64 encoded and added to an HTTP header which will be included in each request. Note that your username and password are sent in plaintext. Args: username: str password: str """ self.use_basic_auth(username, password) #@atom.v1_deprecated('Please use atom.client.AtomPubClient for requests.') def request(self, operation, url, data=None, headers=None, url_params=None): if isinstance(url, (str, unicode)): if url.startswith('http:') and self.ssl: # Force all requests to be https if self.ssl is True. url = atom.url.parse_url('https:' + url[5:]) elif not url.startswith('http') and self.ssl: url = atom.url.parse_url('https://%s%s' % (self.server, url)) elif not url.startswith('http'): url = atom.url.parse_url('http://%s%s' % (self.server, url)) else: url = atom.url.parse_url(url) if url_params: for name, value in url_params.iteritems(): url.params[name] = value all_headers = self.additional_headers.copy() if headers: all_headers.update(headers) # If the list of headers does not include a Content-Length, attempt to # calculate it based on the data object. if data and 'Content-Length' not in all_headers: content_length = CalculateDataLength(data) if content_length: all_headers['Content-Length'] = str(content_length) # Find an Authorization token for this URL if one is available. if self.override_token: auth_token = self.override_token else: auth_token = self.token_store.find_token(url) return auth_token.perform_request(self.http_client, operation, url, data=data, headers=all_headers) request = atom.v1_deprecated( 'Please use atom.client.AtomPubClient for requests.')( request) # CRUD operations def Get(self, uri, extra_headers=None, url_params=None, escape_params=True): """Query the APP server with the given URI The uri is the portion of the URI after the server value (server example: 'www.google.com'). Example use: To perform a query against Google Base, set the server to 'base.google.com' and set the uri to '/base/feeds/...', where ... is your query. For example, to find snippets for all digital cameras uri should be set to: '/base/feeds/snippets?bq=digital+camera' Args: uri: string The query in the form of a URI. Example: '/base/feeds/snippets?bq=digital+camera'. extra_headers: dicty (optional) Extra HTTP headers to be included in the GET request. These headers are in addition to those stored in the client's additional_headers property. The client automatically sets the Content-Type and Authorization headers. url_params: dict (optional) Additional URL parameters to be included in the query. These are translated into query arguments in the form '&dict_key=value&...'. Example: {'max-results': '250'} becomes &max-results=250 escape_params: boolean (optional) If false, the calling code has already ensured that the query will form a valid URL (all reserved characters have been escaped). If true, this method will escape the query and any URL parameters provided. Returns: httplib.HTTPResponse The server's response to the GET request. """ return self.request('GET', uri, data=None, headers=extra_headers, url_params=url_params) def Post(self, data, uri, extra_headers=None, url_params=None, escape_params=True, content_type='application/atom+xml'): """Insert data into an APP server at the given URI. Args: data: string, ElementTree._Element, or something with a __str__ method The XML to be sent to the uri. uri: string The location (feed) to which the data should be inserted. Example: '/base/feeds/items'. extra_headers: dict (optional) HTTP headers which are to be included. The client automatically sets the Content-Type, Authorization, and Content-Length headers. url_params: dict (optional) Additional URL parameters to be included in the URI. These are translated into query arguments in the form '&dict_key=value&...'. Example: {'max-results': '250'} becomes &max-results=250 escape_params: boolean (optional) If false, the calling code has already ensured that the query will form a valid URL (all reserved characters have been escaped). If true, this method will escape the query and any URL parameters provided. Returns: httplib.HTTPResponse Server's response to the POST request. """ if extra_headers is None: extra_headers = {} if content_type: extra_headers['Content-Type'] = content_type return self.request('POST', uri, data=data, headers=extra_headers, url_params=url_params) def Put(self, data, uri, extra_headers=None, url_params=None, escape_params=True, content_type='application/atom+xml'): """Updates an entry at the given URI. Args: data: string, ElementTree._Element, or xml_wrapper.ElementWrapper The XML containing the updated data. uri: string A URI indicating entry to which the update will be applied. Example: '/base/feeds/items/ITEM-ID' extra_headers: dict (optional) HTTP headers which are to be included. The client automatically sets the Content-Type, Authorization, and Content-Length headers. url_params: dict (optional) Additional URL parameters to be included in the URI. These are translated into query arguments in the form '&dict_key=value&...'. Example: {'max-results': '250'} becomes &max-results=250 escape_params: boolean (optional) If false, the calling code has already ensured that the query will form a valid URL (all reserved characters have been escaped). If true, this method will escape the query and any URL parameters provided. Returns: httplib.HTTPResponse Server's response to the PUT request. """ if extra_headers is None: extra_headers = {} if content_type: extra_headers['Content-Type'] = content_type return self.request('PUT', uri, data=data, headers=extra_headers, url_params=url_params) def Delete(self, uri, extra_headers=None, url_params=None, escape_params=True): """Deletes the entry at the given URI. Args: uri: string The URI of the entry to be deleted. Example: '/base/feeds/items/ITEM-ID' extra_headers: dict (optional) HTTP headers which are to be included. The client automatically sets the Content-Type and Authorization headers. url_params: dict (optional) Additional URL parameters to be included in the URI. These are translated into query arguments in the form '&dict_key=value&...'. Example: {'max-results': '250'} becomes &max-results=250 escape_params: boolean (optional) If false, the calling code has already ensured that the query will form a valid URL (all reserved characters have been escaped). If true, this method will escape the query and any URL parameters provided. Returns: httplib.HTTPResponse Server's response to the DELETE request. """ return self.request('DELETE', uri, data=None, headers=extra_headers, url_params=url_params) class BasicAuthToken(atom.http_interface.GenericToken): def __init__(self, auth_header, scopes=None): """Creates a token used to add Basic Auth headers to HTTP requests. Args: auth_header: str The value for the Authorization header. scopes: list of str or atom.url.Url specifying the beginnings of URLs for which this token can be used. For example, if scopes contains 'http://example.com/foo', then this token can be used for a request to 'http://example.com/foo/bar' but it cannot be used for a request to 'http://example.com/baz' """ self.auth_header = auth_header self.scopes = scopes or [] def perform_request(self, http_client, operation, url, data=None, headers=None): """Sets the Authorization header to the basic auth string.""" if headers is None: headers = {'Authorization':self.auth_header} else: headers['Authorization'] = self.auth_header return http_client.request(operation, url, data=data, headers=headers) def __str__(self): return self.auth_header def valid_for_scope(self, url): """Tells the caller if the token authorizes access to the desired URL. """ if isinstance(url, (str, unicode)): url = atom.url.parse_url(url) for scope in self.scopes: if scope == atom.token_store.SCOPE_ALL: return True if isinstance(scope, (str, unicode)): scope = atom.url.parse_url(scope) if scope == url: return True # Check the host and the path, but ignore the port and protocol. elif scope.host == url.host and not scope.path: return True elif scope.host == url.host and scope.path and not url.path: continue elif scope.host == url.host and url.path.startswith(scope.path): return True return False def PrepareConnection(service, full_uri): """Opens a connection to the server based on the full URI. This method is deprecated, instead use atom.http.HttpClient.request. Examines the target URI and the proxy settings, which are set as environment variables, to open a connection with the server. This connection is used to make an HTTP request. Args: service: atom.AtomService or a subclass. It must have a server string which represents the server host to which the request should be made. It may also have a dictionary of additional_headers to send in the HTTP request. full_uri: str Which is the target relative (lacks protocol and host) or absolute URL to be opened. Example: 'https://www.google.com/accounts/ClientLogin' or 'base/feeds/snippets' where the server is set to www.google.com. Returns: A tuple containing the httplib.HTTPConnection and the full_uri for the request. """ deprecation('calling deprecated function PrepareConnection') (server, port, ssl, partial_uri) = ProcessUrl(service, full_uri) if ssl: # destination is https proxy = os.environ.get('https_proxy') if proxy: (p_server, p_port, p_ssl, p_uri) = ProcessUrl(service, proxy, True) proxy_username = os.environ.get('proxy-username') if not proxy_username: proxy_username = os.environ.get('proxy_username') proxy_password = os.environ.get('proxy-password') if not proxy_password: proxy_password = os.environ.get('proxy_password') if proxy_username: user_auth = base64.encodestring('%s:%s' % (proxy_username, proxy_password)) proxy_authorization = ('Proxy-authorization: Basic %s\r\n' % ( user_auth.strip())) else: proxy_authorization = '' proxy_connect = 'CONNECT %s:%s HTTP/1.0\r\n' % (server, port) user_agent = 'User-Agent: %s\r\n' % ( service.additional_headers['User-Agent']) proxy_pieces = (proxy_connect + proxy_authorization + user_agent + '\r\n') #now connect, very simple recv and error checking p_sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM) p_sock.connect((p_server,p_port)) p_sock.sendall(proxy_pieces) response = '' # Wait for the full response. while response.find("\r\n\r\n") == -1: response += p_sock.recv(8192) p_status=response.split()[1] if p_status!=str(200): raise atom.http.ProxyError('Error status=%s' % p_status) # Trivial setup for ssl socket. ssl = socket.ssl(p_sock, None, None) fake_sock = httplib.FakeSocket(p_sock, ssl) # Initalize httplib and replace with the proxy socket. connection = httplib.HTTPConnection(server) connection.sock=fake_sock full_uri = partial_uri else: connection = httplib.HTTPSConnection(server, port) full_uri = partial_uri else: # destination is http proxy = os.environ.get('http_proxy') if proxy: (p_server, p_port, p_ssl, p_uri) = ProcessUrl(service.server, proxy, True) proxy_username = os.environ.get('proxy-username') if not proxy_username: proxy_username = os.environ.get('proxy_username') proxy_password = os.environ.get('proxy-password') if not proxy_password: proxy_password = os.environ.get('proxy_password') if proxy_username: UseBasicAuth(service, proxy_username, proxy_password, True) connection = httplib.HTTPConnection(p_server, p_port) if not full_uri.startswith("http://"): if full_uri.startswith("/"): full_uri = "http://%s%s" % (service.server, full_uri) else: full_uri = "http://%s/%s" % (service.server, full_uri) else: connection = httplib.HTTPConnection(server, port) full_uri = partial_uri return (connection, full_uri) def UseBasicAuth(service, username, password, for_proxy=False): """Sets an Authenticaiton: Basic HTTP header containing plaintext. Deprecated, use AtomService.use_basic_auth insread. The username and password are base64 encoded and added to an HTTP header which will be included in each request. Note that your username and password are sent in plaintext. The auth header is added to the additional_headers dictionary in the service object. Args: service: atom.AtomService or a subclass which has an additional_headers dict as a member. username: str password: str """ deprecation('calling deprecated function UseBasicAuth') base_64_string = base64.encodestring('%s:%s' % (username, password)) base_64_string = base_64_string.strip() if for_proxy: header_name = 'Proxy-Authorization' else: header_name = 'Authorization' service.additional_headers[header_name] = 'Basic %s' % (base_64_string,) def ProcessUrl(service, url, for_proxy=False): """Processes a passed URL. If the URL does not begin with https?, then the default value for server is used This method is deprecated, use atom.url.parse_url instead. """ if not isinstance(url, atom.url.Url): url = atom.url.parse_url(url) server = url.host ssl = False port = 80 if not server: if hasattr(service, 'server'): server = service.server else: server = service if not url.protocol and hasattr(service, 'ssl'): ssl = service.ssl if hasattr(service, 'port'): port = service.port else: if url.protocol == 'https': ssl = True elif url.protocol == 'http': ssl = False if url.port: port = int(url.port) elif port == 80 and ssl: port = 443 return (server, port, ssl, url.get_request_uri()) def DictionaryToParamList(url_parameters, escape_params=True): """Convert a dictionary of URL arguments into a URL parameter string. This function is deprcated, use atom.url.Url instead. Args: url_parameters: The dictionaty of key-value pairs which will be converted into URL parameters. For example, {'dry-run': 'true', 'foo': 'bar'} will become ['dry-run=true', 'foo=bar']. Returns: A list which contains a string for each key-value pair. The strings are ready to be incorporated into a URL by using '&'.join([] + parameter_list) """ # Choose which function to use when modifying the query and parameters. # Use quote_plus when escape_params is true. transform_op = [str, urllib.quote_plus][bool(escape_params)] # Create a list of tuples containing the escaped version of the # parameter-value pairs. parameter_tuples = [(transform_op(param), transform_op(value)) for param, value in (url_parameters or {}).items()] # Turn parameter-value tuples into a list of strings in the form # 'PARAMETER=VALUE'. return ['='.join(x) for x in parameter_tuples] def BuildUri(uri, url_params=None, escape_params=True): """Converts a uri string and a collection of parameters into a URI. This function is deprcated, use atom.url.Url instead. Args: uri: string url_params: dict (optional) escape_params: boolean (optional) uri: string The start of the desired URI. This string can alrady contain URL parameters. Examples: '/base/feeds/snippets', '/base/feeds/snippets?bq=digital+camera' url_parameters: dict (optional) Additional URL parameters to be included in the query. These are translated into query arguments in the form '&dict_key=value&...'. Example: {'max-results': '250'} becomes &max-results=250 escape_params: boolean (optional) If false, the calling code has already ensured that the query will form a valid URL (all reserved characters have been escaped). If true, this method will escape the query and any URL parameters provided. Returns: string The URI consisting of the escaped URL parameters appended to the initial uri string. """ # Prepare URL parameters for inclusion into the GET request. parameter_list = DictionaryToParamList(url_params, escape_params) # Append the URL parameters to the URL. if parameter_list: if uri.find('?') != -1: # If there are already URL parameters in the uri string, add the # parameters after a new & character. full_uri = '&'.join([uri] + parameter_list) else: # The uri string did not have any URL parameters (no ? character) # so put a ? between the uri and URL parameters. full_uri = '%s%s' % (uri, '?%s' % ('&'.join([] + parameter_list))) else: full_uri = uri return full_uri def HttpRequest(service, operation, data, uri, extra_headers=None, url_params=None, escape_params=True, content_type='application/atom+xml'): """Performs an HTTP call to the server, supports GET, POST, PUT, and DELETE. This method is deprecated, use atom.http.HttpClient.request instead. Usage example, perform and HTTP GET on http://www.google.com/: import atom.service client = atom.service.AtomService() http_response = client.Get('http://www.google.com/') or you could set the client.server to 'www.google.com' and use the following: client.server = 'www.google.com' http_response = client.Get('/') Args: service: atom.AtomService object which contains some of the parameters needed to make the request. The following members are used to construct the HTTP call: server (str), additional_headers (dict), port (int), and ssl (bool). operation: str The HTTP operation to be performed. This is usually one of 'GET', 'POST', 'PUT', or 'DELETE' data: ElementTree, filestream, list of parts, or other object which can be converted to a string. Should be set to None when performing a GET or PUT. If data is a file-like object which can be read, this method will read a chunk of 100K bytes at a time and send them. If the data is a list of parts to be sent, each part will be evaluated and sent. uri: The beginning of the URL to which the request should be sent. Examples: '/', '/base/feeds/snippets', '/m8/feeds/contacts/default/base' extra_headers: dict of strings. HTTP headers which should be sent in the request. These headers are in addition to those stored in service.additional_headers. url_params: dict of strings. Key value pairs to be added to the URL as URL parameters. For example {'foo':'bar', 'test':'param'} will become ?foo=bar&test=param. escape_params: bool default True. If true, the keys and values in url_params will be URL escaped when the form is constructed (Special characters converted to %XX form.) content_type: str The MIME type for the data being sent. Defaults to 'application/atom+xml', this is only used if data is set. """ deprecation('call to deprecated function HttpRequest') full_uri = BuildUri(uri, url_params, escape_params) (connection, full_uri) = PrepareConnection(service, full_uri) if extra_headers is None: extra_headers = {} # Turn on debug mode if the debug member is set. if service.debug: connection.debuglevel = 1 connection.putrequest(operation, full_uri) # If the list of headers does not include a Content-Length, attempt to # calculate it based on the data object. if (data and not service.additional_headers.has_key('Content-Length') and not extra_headers.has_key('Content-Length')): content_length = CalculateDataLength(data) if content_length: extra_headers['Content-Length'] = str(content_length) if content_type: extra_headers['Content-Type'] = content_type # Send the HTTP headers. if isinstance(service.additional_headers, dict): for header in service.additional_headers: connection.putheader(header, service.additional_headers[header]) if isinstance(extra_headers, dict): for header in extra_headers: connection.putheader(header, extra_headers[header]) connection.endheaders() # If there is data, send it in the request. if data: if isinstance(data, list): for data_part in data: __SendDataPart(data_part, connection) else: __SendDataPart(data, connection) # Return the HTTP Response from the server. return connection.getresponse() def __SendDataPart(data, connection): """This method is deprecated, use atom.http._send_data_part""" deprecated('call to deprecated function __SendDataPart') if isinstance(data, str): #TODO add handling for unicode. connection.send(data) return elif ElementTree.iselement(data): connection.send(ElementTree.tostring(data)) return # Check to see if data is a file-like object that has a read method. elif hasattr(data, 'read'): # Read the file and send it a chunk at a time. while 1: binarydata = data.read(100000) if binarydata == '': break connection.send(binarydata) return else: # The data object was not a file. # Try to convert to a string and send the data. connection.send(str(data)) return def CalculateDataLength(data): """Attempts to determine the length of the data to send. This method will respond with a length only if the data is a string or and ElementTree element. Args: data: object If this is not a string or ElementTree element this funtion will return None. """ if isinstance(data, str): return len(data) elif isinstance(data, list): return None elif ElementTree.iselement(data): return len(ElementTree.tostring(data)) elif hasattr(data, 'read'): # If this is a file-like object, don't try to guess the length. return None else: return len(str(data)) def deprecation(message): warnings.warn(message, DeprecationWarning, stacklevel=2) gdata-2.0.18/samples/apps/search_organize_users.py0000640036466300116100000001161112156622362022302 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright 2011 Google Inc. All Rights Reserved. # # 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. """Search users with a given pattern and move to a new organization. Sample to move users to a new organization based on a pattern using the User Provisioning and Organization Provisioning APIs. Usage: $ python search_organize_users.py """ __author__ = 'Shraddha Gupta ' from optparse import OptionParser import re from gdata.apps.client import AppsClient from gdata.apps.organization.client import OrganizationUnitProvisioningClient import gdata.gauth BATCH_SIZE = 25 SCOPES = ('https://apps-apis.google.com/a/feeds/user/ ' 'https://apps-apis.google.com/a/feeds/policies/') USER_AGENT = 'SearchAndOrganizeUsers' class SearchAndOrganizeUsers(object): """Search users with a pattern and move them to organization.""" def __init__(self, client_id, client_secret, domain): """Create a new SearchAndOrganizeUsers object configured for a domain. Args: client_id: [string] The clientId of the developer. client_secret: [string] The clientSecret of the developer. domain: [string] The domain on which the functions are to be performed. """ self.client_id = client_id self.client_secret = client_secret self.domain = domain def AuthorizeClient(self): """Authorize the clients for making API requests.""" self.token = gdata.gauth.OAuth2Token( client_id=self.client_id, client_secret=self.client_secret, scope=SCOPES, user_agent=USER_AGENT) uri = self.token.generate_authorize_url() print 'Please visit this URL to authorize the application:' print uri # Get the verification code from the standard input. code = raw_input('What is the verification code? ').strip() self.token.get_access_token(code) self.user_client = AppsClient(domain=self.domain, auth_token=self.token) self.org_client = OrganizationUnitProvisioningClient( domain=self.domain, auth_token=self.token) def OrganizeUsers(self, customer_id, org_unit_path, pattern): """Find users with given pattern and move to an organization in batches. Args: customer_id: [string] customer_id to make calls to Organization API. org_unit_path: [string] path of organization unit where users are moved pattern: [regex object] regex to match with users """ users = self.user_client.RetrieveAllUsers() matched_users = [] # Search the users that match given pattern for user in users.entry: if (pattern.search(user.login.user_name) or pattern.search(user.name.given_name) or pattern.search(user.name.family_name)): user_email = '%s@%s' % (user.login.user_name, self.domain) matched_users.append(user_email) # Maximum BATCH_SIZE users can be moved at one time # Split users into batches of BATCH_SIZE and move in batches for i in xrange(0, len(matched_users), BATCH_SIZE): batch_to_move = matched_users[i: i + BATCH_SIZE] self.org_client.MoveUserToOrgUnit(customer_id, org_unit_path, batch_to_move) print 'Number of users moved = %d' % len(matched_users) def Run(self, org_unit_path, regex): self.AuthorizeClient() customer_id_entry = self.org_client.RetrieveCustomerId() customer_id = customer_id_entry.customer_id pattern = re.compile(regex) print 'Moving Users with the pattern %s' % regex self.OrganizeUsers(customer_id, org_unit_path, pattern) def main(): usage = 'Usage: %prog [options]' parser = OptionParser(usage=usage) parser.add_option('--DOMAIN', help='Google Apps Domain, e.g. "domain.com".') parser.add_option('--CLIENT_ID', help='Registered CLIENT_ID of Domain.') parser.add_option('--CLIENT_SECRET', help='Registered CLIENT_SECRET of Domain.') parser.add_option('--ORG_UNIT_PATH', help='Orgunit path of organization where to move users.') parser.add_option('--PATTERN', help='Pattern to search in users') (options, args) = parser.parse_args() if not (options.DOMAIN and options.CLIENT_ID and options.CLIENT_SECRET and options.ORG_UNIT_PATH and options.PATTERN): parser.print_help() return sample = SearchAndOrganizeUsers(options.CLIENT_ID, options.CLIENT_SECRET, options.DOMAIN) sample.Run(options.ORG_UNIT_PATH, options.PATTERN) if __name__ == '__main__': main() gdata-2.0.18/samples/apps/emailsettings_pop_settings.py0000640036466300116100000001143712156622362023372 0ustar afshareng00000000000000#!/usr/bin/python2.4 # # Copyright 2011 Google Inc. All Rights Reserved. # # 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. """Demonstrates Email Settings API's POP settings. Enables POP for all of the domain's users for all messages and archives Gmail's copy. """ __author__ = 'Gunjan Sharma ' import getopt import sys import gdata.apps.client import gdata.apps.emailsettings.client SCOPES = ['https://apps-apis.google.com/a/feeds/emailsettings/2.0/', 'https://apps-apis.google.com/a/feeds/user/'] class PopSettingsException(Exception): """Exception class for PopSettings to show appropriate error message.""" def __init__(self, message): """Create a new PopSettingsException with the appropriate error message.""" super(PopSettingsException, self).__init__(message) class PopSettings(object): """Sample demonstrating how to enable POP for all of a domain's users.""" def __init__(self, consumer_key, consumer_secret, domain): """Create a new PopSettings object configured for a domain. Args: consumer_key: [string] The consumerKey of the domain. consumer_secret: [string] The consumerSecret of the domain. domain: [string] The domain whose user's POP settings to be changed. """ self.consumer_key = consumer_key self.consumer_secret = consumer_secret self.domain = domain def Authorize(self): """Asks the domain's admin to authorize. Access to two APIs needs to be authorized, provisioning users and Gmail settings. The function creates clients for both APIs. """ self.email_settings_client = ( gdata.apps.emailsettings.client.EmailSettingsClient( domain=self.domain)) self.provisioning_client = gdata.apps.client.AppsClient(domain=self.domain) request_token = self.email_settings_client.GetOAuthToken( SCOPES, None, self.consumer_key, consumer_secret=self.consumer_secret) print request_token.GenerateAuthorizationUrl() raw_input('Manually go to the above URL and authenticate.' 'Press Return after authorization.') access_token = self.email_settings_client.GetAccessToken(request_token) self.email_settings_client.auth_token = access_token self.provisioning_client.auth_token = access_token def UpdateDomainUsersPopSettings(self): """Updates POP settings for all of the domain's users.""" users = self.provisioning_client.RetrieveAllUsers() for user in users.entry: self.email_settings_client.UpdatePop(username=user.login.user_name, enable=True, enable_for='ALL_MAIL', action='ARCHIVE') def PrintUsageString(): """Prints the correct call for running the sample.""" print ('python emailsettings_pop_settings.py' '--consumer_key [ConsumerKey] --consumer_secret [ConsumerSecret]' '--domain [domain]') def main(): """Updates POP settings for all of the domain's users using the Email Settings API. """ try: opts, args = getopt.getopt(sys.argv[1:], '', ['consumer_key=', 'consumer_secret=', 'domain=']) except getopt.error, msg: PrintUsageString() sys.exit(1) consumer_key = '' consumer_secret = '' domain = '' for option, arg in opts: if option == '--consumer_key': consumer_key = arg elif option == '--consumer_secret': consumer_secret = arg elif option == '--domain': domain = arg if not (consumer_key and consumer_secret and domain): print 'Requires exactly three flags.' PrintUsageString() sys.exit(1) pop_settings = PopSettings( consumer_key, consumer_secret, domain) try: pop_settings.Authorize() pop_settings.UpdateDomainUsersPopSettings() except gdata.client.RequestError, e: if e.status == 403: raise PopSettingsException('Invalid Domain') elif e.status == 400: raise PopSettingsException('Invalid consumer credentials') elif e.status == 503: raise PopSettingsException('Server busy') else e.status == 500: raise PopSettingsException('Internal server error') else: raise PopSettingsException('Unknown error') if __name__ == '__main__': main() gdata-2.0.18/samples/apps/suspended_users_cleanup.py0000640036466300116100000000743412156622362022650 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright 2012 Google Inc. All Rights Reserved. # # 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. """Sample to delete suspended users whose last login date is 6 months old. Sample to obtain the suspended users and their last login date from the Reporting API and delete the user if the last login date is older than 6 months using the Provisioning API. """ __author__ = 'Shraddha Gupta ' import datetime import getopt import sys import gdata.apps.client import gdata.client import google.apps.reporting def Usage(): print ('Usage: suspended_users_cleanup.py --email= ' '--password= --domain= ') def GetAccountsReport(report_runner): """Gets the account report from the Reporting API for current date. Args: report_runner: An instance of google.apps.reporting.ReportRunner. Returns: response: String containing accounts report. """ if report_runner.token is None: report_runner.Login() request = google.apps.reporting.ReportRequest() request.token = report_runner.token request.domain = report_runner.domain request.report_name = 'accounts' request.date = report_runner.GetLatestReportDate() response = report_runner.GetReportData(request) return response def main(): """Gets accounts report and deletes old suspended users.""" try: (opts, args) = getopt.getopt(sys.argv[1:], '', ['email=', 'password=', 'domain=']) except getopt.GetoptError: print 'Error' Usage() sys.exit(1) opts = dict(opts) report_runner = google.apps.reporting.ReportRunner() email = opts.get('--email') report_runner.admin_email = email password = opts.get('--password') report_runner.admin_password = password domain = opts.get('--domain') report_runner.domain = domain if not email or not password or not domain: Usage() sys.exit(1) # Instantiate a client for Provisioning API client = gdata.apps.client.AppsClient(domain=domain) client.ClientLogin(email=email, password=password, source='DeleteOldSuspendedUsers') # Get accounts report from the Reporting API response = GetAccountsReport(report_runner) accounts = response.splitlines() current_date = datetime.datetime.now() # accounts[0] contains the headings for fields, read data starting at index 1 for i in range(1, len(accounts)): account_fields = accounts[i].split(',') # Find the suspended users and check if last login date is 180 days old if account_fields[3] != 'ACTIVE': last_login_date_str = account_fields[10] last_login_date = datetime.datetime.strptime(last_login_date_str, '%Y%m%d') duration = current_date - last_login_date days = duration.days if days > 180: account_id = account_fields[2] print 'This user is obsolete: %s' % account_id user_name = account_id.split('@') delete_flag = raw_input('Do you want to delete the user (y/n)? ') if delete_flag == 'y': try: client.DeleteUser(user_name[0]) print 'Deleted %s ' % account_id except gdata.client.RequestError, e: print 'Request Error %s %s %s' % (e.status, e.reason, e.body) if __name__ == '__main__': main() gdata-2.0.18/samples/apps/email_audit_email_monitoring.py0000640036466300116100000002120112156622362023603 0ustar afshareng00000000000000#!/usr/bin/python2.4 # # Copyright 2012 Google Inc. All Rights Reserved. # # 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. """Sample to demonstrate the Email Audit API's email monitoring functions. The sample demonstrates the creating, updating, retrieving and deleting of email monitors. """ __author__ = 'Gunjan Sharma ' from datetime import datetime import getopt import re import sys import gdata from gdata.apps.audit.service import AuditService class EmailMonitoringException(Exception): """Exception class for EmailMonitoring, shows appropriate error message.""" class EmailMonitoring(object): """Sample demonstrating how to perform CRUD operations on email monitor.""" def __init__(self, consumer_key, consumer_secret, domain): """Create a new EmailMonitoring object configured for a domain. Args: consumer_key: A string representing a consumerKey. consumer_secret: A string representing a consumerSecret. domain: A string representing the domain to work on in the sample. """ self.consumer_key = consumer_key self.consumer_secret = consumer_secret self.domain = domain self._Authorize() def _Authorize(self): """Asks the domain's admin to authorize access to the apps Apis.""" self.service = AuditService(domain=self.domain, source='emailAuditSample') self.service.SetOAuthInputParameters( gdata.auth.OAuthSignatureMethod.HMAC_SHA1, self.consumer_key, self.consumer_secret) request_token = self.service.FetchOAuthRequestToken() self.service.SetOAuthToken(request_token) auth_url = self.service.GenerateOAuthAuthorizationURL() print auth_url raw_input('Manually go to the above URL and authenticate.' 'Press Return after authorization.') self.service.UpgradeToOAuthAccessToken() def _CheckUsername(self, username): """Checks if a given username is valid or not. Args: username: A string to check for validity. Returns: True if username is valid, False otherwise. """ if len(username) > 64: print 'Username length should be less than 64' return False pattern = re.compile('[^\w\.\+-_\']+') return not bool(pattern.search(username)) def _GetValidUsername(self, typeof): """Takes a valid username as input. Args: typeof: A string representing the type of user. Returns: A valid string corresponding to username. """ username = '' while not username: username = raw_input('Enter a valid %s username: ' % typeof) if not self._CheckUsername(username): print 'Invalid username' username = '' return username def _GetValidDate(self, is_neccessary): """Takes a valid date as input in 'yyyy-mm-dd HH:MM' format. Args: is_neccessary: A boolean denoting if a non empty value is needed. Returns: A valid string corresponding to date. """ date = '' extra_stmt = '' if not is_neccessary: extra_stmt = '. Press enter to skip.' while not date: date = raw_input( 'Enter a valid date as (yyyy-mm-dd HH:MM)%s:' % extra_stmt) if not (date and is_neccessary): return date try: datetime.strptime(date, '%Y-%m-%d %H:%M') return date except ValueError: print 'Not a valid date!' date = '' def _GetBool(self, name): """Takes a boolean value as input. Args: name: A string for which input is to be taken. Returns: A boolean for an entity represented by name. """ choice = raw_input( 'Enter your choice (t/f) for %s (defaults to False):' % name).strip() if choice == 't': return True return False def _CreateEmailMonitor(self): """Creates/Updates an email monitor.""" src_user = self._GetValidUsername('source') dest_user = self._GetValidUsername('destination') end_date = self._GetValidDate(True) start_date = self._GetValidDate(False) incoming_headers = self._GetBool('incoming headers') outgoing_headers = self._GetBool('outgoing headers') drafts = self._GetBool('drafts') drafts_headers = False if drafts: drafts_headers = self._GetBool('drafts headers') chats = self._GetBool('chats') chats_headers = False if chats: self._GetBool('chats headers') self.service.createEmailMonitor( src_user, dest_user, end_date, start_date, incoming_headers, outgoing_headers, drafts, drafts_headers, chats, chats_headers) print 'Email monitor created/updated successfully!\n' def _RetrieveEmailMonitor(self): """Retrieves all email monitors for a user.""" src_user = self._GetValidUsername('source') monitors = self.service.getEmailMonitors(src_user) for monitor in monitors: for key in monitor.keys(): print '%s ----------- %s' % (key, monitor.get(key)) print '' print 'Email monitors retrieved successfully!\n' def _DeleteEmailMonitor(self): """Deletes an email monitor.""" src_user = self._GetValidUsername('source') dest_user = self._GetValidUsername('destination') self.service.deleteEmailMonitor(src_user, dest_user) print 'Email monitor deleted successfully!\n' def Run(self): """Handles the flow of the sample.""" functions_list = [ { 'function': self._CreateEmailMonitor, 'description': 'Create a email monitor for a domain user' }, { 'function': self._CreateEmailMonitor, 'description': 'Update a email monitor for a domain user' }, { 'function': self._RetrieveEmailMonitor, 'description': 'Retrieve all email monitors for a domain user' }, { 'function': self._DeleteEmailMonitor, 'description': 'Delete a email monitor for a domain user' } ] while True: print 'What would you like to do? Choose an option:' print '0 - To exit' for i in range (0, len(functions_list)): print '%d - %s' % ((i + 1), functions_list[i].get('description')) choice = raw_input('Enter your choice: ').strip() if choice.isdigit(): choice = int(choice) if choice == 0: break if choice < 0 or choice > len(functions_list): print 'Not a valid option!' continue try: functions_list[choice - 1].get('function')() except gdata.apps.service.AppsForYourDomainException, e: if e.error_code == 1301: print '\nError: Invalid username!!\n' else: raise e def PrintUsageString(): """Prints the correct call for running the sample.""" print ('python email_audit_email_monitoring.py ' '--consumer_key [ConsumerKey] --consumer_secret [ConsumerSecret] ' '--domain [domain]') def main(): """Runs the sample using an instance of EmailMonitoring.""" try: opts, args = getopt.getopt(sys.argv[1:], '', ['consumer_key=', 'consumer_secret=', 'domain=']) except getopt.error, msg: PrintUsageString() sys.exit(1) consumer_key = '' consumer_secret = '' domain = '' for option, arg in opts: if option == '--consumer_key': consumer_key = arg elif option == '--consumer_secret': consumer_secret = arg elif option == '--domain': domain = arg if not (consumer_key and consumer_secret and domain): print 'Requires exactly three flags.' PrintUsageString() sys.exit(1) try: email_monitoring = EmailMonitoring( consumer_key, consumer_secret, domain) email_monitoring.Run() except gdata.apps.service.AppsForYourDomainException, e: raise EmailMonitoringException('Invalid Domain') except gdata.service.FetchingOAuthRequestTokenFailed, e: raise EmailMonitoringException('Invalid consumer credentials') except Exception, e: if e.args[0].get('status') == 503: raise EmailMonitoringException('Server busy') elif e.args[0].get('status') == 500: raise EmailMonitoringException('Internal server error') else: raise EmailMonitoringException('Unknown error') if __name__ == '__main__': main() gdata-2.0.18/samples/apps/userprovisioning_quick_start_example.py0000640036466300116100000002433412156622362025475 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright 2011 Google Inc. All Rights Reserved. # # 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. """Sample for the User Provisioning API to demonstrate all methods. Usage: $ python userprovisioning_quick_start_example.py You can also specify the user credentials from the command-line $ python userprovisioning_quick_start_example.py --client_id [client_id] --client_secret [client_scret] --domain [domain] You can get help for command-line arguments as $ python userprovisioning_quick_start_example.py --help """ __author__ = 'Shraddha Gupta ' import getopt import getpass import sys import gdata.apps.client SCOPE = 'https://apps-apis.google.com/a/feeds/user/' USER_AGENT = 'UserProvisioningQuickStartExample' class UserProvisioning(object): """Demonstrates all the functions of user provisioning.""" def __init__(self, client_id, client_secret, domain): """ Args: client_id: [string] The clientId of the developer. client_secret: [string] The clientSecret of the developer. domain: [string] The domain on which the functions are to be performed. """ self.client_id = client_id self.client_secret = client_secret self.domain = domain def _AuthorizeClient(self): """Authorize the client for making API requests.""" self.token = gdata.gauth.OAuth2Token( client_id=self.client_id, client_secret=self.client_secret, scope=SCOPE, user_agent=USER_AGENT) uri = self.token.generate_authorize_url() print 'Please visit this URL to authorize the application:' print uri # Get the verification code from the standard input. code = raw_input('What is the verification code? ').strip() self.token.get_access_token(code) self.client = gdata.apps.client.AppsClient( domain=self.domain, auth_token=self.token) def _PrintUserDetails(self, entry): """Prints the attributes for a user entry. Args: entry: [UserEntry] User entry corresponding to a user """ print '\nGiven Name: %s' % (entry.name.given_name) print 'Family Name: %s' % (entry.name.family_name) print 'Username: %s' % (entry.login.user_name) print 'Is Admin: %s' % (entry.login.admin) print 'Is Suspended: %s' % (entry.login.suspended) print 'Change password at next login: %s\n' % ( entry.login.change_password) def _PrintNicknameDetails(self, entry): """Prints the attributes for a user nickname entry. Args: entry: [NicknameEntry] """ print 'Username: %s' % (entry.login.user_name) print 'Nickname: %s\n' % (entry.nickname.name) def _GetChoice(self, for_value): choice = raw_input(('(Optional) Enter a choice for %s\n' '1-True 2-False ') % (for_value)) if choice == '1': return True return False def _CreateUser(self): """Creates a new user account.""" user_name = given_name = family_name = password = None confirm_password = '' while not user_name: user_name = raw_input('Enter a new username: ') while not given_name: given_name = raw_input('Enter given name for the user: ') while not family_name: family_name = raw_input('Enter family name for the user: ') while not password == confirm_password: password = '' while not password: sys.stdout.write('Enter password for the user: ') password = getpass.getpass() if password.__len__() == 0: break if password.__len__() < 8: print 'Password must be at least 8 characters long' password = '' sys.stdout.write('Confirm password: ') confirm_password = getpass.getpass() is_admin = self._GetChoice('is_admin ') hash_function = raw_input('(Optional) Enter a hash function ') suspended = self._GetChoice('suspended ') change_password = self._GetChoice('change_password ') quota = raw_input('(Optional) Enter a quota ') if quota == 'None' or not quota.isdigit(): quota = None user_entry = self.client.CreateUser( user_name=user_name, given_name=given_name, family_name=family_name, password=password, admin=is_admin, suspended=suspended, password_hash_function=hash_function, change_password=change_password) self._PrintUserDetails(user_entry) print 'User Created' def _UpdateUser(self): """Updates a user.""" user_name = raw_input('Enter the username ') if user_name is None: print 'Username missing\n' return user_entry = self.client.RetrieveUser(user_name=user_name) print self._PrintUserDetails(user_entry) attributes = {1: 'given_name', 2: 'family_name', 3: 'user_name', 4: 'suspended', 5: 'is_admin'} print attributes attr = int(raw_input('\nEnter number(1-5) of attribute to be updated ')) updated_val = raw_input('Enter updated value ') if attr == 1: user_entry.name.given_name = updated_val if attr == 2: user_entry.name.family_name = updated_val if attr == 3: user_entry.login.user_name = updated_val if attr == 4: user_entry.login.suspended = updated_val if attr == 5: user_entry.login.admin = updated_val updated = self.client.UpdateUser(user_entry.login.user_name, user_entry) self._PrintUserDetails(updated) def _RetrieveSingleUser(self): """Retrieves a single user.""" user_name = raw_input('Enter the username ') if user_name is None: print 'Username missing\n' return response = self.client.RetrieveUser(user_name=user_name) self._PrintUserDetails(response) def _RetrieveAllUsers(self): """Retrieves all users from all the domains.""" response = self.client.RetrieveAllUsers() for entry in response.entry: self._PrintUserDetails(entry) def _DeleteUser(self): """Deletes a user.""" user_name = raw_input('Enter the username ') if user_name is None: print 'Username missing\n' return self.client.DeleteUser(user_name=user_name) print 'User Deleted' def _CreateNickname(self): """Creates a user alias.""" user_name = raw_input('Enter the username ') nickname = raw_input('Enter a nickname for user ') if None in (user_name, nickname): print 'Username/Nickname missing\n' return nickname = self.client.CreateNickname( user_name=user_name, nickname=nickname) print nickname print 'Nickname Created' def _RetrieveNickname(self): """Retrieves a nickname entry.""" nickname = raw_input('Enter the username ') if nickname is None: print 'Nickname missing\n' return response = self.client.RetrieveNickname(nickname=nickname) self._PrintNicknameDetails(response) def _RetrieveUserNicknames(self): """Retrieves all nicknames of a user.""" user_name = raw_input('Enter the username ') if user_name is None: print 'Username missing\n' return response = self.client.RetrieveNicknames(user_name=user_name) for entry in response.entry: self._PrintNicknameDetails(entry) def _DeleteNickname(self): """Deletes a nickname.""" nickname = raw_input('Enter the username ') if nickname is None: print 'Nickname missing\n' return self.client.DeleteNickname(nickname=nickname) print 'Nickname deleted' def Run(self): """Runs the sample by getting user input and taking appropriate action.""" # List of all the functions and their descriptions functions_list = [ {'function': self._CreateUser, 'description': 'Create a user'}, {'function': self._UpdateUser, 'description': 'Update a user'}, {'function': self._RetrieveSingleUser, 'description': 'Retrieve a single user'}, {'function': self._RetrieveAllUsers, 'description': 'Retrieve all users'}, {'function': self._DeleteUser, 'description': 'Delete a user'}, {'function': self._CreateNickname, 'description': 'Create a nickname'}, {'function': self._RetrieveNickname, 'description': 'Retrieve a nickname'}, {'function': self._RetrieveUserNicknames, 'description': 'Retrieve all nicknames for a user'}, {'function': self._DeleteNickname, 'description': 'Delete a nickname'} ] self._AuthorizeClient() while True: print '\nChoose an option:\n0 - to exit' for i in range (0, len(functions_list)): print '%d - %s' % ((i+1), functions_list[i]['description']) choice = int(raw_input()) if choice == 0: break if choice < 0 or choice > 11: print 'Not a valid option!' continue functions_list[choice-1]['function']() def main(): """A menu driven application to demo all methods of user provisioning.""" usage = ('python userprovisioning_quick_start_example.py ' '--client_id [clientId] --client_secret [clientSecret] ' '--domain [domain]') # Parse command line options try: opts, args = getopt.getopt(sys.argv[1:], '', ['client_id=', 'client_secret=', 'domain=']) except getopt.error, msg: print 'Usage: %s' % usage return client_id = None client_secret = None domain = None # Parse options for option, arg in opts: if option == '--client_id': client_id = arg elif option == '--client_secret': client_secret = arg elif option == '--domain': domain = arg if None in (client_id, client_secret, domain): print 'Usage: %s' % usage return try: user_provisioning = UserProvisioning(client_id, client_secret, domain) except gdata.service.BadAuthentication: print 'Invalid user credentials given.' return user_provisioning.Run() if __name__ == '__main__': main() gdata-2.0.18/samples/apps/groups_provisioning_quick_start_example.py0000640036466300116100000002511212156622362026170 0ustar afshareng00000000000000#!/usr/bin/python2.4 # # Copyright 2011 Google Inc. All Rights Reserved. # # 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. __author__ = 'Gunjan Sharma ' import getopt import sys import gdata.apps.groups.client import gdata.apps.groups.data SCOPE = 'https://apps-apis.google.com/a/feeds/groups/' USER_AGENT = 'GroupsQuickStartExample' class GroupsQuickStartExample(object): """Demonstrates all the functions of Groups provisioning.""" def __init__(self, client_id, client_secret, domain): """Constructor for the GroupsQuickStartExample object. Takes a client_id, client_secret and domain needed to create an object for groups provisioning. Args: client_id: [string] The clientId of the developer. client_secret: [string] The clientSecret of the developer. domain: [string] The domain on which the functions are to be performed. """ self.client_id = client_id self.client_secret = client_secret self.domain = domain def CreateGroupsClient(self): """Creates a groups provisioning client using OAuth2.0 flow.""" token = gdata.gauth.OAuth2Token( client_id=self.client_id, client_secret=self.client_secret, scope=SCOPE, user_agent=USER_AGENT) uri = token.generate_authorize_url() print 'Please visit this URL to authorize the application:' print uri # Get the verification code from the standard input. code = raw_input('What is the verification code? ').strip() token.get_access_token(code) self.groups_client = gdata.apps.groups.client.GroupsProvisioningClient( domain=self.domain, auth_token=token) def _GetValidGroupId(self): """Takes a valid group email address as input. Return: group_id: [string] a valid group email address """ group_id = '' while not group_id: group_id = raw_input('Enter a valid group email address' '(group@domain.com): ') return group_id def _GetValidMemberId(self): """Takes a valid member email address as input. Return: member_id: [string] a valid member email address """ member_id = '' while not member_id: member_id = raw_input('Enter a valid member email address' '(username@domain.com): ') return member_id def _PrintGroupDetails(self, group_entry): """Print all the details of a group. Args: group_entry: [GroupEntry] contains all the data about the group. """ print 'Group ID: ' + group_entry.group_id print 'Group Name: ' + group_entry.group_name print 'Description: ' + group_entry.description print 'Email Permissions: ' + group_entry.email_permission print '' def _PrintMemberDetails(self, member_entry): """Print all the details of a group member. Args: member_entry: [GroupMemberEntry] contains all the data about the group member. """ print 'Member ID: ' + member_entry.member_id print 'Member Type: ' + member_entry.member_type print 'Is Direct Member: ' + member_entry.direct_member print '' def _TakeGroupData(self, function='create'): """Takes input data for _UpdateGroup and _CreateGroup functions. Args: function: [string] representing the kind of function (create/update) from where this function was called. Return: group_data: [gdata.apps.groups.data.GroupEntry] All data for a group. """ email_permission_options = ['Owner', 'Member', 'Domain', 'Anyone'] extra_stmt = '' if function == 'update': extra_stmt = '. Press enter to not update the field' group_data = gdata.apps.groups.data.GroupEntry() group_data.group_id = self._GetValidGroupId() while not group_data.group_name: group_data.group_name = raw_input('Enter name for the group%s: ' % extra_stmt) if function == 'update': break group_data.description = raw_input('Enter description for the group%s: ' % extra_stmt) print ('Choose an option for email permission%s:' % extra_stmt) i = 1 for option in email_permission_options: print '%d - %s' % (i, option) i += 1 choice = (raw_input()) if not choice: choice = -1 choice = int(choice) if choice > 0 and choice <= len(email_permission_options): group_data.email_permission = email_permission_options[choice-1] return group_data def _CreateGroup(self): """Creates a new group.""" group_data = self._TakeGroupData() self.groups_client.CreateGroup(group_data.group_id, group_data.group_name, group_data.description, group_data.email_permission) def _UpdateGroup(self): """Updates an existing group.""" group_data = self._TakeGroupData(function='update') group_entry = self.groups_client.RetrieveGroup(group_data.group_id) if group_data.group_name: group_entry.group_name = group_data.group_name if group_data.description: group_entry.description = group_data.description if group_data.email_permission: group_entry.email_permission = group_data.email_permission self.groups_client.UpdateGroup(group_data.GetGroupId(), group_entry) def _RetrieveSingleGroup(self): """Retrieves a single group.""" group_id = self._GetValidGroupId() group_entry = self.groups_client.RetrieveGroup(group_id) self._PrintGroupDetails(group_entry) def _RetrieveAllGroupsForMember(self): """Retrieves all the groups the user is a member of.""" member_id = self._GetValidMemberId() direct_only = raw_input('Write true/false for direct_only: ') if direct_only == 'true': direct_only = True else: direct_only = False groups_feed = self.groups_client.RetrieveGroups(member_id, direct_only) for entry in groups_feed.entry: self._PrintGroupDetails(entry) def _RetrieveAllGroups(self): """Retrieves all the groups in a domain.""" groups_feed = self.groups_client.RetrieveAllGroups() for entry in groups_feed.entry: self._PrintGroupDetails(entry) def _DeleteGroup(self): """Delete a group.""" group_id = self._GetValidGroupId() self.groups_client.DeleteGroup(group_id) def _AddMemberToGroup(self): """Add a member to a particular group.""" group_id = self._GetValidGroupId() member_id = self._GetValidMemberId() self.groups_client.AddMemberToGroup(group_id, member_id) def _RetrieveSingleMember(self): """Retrieve a single member from a group.""" group_id = self._GetValidGroupId() member_id = self._GetValidMemberId() member_entry = self.groups_client.RetrieveGroupMember(group_id, member_id) self._PrintMemberDetails(member_entry) def _RetrieveAllMembersOfGroup(self): """Retrieve all the members of a group.""" group_id = self._GetValidGroupId() members_feed = self.groups_client.RetrieveAllMembers(group_id) for entry in members_feed.entry: self._PrintMemberDetails(entry) def _RemoveMemberFromGroup(self): """Remove a member from a group.""" group_id = self._GetValidGroupId() member_id = self._GetValidMemberId() self.groups_client.RemoveMemberFromGroup(group_id, member_id) def Run(self): """Runs the sample by getting user input and takin appropriate action.""" #List of all the function and there description functions_list = [ {'function': self._CreateGroup, 'description': 'Create a Group'}, {'function': self._UpdateGroup, 'description': 'Updating a Group'}, {'function': self._RetrieveSingleGroup, 'description': 'Retrieve a single Group'}, {'function': self._RetrieveAllGroupsForMember, 'description': 'Retrieve all Groups for a member'}, {'function': self._RetrieveAllGroups, 'description': 'Retrieve all Groups in a domain'}, {'function': self._DeleteGroup, 'description': 'Deleting a Group from a domain'}, {'function': self._AddMemberToGroup, 'description': 'Add a member to a Group'}, {'function': self._RetrieveSingleMember, 'description': 'Retrieve a member of a group'}, {'function': self._RetrieveAllMembersOfGroup, 'description': 'Retrieve all members of a group'}, {'function': self._RemoveMemberFromGroup, 'description': 'Delete a member from a Group'} ] while True: print 'Choose an option:\n0 - to exit' for i in range (0, len(functions_list)): print '%d - %s' % ((i+1), functions_list[i]['description']) choice = int(raw_input()) if choice == 0: break if choice < 0 or choice > len(functions_list): print 'Not a valid option!' continue functions_list[choice-1]['function']() def main(): """Runs the sample using an instance of GroupsQuickStartExample.""" # Parse command line options try: opts, args = getopt.getopt(sys.argv[1:], '', ['client_id=', 'client_secret=', 'domain=']) except getopt.error, msg: print ('python groups_provisioning_quick_start_example.py' ' --client_id [clientId] --client_secret [clientSecret]' ' --domain [domain]') sys.exit(2) client_id = '' client_secret = '' domain = '' # Parse options for option, arg in opts: if option == '--client_id': client_id = arg elif option == '--client_secret': client_secret = arg elif option == '--domain': domain = arg while not client_id: client_id = raw_input('Please enter a clientId: ') while not client_secret: client_secret = raw_input('Please enter a clientSecret: ') while not domain: domain = raw_input('Please enter domain name (example.com): ') try: groups_quick_start_example = GroupsQuickStartExample( client_id, client_secret, domain) except gdata.service.BadAuthentication: print 'Invalid user credentials given.' return groups_quick_start_example.CreateGroupsClient() groups_quick_start_example.Run() if __name__ == '__main__': main() gdata-2.0.18/samples/apps/adminsettings_example.py0000750036466300116100000001305512156622362022310 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright 2009 Google Inc. All Rights Reserved. # # 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. """Contains a Sample for Google Apps Admin Settings. AdminSettingsSample: shows everything you ever wanted to know about your Google Apps Domain but were afraid to ask. """ __author__ = 'jlee@pbu.edu' import getopt import getpass import sys import time import gdata.apps.service import gdata.apps.adminsettings.service class AdminSettingsSample(object): """AdminSettingsSample object demos Admin Settings API.""" def __init__(self, email, password, domain): """Constructor for the AdminSettingsSample object. Takes an email and password corresponding to a google apps admin account to demon the Admin Settings API. Args: email: [string] The e-mail address of the account to use for the sample. password: [string] The password corresponding to the account specified by the email parameter. domain: [string] The domain for the Profiles feed """ self.gd_client = gdata.apps.adminsettings.service.AdminSettingsService() self.gd_client.domain = domain self.gd_client.email = email self.gd_client.password = password self.gd_client.source = 'GoogleInc-AdminSettingsPythonSample-1' self.gd_client.ProgrammaticLogin() def Run(self): #pause 1 sec inbetween calls to prevent quota warning print 'Google Apps Domain: ', self.gd_client.domain time.sleep(1) print 'Default Language: ', self.gd_client.GetDefaultLanguage() time.sleep(1) print 'Organization Name: ', self.gd_client.GetOrganizationName() time.sleep(1) print 'Maximum Users: ', self.gd_client.GetMaximumNumberOfUsers() time.sleep(1) print 'Current Users: ', self.gd_client.GetCurrentNumberOfUsers() time.sleep(1) print 'Domain is Verified: ',self.gd_client.IsDomainVerified() time.sleep(1) print 'Support PIN: ',self.gd_client.GetSupportPIN() time.sleep(1) print 'Domain Edition: ', self.gd_client.GetEdition() time.sleep(1) print 'Customer PIN: ', self.gd_client.GetCustomerPIN() time.sleep(1) print 'Domain Creation Time: ', self.gd_client.GetCreationTime() time.sleep(1) print 'Domain Country Code: ', self.gd_client.GetCountryCode() time.sleep(1) print 'Admin Secondary Email: ', self.gd_client.GetAdminSecondaryEmail() time.sleep(1) cnameverificationstatus = self.gd_client.GetCNAMEVerificationStatus() print 'CNAME Verification Record Name: ', cnameverificationstatus['recordName'] print 'CNAME Verification Verified: ', cnameverificationstatus['verified'] print 'CNAME Verification Method: ', cnameverificationstatus['verificationMethod'] time.sleep(1) mxverificationstatus = self.gd_client.GetMXVerificationStatus() print 'MX Verification Verified: ', mxverificationstatus['verified'] print 'MX Verification Method: ', mxverificationstatus['verificationMethod'] time.sleep(1) ssosettings = self.gd_client.GetSSOSettings() print 'SSO Enabled: ', ssosettings['enableSSO'] print 'SSO Signon Page: ', ssosettings['samlSignonUri'] print 'SSO Logout Page: ', ssosettings['samlLogoutUri'] print 'SSO Password Page: ', ssosettings['changePasswordUri'] print 'SSO Whitelist IPs: ', ssosettings['ssoWhitelist'] print 'SSO Use Domain Specific Issuer: ', ssosettings['useDomainSpecificIssuer'] time.sleep(1) ssokey = self.gd_client.GetSSOKey() print 'SSO Key Modulus: ', ssokey['modulus'] print 'SSO Key Exponent: ', ssokey['exponent'] print 'SSO Key Algorithm: ', ssokey['algorithm'] print 'SSO Key Format: ', ssokey['format'] print 'User Migration Enabled: ', self.gd_client.IsUserMigrationEnabled() time.sleep(1) outboundgatewaysettings = self.gd_client.GetOutboundGatewaySettings() print 'Outbound Gateway Smart Host: ', outboundgatewaysettings['smartHost'] print 'Outbound Gateway Mode: ', outboundgatewaysettings['smtpMode'] def main(): """Demonstrates use of the Admin Settings API using the AdminSettingsSample object.""" # Parse command line options try: opts, args = getopt.getopt(sys.argv[1:], '', ['user=', 'pw=', 'domain=']) except getopt.error, msg: print 'python adminsettings_example.py --user [username] --pw [password]' print ' --domain [domain]' sys.exit(2) user = '' pw = '' domain = '' # Process options for option, arg in opts: if option == '--user': user = arg elif option == '--pw': pw = arg elif option == '--domain': domain = arg while not domain: print 'NOTE: Please run these tests only with a test account.' domain = raw_input('Please enter your apps domain: ') while not user: user = raw_input('Please enter a administrator account: ')+'@'+domain while not pw: pw = getpass.getpass('Please enter password: ') if not pw: print 'Password cannot be blank.' try: sample = AdminSettingsSample(user, pw, domain) except gdata.service.BadAuthentication: print 'Invalid user credentials given.' return sample.Run() if __name__ == '__main__': main() gdata-2.0.18/samples/apps/migration_example.py0000750036466300116100000001371712156622362021435 0ustar afshareng00000000000000#!/usr/bin/python2.4 # # Copyright 2011 Google Inc. All Rights Reserved. # # 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. """Sample app for Google Apps Email Migration features. EmailMigrationSample: Demonstrates the use of the Email Migration API """ __author__ = 'pti@google.com (Prashant Tiwari)' from optparse import OptionParser import os from gdata.apps.migration import service class EmailMigrationSample(object): """Sample application demonstrating use of the Email Migration API.""" def __init__(self, domain, email, password): """Constructor for the EmailMigrationSample object. Construct an EmailMigrationSample with the given args. Args: domain: The domain name ("domain.com") email: The email account of the user or the admin ("john@domain.com") password: The domain admin's password """ self.service = service.MigrationService( email=email, password=password, domain=domain, source='googlecode-migrationsample-v1') self.service.ProgrammaticLogin() # Sample mail properties self.mail_item_properties = ['IS_INBOX', 'IS_UNREAD'] self.mail_labels = ['EmailMigrationSample'] def Migrate(self, path): """Migrates messages at the given path. Args: path: The file or directory path where messages are stored """ if os.path.isfile(path): if os.path.splitext(path)[1] != '.txt': print "The input file is not a .txt file" return self._MigrateOneMail(path) elif os.path.isdir(path): if path.endswith(os.sep): path = path[0: len(path) - 1] txt_file_paths = [] filenames = os.listdir(path) for filename in filenames: # Filter out the non-txt files in the directory filepath = path + os.sep + filename if os.path.isfile(filepath) and os.path.splitext(filepath)[1] == '.txt': txt_file_paths.append(filepath) if not txt_file_paths: print "Found no .txt file in the directory" return elif len(txt_file_paths) == 1: # Don't use threading if there's only one txt file in the dir self._MigrateOneMail(txt_file_paths[0]) else: self._MigrateManyMails(txt_file_paths) def _MigrateOneMail(self, path): """Imports a single message via the ImportMail service. Args: path: The path of the message file """ print "Attempting to migrate 1 message..." content = self._ReadFileAsString(path) self.service.ImportMail(user_name=options.username, mail_message=content, mail_item_properties=self.mail_item_properties, mail_labels=self.mail_labels) print "Successfully migrated 1 message." def _MigrateManyMails(self, paths): """Imports several messages via the ImportMultipleMails service. Args: paths: List of paths of message files """ print "Attempting to migrate %d messages..." % (len(paths)) for path in paths: content = self._ReadFileAsString(path) self.service.AddMailEntry(mail_message=content, mail_item_properties=self.mail_item_properties, mail_labels=self.mail_labels, identifier=path) success = self.service.ImportMultipleMails(user_name=options.username) print "Successfully migrated %d of %d messages." % (success, len(paths)) def _ReadFileAsString(self, path): """Reads the file found at path into a string Args: path: The path of the message file Returns: The file contents as a string Raises: IOError: An error occurred while trying to read the file """ try: input_file = open(path, 'r') file_str = [] for eachline in input_file: file_str.append(eachline) input_file.close() return ''.join(file_str) except IOError, e: raise IOError(e.args[1] + ': ' + path) def main(): """Demonstrates the Email Migration API using EmailMigrationSample.""" usage = 'usage: %prog [options]' global options parser = OptionParser(usage=usage) parser.add_option('-d', '--domain', help="the Google Apps domain, e.g. 'domain.com'") parser.add_option('-e', '--email', help="the email account of the user or the admin, \ e.g. 'john.smith@domain.com'") parser.add_option('-p', '--password', help="the account password") parser.add_option('-u', '--username', help="the user account on which to perform operations. for\ non-admin users this will be their own account name. \ e.g. 'jane.smith'") parser.add_option('-f', '--file', help="the system path of an RFC822 format .txt file or\ directory containing multiple such files to be migrated") (options, args) = parser.parse_args() if (options.domain is None or options.email is None or options.password is None or options.username is None or options.file is None): parser.print_help() return options.file = options.file.strip() if not os.path.exists(options.file): print "Invalid file or directory path" return sample = EmailMigrationSample(domain=options.domain, email=options.email, password=options.password) sample.Migrate(options.file) if __name__ == '__main__': main() gdata-2.0.18/samples/apps/provisioning_oauth_example/0000750036466300116100000000000012156625015023002 5ustar afshareng00000000000000gdata-2.0.18/samples/apps/provisioning_oauth_example/js/0000750036466300116100000000000012156625015023416 5ustar afshareng00000000000000gdata-2.0.18/samples/apps/provisioning_oauth_example/js/index.js0000640036466300116100000000716712156622362025101 0ustar afshareng00000000000000// Copyright 2011, Google Inc. All Rights Reserved. // // 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. /** * @author pti@google.com (Prashant Tiwari) */ /** * @fileoverview Contains JS methods for index.html */ /** * Tracks the start index for the prev and next links */ var startIndex = 0; /** * Creates the users' table on each page. * * @param {Array} * entries The user entries to be displayed on the table. * @param {Int} * start The start index for items displayed in the table. * @param {Int} * size The table size. * @returns The table html. */ buildUserTable = function(entries, start, size) { var html = []; html.push(''); html.push('', '', '', '', '', ''); for ( var i = 0; i < size; i++) { if (start + i < entries.length) { var className = ((i % 2) == 0) ? 'odd' : 'even'; html.push(''); html.push(''); html.push(''); html.push(''); if (entries[start + i].admin == 'true') { html.push(''); } else { html.push(''); } html.push(''); } } html.push('
    #Account NameUser NameIs Admin
    ' + (start + i + 1) + '' + entries[start + i].username + '' + entries[start + i].given_name + ' ' + entries[start + i].family_name + 'Yes
    '); return html.join('\n'); }; /** * Creates the back and forth navigator links. * * @param {String} * pagingElements The element that holds the paged content. * @param {Int} * pagesize The page size. */ createNavigator = function(pagingElements, pagesize) { var paging_elements = pagingElements; var page_size = pagesize; return function() { var prev_link = document.getElementById('prev'); prev_link.onclick = function() { if (startIndex - page_size >= 0) { startIndex = startIndex - page_size; var user_div = document.getElementById('users') user_div.innerHTML = buildUserTable(user_entries, startIndex, page_size); } }; var next_link = document.getElementById('next'); next_link.onclick = function() { if (startIndex == 0 && startIndex - page_size > 0) { startIndex = startIndex - page_size; var user_div = document.getElementById('users') user_div.innerHTML = buildUserTable(user_entries, startIndex, page_size); } else if (startIndex + page_size + 1 <= paging_elements.length) { startIndex = startIndex + page_size; var user_div = document.getElementById('users') user_div.innerHTML = buildUserTable(user_entries, startIndex, page_size); } }; }; }; /** * Called when index.html loads */ function onload() { var user_div = document.getElementById('users') var entries_per_page = 25 user_div.innerHTML = buildUserTable(user_entries, 0, entries_per_page); new createNavigator(user_entries, entries_per_page)(); } gdata-2.0.18/samples/apps/provisioning_oauth_example/app.yaml0000640036466300116100000000033312156622362024450 0ustar afshareng00000000000000application: provisioning-oauth-example version: 1 runtime: python api_version: 1 handlers: - url: /css static_dir: css - url: /js static_dir: js - url: /images static_dir: images - url: /.* script: main.py gdata-2.0.18/samples/apps/provisioning_oauth_example/footer.html0000640036466300116100000000037412156622362025175 0ustar afshareng00000000000000 gdata-2.0.18/samples/apps/provisioning_oauth_example/index.html0000640036466300116100000000055312156622362025005 0ustar afshareng00000000000000{% include "header.html" %}
    {% include "footer.html" %} gdata-2.0.18/samples/apps/provisioning_oauth_example/main.py0000640036466300116100000001455312156622362024313 0ustar afshareng00000000000000#!/usr/bin/python2.4 # # Copyright 2011 Google Inc. All Rights Reserved. # # 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. """Sample app for Google Apps Provisioning API using OAuth. ProvisioningOAuthSample: Demonstrates the use of the Provisioning API with OAuth in a Google App Engine app. """ __author__ = 'pti@google.com (Prashant Tiwari)' import json import os import atom.http_interface import gdata.apps.service import gdata.auth from django.utils import simplejson from google.appengine.ext import webapp from google.appengine.ext.webapp import template from google.appengine.ext.webapp.util import run_wsgi_app from gdata.apps.service import AppsForYourDomainException INIT = { 'APP_NAME': 'googlecode-provisioningtester-v1', 'SCOPES': ['https://apps-apis.google.com/a/feeds'] } secret = None class MainPage(webapp.RequestHandler): """Defines the entry point for the App Engine app""" def get(self): global service try: service except: self.redirect('/login') return oauth_token = None json = None if self.request.get('two_legged_oauth'): two_legged_oauth = True app_title = 'Provisioning API Sample (2-legged OAuth)' start_over_text = 'Start over' else: two_legged_oauth = False app_title = 'Provisioning API Sample (3-legged OAuth)' start_over_text = 'Revoke token' try: if service: oauth_token = service.token_store.find_token('%20'.join(INIT['SCOPES'])) if isinstance(oauth_token, gdata.auth.OAuthToken) or isinstance( oauth_token, atom.http_interface.GenericToken): user_feed = service.RetrieveAllUsers() json = get_json_from_feed(user_feed) else: self.redirect('/login') return except AppsForYourDomainException, e: # Usually a Forbidden (403) when signed-in user isn't the admin. self.response.out.write(e.args[0].get('body')) else: template_values = { 'oauth_token': oauth_token, 'json': json, 'two_legged_oauth': two_legged_oauth, 'start_over_text': start_over_text, 'app_title': app_title } path = os.path.join(os.path.dirname(__file__), 'index.html') self.response.out.write(template.render(path, template_values)) def get_json_from_feed(user_feed): """Constructs and returns a JSON object from the given feed object Args: user_feed: A gdata.apps.UserFeed object Returns: A JSON object containing the first and last names of the domain users """ json = [] for entry in user_feed.entry: json.append({'given_name': entry.name.given_name, 'family_name': entry.name.family_name, 'username': entry.login.user_name, 'admin': entry.login.admin }) return simplejson.dumps(json) class DoLogin(webapp.RequestHandler): """Brings up the app's login page""" def get(self): path = os.path.join(os.path.dirname(__file__), 'login.html') self.response.out.write(template.render(path, None)) class DoAuth(webapp.RequestHandler): """Handles the entire OAuth flow for the app""" def post(self): global service global secret # Get instance of the AppsService for the given consumer_key (domain) service = gdata.apps.service.AppsService(source=INIT['APP_NAME'], domain=self.request.get('key')) two_legged_oauth = False if self.request.get('oauth') == 'two_legged_oauth': two_legged_oauth = True service.SetOAuthInputParameters( signature_method=gdata.auth.OAuthSignatureMethod.HMAC_SHA1, consumer_key=self.request.get('key'), consumer_secret=self.request.get('secret'), two_legged_oauth=two_legged_oauth) if two_legged_oauth: # Redirect to MainPage if 2-legged OAuth is requested self.redirect('/?two_legged_oauth=true') return request_token = service.FetchOAuthRequestToken( scopes=INIT['SCOPES'], oauth_callback=self.request.uri) secret = request_token.secret service.SetOAuthToken(request_token) # Send user to Google authorization page google_auth_page_url = service.GenerateOAuthAuthorizationURL() self.redirect(google_auth_page_url) def get(self): global service global secret # Extract the OAuth request token from the URL oauth_token = gdata.auth.OAuthTokenFromUrl(self.request.uri) if oauth_token: oauth_token.secret = secret oauth_token.oauth_input_params = service.GetOAuthInputParameters() service.SetOAuthToken(oauth_token) # Exchange the request token for an access token oauth_verifier = self.request.get('oauth_verifier', default_value='') access_token = service.UpgradeToOAuthAccessToken( oauth_verifier=oauth_verifier) # Store access_token to the service token_store for later access if access_token: service.current_token = access_token service.SetOAuthToken(access_token) self.redirect('/') class DoStartOver(webapp.RequestHandler): """Revokes the OAuth token if needed and starts over""" def get(self): global service two_legged_oauth = self.request.get('two_legged_oauth') # Revoke the token for 3-legged OAuth if two_legged_oauth != 'True': try: service.RevokeOAuthToken() except gdata.service.RevokingOAuthTokenFailed: pass except gdata.service.NonOAuthToken: pass finally: service.token_store.remove_all_tokens() service = None self.redirect('/') application = webapp.WSGIApplication([('/', MainPage), ('/do_auth', DoAuth), ('/start_over', DoStartOver), ('/login', DoLogin)], debug=True) def main(): run_wsgi_app(application) if __name__ == "__main__": main() gdata-2.0.18/samples/apps/provisioning_oauth_example/images/0000750036466300116100000000000012156625015024247 5ustar afshareng00000000000000gdata-2.0.18/samples/apps/provisioning_oauth_example/images/google-small.png0000640036466300116100000000461512156622362027350 0ustar afshareng00000000000000‰PNG  IHDR2ÒÏ^" pHYs.#.#x¥?vgAMA±Ž|ûQ“ cHRMz%€ƒùÿ€éu0ê`:˜o’_ÅF IDATxÚÜWip•Õ~Þó}÷[î–›…H€(—€¥EŠEÁ}ŶŠÁ¥#¸ Xuhé8"3âRA‹J¤ -*(ÐJв$,² &$†@’{s÷ûÝo9o hm;ÒÇÎô9ÎÒ)ÂŒ'-Ü9!4çÀ]6(ÄXúvS_êéBE¯ÛÞƒ%àØ °|/¢WƒÈ3ÁóÉþ#.ñèîû+³ »Ÿ.n-\'U ‹Rx¢˜cä˜ ª¯ûŠHÚAÙ“K¢/,Y4ßäöu5ýE2*ÉŒ¤‚ߨÜ{Îl_jGm¯Îç&éHÖ ÜïHHäó©}©}óAŠ'«´Ån?aÆS±1Å-Áß³Êpˆãé°D…ªÀaúWE’6Ê&<lóšWî«ï%kg¨šYÏRIƒ£ž?¶àeÍ:üpœûMVØuFŒ ò; #Y@«žŠxþ\8‡^BÀ:˜Óz’¢u{`hÌ ç1B'ìeåß·Ö+ë¼y«^{¡°;ž%½`§<)ð”p&hx)Ä{jyûáÊ~[äA‰ª€©0år ¨ø†L ƒS X¹¼@Èüg‰“K€[~ ZrÃQÑ8BMùÙe—+ÃÀ¤ˆ‚@SÙÊ×ʨNþÜà-™q7ÓÛþ¬èÆö/Iù|’LCƱ6å. ©˜Wé‡?—»lµE£ÚÅ?È/ϽCXkWJó r¿<ÜÉÉqk×kçd¥°ª_và-—Æß\SxݧÌR;Õq•¥Ës‚={õu¢G;ÍĶU¦°"T–ì¹L^™J˜ÔC·vg'l²¨×?O© +d`&ÓÉ|êþÏí‹Ú¹:e?Dö ±‘wUœ?b,tık{ ™Å舉y•&üÁà]³[’÷Ìɧç…RW•Éz&ß±Èdsø\ôþy2kÏzä)£ú͵%3¤'Ú/ûQrü“wͧµjkécŠ 3­æ=‹ÌÚ ¯¦…ýqcßß½œË¤Ã•Ý©:®'•ÁKp^²3¶ÈÐ<ÝX~GíÞ!õùç¥ÝêÝØ_«¨&P:DŠË‹G BÕl|ÑBæpÔ¸cîx<·Æèe(ºtšW`ú‰\Ù¾Œ|þ­ŽôEU6Eˆë„ñ§UfY£9}S?™¾îÀ~1x×ë7«6…WL쌄½Æ> Íøx_øý±£bÓ ýñA²½é‘®ý+KtïØ‡ª[é\|·VT«ªˆ`ƒ  °LëÁÐÍï´Å/:è*Ëo5]çzÝ-~ÝóïlFÐÇÑÔx¸Óâb8¾òþ`Ä\µnÁÏ Ø)P;ÃÌ‚AW‹ý÷øÒFù…¹XgÀ‘òˆ¤@gŸŠV„JËPhe¯©ˆX7tw!T:J„<»Þ*0Š#îØöDdP<Ô„¢%<Òrë6–…YÇu«ÄÌLD\¡«#s‰„3œ¬–¹Ö]m!Þ3z<Ðm†Ÿ&p„žù¤B }-CˆmXZ%4Ÿ‹šÚݥϼøÁ4ytËœvªÝžÑóICKÄQÄn‰òÐn(­na‰Ì1‚Šã… ƒ.‚~¯@J€ÄèLølË:æ±Cë7¦ü—ôO«G©šjÅå=et§–Ÿï`RÁ_2éL¶ÂÉ|_ëBš{Ú2 ÔºŒf#wp(õáûVäšQÿSüÖAœqâ—Øºø–ø¦EãRÝìw›N òw¯îq «mO.u/àÜåîIc‚†!HkËdE›i~¶mñQQ8@fŒe èC!ÀàP]‹‚ÉHsÏîE³ +Þë \?»dÔÌçÝ>·-2[æ?k(ö» ‚ A HЖ¶®õ•ù4 wR×2QñáœãóÇÑOæ¿°_(ëšóë|IÁ%>_a®(ù·Ùªõ<»¼¬OkIYÏÝ1• XøŽ²úÙžy¥×ùh“œØì ,œe¤Ô.…^Ñ™9à†Cëw¤mooŒ¬¼itlbS[`IÐ/6 ¬Z¾mû^ÕÍ<°ÒN ­Ÿ×°^ýªÞ³±KøŽþêÉ}æY'[ˆ¡ËòJï ]µ¸PÕ;~D•±‰(„¯!H› æÁ:ËÞYP7ˆÚuð 6pò^Ø®ÐTEž§©IeE—ªp‡¦z‘S:ifÃKÏ—-ï¬Ñò M–ÚçÇÇK§M1S›7ôKüáJ¸>HôaÁ.ˆ;EäÔK ‚§‚¥€ÂeR\@8ÿiV¢o›§ø¤k=°b ò¦÷67ÄæÙ[&ÞlÚz°L /åÄ#7>Ðf\7çŒÎiÃ|ÂÛÅ8œÿ44~`tÒZ¿-èt0_‚Hå£8þégCZ½Q®ùAfcùðH¢Á/ìÂ|ä¸Ây…áû¯rŠïûß TÉúå‹sŽ’n->7üÉU±¾Ÿ²tL¼è–‡"ñ¿,B4^‰¾Fî›­õ}± [^•4GNvüU¥*gŽéùúFºv›ÊéíL¾V§ýQ8T_÷¿!ò•¹Øa0÷X"ÍBOð©yïôãP}þ1ÚÉi®·qÖIEND®B`‚gdata-2.0.18/samples/apps/provisioning_oauth_example/css/0000750036466300116100000000000012156625015023572 5ustar afshareng00000000000000gdata-2.0.18/samples/apps/provisioning_oauth_example/css/index.css0000640036466300116100000000325012156622362025416 0ustar afshareng00000000000000/** * Copyright 2011, Google Inc. All Rights Reserved. * * 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. */ /** * author pti@google.com (Prashant Tiwari) */ body { font-family: 'Verdana', sans-serif; font-size: 11px; } #users table { width: 100%; border: dotted 1px #000000; font-family: 'Verdana', sans-serif; font-size: 11px; } #navigator #prev { cursor: pointer; } #navigator #next { cursor: pointer; } #users table .odd { background-color: #E5F2FB; } #users table .even { background-color: #FFFFFF; } #users td { padding: 3px; } #footer { text-align: center; padding: 10px 10px 10px 10px; } #header { padding: 5px 5px 0px 0px; } #titleholder { float: left; padding-top: 0px; margin-top: 0px; margin-left: 10px; color: #133BC1; } #titleholder h1 { font-family: Arial; font-weight: normal; font-size: 24px; } #titlecrumb { float: right; } #main { width: 100%; text-align: left; } .outer { margin-left: 25%; margin-right: 25%; } .serialno { text-align: center; } .username { font-style: italic; font-weight: bold; } .accountname { font-weight: bold; } .isadmin { font-weight: bold; text-align: center; } gdata-2.0.18/samples/apps/provisioning_oauth_example/header.html0000640036466300116100000000251012156622362025121 0ustar afshareng00000000000000
    gdata-2.0.18/samples/apps/provisioning_oauth_example/login.html0000640036466300116100000000514112156622362025004 0ustar afshareng00000000000000
    Authentication
    2-legged OAuth
    3-legged OAuth
    gdata-2.0.18/samples/apps/orgunit_quick_start_example.py0000640036466300116100000003543712156622362023545 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright 2011 Google Inc. All Rights Reserved. # # 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. """Sample for the Orgunits Provisioning API to demonstrate all methods. Usage: $ python orgunit_quick_start_example.py You can also specify the user credentials from the command-line $ python orgunit_quick_start_example.py --client_id [client_id] --client_secret [client_scret] --domain [domain] You can get help for command-line arguments as $ python orgunit_quick_start_example.py --help """ __author__ = 'Shraddha Gupta ' import getopt import sys import gdata.apps.client from gdata.apps.organization.client import OrganizationUnitProvisioningClient import gdata.client import gdata.gauth SCOPE = 'https://apps-apis.google.com/a/feeds/policies/' USER_AGENT = 'OrgUnitProvisioningQuickStartExample' class OrgUnitProvisioning(object): """Demonstrates all the functions of org units provisioning.""" def __init__(self, client_id, client_secret, domain): """ Args: client_id: [string] The clientId of the developer. client_secret: [string] The clientSecret of the developer. domain: [string] The domain on which the functions are to be performed. """ self.client_id = client_id self.client_secret = client_secret self.domain = domain def _AuthorizeClient(self): """Authorize the client for making API requests.""" self.token = gdata.gauth.OAuth2Token( client_id=self.client_id, client_secret=self.client_secret, scope=SCOPE, user_agent=USER_AGENT) uri = self.token.generate_authorize_url() print 'Please visit this URL to authorize the application:' print uri # Get the verification code from the standard input. code = raw_input('What is the verification code? ').strip() self.token.get_access_token(code) self.client = OrganizationUnitProvisioningClient( domain=self.domain, auth_token=self.token) def _PrintCustomerIdDetails(self, entry): """Prints the attributes for a CustomerIdEntry. Args: entry: [gdata.apps.organization.data.CustomerIdEntry] """ print '\nCustomer Id: %s' % (entry.customer_id) print 'Org unit name: %s' % (entry.org_unit_name) print 'Customer org unit name: %s' % (entry.customer_org_unit_name) print 'Org unit description: %s' % (entry.org_unit_description) print 'Customer org unit description: %s' % ( entry.customer_org_unit_description) def _PrintOrgUnitDetails(self, entry): """Prints the attributes for a OrgUnitEntry. Args: entry: [gdata.apps.organization.data.OrgUnitEntry] """ if entry: print '\nOrg unit name: %s' % (entry.org_unit_name) print 'Org unit path: %s' % (entry.org_unit_path) print 'Parent org unit path: %s' % (entry.parent_org_unit_path) print 'organization unit description: %s' % (entry.org_unit_description) print 'Block inheritance flag: %s' % (entry.org_unit_block_inheritance) else: print 'null entry' def _PrintOrgUserDetails(self, entry): """Prints the attributes for a OrgUserEntry. Args: entry: [gdata.apps.organization.data.OrgUserEntry] """ if entry: print 'Org user email: %s' % (entry.user_email) print 'Org unit path: %s' % (entry.org_unit_path) else: print 'null entry' def _GetChoice(self, for_field): """Gets input for boolean fields. Args: for_value : string field for which input is required Returns: boolean input for the given field """ choice = raw_input(('(Optional) Enter a choice for %s\n' '1-True 2-False ') % (for_field)) if choice == '1': return True return False def _GetOrgUnitPath(self): """Gets org_unit_path from user. Returns: string org_unit_path entered by the user """ org_unit_path = raw_input('Enter the org unit path ') if org_unit_path is None: print 'Organization path missing\n' return return org_unit_path def _RetrieveCustomerId(self): """Retrieves Groups Apps account customerId. Returns: CustomerIdEntry """ response = self.client.RetrieveCustomerId() self._PrintCustomerIdDetails(response) return response def _CreateOrgUnit(self, customer_id): """Creates a new org unit. Args: customer_id : string customer_id of organization """ name = raw_input('Enter a name for organization: ') parent_org_unit_path = raw_input('(default "/")' 'Enter full path of the parentental tree: ') description = raw_input('(Optional) Enter description of organization: ') block_inheritance = self._GetChoice('(default: False) block_inheritance: ') if not parent_org_unit_path: parent_org_unit_path = '/' try: orgunit_entry = self.client.CreateOrgUnit( customer_id=customer_id, name=name, parent_org_unit_path=parent_org_unit_path, description=description, block_inheritance=block_inheritance) self._PrintOrgUnitDetails(orgunit_entry) print 'Org unit Created' except gdata.client.RequestError, e: print e.reason, e.body return def _UpdateOrgUnit(self, customer_id): """Updates an org unit. Args: customer_id : string customer_id of organization """ org_unit_path = self._GetOrgUnitPath() if org_unit_path is None: return try: org_unit_entry = self.client.RetrieveOrgUnit(customer_id=customer_id, org_unit_path=org_unit_path) print self._PrintOrgUnitDetails(org_unit_entry) attributes = {1: 'org_name', 2: 'parent_org_unit_path', 3: 'description', 4: 'block_inheritance'} print attributes while True: attr = int(raw_input('\nEnter number(1-4) of attribute to be updated')) updated_val = raw_input('Enter updated value ') if attr == 1: org_unit_entry.org_unit_name = updated_val if attr == 2: org_unit_entry.parent_org_unit_path = updated_val if attr == 3: org_unit_entry.org_unit_description = updated_val if attr == 4: org_unit_entry.login.org_unit_block_inheritance = updated_val choice = raw_input('\nDo you want to update more attributes y/n') if choice != 'y': break self.client.UpdateOrgUnit(customer_id=customer_id, org_unit_path=org_unit_path, org_unit_entry=org_unit_entry) print 'Updated Org unit' except gdata.client.RequestError, e: print e.reason, e.body return def _RetrieveOrgUnit(self, customer_id): """Retrieves a single org unit. Args: customer_id : string customer_id of organization """ org_unit_path = self._GetOrgUnitPath() if org_unit_path is None: return try: response = self.client.RetrieveOrgUnit(customer_id=customer_id, org_unit_path=org_unit_path) self._PrintOrgUnitDetails(response) except gdata.client.RequestError, e: print e.reason, e.body return def _RetrieveAllOrgUnits(self, customer_id): """Retrieves all org units. Args: customer_id : string customer_id of organization """ try: response = self.client.RetrieveAllOrgUnits(customer_id=customer_id) for entry in response.entry: self._PrintOrgUnitDetails(entry) except gdata.client.RequestError, e: print e.reason, e.body return def _RetrieveSubOrgUnits(self, customer_id): """Retrieves all sub org units of an org unit. Args: customer_id : string customer_id of organization """ org_unit_path = self._GetOrgUnitPath() if org_unit_path is None: return try: response = self.client.RetrieveSubOrgUnits(customer_id=customer_id, org_unit_path=org_unit_path) if not response.entry: print 'No Sub organization units' return for entry in response.entry: self._PrintOrgUnitDetails(entry) except gdata.client.RequestError, e: print e.reason, e.body return def _MoveUsers(self, customer_id): """Moves users to an org unit. Args: customer_id : string customer_id of organization """ org_unit_path = self._GetOrgUnitPath() if org_unit_path is None: return users = [] while True: user = raw_input('Enter user email address ') if user: users.append(user) else: break if users is None: print 'No users given to move' return try: self.client.MoveUserToOrgUnit(customer_id=customer_id, org_unit_path=org_unit_path, users_to_move=users) print 'Moved users' print users except gdata.client.RequestError, e: print e.reason, e.body return def _DeleteOrgUnit(self, customer_id): """Deletes an org unit. Args: customer_id : string customer_id of organization """ org_unit_path = self._GetOrgUnitPath() if org_unit_path is None: return try: self.client.DeleteOrgUnit(customer_id=customer_id, org_unit_path=org_unit_path) print 'OrgUnit Deleted' except gdata.client.RequestError, e: print e.reason, e.body return def _UpdateOrgUser(self, customer_id): """Updates the orgunit of an org user. Args: customer_id : string customer_id of organization """ org_unit_path = self._GetOrgUnitPath() user_email = raw_input('Enter the email address') if None in (org_unit_path, user_email): print 'Organization path and email are both required\n' return try: org_user_entry = self.client.UpdateOrgUser(customer_id=customer_id, user_email=user_email, org_unit_path=org_unit_path) print 'Updated org unit for user' print self._PrintOrgUserDetails(org_user_entry) except gdata.client.RequestError, e: print e.reason, e.body return def _RetrieveOrgUser(self, customer_id): """Retrieves an organization user. Args: customer_id : string customer_id of organization """ user_email = raw_input('Enter the email address ') if user_email is None: print 'Email address missing\n' return try: response = self.client.RetrieveOrgUser(customer_id=customer_id, user_email=user_email) self._PrintOrgUserDetails(response) except gdata.client.RequestError, e: print e.reason, e.body return def _RetrieveOrgUnitUsers(self, customer_id): """Retrieves all org users of an org unit. Args: customer_id : string customer_id of organization """ org_unit_path = self._GetOrgUnitPath() if org_unit_path is None: return try: response = self.client.RetrieveOrgUnitUsers(customer_id=customer_id, org_unit_path=org_unit_path) if not response.entry: print 'No users in this organization' return for entry in response.entry: self._PrintOrgUserDetails(entry) except gdata.client.RequestError, e: print e.reason, e.body return def _RetrieveAllOrgUsers(self, customer_id): """Retrieves all org users. Args: customer_id : string customer_id of organization """ try: response = self.client.RetrieveAllOrgUsers(customer_id=customer_id) for entry in response.entry: self._PrintOrgUserDetails(entry) except gdata.client.RequestError, e: print e.reason, e.body return def Run(self): """Runs the sample by getting user input and taking appropriate action.""" self._AuthorizeClient() # CustomerId is required to perform any opertaion on org unit # Retrieve customerId beforehand. customer_id_entry = self._RetrieveCustomerId() customer_id = customer_id_entry.customer_id # List of all the functions and their descriptions functions_list = [ {'function': self._CreateOrgUnit, 'description': 'Create an org unit'}, {'function': self._UpdateOrgUnit, 'description': 'Update an org unit'}, {'function': self._RetrieveOrgUnit, 'description': 'Retrieve an org unit'}, {'function': self._RetrieveAllOrgUnits, 'description': 'Retrieve all org units'}, {'function': self._RetrieveSubOrgUnits, 'description': 'Retrieve sub org unit of a given org unit'}, {'function': self._MoveUsers, 'description': 'Move users to an org unit'}, {'function': self._DeleteOrgUnit, 'description': 'Delete an org unit'}, {'function': self._UpdateOrgUser, 'description': 'Update org unit of a user'}, {'function': self._RetrieveOrgUser, 'description': 'Retrieve an org user '}, {'function': self._RetrieveOrgUnitUsers, 'description': 'Retrieve users of an org unit'}, {'function': self._RetrieveAllOrgUsers, 'description': 'Retrieve all org users'} ] while True: print '\nChoose an option:\n0 - to exit' for i in range (0, len(functions_list)): print '%d - %s' % ((i+1), functions_list[i]['description']) choice = int(raw_input()) if choice == 0: break if choice < 0 or choice > 11: print 'Not a valid option!' continue functions_list[choice-1]['function'](customer_id=customer_id) def main(): """A menu driven application to demo all methods of org unit provisioning.""" usage = ('python orgunit_quick_start_example.py ' '--client_id [clientId] --client_secret [clientSecret] ' '--domain [domain]') # Parse command line options try: opts, args = getopt.getopt(sys.argv[1:], '', ['client_id=', 'client_secret=', 'domain=']) except getopt.error: print 'Usage: %s' % usage return client_id = None client_secret = None domain = None # Parse options for option, arg in opts: if option == '--client_id': client_id = arg elif option == '--client_secret': client_secret = arg elif option == '--domain': domain = arg if None in (client_id, client_secret, domain): print 'Usage: %s' % usage return try: orgunit_provisioning = OrgUnitProvisioning( client_id, client_secret, domain) except gdata.service.BadAuthentication: print 'Invalid user credentials given.' return orgunit_provisioning.Run() if __name__ == '__main__': main() gdata-2.0.18/samples/apps/emailsettings_example.py0000750036466300116100000003216612156622362022313 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright 2011 Google Inc. All Rights Reserved. # # 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. """A sample app for Google Apps Email Settings features. EmailSettingsSample: demonstrates getting and setting/updating email settings """ __author__ = 'Prashant Tiwari ' from optparse import OptionParser from gdata.apps.emailsettings.client import EmailSettingsClient #defaults for sendAs alias settings SEND_AS_NAME = 'test-alias' #update SEND_AS_ADDRESS to a valid account on your domain SEND_AS_ADDRESS = 'johndoe@domain.com' SEND_AS_REPLY_TO = 'replyto@example.com' SEND_AS_MAKE_DEFAULT = False #defaults for label settings LABEL_NAME = 'label' #defaults for forwarding settings #update FORWARD_TO to a valid account on your domain FORWARD_TO = 'account@domain.com' FORWARDING_ACTION = 'ARCHIVE' #defaults for pop settings POP_ENABLE_FOR = 'MAIL_FROM_NOW_ON' POP_ACTION = 'ARCHIVE' #defaults for signature settings SIGNATURE = "" #defaults for vacation settings VACATION_SUBJECT = "On vacation" VACATION_MESSAGE = "I'm on vacation, will respond when I return." VACATION_CONTACTS_ONLY = True #defaults for filter settings FILTER_FROM = 'me@domain.com' FILTER_TO = 'you@domain.com' FILTER_SUBJECT = 'subject' FILTER_HAS_THE_WORD = 'has' FILTER_DOES_NOT_HAVE_THE_WORD = 'no' FILTER_HAS_ATTACHMENT = True FILTER_SHOULD_MARK_AS_READ = True FILTER_SHOULD_ARCHIVE = True FILTER_LABEL = 'label' #defaults for general settings GENERAL_PAGE_SIZE = '50' GENERAL_ENABLE_SHORTCUTS = True GENERAL_ENABLE_ARROWS = True GENERAL_ENABLE_SNIPPETS = True GENERAL_ENABLE_UNICODE = True #defaults for language settings LANGUAGE = 'en-US' parser = None options = None class EmailSettingsSample(object): """EmailsSettingsSample object demos the Email Settings API.""" def __init__(self, domain, email, password, app): """Constructor for the EmailSettingsSample object. Takes an email, password and an app id corresponding to a google apps admin account to demo the Email Settings API. Args: domain: [string] The domain name (e.g. domain.com) email: [string] The e-mail address of a domain admin account. password: [string] The domain admin's password. app: [string] The app name of the form companyName-applicationName-versionID """ self.client = EmailSettingsClient(domain=domain) self.client.ClientLogin(email=email, password=password, source=app) def run(self, username, setting, method, args): """Method that invokes the EmailSettingsClient services Args: username: [string] The name of the account for whom to get/set settings setting: [string] The email setting to be got/set/updated method: [string] Specifies the get or set method """ if setting == 'label': if method == 'get': print "getting labels for %s...\n" % (username) print self.client.RetrieveLabels(username=username) elif method == 'set': print "creating label for %s...\n" % (username) print self.client.CreateLabel(username=username, name=LABEL_NAME) else: print "deleting labels isn't supported" elif setting == 'forwarding': if method == 'get': print "getting forwarding for %s...\n" % (username) print self.client.RetrieveForwarding(username) elif method == 'set': print "updating forwarding settings for %s...\n" % (username) print self.client.UpdateForwarding(username=username, enable=not(options.disable), forward_to=FORWARD_TO, action=FORWARDING_ACTION) else: print "deleting forwarding settings isn't supported" elif setting == 'sendas': if method == 'get': print "getting sendAs alias for %s...\n" % (username) print self.client.RetrieveSendAs(username=username) elif method == 'set': print "creating sendAs alias for %s...\n" % (username) print self.client.CreateSendAs(username=username, name=SEND_AS_NAME, address=SEND_AS_ADDRESS, reply_to=SEND_AS_REPLY_TO, make_default=SEND_AS_MAKE_DEFAULT) else: print "deleting send-as settings isn't supported" elif setting == 'pop': if method == 'get': print "getting pop settings for %s...\n" % (username) print self.client.RetrievePop(username=username) elif method == 'set': print "updating pop settings for %s...\n" % (username) print self.client.UpdatePop(username=username, enable=not(options.disable), enable_for=POP_ENABLE_FOR, action=POP_ACTION) else: print "deleting pop settings isn't supported" elif setting == 'signature': if method == 'get': print "getting signature for %s...\n" % (username) print self.client.RetrieveSignature(username=username) elif method == 'set': print "updating signature for %s...\n" % (username) print self.client.UpdateSignature(username=username, signature=SIGNATURE) else: print "deleting signature settings isn't supported" elif setting == 'vacation': if method == 'get': print "getting vacation settings for %s...\n" % (username) print self.client.RetrieveVacation(username=username) elif method == 'set': print "updating vacation settings for %s...\n" % (username) print self.client.UpdateVacation(username=username, enable=not(options.disable), subject=VACATION_SUBJECT, message=VACATION_MESSAGE, contacts_only=VACATION_CONTACTS_ONLY) else: print "deleting vacation settings isn't supported" elif setting == 'imap': if method == 'get': print "getting imap settings for %s...\n" % (username) print self.client.RetrieveImap(username) elif setting == 'set': print "updating imap settings for %s...\n" % (username) print self.client.UpdateImap(username=username, enable=not(options.disable)) else: print "deleting imap settings isn't supported" elif setting == 'filter': if method == 'get': print "getting email filters is not yet possible\n" parser.print_help() elif method == 'set': print "creating an email filter for %s...\n" % (username) print self.client.CreateFilter(username=username, from_address=FILTER_FROM, to_address=FILTER_TO, subject=FILTER_SUBJECT, has_the_word=FILTER_HAS_THE_WORD, does_not_have_the_word= FILTER_DOES_NOT_HAVE_THE_WORD, has_attachments=FILTER_HAS_ATTACHMENT, label=FILTER_LABEL, mark_as_read=FILTER_SHOULD_MARK_AS_READ, archive=FILTER_SHOULD_ARCHIVE) else: print "deleting filters isn't supported" elif setting == 'general': if method == 'get': print "getting general email settings is not yet possible\n" parser.print_help() elif method == 'set': print "updating general settings for %s...\n" % (username) print self.client.UpdateGeneralSettings(username=username, page_size=GENERAL_PAGE_SIZE, shortcuts= GENERAL_ENABLE_SHORTCUTS, arrows= GENERAL_ENABLE_ARROWS, snippets= GENERAL_ENABLE_SNIPPETS, use_unicode= GENERAL_ENABLE_UNICODE) else: print "deleting general settings isn't supported" elif setting == 'language': if method == 'get': print "getting language settings is not yet possible\n" parser.print_help() elif method == 'set': print "updating language for %s...\n" % (username) print self.client.UpdateLanguage(username=username, language=LANGUAGE) else: print "deleting language settings isn't supported" elif setting == 'webclip': if method == 'get': print "getting webclip settings is not yet possible\n" parser.print_help() elif method == 'get': print "updating webclip settings for %s...\n" % (username) print self.client.UpdateWebclip(username=username, enable=not(options.disable)) else: print "deleting webclip settings isn't supported" elif setting == 'delegation': if method == 'get': print "getting email delegates for %s..." % (username) print self.client.RetrieveEmailDelegates(username=username) elif method == 'set': address = args['delegationId'] print "adding %s as an email delegate to %s..." % (address, username) print self.client.AddEmailDelegate(username=username, address=address) else: address = args['delegationId'] print "deleting %s as an email delegate for %s..." % (address, username) print self.client.DeleteEmailDelegate(username=username, address=address) else: parser.print_help() def main(): """Demos the Email Settings API using the EmailSettingsSample object.""" usage = 'usage: %prog [options]' global parser global options parser = OptionParser(usage=usage) parser.add_option('--domain', help="The Google Apps domain, e.g. 'domain.com'.") parser.add_option('--email', help="The admin's email account, e.g. 'admin@domain.com'.") parser.add_option('--password', help="The admin's password.") parser.add_option('--app', help="The name of the app.") parser.add_option('--username', help="The user account on which to perform operations.") parser.add_option('--setting', choices=['filter', 'label', 'forwarding', 'sendas', 'pop', 'signature', 'vacation', 'imap', 'general', 'language', 'webclip', 'delegation'], help="The email setting to use. Choose from filter, label, \ forwarding, sendas, pop, signature, vacation, imap, \ general, language, webclip, and delegation.") parser.add_option('--method', default='get', choices=['get', 'set', 'delete'], help="Specify whether to get, set/update or delete \ setting. Choose between get (default), set, and delete.") parser.add_option('--disable', action="store_true", default=False, dest="disable", help="Disable a setting when using the set method with the\ --disable option. The default is to enable the setting.") parser.add_option('--delegationId', default=None, help="The emailId of the account to which email access has\ to be delegated. Required for adding or deleting an \ email delegate.") (options, args) = parser.parse_args() if (options.domain is None or options.email is None or options.password == None or options.username is None or options.app is None or options.setting is None or (options.setting == 'delegation' and options.method != 'get' and options.delegationId is None)): parser.print_help() return args = {'delegationId':options.delegationId} sample = EmailSettingsSample(options.domain, options.email, options.password, options.app) sample.run(options.username, options.setting, options.method, args) if __name__ == '__main__': main() gdata-2.0.18/samples/apps/email_settings_labels_filters.py0000640036466300116100000001741612156622362024010 0ustar afshareng00000000000000#!/usr/bin/python2.4 # # Copyright 2011 Google Inc. All Rights Reserved. # # 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. """Sample to demonstrate the Email Settings API's labels and filters creation. The sample creates labels and filters corresponding to festivals, which are some days ahead. """ __author__ = 'Gunjan Sharma ' from datetime import date from datetime import datetime import getopt import random import sys import time import gdata.apps.client import gdata.apps.emailsettings.client SCOPES = ['https://apps-apis.google.com/a/feeds/emailsettings/2.0/', 'https://apps-apis.google.com/a/feeds/user/'] FESTIVALS_LIST = [ { 'name': 'Christmas', 'date': '25/12', 'tags': ['Merry Christmas', 'Happy Christmas', 'Happy X-mas', 'Merry X-mas'] }, { 'name': 'Halloween', 'date': '31/10', 'tags': ['Happy Halloween', 'Trick or Treat', 'Guise'] }, { 'name': 'Sankranti', 'date': '14/01', 'tags': ['Makar Sankranti', 'Happy Sankranti'] }, { 'name': 'New Year', 'date': '31/12', 'tags': ['Happy New Year', 'New Year Best Wishes'] }] # Number of days left before a festival should be less than MAX_DAY_LEFT # to be considered for filter creation. MAX_DAYS_LEFT = 15 # Maximum retries to be done for exponential back-off MAX_RETRIES = 6 class FestivalSettingsException(Exception): """Exception class for FestivalSettings to show appropriate error message.""" def __init__(self, message): """Create new FestivalSettingsException with appropriate error message.""" super(FestivalSettingsException, self).__init__(message) class FestivalSettings(object): """Sample demonstrating how to create filter and label for domain's user.""" def __init__(self, consumer_key, consumer_secret, domain): """Create a new FestivalSettings object configured for a domain. Args: consumer_key: [string] The consumerKey of the domain. consumer_secret: [string] The consumerSecret of the domain. domain: [string] The domain whose user's POP settings to be changed. """ self.consumer_key = consumer_key self.consumer_secret = consumer_secret self.domain = domain def Authorize(self): """Asks the domain's admin to authorize. Access to the two APIs needs to be authorized, provisioning users and Gmail settings. The function creates clients for both APIs. """ self.email_settings_client = ( gdata.apps.emailsettings.client.EmailSettingsClient( domain=self.domain)) self.provisioning_client = gdata.apps.client.AppsClient(domain=self.domain) request_token = self.email_settings_client.GetOAuthToken( SCOPES, None, self.consumer_key, consumer_secret=self.consumer_secret) print request_token.GenerateAuthorizationUrl() raw_input('Manually go to the above URL and authenticate.' 'Press Return after authorization.') access_token = self.email_settings_client.GetAccessToken(request_token) self.email_settings_client.auth_token = access_token self.provisioning_client.auth_token = access_token def _CreateLabelsFiltersForUser(self, username): """Creates labels and filters for a domain's user. Args: username: [string] The user's username to create labels and filters for. """ today = date.today() for festival in FESTIVALS_LIST: d = datetime.strptime(festival['date'], '%d/%m').date() d = d.replace(year=today.year) days_left = (d - today).days d = d.replace(year=today.year+1) days_left_next_year = (d - today).days if ((0 <= days_left <= MAX_DAYS_LEFT) or (0 <= days_left_next_year <= MAX_DAYS_LEFT)): self._CreateFestivalSettingsForUser(username, festival, 1) def _CreateFestivalSettingsForUser(self, username, festival, tries): """Creates the festival wishes labels and filters for a domain's user. Args: username: [string] The user's username to create labels and filters for. festival: [dictionary] The details for the festival. tries: Number of times the operation has been retried. """ label_name = festival['name'] + ' Wishes' try: self.email_settings_client.CreateLabel(username=username, name=label_name) for tag in festival['tags']: self.email_settings_client.CreateFilter(username=username, has_the_word=tag, label=label_name) except gdata.client.RequestError, e: if e.status == 503 and tries < MAX_RETRIES: time.sleep(2 ^ tries + random.randint(0, 10)) self._CreateFestivalSettingsForUser(username, festival, tries+1) def Run(self): """Handles the flow of the sample. Asks application user for whom to create the festival wishes labels and filters. """ print 'Whom would you like to create labels for?' print ('1 - For all user in the domain.' '(WARNING: May take a long time depending on your domain size.)') print '2 - Single user' choice = raw_input('Enter your choice: ').strip() if choice.isdigit(): choice = int(choice) if choice == 1: users = self.provisioning_client.RetrieveAllUsers() for user in users.entry: self._CreateLabelsFiltersForUser(user.login.user_name) elif choice == 2: username = raw_input('Enter a valid username: ') self._CreateLabelsFiltersForUser(username) else: print 'Invalid choice' return print 'Labels and Filters created successfully' def PrintUsageString(): """Prints the correct call for running the sample.""" print ('python emailsettings_labels_filters.py' '--consumer_key [ConsumerKey] --consumer_secret [ConsumerSecret]' '--domain [domain]') def main(): """Runs the sample using an instance of FestivalSettings.""" try: opts, args = getopt.getopt(sys.argv[1:], '', ['consumer_key=', 'consumer_secret=', 'domain=']) except getopt.error, msg: PrintUsageString() sys.exit(1) consumer_key = '' consumer_secret = '' domain = '' for option, arg in opts: if option == '--consumer_key': consumer_key = arg elif option == '--consumer_secret': consumer_secret = arg elif option == '--domain': domain = arg if not (consumer_key and consumer_secret and domain): print 'Requires exactly three flags.' PrintUsageString() sys.exit(1) festival_wishes_labels = FestivalSettings( consumer_key, consumer_secret, domain) try: festival_wishes_labels.Authorize() except gdata.client.RequestError, e: if e.status == 400: raise FestivalSettingsException('Invalid consumer credentials') else: raise FestivalSettingsException('Unknown server error') sys.exit(1) try: festival_wishes_labels.Run() except gdata.client.RequestError, e: if e.status == 403: raise FestivalSettingsException('Invalid Domain') elif e.status == 400: raise FestivalSettingsException('Invalid username') else: print e.reason raise FestivalSettingsException('Unknown error') if __name__ == '__main__': main() gdata-2.0.18/samples/apps/org_unit_sites.py0000640036466300116100000003000512156622362020751 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright (C) 2007, 2009 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. __author__ = 'Gunjan Sharma ' import getopt import getpass import sys import time import atom import atom.data import gdata.apps.multidomain.client import gdata.apps.organization.service import gdata.apps.service import gdata.client import gdata.contacts.client import gdata.contacts.data import gdata.contacts.service import gdata.sites.client import gdata.sites.data #The title for the site ORG_SITE_TITLE = 'Organization Hierarchy' #Title for the users site USER_SITE_TITLE = 'Users' #The template URI for a site URI = 'https://sites.google.com/a/%s/%s/' #Description for the sites DESCRIPTION = 'Under Construction' #Theme for the sites THEME = 'slate' #Header for the new webpages HTML_HEADER = ('' '' '') #Footer for the new webpages HTML_FOOTER = '' #The template string for each user's html page TEMPLATE_USER_HTML = ('

    Name:

    %s

    Gender:

    %s

    ' '

    Address:

    %s

    Email:

    %s

    ') class OrgUnitAddressBook(object): """Creates organization unit sites from the domain.""" def __init__(self, email, password, domain): """Constructor for the OrgUnitSites object. Takes an email, password and domain to create a site map corresponding to the organization units in the domain. Args: email: [string] The email address of the admin. password: [string] The password corresponding to the admin account. domain: [string] The domain for which sites are to be made. Returns: A OrgUnitSites object used to run the site making. """ source = 'Making sites corresponding to org units' self.domain = domain # Create sites object self.sites_client = gdata.sites.client.SitesClient(source=source, domain=domain) self.sites_client.ClientLogin(email, password, self.sites_client.source); #Get the sites feed self.all_site_feed = self.sites_client.GetSiteFeed() # Create google contacts object self.profiles_client = gdata.contacts.client.ContactsClient(domain=domain) self.profiles_client.client_login(email, password, 'cp', service='cp') # Create an Organization Unit Client self.org_unit_client = gdata.apps.organization.service.OrganizationService( email=email, domain=domain, password=password) self.org_unit_client.ProgrammaticLogin() customer_id_set = self.org_unit_client.RetrieveCustomerId() self.customer_id = customer_id_set['customerId'] def _GetSiteName(self, site_title): """Returns the corresponding site_name. This is needed since the site name isn't the same as the title. Args: site_title: [string] The title for a site. Returns: A string which is the corresponding site_name. """ site_name = site_title.replace(' ', '-') site_name = site_name.lower() return site_name def _GetSiteURI(self, site_title): """Returns the corresponding uri to the site. Needed to get the link to a particular user page. Args: site_title: [string] The title for a site. Returns: A string which is the corresponding site's URI. """ uri = URI % (self.domain, self._GetSiteName(site_title)) return uri def _CreateSite(self, site_title, description=DESCRIPTION, theme=THEME): """Creates a site with the site_title, if it not already exists. Args: site_title: [string] The title for a site. description: [string] The description for the site. theme: [string] The theme that should be set for the site. Returns: A Content feed object for the created site. """ site_entry = None site_found = False site_name = self._GetSiteName(site_title) for site in self.all_site_feed.entry: if site.site_name.text == site_name: site_found = True site_entry = site break if site_found == False: try: site_entry = self.sites_client.CreateSite(site_title, description=description, theme=theme) except gdata.client.RequestError, error: print error self.sites_client.site = site_name return site_entry def _DeleteAllPages(self): '''Deletes all the pages in a site except home''' feed_uri = self.sites_client.make_content_feed_uri() while feed_uri: feed = self.sites_client.GetContentFeed() for entry in feed.entry: if entry.page_name.text != 'home': self.sites_client.Delete(entry) feed_uri = feed.FindNextLink() def _GetUsersProfileFeed(self): """Retrieves all the user's profile. Returns: A Dictionary of email address to ContentEntry objects """ profiles = [] feed_uri = self.profiles_client.GetFeedUri('profiles') while feed_uri: feed = self.profiles_client.GetProfilesFeed(uri=feed_uri) profiles.extend(feed.entry) feed_uri = feed.FindNextLink() profiles_dict = {} for profile in profiles: for email in profile.email: if email.primary and email.primary == 'true': profiles_dict[email.address] = profile break return profiles_dict def _CreateUserPageHTML(self, profile): """Creates HTML for a user profile. Args: profile: [gdata.contacts.data.ProfileEntry] It is the profile of the user whose HTML has to be created. Returns: A String which is the HTML code. """ address_string = '' email_string = '' for address in profile.structured_postal_address: address_string = '%s
  • %s

  • ' % (address_string, address) for email in profile.email: if email.primary and email.primary == 'true': email_string = '%s
  • %s

  • ' % (email_string, email.address) new_html = TEMPLATE_USER_HTML % (profile.name.full_name.text, str(profile.gender), address_string, email_string) return HTML_HEADER + new_html + HTML_FOOTER def _GetUserPageName(self, user_email): """Creates a page name for a particular user depending on the email address. Args: user_email: [string] The email address of the user. Returns: A String which defines the user page name. """ user_page_name = user_email.replace('@', '-') user_page_name = user_page_name.replace('.', '_') user_page_name = user_page_name.lower() return user_page_name def _CreateUserPages(self): """Makes all the user pages""" entry = self._CreateSite(USER_SITE_TITLE) #Delete all the pages self._DeleteAllPages() users_profile = self._GetUsersProfileFeed() users = self.org_unit_client.RetrieveAllOrgUsers(self.customer_id) for user in users: user_email = user['orgUserEmail'] user_profile = users_profile[user_email] new_html = self._CreateUserPageHTML(user_profile) user_page_name = self._GetUserPageName(user_email) self.sites_client.CreatePage('webpage', user_profile.name.full_name.text, html=new_html, page_name=user_page_name) def _GetOrgUnitPageHTML(self, path): """Creates HTML for a Org Unit Page. Args: profile: [string] Path of the Org Unit. Returns: A String which is the HTML code. """ domain_users = self.org_unit_client.RetrieveOrgUnitUsers(self.customer_id, path) new_html = '

    ' for user in domain_users: user_email = user['orgUserEmail'] user_page_name = self._GetUserPageName(user_email) site_uri = self._GetSiteURI(USER_SITE_TITLE) new_html = '%s

  • %s

  • ' % (new_html, site_uri + user_page_name, user_email) new_html = new_html + '

    ' return HTML_HEADER + new_html + HTML_FOOTER def _SetOrgSiteHomePage(self): """Sets up the home page for Org Unit Site""" new_html = self._GetOrgUnitPageHTML('/') home_path = self._GetUnitPath() home_feed = self.sites_client.GetContentFeed(uri=home_path) home_feed.entry[0].title.text = self.domain home_feed.entry[0].content.html = new_html self.sites_client.Update(home_feed.entry[0]) def _GetUnitPath(self, path=None): """Returns path to the parent unit Args: parent_path: [string] Path of the Parent Org Unit. Returns: A String which is the path to the parent. """ path_uri = '%s?path=/%s' if path: path = path.replace('+', '-') path = path.lower() path = 'home/' + path else: path = 'home' uri = path_uri % (self.sites_client.MakeContentFeedUri(), path) return uri def _CreateOrgUnitPages(self): """Creates all the org unit pages""" entry = self._CreateSite(ORG_SITE_TITLE) #Delete all the pages self._DeleteAllPages() self._SetOrgSiteHomePage() orgUnits = self.org_unit_client.RetrieveAllOrgUnits(self.customer_id) for unit in orgUnits: parent_uri = self._GetUnitPath(unit['parentOrgUnitPath']) parent_feed = self.sites_client.GetContentFeed(uri=parent_uri) new_html = self._GetOrgUnitPageHTML(unit['orgUnitPath']) self.sites_client.CreatePage('webpage', unit['name'], html=new_html, parent=parent_feed.entry[0]) def Run(self): """Controls the entire flow of the sites making process""" print 'Starting the process. This may take few minutes.' print 'Creating user pages...' self._CreateUserPages() print 'User pages created' print 'Creating Organization Unit Pages' self._CreateOrgUnitPages() print 'Your website is ready, visit it at: %s' % (self._GetSiteURI( ORG_SITE_TITLE)) def main(): """Runs the site making module using an instance of OrgUnitAddressBook""" # Parse command line options try: opts, args = getopt.getopt(sys.argv[1:], '', ['email=', 'pw=', 'domain=']) except getopt.error, msg: print ('python org_unit_sites.py --email [emailaddress] --pw [password]' ' --domain [domain]') sys.exit(2) email = '' password = '' domain = '' # Parse options for option, arg in opts: if option == '--email': email = arg elif option == '--pw': password = arg elif option == '--domain': domain = arg while not email: email = raw_input('Please enter admin email address (admin@example.com): ') while not password: sys.stdout.write('Admin Password: ') password = getpass.getpass() if not password: print 'Password cannot be blank.' while not domain: username, domain = email.split('@', 1) choice = raw_input('You have not given us the domain name. ' + 'Is it %s? (y/n)' % (domain)) if choice == 'n': domain = raw_input('Please enter domain name (domain.com): ') try: org_unit_address_book = OrgUnitAddressBook(email, password, domain) except gdata.service.BadAuthentication: print 'Invalid user credentials given.' return org_unit_address_book.Run() if __name__ == '__main__': main() gdata-2.0.18/samples/apps/multidomain_quick_start_example.py0000640036466300116100000003157412156622362024376 0ustar afshareng00000000000000#!/usr/bin/python2.4 # # Copyright 2011 Google Inc. All Rights Reserved. # # 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. __author__ = 'Gunjan Sharma ' import getopt import getpass import sys import gdata.apps.multidomain.client SCOPE = 'https://apps-apis.google.com/a/feeds/user/' USER_AGENT = 'MultiDomainQuickStartExample' class UserData(object): """Data corresponding to a single user.""" def __init__(self): self.email = '' self.first_name = '' self.last_name = '' self.password = '' self.confirm_password = 'temp' self.is_admin = '' self.hash_function = '' self.suspended = '' self.change_password = '' self.ip_whitelisted = '' self.quota = '' class MultiDomainQuickStartExample(object): """Demonstrates all the functions of multidomain user provisioning.""" def __init__(self, client_id, client_secret, domain): """Constructor for the MultiDomainQuickStartExample object. Takes a client_id, client_secret and domain to create an object for multidomain user provisioning. Args: client_id: [string] The clientId of the developer. client_secret: [string] The clientSecret of the developer. domain: [string] The domain on which the functions are to be performed. """ self.client_id = client_id self.client_secret = client_secret self.domain = domain token = gdata.gauth.OAuth2Token( client_id=self.client_id, client_secret=self.client_secret, scope=SCOPE, user_agent=USER_AGENT) uri = token.generate_authorize_url() print 'Please visit this URL to authorize the application:' print uri # Get the verification code from the standard input. code = raw_input('What is the verification code? ').strip() token.get_access_token(code) self.multidomain_client = ( gdata.apps.multidomain.client.MultiDomainProvisioningClient( domain=self.domain, auth_token=token)) def _PrintUserDetails(self, entry): """Prints all the information for a user entry. Args: entry: [UserEntry] User entry corresponding to a user """ print 'First Name: %s' % (entry.first_name) print 'Last Name: %s' % (entry.last_name) print 'Email: %s' % (entry.email) print 'Is Admin: %s' % (entry.is_admin) print 'Is Suspended: %s' % (entry.suspended) print 'Change password at next login: %s' % ( entry.change_password_at_next_login) print '\n' def _PrintAliasDetails(self, entry): """Prints all the information for a user alias entry. Args: entry: [AliasEntry] Alias entry correspoding to an alias """ print 'User Email: %s' % (entry.user_email) print 'Alias Email: %s' % (entry.alias_email) print '\n' def _GetChoice(self, for_field): """Gets a choice for a field. Args: for_field: [string] The field for which input is to be taken. Return: True/False/None: Depending on the choice made by the user. """ choice = int(raw_input(('Enter a choice for %s\n' '1-True 2-False 3-Default/Skip: ') % (for_field))) if choice == 1: return True elif choice == 2: return False return None def _TakeUserData(self, function='create'): """Takes input data for _UpdateUser and _CreateUser functions. Args: function: [string] representing the kind of function (create/update) from where this function was called. Return: user_data: [UserData] All data for a user. """ extra_stmt = '' if function == 'update': extra_stmt = '. Press enter to not update the field' user_data = UserData() while not user_data.email: user_data.email = raw_input('Enter a valid email address' '(username@domain.com): ') while not user_data.first_name: user_data.first_name = raw_input(('Enter first name for the user%s: ') % (extra_stmt)) if function == 'update': break while not user_data.last_name: user_data.last_name = raw_input(('Enter last name for the user%s: ') % (extra_stmt)) if function == 'update': break while not user_data.password == user_data.confirm_password: user_data.password = '' while not user_data.password: sys.stdout.write(('Enter password for the user%s: ') % (extra_stmt)) user_data.password = getpass.getpass() if function == 'update' and user_data.password.__len__() == 0: break if user_data.password.__len__() < 8: print 'Password must be at least 8 characters long' user_data.password = '' if function == 'update' and user_data.password.__len__() == 0: break sys.stdout.write('Confirm password: ') user_data.confirm_password = getpass.getpass() user_data.is_admin = self._GetChoice('is_admin') user_data.hash_function = raw_input('Enter a hash function or None: ') user_data.suspended = self._GetChoice('suspended') user_data.change_password = self._GetChoice('change_password') user_data.ip_whitelisted = self._GetChoice('ip_whitelisted') user_data.quota = raw_input('Enter a quota or None: ') if user_data.quota == 'None' or not user_data.quota.isdigit(): user_data.quota = None if user_data.hash_function == 'None': user_data.hash_function = None return user_data def _CreateUser(self): """Creates a new user account.""" user_data = self._TakeUserData() self.multidomain_client.CreateUser( user_data.email, user_data.first_name, user_data.last_name, user_data.password, user_data.is_admin, hash_function=user_data.hash_function, suspended=user_data.suspended, change_password=user_data.change_password, ip_whitelisted=user_data.ip_whitelisted, quota=user_data.quota) def _UpdateUser(self): """Updates a user.""" user_data = self._TakeUserData('update') user_entry = self.multidomain_client.RetrieveUser(user_data.email) if user_data.first_name: user_entry.first_name = user_data.first_name if user_data.last_name: user_entry.last_name = user_data.last_name if user_data.password: user_entry.password = user_data.password if not user_data.hash_function is None: user_entry.hash_function = user_data.hash_function if not user_data.quota is None: user_entry.quota = user_entry.quota if not user_data.is_admin is None: user_entry.is_admin = (str(user_data.is_admin)).lower() if not user_data.suspended is None: user_entry.suspended = (str(user_data.suspended)).lower() if not user_data.change_password is None: user_entry.change_password = (str(user_data.change_password)).lower() if not user_data.ip_whitelisted is None: user_entry.ip_whitelisted = (str(user_data.ip_whitelisted)).lower() self.multidomain_client.UpdateUser(user_data.email, user_entry) def _RenameUser(self): """Renames username of a user.""" old_email = '' new_email = '' while not old_email: old_email = raw_input('Enter old email address(username@domain.com): ') while not new_email: new_email = raw_input('Enter new email address(username@domain.com): ') self.multidomain_client.RenameUser(old_email, new_email) def _RetrieveSingleUser(self): """Retrieves a single user.""" email = '' while not email: email = raw_input('Enter a valid email address(username@domain.com): ') response = self.multidomain_client.RetrieveUser(email) self._PrintUserDetails(response) def _RetrieveAllUsers(self): """Retrieves all users from all the domains.""" response = self.multidomain_client.RetrieveAllUsers() for entry in response.entry: self._PrintUserDetails(entry) def _DeleteUser(self): """Deletes a user.""" email = '' while not email: email = raw_input('Enter a valid email address(username@domain.com): ') self.multidomain_client.DeleteUser(email) def _CreateUserAlias(self): """Creates a user alias.""" email = '' alias = '' while not email: email = raw_input('Enter a valid email address(username@domain.com): ') while not alias: alias = raw_input('Enter a valid alias email address' '(username@domain.com): ') self.multidomain_client.CreateAlias(email, alias) def _RetrieveAlias(self): """Retrieves a user corresponding to an alias.""" alias = '' while not alias: alias = raw_input('Enter a valid alias email address' '(username@domain.com): ') response = self.multidomain_client.RetrieveAlias(alias) self._PrintAliasDetails(response) def _RetrieveAllAliases(self): """Retrieves all user aliases for all users.""" response = self.multidomain_client.RetrieveAllAliases() for entry in response.entry: self._PrintAliasDetails(entry) def _RetrieveAllUserAliases(self): """Retrieves all user aliases of a user.""" email = '' while not email: email = raw_input('Enter a valid email address(username@domain.com): ') response = self.multidomain_client.RetrieveAllUserAliases(email) for entry in response.entry: self._PrintAliasDetails(entry) def _DeleteAlias(self): """Deletes a user alias.""" alias = '' while not alias: alias = raw_input('Enter a valid alias email address' '(username@domain.com): ') self.multidomain_client.DeleteAlias(alias) def Run(self): """Runs the sample by getting user input and takin appropriate action.""" #List of all the function and there description functions_list = [ {'function': self._CreateUser, 'description': 'Create a user'}, {'function': self._UpdateUser, 'description': 'Updating a user'}, {'function': self._RenameUser, 'description': 'Renaming a user'}, {'function': self._RetrieveSingleUser, 'description': 'Retrieve a single user'}, {'function': self._RetrieveAllUsers, 'description': 'Retrieve all users in all domains'}, {'function': self._DeleteUser, 'description': 'Deleting a User from a domain'}, {'function': self._CreateUserAlias, 'description': 'Create a User Alias in a domain'}, {'function': self._RetrieveAlias, 'description': 'Retrieve a User Alias in a domain'}, {'function': self._RetrieveAllAliases, 'description': 'Retrieve all User Aliases in a Domain'}, {'function': self._RetrieveAllUserAliases, 'description': 'Retrieve all User Aliases for a User'}, {'function': self._DeleteAlias, 'description': 'Delete a user alias from a domain'} ] while True: print 'Choose an option:\n0 - to exit' for i in range (0, len(functions_list)): print '%d - %s' % ((i+1), functions_list[i]['description']) choice = int(raw_input()) if choice == 0: break if choice < 0 or choice > len(functions_list): print 'Not a valid option!' continue functions_list[choice-1]['function']() def main(): """Runs the sample using an instance of MultiDomainQuickStartExample.""" # Parse command line options try: opts, args = getopt.getopt(sys.argv[1:], '', ['client_id=', 'client_secret=', 'domain=']) except getopt.error, msg: print ('python multidomain_provisioning_quick_start_example.py' '--client_id [clientId] --client_secret [clientSecret]' '--domain [domain]') sys.exit(2) client_id = '' client_secret = '' domain = '' # Parse options for option, arg in opts: if option == '--client_id': client_id = arg elif option == '--client_secret': client_secret = arg elif option == '--domain': domain = arg while not client_id: client_id = raw_input('Please enter a clientId: ') while not client_secret: client_secret = raw_input('Please enter a clientSecret: ') while not domain: domain = raw_input('Please enter domain name (example.com): ') try: multidomain_quick_start_example = MultiDomainQuickStartExample( client_id, client_secret, domain) except gdata.service.BadAuthentication: print 'Invalid user credentials given.' return multidomain_quick_start_example.Run() if __name__ == '__main__': main() gdata-2.0.18/samples/apps/create_update_group.py0000640036466300116100000001334112156622362021741 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright 2011 Google Inc. All Rights Reserved. # # 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. """Create group and update settings using two APIs. Sample to use the Groups Settings API in google-api-python client library http://code.google.com/p/google-api-python-client/source/checkout with the Groups Provisioning API in gdata-python-client library http://code.google.com/p/gdata-python-client/source/checkout to create a group and update its settings. Usage: $ python create_update_group.py """ __author__ = 'Shraddha Gupta ' import os import pprint import sys from apiclient.discovery import build import gdata.apps.groups.client import gflags import httplib2 from oauth2client.client import flow_from_clientsecrets from oauth2client.file import Storage from oauth2client.tools import run # CLIENT_SECRETS, name of a file containing the OAuth 2.0 information for this # application, including client_id and client_secret, which are found # on the API Access tab on the Google APIs # Console CLIENT_SECRETS = 'client_secrets.json' # Helpful message to display in the browser if the CLIENT_SECRETS file # is missing. MISSING_CLIENT_SECRETS_MESSAGE = """ WARNING: Please configure OAuth 2.0 To make this sample run you will need to populate the client_secrets.json file found at: %s with information from the APIs Console . """ % os.path.join(os.path.dirname(__file__), CLIENT_SECRETS) # Request a token for the scope of two APIs: # Groups Provisioning and Groups Settings APIs SCOPES = ('https://apps-apis.google.com/a/feeds/groups/ ' 'https://www.googleapis.com/auth/apps.groups.settings') FLOW = flow_from_clientsecrets(CLIENT_SECRETS, scope=SCOPES, message=MISSING_CLIENT_SECRETS_MESSAGE) FLAGS = gflags.FLAGS def GetOAuth2Token(client_id, client_secret, access_token, refresh_token): """Get the OAuth 2.0 token to be used with the Groups Provisioning API. Args: client_id: String client_id of the installed application client_secret: String client_secret of the installed application access_token: String access token obtained from OAuth 2.0 server flow refresh_token: String refresh token obtained with access token Returns: token: String OAuth 2.0 token adapted for the Groups Provisioning API. """ token = gdata.gauth.OAuth2Token(client_id=client_id, client_secret=client_secret, scope=SCOPES, access_token=access_token, refresh_token=refresh_token, user_agent='create-manage-group-sample') return token def CreateAndUpdateGroup(http, groups_client, domain): """Create a group and update its settings. Args: http: httplib2.Http authorized object groups_client: gdata.apps.groups.client.GroupsProvisioningClient authorized group provisioning client domain: String domain name """ group_id = raw_input('Enter the group id: ') group_name = raw_input('Enter the group name: ') group_description = raw_input('Enter the group description: ') email_permission = raw_input('Enter the email permission: ') if not (group_id and group_name): print 'One or more required fields missing: group id, group name' sys.exit(1) new_group = groups_client.CreateGroup(group_id=group_id, group_name=group_name, description=group_description, email_permission=email_permission) print 'Group Created %s' % new_group.group_id print 'Name: %s\nDescription %s\nEmail Permission %s' % ( new_group.group_name, new_group.description, new_group.email_permission) group_id = '%s@%s' % (new_group.group_id, domain) service = build('groupssettings', 'v1', http=http) # Get the resource 'group' from the set of resources of the API. group_resource = service.groups() body = {'showInGroupDirectory': True, 'whoCanViewGroup': 'ALL_IN_DOMAIN_CAN_VIEW', 'whoCanViewMembership': 'ALL_IN_DOMAIN_CAN_VIEW'} # Update the group properties g = group_resource.update(groupUniqueId=group_id, body=body).execute() print '\nUpdated Access Permissions to the group\n' pprint.pprint(g) def main(argv): """Demonstrates creation of a group and updation of its settings.""" storage = Storage('group.dat') credentials = storage.get() if credentials is None or credentials.invalid: print 'Credentials are invalid or do not exist.' credentials = run(FLOW, storage) # Create an httplib2.Http object to handle our HTTP requests and authorize it # with the valid credentials. http = httplib2.Http() http = credentials.authorize(http) domain = raw_input('Enter the domain: ') # Create an OAuth 2.0 token suitable for use with the GData client library oauth2token = GetOAuth2Token(credentials.client_id, credentials.client_secret, credentials.access_token, credentials.refresh_token) groups_client = oauth2token.authorize( gdata.apps.groups.client.GroupsProvisioningClient(domain=domain)) CreateAndUpdateGroup(http, groups_client, domain) if __name__ == '__main__': main(sys.argv) gdata-2.0.18/samples/apps/user_profile_contacts_2lo.py0000640036466300116100000001335012156622362023070 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright 2012 Google Inc. All Rights Reserved. # # 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. """Sample to retrieve profile and contacts of a domain user. The sample uses 2-legged OAuth to authorize clients of User Provisioning and Contacts API and retrieves user's profile and contacts. """ __author__ = 'Shraddha Gupta ' from optparse import OptionParser import gdata.apps.client import gdata.contacts.client import gdata.gauth class UserProfileAndContactsSample(object): """Class to demonstrate retrieval of domain user's profile and contacts.""" def __init__(self, consumer_key, consumer_secret): """Creates a new UserProfileAndContactsSample object for a domain. Args: consumer_key: String, consumer key of the domain. consumer_secret: String, consumer secret of the domain. """ self.consumer_key = consumer_key self.consumer_secret = consumer_secret self.domain = consumer_key def TwoLOAuthorize(self, admin_id): """Authorize clients of User Provisioning and Contacts API using 2LO. Args: admin_id: String, admin username for 2LO with the Provisioning API. """ requestor_id = '%s@%s' % (admin_id, self.consumer_key) two_legged_oauth_token = gdata.gauth.TwoLeggedOAuthHmacToken( self.consumer_key, self.consumer_secret, requestor_id) self.apps_client = gdata.apps.client.AppsClient(domain=self.domain) self.apps_client.auth_token = two_legged_oauth_token self.contacts_client = gdata.contacts.client.ContactsClient( domain=self.domain) self.contacts_client.auth_token = two_legged_oauth_token def PrintProfile(self, profile): """Prints the contents of a profile entry. Args: profile: gdata.contacts.data.ProfileEntry. """ for email in profile.email: if email.primary == 'true': print 'Email: %s (primary)' % email.address else: print 'Email: %s' % email.address if profile.name: print 'Name: %s' % profile.name.full_name.text if profile.nickname: print 'Nickname: %s' % profile.nickname.text if profile.occupation: print 'Occupation: %s' % profile.occupation.text if profile.gender: print 'Gender: %s' % profile.gender.value if profile.birthday: print 'Birthday: %s' % profile.birthday.when for phone_number in profile.phone_number: print 'Phone Number: %s' % phone_number.text def GetProfile(self, admin_id, user_id): """Retrieves the profile of a user. Args: admin_id: String, admin username. user_id: String, user whose profile is retrieved. Returns: profile_entry: gdata.contacts.data.ProfileEntry. """ requestor_id = '%s@%s' % (admin_id, self.domain) self.contacts_client.auth_token.requestor_id = requestor_id entry_uri = '%s/%s' % (self.contacts_client.GetFeedUri('profiles'), user_id) profile_entry = self.contacts_client.GetProfile(entry_uri) return profile_entry def GetContacts(self, user_id): """Retrieves the contacts of a user. Args: user_id: String, user whose contacts are retrieved. Returns: contacts: List of strings containing user contacts. """ requestor_id = '%s@%s' % (user_id, self.domain) self.contacts_client.auth_token.requestor_id = requestor_id contacts = [] try: contact_feed = self.contacts_client.GetContacts() for contact_entry in contact_feed.entry: contacts.append(contact_entry.title.text) except gdata.client.Unauthorized, e: print 'Error: %s %s' % (e.status, e.reason) return contacts def Run(self, admin_id, user_id): """Retrieves profile and contacts of a user. Args: admin_id: String, admin username. user_id: String, user whose information is retrieved. """ self.TwoLOAuthorize(admin_id) print 'Profile of user: %s' % user_id profile = self.GetProfile(admin_id, user_id) self.PrintProfile(profile) user = self.apps_client.RetrieveUser(user_id) print 'Is admin: %s' % user.login.admin print 'Suspended: %s' % user.login.suspended contacts = self.GetContacts(user_id) print '\nContacts of user ' for contact in contacts: print contact def main(): """Demonstrates retrieval of domain user's profile and contacts using 2LO.""" usage = ('Usage: %prog --consumer_key ' '--consumer_secret --admin_id ' '--user_id= ') parser = OptionParser(usage=usage) parser.add_option('--consumer_key', help='Domain name is also consumer key.') parser.add_option('--consumer_secret', help='Consumer secret of the domain.') parser.add_option('--admin_id', help='Username of admin.') parser.add_option('--user_id', help='Username of domain user.') (options, args) = parser.parse_args() if (not options.consumer_key or not options.consumer_secret or not options.admin_id or not options.user_id): parser.print_help() return 1 sample = UserProfileAndContactsSample(options.consumer_key, options.consumer_secret) sample.Run(options.admin_id, options.user_id) if __name__ == '__main__': main() gdata-2.0.18/samples/apps/provisioning_oauth2_example.py0000640036466300116100000001075312156622362023447 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright 2011 Google Inc. All Rights Reserved. # # 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. """Sample for the Provisioning API and the Email Settings API with OAuth 2.0.""" __author__ = 'Shraddha Gupta ' from optparse import OptionParser import gdata.apps import gdata.apps.emailsettings.client import gdata.apps.groups.client import gdata.client import gdata.gauth API_VERSION = '2.0' BASE_URL = '/a/feeds/group/%s' % API_VERSION SCOPE = ('https://apps-apis.google.com/a/feeds/groups/ ' 'https://apps-apis.google.com/a/feeds/emailsettings/2.0/') HOST = 'apps-apis.google.com' class OAuth2ClientSample(object): """OAuth2ClientSample object demos the use of OAuth2Token for retrieving Members of a Group and updating Email Settings for them.""" def __init__(self, domain, client_id, client_secret): """ Args: domain: string Domain name (e.g. domain.com) client_id: string Client_id of domain admin account. client_secret: string Client_secret of domain admin account. """ try: self.token = gdata.gauth.OAuth2Token(client_id=client_id, client_secret=client_secret, scope=SCOPE, user_agent='oauth2-provisioningv2') self.uri = self.token.generate_authorize_url() print 'Please visit this URL to authorize the application:' print self.uri # Get the verification code from the standard input. code = raw_input('What is the verification code? ').strip() self.token.get_access_token(code) except gdata.gauth.OAuth2AccessTokenError, e: print 'Invalid Access token, Check your credentials %s' % e exit(0) self.domain = domain self.baseuri = '%s/%s' % (BASE_URL, domain) self.client = gdata.apps.groups.client.GroupsProvisioningClient( domain=self.domain, auth_token=self.token) # Authorize the client. # This will add the Authorization header to all future requests. self.token.authorize(self.client) self.email_client = gdata.apps.emailsettings.client.EmailSettingsClient( domain=self.domain, auth_token=self.token) self.token.authorize(self.email_client) def create_filter(self, feed): """Creates a mail filter that marks as read all messages not containing Domain name as one of their words for each member of the group. Args: feed: GroupMemberFeed members whose emailsettings need to updated """ for entry in feed.entry: user_name, domain = entry.member_id.split('@', 1) if entry.member_type == 'User' and domain == self.domain: print 'creating filter for %s' % entry.member_id self.email_client.CreateFilter(user_name, does_not_have_the_word=self.domain, mark_as_read=True) elif entry.member_type == 'User': print 'User belongs to other Domain %s' %entry.member_id else: print 'Member is a group %s' %entry.member_id def run(self, group): feed = self.client.RetrieveAllMembers(group) self.create_filter(feed) def main(): """Demos the Provisioning API and the Email Settings API with OAuth 2.0.""" usage = 'usage: %prog [options]' parser = OptionParser(usage=usage) parser.add_option('--DOMAIN', help='Google Apps Domain, e.g. "domain.com".') parser.add_option('--CLIENT_ID', help='Registered CLIENT_ID of Domain.') parser.add_option('--CLIENT_SECRET', help='Registered CLIENT_SECRET of Domain.') parser.add_option('--GROUP', help='Group identifier') (options, args) = parser.parse_args() if None in (options.DOMAIN, options.CLIENT_ID, options.CLIENT_SECRET, options.GROUP): parser.print_help() return sample = OAuth2ClientSample(options.DOMAIN, options.CLIENT_ID, options.CLIENT_SECRET) sample.run(options.GROUP) if __name__ == '__main__': main() gdata-2.0.18/samples/apps/client_secrets.json0000640036466300116100000000042112156622362021242 0ustar afshareng00000000000000{ "installed": { "client_id": "[[INSERT CLIENT ID HERE]]", "client_secret": "[[INSERT CLIENT SECRET HERE]]", "redirect_uris": [], "auth_uri": "https://accounts.google.com/o/oauth2/auth", "token_uri": "https://accounts.google.com/o/oauth2/token" } } gdata-2.0.18/samples/calendar/0000750036466300116100000000000012156625015016147 5ustar afshareng00000000000000gdata-2.0.18/samples/calendar/calendarExample.py0000750036466300116100000006556212156622362021631 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright (C) 2009 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. __author__ = 'api.rboyd@gmail.com (Ryan Boyd)' try: from xml.etree import ElementTree except ImportError: from elementtree import ElementTree import gdata.calendar.data import gdata.calendar.client import gdata.acl.data import atom import getopt import sys import string import time class CalendarExample: def __init__(self, email, password): """Creates a CalendarService and provides ClientLogin auth details to it. The email and password are required arguments for ClientLogin. The CalendarService automatically sets the service to be 'cl', as is appropriate for calendar. The 'source' defined below is an arbitrary string, but should be used to reference your name or the name of your organization, the app name and version, with '-' between each of the three values. The account_type is specified to authenticate either Google Accounts or Google Apps accounts. See gdata.service or http://code.google.com/apis/accounts/AuthForInstalledApps.html for more info on ClientLogin. NOTE: ClientLogin should only be used for installed applications and not for multi-user web applications.""" self.cal_client = gdata.calendar.client.CalendarClient(source='Google-Calendar_Python_Sample-1.0') self.cal_client.ClientLogin(email, password, self.cal_client.source); def _PrintUserCalendars(self): """Retrieves the list of calendars to which the authenticated user either owns or subscribes to. This is the same list as is represented in the Google Calendar GUI. Although we are only printing the title of the calendar in this case, other information, including the color of the calendar, the timezone, and more. See CalendarListEntry for more details on available attributes.""" feed = self.cal_client.GetAllCalendarsFeed() print 'Printing allcalendars: %s' % feed.title.text for i, a_calendar in zip(xrange(len(feed.entry)), feed.entry): print '\t%s. %s' % (i, a_calendar.title.text,) def _PrintOwnCalendars(self): """Retrieves the list of calendars to which the authenticated user owns -- Although we are only printing the title of the calendar in this case, other information, including the color of the calendar, the timezone, and more. See CalendarListEntry for more details on available attributes.""" feed = self.cal_client.GetOwnCalendarsFeed() print 'Printing owncalendars: %s' % feed.title.text for i, a_calendar in zip(xrange(len(feed.entry)), feed.entry): print '\t%s. %s' % (i, a_calendar.title.text,) def _PrintAllEventsOnDefaultCalendar(self): """Retrieves all events on the primary calendar for the authenticated user. In reality, the server limits the result set intially returned. You can use the max_results query parameter to allow the server to send additional results back (see query parameter use in DateRangeQuery for more info). Additionally, you can page through the results returned by using the feed.GetNextLink().href value to get the location of the next set of results.""" feed = self.cal_client.GetCalendarEventFeed() print 'Events on Primary Calendar: %s' % (feed.title.text,) for i, an_event in zip(xrange(len(feed.entry)), feed.entry): print '\t%s. %s' % (i, an_event.title.text,) for p, a_participant in zip(xrange(len(an_event.who)), an_event.who): print '\t\t%s. %s' % (p, a_participant.email,) print '\t\t\t%s' % (a_participant.value,) if a_participant.attendee_status: print '\t\t\t%s' % (a_participant.attendee_status.value,) def _FullTextQuery(self, text_query='Tennis'): """Retrieves events from the calendar which match the specified full-text query. The full-text query searches the title and content of an event, but it does not search the value of extended properties at the time of this writing. It uses the default (primary) calendar of the authenticated user and uses the private visibility/full projection feed. Please see: http://code.google.com/apis/calendar/reference.html#Feeds for more information on the feed types. Note: as we're not specifying any query parameters other than the full-text query, recurring events returned will not have gd:when elements in the response. Please see the Google Calendar API query paramters reference for more info: http://code.google.com/apis/calendar/reference.html#Parameters""" print 'Full text query for events on Primary Calendar: \'%s\'' % ( text_query,) query = gdata.calendar.client.CalendarEventQuery(text_query=text_query) feed = self.cal_client.GetCalendarEventFeed(q=query) for i, an_event in zip(xrange(len(feed.entry)), feed.entry): print '\t%s. %s' % (i, an_event.title.text,) print '\t\t%s. %s' % (i, an_event.content.text,) for a_when in an_event.when: print '\t\tStart time: %s' % (a_when.start,) print '\t\tEnd time: %s' % (a_when.end,) def _DateRangeQuery(self, start_date='2007-01-01', end_date='2007-07-01'): """Retrieves events from the server which occur during the specified date range. This uses the CalendarEventQuery class to generate the URL which is used to retrieve the feed. For more information on valid query parameters, see: http://code.google.com/apis/calendar/reference.html#Parameters""" print 'Date range query for events on Primary Calendar: %s to %s' % ( start_date, end_date,) query = gdata.calendar.client.CalendarEventQuery(start_min=start_date, start_max=end_date) feed = self.cal_client.GetCalendarEventFeed(q=query) for i, an_event in zip(xrange(len(feed.entry)), feed.entry): print '\t%s. %s' % (i, an_event.title.text,) for a_when in an_event.when: print '\t\tStart time: %s' % (a_when.start,) print '\t\tEnd time: %s' % (a_when.end,) def _InsertCalendar(self, title='Little League Schedule', description='This calendar contains practice and game times', time_zone='America/Los_Angeles', hidden=False, location='Oakland', color='#2952A3'): """Creates a new calendar using the specified data.""" print 'Creating new calendar with title "%s"' % title calendar = gdata.calendar.data.CalendarEntry() calendar.title = atom.data.Title(text=title) calendar.summary = atom.data.Summary(text=description) calendar.where.append(gdata.calendar.data.CalendarWhere(value=location)) calendar.color = gdata.calendar.data.ColorProperty(value=color) calendar.timezone = gdata.calendar.data.TimeZoneProperty(value=time_zone) if hidden: calendar.hidden = gdata.calendar.data.HiddenProperty(value='true') else: calendar.hidden = gdata.calendar.data.HiddenProperty(value='false') new_calendar = self.cal_client.InsertCalendar(new_calendar=calendar) return new_calendar def _UpdateCalendar(self, calendar, title='New Title', color=None): """Updates the title and, optionally, the color of the supplied calendar""" print 'Updating the calendar titled "%s" with the title "%s"' % ( calendar.title.text, title) calendar.title = atom.data.Title(text=title) if color is not None: calendar.color = gdata.calendar.data.ColorProperty(value=color) updated_calendar = self.cal_client.Update(calendar) return updated_calendar def _DeleteAllCalendars(self): """Deletes all calendars. Note: the primary calendar cannot be deleted""" feed = self.cal_client.GetOwnCalendarsFeed() for entry in feed.entry: print 'Deleting calendar: %s' % entry.title.text try: self.cal_client.Delete(entry.GetEditLink().href) except gdata.client.RequestError, msg: if msg.body.startswith('Cannot remove primary calendar'): print '\t%s' % msg.body else: print '\tUnexpected Error: %s' % msg.body def _InsertSubscription(self, id='python.gcal.test%40gmail.com'): """Subscribes to the calendar with the specified ID.""" print 'Subscribing to the calendar with ID: %s' % id calendar = gdata.calendar.data.CalendarEntry() calendar.id = atom.data.Id(text=id) returned_calendar = self.cal_client.InsertCalendarSubscription(calendar) return returned_calendar def _UpdateCalendarSubscription(self, id='python.gcal.test%40gmail.com', color=None, hidden=None, selected=None): """Updates the subscription to the calendar with the specified ID.""" print 'Updating the calendar subscription with ID: %s' % id calendar_url = ( 'http://www.google.com/calendar/feeds/default/allcalendars/full/%s' % id) calendar_entry = self.cal_client.GetCalendarEntry(calendar_url) if color is not None: calendar_entry.color = gdata.calendar.data.ColorProperty(value=color) if hidden is not None: if hidden: calendar_entry.hidden = gdata.calendar.data.HiddenProperty(value='true') else: calendar_entry.hidden = gdata.calendar.data.HiddenProperty(value='false') if selected is not None: if selected: calendar_entry.selected = gdata.calendar.data.SelectedProperty(value='true') else: calendar_entry.selected = gdata.calendar.data.SelectedProperty(value='false') updated_calendar = self.cal_client.Update(calendar_entry) return updated_calendar def _DeleteCalendarSubscription(self, id='python.gcal.test%40gmail.com'): """Deletes the subscription to the calendar with the specified ID.""" print 'Deleting the calendar subscription with ID: %s' % id calendar_url = ( 'http://www.google.com/calendar/feeds/default/allcalendars/full/%s' % id) calendar_entry = self.cal_client.GetCalendarEntry(calendar_url) self.cal_client.Delete(calendar_entry.GetEditLink().href) def _InsertEvent(self, title='Tennis with Beth', content='Meet for a quick lesson', where='On the courts', start_time=None, end_time=None, recurrence_data=None): """Inserts a basic event using either start_time/end_time definitions or gd:recurrence RFC2445 icalendar syntax. Specifying both types of dates is not valid. Note how some members of the CalendarEventEntry class use arrays and others do not. Members which are allowed to occur more than once in the calendar or GData "kinds" specifications are stored as arrays. Even for these elements, Google Calendar may limit the number stored to 1. The general motto to use when working with the Calendar data API is that functionality not available through the GUI will not be available through the API. Please see the GData Event "kind" document: http://code.google.com/apis/gdata/elements.html#gdEventKind for more information""" event = gdata.calendar.data.CalendarEventEntry() event.title = atom.data.Title(text=title) event.content = atom.data.Content(text=content) event.where.append(gdata.data.Where(value=where)) if recurrence_data is not None: # Set a recurring event event.recurrence = gdata.data.Recurrence(text=recurrence_data) else: if start_time is None: # Use current time for the start_time and have the event last 1 hour start_time = time.strftime('%Y-%m-%dT%H:%M:%S.000Z', time.gmtime()) end_time = time.strftime('%Y-%m-%dT%H:%M:%S.000Z', time.gmtime(time.time() + 3600)) event.when.append(gdata.data.When(start=start_time, end=end_time)) new_event = self.cal_client.InsertEvent(event) return new_event def _InsertSingleEvent(self, title='One-time Tennis with Beth', content='Meet for a quick lesson', where='On the courts', start_time=None, end_time=None): """Uses the _InsertEvent helper method to insert a single event which does not have any recurrence syntax specified.""" new_event = self._InsertEvent(title, content, where, start_time, end_time, recurrence_data=None) print 'New single event inserted: %s' % (new_event.id.text,) print '\tEvent edit URL: %s' % (new_event.GetEditLink().href,) print '\tEvent HTML URL: %s' % (new_event.GetHtmlLink().href,) return new_event def _InsertRecurringEvent(self, title='Weekly Tennis with Beth', content='Meet for a quick lesson', where='On the courts', recurrence_data=None): """Uses the _InsertEvent helper method to insert a recurring event which has only RFC2445 icalendar recurrence syntax specified. Note the use of carriage return/newline pairs at the end of each line in the syntax. Even when specifying times (as opposed to only dates), VTIMEZONE syntax is not required if you use a standard Java timezone ID. Please see the docs for more information on gd:recurrence syntax: http://code.google.com/apis/gdata/elements.html#gdRecurrence """ if recurrence_data is None: recurrence_data = ('DTSTART;VALUE=DATE:20070501\r\n' + 'DTEND;VALUE=DATE:20070502\r\n' + 'RRULE:FREQ=WEEKLY;BYDAY=Tu;UNTIL=20070904\r\n') new_event = self._InsertEvent(title, content, where, recurrence_data=recurrence_data, start_time=None, end_time=None) print 'New recurring event inserted: %s' % (new_event.id.text,) print '\tEvent edit URL: %s' % (new_event.GetEditLink().href,) print '\tEvent HTML URL: %s' % (new_event.GetHtmlLink().href,) return new_event def _InsertQuickAddEvent(self, content="Tennis with John today 3pm-3:30pm"): """Creates an event with the quick_add property set to true so the content is processed as quick add content instead of as an event description.""" event = gdata.calendar.data.CalendarEventEntry() event.content = atom.data.Content(text=content) event.quick_add = gdata.calendar.data.QuickAddProperty(value='true') new_event = self.cal_client.InsertEvent(event) return new_event def _InsertSimpleWebContentEvent(self): """Creates a WebContent object and embeds it in a WebContentLink. The WebContentLink is appended to the existing list of links in the event entry. Finally, the calendar client inserts the event.""" # Create a WebContent object url = 'http://www.google.com/logos/worldcup06.gif' web_content = gdata.calendar.data.WebContent(url=url, width='276', height='120') # Create a WebContentLink object that contains the WebContent object title = 'World Cup' href = 'http://www.google.com/calendar/images/google-holiday.gif' type = 'image/gif' web_content_link = gdata.calendar.data.WebContentLink(title=title, href=href, link_type=type, web_content=web_content) # Create an event that contains this web content event = gdata.calendar.data.CalendarEventEntry() event.link.append(web_content_link) print 'Inserting Simple Web Content Event' new_event = self.cal_client.InsertEvent(event) return new_event def _InsertWebContentGadgetEvent(self): """Creates a WebContent object and embeds it in a WebContentLink. The WebContentLink is appended to the existing list of links in the event entry. Finally, the calendar client inserts the event. Web content gadget events display Calendar Gadgets inside Google Calendar.""" # Create a WebContent object url = 'http://google.com/ig/modules/datetime.xml' web_content = gdata.calendar.data.WebContent(url=url, width='300', height='136') web_content.web_content_gadget_pref.append( gdata.calendar.data.WebContentGadgetPref(name='color', value='green')) # Create a WebContentLink object that contains the WebContent object title = 'Date and Time Gadget' href = 'http://gdata.ops.demo.googlepages.com/birthdayicon.gif' type = 'application/x-google-gadgets+xml' web_content_link = gdata.calendar.data.WebContentLink(title=title, href=href, link_type=type, web_content=web_content) # Create an event that contains this web content event = gdata.calendar.data.CalendarEventEntry() event.link.append(web_content_link) print 'Inserting Web Content Gadget Event' new_event = self.cal_client.InsertEvent(event) return new_event def _UpdateTitle(self, event, new_title='Updated event title'): """Updates the title of the specified event with the specified new_title. Note that the UpdateEvent method (like InsertEvent) returns the CalendarEventEntry object based upon the data returned from the server after the event is inserted. This represents the 'official' state of the event on the server. The 'edit' link returned in this event can be used for future updates. Due to the use of the 'optimistic concurrency' method of version control, most GData services do not allow you to send multiple update requests using the same edit URL. Please see the docs: http://code.google.com/apis/gdata/reference.html#Optimistic-concurrency """ previous_title = event.title.text event.title.text = new_title print 'Updating title of event from:\'%s\' to:\'%s\'' % ( previous_title, event.title.text,) return self.cal_client.Update(event) def _AddReminder(self, event, minutes=10): """Adds a reminder to the event. This uses the default reminder settings for the user to determine what type of notifications are sent (email, sms, popup, etc.) and sets the reminder for 'minutes' number of minutes before the event. Note: you can only use values for minutes as specified in the Calendar GUI.""" for a_when in event.when: if len(a_when.reminder) > 0: a_when.reminder[0].minutes = minutes else: a_when.reminder.append(gdata.data.Reminder(minutes=str(minutes))) print 'Adding %d minute reminder to event' % (minutes,) return self.cal_client.Update(event) def _AddExtendedProperty(self, event, name='http://www.example.com/schemas/2005#mycal.id', value='1234'): """Adds an arbitrary name/value pair to the event. This value is only exposed through the API. Extended properties can be used to store extra information needed by your application. The recommended format is used as the default arguments above. The use of the URL format is to specify a namespace prefix to avoid collisions between different applications.""" event.extended_property.append( gdata.calendar.data.CalendarExtendedProperty(name=name, value=value)) print 'Adding extended property to event: \'%s\'=\'%s\'' % (name, value,) return self.cal_client.Update(event) def _DeleteEvent(self, event): """Given an event object returned for the calendar server, this method deletes the event. The edit link present in the event is the URL used in the HTTP DELETE request.""" self.cal_client.Delete(event.GetEditLink().href) def _PrintAclFeed(self): """Sends a HTTP GET to the default ACL URL (http://www.google.com/calendar/feeds/default/acl/full) and displays the feed returned in the response.""" feed = self.cal_client.GetCalendarAclFeed() print feed.title.text for i, a_rule in zip(xrange(len(feed.entry)), feed.entry): print '\t%s. %s' % (i, a_rule.title.text,) print '\t\t Role: %s' % (a_rule.role.value,) print '\t\t Scope %s - %s' % (a_rule.scope.type, a_rule.scope.value) def _CreateAclRule(self, username): """Creates a ACL rule that grants the given user permission to view free/busy information on the default calendar. Note: It is not necessary to specify a title for the ACL entry. The server will set this to be the value of the role specified (in this case "freebusy").""" print 'Creating Acl rule for user: %s' % username rule = gdata.calendar.data.CalendarAclEntry() rule.scope = gdata.acl.data.AclScope(value=username, type="user") roleValue = "http://schemas.google.com/gCal/2005#%s" % ("freebusy") rule.role = gdata.acl.data.AclRole(value=roleValue) aclUrl = "https://www.google.com/calendar/feeds/default/acl/full" returned_rule = self.cal_client.InsertAclEntry(rule, aclUrl) def _RetrieveAclRule(self, username): """Builds the aclEntryUri or the entry created in the previous example. The sends a HTTP GET message and displays the entry returned in the response.""" aclEntryUri = "http://www.google.com/calendar/feeds/" aclEntryUri += "default/acl/full/user:%s" % (username) entry = self.cal_client.GetCalendarAclEntry(aclEntryUri) print '\t%s' % (entry.title.text,) print '\t\t Role: %s' % (entry.role.value,) print '\t\t Scope %s - %s' % (entry.scope.type, entry.scope.value) return entry def _UpdateAclRule(self, entry): """Modifies the value of the role in the given entry and POSTs the updated entry. Note that while the role of an ACL entry can be updated, the scope can not be modified.""" print 'Update Acl rule: %s' % (entry.GetEditLink().href) roleValue = "http://schemas.google.com/gCal/2005#%s" % ("read") entry.role = gdata.acl.data.AclRole(value=roleValue) returned_rule = self.cal_client.Update(entry) def _DeleteAclRule(self, entry): """Given an ACL entry returned for the calendar server, this method deletes the entry. The edit link present in the entry is the URL used in the HTTP DELETE request.""" self.cal_client.Delete(entry.GetEditLink().href) def _batchRequest(self, updateEntry, deleteEntry): """Execute a batch request to create, update and delete an entry.""" print 'Executing batch request to insert, update and delete entries.' # feed that holds all the batch rquest entries request_feed = gdata.calendar.data.CalendarEventFeed() # creating an event entry to insert insertEntry = gdata.calendar.data.CalendarEventEntry() insertEntry.title = atom.data.Title(text='Python: batch insert') insertEntry.content = atom.data.Content(text='my content') start_time = time.strftime('%Y-%m-%dT%H:%M:%S.000Z', time.gmtime()) end_time = time.strftime('%Y-%m-%dT%H:%M:%S.000Z', time.gmtime(time.time() + 3600)) insertEntry.when.append(gdata.calendar.data.When(start=start_time, end=end_time)) insertEntry.batch_id = gdata.data.BatchId(text='insert-request') # add the insert entry to the batch feed request_feed.AddInsert(entry=insertEntry) if updateEntry: updateEntry.batch_id = gdata.data.BatchId(text='update-request') updateEntry.title = atom.data.Title(text='Python: batch update') # add the update entry to the batch feed request_feed.AddUpdate(entry=updateEntry) if deleteEntry: deleteEntry.batch_id = gdata.data.BatchId(text='delete-request') # add the delete entry to the batch feed request_feed.AddDelete(entry=deleteEntry) # submit the batch request to the server response_feed = self.cal_client.ExecuteBatch(request_feed, gdata.calendar.client.DEFAULT_BATCH_URL) # iterate the response feed to get the operation status for entry in response_feed.entry: print '\tbatch id: %s' % (entry.batch_id.text,) print '\tstatus: %s' % (entry.batch_status.code,) print '\treason: %s' % (entry.batch_status.reason,) if entry.batch_id.text == 'insert-request': insertEntry = entry elif entry.batch_id.text == 'update-request': updateEntry = entry return (insertEntry, updateEntry) def Run(self, delete='false'): """Runs each of the example methods defined above. Note how the result of the _InsertSingleEvent call is used for updating the title and the result of updating the title is used for inserting the reminder and again with the insertion of the extended property. This is due to the Calendar's use of GData's optimistic concurrency versioning control system: http://code.google.com/apis/gdata/reference.html#Optimistic-concurrency """ # Getting feeds and query results self._PrintUserCalendars() self._PrintOwnCalendars() self._PrintAllEventsOnDefaultCalendar() self._FullTextQuery() self._DateRangeQuery() # Inserting and updating events see = self._InsertSingleEvent() see_u_title = self._UpdateTitle(see, 'New title for single event') see_u_reminder = self._AddReminder(see_u_title, minutes=30) see_u_ext_prop = self._AddExtendedProperty(see_u_reminder, name='propname', value='propvalue') ree = self._InsertRecurringEvent() simple_web_content_event = self._InsertSimpleWebContentEvent() web_content_gadget_event = self._InsertWebContentGadgetEvent() quick_add_event = self._InsertQuickAddEvent() # Access Control List examples self._PrintAclFeed() self._CreateAclRule("user@gmail.com") entry = self._RetrieveAclRule("user@gmail.com") self._UpdateAclRule(entry) self._DeleteAclRule(entry) # Creating, updating and deleting calendars inserted_calendar = self._InsertCalendar() updated_calendar = self._UpdateCalendar(calendar=inserted_calendar) # Insert Subscription inserted_subscription = self._InsertSubscription() updated_subscription = self._UpdateCalendarSubscription(selected=False) # Execute a batch request (quick_add_event, see_u_ext_prop) = self._batchRequest(see_u_ext_prop, quick_add_event) # Delete entries if delete argument='true' if delete == 'true': print 'Deleting created events' self.cal_client.Delete(see_u_ext_prop) self.cal_client.Delete(ree) self.cal_client.Delete(simple_web_content_event) self.cal_client.Delete(web_content_gadget_event) self.cal_client.Delete(quick_add_event) print 'Deleting subscriptions' self._DeleteCalendarSubscription() print 'Deleting all calendars' self._DeleteAllCalendars() def main(): """Runs the CalendarExample application with the provided username and and password values. Authentication credentials are required. NOTE: It is recommended that you run this sample using a test account.""" # parse command line options try: opts, args = getopt.getopt(sys.argv[1:], "", ["user=", "pw=", "delete="]) except getopt.error, msg: print ('python calendarExample.py --user [username] --pw [password] ' + '--delete [true|false] ') sys.exit(2) user = '' pw = '' delete = 'false' # Process options for o, a in opts: if o == "--user": user = a elif o == "--pw": pw = a elif o == "--delete": delete = a if user == '' or pw == '': print ('python calendarExample.py --user [username] --pw [password] ' + '--delete [true|false] ') sys.exit(2) sample = CalendarExample(user, pw) sample.Run(delete) if __name__ == '__main__': main() gdata-2.0.18/samples/codesearch/0000750036466300116100000000000012156625015016476 5ustar afshareng00000000000000gdata-2.0.18/samples/codesearch/CodesearchExample.py0000750036466300116100000000420112156622362022426 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright (C) 2007 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. # This file demonstrates how to use the Google Data API's Python client library # to interface with the Codesearch service. __author__ = 'vbarathan@gmail.com (Prakash Barathan)' from gdata import service import gdata.codesearch.service import gdata import atom import getopt import sys class CodesearchExample: def __init__(self): """Creates a GData service instance to talk to Codesearch service.""" self.service = gdata.codesearch.service.CodesearchService( source='Codesearch_Python_Sample-1.0') def PrintCodeSnippets(self, query): """Prints the codesearch results for given query.""" feed = self.service.GetSnippetsFeed(query) print feed.title.text + " Results for '" + query + "'" print '============================================' for entry in feed.entry: print "" + entry.title.text for match in entry.match: print "\tline#" + match.line_number + ":" + match.text.replace('\n', '') print def main(): """The main function runs the CodesearchExample application with user specified query.""" # parse command line options try: opts, args = getopt.getopt(sys.argv[1:], "", ["query="]) except getopt.error, msg: print ('python CodesearchExample.py --query [query_text]') sys.exit(2) query = '' # Process options for o, a in opts: if o == "--query": query = a if query == '': print ('python CodesearchExample.py --query [query]') sys.exit(2) sample = CodesearchExample() sample.PrintCodeSnippets(query) if __name__ == '__main__': main() gdata-2.0.18/samples/webmastertools/0000750036466300116100000000000012156625015017450 5ustar afshareng00000000000000gdata-2.0.18/samples/webmastertools/SitesFeedSummary.py0000750036466300116100000000366712156622362023274 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright (C) 2008 Yu-Jie Lin # # 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. import gdata.webmastertools.service import gdata.service try: from xml.etree import ElementTree except ImportError: from elementtree import ElementTree import atom import getpass username = '' password = '' username = raw_input('Please enter your username: ') password = getpass.getpass() client = gdata.webmastertools.service.GWebmasterToolsService( email=username, password=password, source='PythonWebmasterToolsSample-1') print 'Logging in' client.ProgrammaticLogin() print 'Retrieving Sites feed' feed = client.GetSitesFeed() # Format the feed print print 'You have %d site(s), last updated at %s' % ( len(feed.entry), feed.updated.text) print print "%-25s %25s %25s" % ('Site', 'Last Updated', 'Last Crawled') print '='*80 def safeElementText(element): if hasattr(element, 'text'): return element.text return '' # Format each site for entry in feed.entry: print "%-25s %25s %25s" % ( entry.title.text.replace('http://', '')[:25], entry.updated.text[:25], safeElementText(entry.crawled)[:25]) print " Preferred: %-23s Indexed: %5s GeoLoc: %10s" % ( safeElementText(entry.preferred_domain)[:30], entry.indexed.text[:5], safeElementText(entry.geolocation)[:10]) print " Crawl rate: %-10s Verified: %5s" % ( safeElementText(entry.crawl_rate)[:10], entry.verified.text[:5]) print gdata-2.0.18/samples/webmastertools/SitemapsFeedSummary.py0000750036466300116100000000352612156622362023764 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright (C) 2008 Yu-Jie Lin # # 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. import gdata.webmastertools.service import gdata.service try: from xml.etree import ElementTree except ImportError: from elementtree import ElementTree import atom import getpass username = '' password = '' site_uri = '' username = raw_input('Please enter your username: ') password = getpass.getpass() site_uri = raw_input('Please enter your site url: ') client = gdata.webmastertools.service.GWebmasterToolsService( email=username, password=password, source='PythonWebmasterToolsSample-1') print 'Logging in' client.ProgrammaticLogin() print 'Retrieving Sitemaps feed' feed = client.GetSitemapsFeed(site_uri) # Format the feed print print 'You have %d sitemap(s), last updated at %s' % ( len(feed.entry), feed.updated.text) print print '='*80 def safeElementText(element): if hasattr(element, 'text'): return element.text return '' # Format each site for entry in feed.entry: print entry.title.text.replace('http://', '')[:80] print " Last Updated : %29s Status: %10s" % ( entry.updated.text[:29], entry.sitemap_status.text[:10]) print " Last Downloaded: %29s URL Count: %10s" % ( safeElementText(entry.sitemap_last_downloaded)[:29], safeElementText(entry.sitemap_url_count)[:10]) print gdata-2.0.18/samples/webmastertools/AddDeleteExampleDotCom.py0000750036466300116100000000634612156622362024275 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright (C) 2008 Yu-Jie Lin # # 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. import urllib import gdata.webmastertools.service import gdata.service try: from xml.etree import ElementTree except ImportError: from elementtree import ElementTree import atom import getpass username = '' password = '' username = raw_input('Please enter your username: ') password = getpass.getpass() client = gdata.webmastertools.service.GWebmasterToolsService( email=username, password=password, source='PythonWebmasterToolsSample-1') EXAMPLE_SITE = 'http://www.example.com/' EXAMPLE_SITEMAP = 'http://www.example.com/sitemap-index.xml' def safeElementText(element): if hasattr(element, 'text'): return element.text return '' print 'Logging in' client.ProgrammaticLogin() print print 'Adding site: %s' % EXAMPLE_SITE entry = client.AddSite(EXAMPLE_SITE) print print "%-25s %25s %25s" % ('Site', 'Last Updated', 'Last Crawled') print '='*80 print "%-25s %25s %25s" % ( entry.title.text.replace('http://', '')[:25], entry.updated.text[:25], safeElementText(entry.crawled)[:25]) print " Preferred: %-23s Indexed: %5s GeoLoc: %10s" % ( safeElementText(entry.preferred_domain)[:30], entry.indexed.text[:5], safeElementText(entry.geolocation)[:10]) print " Crawl rate: %-10s Verified: %5s" % ( safeElementText(entry.crawl_rate)[:10], entry.verified.text[:5]) # Verifying a site. This sample won't do this since we don't own example.com #client.VerifySite(EXAMPLE_SITE, 'htmlpage') # The following needs the ownership of the site #client.UpdateGeoLocation(EXAMPLE_SITE, 'US') #client.UpdateCrawlRate(EXAMPLE_SITE, 'normal') #client.UpdatePreferredDomain(EXAMPLE_SITE, 'preferwww') #client.UpdateEnhancedImageSearch(EXAMPLE_SITE, 'true') print print 'Adding sitemap: %s' % EXAMPLE_SITEMAP entry = client.AddSitemap(EXAMPLE_SITE, EXAMPLE_SITEMAP) print entry.title.text.replace('http://', '')[:80] print " Last Updated : %29s Status: %10s" % ( entry.updated.text[:29], entry.sitemap_status.text[:10]) print " Last Downloaded: %29s URL Count: %10s" % ( safeElementText(entry.sitemap_last_downloaded)[:29], safeElementText(entry.sitemap_url_count)[:10]) # Add a mobile sitemap #entry = client.AddMobileSitemap(EXAMPLE_SITE, 'http://.../sitemap-mobile-example.xml', 'XHTML') # Add a news sitemap, your site must be included in Google News. # See also http://google.com/support/webmasters/bin/answer.py?answer=42738 #entry = client.AddNewsSitemap(EXAMPLE_SITE, 'http://.../sitemap-news-example.xml', 'Label') print print 'Deleting sitemap: %s' % EXAMPLE_SITEMAP client.DeleteSitemap(EXAMPLE_SITE, EXAMPLE_SITEMAP) print print 'Deleting site: %s' % EXAMPLE_SITE client.DeleteSite(EXAMPLE_SITE) print gdata-2.0.18/samples/calendar_resource/0000750036466300116100000000000012156625015020056 5ustar afshareng00000000000000gdata-2.0.18/samples/calendar_resource/calendar_resource_example.py0000640036466300116100000001413312156622362025630 0ustar afshareng00000000000000#!/usr/bin/python2.4 # # Copyright 2011 Google Inc. All Rights Reserved. # # 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. """Sample app for Google Apps Calendar Resource features. CalendarResourceSample: Demonstrates the use of the Calendar Resource API """ __author__ = 'pti@google.com (Prashant Tiwari)' import getpass from gdata.calendar_resource.client import CalendarResourceClient class CalendarResourceSample(object): def __init__(self, domain, email, password): """Constructor for the CalendarResourceSample object. Construct a CalendarResourceSample with the given args. Args: domain: The domain name ("domain.com") email: The email account of the user or the admin ("john@domain.com") password: The domain admin's password """ self.client = CalendarResourceClient(domain=domain) self.client.ClientLogin(email=email, password=password, source='googlecode-calendarresourcesample-v1') def create(self, resource_properties): """Creates a calendar resource with the given resource_properties Args: resource_properties: A dictionary of calendar resource properties """ print 'Creating a new calendar resource with id %s...' % ( resource_properties['resource_id']) print self.client.CreateResource( resource_id=resource_properties['resource_id'], resource_common_name=resource_properties['resource_name'], resource_description=resource_properties['resource_description'], resource_type=resource_properties['resource_type']) def get(self, resource_id=None): """Retrieves the calendar resource with the given resource_id Args: resource_id: The optional calendar resource identifier """ if resource_id: print 'Retrieving the calendar resource with id %s...' % (resource_id) print self.client.GetResource(resource_id=resource_id) else: print 'Retrieving all calendar resources...' print self.client.GetResourceFeed() def update(self, resource_properties): """Updates the calendar resource with the given resource_properties Args: resource_properties: A dictionary of calendar resource properties """ print 'Updating the calendar resource with id %s...' % ( resource_properties['resource_id']) print self.client.UpdateResource( resource_id=resource_properties['resource_id'], resource_common_name=resource_properties['resource_name'], resource_description=resource_properties['resource_description'], resource_type=resource_properties['resource_type']) def delete(self, resource_id): """Deletes the calendar resource with the given resource_id Args: resource_id: The unique calendar resource identifier """ print 'Deleting the calendar resource with id %s...' % (resource_id) self.client.DeleteResource(resource_id) print 'Calendar resource successfully deleted.' def main(): """Demonstrates the Calendar Resource API using CalendarResourceSample.""" domain = None admin_email = None admin_password = None do_continue = 'y' print("Google Apps Calendar Resource API Sample\n\n") while not domain: domain = raw_input('Google Apps domain: ') while not admin_email: admin_email = '%s@%s' % (raw_input('Administrator username: '), domain) while not admin_password: admin_password = getpass.getpass('Administrator password: ') sample = CalendarResourceSample(domain=domain, email=admin_email, password=admin_password) while do_continue.lower() != 'n': do_continue = call_service(sample) def call_service(sample): """Calls the service methods on the user input""" operation = None while operation not in ['c', 'C', 'g', 'G', 'u', 'U', 'd', 'D', 'q', 'Q']: operation = raw_input('Do [c=create|g=get|u=update|d=delete|q=quit]: ') operation = operation.lower() if operation == 'q': return 'n' resource_properties = get_input(operation) if operation == 'c': sample.create(resource_properties) elif operation == 'g': sample.get(resource_properties['resource_id']) elif operation == 'u': sample.update(resource_properties) elif operation == 'd': sample.delete(resource_properties['resource_id']) do_continue = None while do_continue not in ['', 'y', 'Y', 'n', 'N']: do_continue = raw_input('Want to continue (Y/n): ') if do_continue == '': do_continue = 'y' return do_continue.lower() def get_input(operation): """Gets user input from console""" resource_id = None resource_name = None resource_description = None resource_type = None if operation == 'g': resource_id = raw_input('Resource id (leave blank to get all resources): ') else: while not resource_id: resource_id = raw_input('Resource id: ') if operation == 'c': resource_name = raw_input('Resource common name (recommended): ') resource_description = raw_input('Resource description (recommended): ') resource_type = raw_input('Resource type (recommended): ') elif operation == 'u': resource_name = raw_input( 'New resource common name (leave blank if no change): ') resource_description = raw_input( 'New resource description (leave blank if no change): ') resource_type = raw_input('New resource type (leave blank if no change): ') resource_properties = {'resource_id': resource_id, 'resource_name': resource_name, 'resource_description': resource_description, 'resource_type': resource_type} return resource_properties if __name__ == '__main__': main() gdata-2.0.18/samples/contentforshopping/0000750036466300116100000000000012156625015020327 5ustar afshareng00000000000000gdata-2.0.18/samples/contentforshopping/ca_list.py0000640036466300116100000000245512156622362022330 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright 2009 Google Inc. All Rights Reserved. # # 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. import getpass from gdata.contentforshopping.client import ContentForShoppingClient # Gather merchant information account_id = raw_input('Merchant Account ID? ').strip() email = raw_input('Google Email Address? ').strip() # Create a client client = ContentForShoppingClient(account_id) # Perform programmatic login client.client_login(email, getpass.getpass('Google Password? '), 'Shopping API for Content sample', 'structuredcontent') # Get the feed of client accounts client_account_feed = client.get_client_accounts() # Display the title and self link for each client account for client_account in client_account_feed.entry: print client_account.title.text, client_account.GetSelfLink().href gdata-2.0.18/samples/contentforshopping/add_product.py0000640036466300116100000000307712156622362023203 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright 2009 Google Inc. All Rights Reserved. # # 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. import getpass from gdata.contentforshopping.data import build_entry from gdata.contentforshopping.client import ContentForShoppingClient # Gather merchant information account_id = raw_input('Merchant Account ID? ').strip() email = raw_input('Google Email Address? ').strip() # Create a client client = ContentForShoppingClient(account_id) # Perform programmatic login client.client_login(email, getpass.getpass('Google Password? '), 'Shopping API for Content sample', 'structuredcontent') # Generate a product entry product_entry = build_entry( product_id='ipod2', target_country = 'US', content_language = 'EN', title='iPod Nano 8GB', content='A nice small mp3 player', price='149', price_unit='USD', shipping_price = '5', shipping_price_unit = 'USD', tax_rate='17.5', condition = 'new', link = 'http://pseudoscience.co.uk/google4e823e35f032f011.html', identifier_exists='hello' ) # Post it to the service client.insert_product(product_entry) gdata-2.0.18/samples/contentforshopping/add_batch_products.py0000640036466300116100000000342512156622362024524 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright 2009 Google Inc. All Rights Reserved. # # 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. import getpass from gdata.contentforshopping.data import build_entry from gdata.contentforshopping.client import ContentForShoppingClient # Gather merchant information account_id = raw_input('Merchant Account ID? ').strip() email = raw_input('Google Email Address? ').strip() # Create a client client = ContentForShoppingClient(account_id) # Perform programmatic login client.client_login(email, getpass.getpass('Google Password? '), 'Shopping API for Content sample', 'structuredcontent') products = [] for color in ['red', 'green', 'white', 'black', 'purple', 'brown', 'yellow', 'orange', 'magenta']: # Generate a product entry product_entry = build_entry( product_id='ipod%s' % color, target_country = 'US', content_language = 'EN', title='iPod Nano 8GB, %s' % color, content='A nice small mp3 player, in %s' % color, price='149', price_unit='USD', shipping_price = '5', shipping_price_unit = 'USD', tax_rate='17.5', condition = 'new', link = 'http://pseudoscience.co.uk/google4e823e35f032f011.html', color = color, ) products.append(product_entry) # Post it to the service client.insert_products(products) gdata-2.0.18/samples/contentforshopping/list_products.py0000640036466300116100000000253212156622362023604 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright 2009 Google Inc. All Rights Reserved. # # 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. import getpass from gdata.contentforshopping.client import ContentForShoppingClient # Gather merchant information account_id = raw_input('Merchant Account ID? ').strip() email = raw_input('Google Email Address? ').strip() # Create a client client = ContentForShoppingClient(account_id) # Perform programmatic login client.client_login(email, getpass.getpass('Google Password? '), 'Shopping API for Content sample', 'structuredcontent') # Get the products list from the products feed product_feed = client.get_products() print 'Listing: %s result(s)' % product_feed.total_results.text # Each product is an element in the feed's entry (a list) for product in product_feed.entry: print '- %s: %s' % (product.title.text, product.content.text) gdata-2.0.18/samples/contentforshopping/ca_insert.py0000640036466300116100000000271212156622362022655 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright 2009 Google Inc. All Rights Reserved. # # 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. import getpass from atom.data import Title from gdata.contentforshopping.client import ContentForShoppingClient from gdata.contentforshopping.data import ClientAccount, AdultContent # Gather merchant information account_id = raw_input('Merchant Account ID? ').strip() email = raw_input('Google Email Address? ').strip() # Create a client client = ContentForShoppingClient(account_id) # Perform programmatic login client.client_login(email, getpass.getpass('Google Password? '), 'Shopping API for Content sample', 'structuredcontent') # Create 10 accounts for i in range(10): client_account = ClientAccount() client_account.title = Title('Test Account %s' % (i + 1)) client_account.adult_content = AdultContent('no') # Insert the client account client.insert_client_account(client_account) # Display something to the user print i + 1, '/', 10 gdata-2.0.18/samples/finance/0000750036466300116100000000000012156625015016001 5ustar afshareng00000000000000gdata-2.0.18/samples/finance/test_finance.py0000750036466300116100000002504112156622362021024 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright (C) 2009 Tan Swee Heng # # 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. __author__ = 'thesweeheng@gmail.com' from gdata.finance.service import \ FinanceService, PortfolioQuery, PositionQuery from gdata.finance import \ PortfolioEntry, PortfolioData, TransactionEntry, TransactionData, \ Price, Commission, Money import datetime import sys def PrintReturns(pfx, d): """Print returns.""" print pfx, '%1.5f(1w) %1.5f(4w) %1.5f(3m) %1.5f(YTD)' % tuple( float(i) for i in (d.return1w, d.return4w, d.return3m, d.returnYTD)) pfx = ' ' * len(pfx) print pfx, '%1.5f(1y) %1.5f(3y) %1.5f(5y) %1.5f(overall)' % tuple( float(i) for i in (d.return1y, d.return3y, d.return5y, d.return_overall)) PrRtn = PrintReturns def PrintTransactions(transactions): """Print transactions.""" print " Transactions:" fmt = ' %4s %-23s %-10s %6s %-11s %-11s' print fmt % ('ID','Date','Type','Shares','Price','Commission') for txn in transactions: d = txn.transaction_data print fmt % (txn.transaction_id, d.date or '----', d.type, d.shares, d.price.money[0], d.commission.money[0]) if d.notes: print " Notes:", d.notes print def PrintPosition(pos, with_returns=False): """Print single position.""" print ' Position :', pos.position_title print ' Ticker ID :', pos.ticker_id print ' Symbol :', pos.symbol print ' Last updated :', pos.updated.text d = pos.position_data print ' Shares :', d.shares if with_returns: print ' Gain % :', d.gain_percentage PrRtn(' Returns :', d) print ' Cost basis :', d.cost_basis print ' Days gain :', d.days_gain print ' Gain :', d.gain print ' Market value :', d.market_value print if pos.transactions: print " \n" PrintTransactions(pos.transactions) print " \n" def PrintPositions(positions, with_returns=False): for pos in positions: PrintPosition(pos, with_returns) def PrintPortfolio(pfl, with_returns=False): """Print single portfolio.""" print 'Portfolio Title :', pfl.portfolio_title print 'Portfolio ID :', pfl.portfolio_id print ' Last updated :', pfl.updated.text d = pfl.portfolio_data print ' Currency :', d.currency_code if with_returns: print ' Gain % :', d.gain_percentage PrRtn(' Returns :', d) print ' Cost basis :', d.cost_basis print ' Days gain :', d.days_gain print ' Gain :', d.gain print ' Market value :', d.market_value print if pfl.positions: print " \n" PrintPositions(pfl.positions, with_returns) print " \n" def PrintPortfolios(portfolios, with_returns=False): for pfl in portfolios: PrintPortfolio(pfl, with_returns) def ShowCallDetails(meth): def wrap(*args, **kwargs): print '@', meth.__name__, args[1:], kwargs meth(*args, **kwargs) return wrap class FinanceTester(object): def __init__(self, email, password): self.client = FinanceService(source='gdata-finance-test') self.client.ClientLogin(email, password) def GetPortfolios(self, with_returns=False, inline_positions=False): query = PortfolioQuery() query.returns = with_returns query.positions = inline_positions return self.client.GetPortfolioFeed(query=query).entry def GetPositions(self, portfolio, with_returns=False, inline_transactions=False): query = PositionQuery() query.returns = with_returns query.transactions = inline_transactions return self.client.GetPositionFeed(portfolio, query=query).entry def GetTransactions(self, position=None, portfolio=None, ticker=None): if position: feed = self.client.GetTransactionFeed(position) elif portfolio and ticker: feed = self.client.GetTransactionFeed( portfolio_id=portfolio.portfolio_id, ticker_id=ticker) return feed.entry @ShowCallDetails def TestShowDetails(self, with_returns=False, inline_positions=False, inline_transactions=False): portfolios = self.GetPortfolios(with_returns, inline_positions) for pfl in portfolios: PrintPortfolio(pfl, with_returns) positions = self.GetPositions(pfl, with_returns, inline_transactions) for pos in positions: PrintPosition(pos, with_returns) PrintTransactions(self.GetTransactions(pos)) def DeletePortfoliosByName(self, portfolio_titles): for pfl in self.GetPortfolios(): if pfl.portfolio_title in portfolio_titles: self.client.DeletePortfolio(pfl) def AddPortfolio(self, portfolio_title, currency_code): pfl = PortfolioEntry(portfolio_data=PortfolioData( currency_code=currency_code)) pfl.portfolio_title = portfolio_title return self.client.AddPortfolio(pfl) def UpdatePortfolio(self, portfolio, portfolio_title=None, currency_code=None): if portfolio_title: portfolio.portfolio_title = portfolio_title if currency_code: portfolio.portfolio_data.currency_code = currency_code return self.client.UpdatePortfolio(portfolio) def DeletePortfolio(self, portfolio): self.client.DeletePortfolio(portfolio) @ShowCallDetails def TestManagePortfolios(self): pfl_one = 'Portfolio Test: Emerging Markets 12345' pfl_two = 'Portfolio Test: Renewable Energy 31415' print '---- Deleting portfolios ----' self.DeletePortfoliosByName([pfl_one, pfl_two]) PrintPortfolios(self.GetPortfolios()) print '---- Adding new portfolio ----' pfl = self.AddPortfolio(pfl_one, 'SGD') PrintPortfolios(self.GetPortfolios()) print '---- Changing portfolio title and currency code ----' pfl = self.UpdatePortfolio(pfl, pfl_two, 'USD') PrintPortfolios(self.GetPortfolios()) print '---- Deleting portfolio ----' self.DeletePortfolio(pfl) PrintPortfolios(self.GetPortfolios()) def Transact(self, type, portfolio, ticker, date=None, shares=None, notes=None, price=None, commission=None, currency_code=None): if price is not None: price = Price(money=[Money(amount=str(price), currency_code=currency_code or portfolio.portfolio_data.currency_code)]) if commission is not None: commission = Commission(money=[Money(amount=str(comission), currency_code=currency_code or portfolio.portfolio_data.currency_code)]) if date is not None and isinstance(date, datetime.datetime): date = date.isoformat() if shares is not None: shares = str(shares) txn = TransactionEntry(transaction_data=TransactionData(type=type, date=date, shares=shares, notes=notes, price=price, commission=commission)) return self.client.AddTransaction(txn, portfolio_id=portfolio.portfolio_id, ticker_id=ticker) def Buy(self, portfolio, ticker, **kwargs): return self.Transact('Buy', portfolio, ticker, **kwargs) def Sell(self, portfolio, ticker, **kwargs): return self.Transact('Sell', portfolio, ticker, **kwargs) def GetPosition(self, portfolio, ticker, with_returns=False, inline_transactions=False): query = PositionQuery() query.returns = with_returns query.transactions = inline_transactions return self.client.GetPosition( portfolio_id=portfolio.portfolio_id, ticker_id=ticker, query=query) def DeletePosition(self, position): self.client.DeletePosition(position_entry=position) def UpdateTransaction(self, transaction): self.client.UpdateTransaction(transaction) def DeleteTransaction(self, transaction): self.client.DeleteTransaction(transaction) @ShowCallDetails def TestManageTransactions(self): pfl_title = 'Transaction Test: Technology 27182' self.DeletePortfoliosByName([pfl_title]) print '---- Adding new portfolio ----' pfl = self.AddPortfolio(pfl_title, 'USD') PrintPortfolios(self.GetPortfolios()) print '---- Adding buy transactions ----' tkr1 = 'NASDAQ:GOOG' date = datetime.datetime(2009,04,01) days = datetime.timedelta(1) txn1 = self.Buy(pfl, tkr1, shares=500, price=321.00, date=date) txn2 = self.Buy(pfl, tkr1, shares=150, price=312.00, date=date+15*days) pos = self.GetPosition(portfolio=pfl, ticker=tkr1, with_returns=True) PrintPosition(pos, with_returns=True) PrintTransactions(self.GetTransactions(pos)) print '---- Adding sell transactions ----' txn3 = self.Sell(pfl, tkr1, shares=400, price=322.00, date=date+30*days) txn4 = self.Sell(pfl, tkr1, shares=200, price=330.00, date=date+45*days) pos = self.GetPosition(portfolio=pfl, ticker=tkr1, with_returns=True) PrintPosition(pos, with_returns=True) PrintTransactions(self.GetTransactions(pos)) print "---- Modifying first and deleting third ----" txn1.transaction_data.shares = '400.0' self.UpdateTransaction(txn1) self.DeleteTransaction(txn3) pos = self.GetPosition(portfolio=pfl, ticker=tkr1, with_returns=True) PrintPosition(pos, with_returns=True) PrintTransactions(self.GetTransactions(pos)) print "---- Deleting position ----" print "Number of positions (before):", len(self.GetPositions(pfl)) self.DeletePosition(pos) print "Number of positions (after) :", len(self.GetPositions(pfl)) print '---- Deleting portfolio ----' self.DeletePortfolio(pfl) PrintPortfolios(self.GetPortfolios()) if __name__ == '__main__': try: email = sys.argv[1] password = sys.argv[2] cases = sys.argv[3:] except IndexError: print "Usage: test_finance account@google.com password [0 1 2...]" sys.exit(1) tester = FinanceTester(email, password) tests = [ tester.TestShowDetails, lambda: tester.TestShowDetails(with_returns=True), tester.TestManagePortfolios, tester.TestManageTransactions, lambda: tester.TestShowDetails(with_returns=True, inline_positions=True), lambda: tester.TestShowDetails(with_returns=True, inline_positions=True, inline_transactions=True),] if not cases: cases = range(len(tests)) for i in cases: print "===== TEST CASE", i, "="*50 tests[int(i)]() gdata-2.0.18/samples/docs/0000750036466300116100000000000012156625015015326 5ustar afshareng00000000000000gdata-2.0.18/samples/docs/docs_example.py0000750036466300116100000002602512156622362020355 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright (C) 2007, 2009 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. __author__ = ('api.jfisher (Jeff Fisher), ' 'e.bidelman (Eric Bidelman)') import sys import re import os.path import getopt import getpass import gdata.docs.service import gdata.spreadsheet.service def truncate(content, length=15, suffix='...'): if len(content) <= length: return content else: return content[:length] + suffix class DocsSample(object): """A DocsSample object demonstrates the Document List feed.""" def __init__(self, email, password): """Constructor for the DocsSample object. Takes an email and password corresponding to a gmail account to demonstrate the functionality of the Document List feed. Args: email: [string] The e-mail address of the account to use for the sample. password: [string] The password corresponding to the account specified by the email parameter. Returns: A DocsSample object used to run the sample demonstrating the functionality of the Document List feed. """ source = 'Document List Python Sample' self.gd_client = gdata.docs.service.DocsService() self.gd_client.ClientLogin(email, password, source=source) # Setup a spreadsheets service for downloading spreadsheets self.gs_client = gdata.spreadsheet.service.SpreadsheetsService() self.gs_client.ClientLogin(email, password, source=source) def _PrintFeed(self, feed): """Prints out the contents of a feed to the console. Args: feed: A gdata.docs.DocumentListFeed instance. """ print '\n' if not feed.entry: print 'No entries in feed.\n' print '%-18s %-12s %s' % ('TITLE', 'TYPE', 'RESOURCE ID') for entry in feed.entry: print '%-18s %-12s %s' % (truncate(entry.title.text.encode('UTF-8')), entry.GetDocumentType(), entry.resourceId.text) def _GetFileExtension(self, file_name): """Returns the uppercase file extension for a file. Args: file_name: [string] The basename of a filename. Returns: A string containing the file extension of the file. """ match = re.search('.*\.([a-zA-Z]{3,}$)', file_name) if match: return match.group(1).upper() return False def _UploadMenu(self): """Prompts that enable a user to upload a file to the Document List feed.""" file_path = '' file_path = raw_input('Enter path to file: ') if not file_path: return elif not os.path.isfile(file_path): print 'Not a valid file.' return file_name = os.path.basename(file_path) ext = self._GetFileExtension(file_name) if not ext or ext not in gdata.docs.service.SUPPORTED_FILETYPES: print 'File type not supported. Check the file extension.' return else: content_type = gdata.docs.service.SUPPORTED_FILETYPES[ext] title = '' while not title: title = raw_input('Enter name for document: ') try: ms = gdata.MediaSource(file_path=file_path, content_type=content_type) except IOError: print 'Problems reading file. Check permissions.' return if ext in ['CSV', 'ODS', 'XLS', 'XLSX']: print 'Uploading spreadsheet...' elif ext in ['PPT', 'PPS']: print 'Uploading presentation...' else: print 'Uploading word processor document...' entry = self.gd_client.Upload(ms, title) if entry: print 'Upload successful!' print 'Document now accessible at:', entry.GetAlternateLink().href else: print 'Upload error.' def _DownloadMenu(self): """Prompts that enable a user to download a local copy of a document.""" resource_id = '' resource_id = raw_input('Enter an resource id: ') file_path = '' file_path = raw_input('Save file to: ') if not file_path or not resource_id: return file_name = os.path.basename(file_path) ext = self._GetFileExtension(file_name) if not ext or ext not in gdata.docs.service.SUPPORTED_FILETYPES: print 'File type not supported. Check the file extension.' return else: content_type = gdata.docs.service.SUPPORTED_FILETYPES[ext] doc_type = resource_id[:resource_id.find(':')] # When downloading a spreadsheet, the authenticated request needs to be # sent with the spreadsheet service's auth token. if doc_type == 'spreadsheet': print 'Downloading spreadsheet to %s...' % (file_path,) docs_token = self.gd_client.GetClientLoginToken() self.gd_client.SetClientLoginToken(self.gs_client.GetClientLoginToken()) self.gd_client.Export(resource_id, file_path, gid=0) self.gd_client.SetClientLoginToken(docs_token) else: print 'Downloading document to %s...' % (file_path,) self.gd_client.Export(resource_id, file_path) def _ListDocuments(self): """Retrieves and displays a list of documents based on the user's choice.""" print 'Retrieve (all/document/folder/presentation/spreadsheet/pdf): ' category = raw_input('Enter a category: ') if category == 'all': feed = self.gd_client.GetDocumentListFeed() elif category == 'folder': query = gdata.docs.service.DocumentQuery(categories=['folder'], params={'showfolders': 'true'}) feed = self.gd_client.Query(query.ToUri()) else: query = gdata.docs.service.DocumentQuery(categories=[category]) feed = self.gd_client.Query(query.ToUri()) self._PrintFeed(feed) def _ListAclPermissions(self): """Retrieves a list of a user's folders and displays them.""" resource_id = raw_input('Enter an resource id: ') query = gdata.docs.service.DocumentAclQuery(resource_id) print '\nListing document permissions:' feed = self.gd_client.GetDocumentListAclFeed(query.ToUri()) for acl_entry in feed.entry: print '%s - %s (%s)' % (acl_entry.role.value, acl_entry.scope.value, acl_entry.scope.type) def _ModifyAclPermissions(self): """Create or updates the ACL entry on an existing document.""" resource_id = raw_input('Enter an resource id: ') email = raw_input('Enter an email address: ') role_value = raw_input('Enter a permission (reader/writer/owner/remove): ') uri = gdata.docs.service.DocumentAclQuery(resource_id).ToUri() acl_feed = self.gd_client.GetDocumentListAclFeed(uri) found_acl_entry = None for acl_entry in acl_feed.entry: if acl_entry.scope.value == email: found_acl_entry = acl_entry break if found_acl_entry: if role_value == 'remove': # delete ACL entry self.gd_client.Delete(found_acl_entry.GetEditLink().href) else: # update ACL entry found_acl_entry.role.value = role_value updated_entry = self.gd_client.Put( found_acl_entry, found_acl_entry.GetEditLink().href, converter=gdata.docs.DocumentListAclEntryFromString) else: scope = gdata.docs.Scope(value=email, type='user') role = gdata.docs.Role(value=role_value) acl_entry = gdata.docs.DocumentListAclEntry(scope=scope, role=role) inserted_entry = self.gd_client.Post( acl_entry, uri, converter=gdata.docs.DocumentListAclEntryFromString) print '\nListing document permissions:' acl_feed = self.gd_client.GetDocumentListAclFeed(uri) for acl_entry in acl_feed.entry: print '%s - %s (%s)' % (acl_entry.role.value, acl_entry.scope.value, acl_entry.scope.type) def _FullTextSearch(self): """Searches a user's documents for a text string. Provides prompts to search a user's documents and displays the results of such a search. The text_query parameter of the DocumentListQuery object corresponds to the contents of the q parameter in the feed. Note that this parameter searches the content of documents, not just their titles. """ input = raw_input('Enter search term: ') query = gdata.docs.service.DocumentQuery(text_query=input) feed = self.gd_client.Query(query.ToUri()) self._PrintFeed(feed) def _PrintMenu(self): """Displays a menu of options for the user to choose from.""" print ('\nDocument List Sample\n' '1) List your documents.\n' '2) Search your documents.\n' '3) Upload a document.\n' '4) Download a document.\n' "5) List a document's permissions.\n" "6) Add/change a document's permissions.\n" '7) Exit.\n') def _GetMenuChoice(self, max): """Retrieves the menu selection from the user. Args: max: [int] The maximum number of allowed choices (inclusive) Returns: The integer of the menu item chosen by the user. """ while True: input = raw_input('> ') try: num = int(input) except ValueError: print 'Invalid choice. Please choose a value between 1 and', max continue if num > max or num < 1: print 'Invalid choice. Please choose a value between 1 and', max else: return num def Run(self): """Prompts the user to choose funtionality to be demonstrated.""" try: while True: self._PrintMenu() choice = self._GetMenuChoice(7) if choice == 1: self._ListDocuments() elif choice == 2: self._FullTextSearch() elif choice == 3: self._UploadMenu() elif choice == 4: self._DownloadMenu() elif choice == 5: self._ListAclPermissions() elif choice == 6: self._ModifyAclPermissions() elif choice == 7: print '\nGoodbye.' return except KeyboardInterrupt: print '\nGoodbye.' return def main(): """Demonstrates use of the Docs extension using the DocsSample object.""" # Parse command line options try: opts, args = getopt.getopt(sys.argv[1:], '', ['user=', 'pw=']) except getopt.error, msg: print 'python docs_example.py --user [username] --pw [password] ' sys.exit(2) user = '' pw = '' key = '' # Process options for option, arg in opts: if option == '--user': user = arg elif option == '--pw': pw = arg while not user: print 'NOTE: Please run these tests only with a test account.' user = raw_input('Please enter your username: ') while not pw: pw = getpass.getpass() if not pw: print 'Password cannot be blank.' try: sample = DocsSample(user, pw) except gdata.service.BadAuthentication: print 'Invalid user credentials given.' return sample.Run() if __name__ == '__main__': main() gdata-2.0.18/samples/docs/samplerunner.py0000640036466300116100000000271412156622362020422 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright 2009 Google Inc. All Rights Reserved. # # 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. """Sample running boilerplate.""" __author__ = 'afshar@google.com (Ali Afshar)' def Run(source_file): """Load a source file and run a sample from it.""" source = open(source_file).read() global_dict = {'__file__': source_file} exec source in global_dict samples = [global_dict[k] for k in global_dict if k.endswith('Sample')] lines = source.splitlines() for i, sample in enumerate(samples): print str(i).rjust(2), sample.__name__, '-', sample.__doc__ try: i = int(raw_input('Select sample: ').strip()) sample = samples[i] print '-' * 80 print 'def', '%s():' % sample.__name__ # print each line until a blank one (or eof). for line in lines[sample.func_code.co_firstlineno:]: if not line: break print line print '-' * 80 sample() except (ValueError, IndexError): print 'Bad selection.' gdata-2.0.18/samples/docs/docs_v3_example.py0000750036466300116100000002104112156622362020756 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright 2009 Google Inc. All Rights Reserved. # # 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. """Samples for the Documents List API v3.""" __author__ = 'afshar@google.com (Ali Afshar)' import os.path import gdata.data import gdata.acl.data import gdata.docs.client import gdata.docs.data import gdata.sample_util class SampleConfig(object): APP_NAME = 'GDataDocumentsListAPISample-v1.0' DEBUG = False def CreateClient(): """Create a Documents List Client.""" client = gdata.docs.client.DocsClient(source=SampleConfig.APP_NAME) client.http_client.debug = SampleConfig.DEBUG # Authenticate the user with CLientLogin, OAuth, or AuthSub. try: gdata.sample_util.authorize_client( client, service=client.auth_service, source=client.source, scopes=client.auth_scopes ) except gdata.client.BadAuthentication: exit('Invalid user credentials given.') except gdata.client.Error: exit('Login Error') return client def PrintResource(resource): """Display a resource to Standard Out.""" print resource.resource_id.text, resource.GetResourceType() def PrintFeed(feed): """Display a feed to Standard Out.""" for entry in feed.entry: PrintResource(entry) def _GetDataFilePath(name): return os.path.join( os.path.dirname( os.path.dirname( os.path.dirname(__file__))), 'tests', 'gdata_tests', 'docs', 'data', name) def GetResourcesSample(): """Get and display first page of resources.""" client = CreateClient() # Get a feed and print it feed = client.GetResources() PrintFeed(feed) def GetAllResourcesSample(): """Get and display all resources, using pagination.""" client = CreateClient() # Unlike client.GetResources, this returns a list of resources for resource in client.GetAllResources(): PrintResource(resource) def GetResourceSample(): """Fetch 5 resources from a feed, then again individually.""" client = CreateClient() for e1 in client.GetResources(limit=5).entry: e2 = client.GetResource(e1) print 'Refetched: ', e2.title.text, e2.resource_id.text def GetMetadataSample(): """Get and display the Metadata for the current user.""" client = CreateClient() # Fetch the metadata entry and display bits of it metadata = client.GetMetadata() print 'Quota' print ' Total:', metadata.quota_bytes_total.text print ' Used:', metadata.quota_bytes_used.text print ' Trashed:', metadata.quota_bytes_used_in_trash.text print 'Import / Export' for input_format in metadata.import_formats: print ' Import:', input_format.source, 'to', input_format.target for export_format in metadata.export_formats: print ' Export:', export_format.source, 'to', export_format.target print 'Features' for feature in metadata.features: print ' Feature:', feature.name.text print 'Upload Sizes' for upload_size in metadata.max_upload_sizes: print ' Kind:', upload_size.kind, upload_size.text def GetChangesSample(): """Get and display the Changes for the user.""" client = CreateClient() changes = client.GetChanges() for change in changes.entry: print change.title.text, change.changestamp.value def GetResourceAclSample(): """Get and display the ACL for a resource.""" client = CreateClient() for resource in client.GetResources(limit=5).entry: acl_feed = client.GetResourceAcl(resource) for acl in acl_feed.entry: print acl.role.value, acl.scope.type, acl.scope.value def CreateEmptyResourceSample(): """Create an empty resource of type document.""" client = CreateClient() document = gdata.docs.data.Resource(type='document', title='My Sample Doc') document = client.CreateResource(document) print 'Created:', document.title.text, document.resource_id.text def CreateCollectionSample(): """Create an empty collection.""" client = CreateClient() col = gdata.docs.data.Resource(type='folder', title='My Sample Folder') col = client.CreateResource(col) print 'Created collection:', col.title.text, col.resource_id.text def CreateResourceInCollectionSample(): """Create a collection, then create a document in it.""" client = CreateClient() col = gdata.docs.data.Resource(type='folder', title='My Sample Folder') col = client.CreateResource(col) print 'Created collection:', col.title.text, col.resource_id.text doc = gdata.docs.data.Resource(type='document', title='My Sample Doc') doc = client.CreateResource(doc, collection=col) print 'Created:', doc.title.text, doc.resource_id.text def UploadResourceSample(): """Upload a document, and convert to Google Docs.""" client = CreateClient() doc = gdata.docs.data.Resource(type='document', title='My Sample Doc') # Set the description doc.description = gdata.docs.data.Description( 'This is a simple Word document.') # This is a convenient MS Word doc that we know exists path = _GetDataFilePath('test.0.doc') print 'Selected file at: %s' % path # Create a MediaSource, pointing to the file media = gdata.data.MediaSource() media.SetFileHandle(path, 'application/msword') # Pass the MediaSource when creating the new Resource doc = client.CreateResource(doc, media=media) print 'Created, and uploaded:', doc.title.text, doc.resource_id.text def UploadUnconvertedFileSample(): """Upload a document, unconverted.""" client = CreateClient() doc = gdata.docs.data.Resource(type='document', title='My Sample Raw Doc') path = _GetDataFilePath('test.0.doc') media = gdata.data.MediaSource() media.SetFileHandle(path, 'application/msword') # Pass the convert=false parameter create_uri = gdata.docs.client.RESOURCE_UPLOAD_URI + '?convert=false' doc = client.CreateResource(doc, create_uri=create_uri, media=media) print 'Created, and uploaded:', doc.title.text, doc.resource_id.text def DeleteResourceSample(): """Delete a resource (after creating it).""" client = CreateClient() doc = gdata.docs.data.Resource(type='document', title='My Sample Doc') doc = client.CreateResource(doc) # Delete the resource we just created. client.DeleteResource(doc) def AddAclSample(): """Create a resource and an ACL.""" client = CreateClient() doc = gdata.docs.data.Resource(type='document', title='My Sample Doc') doc = client.CreateResource(doc) acl_entry = gdata.docs.data.AclEntry( scope=gdata.acl.data.AclScope(value='user@example.com', type='user'), role=gdata.acl.data.AclRole(value='reader'), ) client.AddAclEntry(doc, acl_entry, send_notifications=False) def DeleteAclSample(): """Create an ACL entry, and delete it.""" client = CreateClient() doc = gdata.docs.data.Resource(type='document', title='My Sample Doc') doc = client.CreateResource(doc) acl_entry = gdata.docs.data.AclEntry( scope=gdata.acl.data.AclScope(value='user@example.com', type='user'), role=gdata.acl.data.AclRole(value='reader'), ) acl_entry = client.AddAclEntry(doc, acl_entry) client.DeleteAclEntry(acl_entry) def AddAclBatchSample(): """Add a list of ACLs as a batch.""" client = CreateClient() doc = gdata.docs.data.Resource(type='document', title='My Sample Doc') doc = client.CreateResource(doc) acl1 = gdata.docs.data.AclEntry( scope=gdata.acl.data.AclScope(value='user1@example.com', type='user'), role=gdata.acl.data.AclRole(value='reader'), batch_operation=gdata.data.BatchOperation(type='insert'), ) acl2 = gdata.docs.data.AclEntry( scope=gdata.acl.data.AclScope(value='user2@example.com', type='user'), role=gdata.acl.data.AclRole(value='reader'), batch_operation=gdata.data.BatchOperation(type='insert'), ) # Create a list of operations to perform together. acl_operations = [acl1, acl2] # Perform the operations. client.BatchProcessAclEntries(doc, acl_operations) def GetRevisionsSample(): """Get the revision history for resources.""" client = CreateClient() for entry in client.GetResources(limit=55).entry: revisions = client.GetRevisions(entry) for revision in revisions.entry: print revision.publish, revision.GetPublishLink() if __name__ == '__main__': import samplerunner samplerunner.Run(__file__) gdata-2.0.18/README.txt0000640036466300116100000000205212156622362014432 0ustar afshareng00000000000000 Copyright (C) 2006-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. For more information on the GData Python client library, please see the project on code.google.com's hosting service here: http://code.google.com/p/gdata-python-client/ Dependency Modules ElementTree - For XML parsing, download here: http://effbot.org/zone/element-index.htm httplib - Part of the core Python library since version 2.0, so it should already be present. urllib - For URL creation and URL encoding, should already be present in current versions of Python. gdata-2.0.18/setup.py0000640036466300116100000000663012156622600014447 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright (C) 2009 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. import sys from distutils.core import setup required = [] if sys.version_info[:3] < (2, 5, 0): required.append('elementtree') setup( name='gdata', version='2.0.18', description='Python client library for Google data APIs', long_description = """\ The Google data Python client library makes it easy to interact with Google services through the Google Data APIs. This library provides data models and service modules for the the following Google data services: - Google Calendar data API - Google Contacts data API - Google Spreadsheets data API - Google Document List data APIs - Google Base data API - Google Apps Provisioning API - Google Apps Email Migration API - Google Apps Email Settings API - Picasa Web Albums Data API - Google Code Search Data API - YouTube Data API - Google Webmaster Tools Data API - Blogger Data API - Google Health API - Google Book Search API - Google Analytics API - Google Finance API - Google Sites Data API - Google Content API For Shopping - Google App Marketplace API - Google Content API for Shopping - core Google data API functionality The core Google data code provides sufficient functionality to use this library with any Google data API (even if a module hasn't been written for it yet). For example, this client can be used with the Notebook API. This library may also be used with any Atom Publishing Protocol service (AtomPub). """, author='Jeffrey Scudder', author_email='j.s@google.com', license='Apache 2.0', url='http://code.google.com/p/gdata-python-client/', packages=[ 'atom', 'gdata', 'gdata.Crypto', 'gdata.Crypto.Cipher', 'gdata.Crypto.Hash', 'gdata.Crypto.Protocol', 'gdata.Crypto.PublicKey', 'gdata.Crypto.Util', 'gdata.acl', 'gdata.alt', 'gdata.analytics', 'gdata.apps', 'gdata.apps.adminsettings', 'gdata.apps.audit', 'gdata.apps.emailsettings', 'gdata.apps.groups', 'gdata.apps.migration', 'gdata.apps.multidomain', 'gdata.apps.organization', 'gdata.blogger', 'gdata.books', 'gdata.calendar', 'gdata.calendar_resource', 'gdata.codesearch', 'gdata.contacts', 'gdata.contentforshopping', 'gdata.docs', 'gdata.dublincore', 'gdata.exif', 'gdata.finance', 'gdata.geo', 'gdata.health', 'gdata.media', 'gdata.notebook', 'gdata.oauth', 'gdata.opensearch', 'gdata.photos', 'gdata.projecthosting', 'gdata.sites', 'gdata.spreadsheet', 'gdata.spreadsheets', 'gdata.tlslite', 'gdata.tlslite.integration', 'gdata.tlslite.utils', 'gdata.webmastertools', 'gdata.youtube', ], package_dir = {'gdata':'src/gdata', 'atom':'src/atom'}, install_requires=required ) gdata-2.0.18/src/0000750036466300116100000000000012156625015013521 5ustar afshareng00000000000000gdata-2.0.18/src/gdata/0000750036466300116100000000000012156625015014601 5ustar afshareng00000000000000gdata-2.0.18/src/gdata/test_data.py0000750036466300116100000125011112156622363017132 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright (C) 2006 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. XML_ENTRY_1 = """ http://www.google.com/test/id/url Testing 2000 series laptop
    A Testing Laptop
    Computer Laptop testing laptop products
    """ TEST_BASE_ENTRY = """ Testing 2000 series laptop
    A Testing Laptop
    yes Computer Laptop testing laptop products
    """ BIG_FEED = """ dive into mark A <em>lot</em> of effort went into making this effortless 2005-07-31T12:29:29Z tag:example.org,2003:3 Copyright (c) 2003, Mark Pilgrim Example Toolkit Atom draft-07 snapshot tag:example.org,2003:3.2397 2005-07-31T12:29:29Z 2003-12-13T08:29:29-04:00 Mark Pilgrim http://example.org/ f8dy@example.com Sam Ruby Joe Gregorio

    [Update: The Atom draft is finished.]

    """ SMALL_FEED = """ Example Feed 2003-12-13T18:30:02Z John Doe urn:uuid:60a76c80-d399-11d9-b93C-0003939e0af6 Atom-Powered Robots Run Amok urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a 2003-12-13T18:30:02Z Some text. """ GBASE_FEED = """ http://www.google.com/base/feeds/snippets 2007-02-08T23:18:21.935Z Items matching query: digital camera GoogleBase 2171885 1 25 http://www.google.com/base/feeds/snippets/13246453826751927533 2007-02-08T13:23:27.000Z 2007-02-08T16:40:57.000Z Digital Camera Battery Notebook Computer 12v DC Power Cable - 5.5mm x 2.5mm (Center +) Camera Connecting Cables Notebook Computer 12v DC Power Cable - 5.5mm x 2.1mm (Center +) This connection cable will allow any Digital Pursuits battery pack to power portable computers that operate with 12v power and have a 2.1mm power connector (center +) Digital ... B&H Photo-Video anon-szot0wdsq0at@base.google.com PayPal & Bill Me Later credit available online only. new 420 9th Ave. 10001 305668-REG Products Digital Camera Battery 2007-03-10T13:23:27.000Z 1172711 34.95 usd Digital Photography>Camera Connecting Cables EN DCB5092 US 1.0 http://base.google.com/base_image?q=http%3A%2F%2Fwww.bhphotovideo.com%2Fimages%2Fitems%2F305668.jpg&dhm=ffffffff84c9a95e&size=6 http://www.google.com/base/feeds/snippets/10145771037331858608 2007-02-08T13:23:27.000Z 2007-02-08T16:40:57.000Z Digital Camera Battery Electronic Device 5v DC Power Cable - 5.5mm x 2.5mm (Center +) Camera Connecting Cables Electronic Device 5v DC Power Cable - 5.5mm x 2.5mm (Center +) This connection cable will allow any Digital Pursuits battery pack to power any electronic device that operates with 5v power and has a 2.5mm power connector (center +) Digital ... B&H Photo-Video anon-szot0wdsq0at@base.google.com 420 9th Ave. 10001 new 0.18 US Digital Photography>Camera Connecting Cables PayPal & Bill Me Later credit available online only. 305656-REG http://base.google.com/base_image?q=http%3A%2F%2Fwww.bhphotovideo.com%2Fimages%2Fitems%2F305656.jpg&dhm=7315bdc8&size=6 DCB5108 838098005108 34.95 usd EN Digital Camera Battery 1172711 Products 2007-03-10T13:23:27.000Z http://www.google.com/base/feeds/snippets/3128608193804768644 2007-02-08T02:21:27.000Z 2007-02-08T15:40:13.000Z Digital Camera Battery Power Cable for Kodak 645 Pro-Back ProBack & DCS-300 Series Camera Connecting Cables Camera Connection Cable - to Power Kodak 645 Pro-Back DCS-300 Series Digital Cameras This connection cable will allow any Digital Pursuits battery pack to power the following digital cameras: Kodak DCS Pro Back 645 DCS-300 series Digital Photography ... B&H Photo-Video anon-szot0wdsq0at@base.google.com 0.3 DCB6006 http://base.google.com/base_image?q=http%3A%2F%2Fwww.bhphotovideo.com%2Fimages%2Fitems%2F305685.jpg&dhm=72f0ca0a&size=6 420 9th Ave. 10001 PayPal & Bill Me Later credit available online only. Products US digital kodak camera Digital Camera Battery 2007-03-10T02:21:27.000Z EN new 34.95 usd 1172711 Digital Photography>Camera Connecting Cables 305685-REG """ EXTENSION_TREE = """ John Doe Bar """ TEST_AUTHOR = """ John Doe johndoes@someemailadress.com http://www.google.com """ TEST_LINK = """ """ TEST_GBASE_ATTRIBUTE = """ Digital Camera Battery """ CALENDAR_FEED = """ http://www.google.com/calendar/feeds/default 2007-03-20T22:48:57.833Z GData Ops Demo's Calendar List GData Ops Demo gdata.ops.demo@gmail.com Google Calendar 1 http://www.google.com/calendar/feeds/default/gdata.ops.demo%40gmail.com 2007-03-20T22:48:57.837Z 2007-03-20T22:48:52.000Z GData Ops Demo GData Ops Demo gdata.ops.demo@gmail.com http://www.google.com/calendar/feeds/default/jnh21ovnjgfph21h32gvms2758%40group.calendar.google.com 2007-03-20T22:48:57.837Z 2007-03-20T22:48:53.000Z GData Ops Demo Secondary Calendar GData Ops Demo Secondary Calendar """ CALENDAR_FULL_EVENT_FEED = """ http://www.google.com/calendar/feeds/default/private/full 2007-03-20T21:29:57.000Z GData Ops Demo GData Ops Demo GData Ops Demo gdata.ops.demo@gmail.com Google Calendar 10 1 25 http://www.google.com/calendar/feeds/default/private/full/o99flmgmkfkfrr8u745ghr3100 2007-03-20T21:29:52.000Z 2007-03-20T21:29:57.000Z test deleted GData Ops Demo gdata.ops.demo@gmail.com http://www.google.com/calendar/feeds/default/private/full/2qt3ao5hbaq7m9igr5ak9esjo0 2007-03-20T21:26:04.000Z 2007-03-20T21:28:46.000Z Afternoon at Dolores Park with Kim GData Ops Demo gdata.ops.demo@gmail.com http://www.google.com/calendar/feeds/default/private/full/uvsqhg7klnae40v50vihr1pvos 2007-03-20T21:28:37.000Z 2007-03-20T21:28:37.000Z Team meeting GData Ops Demo gdata.ops.demo@gmail.com DTSTART;TZID=America/Los_Angeles:20070323T090000 DTEND;TZID=America/Los_Angeles:20070323T100000 RRULE:FREQ=WEEKLY;BYDAY=FR;UNTIL=20070817T160000Z;WKST=SU BEGIN:VTIMEZONE TZID:America/Los_Angeles X-LIC-LOCATION:America/Los_Angeles BEGIN:STANDARD TZOFFSETFROM:-0700 TZOFFSETTO:-0800 TZNAME:PST DTSTART:19701025T020000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU END:STANDARD BEGIN:DAYLIGHT TZOFFSETFROM:-0800 TZOFFSETTO:-0700 TZNAME:PDT DTSTART:19700405T020000 RRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=1SU END:DAYLIGHT END:VTIMEZONE http://www.google.com/calendar/feeds/default/private/full/st4vk9kiffs6rasrl32e4a7alo 2007-03-20T21:25:46.000Z 2007-03-20T21:25:46.000Z Movie with Kim and danah GData Ops Demo gdata.ops.demo@gmail.com http://www.google.com/calendar/feeds/default/private/full/ofl1e45ubtsoh6gtu127cls2oo 2007-03-20T21:24:43.000Z 2007-03-20T21:25:08.000Z Dinner with Kim and Sarah GData Ops Demo gdata.ops.demo@gmail.com http://www.google.com/calendar/feeds/default/private/full/b69s2avfi2joigsclecvjlc91g 2007-03-20T21:24:19.000Z 2007-03-20T21:25:05.000Z Dinner with Jane and John GData Ops Demo gdata.ops.demo@gmail.com http://www.google.com/calendar/feeds/default/private/full/u9p66kkiotn8bqh9k7j4rcnjjc 2007-03-20T21:24:33.000Z 2007-03-20T21:24:33.000Z Tennis with Elizabeth GData Ops Demo gdata.ops.demo@gmail.com http://www.google.com/calendar/feeds/default/private/full/76oj2kceidob3s708tvfnuaq3c 2007-03-20T21:24:00.000Z 2007-03-20T21:24:00.000Z Lunch with Jenn GData Ops Demo gdata.ops.demo@gmail.com http://www.google.com/calendar/feeds/default/private/full/5np9ec8m7uoauk1vedh5mhodco 2007-03-20T07:50:02.000Z 2007-03-20T20:39:26.000Z test entry test desc GData Ops Demo gdata.ops.demo@gmail.com http://www.google.com/calendar/feeds/default/private/full/fu6sl0rqakf3o0a13oo1i1a1mg 2007-02-14T23:23:37.000Z 2007-02-14T23:25:30.000Z test GData Ops Demo gdata.ops.demo@gmail.com http://www.google.com/calendar/feeds/default/private/full/h7a0haa4da8sil3rr19ia6luvc 2007-07-16T22:13:28.000Z 2007-07-16T22:13:29.000Z GData Ops Demo gdata.ops.demo@gmail.com """ CALENDAR_BATCH_REQUEST = """ 1 Event inserted via batch 2 http://www.google.com/calendar/feeds/default/private/full/glcs0kv2qqa0gf52qi1jo018gc Event queried via batch 3 http://www.google.com/calendar/feeds/default/private/full/ujm0go5dtngdkr6u91dcqvj0qs Event updated via batch 4 http://www.google.com/calendar/feeds/default/private/full/d8qbg9egk1n6lhsgq1sjbqffqc Event deleted via batch """ CALENDAR_BATCH_RESPONSE = """ http://www.google.com/calendar/feeds/default/private/full 2007-09-21T23:01:00.380Z Batch Feed 1 http://www.google.com/calendar/feeds/default/private/full/n9ug78gd9tv53ppn4hdjvk68ek Event inserted via batch 2 http://www.google.com/calendar/feeds/default/private/full/glsc0kv2aqa0ff52qi1jo018gc Event queried via batch 3 http://www.google.com/calendar/feeds/default/private/full/ujm0go5dtngdkr6u91dcqvj0qs Event updated via batch 3 4 http://www.google.com/calendar/feeds/default/private/full/d8qbg9egk1n6lhsgq1sjbqffqc Event deleted via batch Deleted """ GBASE_ATTRIBUTE_FEED = """ http://www.google.com/base/feeds/attributes 2006-11-01T20:35:59.578Z Attribute histogram for query: [item type:jobs] GoogleBase 16 1 16 http://www.google.com/base/feeds/attributes/job+industry%28text%29N%5Bitem+type%3Ajobs%5D 2006-11-01T20:36:00.100Z job industry(text) Attribute"job industry" of type text. it internet healthcare information technology accounting clerical and administrative other sales and sales management information systems engineering and architecture sales """ GBASE_ATTRIBUTE_ENTRY = """ http://www.google.com/base/feeds/attributes/job+industry%28text%29N%5Bitem+type%3Ajobs%5D 2006-11-01T20:36:00.100Z job industry(text) Attribute"job industry" of type text. it internet healthcare information technology accounting clerical and administrative other sales and sales management information systems engineering and architecture sales """ GBASE_LOCALES_FEED = """ http://www.google.com/base/feeds/locales/ 2006-06-13T18:11:40.120Z Locales Google Inc. base@google.com GoogleBase 3 25 http://www.google.com/base/feeds/locales/en_US 2006-03-27T22:27:36.658Z en_US en_US http://www.google.com/base/feeds/locales/en_GB 2006-06-13T18:14:18.601Z en_GB en_GB http://www.google.com/base/feeds/locales/de_DE 2006-06-13T18:14:18.601Z de_DE de_DE """ GBASE_STRING_ENCODING_ENTRY = """ http://www.google.com/base/feeds/snippets/17495780256183230088 2007-12-09T03:13:07.000Z 2008-01-07T03:26:46.000Z Digital Camera Cord Fits SONY Cybershot DSC-R1 S40 SONY \xC2\xB7 Cybershot Digital Camera Usb Cable DESCRIPTION This is a 2.5 USB 2.0 A to Mini B (5 Pin) high quality digital camera cable used for connecting your Sony Digital Cameras and Camcoders. Backward Compatible with USB 2.0, 1.0 and 1.1. Fully ... eBay Products EN US 0.99 usd http://thumbs.ebaystatic.com/pict/270195049057_1.jpg Cameras & Photo>Digital Camera Accessories>Cables Cords & Connectors>USB Cables>For Other Brands 11729 270195049057 2008-02-06T03:26:46Z """ RECURRENCE_EXCEPTION_ENTRY = """ http://www.google.com/calendar/feeds/default/private/composite/i7lgfj69mjqjgnodklif3vbm7g 2007-04-05T21:51:49.000Z 2007-04-05T21:51:49.000Z testDavid gdata ops gdata.ops.test@gmail.com DTSTART;TZID=America/Anchorage:20070403T100000 DTEND;TZID=America/Anchorage:20070403T110000 RRULE:FREQ=DAILY;UNTIL=20070408T180000Z;WKST=SU EXDATE;TZID=America/Anchorage:20070407T100000 EXDATE;TZID=America/Anchorage:20070405T100000 EXDATE;TZID=America/Anchorage:20070404T100000 BEGIN:VTIMEZONE TZID:America/Anchorage X-LIC-LOCATION:America/Anchorage BEGIN:STANDARD TZOFFSETFROM:-0800 TZOFFSETTO:-0900 TZNAME:AKST DTSTART:19701025T020000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU END:STANDARD BEGIN:DAYLIGHT TZOFFSETFROM:-0900 TZOFFSETTO:-0800 TZNAME:AKDT DTSTART:19700405T020000 RRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=1SU END:DAYLIGHT END:VTIMEZONE i7lgfj69mjqjgnodklif3vbm7g_20070407T180000Z 2007-04-05T21:51:49.000Z 2007-04-05T21:52:58.000Z testDavid gdata ops gdata.ops.test@gmail.com 2007-04-05T21:54:09.285Z Comments for: testDavid """ NICK_ENTRY = """ https://apps-apis.google.com/a/feeds/example.com/nickname/2.0/Foo 1970-01-01T00:00:00.000Z Foo """ NICK_FEED = """ http://apps-apis.google.com/a/feeds/example.com/nickname/2.0 1970-01-01T00:00:00.000Z Nicknames for user SusanJones 1 2 http://apps-apis.google.com/a/feeds/example.com/nickname/2.0/Foo Foo http://apps-apis.google.com/a/feeds/example.com/nickname/2.0/suse suse """ USER_ENTRY = """ https://apps-apis.google.com/a/feeds/example.com/user/2.0/TestUser 1970-01-01T00:00:00.000Z TestUser """ USER_FEED = """ http://apps-apis.google.com/a/feeds/example.com/user/2.0 1970-01-01T00:00:00.000Z Users """ EMAIL_LIST_ENTRY = """ https://apps-apis.google.com/a/feeds/example.com/emailList/2.0/testlist 1970-01-01T00:00:00.000Z testlist """ EMAIL_LIST_FEED = """ http://apps-apis.google.com/a/feeds/example.com/emailList/2.0 1970-01-01T00:00:00.000Z EmailLists """ EMAIL_LIST_RECIPIENT_ENTRY = """ https://apps-apis.google.com/a/feeds/example.com/emailList/2.0/us-sales/recipient/TestUser%40example.com 1970-01-01T00:00:00.000Z TestUser """ EMAIL_LIST_RECIPIENT_FEED = """ http://apps-apis.google.com/a/feeds/example.com/emailList/2.0/us-sales/recipient 1970-01-01T00:00:00.000Z Recipients for email list us-sales """ ACL_FEED = """ http://www.google.com/calendar/feeds/liz%40gmail.com/acl/full 2007-04-21T00:52:04.000Z Elizabeth Bennet's access control list Google Calendar 2 1 http://www.google.com/calendar/feeds/liz%40gmail.com/acl/full/user%3Aliz%40gmail.com 2007-04-21T00:52:04.000Z owner Elizabeth Bennet liz@gmail.com http://www.google.com/calendar/feeds/liz%40gmail.com/acl/full/default 2007-04-21T00:52:04.000Z read Elizabeth Bennet liz@gmail.com """ ACL_ENTRY = """ http://www.google.com/calendar/feeds/liz%40gmail.com/acl/full/user%3Aliz%40gmail.com 2007-04-21T00:52:04.000Z owner Elizabeth Bennet liz@gmail.com """ DOCUMENT_LIST_FEED = """ 21test.usertest.user@gmail.comhttps://docs.google.com/feeds/documents/private/full/spreadsheet%3AsupercalifragilisticexpeadociousTest Spreadsheet2007-07-03T18:03:32.045Z document:dfrkj84g_3348jbxpxcd test.user test.user@gmail.com 2009-03-05T07:48:21.493Z test.usertest.user@gmail.comhttp://docs.google.com/feeds/documents/private/full/document%3Agr00vyTest Document2007-07-03T18:02:50.338Z test.user test.user@gmail.com 2009-03-05T07:48:21.493Z http://docs.google.com/feeds/documents/private/fullAvailable Documents - test.user@gmail.com2007-07-09T23:07:21.898Z """ DOCUMENT_LIST_ENTRY = """ test.usertest.user@gmail.com https://docs.google.com/feeds/documents/private/full/spreadsheet%3Asupercalifragilisticexpealidocious Test Spreadsheet2007-07-03T18:03:32.045Z spreadsheet:supercalifragilisticexpealidocious test.user test.user@gmail.com 2009-03-05T07:48:21.493Z """ DOCUMENT_LIST_ENTRY_V3 = """ test.usertest.user@gmail.com https://docs.google.com/feeds/documents/private/full/spreadsheet%3Asupercalifragilisticexpealidocious Test Spreadsheet2007-07-03T18:03:32.045Z spreadsheet:supercalifragilisticexpealidocious test.user test.user@gmail.com 2009-03-05T07:48:21.493Z 1000 """ DOCUMENT_LIST_ACL_ENTRY = """ """ DOCUMENT_LIST_ACL_WITHKEY_ENTRY = """ """ DOCUMENT_LIST_ACL_ADDITIONAL_ROLE_ENTRY = """ """ DOCUMENT_LIST_ACL_FEED = """ http://docs.google.com/feeds/acl/private/full/spreadsheet%3ApFrmMi8feTQYCgZpwUQ 2009-02-22T03:48:25.895Z Document Permissions 2 1 http://docs.google.com/feeds/acl/private/full/spreadsheet%3ApFrmMi8feTQp4pwUwUQ/user%3Auser%40gmail.com 2009-02-22T03:48:25.896Z Document Permission - user@gmail.com http://docs.google.com/feeds/acl/private/full/spreadsheet%3ApFrmMi8fCgZp4pwUwUQ/user%3Auser2%40google.com 2009-02-22T03:48:26.257Z Document Permission - user2@google.com """ DOCUMENT_LIST_REVISION_FEED = """ https://docs.google.com/feeds/default/private/full/resource_id/revisions 2009-08-17T04:22:10.378Z Document Revisions 6 1 https://docs.google.com/feeds/id/resource_id/revisions/2 2009-08-17T04:22:10.440Z 2009-08-14T07:11:34.197Z Revision 2 another_user another_user@gmail.com """ DOCUMENT_LIST_METADATA = """ """ BATCH_ENTRY = """ http://www.google.com/base/feeds/items/2173859253842813008 2006-07-11T14:51:43.560Z 2006-07-11T14:51: 43.560Z title content recipes itemB """ BATCH_FEED_REQUEST = """ My Batch Feed http://www.google.com/base/feeds/items/13308004346459454600 http://www.google.com/base/feeds/items/17437536661927313949 ... ... itemA recipes ... ... itemB recipes """ BATCH_FEED_RESULT = """ http://www.google.com/base/feeds/items 2006-07-11T14:51:42.894Z My Batch http://www.google.com/base/feeds/items/2173859253842813008 2006-07-11T14:51:43.560Z 2006-07-11T14:51: 43.560Z ... ... recipes itemB http://www.google.com/base/feeds/items/11974645606383737963 2006-07-11T14:51:43.247Z 2006-07-11T14:51: 43.247Z ... ... recipes itemA http://www.google.com/base/feeds/items/13308004346459454600 2006-07-11T14:51:42.894Z Error Bad request http://www.google.com/base/feeds/items/17437536661927313949 2006-07-11T14:51:43.246Z Deleted """ ALBUM_FEED = """ http://picasaweb.google.com/data/feed/api/user/sample.user/albumid/1 2007-09-21T18:23:05.000Z Test public http://lh6.google.com/sample.user/Rt8WNoDZEJE/AAAAAAAAABk/HQGlDhpIgWo/s160-c/Test.jpg sample http://picasaweb.google.com/sample.user Picasaweb 4 1 500 1 Test public 1188975600000 2 sample.user sample true 0 http://picasaweb.google.com/data/entry/api/user/sample.user/albumid/1/photoid/2 2007-09-05T20:49:23.000Z 2007-09-21T18:23:05.000Z Aqua Blue.jpg Blue 2 1190398985145172 0.0 1 2560 1600 883405 1189025362000 true c041ce17aaa637eb656c81d9cf526c24 true 1 Aqua Blue.jpg Blue tag, test sample http://picasaweb.google.com/data/entry/api/user/sample.user/albumid/1/photoid/3 2007-09-05T20:49:24.000Z 2007-09-21T18:19:38.000Z Aqua Graphite.jpg Gray 3 1190398778006402 1.0 1 2560 1600 798334 1189025363000 true a5ce2e36b9df7d3cb081511c72e73926 true 0 Aqua Graphite.jpg Gray sample http://picasaweb.google.com/data/entry/api/user/sample.user/albumid/1/tag/tag 2007-09-05T20:49:24.000Z tag tag sample http://picasaweb.google.com/sample.user http://picasaweb.google.com/data/entry/api/user/sample.user/albumid/1/tag/test 2007-09-05T20:49:24.000Z test test sample http://picasaweb.google.com/sample.user """ CODE_SEARCH_FEED = """ http://www.google.com/codesearch/feeds/search?q=malloc 2007-12-19T16:08:04Z Google Code Search Google Code Search 2530000 1 Google Code Search http://www.google.com/codesearch http://www.google.com/codesearch?hl=en&q=+malloc+show:LDjwp-Iqc7U:84hEYaYsZk8:xDGReDhvNi0&sa=N&ct=rx&cd=1&cs_p=http://www.gnu.org&cs_f=software/autoconf/manual/autoconf-2.60/autoconf.html-002&cs_p=http://www.gnu.org&cs_f=software/autoconf/manual/autoconf-2.60/autoconf.html-002#first2007-12-19T16:08:04ZCode owned by external author.software/autoconf/manual/autoconf-2.60/autoconf.html<pre> 8: void *<b>malloc</b> (); </pre><pre> #undef <b>malloc</b> </pre><pre> void *<b>malloc</b> (); </pre><pre> rpl_<b>malloc</b> (size_t n) </pre><pre> return <b>malloc</b> (n); </pre> http://www.google.com/codesearch?hl=en&q=+malloc+show:h4hfh-fV-jI:niBq_bwWZNs:H0OhClf0HWQ&sa=N&ct=rx&cd=2&cs_p=ftp://ftp.gnu.org/gnu/guile/guile-1.6.8.tar.gz&cs_f=guile-1.6.8/libguile/mallocs.c&cs_p=ftp://ftp.gnu.org/gnu/guile/guile-1.6.8.tar.gz&cs_f=guile-1.6.8/libguile/mallocs.c#first2007-12-19T16:08:04ZCode owned by external author.guile-1.6.8/libguile/mallocs.c<pre> 86: { scm_t_bits mem = n ? (scm_t_bits) <b>malloc</b> (n) : 0; if (n &amp;&amp; !mem) </pre><pre>#include &lt;<b>malloc</b>.h&gt; </pre><pre>scm_t_bits scm_tc16_<b>malloc</b>; </pre><pre><b>malloc</b>_free (SCM ptr) </pre><pre><b>malloc</b>_print (SCM exp, SCM port, scm_print_state *pstate SCM_UNUSED) </pre><pre> scm_puts(&quot;#&lt;<b>malloc</b> &quot;, port); </pre><pre> scm_t_bits mem = n ? (scm_t_bits) <b>malloc</b> (n) : 0; </pre><pre> SCM_RETURN_NEWSMOB (scm_tc16_<b>malloc</b>, mem); </pre><pre> scm_tc16_<b>malloc</b> = scm_make_smob_type (&quot;<b>malloc</b>&quot;, 0); </pre><pre> scm_set_smob_free (scm_tc16_<b>malloc</b>, <b>malloc</b>_free); </pre>GPL http://www.google.com/codesearch?hl=en&q=+malloc+show:9wyZUG-N_30:7_dFxoC1ZrY:C0_iYbFj90M&sa=N&ct=rx&cd=3&cs_p=http://ftp.gnu.org/gnu/bash/bash-3.0.tar.gz&cs_f=bash-3.0/lib/malloc/alloca.c&cs_p=http://ftp.gnu.org/gnu/bash/bash-3.0.tar.gz&cs_f=bash-3.0/lib/malloc/alloca.c#first2007-12-19T16:08:04ZCode owned by external author.bash-3.0/lib/malloc/alloca.c<pre> 78: #ifndef emacs #define <b>malloc</b> x<b>malloc</b> extern pointer x<b>malloc</b> (); </pre><pre> <b>malloc</b>. The Emacs executable needs alloca to call x<b>malloc</b>, because </pre><pre> ordinary <b>malloc</b> isn&#39;t protected from input signals. On the other </pre><pre> hand, the utilities in lib-src need alloca to call <b>malloc</b>; some of </pre><pre> them are very simple, and don&#39;t have an x<b>malloc</b> routine. </pre><pre> Callers below should use <b>malloc</b>. */ </pre><pre>#define <b>malloc</b> x<b>malloc</b> </pre><pre>extern pointer x<b>malloc</b> (); </pre><pre> It is very important that sizeof(header) agree with <b>malloc</b> </pre><pre> register pointer new = <b>malloc</b> (sizeof (header) + size); </pre>GPL http://www.google.com/codesearch?hl=en&q=+malloc+show:uhVCKyPcT6k:8juMxxzmUJw:H7_IDsTB2L4&sa=N&ct=rx&cd=4&cs_p=http://ftp.mozilla.org/pub/mozilla.org/mozilla/releases/mozilla1.7b/src/mozilla-source-1.7b-source.tar.bz2&cs_f=mozilla/xpcom/build/malloc.c&cs_p=http://ftp.mozilla.org/pub/mozilla.org/mozilla/releases/mozilla1.7b/src/mozilla-source-1.7b-source.tar.bz2&cs_f=mozilla/xpcom/build/malloc.c#first2007-12-19T16:08:04ZCode owned by external author.mozilla/xpcom/build/malloc.c<pre> 54: http://gee.cs.oswego.edu/dl/html/<b>malloc</b>.html You may already by default be using a c library containing a <b>malloc</b> </pre><pre>/* ---------- To make a <b>malloc</b>.h, start cutting here ------------ */ </pre><pre> Note: There may be an updated version of this <b>malloc</b> obtainable at </pre><pre> ftp://gee.cs.oswego.edu/pub/misc/<b>malloc</b>.c </pre><pre>* Why use this <b>malloc</b>? </pre><pre> most tunable <b>malloc</b> ever written. However it is among the fastest </pre><pre> allocator for <b>malloc</b>-intensive programs. </pre><pre> http://gee.cs.oswego.edu/dl/html/<b>malloc</b>.html </pre><pre> You may already by default be using a c library containing a <b>malloc</b> </pre><pre> that is somehow based on some version of this <b>malloc</b> (for example in </pre>Mozilla http://www.google.com/codesearch?hl=en&q=+malloc+show:4n1P2HVOISs:Ybbpph0wR2M:OhIN_sDrG0U&sa=N&ct=rx&cd=5&cs_p=http://regexps.srparish.net/src/hackerlab/hackerlab-1.0pre2.tar.gz&cs_f=hackerlab-1.0pre2/src/hackerlab/tests/mem-tests/unit-must-malloc.sh&cs_p=http://regexps.srparish.net/src/hackerlab/hackerlab-1.0pre2.tar.gz&cs_f=hackerlab-1.0pre2/src/hackerlab/tests/mem-tests/unit-must-malloc.sh#first2007-12-19T16:08:04ZCode owned by external author.hackerlab-1.0pre2/src/hackerlab/tests/mem-tests/unit-must-malloc.sh<pre> 11: echo ================ unit-must-<b>malloc</b> tests ================ ./unit-must-<b>malloc</b> echo ...passed </pre><pre># tag: Tom Lord Tue Dec 4 14:54:29 2001 (mem-tests/unit-must-<b>malloc</b>.sh) </pre><pre>echo ================ unit-must-<b>malloc</b> tests ================ </pre><pre>./unit-must-<b>malloc</b> </pre>GPL http://www.google.com/codesearch?hl=en&q=+malloc+show:GzkwiWG266M:ykuz3bG00ws:2sTvVSif08g&sa=N&ct=rx&cd=6&cs_p=http://ftp.gnu.org/gnu/tar/tar-1.14.tar.bz2&cs_f=tar-1.14/lib/malloc.c&cs_p=http://ftp.gnu.org/gnu/tar/tar-1.14.tar.bz2&cs_f=tar-1.14/lib/malloc.c#first2007-12-19T16:08:04ZCode owned by external author.tar-1.14/lib/malloc.c<pre> 22: #endif #undef <b>malloc</b> </pre><pre>/* Work around bug on some systems where <b>malloc</b> (0) fails. </pre><pre>#undef <b>malloc</b> </pre><pre>rpl_<b>malloc</b> (size_t n) </pre><pre> return <b>malloc</b> (n); </pre>GPL http://www.google.com/codesearch?hl=en&q=+malloc+show:o_TFIeBY6dY:ktI_dt8wPao:AI03BD1Dz0Y&sa=N&ct=rx&cd=7&cs_p=http://ftp.gnu.org/gnu/tar/tar-1.16.1.tar.gz&cs_f=tar-1.16.1/lib/malloc.c&cs_p=http://ftp.gnu.org/gnu/tar/tar-1.16.1.tar.gz&cs_f=tar-1.16.1/lib/malloc.c#first2007-12-19T16:08:04ZCode owned by external author.tar-1.16.1/lib/malloc.c<pre> 21: #include &lt;config.h&gt; #undef <b>malloc</b> </pre><pre>/* <b>malloc</b>() function that is glibc compatible. </pre><pre>#undef <b>malloc</b> </pre><pre>rpl_<b>malloc</b> (size_t n) </pre><pre> return <b>malloc</b> (n); </pre>GPL http://www.google.com/codesearch?hl=en&q=+malloc+show:_ibw-VLkMoI:jBOtIJSmFd4:-0NUEVeCwfY&sa=N&ct=rx&cd=8&cs_p=http://freshmeat.net/redir/uclibc/20616/url_bz2/uClibc-0.9.28.1.tar.bz2&cs_f=uClibc-0.9.29/include/malloc.h&cs_p=http://freshmeat.net/redir/uclibc/20616/url_bz2/uClibc-0.9.28.1.tar.bz2&cs_f=uClibc-0.9.29/include/malloc.h#first2007-12-19T16:08:04ZCode owned by external author.uClibc-0.9.29/include/malloc.h<pre> 1: /* Prototypes and definition for <b>malloc</b> implementation. Copyright (C) 1996, 1997, 1999, 2000 Free Software Foundation, Inc. </pre><pre>/* Prototypes and definition for <b>malloc</b> implementation. </pre><pre> `pt<b>malloc</b>&#39;, a <b>malloc</b> implementation for multiple threads without </pre><pre> See the files `pt<b>malloc</b>.c&#39; or `COPYRIGHT&#39; for copying conditions. </pre><pre> This work is mainly derived from <b>malloc</b>-2.6.4 by Doug Lea </pre><pre> ftp://g.oswego.edu/pub/misc/<b>malloc</b>.c </pre><pre> `pt<b>malloc</b>.c&#39;. </pre><pre># define __<b>malloc</b>_ptr_t void * </pre><pre># define __<b>malloc</b>_ptr_t char * </pre><pre># define __<b>malloc</b>_size_t size_t </pre>LGPL http://www.google.com/codesearch?hl=en&q=+malloc+show:F6qHcZ9vefo:bTX7o9gKfks:hECF4r_eKC0&sa=N&ct=rx&cd=9&cs_p=http://ftp.gnu.org/gnu/glibc/glibc-2.0.1.tar.gz&cs_f=glibc-2.0.1/hurd/hurdmalloc.h&cs_p=http://ftp.gnu.org/gnu/glibc/glibc-2.0.1.tar.gz&cs_f=glibc-2.0.1/hurd/hurdmalloc.h#first2007-12-19T16:08:04ZCode owned by external author.glibc-2.0.1/hurd/hurdmalloc.h<pre> 15: #define <b>malloc</b> _hurd_<b>malloc</b> #define realloc _hurd_realloc </pre><pre> All hurd-internal code which uses <b>malloc</b> et al includes this file so it </pre><pre> will use the internal <b>malloc</b> routines _hurd_{<b>malloc</b>,realloc,free} </pre><pre> of <b>malloc</b> et al is the unixoid one using sbrk. </pre><pre>extern void *_hurd_<b>malloc</b> (size_t); </pre><pre>#define <b>malloc</b> _hurd_<b>malloc</b> </pre>GPL http://www.google.com/codesearch?hl=en&q=+malloc+show:CHUvHYzyLc8:pdcAfzDA6lY:wjofHuNLTHg&sa=N&ct=rx&cd=10&cs_p=ftp://apache.mirrors.pair.com/httpd/httpd-2.2.4.tar.bz2&cs_f=httpd-2.2.4/srclib/apr/include/arch/netware/apr_private.h&cs_p=ftp://apache.mirrors.pair.com/httpd/httpd-2.2.4.tar.bz2&cs_f=httpd-2.2.4/srclib/apr/include/arch/netware/apr_private.h#first2007-12-19T16:08:04ZCode owned by external author.httpd-2.2.4/srclib/apr/include/arch/netware/apr_private.h<pre> 173: #undef <b>malloc</b> #define <b>malloc</b>(x) library_<b>malloc</b>(gLibHandle,x) </pre><pre>/* Redefine <b>malloc</b> to use the library <b>malloc</b> call so </pre><pre>#undef <b>malloc</b> </pre><pre>#define <b>malloc</b>(x) library_<b>malloc</b>(gLibHandle,x) </pre>Apache """ YOUTUBE_VIDEO_FEED = """http://gdata.youtube.com/feeds/api/standardfeeds/top_rated2008-05-14T02:24:07.000-07:00Top Ratedhttp://www.youtube.com/img/pic_youtubelogo_123x63.gifYouTubehttp://www.youtube.com/YouTube data API100125 http://gdata.youtube.com/feeds/api/videos/C71ypXYGho82008-03-20T10:17:27.000-07:002008-05-14T04:26:37.000-07:00Me odeio por te amar - KARYN GARCIAhttp://www.karyngarcia.com.brTvKarynGarciahttp://gdata.youtube.com/feeds/api/users/tvkaryngarciaMe odeio por te amar - KARYN GARCIAhttp://www.karyngarcia.com.bramar, boyfriend, garcia, karyn, me, odeio, por, teMusictest111test222 http://gdata.youtube.com/feeds/api/videos/gsVaTyb1tBw2008-02-15T04:31:45.000-08:002008-05-14T05:09:42.000-07:00extreme helmet cam Kani, Keil and Patotrimmedperaltamagichttp://gdata.youtube.com/feeds/api/users/peraltamagicextreme helmet cam Kani, Keil and Patotrimmedalcala, cam, campillo, dirt, extreme, helmet, kani, patoSports """ YOUTUBE_ENTRY_PRIVATE = """ http://gdata.youtube.com/feeds/videos/UMFI1hdm96E 2007-01-07T01:50:15.000Z 2007-01-07T01:50:15.000Z "Crazy (Gnarles Barkley)" - Acoustic Cover <div style="color: #000000;font-family: Arial, Helvetica, sans-serif; font-size:12px; font-size: 12px; width: 555px;"><table cellspacing="0" cellpadding="0" border="0"><tbody><tr><td width="140" valign="top" rowspan="2"><div style="border: 1px solid #999999; margin: 0px 10px 5px 0px;"><a href="http://www.youtube.com/watch?v=UMFI1hdm96E"><img alt="" src="http://img.youtube.com/vi/UMFI1hdm96E/2.jpg"></a></div></td> <td width="256" valign="top"><div style="font-size: 12px; font-weight: bold;"><a style="font-size: 15px; font-weight: bold; font-decoration: none;" href="http://www.youtube.com/watch?v=UMFI1hdm96E">&quot;Crazy (Gnarles Barkley)&quot; - Acoustic Cover</a> <br></div> <div style="font-size: 12px; margin: 3px 0px;"><span>Gnarles Barkley acoustic cover http://www.myspace.com/davidchoimusic</span></div></td> <td style="font-size: 11px; line-height: 1.4em; padding-left: 20px; padding-top: 1px;" width="146" valign="top"><div><span style="color: #666666; font-size: 11px;">From:</span> <a href="http://www.youtube.com/profile?user=davidchoimusic">davidchoimusic</a></div> <div><span style="color: #666666; font-size: 11px;">Views:</span> 113321</div> <div style="white-space: nowrap;text-align: left"><img style="border: 0px none; margin: 0px; padding: 0px; vertical-align: middle; font-size: 11px;" align="top" alt="" src="http://gdata.youtube.com/static/images/icn_star_full_11x11.gif"> <img style="border: 0px none; margin: 0px; padding: 0px; vertical-align: middle; font-size: 11px;" align="top" alt="" src="http://gdata.youtube.com/static/images/icn_star_full_11x11.gif"> <img style="border: 0px none; margin: 0px; padding: 0px; vertical-align: middle; font-size: 11px;" align="top" alt="" src="http://gdata.youtube.com/static/images/icn_star_full_11x11.gif"> <img style="border: 0px none; margin: 0px; padding: 0px; vertical-align: middle; font-size: 11px;" align="top" alt="" src="http://gdata.youtube.com/static/images/icn_star_full_11x11.gif"> <img style="border: 0px none; margin: 0px; padding: 0px; vertical-align: middle; font-size: 11px;" align="top" alt="" src="http://gdata.youtube.com/static/images/icn_star_half_11x11.gif"></div> <div style="font-size: 11px;">1005 <span style="color: #666666; font-size: 11px;">ratings</span></div></td></tr> <tr><td><span style="color: #666666; font-size: 11px;">Time:</span> <span style="color: #000000; font-size: 11px; font-weight: bold;">04:15</span></td> <td style="font-size: 11px; padding-left: 20px;"><span style="color: #666666; font-size: 11px;">More in</span> <a href="http://www.youtube.com/categories_portal?c=10">Music</a></td></tr></tbody></table></div> davidchoimusic http://gdata.youtube.com/feeds/users/davidchoimusic "Crazy (Gnarles Barkley)" - Acoustic Cover Gnarles Barkley acoustic cover http://www.myspace.com/davidchoimusic music, singing, gnarls, barkley, acoustic, cover Music DeveloperTag1 37.398529052734375 -122.0635986328125 yes The content of this video may violate the terms of use. """ YOUTUBE_COMMENT_FEED = """ http://gdata.youtube.com/feeds/videos/2Idhz9ef5oU/comments2008-05-19T21:45:45.261ZCommentshttp://www.youtube.com/img/pic_youtubelogo_123x63.gifYouTubehttp://www.youtube.com/YouTube data API0125 http://gdata.youtube.com/feeds/videos/2Idhz9ef5oU/comments/91F809A3DE2EB81B 2008-02-22T15:27:15.000-08:002008-02-22T15:27:15.000-08:00 test66 test66 apitestjhartmannhttp://gdata.youtube.com/feeds/users/apitestjhartmann http://gdata.youtube.com/feeds/videos/2Idhz9ef5oU/comments/A261AEEFD23674AA 2008-02-22T15:27:01.000-08:002008-02-22T15:27:01.000-08:00 test333 test333 apitestjhartmannhttp://gdata.youtube.com/feeds/users/apitestjhartmann http://gdata.youtube.com/feeds/videos/2Idhz9ef5oU/comments/0DCF1E3531B3FF85 2008-02-22T15:11:06.000-08:002008-02-22T15:11:06.000-08:00 test2 test2 apitestjhartmannhttp://gdata.youtube.com/feeds/users/apitestjhartmann """ YOUTUBE_PLAYLIST_FEED = """ http://gdata.youtube.com/feeds/users/andyland74/playlists?start-index=1&max-results=25 2008-02-26T00:26:15.635Z andyland74's Playlists http://www.youtube.com/img/pic_youtubelogo_123x63.gif andyland74 http://gdata.youtube.com/feeds/users/andyland74 YouTube data API 1 1 25 My new playlist Description http://gdata.youtube.com/feeds/users/andyland74/playlists/8BCDD04DE8F771B2 2007-11-04T17:30:27.000-08:00 2008-02-22T09:55:14.000-08:00 My New Playlist Title My new playlist Description andyland74 http://gdata.youtube.com/feeds/users/andyland74 """ YOUTUBE_PLAYLIST_VIDEO_FEED = """http://gdata.youtube.com/feeds/api/playlists/BCB3BB96DF51B5052008-05-16T12:03:17.000-07:00Test PlaylistTest playlist 1http://www.youtube.com/img/pic_youtubelogo_123x63.gifgdpythonhttp://gdata.youtube.com/feeds/api/users/gdpythonYouTube data API1125Test PlaylistTest playlist 1http://gdata.youtube.com/feeds/api/playlists/BCB3BB96DF51B505/B0F29389E537F8882008-05-16T20:54:08.520ZUploading YouTube Videos with the PHP Client LibraryJochen Hartmann demonstrates the basics of how to use the PHP Client Library with the YouTube Data API. PHP Developer's Guide: http://code.google.com/apis/youtube/developers_guide_php.html Other documentation: http://code.google.com/apis/youtube/GoogleDevelopershttp://gdata.youtube.com/feeds/api/users/googledevelopersUploading YouTube Videos with the PHP Client LibraryJochen Hartmann demonstrates the basics of how to use the PHP Client Library with the YouTube Data API. PHP Developer's Guide: http://code.google.com/apis/youtube/developers_guide_php.html Other documentation: http://code.google.com/apis/youtube/api, data, demo, php, screencast, tutorial, uploading, walkthrough, youtubeEducationundefined1""" YOUTUBE_SUBSCRIPTION_FEED = """ http://gdata.youtube.com/feeds/users/andyland74/subscriptions?start-index=1&max-results=25 2008-02-26T00:26:15.635Z andyland74's Subscriptions http://www.youtube.com/img/pic_youtubelogo_123x63.gif andyland74 http://gdata.youtube.com/feeds/users/andyland74 YouTube data API 1 1 25 http://gdata.youtube.com/feeds/users/andyland74/subscriptions/d411759045e2ad8c 2007-11-04T17:30:27.000-08:00 2008-02-22T09:55:14.000-08:00 Videos published by : NBC andyland74 http://gdata.youtube.com/feeds/users/andyland74 NBC """ YOUTUBE_VIDEO_RESPONSE_FEED = """ http://gdata.youtube.com/feeds/videos/2c3q9K4cHzY/responses2008-05-19T22:37:34.076ZVideos responses to 'Giant NES controller coffee table'http://www.youtube.com/img/pic_youtubelogo_123x63.gifYouTubehttp://www.youtube.com/YouTube data API8125 http://gdata.youtube.com/feeds/videos/7b9EnRI9VbY2008-03-11T19:08:53.000-07:002008-05-18T21:33:10.000-07:00 Catnip Partysnipped PismoBeachhttp://gdata.youtube.com/feeds/users/pismobeach Catnip Party Uncle, Hillary, Hankette, and B4 all but overdose on the patioBrattman, cat, catmint, catnip, cats, chat, drug, gato, gatto, kat, kato, katt, Katze, kedi, kissa, OD, overdose, party, sex, Uncle Animals """ YOUTUBE_PROFILE = """ http://gdata.youtube.com/feeds/users/andyland74 2006-10-16T00:09:45.000-07:00 2008-02-26T11:48:21.000-08:00 andyland74 Channel andyland74 http://gdata.youtube.com/feeds/users/andyland74 33 andyland74 andy example Catch-22 m Google Testing YouTube APIs Somewhere US Aqua Teen Hungerforce Elliott Smith Technical Writer University of North Carolina """ YOUTUBE_CONTACTS_FEED = """ http://gdata.youtube.com/feeds/users/apitestjhartmann/contacts2008-05-16T19:24:34.916Zapitestjhartmann's Contactshttp://www.youtube.com/img/pic_youtubelogo_123x63.gifapitestjhartmannhttp://gdata.youtube.com/feeds/users/apitestjhartmannYouTube data API2125 http://gdata.youtube.com/feeds/users/apitestjhartmann/contacts/test898990902008-02-04T11:27:54.000-08:002008-05-16T19:24:34.916Ztest89899090apitestjhartmannhttp://gdata.youtube.com/feeds/users/apitestjhartmanntest89899090requested http://gdata.youtube.com/feeds/users/apitestjhartmann/contacts/testjfisher2008-02-26T14:13:03.000-08:002008-05-16T19:24:34.916Ztestjfisherapitestjhartmannhttp://gdata.youtube.com/feeds/users/apitestjhartmanntestjfisherpending """ NEW_CONTACT = """ http://www.google.com/m8/feeds/contacts/liz%40gmail.com/base/8411573 2008-02-28T18:47:02.303Z Fitzgerald Notes (206)555-1212 456-123-2133 (206)555-1213 1600 Amphitheatre Pkwy Mountain View """ CONTACTS_FEED = """ http://www.google.com/m8/feeds/contacts/liz%40gmail.com/base 2008-03-05T12:36:38.836Z Contacts Elizabeth Bennet liz@gmail.com Contacts 1 1 25 http://www.google.com/m8/feeds/contacts/liz%40gmail.com/base/c9012de 2008-03-05T12:36:38.835Z Fitzgerald 456 """ CONTACT_GROUPS_FEED = """ jo@gmail.com 2008-05-21T21:11:25.237Z Jo's Contact Groups Jo Brown jo@gmail.com Contacts 3 1 25 http://google.com/m8/feeds/groups/jo%40gmail.com/base/270f 2008-05-14T13:10:19.070Z joggers joggers """ CONTACT_GROUP_ENTRY = """ http://www.google.com/feeds/groups/jo%40gmail.com/base/1234 2005-01-18T21:00:00Z 2006-01-01T00:00:00Z Salsa group Salsa group Very nice people. """ CALENDAR_RESOURCE_ENTRY = """ """ CALENDAR_RESOURCES_FEED = """ https://apps-apis.google.com/a/feeds/calendar/resource/2.0/yourdomain.com 2008-10-17T15:29:21.064Z 1 https://apps-apis.google.com/a/feeds/calendar/resource/2.0/yourdomain.com/CR-NYC-14-12-BR 2008-10-17T15:29:21.064Z https://apps-apis.google.com/a/feeds/calendar/resource/2.0/yourdomain.com/?start=(Bike)-London-43-Lobby-Bike-1 2008-10-17T15:29:21.064Z """ BLOG_ENTRY = """ tag:blogger.com,1999:blog-blogID.post-postID 2006-08-02T18:44:43.089-07:00 2006-11-08T18:10:23.020-08:00 Lizzy's Diary Being the journal of Elizabeth Bennet Elizabeth Bennet liz@gmail.com """ BLOG_POST = """ Marriage!

    Mr. Darcy has proposed marriage to me!

    He is the last man on earth I would ever desire to marry.

    Whatever shall I do?

    Elizabeth Bennet liz@gmail.com
    """ BLOG_POSTS_FEED = """ tag:blogger.com,1999:blog-blogID 2006-11-08T18:10:23.020-08:00 Lizzy's Diary Elizabeth Bennet liz@gmail.com Blogger tag:blogger.com,1999:blog-blogID.post-postID 2006-11-08T18:10:00.000-08:00 2006-11-08T18:10:14.954-08:00 Quite disagreeable <p>I met Mr. Bingley's friend Mr. Darcy this evening. I found him quite disagreeable.</p> Elizabeth Bennet liz@gmail.com """ BLOG_COMMENTS_FEED = """ tag:blogger.com,1999:blog-blogID.postpostID..comments 2007-04-04T21:56:29.803-07:00 My Blog : Time to relax Blog Author name Blogger 1 1 tag:blogger.com,1999:blog-blogID.post-commentID 2007-04-04T21:56:00.000-07:00 2007-04-04T21:56:29.803-07:00 This is my first comment This is my first comment Blog Author name """ SITES_FEED = """ https://www.google.com/webmasters/tools/feeds/sites Sites 1 2008-10-02T07:26:51.833Z http://www.example.com http://www.example.com 2007-11-17T18:27:32.543Z true 2008-09-14T08:59:28.000 US none normal true false 456456-google.html """ SITEMAPS_FEED = """ http://www.example.com http://www.example.com/ 2006-11-17T18:27:32.543Z HTML WAP Value1 Value2 Value3 http://www.example.com/sitemap-index.xml http://www.example.com/sitemap-index.xml 2006-11-17T18:27:32.543Z WEB StatusValue 2006-11-18T19:27:32.543Z 102 http://www.example.com/mobile/sitemap-index.xml http://www.example.com/mobile/sitemap-index.xml 2006-11-17T18:27:32.543Z StatusValue 2006-11-18T19:27:32.543Z 102 HTML http://www.example.com/news/sitemap-index.xml http://www.example.com/news/sitemap-index.xml 2006-11-17T18:27:32.543Z StatusValue 2006-11-18T19:27:32.543Z 102 LabelValue """ HEALTH_CCR_NOTICE_PAYLOAD = """ Start date 2007-04-04T07:00:00Z Aortic valve disorders 410.10 ICD9 2004 Active """ HEALTH_PROFILE_ENTRY_DIGEST = """ https://www.google.com/health/feeds/profile/default/vneCn5qdEIY_digest 2008-09-29T07:52:17.176Z vneCn5qdEIY English en ISO-639-1 V1.0 2008-09-29T07:52:17.176Z Google Health Profile Pregnancy status Not pregnant user@google.com Patient Breastfeeding status Not breastfeeding user@gmail.com Patient Hn0FE0IlcY-FMFFgSTxkvA/CONDITION/0 Start date 2007-04-04T07:00:00Z Aortic valve disorders 410.10 ICD9 2004 Active example.com Information Provider Malaria 136.9 ICD9_Broader 084.6 ICD9 ACTIVE user@gmail.com Patient Race S15814 HL7 White user@gmail.com Patient Allergy A-Fil ACTIVE user@gmail.com Patient Severe Allergy A.E.R Traveler ACTIVE user@gmail.com Patient Severe ACTIVE user@gmail.com Patient A& D 0 0 To skin C38305 FDA 0 ACTIVE user@gmail.com Patient A-Fil 0 0 To skin C38305 FDA 0 ACTIVE user@gmail.com Patient Lipitor 0 0 By mouth C38288 FDA 0 user@gmail.com Patient Chickenpox Vaccine 21 HL7 user@gmail.com Patient Height 0 70 inches user@gmail.com Patient Weight 0 2480 ounces user@gmail.com Patient Blood Type 0 O+ user@gmail.com Patient Collection start date 2008-09-03 Acetaldehyde - Blood 0 Abdominal Ultrasound user@gmail.com Patient Abdominoplasty user@gmail.com Patient Google Health Profile 1984-07-22 Male user@gmail.com Patient """ HEALTH_PROFILE_FEED = """ https://www.google.com/health/feeds/profile/default 2008-09-30T01:07:17.888Z Profile Feed 1 https://www.google.com/health/feeds/profile/default/DysasdfARnFAao 2008-09-29T03:12:50.850Z 2008-09-29T03:12:50.850Z <content type="html"/> <link rel="http://schemas.google.com/health/data#complete" type="application/atom+xml" href="https://www.google.com/health/feeds/profile/default/-/MEDICATION/%7Bhttp%3A%2F%2Fschemas.google.com%2Fg%2F2005%23kind%7Dhttp%3A%2F%2Fschemas.google.com%2Fhealth%2Fkinds%23profile/%7Bhttp%3A%2F%2Fschemas.google.com%2Fhealth%2Fitem%7DA%26+D"/> <link rel="self" type="application/atom+xml" href="https://www.google.com/health/feeds/profile/default/DysasdfARnFAao"/> <link rel="edit" type="application/atom+xml" href="https://www.google.com/health/feeds/profile/default/DysasdfARnFAao"/> <author> <name>User Name</name> <email>user@gmail.com</email> </author> <ContinuityOfCareRecord xmlns="urn:astm-org:CCR"> <CCRDocumentObjectID>hiD9sEigSzdk8nNT0evR4g</CCRDocumentObjectID> <Language/> <DateTime> <Type/> </DateTime> <Patient/> <Body> <Medications> <Medication> <Type/> <Description/> <Status> <Text>ACTIVE</Text> </Status> <Source> <Actor> <ActorID>user@gmail.com</ActorID> <ActorRole> <Text>Patient</Text> </ActorRole> </Actor> </Source> <Product> <ProductName> <Text>A& D</Text> </ProductName> <Strength> <Units/> <StrengthSequencePosition>0</StrengthSequencePosition> <VariableStrengthModifier/> </Strength> </Product> <Directions> <Direction> <Description/> <DeliveryMethod/> <Dose> <Units/> <DoseSequencePosition>0</DoseSequencePosition> <VariableDoseModifier/> </Dose> <Route> <Text>To skin</Text> <Code> <Value>C38305</Value> <CodingSystem>FDA</CodingSystem> </Code> <RouteSequencePosition>0</RouteSequencePosition> <MultipleRouteModifier/> </Route> </Direction> </Directions> <Refills/> </Medication> </Medications> </Body> </ContinuityOfCareRecord> </entry> <entry> <id>https://www.google.com/health/feeds/profile/default/7I1WQzZrgp4</id> <published>2008-09-29T03:27:14.909Z</published> <updated>2008-09-29T03:27:14.909Z</updated> <category scheme="http://schemas.google.com/g/2005#kind" term="http://schemas.google.com/health/kinds#profile"/> <category scheme="http://schemas.google.com/health/item" term="A-Fil"/> <category term="ALLERGY"/> <title type="text"/> <content type="html"/> <link rel="http://schemas.google.com/health/data#complete" type="application/atom+xml" href="https://www.google.com/health/feeds/profile/default/-/%7Bhttp%3A%2F%2Fschemas.google.com%2Fg%2F2005%23kind%7Dhttp%3A%2F%2Fschemas.google.com%2Fhealth%2Fkinds%23profile/%7Bhttp%3A%2F%2Fschemas.google.com%2Fhealth%2Fitem%7DA-Fil/ALLERGY"/> <link rel="self" type="application/atom+xml" href="https://www.google.com/health/feeds/profile/default/7I1WQzZrgp4"/> <link rel="edit" type="application/atom+xml" href="https://www.google.com/health/feeds/profile/default/7I1WQzZrgp4"/> <author> <name>User Name</name> <email>user@gmail.com</email> </author> <ContinuityOfCareRecord xmlns="urn:astm-org:CCR"> <CCRDocumentObjectID>YOyHDxQUiECCPgnsjV8SlQ</CCRDocumentObjectID> <Language/> <DateTime> <Type/> </DateTime> <Patient/> <Body> <Alerts> <Alert> <Type> <Text>Allergy</Text> </Type> <Description> <Text>A-Fil</Text> </Description> <Status> <Text>ACTIVE</Text> </Status> <Source> <Actor> <ActorID>user@gmail.com</ActorID> <ActorRole> <Text>Patient</Text> </ActorRole> </Actor> </Source> <Reaction> <Description/> <Severity> <Text>Severe</Text> </Severity> </Reaction> </Alert> </Alerts> </Body> </ContinuityOfCareRecord> </entry> <entry> <id>https://www.google.com/health/feeds/profile/default/Dz9wV83sKFg</id> <published>2008-09-29T03:12:52.166Z</published> <updated>2008-09-29T03:12:52.167Z</updated> <category term="MEDICATION"/> <category scheme="http://schemas.google.com/g/2005#kind" term="http://schemas.google.com/health/kinds#profile"/> <category scheme="http://schemas.google.com/health/item" term="A-Fil"/> <title type="text"/> <content type="html"/> <link rel="http://schemas.google.com/health/data#complete" type="application/atom+xml" href="https://www.google.com/health/feeds/profile/default/-/MEDICATION/%7Bhttp%3A%2F%2Fschemas.google.com%2Fg%2F2005%23kind%7Dhttp%3A%2F%2Fschemas.google.com%2Fhealth%2Fkinds%23profile/%7Bhttp%3A%2F%2Fschemas.google.com%2Fhealth%2Fitem%7DA-Fil"/> <link rel="self" type="application/atom+xml" href="https://www.google.com/health/feeds/profile/default/Dz9wV83sKFg"/> <link rel="edit" type="application/atom+xml" href="https://www.google.com/health/feeds/profile/default/Dz9wV83sKFg"/> <author> <name>User Name</name> <email>user@gmail.com</email> </author> <ContinuityOfCareRecord xmlns="urn:astm-org:CCR"> <CCRDocumentObjectID>7w.XFEPeuIYN3Rn32pUiUw</CCRDocumentObjectID> <Language/> <DateTime> <Type/> </DateTime> <Patient/> <Body> <Medications> <Medication> <Type/> <Description/> <Status> <Text>ACTIVE</Text> </Status> <Source> <Actor> <ActorID>user@gmail.com</ActorID> <ActorRole> <Text>Patient</Text> </ActorRole> </Actor> </Source> <Product> <ProductName> <Text>A-Fil</Text> </ProductName> <Strength> <Units/> <StrengthSequencePosition>0</StrengthSequencePosition> <VariableStrengthModifier/> </Strength> </Product> <Directions> <Direction> <Description/> <DeliveryMethod/> <Dose> <Units/> <DoseSequencePosition>0</DoseSequencePosition> <VariableDoseModifier/> </Dose> <Route> <Text>To skin</Text> <Code> <Value>C38305</Value> <CodingSystem>FDA</CodingSystem> </Code> <RouteSequencePosition>0</RouteSequencePosition> <MultipleRouteModifier/> </Route> </Direction> </Directions> <Refills/> </Medication> </Medications> </Body> </ContinuityOfCareRecord> </entry> <entry> <id>https://www.google.com/health/feeds/profile/default/lzsxVzqZUyw</id> <published>2008-09-29T03:13:07.496Z</published> <updated>2008-09-29T03:13:07.497Z</updated> <category scheme="http://schemas.google.com/health/item" term="A.E.R Traveler"/> <category scheme="http://schemas.google.com/g/2005#kind" term="http://schemas.google.com/health/kinds#profile"/> <category term="ALLERGY"/> <title type="text"/> <content type="html"/> <link rel="http://schemas.google.com/health/data#complete" type="application/atom+xml" href="https://www.google.com/health/feeds/profile/default/-/%7Bhttp%3A%2F%2Fschemas.google.com%2Fhealth%2Fitem%7DA.E.R+Traveler/%7Bhttp%3A%2F%2Fschemas.google.com%2Fg%2F2005%23kind%7Dhttp%3A%2F%2Fschemas.google.com%2Fhealth%2Fkinds%23profile/ALLERGY"/> <link rel="self" type="application/atom+xml" href="https://www.google.com/health/feeds/profile/default/lzsxVzqZUyw"/> <link rel="edit" type="application/atom+xml" href="https://www.google.com/health/feeds/profile/default/lzsxVzqZUyw"/> <author> <name>User Name</name> <email>user@gmail.com</email> </author> <ContinuityOfCareRecord xmlns="urn:astm-org:CCR"> <CCRDocumentObjectID>5efFB0J2WgEHNUvk2z3A1A</CCRDocumentObjectID> <Language/> <DateTime> <Type/> </DateTime> <Patient/> <Body> <Alerts> <Alert> <Type> <Text>Allergy</Text> </Type> <Description> <Text>A.E.R Traveler</Text> </Description> <Status> <Text>ACTIVE</Text> </Status> <Source> <Actor> <ActorID>user@gmail.com</ActorID> <ActorRole> <Text>Patient</Text> </ActorRole> </Actor> </Source> <Reaction> <Description/> <Severity> <Text>Severe</Text> </Severity> </Reaction> </Alert> </Alerts> </Body> </ContinuityOfCareRecord> </entry> <entry> <id>https://www.google.com/health/feeds/profile/default/6PvhfKAXyYw</id> <published>2008-09-29T03:13:02.123Z</published> <updated>2008-09-29T03:13:02.124Z</updated> <category scheme="http://schemas.google.com/g/2005#kind" term="http://schemas.google.com/health/kinds#profile"/> <category term="PROCEDURE"/> <category scheme="http://schemas.google.com/health/item" term="Abdominal Ultrasound"/> <title type="text"/> <content type="html"/> <link rel="http://schemas.google.com/health/data#complete" type="application/atom+xml" href="https://www.google.com/health/feeds/profile/default/-/%7Bhttp%3A%2F%2Fschemas.google.com%2Fg%2F2005%23kind%7Dhttp%3A%2F%2Fschemas.google.com%2Fhealth%2Fkinds%23profile/PROCEDURE/%7Bhttp%3A%2F%2Fschemas.google.com%2Fhealth%2Fitem%7DAbdominal+Ultrasound"/> <link rel="self" type="application/atom+xml" href="https://www.google.com/health/feeds/profile/default/6PvhfKAXyYw"/> <link rel="edit" type="application/atom+xml" href="https://www.google.com/health/feeds/profile/default/6PvhfKAXyYw"/> <author> <name>User Name</name> <email>user@gmail.com</email> </author> <ContinuityOfCareRecord xmlns="urn:astm-org:CCR"> <CCRDocumentObjectID>W3Wbvx_QHwG5pxVchpuF1A</CCRDocumentObjectID> <Language/> <DateTime> <Type/> </DateTime> <Patient/> <Body> <Procedures> <Procedure> <Type/> <Description> <Text>Abdominal Ultrasound</Text> </Description> <Status/> <Source> <Actor> <ActorID>user@gmail.com</ActorID> <ActorRole> <Text>Patient</Text> </ActorRole> </Actor> </Source> </Procedure> </Procedures> </Body> </ContinuityOfCareRecord> </entry> <entry> <id>https://www.google.com/health/feeds/profile/default/r2zGPGewCeU</id> <published>2008-09-29T03:13:03.434Z</published> <updated>2008-09-29T03:13:03.435Z</updated> <category scheme="http://schemas.google.com/health/item" term="Abdominoplasty"/> <category scheme="http://schemas.google.com/g/2005#kind" term="http://schemas.google.com/health/kinds#profile"/> <category term="PROCEDURE"/> <title type="text"/> <content type="html"/> <link rel="http://schemas.google.com/health/data#complete" type="application/atom+xml" href="https://www.google.com/health/feeds/profile/default/-/%7Bhttp%3A%2F%2Fschemas.google.com%2Fhealth%2Fitem%7DAbdominoplasty/%7Bhttp%3A%2F%2Fschemas.google.com%2Fg%2F2005%23kind%7Dhttp%3A%2F%2Fschemas.google.com%2Fhealth%2Fkinds%23profile/PROCEDURE"/> <link rel="self" type="application/atom+xml" href="https://www.google.com/health/feeds/profile/default/r2zGPGewCeU"/> <link rel="edit" type="application/atom+xml" href="https://www.google.com/health/feeds/profile/default/r2zGPGewCeU"/> <author> <name>User Name</name> <email>user@gmail.com</email> </author> <ContinuityOfCareRecord xmlns="urn:astm-org:CCR"> <CCRDocumentObjectID>OUKgj5X0KMnbkC5sDL.yHA</CCRDocumentObjectID> <Language/> <DateTime> <Type/> </DateTime> <Patient/> <Body> <Procedures> <Procedure> <Type/> <Description> <Text>Abdominoplasty</Text> </Description> <Status/> <Source> <Actor> <ActorID>user@gmail.com</ActorID> <ActorRole> <Text>Patient</Text> </ActorRole> </Actor> </Source> </Procedure> </Procedures> </Body> </ContinuityOfCareRecord> </entry> <entry> <id>https://www.google.com/health/feeds/profile/default/_cCCbQ0O3ug</id> <published>2008-09-29T03:13:29.041Z</published> <updated>2008-09-29T03:13:29.042Z</updated> <category scheme="http://schemas.google.com/g/2005#kind" term="http://schemas.google.com/health/kinds#profile"/> <category scheme="http://schemas.google.com/health/item" term="Acetaldehyde - Blood"/> <category term="LABTEST"/> <title type="text"/> <content type="html"/> <link rel="http://schemas.google.com/health/data#complete" type="application/atom+xml" href="https://www.google.com/health/feeds/profile/default/-/%7Bhttp%3A%2F%2Fschemas.google.com%2Fg%2F2005%23kind%7Dhttp%3A%2F%2Fschemas.google.com%2Fhealth%2Fkinds%23profile/%7Bhttp%3A%2F%2Fschemas.google.com%2Fhealth%2Fitem%7DAcetaldehyde+-+Blood/LABTEST"/> <link rel="self" type="application/atom+xml" href="https://www.google.com/health/feeds/profile/default/_cCCbQ0O3ug"/> <link rel="edit" type="application/atom+xml" href="https://www.google.com/health/feeds/profile/default/_cCCbQ0O3ug"/> <author> <name>User Name</name> <email>user@gmail.com</email> </author> <ContinuityOfCareRecord xmlns="urn:astm-org:CCR"> <CCRDocumentObjectID>YWtomFb8aG.DueZ7z7fyug</CCRDocumentObjectID> <Language/> <DateTime> <Type/> </DateTime> <Patient/> <Body> <Results> <Result> <Type/> <Description/> <Status/> <Source> <Actor> <ActorID>user@gmail.com</ActorID> <ActorRole> <Text>Patient</Text> </ActorRole> </Actor> </Source> <Substance/> <Test> <DateTime> <Type> <Text>Collection start date</Text> </Type> <ExactDateTime>2008-09-03</ExactDateTime> </DateTime> <Type/> <Description> <Text>Acetaldehyde - Blood</Text> </Description> <Status/> <TestResult> <ResultSequencePosition>0</ResultSequencePosition> <VariableResultModifier/> <Units/> </TestResult> <ConfidenceValue/> </Test> </Result> </Results> </Body> </ContinuityOfCareRecord> </entry> <entry> <id>https://www.google.com/health/feeds/profile/default/BdyA3iJZyCc</id> <published>2008-09-29T03:00:45.915Z</published> <updated>2008-09-29T03:00:45.915Z</updated> <category scheme="http://schemas.google.com/health/item" term="Aortic valve disorders"/> <category scheme="http://schemas.google.com/g/2005#kind" term="http://schemas.google.com/health/kinds#profile"/> <category term="CONDITION"/> <title type="text">Aortic valve disorders example.com example.com h1ljpoeKJ85li.1FHsG9Gw Hn0FE0IlcY-FMFFgSTxkvA/CONDITION/0 Start date 2007-04-04T07:00:00Z Aortic valve disorders 410.10 ICD9 2004 Active example.com Information Provider https://www.google.com/health/feeds/profile/default/Cl.aMWIH5VA 2008-09-29T03:13:34.996Z 2008-09-29T03:13:34.997Z <content type="html"/> <link rel="http://schemas.google.com/health/data#complete" type="application/atom+xml" href="https://www.google.com/health/feeds/profile/default/-/%7Bhttp%3A%2F%2Fschemas.google.com%2Fg%2F2005%23kind%7Dhttp%3A%2F%2Fschemas.google.com%2Fhealth%2Fkinds%23profile/%7Bhttp%3A%2F%2Fschemas.google.com%2Fhealth%2Fitem%7DChickenpox+Vaccine/IMMUNIZATION"/> <link rel="self" type="application/atom+xml" href="https://www.google.com/health/feeds/profile/default/Cl.aMWIH5VA"/> <link rel="edit" type="application/atom+xml" href="https://www.google.com/health/feeds/profile/default/Cl.aMWIH5VA"/> <author> <name>User Name</name> <email>user@gmail.com</email> </author> <ContinuityOfCareRecord xmlns="urn:astm-org:CCR"> <CCRDocumentObjectID>KlhUqfftgELIitpKbqYalw</CCRDocumentObjectID> <Language/> <DateTime> <Type/> </DateTime> <Patient/> <Body> <Immunizations> <Immunization> <Type/> <Description/> <Status/> <Source> <Actor> <ActorID>user@gmail.com</ActorID> <ActorRole> <Text>Patient</Text> </ActorRole> </Actor> </Source> <Product> <ProductName> <Text>Chickenpox Vaccine</Text> <Code> <Value>21</Value> <CodingSystem>HL7</CodingSystem> </Code> </ProductName> </Product> <Directions> <Direction> <Description/> <DeliveryMethod/> </Direction> </Directions> <Refills/> </Immunization> </Immunizations> </Body> </ContinuityOfCareRecord> </entry> <entry> <id>https://www.google.com/health/feeds/profile/default/l0a7.FlX3_0</id> <published>2008-09-29T03:14:47.461Z</published> <updated>2008-09-29T03:14:47.461Z</updated> <category scheme="http://schemas.google.com/g/2005#kind" term="http://schemas.google.com/health/kinds#profile"/> <category term="DEMOGRAPHICS"/> <category scheme="http://schemas.google.com/health/item" term="Demographics"/> <title type="text">Demographics User Name user@gmail.com U5GDAVOxFbexQw3iyvqPYg 1984-07-22 Male user@gmail.com Patient https://www.google.com/health/feeds/profile/default/oIBDdgwFLyo 2008-09-29T03:14:47.690Z 2008-09-29T03:14:47.691Z FunctionalStatus User Name user@gmail.com W.EJcnhxb7W5M4eR4Tr1YA Pregnancy status Not pregnant user@gmail.com Patient Breastfeeding status Not breastfeeding user@gmail.com Patient https://www.google.com/health/feeds/profile/default/wwljIlXuTVg 2008-09-29T03:26:10.080Z 2008-09-29T03:26:10.081Z <content type="html"/> <link rel="http://schemas.google.com/health/data#complete" type="application/atom+xml" href="https://www.google.com/health/feeds/profile/default/-/MEDICATION/%7Bhttp%3A%2F%2Fschemas.google.com%2Fg%2F2005%23kind%7Dhttp%3A%2F%2Fschemas.google.com%2Fhealth%2Fkinds%23profile/%7Bhttp%3A%2F%2Fschemas.google.com%2Fhealth%2Fitem%7DLipitor"/> <link rel="self" type="application/atom+xml" href="https://www.google.com/health/feeds/profile/default/wwljIlXuTVg"/> <link rel="edit" type="application/atom+xml" href="https://www.google.com/health/feeds/profile/default/wwljIlXuTVg"/> <author> <name>User Name</name> <email>user@gmail.com</email> </author> <ContinuityOfCareRecord xmlns="urn:astm-org:CCR"> <CCRDocumentObjectID>OrpghzvvbG_YaO5koqT2ug</CCRDocumentObjectID> <Language/> <DateTime> <Type/> </DateTime> <Patient/> <Body> <Medications> <Medication> <Type/> <Description/> <Status> <Text>ACTIVE</Text> </Status> <Source> <Actor> <ActorID>user@gmail.com</ActorID> <ActorRole> <Text>Patient</Text> </ActorRole> </Actor> </Source> <Product> <ProductName> <Text>Lipitor</Text> </ProductName> <Strength> <Units/> <StrengthSequencePosition>0</StrengthSequencePosition> <VariableStrengthModifier/> </Strength> </Product> <Directions> <Direction> <Description/> <DeliveryMethod/> <Dose> <Units/> <DoseSequencePosition>0</DoseSequencePosition> <VariableDoseModifier/> </Dose> <Route> <Text>By mouth</Text> <Code> <Value>C38288</Value> <CodingSystem>FDA</CodingSystem> </Code> <RouteSequencePosition>0</RouteSequencePosition> <MultipleRouteModifier/> </Route> </Direction> </Directions> <Refills/> </Medication> </Medications> </Body> </ContinuityOfCareRecord> </entry> <entry> <id>https://www.google.com/health/feeds/profile/default/dd09TR12SiY</id> <published>2008-09-29T07:52:17.175Z</published> <updated>2008-09-29T07:52:17.176Z</updated> <category scheme="http://schemas.google.com/g/2005#kind" term="http://schemas.google.com/health/kinds#profile"/> <category scheme="http://schemas.google.com/health/item" term="Malaria"/> <category term="CONDITION"/> <title type="text"/> <content type="html"/> <link rel="http://schemas.google.com/health/data#complete" type="application/atom+xml" href="https://www.google.com/health/feeds/profile/default/-/%7Bhttp%3A%2F%2Fschemas.google.com%2Fg%2F2005%23kind%7Dhttp%3A%2F%2Fschemas.google.com%2Fhealth%2Fkinds%23profile/%7Bhttp%3A%2F%2Fschemas.google.com%2Fhealth%2Fitem%7DMalaria/CONDITION"/> <link rel="self" type="application/atom+xml" href="https://www.google.com/health/feeds/profile/default/dd09TR12SiY"/> <link rel="edit" type="application/atom+xml" href="https://www.google.com/health/feeds/profile/default/dd09TR12SiY"/> <author> <name>User Name</name> <email>user@gmail.com</email> </author> <ContinuityOfCareRecord xmlns="urn:astm-org:CCR"> <CCRDocumentObjectID>XF99N6X4lpy.jfPUPLMMSQ</CCRDocumentObjectID> <Language/> <DateTime> <Type/> </DateTime> <Patient/> <Body> <Problems> <Problem> <Type/> <Description> <Text>Malaria</Text> <Code> <Value>136.9</Value> <CodingSystem>ICD9_Broader</CodingSystem> </Code> <Code> <Value>084.6</Value> <CodingSystem>ICD9</CodingSystem> </Code> </Description> <Status> <Text>ACTIVE</Text> </Status> <Source> <Actor> <ActorID>user@gmail.com</ActorID> <ActorRole> <Text>Patient</Text> </ActorRole> </Actor> </Source> <HealthStatus> <Description/> </HealthStatus> </Problem> </Problems> </Body> </ContinuityOfCareRecord> </entry> <entry> <id>https://www.google.com/health/feeds/profile/default/aS0Cf964DPs</id> <published>2008-09-29T03:14:47.463Z</published> <updated>2008-09-29T03:14:47.463Z</updated> <category scheme="http://schemas.google.com/g/2005#kind" term="http://schemas.google.com/health/kinds#profile"/> <category term="DEMOGRAPHICS"/> <category scheme="http://schemas.google.com/health/item" term="SocialHistory (Drinking, Smoking)"/> <title type="text">SocialHistory (Drinking, Smoking) User Name user@gmail.com kXylGU5YXLBzriv61xPGZQ Race S15814 HL7 White user@gmail.com Patient https://www.google.com/health/feeds/profile/default/s5lII5xfj_g 2008-09-29T03:14:47.544Z 2008-09-29T03:14:47.545Z VitalSigns User Name user@gmail.com FTTIiY0TVVj35kZqFFjPjQ user@gmail.com Patient Height 0 70 inches user@gmail.com Patient Weight 0 2480 ounces user@gmail.com Patient Blood Type 0 O+ """ HEALTH_PROFILE_LIST_ENTRY = """ https://www.google.com/health/feeds/profile/list/vndCn5sdfwdEIY 1970-01-01T00:00:00.000Z profile name vndCn5sdfwdEIY user@gmail.com """ BOOK_ENTRY = """"""\ """"""\ """http://www.google.com/books/feeds/volumes/b7GZr5Btp30C"""\ """2009-04-24T23:35:16.000Z"""\ """"""\ """A theory of justice"""\ """"""\ """"""\ """"""\ """"""\ """"""\ """"""\ """"""\ """"""\ """"""\ """John Rawls"""\ """1999"""\ """p Since it appeared in 1971, John Rawls's i A Theory of Justice /i has become a classic. The author has now revised the original edition to clear up a number of difficulties he and others have found in the original book. /p p Rawls aims to express an essential part of the common core of the democratic tradition--justice as fairness--and to provide an alternative to utilitarianism, which had dominated the Anglo-Saxon tradition of political thought since the nineteenth century. Rawls substitutes the ideal of the social contract as a more satisfactory account of the basic rights and liberties of citizens as free and equal persons. "Each person," writes Rawls, "possesses an inviolability founded on justice that even the welfare of society as a whole cannot override." Advancing the ideas of Rousseau, Kant, Emerson, and Lincoln, Rawls's theory is as powerful today as it was when first published. /p"""\ """538 pages"""\ """b7GZr5Btp30C"""\ """ISBN:0198250541"""\ """ISBN:9780198250548"""\ """en"""\ """Oxford University Press"""\ """A theory of justice"""\ """""" BOOK_FEED = """"""\ """"""\ """http://www.google.com/books/feeds/volumes"""\ """2009-04-24T23:39:47.000Z"""\ """"""\ """Search results for 9780198250548"""\ """"""\ """"""\ """"""\ """"""\ """Google Books Search"""\ """http://www.google.com"""\ """"""\ """Google Book Search data API"""\ """1"""\ """1"""\ """20"""\ """"""\ """http://www.google.com/books/feeds/volumes/b7GZr5Btp30C"""\ """2009-04-24T23:39:47.000Z"""\ """"""\ """A theory of justice"""\ """"""\ """"""\ """"""\ """"""\ """"""\ """"""\ """"""\ """"""\ """"""\ """John Rawls"""\ """1999"""\ """... 9780198250548 ..."""\ """538 pages"""\ """b7GZr5Btp30C"""\ """ISBN:0198250541"""\ """ISBN:9780198250548"""\ """Law"""\ """A theory of justice"""\ """"""\ """""" MAP_FEED = """ http://maps.google.com/maps/feeds/maps/208825816854482607313 2009-07-27T18:48:29.631Z My maps Roman 1 1 1 http://maps.google.com/maps/feeds/maps/208825816854482607313/00046fb45f88fa910bcea 2009-07-27T18:46:34.451Z 2009-07-27T18:48:29.631Z 2009-07-27T18:48:29.631Z yes Untitled Roman """ MAP_ENTRY = """ http://maps.google.com/maps/feeds/maps/208825816854482607313/00046fb45f88fa910bcea 2009-07-27T18:46:34.451Z 2009-07-27T18:48:29.631Z 2009-07-27T18:48:29.631Z yes Untitled Roman """ MAP_FEATURE_FEED = """ http://maps.google.com/maps/feeds/features/208825816854482607313/00046fb45f88fa910bcea 2009-07-27T18:48:29.631Z Untitled 4 1 4 http://maps.google.com/maps/feeds/features/208825816854482607313/00046fb45f88fa910bcea/00046fb4632573b19e0b7 2009-07-27T18:47:35.037Z 2009-07-27T18:47:35.037Z 2009-07-27T18:47:35.037Z Some feature title Some feature title Some feature content
    ]]> -113.818359,41.442726,0.0 Roman Roman http://maps.google.com/maps/feeds/features/208825816854482607313/00046fb45f88fa910bcea/00046fb46325e839a11e6 2009-07-27T18:47:35.067Z 2009-07-27T18:48:22.184Z 2009-07-27T18:48:22.184Z A cool poly! A cool poly! And a description]]> 1 -109.775391,47.457809,0.0 -99.755859,51.508742,0.0 -92.900391,48.04871,0.0 -92.8125,44.339565,0.0 -95.273437,44.402392,0.0 -97.207031,46.619261,0.0 -100.898437,46.073231,0.0 -102.480469,43.068888,0.0 -110.742187,45.274886,0.0 -109.775391,47.457809,0.0 Roman Roman http://maps.google.com/maps/feeds/features/208825816854482607313/00046fb45f88fa910bcea/00046fb465f5002e56b7a 2009-07-27T18:48:22.194Z 2009-07-27T18:48:22.194Z 2009-07-27T18:48:22.194Z New Mexico New Mexico Word.]]> 1 -110.039062,37.788081,0.0 -103.183594,37.926868,0.0 -103.183594,32.472695,0.0 -108.896484,32.026706,0.0 -109.863281,31.203405,0.0 -110.039062,37.788081,0.0 Roman Roman """ MAP_FEATURE_ENTRY = """ http://maps.google.com/maps/feeds/features/208825816854482607313/00046fb45f88fa910bcea/00046fb4632573b19e0b7 2009-07-27T18:47:35.037Z 2009-07-27T18:47:35.037Z 2009-07-27T18:47:35.037Z Some feature title Some feature title Some feature content]]> -113.818359,41.442726,0.0 Roman Roman """ MAP_FEATURE_KML = """ Some feature title Some feature content]]> -113.818359,41.442726,0.0 """ SITES_LISTPAGE_ENTRY = ''' http:///sites.google.com/feeds/content/site/gdatatestsite/1712987567114738703 2009-06-16T00:37:37.393Z ListPagesTitle
    stuff go here
    asdf
    sdf

    Test User test@gmail.com
    ''' SITES_COMMENT_ENTRY = ''' http://sites.google.com/feeds/content/site/gdatatestsite/abc123 2009-06-15T18:40:22.407Z <content type="xhtml"> <div xmlns="http://www.w3.org/1999/xhtml">first comment</div> </content> <link rel="http://schemas.google.com/sites/2008#parent" type="application/atom+xml" href="http://sites.google.com/feeds/content/site/gdatatestsite/abc123parent"/> <link rel="self" type="application/atom+xml" href="http://sites.google.com/feeds/content/site/gdatatestsite/abc123"/> <link rel="edit" type="application/atom+xml" href="http://sites.google.com/feeds/content/site/gdatatestsite/abc123"/> <author> <name>Test User</name> <email>test@gmail.com</email> </author> <thr:in-reply-to xmlns:thr="http://purl.org/syndication/thread/1.0" href="http://sites.google.com/site/gdatatestsite/annoucment/testpost" ref="http://sites.google.com/feeds/content/site/gdatatestsite/abc123" source="http://sites.google.com/feeds/content/site/gdatatestsite" type="text/html"/> </entry>''' SITES_LISTITEM_ENTRY = '''<?xml version="1.0" encoding="UTF-8"?> <entry xmlns="http://www.w3.org/2005/Atom"> <id>http://sites.google.com/feeds/content/site/gdatatestsite/abc123</id> <updated>2009-06-16T00:34:55.633Z</updated> <category scheme="http://schemas.google.com/g/2005#kind" term="http://schemas.google.com/sites/2008#listitem"/> <title type="text"/> <link rel="http://schemas.google.com/sites/2008#parent" type="application/atom+xml" href="http://sites.google.com/feeds/content/site/gdatatestsite/abc123def"/> <link rel="self" type="application/atom+xml" href="http://sites.google.com/feeds/content/site/gdatatestsite/abc123"/> <link rel="edit" type="application/atom+xml" href="http://sites.google.com/feeds/content/site/gdatatestsite/abc123"/> <author> <name>Test User</name> <email>test@gmail.com</email> </author> <gs:field xmlns:gs="http://schemas.google.com/spreadsheets/2006" index="A" name="Owner">test value</gs:field> <gs:field xmlns:gs="http://schemas.google.com/spreadsheets/2006" index="B" name="Description">test</gs:field> <gs:field xmlns:gs="http://schemas.google.com/spreadsheets/2006" index="C" name="Resolution">90</gs:field> <gs:field xmlns:gs="http://schemas.google.com/spreadsheets/2006" index="D" name="Complete"/> <gs:field xmlns:gs="http://schemas.google.com/spreadsheets/2006" index="E" name="MyCo">2009-05-31</gs:field> </entry>''' SITES_CONTENT_FEED = '''<?xml version="1.0" encoding="UTF-8"?> <feed xmlns="http://www.w3.org/2005/Atom" xmlns:openSearch="http://a9.com/-/spec/opensearch/1.1/" xmlns:sites="http://schemas.google.com/sites/2008" xmlns:gs="http://schemas.google.com/spreadsheets/2006" xmlns:dc="http://purl.org/dc/terms" xmlns:batch="http://schemas.google.com/gdata/batch" xmlns:gd="http://schemas.google.com/g/2005" xmlns:thr="http://purl.org/syndication/thread/1.0"> <id>http://sites.google.com/feeds/content/site/gdatatestsite</id> <updated>2009-06-15T21:35:43.282Z</updated> <link rel="http://schemas.google.com/g/2005#feed" type="application/atom+xml" href="http://sites.google.com/feeds/content/site/gdatatestsite"/> <link rel="http://schemas.google.com/g/2005#post" type="application/atom+xml" href="http://sites.google.com/feeds/content/site/gdatatestsite"/> <link rel="self" type="application/atom+xml" href="http://sites.google.com/feeds/content/site/gdatatestsite"/> <generator version="1" uri="http://sites.google.com">Google Sites</generator> <openSearch:startIndex>1</openSearch:startIndex> <entry> <id>http:///sites.google.com/feeds/content/site/gdatatestsite/1712987567114738703</id> <updated>2009-06-16T00:37:37.393Z</updated> <category scheme="http://schemas.google.com/g/2005#kind" term="http://schemas.google.com/sites/2008#listpage"/> <title type="text">ListPagesTitle
    stuff go here
    asdf
    sdf

    Test User test@gmail.com 2 home
    http://sites.google.com/feeds/content/site/gdatatestsite/abc123 2009-06-17T00:40:37.082Z filecabinet
    sdf
    Test User test@gmail.com
    http://sites.google.com/feeds/content/site/gdatatestsite/abc123 2009-06-16T00:34:55.633Z <link rel="http://schemas.google.com/sites/2008#parent" type="application/atom+xml" href="http://sites.google.com/feeds/content/site/gdatatestsite/abc123def"/> <link rel="self" type="application/atom+xml" href="http://sites.google.com/feeds/content/site/gdatatestsite/abc123"/> <link rel="edit" type="application/atom+xml" href="http://sites.google.com/feeds/content/site/gdatatestsite/abc123"/> <link rel="http://schemas.google.com/sites/2008#revision" type="application/atom+xml" href="http:///sites.google.com/feeds/content/site/gdatatestsite/abc123"/> <author> <name>Test User</name> <email>test@gmail.com</email> </author> <gs:field xmlns:gs="http://schemas.google.com/spreadsheets/2006" index="A" name="Owner">test value</gs:field> <gs:field xmlns:gs="http://schemas.google.com/spreadsheets/2006" index="B" name="Description">test</gs:field> <gs:field xmlns:gs="http://schemas.google.com/spreadsheets/2006" index="C" name="Resolution">90</gs:field> <gs:field xmlns:gs="http://schemas.google.com/spreadsheets/2006" index="D" name="Complete"/> <gs:field xmlns:gs="http://schemas.google.com/spreadsheets/2006" index="E" name="MyCo">2009-05-31</gs:field> </entry> <entry> <id>http://sites.google.com/feeds/content/site/gdatatestsite/abc123</id> <updated>2009-06-15T18:40:32.922Z</updated> <category scheme="http://schemas.google.com/g/2005#kind" term="http://schemas.google.com/sites/2008#attachment"/> <title type="text">testFile.ods Test User test@gmail.com something else http://sites.google.com/feeds/content/site/gdatatestsite/abc123 2009-06-15T18:40:22.407Z <content type="xhtml"> <div xmlns="http://www.w3.org/1999/xhtml">first comment</div> </content> <link rel="http://schemas.google.com/sites/2008#parent" type="application/atom+xml" href="http://sites.google.com/feeds/content/site/gdatatestsite/abc123"/> <link rel="self" type="application/atom+xml" href="http://sites.google.com/feeds/content/site/gdatatestsite/abc123"/> <link rel="edit" type="application/atom+xml" href="http://sites.google.com/feeds/content/site/gdatatestsite/abc123"/> <link rel="http://schemas.google.com/sites/2008#revision" type="application/atom+xml" href="http:///sites.google.com/feeds/content/site/gdatatestsite/abc123"/> <author> <name>Test User</name> <email>test@gmail.com</email> </author> <thr:in-reply-to xmlns:thr="http://purl.org/syndication/thread/1.0" href="http://sites.google.com/site/gdatatestsite/annoucment/testpost" ref="http://sites.google.com/feeds/content/site/gdatatestsite/abc123" source="http://sites.google.com/feeds/content/site/gdatatestsite" type="text/html"/> </entry> <entry> <id>http://sites.google.com/feeds/content/site/gdatatestsite/abc123</id> <updated>2009-06-15T18:40:16.388Z</updated> <category scheme="http://schemas.google.com/g/2005#kind" term="http://schemas.google.com/sites/2008#announcement"/> <title type="text">TestPost
    content goes here
    Test User test@gmail.com
    http://sites.google.com/feeds/content/site/gdatatestsite/abc123 2009-06-12T23:37:59.417Z Home
    Some Content goes here


    Test User test@gmail.com
    http://sites.google.com/feeds/content/site/gdatatestsite/2639323850129333500 2009-06-12T23:32:09.191Z annoucment
    Test User test@gmail.com
    ''' SITES_ACTIVITY_FEED = ''' http://sites.google.com/feeds/activity/site/siteName 2009-08-19T05:46:01.503Z Activity Google Sites 1 http://sites.google.com/feeds/activity/site/siteName/197441951793148343 2009-08-17T00:08:19.387Z NewWebpage3
    User deleted NewWebpage3
    User user@gmail.com
    http://sites.google.com/feeds/activity/site/siteName/7299542210274956360 2009-08-17T00:08:03.711Z NewWebpage3
    User edited NewWebpage3
    User user@gmail.com
    ''' SITES_REVISION_FEED = ''' http://sites.google.com/feeds/revision/site/siteName/2947510322163358574 2009-08-19T06:20:18.151Z Revisions Google Sites 1 http://sites.google.com/feeds/revision/site/siteName/2947510322163358574/1 2009-08-19T04:33:14.856Z <content type="xhtml"> <div xmlns="http://www.w3.org/1999/xhtml"> <table cellspacing="0" class="sites-layout-name-one-column sites-layout-hbox"> <tbody> <tr> <td class="sites-layout-tile sites-tile-name-content-1">testcomment</td> </tr> </tbody> </table> </div> </content> <link rel="http://schemas.google.com/sites/2008#parent" type="application/atom+xml" href="http://sites.google.com/feeds/content/site/siteName/54395424125706119"/> <link rel="alternate" type="text" href="http://sites.google.com/site/system/app/pages/admin/compare?wuid=wuid%3Agx%3A28e7a9057c581b6e&rev1=1"/> <link rel="self" type="application/atom+xml" href="http://sites.google.com/feeds/revision/site/siteName/2947510322163358574/1"/> <author> <name>User</name> <email>user@gmail.com</email> </author> <thr:in-reply-to href="http://sites.google.com/site/siteName/code/js" ref="http://sites.google.com/feeds/content/site/siteName/54395424125706119" source="http://sites.google.com/feeds/content/google.com/siteName" type="text/html;charset=UTF-8"/> <sites:revision>1</sites:revision> </entry> </feed>''' SITES_SITE_FEED = ''' <feed xmlns="http://www.w3.org/2005/Atom" xmlns:openSearch="http://a9.com/-/spec/opensearch/1.1/" xmlns:gAcl="http://schemas.google.com/acl/2007" xmlns:sites="http://schemas.google.com/sites/2008" xmlns:gs="http://schemas.google.com/spreadsheets/2006" xmlns:dc="http://purl.org/dc/terms" xmlns:batch="http://schemas.google.com/gdata/batch" xmlns:gd="http://schemas.google.com/g/2005" xmlns:thr="http://purl.org/syndication/thread/1.0"> <id>https://sites.google.com/feeds/site/example.com</id> <updated>2009-12-09T01:05:54.631Z</updated> <title>Site Google Sites 1 https://sites.google.com/feeds/site/example.com/new-test-site 2009-12-02T22:55:31.040Z 2009-12-02T22:55:31.040Z New Test Site A new site to hold memories new-test-site iceberg https://sites.google.com/feeds/site/example.com/newautosite2 2009-12-05T00:28:01.077Z 2009-12-05T00:28:01.077Z newAutoSite3 A new site to hold memories2 newautosite2 default ''' SITES_ACL_FEED = ''' https://sites.google.comsites.google.com/feeds/acl/site/example.com/new-test-site 2009-12-09T01:24:59.080Z Acl Google Sites 1 https://sites.google.com/feeds/acl/site/google.com/new-test-site/user%3Auser%40example.com 2009-12-09T01:24:59.080Z 2009-12-09T01:24:59.080Z ''' ANALYTICS_ACCOUNT_FEED_old = ''' http://www.google.com/analytics/feeds/accounts/abc@test.com 2009-06-25T03:55:22.000-07:00 Profile list for abc@test.com Google Analytics Google Analytics 12 1 12 http://www.google.com/analytics/feeds/accounts/ga:1174 2009-06-25T03:55:22.000-07:00 www.googlestore.com ga:1174 ''' ANALYTICS_ACCOUNT_FEED = ''' http://www.google.com/analytics/feeds/accounts/api.nickm@google.com 2009-10-14T09:14:25.000-07:00 Profile list for abc@test.com Google Analytics Google Analytics 37 1 37 ga:operatingSystem==iPhone http://www.google.com/analytics/feeds/accounts/ga:1174 2009-10-14T09:14:25.000-07:00 www.googlestore.com ga:1174 ''' ANALYTICS_DATA_FEED = ''' http://www.google.com/analytics/feeds/data?ids=ga:1174&dimensions=ga:medium,ga:source&metrics=ga:bounces,ga:visits&filters=ga:medium%3D%3Dreferral&start-date=2008-10-01&end-date=2008-10-31 2008-10-31T16:59:59.999-07:00 Google Analytics Data for Profile 1174 Google Analytics Google Analytics 6451 1 2 2008-10-01 2008-10-31 ga:operatingSystem==iPhone true ga:1174 www.googlestore.com http://www.google.com/analytics/feeds/data?ids=ga:1174&ga:medium=referral&ga:source=blogger.com&filters=ga:medium%3D%3Dreferral&start-date=2008-10-01&end-date=2008-10-31 2008-10-30T17:00:00.001-07:00 ga:source=blogger.com | ga:medium=referral ''' ANALYTICS_MGMT_PROFILE_FEED = ''' https://www.google.com/analytics/feeds/datasources/ga/accounts/~all/webproperties/~all/profiles 2010-06-14T22:18:48.676Z Google Analytics Profiles for superman@gmail.com Google Analytics Google Analytics 1 1 1000 https://www.google.com/analytics/feeds/datasources/ga/accounts/30481/webproperties/UA-30481-1/profiles/1174 2010-06-09T05:58:15.436-07:00 Google Analytics Profile www.googlestore.com ''' ANALYTICS_MGMT_GOAL_FEED = ''' https://www.google.com/analytics/feeds/datasources/ga/accounts/~all/webproperties/~all/profiles/~all/goals 2010-06-14T22:21:18.485Z Google Analytics Goals for superman@gmail.com Google Analytics Google Analytics 3 1 1000 https://www.google.com/analytics/feeds/datasources/ga/accounts/30481/webproperties/UA-30481-1/profiles/1174/goals/1 2010-02-07T13:12:43.377-08:00 Google Analytics Goal 1 https://www.google.com/analytics/feeds/datasources/ga/accounts/30481/webproperties/UA-30481-1/profiles/1174/goals/2 2010-02-07T13:12:43.376-08:00 Google Analytics Goal 2 ''' ANALYTICS_MGMT_ADV_SEGMENT_FEED = ''' https://www.google.com/analytics/feeds/datasources/ga/segments 2010-06-14T22:22:02.728Z Google Analytics Advanced Segments for superman@gmail.com Google Analytics Google Analytics 2 1 1000 https://www.google.com/analytics/feeds/datasources/ga/segments/gaid::0 2009-10-26T13:00:44.915-07:00 Google Analytics Advanced Segment Sources Form Google ga:source=~^\Qgoogle\E ''' MULTIDOMAIN_USER_ENTRY = """ """ MULTIDOMAIN_USER_FEED = """ https://apps-apis.google.com/a/feeds/user/2.0/example.com 2010-01-26T23:38:13.215Z 1 https://apps-apis.google.com/a/feeds/user/2.0/example.com/admin%40example.com 2010-01-26T23:38:13.210Z https://apps-apis.google.com/a/feeds/user/2.0/example.com/liz%40example.com 2010-01-26T23:38:13.210Z """ MULTIDOMAIN_USER_RENAME_REQUEST = """ """ MULTIDOMAIN_ALIAS_ENTRY = """ https://apps-apis.google.com/a/feeds/alias/2.0/gethelp_example.com/helpdesk%40gethelp_example.com 2008-10-17T15:02:45.646Z """ MULTIDOMAIN_ALIAS_FEED = """ https://apps-apis.google.com/a/feeds/alias/2.0/gethelp_example.com 2010-01-26T23:38:13.215Z 1 https://apps-apis.google.com/a/feeds/alias/2.0/gethelp_example.com/helpdesk%40gethelp_example.com 2010-01-26T23:38:13.210Z https://apps-apis.google.com/a/feeds/alias/2.0/gethelp_example.com/support%40gethelp_example.com 2010-01-26T23:38:13.210Z """ USER_ENTRY1 = """ http://apps-apis.google.com/a/feeds/srkapps.com/user/2.0/abcd12310 1970-01-01T00:00:00.000Z abcd12310 """ USER_FEED1 = """ https://apps-apis.google.com/a/feeds/srkapps.com/user/2.0 1 Users 1970-01-01T00:00:00.000Z https://apps-apis.google.com/a/feeds/srkapps.com/user/2.0/user8306 1970-01-01T00:00:00.000Z user8306 https://apps-apis.google.com/a/feeds/srkapps.com/user/2.0/user8307 1970-01-01T00:00:00.000Z user8307 """ NICKNAME_ENTRY = """ https://apps-apis.google.com/a/feeds/srkapps.net/nickname/2.0/nehag 1970-01-01T00:00:00.000Z nehag """ NICKNAME_FEED = """ https://apps-apis.google.com/a/feeds/srkapps.net/nickname/2.0 1970-01-01T00:00:00.000Z Nicknames 1 https://apps-apis.google.com/a/feeds/srkapps.net/nickname/2.0/nehag 1970-01-01T00:00:00.000Z nehag https://apps-apis.google.com/a/feeds/srkapps.net/nickname/2.0/richag 1970-01-01T00:00:00.000Z richag """ GROUP_ENTRY = """ http://apps-apis.google.com/a/feeds/group/2.0/srkapps.com/trial%40srkapps.com 2011-11-10T16:54:56.784Z """ GROUP_FEED= """ http://apps-apis.google.com/a/feeds/group/2.0/srkapps.com 2011-11-10T16:56:03.830Z 1 http://apps-apis.google.com/a/feeds/group/2.0/srkapps.com/firstgroup%40srkapps.com 2011-11-10T16:56:03.830Z http://apps-apis.google.com/a/feeds/group/2.0/srkapps.com/trial%40srkapps.com 2011-11-10T16:56:03.830Z """ GROUP_MEMBER_ENTRY = """ http://apps-apis.google.com/a/feeds/group/2.0/srkapps.com/trial/member/abcd12310%40srkapps.com 2011-11-10T16:58:40.804Z """ GROUP_MEMBER_FEED = """ http://apps-apis.google.com/a/feeds/group/2.0/srkapps.com/trial/member 2011-11-10T16:57:15.574Z 1 http://apps-apis.google.com/a/feeds/group/2.0/srkapps.com/trial/member/abcd12310%40srkapps.com 2011-11-10T16:57:15.574Z http://apps-apis.google.com/a/feeds/group/2.0/srkapps.com/trial/member/neha.technocrat%40srkapps.com 2011-11-10T16:57:15.574Z """ ORGANIZATION_UNIT_CUSTOMER_ID_ENTRY = """ https://apps-apis.google.com/a/feeds/customer/2.0/C123A456B 2011-11-21T13:17:02.274Z """ ORGANIZATION_UNIT_ORGUNIT_ENTRY = """ https://apps-apis.google.com/a/feeds/orgunit/2.0/C123A456B/Test+Organization 2011-11-21T13:32:12.334Z """ ORGANIZATION_UNIT_ORGUNIT_FEED = """ https://apps-apis.google.com/a/feeds/orgunit/2.0/C123A456B 2011-11-21T13:47:12.551Z 1 https://apps-apis.google.com/a/feeds/orgunit/2.0/C123A456B/testOrgUnit92 2011-11-21T13:42:45.349Z https://apps-apis.google.com/a/feeds/orgunit/2.0/C123A456B/testOrgUnit93 2011-11-21T13:42:45.349Z """ ORGANIZATION_UNIT_ORGUSER_ENTRY = """ https://apps-apis.google.com/a/feeds/orguser/2.0/C123A456B/admin%40example.com 2011-11-21T14:05:17.734Z """ ORGANIZATION_UNIT_ORGUSER_FEED = """ https://apps-apis.google.com/a/feeds/orguser/2.0/C123A456B 2011-11-21T14:10:48.206Z 1 https://apps-apis.google.com/a/feeds/orguser/2.0/C123A456B/user720430%40example.com 2011-11-21T14:09:16.600Z https://apps-apis.google.com/a/feeds/orguser/2.0/C123A456B/user832648%40example.com 2011-11-21T14:09:16.600Z """ gdata-2.0.18/src/gdata/spreadsheets/0000750036466300116100000000000012156625015017273 5ustar afshareng00000000000000gdata-2.0.18/src/gdata/spreadsheets/data.py0000640036466300116100000002626712156622363020577 0ustar afshareng00000000000000#!/usr/bin/env python # # Copyright (C) 2009 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. # This module is used for version 2 of the Google Data APIs. """Provides classes and constants for the XML in the Google Spreadsheets API. Documentation for the raw XML which these classes represent can be found here: http://code.google.com/apis/spreadsheets/docs/3.0/reference.html#Elements """ __author__ = 'j.s@google.com (Jeff Scudder)' import atom.core import gdata.data GS_TEMPLATE = '{http://schemas.google.com/spreadsheets/2006}%s' GSX_NAMESPACE = 'http://schemas.google.com/spreadsheets/2006/extended' INSERT_MODE = 'insert' OVERWRITE_MODE = 'overwrite' WORKSHEETS_REL = 'http://schemas.google.com/spreadsheets/2006#worksheetsfeed' BATCH_POST_ID_TEMPLATE = ('https://spreadsheets.google.com/feeds/cells' '/%s/%s/private/full') BATCH_ENTRY_ID_TEMPLATE = '%s/R%sC%s' BATCH_EDIT_LINK_TEMPLATE = '%s/batch' class Error(Exception): pass class FieldMissing(Exception): pass class HeaderNotSet(Error): """The desired column header had no value for the row in the list feed.""" class Cell(atom.core.XmlElement): """The gs:cell element. A cell in the worksheet. The element can appear only as a child of . """ _qname = GS_TEMPLATE % 'cell' col = 'col' input_value = 'inputValue' numeric_value = 'numericValue' row = 'row' class ColCount(atom.core.XmlElement): """The gs:colCount element. Indicates the number of columns in the worksheet, including columns that contain only empty cells. The element can appear as a child of or """ _qname = GS_TEMPLATE % 'colCount' class Field(atom.core.XmlElement): """The gs:field element. A field single cell within a record. Contained in an . """ _qname = GS_TEMPLATE % 'field' index = 'index' name = 'name' class Column(Field): """The gs:column element.""" _qname = GS_TEMPLATE % 'column' class Data(atom.core.XmlElement): """The gs:data element. A data region of a table. Contained in an element. """ _qname = GS_TEMPLATE % 'data' column = [Column] insertion_mode = 'insertionMode' num_rows = 'numRows' start_row = 'startRow' class Header(atom.core.XmlElement): """The gs:header element. Indicates which row is the header row. Contained in an . """ _qname = GS_TEMPLATE % 'header' row = 'row' class RowCount(atom.core.XmlElement): """The gs:rowCount element. Indicates the number of total rows in the worksheet, including rows that contain only empty cells. The element can appear as a child of or . """ _qname = GS_TEMPLATE % 'rowCount' class Worksheet(atom.core.XmlElement): """The gs:worksheet element. The worksheet where the table lives.Contained in an . """ _qname = GS_TEMPLATE % 'worksheet' name = 'name' class Spreadsheet(gdata.data.GDEntry): """An Atom entry which represents a Google Spreadsheet.""" def find_worksheets_feed(self): return self.find_url(WORKSHEETS_REL) FindWorksheetsFeed = find_worksheets_feed def get_spreadsheet_key(self): """Extracts the spreadsheet key unique to this spreadsheet.""" return self.get_id().split('/')[-1] GetSpreadsheetKey = get_spreadsheet_key class SpreadsheetsFeed(gdata.data.GDFeed): """An Atom feed listing a user's Google Spreadsheets.""" entry = [Spreadsheet] class WorksheetEntry(gdata.data.GDEntry): """An Atom entry representing a single worksheet in a spreadsheet.""" row_count = RowCount col_count = ColCount def get_worksheet_id(self): """The worksheet ID identifies this worksheet in its spreadsheet.""" return self.get_id().split('/')[-1] GetWorksheetId = get_worksheet_id class WorksheetsFeed(gdata.data.GDFeed): """A feed containing the worksheets in a single spreadsheet.""" entry = [WorksheetEntry] class Table(gdata.data.GDEntry): """An Atom entry that represents a subsection of a worksheet. A table allows you to treat part or all of a worksheet somewhat like a table in a database that is, as a set of structured data items. Tables don't exist until you explicitly create them before you can use a table feed, you have to explicitly define where the table data comes from. """ data = Data header = Header worksheet = Worksheet def get_table_id(self): if self.id.text: return self.id.text.split('/')[-1] return None GetTableId = get_table_id class TablesFeed(gdata.data.GDFeed): """An Atom feed containing the tables defined within a worksheet.""" entry = [Table] class Record(gdata.data.GDEntry): """An Atom entry representing a single record in a table. Note that the order of items in each record is the same as the order of columns in the table definition, which may not match the order of columns in the GUI. """ field = [Field] def value_for_index(self, column_index): for field in self.field: if field.index == column_index: return field.text raise FieldMissing('There is no field for %s' % column_index) ValueForIndex = value_for_index def value_for_name(self, name): for field in self.field: if field.name == name: return field.text raise FieldMissing('There is no field for %s' % name) ValueForName = value_for_name def get_record_id(self): if self.id.text: return self.id.text.split('/')[-1] return None class RecordsFeed(gdata.data.GDFeed): """An Atom feed containing the individuals records in a table.""" entry = [Record] class ListRow(atom.core.XmlElement): """A gsx column value within a row. The local tag in the _qname is blank and must be set to the column name. For example, when adding to a ListEntry, do: col_value = ListRow(text='something') col_value._qname = col_value._qname % 'mycolumnname' """ _qname = '{http://schemas.google.com/spreadsheets/2006/extended}%s' class ListEntry(gdata.data.GDEntry): """An Atom entry representing a worksheet row in the list feed. The values for a particular column can be get and set using x.get_value('columnheader') and x.set_value('columnheader', 'value'). See also the explanation of column names in the ListFeed class. """ def get_value(self, column_name): """Returns the displayed text for the desired column in this row. The formula or input which generated the displayed value is not accessible through the list feed, to see the user's input, use the cells feed. If a column is not present in this spreadsheet, or there is no value for a column in this row, this method will return None. """ values = self.get_elements(column_name, GSX_NAMESPACE) if len(values) == 0: return None return values[0].text def set_value(self, column_name, value): """Changes the value of cell in this row under the desired column name. Warning: if the cell contained a formula, it will be wiped out by setting the value using the list feed since the list feed only works with displayed values. No client side checking is performed on the column_name, you need to ensure that the column_name is the local tag name in the gsx tag for the column. For example, the column_name will not contain special characters, spaces, uppercase letters, etc. """ # Try to find the column in this row to change an existing value. values = self.get_elements(column_name, GSX_NAMESPACE) if len(values) > 0: values[0].text = value else: # There is no value in this row for the desired column, so add a new # gsx:column_name element. new_value = ListRow(text=value) new_value._qname = new_value._qname % (column_name,) self._other_elements.append(new_value) def to_dict(self): """Converts this row to a mapping of column names to their values.""" result = {} values = self.get_elements(namespace=GSX_NAMESPACE) for item in values: result[item._get_tag()] = item.text return result def from_dict(self, values): """Sets values for this row from the dictionary. Old values which are already in the entry will not be removed unless they are overwritten with new values from the dict. """ for column, value in values.iteritems(): self.set_value(column, value) class ListsFeed(gdata.data.GDFeed): """An Atom feed in which each entry represents a row in a worksheet. The first row in the worksheet is used as the column names for the values in each row. If a header cell is empty, then a unique column ID is used for the gsx element name. Spaces in a column name are removed from the name of the corresponding gsx element. Caution: The columnNames are case-insensitive. For example, if you see a element in a feed, you can't know whether the column heading in the original worksheet was "e-mail" or "E-Mail". Note: If two or more columns have the same name, then subsequent columns of the same name have _n appended to the columnName. For example, if the first column name is "e-mail", followed by columns named "E-Mail" and "E-mail", then the columnNames will be gsx:e-mail, gsx:e-mail_2, and gsx:e-mail_3 respectively. """ entry = [ListEntry] class CellEntry(gdata.data.BatchEntry): """An Atom entry representing a single cell in a worksheet.""" cell = Cell class CellsFeed(gdata.data.BatchFeed): """An Atom feed contains one entry per cell in a worksheet. The cell feed supports batch operations, you can send multiple cell operations in one HTTP request. """ entry = [CellEntry] def add_set_cell(self, row, col, input_value): """Adds a request to change the contents of a cell to this batch request. Args: row: int, The row number for this cell. Numbering starts at 1. col: int, The column number for this cell. Starts at 1. input_value: str, The desired formula/content this cell should contain. """ self.add_update(CellEntry( id=atom.data.Id(text=BATCH_ENTRY_ID_TEMPLATE % ( self.id.text, row, col)), cell=Cell(col=str(col), row=str(row), input_value=input_value))) return self AddSetCell = add_set_cell def build_batch_cells_update(spreadsheet_key, worksheet_id): """Creates an empty cells feed for adding batch cell updates to. Call batch_set_cell on the resulting CellsFeed instance then send the batch request TODO: fill in Args: spreadsheet_key: The ID of the spreadsheet worksheet_id: """ feed_id_text = BATCH_POST_ID_TEMPLATE % (spreadsheet_key, worksheet_id) return CellsFeed( id=atom.data.Id(text=feed_id_text), link=[atom.data.Link( rel='edit', href=BATCH_EDIT_LINK_TEMPLATE % (feed_id_text,))]) BuildBatchCellsUpdate = build_batch_cells_update gdata-2.0.18/src/gdata/spreadsheets/__init__.py0000640036466300116100000000000012156622363021376 0ustar afshareng00000000000000gdata-2.0.18/src/gdata/spreadsheets/client.py0000640036466300116100000007146212156622363021141 0ustar afshareng00000000000000#!/usr/bin/env python # # Copyright (C) 2009 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. """Contains a client to communicate with the Google Spreadsheets servers. For documentation on the Spreadsheets API, see: http://code.google.com/apis/spreadsheets/ """ __author__ = 'j.s@google.com (Jeff Scudder)' import gdata.client import gdata.gauth import gdata.spreadsheets.data import atom.data import atom.http_core SPREADSHEETS_URL = ('https://spreadsheets.google.com/feeds/spreadsheets' '/private/full') WORKSHEETS_URL = ('https://spreadsheets.google.com/feeds/worksheets/' '%s/private/full') WORKSHEET_URL = ('https://spreadsheets.google.com/feeds/worksheets/' '%s/private/full/%s') TABLES_URL = 'https://spreadsheets.google.com/feeds/%s/tables' RECORDS_URL = 'https://spreadsheets.google.com/feeds/%s/records/%s' RECORD_URL = 'https://spreadsheets.google.com/feeds/%s/records/%s/%s' CELLS_URL = 'https://spreadsheets.google.com/feeds/cells/%s/%s/private/full' CELL_URL = ('https://spreadsheets.google.com/feeds/cells/%s/%s/private/full/' 'R%sC%s') LISTS_URL = 'https://spreadsheets.google.com/feeds/list/%s/%s/private/full' class SpreadsheetsClient(gdata.client.GDClient): api_version = '3' auth_service = 'wise' auth_scopes = gdata.gauth.AUTH_SCOPES['wise'] ssl = True def get_spreadsheets(self, auth_token=None, desired_class=gdata.spreadsheets.data.SpreadsheetsFeed, **kwargs): """Obtains a feed with the spreadsheets belonging to the current user. Args: auth_token: An object which sets the Authorization HTTP header in its modify_request method. Recommended classes include gdata.gauth.ClientLoginToken and gdata.gauth.AuthSubToken among others. Represents the current user. Defaults to None and if None, this method will look for a value in the auth_token member of SpreadsheetsClient. desired_class: class descended from atom.core.XmlElement to which a successful response should be converted. If there is no converter function specified (converter=None) then the desired_class will be used in calling the atom.core.parse function. If neither the desired_class nor the converter is specified, an HTTP reponse object will be returned. Defaults to gdata.spreadsheets.data.SpreadsheetsFeed. """ return self.get_feed(SPREADSHEETS_URL, auth_token=auth_token, desired_class=desired_class, **kwargs) GetSpreadsheets = get_spreadsheets def get_worksheets(self, spreadsheet_key, auth_token=None, desired_class=gdata.spreadsheets.data.WorksheetsFeed, **kwargs): """Finds the worksheets within a given spreadsheet. Args: spreadsheet_key: str, The unique ID of this containing spreadsheet. This can be the ID from the URL or as provided in a Spreadsheet entry. auth_token: An object which sets the Authorization HTTP header in its modify_request method. Recommended classes include gdata.gauth.ClientLoginToken and gdata.gauth.AuthSubToken among others. Represents the current user. Defaults to None and if None, this method will look for a value in the auth_token member of SpreadsheetsClient. desired_class: class descended from atom.core.XmlElement to which a successful response should be converted. If there is no converter function specified (converter=None) then the desired_class will be used in calling the atom.core.parse function. If neither the desired_class nor the converter is specified, an HTTP reponse object will be returned. Defaults to gdata.spreadsheets.data.WorksheetsFeed. """ return self.get_feed(WORKSHEETS_URL % spreadsheet_key, auth_token=auth_token, desired_class=desired_class, **kwargs) GetWorksheets = get_worksheets def add_worksheet(self, spreadsheet_key, title, rows, cols, auth_token=None, **kwargs): """Creates a new worksheet entry in the spreadsheet. Args: spreadsheet_key: str, The unique ID of this containing spreadsheet. This can be the ID from the URL or as provided in a Spreadsheet entry. title: str, The title to be used in for the worksheet. rows: str or int, The number of rows this worksheet should start with. cols: str or int, The number of columns this worksheet should start with. auth_token: An object which sets the Authorization HTTP header in its modify_request method. Recommended classes include gdata.gauth.ClientLoginToken and gdata.gauth.AuthSubToken among others. Represents the current user. Defaults to None and if None, this method will look for a value in the auth_token member of SpreadsheetsClient. """ new_worksheet = gdata.spreadsheets.data.WorksheetEntry( title=atom.data.Title(text=title), row_count=gdata.spreadsheets.data.RowCount(text=str(rows)), col_count=gdata.spreadsheets.data.ColCount(text=str(cols))) return self.post(new_worksheet, WORKSHEETS_URL % spreadsheet_key, auth_token=auth_token, **kwargs) AddWorksheet = add_worksheet def get_worksheet(self, spreadsheet_key, worksheet_id, desired_class=gdata.spreadsheets.data.WorksheetEntry, auth_token=None, **kwargs): """Retrieves a single worksheet. Args: spreadsheet_key: str, The unique ID of this containing spreadsheet. This can be the ID from the URL or as provided in a Spreadsheet entry. worksheet_id: str, The unique ID for the worksheet withing the desired spreadsheet. auth_token: An object which sets the Authorization HTTP header in its modify_request method. Recommended classes include gdata.gauth.ClientLoginToken and gdata.gauth.AuthSubToken among others. Represents the current user. Defaults to None and if None, this method will look for a value in the auth_token member of SpreadsheetsClient. desired_class: class descended from atom.core.XmlElement to which a successful response should be converted. If there is no converter function specified (converter=None) then the desired_class will be used in calling the atom.core.parse function. If neither the desired_class nor the converter is specified, an HTTP reponse object will be returned. Defaults to gdata.spreadsheets.data.WorksheetEntry. """ return self.get_entry(WORKSHEET_URL % (spreadsheet_key, worksheet_id,), auth_token=auth_token, desired_class=desired_class, **kwargs) GetWorksheet = get_worksheet def add_table(self, spreadsheet_key, title, summary, worksheet_name, header_row, num_rows, start_row, insertion_mode, column_headers, auth_token=None, **kwargs): """Creates a new table within the worksheet. Args: spreadsheet_key: str, The unique ID of this containing spreadsheet. This can be the ID from the URL or as provided in a Spreadsheet entry. title: str, The title for the new table within a worksheet. summary: str, A description of the table. worksheet_name: str The name of the worksheet in which this table should live. header_row: int or str, The number of the row in the worksheet which will contain the column names for the data in this table. num_rows: int or str, The number of adjacent rows in this table. start_row: int or str, The number of the row at which the data begins. insertion_mode: str column_headers: dict of strings, maps the column letters (A, B, C) to the desired name which will be viewable in the worksheet. auth_token: An object which sets the Authorization HTTP header in its modify_request method. Recommended classes include gdata.gauth.ClientLoginToken and gdata.gauth.AuthSubToken among others. Represents the current user. Defaults to None and if None, this method will look for a value in the auth_token member of SpreadsheetsClient. """ data = gdata.spreadsheets.data.Data( insertion_mode=insertion_mode, num_rows=str(num_rows), start_row=str(start_row)) for index, name in column_headers.iteritems(): data.column.append(gdata.spreadsheets.data.Column( index=index, name=name)) new_table = gdata.spreadsheets.data.Table( title=atom.data.Title(text=title), summary=atom.data.Summary(summary), worksheet=gdata.spreadsheets.data.Worksheet(name=worksheet_name), header=gdata.spreadsheets.data.Header(row=str(header_row)), data=data) return self.post(new_table, TABLES_URL % spreadsheet_key, auth_token=auth_token, **kwargs) AddTable = add_table def get_tables(self, spreadsheet_key, desired_class=gdata.spreadsheets.data.TablesFeed, auth_token=None, **kwargs): """Retrieves a feed listing the tables in this spreadsheet. Args: spreadsheet_key: str, The unique ID of this containing spreadsheet. This can be the ID from the URL or as provided in a Spreadsheet entry. desired_class: class descended from atom.core.XmlElement to which a successful response should be converted. If there is no converter function specified (converter=None) then the desired_class will be used in calling the atom.core.parse function. If neither the desired_class nor the converter is specified, an HTTP reponse object will be returned. Defaults to gdata.spreadsheets.data.TablesFeed. auth_token: An object which sets the Authorization HTTP header in its modify_request method. Recommended classes include gdata.gauth.ClientLoginToken and gdata.gauth.AuthSubToken among others. Represents the current user. Defaults to None and if None, this method will look for a value in the auth_token member of SpreadsheetsClient. """ return self.get_feed(TABLES_URL % spreadsheet_key, desired_class=desired_class, auth_token=auth_token, **kwargs) GetTables = get_tables def add_record(self, spreadsheet_key, table_id, fields, title=None, auth_token=None, **kwargs): """Adds a new row to the table. Args: spreadsheet_key: str, The unique ID of this containing spreadsheet. This can be the ID from the URL or as provided in a Spreadsheet entry. table_id: str, The ID of the table within the worksheet which should receive this new record. The table ID can be found using the get_table_id method of a gdata.spreadsheets.data.Table. fields: dict of strings mapping column names to values. title: str, optional The title for this row. auth_token: An object which sets the Authorization HTTP header in its modify_request method. Recommended classes include gdata.gauth.ClientLoginToken and gdata.gauth.AuthSubToken among others. Represents the current user. Defaults to None and if None, this method will look for a value in the auth_token member of SpreadsheetsClient. """ new_record = gdata.spreadsheets.data.Record() if title is not None: new_record.title = atom.data.Title(text=title) for name, value in fields.iteritems(): new_record.field.append(gdata.spreadsheets.data.Field( name=name, text=value)) return self.post(new_record, RECORDS_URL % (spreadsheet_key, table_id), auth_token=auth_token, **kwargs) AddRecord = add_record def get_records(self, spreadsheet_key, table_id, desired_class=gdata.spreadsheets.data.RecordsFeed, auth_token=None, **kwargs): """Retrieves the records in a table. Args: spreadsheet_key: str, The unique ID of this containing spreadsheet. This can be the ID from the URL or as provided in a Spreadsheet entry. table_id: str, The ID of the table within the worksheet whose records we would like to fetch. The table ID can be found using the get_table_id method of a gdata.spreadsheets.data.Table. desired_class: class descended from atom.core.XmlElement to which a successful response should be converted. If there is no converter function specified (converter=None) then the desired_class will be used in calling the atom.core.parse function. If neither the desired_class nor the converter is specified, an HTTP reponse object will be returned. Defaults to gdata.spreadsheets.data.RecordsFeed. auth_token: An object which sets the Authorization HTTP header in its modify_request method. Recommended classes include gdata.gauth.ClientLoginToken and gdata.gauth.AuthSubToken among others. Represents the current user. Defaults to None and if None, this method will look for a value in the auth_token member of SpreadsheetsClient. """ return self.get_feed(RECORDS_URL % (spreadsheet_key, table_id), desired_class=desired_class, auth_token=auth_token, **kwargs) GetRecords = get_records def get_record(self, spreadsheet_key, table_id, record_id, desired_class=gdata.spreadsheets.data.Record, auth_token=None, **kwargs): """Retrieves a single record from the table. Args: spreadsheet_key: str, The unique ID of this containing spreadsheet. This can be the ID from the URL or as provided in a Spreadsheet entry. table_id: str, The ID of the table within the worksheet whose records we would like to fetch. The table ID can be found using the get_table_id method of a gdata.spreadsheets.data.Table. record_id: str, The ID of the record within this table which we want to fetch. You can find the record ID using get_record_id() on an instance of the gdata.spreadsheets.data.Record class. desired_class: class descended from atom.core.XmlElement to which a successful response should be converted. If there is no converter function specified (converter=None) then the desired_class will be used in calling the atom.core.parse function. If neither the desired_class nor the converter is specified, an HTTP reponse object will be returned. Defaults to gdata.spreadsheets.data.RecordsFeed. auth_token: An object which sets the Authorization HTTP header in its modify_request method. Recommended classes include gdata.gauth.ClientLoginToken and gdata.gauth.AuthSubToken among others. Represents the current user. Defaults to None and if None, this method will look for a value in the auth_token member of SpreadsheetsClient. """ return self.get_entry(RECORD_URL % (spreadsheet_key, table_id, record_id), desired_class=desired_class, auth_token=auth_token, **kwargs) GetRecord = get_record def get_cells(self, spreadsheet_key, worksheet_id, desired_class=gdata.spreadsheets.data.CellsFeed, auth_token=None, **kwargs): """Retrieves the cells which have values in this spreadsheet. Blank cells are not included. Args: spreadsheet_key: str, The unique ID of this containing spreadsheet. This can be the ID from the URL or as provided in a Spreadsheet entry. worksheet_id: str, The unique ID of the worksheet in this spreadsheet whose cells we want. This can be obtained using WorksheetEntry's get_worksheet_id method. desired_class: class descended from atom.core.XmlElement to which a successful response should be converted. If there is no converter function specified (converter=None) then the desired_class will be used in calling the atom.core.parse function. If neither the desired_class nor the converter is specified, an HTTP reponse object will be returned. Defaults to gdata.spreadsheets.data.CellsFeed. auth_token: An object which sets the Authorization HTTP header in its modify_request method. Recommended classes include gdata.gauth.ClientLoginToken and gdata.gauth.AuthSubToken among others. Represents the current user. Defaults to None and if None, this method will look for a value in the auth_token member of SpreadsheetsClient. """ return self.get_feed(CELLS_URL % (spreadsheet_key, worksheet_id), auth_token=auth_token, desired_class=desired_class, **kwargs) GetCells = get_cells def get_cell(self, spreadsheet_key, worksheet_id, row_num, col_num, desired_class=gdata.spreadsheets.data.CellEntry, auth_token=None, **kwargs): """Retrieves a single cell from the worksheet. Indexes are 1 based so the first cell in the worksheet is 1, 1. Args: spreadsheet_key: str, The unique ID of this containing spreadsheet. This can be the ID from the URL or as provided in a Spreadsheet entry. worksheet_id: str, The unique ID of the worksheet in this spreadsheet whose cells we want. This can be obtained using WorksheetEntry's get_worksheet_id method. row_num: int, The row of the cell that we want. Numbering starts with 1. col_num: int, The column of the cell we want. Numbering starts with 1. desired_class: class descended from atom.core.XmlElement to which a successful response should be converted. If there is no converter function specified (converter=None) then the desired_class will be used in calling the atom.core.parse function. If neither the desired_class nor the converter is specified, an HTTP reponse object will be returned. Defaults to gdata.spreadsheets.data.CellEntry. auth_token: An object which sets the Authorization HTTP header in its modify_request method. Recommended classes include gdata.gauth.ClientLoginToken and gdata.gauth.AuthSubToken among others. Represents the current user. Defaults to None and if None, this method will look for a value in the auth_token member of SpreadsheetsClient. """ return self.get_entry( CELL_URL % (spreadsheet_key, worksheet_id, row_num, col_num), auth_token=auth_token, desired_class=desired_class, **kwargs) GetCell = get_cell def get_list_feed(self, spreadsheet_key, worksheet_id, desired_class=gdata.spreadsheets.data.ListsFeed, auth_token=None, **kwargs): """Retrieves the value rows from the worksheet's list feed. The list feed is a view of the spreadsheet in which the first row is used for column names and subsequent rows up to the first blank line are records. Args: spreadsheet_key: str, The unique ID of this containing spreadsheet. This can be the ID from the URL or as provided in a Spreadsheet entry. worksheet_id: str, The unique ID of the worksheet in this spreadsheet whose cells we want. This can be obtained using WorksheetEntry's get_worksheet_id method. desired_class: class descended from atom.core.XmlElement to which a successful response should be converted. If there is no converter function specified (converter=None) then the desired_class will be used in calling the atom.core.parse function. If neither the desired_class nor the converter is specified, an HTTP reponse object will be returned. Defaults to gdata.spreadsheets.data.ListsFeed. auth_token: An object which sets the Authorization HTTP header in its modify_request method. Recommended classes include gdata.gauth.ClientLoginToken and gdata.gauth.AuthSubToken among others. Represents the current user. Defaults to None and if None, this method will look for a value in the auth_token member of SpreadsheetsClient. """ return self.get_feed(LISTS_URL % (spreadsheet_key, worksheet_id), auth_token=auth_token, desired_class=desired_class, **kwargs) GetListFeed = get_list_feed def add_list_entry(self, list_entry, spreadsheet_key, worksheet_id, auth_token=None, **kwargs): """Adds a new row to the worksheet's list feed. Args: list_entry: gdata.spreadsheets.data.ListsEntry An entry which contains the values which should be set for the columns in this record. spreadsheet_key: str, The unique ID of this containing spreadsheet. This can be the ID from the URL or as provided in a Spreadsheet entry. worksheet_id: str, The unique ID of the worksheet in this spreadsheet whose cells we want. This can be obtained using WorksheetEntry's get_worksheet_id method. auth_token: An object which sets the Authorization HTTP header in its modify_request method. Recommended classes include gdata.gauth.ClientLoginToken and gdata.gauth.AuthSubToken among others. Represents the current user. Defaults to None and if None, this method will look for a value in the auth_token member of SpreadsheetsClient. """ return self.post(list_entry, LISTS_URL % (spreadsheet_key, worksheet_id), auth_token=auth_token, **kwargs) AddListEntry = add_list_entry class SpreadsheetQuery(gdata.client.Query): def __init__(self, title=None, title_exact=None, **kwargs): """Adds Spreadsheets feed query parameters to a request. Args: title: str Specifies the search terms for the title of a document. This parameter used without title-exact will only submit partial queries, not exact queries. title_exact: str Specifies whether the title query should be taken as an exact string. Meaningless without title. Possible values are 'true' and 'false'. """ gdata.client.Query.__init__(self, **kwargs) self.title = title self.title_exact = title_exact def modify_request(self, http_request): gdata.client._add_query_param('title', self.title, http_request) gdata.client._add_query_param('title-exact', self.title_exact, http_request) gdata.client.Query.modify_request(self, http_request) ModifyRequest = modify_request class WorksheetQuery(SpreadsheetQuery): pass class ListQuery(gdata.client.Query): def __init__(self, order_by=None, reverse=None, sq=None, **kwargs): """Adds List-feed specific query parameters to a request. Args: order_by: str Specifies what column to use in ordering the entries in the feed. By position (the default): 'position' returns rows in the order in which they appear in the GUI. Row 1, then row 2, then row 3, and so on. By column: 'column:columnName' sorts rows in ascending order based on the values in the column with the given columnName, where columnName is the value in the header row for that column. reverse: str Specifies whether to sort in descending or ascending order. Reverses default sort order: 'true' results in a descending sort; 'false' (the default) results in an ascending sort. sq: str Structured query on the full text in the worksheet. [columnName][binaryOperator][value] Supported binaryOperators are: - (), for overriding order of operations - = or ==, for strict equality - <> or !=, for strict inequality - and or &&, for boolean and - or or ||, for boolean or """ gdata.client.Query.__init__(self, **kwargs) self.order_by = order_by self.reverse = reverse self.sq = sq def modify_request(self, http_request): gdata.client._add_query_param('orderby', self.order_by, http_request) gdata.client._add_query_param('reverse', self.reverse, http_request) gdata.client._add_query_param('sq', self.sq, http_request) gdata.client.Query.modify_request(self, http_request) ModifyRequest = modify_request class TableQuery(ListQuery): pass class CellQuery(gdata.client.Query): def __init__(self, min_row=None, max_row=None, min_col=None, max_col=None, range=None, return_empty=None, **kwargs): """Adds Cells-feed specific query parameters to a request. Args: min_row: str or int Positional number of minimum row returned in query. max_row: str or int Positional number of maximum row returned in query. min_col: str or int Positional number of minimum column returned in query. max_col: str or int Positional number of maximum column returned in query. range: str A single cell or a range of cells. Use standard spreadsheet cell-range notations, using a colon to separate start and end of range. Examples: - 'A1' and 'R1C1' both specify only cell A1. - 'D1:F3' and 'R1C4:R3C6' both specify the rectangle of cells with corners at D1 and F3. return_empty: str If 'true' then empty cells will be returned in the feed. If omitted, the default is 'false'. """ gdata.client.Query.__init__(self, **kwargs) self.min_row = min_row self.max_row = max_row self.min_col = min_col self.max_col = max_col self.range = range self.return_empty = return_empty def modify_request(self, http_request): gdata.client._add_query_param('min-row', self.min_row, http_request) gdata.client._add_query_param('max-row', self.max_row, http_request) gdata.client._add_query_param('min-col', self.min_col, http_request) gdata.client._add_query_param('max-col', self.max_col, http_request) gdata.client._add_query_param('range', self.range, http_request) gdata.client._add_query_param('return-empty', self.return_empty, http_request) gdata.client.Query.modify_request(self, http_request) ModifyRequest = modify_request gdata-2.0.18/src/gdata/spreadsheet/0000750036466300116100000000000012156625015017110 5ustar afshareng00000000000000gdata-2.0.18/src/gdata/spreadsheet/__init__.py0000750036466300116100000004303312156622363021232 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright (C) 2007 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. """Contains extensions to Atom objects used with Google Spreadsheets. """ __author__ = 'api.laurabeth@gmail.com (Laura Beth Lincoln)' try: from xml.etree import cElementTree as ElementTree except ImportError: try: import cElementTree as ElementTree except ImportError: try: from xml.etree import ElementTree except ImportError: from elementtree import ElementTree import atom import gdata import re import string # XML namespaces which are often used in Google Spreadsheets entities. GSPREADSHEETS_NAMESPACE = 'http://schemas.google.com/spreadsheets/2006' GSPREADSHEETS_TEMPLATE = '{http://schemas.google.com/spreadsheets/2006}%s' GSPREADSHEETS_EXTENDED_NAMESPACE = ('http://schemas.google.com/spreadsheets' '/2006/extended') GSPREADSHEETS_EXTENDED_TEMPLATE = ('{http://schemas.google.com/spreadsheets' '/2006/extended}%s') class ColCount(atom.AtomBase): """The Google Spreadsheets colCount element """ _tag = 'colCount' _namespace = GSPREADSHEETS_NAMESPACE _children = atom.AtomBase._children.copy() _attributes = atom.AtomBase._attributes.copy() def __init__(self, text=None, extension_elements=None, extension_attributes=None): self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} def ColCountFromString(xml_string): return atom.CreateClassFromXMLString(ColCount, xml_string) class RowCount(atom.AtomBase): """The Google Spreadsheets rowCount element """ _tag = 'rowCount' _namespace = GSPREADSHEETS_NAMESPACE _children = atom.AtomBase._children.copy() _attributes = atom.AtomBase._attributes.copy() def __init__(self, text=None, extension_elements=None, extension_attributes=None): self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} def RowCountFromString(xml_string): return atom.CreateClassFromXMLString(RowCount, xml_string) class Cell(atom.AtomBase): """The Google Spreadsheets cell element """ _tag = 'cell' _namespace = GSPREADSHEETS_NAMESPACE _children = atom.AtomBase._children.copy() _attributes = atom.AtomBase._attributes.copy() _attributes['row'] = 'row' _attributes['col'] = 'col' _attributes['inputValue'] = 'inputValue' _attributes['numericValue'] = 'numericValue' def __init__(self, text=None, row=None, col=None, inputValue=None, numericValue=None, extension_elements=None, extension_attributes=None): self.text = text self.row = row self.col = col self.inputValue = inputValue self.numericValue = numericValue self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} def CellFromString(xml_string): return atom.CreateClassFromXMLString(Cell, xml_string) class Custom(atom.AtomBase): """The Google Spreadsheets custom element""" _namespace = GSPREADSHEETS_EXTENDED_NAMESPACE _children = atom.AtomBase._children.copy() _attributes = atom.AtomBase._attributes.copy() def __init__(self, column=None, text=None, extension_elements=None, extension_attributes=None): self.column = column # The name of the column self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} def _BecomeChildElement(self, tree): new_child = ElementTree.Element('') tree.append(new_child) new_child.tag = '{%s}%s' % (self.__class__._namespace, self.column) self._AddMembersToElementTree(new_child) def _ToElementTree(self): new_tree = ElementTree.Element('{%s}%s' % (self.__class__._namespace, self.column)) self._AddMembersToElementTree(new_tree) return new_tree def _HarvestElementTree(self, tree): namespace_uri, local_tag = string.split(tree.tag[1:], "}", 1) self.column = local_tag # Fill in the instance members from the contents of the XML tree. for child in tree: self._ConvertElementTreeToMember(child) for attribute, value in tree.attrib.iteritems(): self._ConvertElementAttributeToMember(attribute, value) self.text = tree.text def CustomFromString(xml_string): element_tree = ElementTree.fromstring(xml_string) return _CustomFromElementTree(element_tree) def _CustomFromElementTree(element_tree): namespace_uri, local_tag = string.split(element_tree.tag[1:], "}", 1) if namespace_uri == GSPREADSHEETS_EXTENDED_NAMESPACE: new_custom = Custom() new_custom._HarvestElementTree(element_tree) new_custom.column = local_tag return new_custom return None class SpreadsheetsSpreadsheet(gdata.GDataEntry): """A Google Spreadsheets flavor of a Spreadsheet Atom Entry """ _tag = 'entry' _namespace = atom.ATOM_NAMESPACE _children = gdata.GDataEntry._children.copy() _attributes = gdata.GDataEntry._attributes.copy() def __init__(self, author=None, category=None, content=None, contributor=None, atom_id=None, link=None, published=None, rights=None, source=None, summary=None, title=None, control=None, updated=None, text=None, extension_elements=None, extension_attributes=None): self.author = author or [] self.category = category or [] self.content = content self.contributor = contributor or [] self.id = atom_id self.link = link or [] self.published = published self.rights = rights self.source = source self.summary = summary self.control = control self.title = title self.updated = updated self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} def SpreadsheetsSpreadsheetFromString(xml_string): return atom.CreateClassFromXMLString(SpreadsheetsSpreadsheet, xml_string) class SpreadsheetsWorksheet(gdata.GDataEntry): """A Google Spreadsheets flavor of a Worksheet Atom Entry """ _tag = 'entry' _namespace = atom.ATOM_NAMESPACE _children = gdata.GDataEntry._children.copy() _attributes = gdata.GDataEntry._attributes.copy() _children['{%s}rowCount' % GSPREADSHEETS_NAMESPACE] = ('row_count', RowCount) _children['{%s}colCount' % GSPREADSHEETS_NAMESPACE] = ('col_count', ColCount) def __init__(self, author=None, category=None, content=None, contributor=None, atom_id=None, link=None, published=None, rights=None, source=None, summary=None, title=None, control=None, updated=None, row_count=None, col_count=None, text=None, extension_elements=None, extension_attributes=None): self.author = author or [] self.category = category or [] self.content = content self.contributor = contributor or [] self.id = atom_id self.link = link or [] self.published = published self.rights = rights self.source = source self.summary = summary self.control = control self.title = title self.updated = updated self.row_count = row_count self.col_count = col_count self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} def SpreadsheetsWorksheetFromString(xml_string): return atom.CreateClassFromXMLString(SpreadsheetsWorksheet, xml_string) class SpreadsheetsCell(gdata.BatchEntry): """A Google Spreadsheets flavor of a Cell Atom Entry """ _tag = 'entry' _namespace = atom.ATOM_NAMESPACE _children = gdata.BatchEntry._children.copy() _attributes = gdata.BatchEntry._attributes.copy() _children['{%s}cell' % GSPREADSHEETS_NAMESPACE] = ('cell', Cell) def __init__(self, author=None, category=None, content=None, contributor=None, atom_id=None, link=None, published=None, rights=None, source=None, summary=None, title=None, control=None, updated=None, cell=None, batch_operation=None, batch_id=None, batch_status=None, text=None, extension_elements=None, extension_attributes=None): self.author = author or [] self.category = category or [] self.content = content self.contributor = contributor or [] self.id = atom_id self.link = link or [] self.published = published self.rights = rights self.source = source self.summary = summary self.control = control self.title = title self.batch_operation = batch_operation self.batch_id = batch_id self.batch_status = batch_status self.updated = updated self.cell = cell self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} def SpreadsheetsCellFromString(xml_string): return atom.CreateClassFromXMLString(SpreadsheetsCell, xml_string) class SpreadsheetsList(gdata.GDataEntry): """A Google Spreadsheets flavor of a List Atom Entry """ _tag = 'entry' _namespace = atom.ATOM_NAMESPACE _children = gdata.GDataEntry._children.copy() _attributes = gdata.GDataEntry._attributes.copy() def __init__(self, author=None, category=None, content=None, contributor=None, atom_id=None, link=None, published=None, rights=None, source=None, summary=None, title=None, control=None, updated=None, custom=None, text=None, extension_elements=None, extension_attributes=None): self.author = author or [] self.category = category or [] self.content = content self.contributor = contributor or [] self.id = atom_id self.link = link or [] self.published = published self.rights = rights self.source = source self.summary = summary self.control = control self.title = title self.updated = updated self.custom = custom or {} self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} # We need to overwrite _ConvertElementTreeToMember to add special logic to # convert custom attributes to members def _ConvertElementTreeToMember(self, child_tree): # Find the element's tag in this class's list of child members if self.__class__._children.has_key(child_tree.tag): member_name = self.__class__._children[child_tree.tag][0] member_class = self.__class__._children[child_tree.tag][1] # If the class member is supposed to contain a list, make sure the # matching member is set to a list, then append the new member # instance to the list. if isinstance(member_class, list): if getattr(self, member_name) is None: setattr(self, member_name, []) getattr(self, member_name).append(atom._CreateClassFromElementTree( member_class[0], child_tree)) else: setattr(self, member_name, atom._CreateClassFromElementTree(member_class, child_tree)) elif child_tree.tag.find('{%s}' % GSPREADSHEETS_EXTENDED_NAMESPACE) == 0: # If this is in the custom namespace, make add it to the custom dict. name = child_tree.tag[child_tree.tag.index('}')+1:] custom = _CustomFromElementTree(child_tree) if custom: self.custom[name] = custom else: atom.ExtensionContainer._ConvertElementTreeToMember(self, child_tree) # We need to overwtite _AddMembersToElementTree to add special logic to # convert custom members to XML nodes. def _AddMembersToElementTree(self, tree): # Convert the members of this class which are XML child nodes. # This uses the class's _children dictionary to find the members which # should become XML child nodes. member_node_names = [values[0] for tag, values in self.__class__._children.iteritems()] for member_name in member_node_names: member = getattr(self, member_name) if member is None: pass elif isinstance(member, list): for instance in member: instance._BecomeChildElement(tree) else: member._BecomeChildElement(tree) # Convert the members of this class which are XML attributes. for xml_attribute, member_name in self.__class__._attributes.iteritems(): member = getattr(self, member_name) if member is not None: tree.attrib[xml_attribute] = member # Convert all special custom item attributes to nodes for name, custom in self.custom.iteritems(): custom._BecomeChildElement(tree) # Lastly, call the ExtensionContainers's _AddMembersToElementTree to # convert any extension attributes. atom.ExtensionContainer._AddMembersToElementTree(self, tree) def SpreadsheetsListFromString(xml_string): return atom.CreateClassFromXMLString(SpreadsheetsList, xml_string) element_tree = ElementTree.fromstring(xml_string) return _SpreadsheetsListFromElementTree(element_tree) class SpreadsheetsSpreadsheetsFeed(gdata.GDataFeed): """A feed containing Google Spreadsheets Spreadsheets""" _tag = 'feed' _namespace = atom.ATOM_NAMESPACE _children = gdata.GDataFeed._children.copy() _attributes = gdata.GDataFeed._attributes.copy() _children['{%s}entry' % atom.ATOM_NAMESPACE] = ('entry', [SpreadsheetsSpreadsheet]) def SpreadsheetsSpreadsheetsFeedFromString(xml_string): return atom.CreateClassFromXMLString(SpreadsheetsSpreadsheetsFeed, xml_string) class SpreadsheetsWorksheetsFeed(gdata.GDataFeed): """A feed containing Google Spreadsheets Spreadsheets""" _tag = 'feed' _namespace = atom.ATOM_NAMESPACE _children = gdata.GDataFeed._children.copy() _attributes = gdata.GDataFeed._attributes.copy() _children['{%s}entry' % atom.ATOM_NAMESPACE] = ('entry', [SpreadsheetsWorksheet]) def SpreadsheetsWorksheetsFeedFromString(xml_string): return atom.CreateClassFromXMLString(SpreadsheetsWorksheetsFeed, xml_string) class SpreadsheetsCellsFeed(gdata.BatchFeed): """A feed containing Google Spreadsheets Cells""" _tag = 'feed' _namespace = atom.ATOM_NAMESPACE _children = gdata.BatchFeed._children.copy() _attributes = gdata.BatchFeed._attributes.copy() _children['{%s}entry' % atom.ATOM_NAMESPACE] = ('entry', [SpreadsheetsCell]) _children['{%s}rowCount' % GSPREADSHEETS_NAMESPACE] = ('row_count', RowCount) _children['{%s}colCount' % GSPREADSHEETS_NAMESPACE] = ('col_count', ColCount) def __init__(self, author=None, category=None, contributor=None, generator=None, icon=None, atom_id=None, link=None, logo=None, rights=None, subtitle=None, title=None, updated=None, entry=None, total_results=None, start_index=None, items_per_page=None, extension_elements=None, extension_attributes=None, text=None, row_count=None, col_count=None, interrupted=None): gdata.BatchFeed.__init__(self, author=author, category=category, contributor=contributor, generator=generator, icon=icon, atom_id=atom_id, link=link, logo=logo, rights=rights, subtitle=subtitle, title=title, updated=updated, entry=entry, total_results=total_results, start_index=start_index, items_per_page=items_per_page, extension_elements=extension_elements, extension_attributes=extension_attributes, text=text, interrupted=interrupted) self.row_count = row_count self.col_count = col_count def GetBatchLink(self): for link in self.link: if link.rel == 'http://schemas.google.com/g/2005#batch': return link return None def SpreadsheetsCellsFeedFromString(xml_string): return atom.CreateClassFromXMLString(SpreadsheetsCellsFeed, xml_string) class SpreadsheetsListFeed(gdata.GDataFeed): """A feed containing Google Spreadsheets Spreadsheets""" _tag = 'feed' _namespace = atom.ATOM_NAMESPACE _children = gdata.GDataFeed._children.copy() _attributes = gdata.GDataFeed._attributes.copy() _children['{%s}entry' % atom.ATOM_NAMESPACE] = ('entry', [SpreadsheetsList]) def SpreadsheetsListFeedFromString(xml_string): return atom.CreateClassFromXMLString(SpreadsheetsListFeed, xml_string) gdata-2.0.18/src/gdata/spreadsheet/text_db.py0000640036466300116100000005141112156622363021121 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright Google 2007-2008, all rights reserved. # # 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. import StringIO import gdata import gdata.service import gdata.spreadsheet import gdata.spreadsheet.service import gdata.docs import gdata.docs.service """Make the Google Documents API feel more like using a database. This module contains a client and other classes which make working with the Google Documents List Data API and the Google Spreadsheets Data API look a bit more like working with a heirarchical database. Using the DatabaseClient, you can create or find spreadsheets and use them like a database, with worksheets representing tables and rows representing records. Example Usage: # Create a new database, a new table, and add records. client = gdata.spreadsheet.text_db.DatabaseClient(username='jo@example.com', password='12345') database = client.CreateDatabase('My Text Database') table = database.CreateTable('addresses', ['name','email', 'phonenumber', 'mailingaddress']) record = table.AddRecord({'name':'Bob', 'email':'bob@example.com', 'phonenumber':'555-555-1234', 'mailingaddress':'900 Imaginary St.'}) # Edit a record record.content['email'] = 'bob2@example.com' record.Push() # Delete a table table.Delete Warnings: Care should be exercised when using this module on spreadsheets which contain formulas. This module treats all rows as containing text and updating a row will overwrite any formula with the output of the formula. The intended use case is to allow easy storage of text data in a spreadsheet. Error: Domain specific extension of Exception. BadCredentials: Error raised is username or password was incorrect. CaptchaRequired: Raised if a login attempt failed and a CAPTCHA challenge was issued. DatabaseClient: Communicates with Google Docs APIs servers. Database: Represents a spreadsheet and interacts with tables. Table: Represents a worksheet and interacts with records. RecordResultSet: A list of records in a table. Record: Represents a row in a worksheet allows manipulation of text data. """ __author__ = 'api.jscudder (Jeffrey Scudder)' class Error(Exception): pass class BadCredentials(Error): pass class CaptchaRequired(Error): pass class DatabaseClient(object): """Allows creation and finding of Google Spreadsheets databases. The DatabaseClient simplifies the process of creating and finding Google Spreadsheets and will talk to both the Google Spreadsheets API and the Google Documents List API. """ def __init__(self, username=None, password=None): """Constructor for a Database Client. If the username and password are present, the constructor will contact the Google servers to authenticate. Args: username: str (optional) Example: jo@example.com password: str (optional) """ self.__docs_client = gdata.docs.service.DocsService() self.__spreadsheets_client = ( gdata.spreadsheet.service.SpreadsheetsService()) self.SetCredentials(username, password) def SetCredentials(self, username, password): """Attempts to log in to Google APIs using the provided credentials. If the username or password are None, the client will not request auth tokens. Args: username: str (optional) Example: jo@example.com password: str (optional) """ self.__docs_client.email = username self.__docs_client.password = password self.__spreadsheets_client.email = username self.__spreadsheets_client.password = password if username and password: try: self.__docs_client.ProgrammaticLogin() self.__spreadsheets_client.ProgrammaticLogin() except gdata.service.CaptchaRequired: raise CaptchaRequired('Please visit https://www.google.com/accounts/' 'DisplayUnlockCaptcha to unlock your account.') except gdata.service.BadAuthentication: raise BadCredentials('Username or password incorrect.') def CreateDatabase(self, name): """Creates a new Google Spreadsheet with the desired name. Args: name: str The title for the spreadsheet. Returns: A Database instance representing the new spreadsheet. """ # Create a Google Spreadsheet to form the foundation of this database. # Spreadsheet is created by uploading a file to the Google Documents # List API. virtual_csv_file = StringIO.StringIO(',,,') virtual_media_source = gdata.MediaSource(file_handle=virtual_csv_file, content_type='text/csv', content_length=3) db_entry = self.__docs_client.UploadSpreadsheet(virtual_media_source, name) return Database(spreadsheet_entry=db_entry, database_client=self) def GetDatabases(self, spreadsheet_key=None, name=None): """Finds spreadsheets which have the unique key or title. If querying on the spreadsheet_key there will be at most one result, but searching by name could yield multiple results. Args: spreadsheet_key: str The unique key for the spreadsheet, this usually in the the form 'pk23...We' or 'o23...423.12,,,3'. name: str The title of the spreadsheets. Returns: A list of Database objects representing the desired spreadsheets. """ if spreadsheet_key: db_entry = self.__docs_client.GetDocumentListEntry( r'/feeds/documents/private/full/spreadsheet%3A' + spreadsheet_key) return [Database(spreadsheet_entry=db_entry, database_client=self)] else: title_query = gdata.docs.service.DocumentQuery() title_query['title'] = name db_feed = self.__docs_client.QueryDocumentListFeed(title_query.ToUri()) matching_databases = [] for entry in db_feed.entry: matching_databases.append(Database(spreadsheet_entry=entry, database_client=self)) return matching_databases def _GetDocsClient(self): return self.__docs_client def _GetSpreadsheetsClient(self): return self.__spreadsheets_client class Database(object): """Provides interface to find and create tables. The database represents a Google Spreadsheet. """ def __init__(self, spreadsheet_entry=None, database_client=None): """Constructor for a database object. Args: spreadsheet_entry: gdata.docs.DocumentListEntry The Atom entry which represents the Google Spreadsheet. The spreadsheet's key is extracted from the entry and stored as a member. database_client: DatabaseClient A client which can talk to the Google Spreadsheets servers to perform operations on worksheets within this spreadsheet. """ self.entry = spreadsheet_entry if self.entry: id_parts = spreadsheet_entry.id.text.split('/') self.spreadsheet_key = id_parts[-1].replace('spreadsheet%3A', '') self.client = database_client def CreateTable(self, name, fields=None): """Add a new worksheet to this spreadsheet and fill in column names. Args: name: str The title of the new worksheet. fields: list of strings The column names which are placed in the first row of this worksheet. These names are converted into XML tags by the server. To avoid changes during the translation process I recommend using all lowercase alphabetic names. For example ['somelongname', 'theothername'] Returns: Table representing the newly created worksheet. """ worksheet = self.client._GetSpreadsheetsClient().AddWorksheet(title=name, row_count=1, col_count=len(fields), key=self.spreadsheet_key) return Table(name=name, worksheet_entry=worksheet, database_client=self.client, spreadsheet_key=self.spreadsheet_key, fields=fields) def GetTables(self, worksheet_id=None, name=None): """Searches for a worksheet with the specified ID or name. The list of results should have one table at most, or no results if the id or name were not found. Args: worksheet_id: str The ID of the worksheet, example: 'od6' name: str The title of the worksheet. Returns: A list of length 0 or 1 containing the desired Table. A list is returned to make this method feel like GetDatabases and GetRecords. """ if worksheet_id: worksheet_entry = self.client._GetSpreadsheetsClient().GetWorksheetsFeed( self.spreadsheet_key, wksht_id=worksheet_id) return [Table(name=worksheet_entry.title.text, worksheet_entry=worksheet_entry, database_client=self.client, spreadsheet_key=self.spreadsheet_key)] else: matching_tables = [] query = None if name: query = gdata.spreadsheet.service.DocumentQuery() query.title = name worksheet_feed = self.client._GetSpreadsheetsClient().GetWorksheetsFeed( self.spreadsheet_key, query=query) for entry in worksheet_feed.entry: matching_tables.append(Table(name=entry.title.text, worksheet_entry=entry, database_client=self.client, spreadsheet_key=self.spreadsheet_key)) return matching_tables def Delete(self): """Deletes the entire database spreadsheet from Google Spreadsheets.""" entry = self.client._GetDocsClient().Get( r'http://docs.google.com/feeds/documents/private/full/spreadsheet%3A' + self.spreadsheet_key) self.client._GetDocsClient().Delete(entry.GetEditLink().href) class Table(object): def __init__(self, name=None, worksheet_entry=None, database_client=None, spreadsheet_key=None, fields=None): self.name = name self.entry = worksheet_entry id_parts = worksheet_entry.id.text.split('/') self.worksheet_id = id_parts[-1] self.spreadsheet_key = spreadsheet_key self.client = database_client self.fields = fields or [] if fields: self.SetFields(fields) def LookupFields(self): """Queries to find the column names in the first row of the worksheet. Useful when you have retrieved the table from the server and you don't know the column names. """ if self.entry: first_row_contents = [] query = gdata.spreadsheet.service.CellQuery() query.max_row = '1' query.min_row = '1' feed = self.client._GetSpreadsheetsClient().GetCellsFeed( self.spreadsheet_key, wksht_id=self.worksheet_id, query=query) for entry in feed.entry: first_row_contents.append(entry.content.text) # Get the next set of cells if needed. next_link = feed.GetNextLink() while next_link: feed = self.client._GetSpreadsheetsClient().Get(next_link.href, converter=gdata.spreadsheet.SpreadsheetsCellsFeedFromString) for entry in feed.entry: first_row_contents.append(entry.content.text) next_link = feed.GetNextLink() # Convert the contents of the cells to valid headers. self.fields = ConvertStringsToColumnHeaders(first_row_contents) def SetFields(self, fields): """Changes the contents of the cells in the first row of this worksheet. Args: fields: list of strings The names in the list comprise the first row of the worksheet. These names are converted into XML tags by the server. To avoid changes during the translation process I recommend using all lowercase alphabetic names. For example ['somelongname', 'theothername'] """ # TODO: If the table already had fields, we might want to clear out the, # current column headers. self.fields = fields i = 0 for column_name in fields: i = i + 1 # TODO: speed this up by using a batch request to update cells. self.client._GetSpreadsheetsClient().UpdateCell(1, i, column_name, self.spreadsheet_key, self.worksheet_id) def Delete(self): """Deletes this worksheet from the spreadsheet.""" worksheet = self.client._GetSpreadsheetsClient().GetWorksheetsFeed( self.spreadsheet_key, wksht_id=self.worksheet_id) self.client._GetSpreadsheetsClient().DeleteWorksheet( worksheet_entry=worksheet) def AddRecord(self, data): """Adds a new row to this worksheet. Args: data: dict of strings Mapping of string values to column names. Returns: Record which represents this row of the spreadsheet. """ new_row = self.client._GetSpreadsheetsClient().InsertRow(data, self.spreadsheet_key, wksht_id=self.worksheet_id) return Record(content=data, row_entry=new_row, spreadsheet_key=self.spreadsheet_key, worksheet_id=self.worksheet_id, database_client=self.client) def GetRecord(self, row_id=None, row_number=None): """Gets a single record from the worksheet based on row ID or number. Args: row_id: The ID for the individual row. row_number: str or int The position of the desired row. Numbering begins at 1, which refers to the second row in the worksheet since the first row is used for column names. Returns: Record for the desired row. """ if row_id: row_entry = self.client._GetSpreadsheetsClient().GetListFeed( self.spreadsheet_key, wksht_id=self.worksheet_id, row_id=row_id) return Record(content=None, row_entry=row_entry, spreadsheet_key=self.spreadsheet_key, worksheet_id=self.worksheet_id, database_client=self.client) else: row_query = gdata.spreadsheet.service.ListQuery() row_query.start_index = str(row_number) row_query.max_results = '1' row_feed = self.client._GetSpreadsheetsClient().GetListFeed( self.spreadsheet_key, wksht_id=self.worksheet_id, query=row_query) if len(row_feed.entry) >= 1: return Record(content=None, row_entry=row_feed.entry[0], spreadsheet_key=self.spreadsheet_key, worksheet_id=self.worksheet_id, database_client=self.client) else: return None def GetRecords(self, start_row, end_row): """Gets all rows between the start and end row numbers inclusive. Args: start_row: str or int end_row: str or int Returns: RecordResultSet for the desired rows. """ start_row = int(start_row) end_row = int(end_row) max_rows = end_row - start_row + 1 row_query = gdata.spreadsheet.service.ListQuery() row_query.start_index = str(start_row) row_query.max_results = str(max_rows) rows_feed = self.client._GetSpreadsheetsClient().GetListFeed( self.spreadsheet_key, wksht_id=self.worksheet_id, query=row_query) return RecordResultSet(rows_feed, self.client, self.spreadsheet_key, self.worksheet_id) def FindRecords(self, query_string): """Performs a query against the worksheet to find rows which match. For details on query string syntax see the section on sq under http://code.google.com/apis/spreadsheets/reference.html#list_Parameters Args: query_string: str Examples: 'name == john' to find all rows with john in the name column, '(cost < 19.50 and name != toy) or cost > 500' Returns: RecordResultSet with the first group of matches. """ row_query = gdata.spreadsheet.service.ListQuery() row_query.sq = query_string matching_feed = self.client._GetSpreadsheetsClient().GetListFeed( self.spreadsheet_key, wksht_id=self.worksheet_id, query=row_query) return RecordResultSet(matching_feed, self.client, self.spreadsheet_key, self.worksheet_id) class RecordResultSet(list): """A collection of rows which allows fetching of the next set of results. The server may not send all rows in the requested range because there are too many. Using this result set you can access the first set of results as if it is a list, then get the next batch (if there are more results) by calling GetNext(). """ def __init__(self, feed, client, spreadsheet_key, worksheet_id): self.client = client self.spreadsheet_key = spreadsheet_key self.worksheet_id = worksheet_id self.feed = feed list(self) for entry in self.feed.entry: self.append(Record(content=None, row_entry=entry, spreadsheet_key=spreadsheet_key, worksheet_id=worksheet_id, database_client=client)) def GetNext(self): """Fetches the next batch of rows in the result set. Returns: A new RecordResultSet. """ next_link = self.feed.GetNextLink() if next_link and next_link.href: new_feed = self.client._GetSpreadsheetsClient().Get(next_link.href, converter=gdata.spreadsheet.SpreadsheetsListFeedFromString) return RecordResultSet(new_feed, self.client, self.spreadsheet_key, self.worksheet_id) class Record(object): """Represents one row in a worksheet and provides a dictionary of values. Attributes: custom: dict Represents the contents of the row with cell values mapped to column headers. """ def __init__(self, content=None, row_entry=None, spreadsheet_key=None, worksheet_id=None, database_client=None): """Constructor for a record. Args: content: dict of strings Mapping of string values to column names. row_entry: gdata.spreadsheet.SpreadsheetsList The Atom entry representing this row in the worksheet. spreadsheet_key: str The ID of the spreadsheet in which this row belongs. worksheet_id: str The ID of the worksheet in which this row belongs. database_client: DatabaseClient The client which can be used to talk the Google Spreadsheets server to edit this row. """ self.entry = row_entry self.spreadsheet_key = spreadsheet_key self.worksheet_id = worksheet_id if row_entry: self.row_id = row_entry.id.text.split('/')[-1] else: self.row_id = None self.client = database_client self.content = content or {} if not content: self.ExtractContentFromEntry(row_entry) def ExtractContentFromEntry(self, entry): """Populates the content and row_id based on content of the entry. This method is used in the Record's contructor. Args: entry: gdata.spreadsheet.SpreadsheetsList The Atom entry representing this row in the worksheet. """ self.content = {} if entry: self.row_id = entry.id.text.split('/')[-1] for label, custom in entry.custom.iteritems(): self.content[label] = custom.text def Push(self): """Send the content of the record to spreadsheets to edit the row. All items in the content dictionary will be sent. Items which have been removed from the content may remain in the row. The content member of the record will not be modified so additional fields in the row might be absent from this local copy. """ self.entry = self.client._GetSpreadsheetsClient().UpdateRow(self.entry, self.content) def Pull(self): """Query Google Spreadsheets to get the latest data from the server. Fetches the entry for this row and repopulates the content dictionary with the data found in the row. """ if self.row_id: self.entry = self.client._GetSpreadsheetsClient().GetListFeed( self.spreadsheet_key, wksht_id=self.worksheet_id, row_id=self.row_id) self.ExtractContentFromEntry(self.entry) def Delete(self): self.client._GetSpreadsheetsClient().DeleteRow(self.entry) def ConvertStringsToColumnHeaders(proposed_headers): """Converts a list of strings to column names which spreadsheets accepts. When setting values in a record, the keys which represent column names must fit certain rules. They are all lower case, contain no spaces or special characters. If two columns have the same name after being sanitized, the columns further to the right have _2, _3 _4, etc. appended to them. If there are column names which consist of all special characters, or if the column header is blank, an obfuscated value will be used for a column name. This method does not handle blank column names or column names with only special characters. """ headers = [] for input_string in proposed_headers: # TODO: probably a more efficient way to do this. Perhaps regex. sanitized = input_string.lower().replace('_', '').replace( ':', '').replace(' ', '') # When the same sanitized header appears multiple times in the first row # of a spreadsheet, _n is appended to the name to make it unique. header_count = headers.count(sanitized) if header_count > 0: headers.append('%s_%i' % (sanitized, header_count+1)) else: headers.append(sanitized) return headers gdata-2.0.18/src/gdata/spreadsheet/service.py0000750036466300116100000004074612156622363021143 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright (C) 2007 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. """SpreadsheetsService extends the GDataService to streamline Google Spreadsheets operations. SpreadsheetService: Provides methods to query feeds and manipulate items. Extends GDataService. DictionaryToParamList: Function which converts a dictionary into a list of URL arguments (represented as strings). This is a utility function used in CRUD operations. """ __author__ = 'api.laurabeth@gmail.com (Laura Beth Lincoln)' import gdata import atom.service import gdata.service import gdata.spreadsheet import atom class Error(Exception): """Base class for exceptions in this module.""" pass class RequestError(Error): pass class SpreadsheetsService(gdata.service.GDataService): """Client for the Google Spreadsheets service.""" def __init__(self, email=None, password=None, source=None, server='spreadsheets.google.com', additional_headers=None, **kwargs): """Creates a client for the Google Spreadsheets service. Args: email: string (optional) The user's email address, used for authentication. password: string (optional) The user's password. source: string (optional) The name of the user's application. server: string (optional) The name of the server to which a connection will be opened. Default value: 'spreadsheets.google.com'. **kwargs: The other parameters to pass to gdata.service.GDataService constructor. """ gdata.service.GDataService.__init__( self, email=email, password=password, service='wise', source=source, server=server, additional_headers=additional_headers, **kwargs) def GetSpreadsheetsFeed(self, key=None, query=None, visibility='private', projection='full'): """Gets a spreadsheets feed or a specific entry if a key is defined Args: key: string (optional) The spreadsheet key defined in /ccc?key= query: DocumentQuery (optional) Query parameters Returns: If there is no key, then a SpreadsheetsSpreadsheetsFeed. If there is a key, then a SpreadsheetsSpreadsheet. """ base_uri = 'https://%s/feeds/spreadsheets' % self.server uri = ('%s/%s/%s' % (base_uri, visibility, projection)) if key is not None: uri = '%s/%s' % (uri, key) if query != None: query.feed = base_uri query.visibility = visibility query.projection = projection uri = query.ToUri() if key: return self.Get(uri, converter=gdata.spreadsheet.SpreadsheetsSpreadsheetFromString) else: return self.Get(uri, converter=gdata.spreadsheet.SpreadsheetsSpreadsheetsFeedFromString) def GetWorksheetsFeed(self, key, wksht_id=None, query=None, visibility='private', projection='full'): """Gets a worksheets feed or a specific entry if a wksht is defined Args: key: string The spreadsheet key defined in /ccc?key= wksht_id: string (optional) The id for a specific worksheet entry query: DocumentQuery (optional) Query parameters Returns: If there is no wksht_id, then a SpreadsheetsWorksheetsFeed. If there is a wksht_id, then a SpreadsheetsWorksheet. """ uri = ('https://%s/feeds/worksheets/%s/%s/%s' % (self.server, key, visibility, projection)) if wksht_id != None: uri = '%s/%s' % (uri, wksht_id) if query != None: query.feed = uri uri = query.ToUri() if wksht_id: return self.Get(uri, converter=gdata.spreadsheet.SpreadsheetsWorksheetFromString) else: return self.Get(uri, converter=gdata.spreadsheet.SpreadsheetsWorksheetsFeedFromString) def AddWorksheet(self, title, row_count, col_count, key): """Creates a new worksheet in the desired spreadsheet. The new worksheet is appended to the end of the list of worksheets. The new worksheet will only have the available number of columns and cells specified. Args: title: str The title which will be displayed in the list of worksheets. row_count: int or str The number of rows in the new worksheet. col_count: int or str The number of columns in the new worksheet. key: str The spreadsheet key to the spreadsheet to which the new worksheet should be added. Returns: A SpreadsheetsWorksheet if the new worksheet was created succesfully. """ new_worksheet = gdata.spreadsheet.SpreadsheetsWorksheet( title=atom.Title(text=title), row_count=gdata.spreadsheet.RowCount(text=str(row_count)), col_count=gdata.spreadsheet.ColCount(text=str(col_count))) return self.Post(new_worksheet, 'https://%s/feeds/worksheets/%s/private/full' % (self.server, key), converter=gdata.spreadsheet.SpreadsheetsWorksheetFromString) def UpdateWorksheet(self, worksheet_entry, url=None): """Changes the size and/or title of the desired worksheet. Args: worksheet_entry: SpreadsheetWorksheet The new contents of the worksheet. url: str (optional) The URL to which the edited worksheet entry should be sent. If the url is None, the edit URL from the worksheet will be used. Returns: A SpreadsheetsWorksheet with the new information about the worksheet. """ target_url = url or worksheet_entry.GetEditLink().href return self.Put(worksheet_entry, target_url, converter=gdata.spreadsheet.SpreadsheetsWorksheetFromString) def DeleteWorksheet(self, worksheet_entry=None, url=None): """Removes the desired worksheet from the spreadsheet Args: worksheet_entry: SpreadsheetWorksheet (optional) The worksheet to be deleted. If this is none, then the DELETE reqest is sent to the url specified in the url parameter. url: str (optaional) The URL to which the DELETE request should be sent. If left as None, the worksheet's edit URL is used. Returns: True if the worksheet was deleted successfully. """ if url: target_url = url else: target_url = worksheet_entry.GetEditLink().href return self.Delete(target_url) def GetCellsFeed(self, key, wksht_id='default', cell=None, query=None, visibility='private', projection='full'): """Gets a cells feed or a specific entry if a cell is defined Args: key: string The spreadsheet key defined in /ccc?key= wksht_id: string The id for a specific worksheet entry cell: string (optional) The R1C1 address of the cell query: DocumentQuery (optional) Query parameters Returns: If there is no cell, then a SpreadsheetsCellsFeed. If there is a cell, then a SpreadsheetsCell. """ uri = ('https://%s/feeds/cells/%s/%s/%s/%s' % (self.server, key, wksht_id, visibility, projection)) if cell != None: uri = '%s/%s' % (uri, cell) if query != None: query.feed = uri uri = query.ToUri() if cell: return self.Get(uri, converter=gdata.spreadsheet.SpreadsheetsCellFromString) else: return self.Get(uri, converter=gdata.spreadsheet.SpreadsheetsCellsFeedFromString) def GetListFeed(self, key, wksht_id='default', row_id=None, query=None, visibility='private', projection='full'): """Gets a list feed or a specific entry if a row_id is defined Args: key: string The spreadsheet key defined in /ccc?key= wksht_id: string The id for a specific worksheet entry row_id: string (optional) The row_id of a row in the list query: DocumentQuery (optional) Query parameters Returns: If there is no row_id, then a SpreadsheetsListFeed. If there is a row_id, then a SpreadsheetsList. """ uri = ('https://%s/feeds/list/%s/%s/%s/%s' % (self.server, key, wksht_id, visibility, projection)) if row_id is not None: uri = '%s/%s' % (uri, row_id) if query is not None: query.feed = uri uri = query.ToUri() if row_id: return self.Get(uri, converter=gdata.spreadsheet.SpreadsheetsListFromString) else: return self.Get(uri, converter=gdata.spreadsheet.SpreadsheetsListFeedFromString) def UpdateCell(self, row, col, inputValue, key, wksht_id='default'): """Updates an existing cell. Args: row: int The row the cell to be editted is in col: int The column the cell to be editted is in inputValue: str the new value of the cell key: str The key of the spreadsheet in which this cell resides. wksht_id: str The ID of the worksheet which holds this cell. Returns: The updated cell entry """ row = str(row) col = str(col) # make the new cell new_cell = gdata.spreadsheet.Cell(row=row, col=col, inputValue=inputValue) # get the edit uri and PUT cell = 'R%sC%s' % (row, col) entry = self.GetCellsFeed(key, wksht_id, cell) for a_link in entry.link: if a_link.rel == 'edit': entry.cell = new_cell return self.Put(entry, a_link.href, converter=gdata.spreadsheet.SpreadsheetsCellFromString) def _GenerateCellsBatchUrl(self, spreadsheet_key, worksheet_id): return ('https://spreadsheets.google.com/feeds/cells/%s/%s/' 'private/full/batch' % (spreadsheet_key, worksheet_id)) def ExecuteBatch(self, batch_feed, url=None, spreadsheet_key=None, worksheet_id=None, converter=gdata.spreadsheet.SpreadsheetsCellsFeedFromString): """Sends a batch request feed to the server. The batch request needs to be sent to the batch URL for a particular worksheet. You can specify the worksheet by providing the spreadsheet_key and worksheet_id, or by sending the URL from the cells feed's batch link. Args: batch_feed: gdata.spreadsheet.SpreadsheetsCellFeed A feed containing BatchEntry elements which contain the desired CRUD operation and any necessary data to modify a cell. url: str (optional) The batch URL for the cells feed to which these changes should be applied. This can be found by calling cells_feed.GetBatchLink().href. spreadsheet_key: str (optional) Used to generate the batch request URL if the url argument is None. If using the spreadsheet key to generate the URL, the worksheet id is also required. worksheet_id: str (optional) Used if the url is not provided, it is oart of the batch feed target URL. This is used with the spreadsheet key. converter: Function (optional) Function to be executed on the server's response. This function should take one string as a parameter. The default value is SpreadsheetsCellsFeedFromString which will turn the result into a gdata.spreadsheet.SpreadsheetsCellsFeed object. Returns: A gdata.BatchFeed containing the results. """ if url is None: url = self._GenerateCellsBatchUrl(spreadsheet_key, worksheet_id) return self.Post(batch_feed, url, converter=converter) def InsertRow(self, row_data, key, wksht_id='default'): """Inserts a new row with the provided data Args: uri: string The post uri of the list feed row_data: dict A dictionary of column header to row data Returns: The inserted row """ new_entry = gdata.spreadsheet.SpreadsheetsList() for k, v in row_data.iteritems(): new_custom = gdata.spreadsheet.Custom() new_custom.column = k new_custom.text = v new_entry.custom[new_custom.column] = new_custom # Generate the post URL for the worksheet which will receive the new entry. post_url = 'https://spreadsheets.google.com/feeds/list/%s/%s/private/full'%( key, wksht_id) return self.Post(new_entry, post_url, converter=gdata.spreadsheet.SpreadsheetsListFromString) def UpdateRow(self, entry, new_row_data): """Updates a row with the provided data If you want to add additional information to a row, it is often easier to change the values in entry.custom, then use the Put method instead of UpdateRow. This UpdateRow method will replace the contents of the row with new_row_data - it will change all columns not just the columns specified in the new_row_data dict. Args: entry: gdata.spreadsheet.SpreadsheetsList The entry to be updated new_row_data: dict A dictionary of column header to row data Returns: The updated row """ entry.custom = {} for k, v in new_row_data.iteritems(): new_custom = gdata.spreadsheet.Custom() new_custom.column = k new_custom.text = v entry.custom[k] = new_custom for a_link in entry.link: if a_link.rel == 'edit': return self.Put(entry, a_link.href, converter=gdata.spreadsheet.SpreadsheetsListFromString) def DeleteRow(self, entry): """Deletes a row, the provided entry Args: entry: gdata.spreadsheet.SpreadsheetsList The row to be deleted Returns: The delete response """ for a_link in entry.link: if a_link.rel == 'edit': return self.Delete(a_link.href) class DocumentQuery(gdata.service.Query): def _GetTitleQuery(self): return self['title'] def _SetTitleQuery(self, document_query): self['title'] = document_query title = property(_GetTitleQuery, _SetTitleQuery, doc="""The title query parameter""") def _GetTitleExactQuery(self): return self['title-exact'] def _SetTitleExactQuery(self, document_query): self['title-exact'] = document_query title_exact = property(_GetTitleExactQuery, _SetTitleExactQuery, doc="""The title-exact query parameter""") class CellQuery(gdata.service.Query): def _GetMinRowQuery(self): return self['min-row'] def _SetMinRowQuery(self, cell_query): self['min-row'] = cell_query min_row = property(_GetMinRowQuery, _SetMinRowQuery, doc="""The min-row query parameter""") def _GetMaxRowQuery(self): return self['max-row'] def _SetMaxRowQuery(self, cell_query): self['max-row'] = cell_query max_row = property(_GetMaxRowQuery, _SetMaxRowQuery, doc="""The max-row query parameter""") def _GetMinColQuery(self): return self['min-col'] def _SetMinColQuery(self, cell_query): self['min-col'] = cell_query min_col = property(_GetMinColQuery, _SetMinColQuery, doc="""The min-col query parameter""") def _GetMaxColQuery(self): return self['max-col'] def _SetMaxColQuery(self, cell_query): self['max-col'] = cell_query max_col = property(_GetMaxColQuery, _SetMaxColQuery, doc="""The max-col query parameter""") def _GetRangeQuery(self): return self['range'] def _SetRangeQuery(self, cell_query): self['range'] = cell_query range = property(_GetRangeQuery, _SetRangeQuery, doc="""The range query parameter""") def _GetReturnEmptyQuery(self): return self['return-empty'] def _SetReturnEmptyQuery(self, cell_query): self['return-empty'] = cell_query return_empty = property(_GetReturnEmptyQuery, _SetReturnEmptyQuery, doc="""The return-empty query parameter""") class ListQuery(gdata.service.Query): def _GetSpreadsheetQuery(self): return self['sq'] def _SetSpreadsheetQuery(self, list_query): self['sq'] = list_query sq = property(_GetSpreadsheetQuery, _SetSpreadsheetQuery, doc="""The sq query parameter""") def _GetOrderByQuery(self): return self['orderby'] def _SetOrderByQuery(self, list_query): self['orderby'] = list_query orderby = property(_GetOrderByQuery, _SetOrderByQuery, doc="""The orderby query parameter""") def _GetReverseQuery(self): return self['reverse'] def _SetReverseQuery(self, list_query): self['reverse'] = list_query reverse = property(_GetReverseQuery, _SetReverseQuery, doc="""The reverse query parameter""") gdata-2.0.18/src/gdata/dublincore/0000750036466300116100000000000012156625015016727 5ustar afshareng00000000000000gdata-2.0.18/src/gdata/dublincore/data.py0000640036466300116100000000407212156622363020221 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright (C) 2009 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. """Contains the data classes of the Dublin Core Metadata Initiative (DCMI) Extension""" __author__ = 'j.s@google.com (Jeff Scudder)' import atom.core DC_TEMPLATE = '{http://purl.org/dc/terms/}%s' class Creator(atom.core.XmlElement): """Entity primarily responsible for making the resource.""" _qname = DC_TEMPLATE % 'creator' class Date(atom.core.XmlElement): """Point or period of time associated with an event in the lifecycle of the resource.""" _qname = DC_TEMPLATE % 'date' class Description(atom.core.XmlElement): """Account of the resource.""" _qname = DC_TEMPLATE % 'description' class Format(atom.core.XmlElement): """File format, physical medium, or dimensions of the resource.""" _qname = DC_TEMPLATE % 'format' class Identifier(atom.core.XmlElement): """An unambiguous reference to the resource within a given context.""" _qname = DC_TEMPLATE % 'identifier' class Language(atom.core.XmlElement): """Language of the resource.""" _qname = DC_TEMPLATE % 'language' class Publisher(atom.core.XmlElement): """Entity responsible for making the resource available.""" _qname = DC_TEMPLATE % 'publisher' class Rights(atom.core.XmlElement): """Information about rights held in and over the resource.""" _qname = DC_TEMPLATE % 'rights' class Subject(atom.core.XmlElement): """Topic of the resource.""" _qname = DC_TEMPLATE % 'subject' class Title(atom.core.XmlElement): """Name given to the resource.""" _qname = DC_TEMPLATE % 'title' gdata-2.0.18/src/gdata/dublincore/__init__.py0000640036466300116100000000113012156622363021037 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright (C) 2009 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. gdata-2.0.18/src/gdata/blogger/0000750036466300116100000000000012156625015016222 5ustar afshareng00000000000000gdata-2.0.18/src/gdata/blogger/data.py0000640036466300116100000001070712156622363017516 0ustar afshareng00000000000000#!/usr/bin/env python # # Copyright (C) 2009 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. """Data model classes for parsing and generating XML for the Blogger API.""" __author__ = 'j.s@google.com (Jeff Scudder)' import re import urlparse import atom.core import gdata.data LABEL_SCHEME = 'http://www.blogger.com/atom/ns#' THR_TEMPLATE = '{http://purl.org/syndication/thread/1.0}%s' BLOG_NAME_PATTERN = re.compile('(http://)(\w*)') BLOG_ID_PATTERN = re.compile('(tag:blogger.com,1999:blog-)(\w*)') BLOG_ID2_PATTERN = re.compile('tag:blogger.com,1999:user-(\d+)\.blog-(\d+)') POST_ID_PATTERN = re.compile( '(tag:blogger.com,1999:blog-)(\w*)(.post-)(\w*)') PAGE_ID_PATTERN = re.compile( '(tag:blogger.com,1999:blog-)(\w*)(.page-)(\w*)') COMMENT_ID_PATTERN = re.compile('.*-(\w*)$') class BloggerEntry(gdata.data.GDEntry): """Adds convenience methods inherited by all Blogger entries.""" def get_blog_id(self): """Extracts the Blogger id of this blog. This method is useful when contructing URLs by hand. The blog id is often used in blogger operation URLs. This should not be confused with the id member of a BloggerBlog. The id element is the Atom id XML element. The blog id which this method returns is a part of the Atom id. Returns: The blog's unique id as a string. """ if self.id.text: match = BLOG_ID_PATTERN.match(self.id.text) if match: return match.group(2) else: return BLOG_ID2_PATTERN.match(self.id.text).group(2) return None GetBlogId = get_blog_id def get_blog_name(self): """Finds the name of this blog as used in the 'alternate' URL. An alternate URL is in the form 'http://blogName.blogspot.com/'. For an entry representing the above example, this method would return 'blogName'. Returns: The blog's URL name component as a string. """ for link in self.link: if link.rel == 'alternate': return urlparse.urlparse(link.href)[1].split(".", 1)[0] return None GetBlogName = get_blog_name class Blog(BloggerEntry): """Represents a blog which belongs to the user.""" class BlogFeed(gdata.data.GDFeed): entry = [Blog] class BlogPost(BloggerEntry): """Represents a single post on a blog.""" def add_label(self, label): """Adds a label to the blog post. The label is represented by an Atom category element, so this method is shorthand for appending a new atom.Category object. Args: label: str """ self.category.append(atom.data.Category(scheme=LABEL_SCHEME, term=label)) AddLabel = add_label def get_post_id(self): """Extracts the postID string from the entry's Atom id. Returns: A string of digits which identify this post within the blog. """ if self.id.text: return POST_ID_PATTERN.match(self.id.text).group(4) return None GetPostId = get_post_id class BlogPostFeed(gdata.data.GDFeed): entry = [BlogPost] class BlogPage(BloggerEntry): """Represents a single page on a blog.""" def get_page_id(self): """Extracts the pageID string from entry's Atom id. Returns: A string of digits which identify this post within the blog. """ if self.id.text: return PAGE_ID_PATTERN.match(self.id.text).group(4) return None GetPageId = get_page_id class BlogPageFeed(gdata.data.GDFeed): entry = [BlogPage] class InReplyTo(atom.core.XmlElement): _qname = THR_TEMPLATE % 'in-reply-to' href = 'href' ref = 'ref' source = 'source' type = 'type' class Comment(BloggerEntry): """Blog post comment entry in a feed listing comments on a post or blog.""" in_reply_to = InReplyTo def get_comment_id(self): """Extracts the commentID string from the entry's Atom id. Returns: A string of digits which identify this post within the blog. """ if self.id.text: return COMMENT_ID_PATTERN.match(self.id.text).group(1) return None GetCommentId = get_comment_id class CommentFeed(gdata.data.GDFeed): entry = [Comment] gdata-2.0.18/src/gdata/blogger/__init__.py0000640036466300116100000001437312156622363020347 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright (C) 2007, 2008 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. """Contains extensions to Atom objects used with Blogger.""" __author__ = 'api.jscudder (Jeffrey Scudder)' import atom import gdata import re LABEL_SCHEME = 'http://www.blogger.com/atom/ns#' THR_NAMESPACE = 'http://purl.org/syndication/thread/1.0' class BloggerEntry(gdata.GDataEntry): """Adds convenience methods inherited by all Blogger entries.""" blog_name_pattern = re.compile('(http://)(\w*)') blog_id_pattern = re.compile('(tag:blogger.com,1999:blog-)(\w*)') blog_id2_pattern = re.compile('tag:blogger.com,1999:user-(\d+)\.blog-(\d+)') def GetBlogId(self): """Extracts the Blogger id of this blog. This method is useful when contructing URLs by hand. The blog id is often used in blogger operation URLs. This should not be confused with the id member of a BloggerBlog. The id element is the Atom id XML element. The blog id which this method returns is a part of the Atom id. Returns: The blog's unique id as a string. """ if self.id.text: match = self.blog_id_pattern.match(self.id.text) if match: return match.group(2) else: return self.blog_id2_pattern.match(self.id.text).group(2) return None def GetBlogName(self): """Finds the name of this blog as used in the 'alternate' URL. An alternate URL is in the form 'http://blogName.blogspot.com/'. For an entry representing the above example, this method would return 'blogName'. Returns: The blog's URL name component as a string. """ for link in self.link: if link.rel == 'alternate': return self.blog_name_pattern.match(link.href).group(2) return None class BlogEntry(BloggerEntry): """Describes a blog entry in the feed listing a user's blogs.""" def BlogEntryFromString(xml_string): return atom.CreateClassFromXMLString(BlogEntry, xml_string) class BlogFeed(gdata.GDataFeed): """Describes a feed of a user's blogs.""" _children = gdata.GDataFeed._children.copy() _children['{%s}entry' % atom.ATOM_NAMESPACE] = ('entry', [BlogEntry]) def BlogFeedFromString(xml_string): return atom.CreateClassFromXMLString(BlogFeed, xml_string) class BlogPostEntry(BloggerEntry): """Describes a blog post entry in the feed of a blog's posts.""" post_id_pattern = re.compile('(tag:blogger.com,1999:blog-)(\w*)(.post-)(\w*)') def AddLabel(self, label): """Adds a label to the blog post. The label is represented by an Atom category element, so this method is shorthand for appending a new atom.Category object. Args: label: str """ self.category.append(atom.Category(scheme=LABEL_SCHEME, term=label)) def GetPostId(self): """Extracts the postID string from the entry's Atom id. Returns: A string of digits which identify this post within the blog. """ if self.id.text: return self.post_id_pattern.match(self.id.text).group(4) return None def BlogPostEntryFromString(xml_string): return atom.CreateClassFromXMLString(BlogPostEntry, xml_string) class BlogPostFeed(gdata.GDataFeed): """Describes a feed of a blog's posts.""" _children = gdata.GDataFeed._children.copy() _children['{%s}entry' % atom.ATOM_NAMESPACE] = ('entry', [BlogPostEntry]) def BlogPostFeedFromString(xml_string): return atom.CreateClassFromXMLString(BlogPostFeed, xml_string) class InReplyTo(atom.AtomBase): _tag = 'in-reply-to' _namespace = THR_NAMESPACE _attributes = atom.AtomBase._attributes.copy() _attributes['href'] = 'href' _attributes['ref'] = 'ref' _attributes['source'] = 'source' _attributes['type'] = 'type' def __init__(self, href=None, ref=None, source=None, type=None, extension_elements=None, extension_attributes=None, text=None): self.href = href self.ref = ref self.source = source self.type = type self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} self.text = text def InReplyToFromString(xml_string): return atom.CreateClassFromXMLString(InReplyTo, xml_string) class CommentEntry(BloggerEntry): """Describes a blog post comment entry in the feed of a blog post's comments.""" _children = BloggerEntry._children.copy() _children['{%s}in-reply-to' % THR_NAMESPACE] = ('in_reply_to', InReplyTo) comment_id_pattern = re.compile('.*-(\w*)$') def __init__(self, author=None, category=None, content=None, contributor=None, atom_id=None, link=None, published=None, rights=None, source=None, summary=None, control=None, title=None, updated=None, in_reply_to=None, extension_elements=None, extension_attributes=None, text=None): BloggerEntry.__init__(self, author=author, category=category, content=content, contributor=contributor, atom_id=atom_id, link=link, published=published, rights=rights, source=source, summary=summary, control=control, title=title, updated=updated, extension_elements=extension_elements, extension_attributes=extension_attributes, text=text) self.in_reply_to = in_reply_to def GetCommentId(self): """Extracts the commentID string from the entry's Atom id. Returns: A string of digits which identify this post within the blog. """ if self.id.text: return self.comment_id_pattern.match(self.id.text).group(1) return None def CommentEntryFromString(xml_string): return atom.CreateClassFromXMLString(CommentEntry, xml_string) class CommentFeed(gdata.GDataFeed): """Describes a feed of a blog post's comments.""" _children = gdata.GDataFeed._children.copy() _children['{%s}entry' % atom.ATOM_NAMESPACE] = ('entry', [CommentEntry]) def CommentFeedFromString(xml_string): return atom.CreateClassFromXMLString(CommentFeed, xml_string) gdata-2.0.18/src/gdata/blogger/client.py0000640036466300116100000001504712156622363020065 0ustar afshareng00000000000000#!/usr/bin/env python # # Copyright (C) 2009 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. """Contains a client to communicate with the Blogger servers. For documentation on the Blogger API, see: http://code.google.com/apis/blogger/ """ __author__ = 'j.s@google.com (Jeff Scudder)' import gdata.client import gdata.gauth import gdata.blogger.data import atom.data import atom.http_core # List user's blogs, takes a user ID, or 'default'. BLOGS_URL = 'http://www.blogger.com/feeds/%s/blogs' # Takes a blog ID. BLOG_POST_URL = 'http://www.blogger.com/feeds/%s/posts/default' # Takes a blog ID. BLOG_PAGE_URL = 'http://www.blogger.com/feeds/%s/pages/default' # Takes a blog ID and post ID. BLOG_POST_COMMENTS_URL = 'http://www.blogger.com/feeds/%s/%s/comments/default' # Takes a blog ID. BLOG_COMMENTS_URL = 'http://www.blogger.com/feeds/%s/comments/default' # Takes a blog ID. BLOG_ARCHIVE_URL = 'http://www.blogger.com/feeds/%s/archive/full' class BloggerClient(gdata.client.GDClient): api_version = '2' auth_service = 'blogger' auth_scopes = gdata.gauth.AUTH_SCOPES['blogger'] def get_blogs(self, user_id='default', auth_token=None, desired_class=gdata.blogger.data.BlogFeed, **kwargs): return self.get_feed(BLOGS_URL % user_id, auth_token=auth_token, desired_class=desired_class, **kwargs) GetBlogs = get_blogs def get_posts(self, blog_id, auth_token=None, desired_class=gdata.blogger.data.BlogPostFeed, query=None, **kwargs): return self.get_feed(BLOG_POST_URL % blog_id, auth_token=auth_token, desired_class=desired_class, query=query, **kwargs) GetPosts = get_posts def get_pages(self, blog_id, auth_token=None, desired_class=gdata.blogger.data.BlogPageFeed, query=None, **kwargs): return self.get_feed(BLOG_PAGE_URL % blog_id, auth_token=auth_token, desired_class=desired_class, query=query, **kwargs) GetPages = get_pages def get_post_comments(self, blog_id, post_id, auth_token=None, desired_class=gdata.blogger.data.CommentFeed, query=None, **kwargs): return self.get_feed(BLOG_POST_COMMENTS_URL % (blog_id, post_id), auth_token=auth_token, desired_class=desired_class, query=query, **kwargs) GetPostComments = get_post_comments def get_blog_comments(self, blog_id, auth_token=None, desired_class=gdata.blogger.data.CommentFeed, query=None, **kwargs): return self.get_feed(BLOG_COMMENTS_URL % blog_id, auth_token=auth_token, desired_class=desired_class, query=query, **kwargs) GetBlogComments = get_blog_comments def get_blog_archive(self, blog_id, auth_token=None, **kwargs): return self.get_feed(BLOG_ARCHIVE_URL % blog_id, auth_token=auth_token, **kwargs) GetBlogArchive = get_blog_archive def add_post(self, blog_id, title, body, labels=None, draft=False, auth_token=None, title_type='text', body_type='html', **kwargs): # Construct an atom Entry for the blog post to be sent to the server. new_entry = gdata.blogger.data.BlogPost( title=atom.data.Title(text=title, type=title_type), content=atom.data.Content(text=body, type=body_type)) if labels: for label in labels: new_entry.add_label(label) if draft: new_entry.control = atom.data.Control(draft=atom.data.Draft(text='yes')) return self.post(new_entry, BLOG_POST_URL % blog_id, auth_token=auth_token, **kwargs) AddPost = add_post def add_page(self, blog_id, title, body, draft=False, auth_token=None, title_type='text', body_type='html', **kwargs): new_entry = gdata.blogger.data.BlogPage( title=atom.data.Title(text=title, type=title_type), content=atom.data.Content(text=body, type=body_type)) if draft: new_entry.control = atom.data.Control(draft=atom.data.Draft(text='yes')) return self.post(new_entry, BLOG_PAGE_URL % blog_id, auth_token=auth_token, **kwargs) AddPage = add_page def add_comment(self, blog_id, post_id, body, auth_token=None, title_type='text', body_type='html', **kwargs): new_entry = gdata.blogger.data.Comment( content=atom.data.Content(text=body, type=body_type)) return self.post(new_entry, BLOG_POST_COMMENTS_URL % (blog_id, post_id), auth_token=auth_token, **kwargs) AddComment = add_comment def update(self, entry, auth_token=None, **kwargs): # The Blogger API does not currently support ETags, so for now remove # the ETag before performing an update. old_etag = entry.etag entry.etag = None response = gdata.client.GDClient.update(self, entry, auth_token=auth_token, **kwargs) entry.etag = old_etag return response Update = update def delete(self, entry_or_uri, auth_token=None, **kwargs): if isinstance(entry_or_uri, (str, unicode, atom.http_core.Uri)): return gdata.client.GDClient.delete(self, entry_or_uri, auth_token=auth_token, **kwargs) # The Blogger API does not currently support ETags, so for now remove # the ETag before performing a delete. old_etag = entry_or_uri.etag entry_or_uri.etag = None response = gdata.client.GDClient.delete(self, entry_or_uri, auth_token=auth_token, **kwargs) # TODO: if GDClient.delete raises and exception, the entry's etag may be # left as None. Should revisit this logic. entry_or_uri.etag = old_etag return response Delete = delete class Query(gdata.client.Query): def __init__(self, order_by=None, **kwargs): gdata.client.Query.__init__(self, **kwargs) self.order_by = order_by def modify_request(self, http_request): gdata.client._add_query_param('orderby', self.order_by, http_request) gdata.client.Query.modify_request(self, http_request) ModifyRequest = modify_request gdata-2.0.18/src/gdata/blogger/service.py0000640036466300116100000001227512156622363020247 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright (C) 2007 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. """Classes to interact with the Blogger server.""" __author__ = 'api.jscudder (Jeffrey Scudder)' import gdata.service import gdata.blogger class BloggerService(gdata.service.GDataService): def __init__(self, email=None, password=None, source=None, server='www.blogger.com', **kwargs): """Creates a client for the Blogger service. Args: email: string (optional) The user's email address, used for authentication. password: string (optional) The user's password. source: string (optional) The name of the user's application. server: string (optional) The name of the server to which a connection will be opened. Default value: 'www.blogger.com'. **kwargs: The other parameters to pass to gdata.service.GDataService constructor. """ gdata.service.GDataService.__init__( self, email=email, password=password, service='blogger', source=source, server=server, **kwargs) def GetBlogFeed(self, uri=None): """Retrieve a list of the blogs to which the current user may manage.""" if not uri: uri = '/feeds/default/blogs' return self.Get(uri, converter=gdata.blogger.BlogFeedFromString) def GetBlogCommentFeed(self, blog_id=None, uri=None): """Retrieve a list of the comments for this blog.""" if blog_id: uri = '/feeds/%s/comments/default' % blog_id return self.Get(uri, converter=gdata.blogger.CommentFeedFromString) def GetBlogPostFeed(self, blog_id=None, uri=None): if blog_id: uri = '/feeds/%s/posts/default' % blog_id return self.Get(uri, converter=gdata.blogger.BlogPostFeedFromString) def GetPostCommentFeed(self, blog_id=None, post_id=None, uri=None): """Retrieve a list of the comments for this particular blog post.""" if blog_id and post_id: uri = '/feeds/%s/%s/comments/default' % (blog_id, post_id) return self.Get(uri, converter=gdata.blogger.CommentFeedFromString) def AddPost(self, entry, blog_id=None, uri=None): if blog_id: uri = '/feeds/%s/posts/default' % blog_id return self.Post(entry, uri, converter=gdata.blogger.BlogPostEntryFromString) def UpdatePost(self, entry, uri=None): if not uri: uri = entry.GetEditLink().href return self.Put(entry, uri, converter=gdata.blogger.BlogPostEntryFromString) def DeletePost(self, entry=None, uri=None): if not uri: uri = entry.GetEditLink().href return self.Delete(uri) def AddComment(self, comment_entry, blog_id=None, post_id=None, uri=None): """Adds a new comment to the specified blog post.""" if blog_id and post_id: uri = '/feeds/%s/%s/comments/default' % (blog_id, post_id) return self.Post(comment_entry, uri, converter=gdata.blogger.CommentEntryFromString) def DeleteComment(self, entry=None, uri=None): if not uri: uri = entry.GetEditLink().href return self.Delete(uri) class BlogQuery(gdata.service.Query): def __init__(self, feed=None, params=None, categories=None, blog_id=None): """Constructs a query object for the list of a user's Blogger blogs. Args: feed: str (optional) The beginning of the URL to be queried. If the feed is not set, and there is no blog_id passed in, the default value is used ('/feeds/default/blogs'). params: dict (optional) categories: list (optional) blog_id: str (optional) """ if not feed and blog_id: feed = '/feeds/default/blogs/%s' % blog_id elif not feed: feed = '/feeds/default/blogs' gdata.service.Query.__init__(self, feed=feed, params=params, categories=categories) class BlogPostQuery(gdata.service.Query): def __init__(self, feed=None, params=None, categories=None, blog_id=None, post_id=None): if not feed and blog_id and post_id: feed = '/feeds/%s/posts/default/%s' % (blog_id, post_id) elif not feed and blog_id: feed = '/feeds/%s/posts/default' % blog_id gdata.service.Query.__init__(self, feed=feed, params=params, categories=categories) class BlogCommentQuery(gdata.service.Query): def __init__(self, feed=None, params=None, categories=None, blog_id=None, post_id=None, comment_id=None): if not feed and blog_id and comment_id: feed = '/feeds/%s/comments/default/%s' % (blog_id, comment_id) elif not feed and blog_id and post_id: feed = '/feeds/%s/%s/comments/default' % (blog_id, post_id) elif not feed and blog_id: feed = '/feeds/%s/comments/default' % blog_id gdata.service.Query.__init__(self, feed=feed, params=params, categories=categories) gdata-2.0.18/src/gdata/analytics/0000750036466300116100000000000012156625015016570 5ustar afshareng00000000000000gdata-2.0.18/src/gdata/analytics/data.py0000750036466300116100000002213512156622363020064 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright 2010 Google Inc. All Rights Reserved. # # 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. """Data model classes for parsing and generating XML for both the Google Analytics Data Export and Management APIs. Although both APIs operate on different parts of Google Analytics, they share common XML elements and are released in the same module. The Management API supports 5 feeds all using the same ManagementFeed data class. """ __author__ = 'api.nickm@google.com (Nick Mihailovski)' import gdata.data import atom.core import atom.data # XML Namespace used in Google Analytics API entities. DXP_NS = '{http://schemas.google.com/analytics/2009}%s' GA_NS = '{http://schemas.google.com/ga/2009}%s' GD_NS = '{http://schemas.google.com/g/2005}%s' class GetProperty(object): """Utility class to simplify retrieving Property objects.""" def get_property(self, name): """Helper method to return a propery object by its name attribute. Args: name: string The name of the element to retrieve. Returns: A property object corresponding to the matching element. if no property is found, None is returned. """ for prop in self.property: if prop.name == name: return prop return None GetProperty = get_property class GetMetric(object): """Utility class to simplify retrieving Metric objects.""" def get_metric(self, name): """Helper method to return a propery value by its name attribute Args: name: string The name of the element to retrieve. Returns: A property object corresponding to the matching element. if no property is found, None is returned. """ for met in self.metric: if met.name == name: return met return None GetMetric = get_metric class GetDimension(object): """Utility class to simplify retrieving Dimension objects.""" def get_dimension(self, name): """Helper method to return a dimention object by its name attribute Args: name: string The name of the element to retrieve. Returns: A dimension object corresponding to the matching element. if no dimension is found, None is returned. """ for dim in self.dimension: if dim.name == name: return dim return None GetDimension = get_dimension class GaLinkFinder(object): """Utility class to return specific links in Google Analytics feeds.""" def get_parent_links(self): """Returns a list of all the parent links in an entry.""" links = [] for link in self.link: if link.rel == link.parent(): links.append(link) return links GetParentLinks = get_parent_links def get_child_links(self): """Returns a list of all the child links in an entry.""" links = [] for link in self.link: if link.rel == link.child(): links.append(link) return links GetChildLinks = get_child_links def get_child_link(self, target_kind): """Utility method to return one child link. Returns: A child link with the given target_kind. None if the target_kind was not found. """ for link in self.link: if link.rel == link.child() and link.target_kind == target_kind: return link return None GetChildLink = get_child_link class StartDate(atom.core.XmlElement): """Analytics Feed """ _qname = DXP_NS % 'startDate' class EndDate(atom.core.XmlElement): """Analytics Feed """ _qname = DXP_NS % 'endDate' class Metric(atom.core.XmlElement): """Analytics Feed """ _qname = DXP_NS % 'metric' name = 'name' type = 'type' value = 'value' confidence_interval = 'confidenceInterval' class Aggregates(atom.core.XmlElement, GetMetric): """Analytics Data Feed """ _qname = DXP_NS % 'aggregates' metric = [Metric] class ContainsSampledData(atom.core.XmlElement): """Analytics Data Feed """ _qname = DXP_NS % 'containsSampledData' class TableId(atom.core.XmlElement): """Analytics Feed """ _qname = DXP_NS % 'tableId' class TableName(atom.core.XmlElement): """Analytics Feed """ _qname = DXP_NS % 'tableName' class Property(atom.core.XmlElement): """Analytics Feed """ _qname = DXP_NS % 'property' name = 'name' value = 'value' class Definition(atom.core.XmlElement): """Analytics Feed """ _qname = DXP_NS % 'definition' class Segment(atom.core.XmlElement): """Analytics Feed """ _qname = DXP_NS % 'segment' id = 'id' name = 'name' definition = Definition class Engagement(atom.core.XmlElement): """Analytics Feed """ _qname = GA_NS % 'engagement' type = 'type' comparison = 'comparison' threshold_value = 'thresholdValue' class Step(atom.core.XmlElement): """Analytics Feed """ _qname = GA_NS % 'step' number = 'number' name = 'name' path = 'path' class Destination(atom.core.XmlElement): """Analytics Feed """ _qname = GA_NS % 'destination' step = [Step] expression = 'expression' case_sensitive = 'caseSensitive' match_type = 'matchType' step1_required = 'step1Required' class Goal(atom.core.XmlElement): """Analytics Feed """ _qname = GA_NS % 'goal' destination = Destination engagement = Engagement number = 'number' name = 'name' value = 'value' active = 'active' class CustomVariable(atom.core.XmlElement): """Analytics Data Feed """ _qname = GA_NS % 'customVariable' index = 'index' name = 'name' scope = 'scope' class DataSource(atom.core.XmlElement, GetProperty): """Analytics Data Feed """ _qname = DXP_NS % 'dataSource' table_id = TableId table_name = TableName property = [Property] class Dimension(atom.core.XmlElement): """Analytics Feed """ _qname = DXP_NS % 'dimension' name = 'name' value = 'value' class AnalyticsLink(atom.data.Link): """Subclass of link """ target_kind = GD_NS % 'targetKind' @classmethod def parent(cls): """Parent target_kind""" return '%s#parent' % GA_NS[1:-3] @classmethod def child(cls): """Child target_kind""" return '%s#child' % GA_NS[1:-3] # Account Feed. class AccountEntry(gdata.data.GDEntry, GetProperty): """Analytics Account Feed """ _qname = atom.data.ATOM_TEMPLATE % 'entry' table_id = TableId property = [Property] goal = [Goal] custom_variable = [CustomVariable] class AccountFeed(gdata.data.GDFeed): """Analytics Account Feed """ _qname = atom.data.ATOM_TEMPLATE % 'feed' segment = [Segment] entry = [AccountEntry] # Data Feed. class DataEntry(gdata.data.GDEntry, GetMetric, GetDimension): """Analytics Data Feed """ _qname = atom.data.ATOM_TEMPLATE % 'entry' dimension = [Dimension] metric = [Metric] def get_object(self, name): """Returns either a Dimension or Metric object with the same name as the name parameter. Args: name: string The name of the object to retrieve. Returns: Either a Dimension or Object that has the same as the name parameter. """ output = self.GetDimension(name) if not output: output = self.GetMetric(name) return output GetObject = get_object class DataFeed(gdata.data.GDFeed): """Analytics Data Feed . Although there is only one datasource, it is stored in an array to replicate the design of the Java client library and ensure backwards compatibility if new data sources are added in the future. """ _qname = atom.data.ATOM_TEMPLATE % 'feed' start_date = StartDate end_date = EndDate aggregates = Aggregates contains_sampled_data = ContainsSampledData data_source = [DataSource] entry = [DataEntry] segment = Segment def has_sampled_data(self): """Returns whether this feed has sampled data.""" if (self.contains_sampled_data.text == 'true'): return True return False HasSampledData = has_sampled_data # Management Feed. class ManagementEntry(gdata.data.GDEntry, GetProperty, GaLinkFinder): """Analytics Managememt Entry .""" _qname = atom.data.ATOM_TEMPLATE % 'entry' kind = GD_NS % 'kind' property = [Property] goal = Goal segment = Segment link = [AnalyticsLink] class ManagementFeed(gdata.data.GDFeed): """Analytics Management Feed . This class holds the data for all 5 Management API feeds: Account, Web Property, Profile, Goal, and Advanced Segment Feeds. """ _qname = atom.data.ATOM_TEMPLATE % 'feed' entry = [ManagementEntry] kind = GD_NS % 'kind' gdata-2.0.18/src/gdata/analytics/__init__.py0000640036466300116100000001552312156622363020713 0ustar afshareng00000000000000#!/usr/bin/python # # Original Copyright (C) 2006 Google Inc. # Refactored in 2009 to work for Google Analytics by Sal Uryasev at Juice 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. # # Note that this module will not function without specifically adding # 'analytics': [ #Google Analytics # 'https://www.google.com/analytics/feeds/'], # to CLIENT_LOGIN_SCOPES in the gdata/service.py file """Contains extensions to Atom objects used with Google Analytics.""" __author__ = 'api.suryasev (Sal Uryasev)' import atom import gdata GAN_NAMESPACE = 'http://schemas.google.com/analytics/2009' class TableId(gdata.GDataEntry): """tableId element.""" _tag = 'tableId' _namespace = GAN_NAMESPACE class Property(gdata.GDataEntry): _tag = 'property' _namespace = GAN_NAMESPACE _children = gdata.GDataEntry._children.copy() _attributes = gdata.GDataEntry._attributes.copy() _attributes['name'] = 'name' _attributes['value'] = 'value' def __init__(self, name=None, value=None, *args, **kwargs): self.name = name self.value = value super(Property, self).__init__(*args, **kwargs) def __str__(self): return self.value def __repr__(self): return self.value class AccountListEntry(gdata.GDataEntry): """The Google Documents version of an Atom Entry""" _tag = 'entry' _namespace = atom.ATOM_NAMESPACE _children = gdata.GDataEntry._children.copy() _attributes = gdata.GDataEntry._attributes.copy() _children['{%s}tableId' % GAN_NAMESPACE] = ('tableId', [TableId]) _children['{%s}property' % GAN_NAMESPACE] = ('property', [Property]) def __init__(self, tableId=None, property=None, *args, **kwargs): self.tableId = tableId self.property = property super(AccountListEntry, self).__init__(*args, **kwargs) def AccountListEntryFromString(xml_string): """Converts an XML string into an AccountListEntry object. Args: xml_string: string The XML describing a Document List feed entry. Returns: A AccountListEntry object corresponding to the given XML. """ return atom.CreateClassFromXMLString(AccountListEntry, xml_string) class AccountListFeed(gdata.GDataFeed): """A feed containing a list of Google Documents Items""" _tag = 'feed' _namespace = atom.ATOM_NAMESPACE _children = gdata.GDataFeed._children.copy() _attributes = gdata.GDataFeed._attributes.copy() _children['{%s}entry' % atom.ATOM_NAMESPACE] = ('entry', [AccountListEntry]) def AccountListFeedFromString(xml_string): """Converts an XML string into an AccountListFeed object. Args: xml_string: string The XML describing an AccountList feed. Returns: An AccountListFeed object corresponding to the given XML. All properties are also linked to with a direct reference from each entry object for convenience. (e.g. entry.AccountName) """ feed = atom.CreateClassFromXMLString(AccountListFeed, xml_string) for entry in feed.entry: for pro in entry.property: entry.__dict__[pro.name.replace('ga:','')] = pro for td in entry.tableId: td.__dict__['value'] = td.text return feed class Dimension(gdata.GDataEntry): _tag = 'dimension' _namespace = GAN_NAMESPACE _children = gdata.GDataEntry._children.copy() _attributes = gdata.GDataEntry._attributes.copy() _attributes['name'] = 'name' _attributes['value'] = 'value' _attributes['type'] = 'type' _attributes['confidenceInterval'] = 'confidence_interval' def __init__(self, name=None, value=None, type=None, confidence_interval = None, *args, **kwargs): self.name = name self.value = value self.type = type self.confidence_interval = confidence_interval super(Dimension, self).__init__(*args, **kwargs) def __str__(self): return self.value def __repr__(self): return self.value class Metric(gdata.GDataEntry): _tag = 'metric' _namespace = GAN_NAMESPACE _children = gdata.GDataEntry._children.copy() _attributes = gdata.GDataEntry._attributes.copy() _attributes['name'] = 'name' _attributes['value'] = 'value' _attributes['type'] = 'type' _attributes['confidenceInterval'] = 'confidence_interval' def __init__(self, name=None, value=None, type=None, confidence_interval = None, *args, **kwargs): self.name = name self.value = value self.type = type self.confidence_interval = confidence_interval super(Metric, self).__init__(*args, **kwargs) def __str__(self): return self.value def __repr__(self): return self.value class AnalyticsDataEntry(gdata.GDataEntry): """The Google Analytics version of an Atom Entry""" _tag = 'entry' _namespace = atom.ATOM_NAMESPACE _children = gdata.GDataEntry._children.copy() _attributes = gdata.GDataEntry._attributes.copy() _children['{%s}dimension' % GAN_NAMESPACE] = ('dimension', [Dimension]) _children['{%s}metric' % GAN_NAMESPACE] = ('metric', [Metric]) def __init__(self, dimension=None, metric=None, *args, **kwargs): self.dimension = dimension self.metric = metric super(AnalyticsDataEntry, self).__init__(*args, **kwargs) class AnalyticsDataFeed(gdata.GDataFeed): """A feed containing a list of Google Analytics Data Feed""" _tag = 'feed' _namespace = atom.ATOM_NAMESPACE _children = gdata.GDataFeed._children.copy() _attributes = gdata.GDataFeed._attributes.copy() _children['{%s}entry' % atom.ATOM_NAMESPACE] = ('entry', [AnalyticsDataEntry]) """ Data Feed """ def AnalyticsDataFeedFromString(xml_string): """Converts an XML string into an AccountListFeed object. Args: xml_string: string The XML describing an AccountList feed. Returns: An AccountListFeed object corresponding to the given XML. Each metric and dimension is also referenced directly from the entry for easier access. (e.g. entry.keyword.value) """ feed = atom.CreateClassFromXMLString(AnalyticsDataFeed, xml_string) if feed.entry: for entry in feed.entry: for met in entry.metric: entry.__dict__[met.name.replace('ga:','')] = met if entry.dimension is not None: for dim in entry.dimension: entry.__dict__[dim.name.replace('ga:','')] = dim return feed gdata-2.0.18/src/gdata/analytics/client.py0000750036466300116100000002317512156622363020436 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright 2010 Google Inc. All Rights Reserved. # # 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. """Streamlines requests to the Google Analytics APIs.""" __author__ = 'api.nickm@google.com (Nick Mihailovski)' import atom.data import gdata.client import gdata.analytics.data import gdata.gauth class AnalyticsClient(gdata.client.GDClient): """Client extension for the Google Analytics API service.""" api_version = '2' auth_service = 'analytics' auth_scopes = gdata.gauth.AUTH_SCOPES['analytics'] account_type = 'GOOGLE' ssl = True def __init__(self, auth_token=None, **kwargs): """Initializes a new client for the Google Analytics Data Export API. Args: auth_token: gdata.gauth.ClientLoginToken, AuthSubToken, or OAuthToken (optional) Authorizes this client to edit the user's data. kwargs: The other parameters to pass to gdata.client.GDClient constructor. """ gdata.client.GDClient.__init__(self, auth_token=auth_token, **kwargs) def get_account_feed(self, feed_uri, auth_token=None, **kwargs): """Makes a request to the Analytics API Account Feed. Args: feed_uri: str or gdata.analytics.AccountFeedQuery The Analytics Account Feed uri to define what data to retrieve from the API. Can also be used with a gdata.analytics.AccountFeedQuery object. """ return self.get_feed(feed_uri, desired_class=gdata.analytics.data.AccountFeed, auth_token=auth_token, **kwargs) GetAccountFeed = get_account_feed def get_data_feed(self, feed_uri, auth_token=None, **kwargs): """Makes a request to the Analytics API Data Feed. Args: feed_uri: str or gdata.analytics.AccountFeedQuery The Analytics Data Feed uri to define what data to retrieve from the API. Can also be used with a gdata.analytics.AccountFeedQuery object. """ return self.get_feed(feed_uri, desired_class=gdata.analytics.data.DataFeed, auth_token=auth_token, **kwargs) GetDataFeed = get_data_feed def get_management_feed(self, feed_uri, auth_token=None, **kwargs): """Makes a request to the Google Analytics Management API. The Management API provides read-only access to configuration data for Google Analytics and supercedes the Data Export API Account Feed. The Management API supports 5 feeds: account, web property, profile, goal, advanced segment. You can access each feed through the respective management query class below. All requests return the same data object. Args: feed_uri: str or AccountQuery, WebPropertyQuery, ProfileQuery, GoalQuery, MgmtAdvSegFeedQuery The Management API Feed uri to define which feed to retrieve. Either use a string or one of the wrapper classes. """ return self.get_feed(feed_uri, desired_class=gdata.analytics.data.ManagementFeed, auth_token=auth_token, **kwargs) GetMgmtFeed = GetManagementFeed = get_management_feed class AnalyticsBaseQuery(gdata.client.GDQuery): """Abstracts common configuration across all query objects. Attributes: scheme: string The default scheme. Should always be https. host: string The default host. """ scheme = 'https' host = 'www.google.com' class AccountFeedQuery(AnalyticsBaseQuery): """Account Feed query class to simplify constructing Account Feed Urls. To use this class, you can either pass a dict in the constructor that has all the data feed query parameters as keys: queryUrl = AccountFeedQuery({'max-results': '10000'}) Alternatively you can add new parameters directly to the query object: queryUrl = AccountFeedQuery() queryUrl.query['max-results'] = '10000' Args: query: dict (optional) Contains all the GA Data Feed query parameters as keys. """ path = '/analytics/feeds/accounts/default' def __init__(self, query={}, **kwargs): self.query = query gdata.client.GDQuery(self, **kwargs) class DataFeedQuery(AnalyticsBaseQuery): """Data Feed query class to simplify constructing Data Feed Urls. To use this class, you can either pass a dict in the constructor that has all the data feed query parameters as keys: queryUrl = DataFeedQuery({'start-date': '2008-10-01'}) Alternatively you can add new parameters directly to the query object: queryUrl = DataFeedQuery() queryUrl.query['start-date'] = '2008-10-01' Args: query: dict (optional) Contains all the GA Data Feed query parameters as keys. """ path = '/analytics/feeds/data' def __init__(self, query={}, **kwargs): self.query = query gdata.client.GDQuery(self, **kwargs) class AccountQuery(AnalyticsBaseQuery): """Management API Account Feed query class. Example Usage: queryUrl = AccountQuery() queryUrl = AccountQuery({'max-results': 100}) queryUrl2 = AccountQuery() queryUrl2.query['max-results'] = 100 Args: query: dict (optional) A dictionary of query parameters. """ path = '/analytics/feeds/datasources/ga/accounts' def __init__(self, query={}, **kwargs): self.query = query gdata.client.GDQuery(self, **kwargs) class WebPropertyQuery(AnalyticsBaseQuery): """Management API Web Property Feed query class. Example Usage: queryUrl = WebPropertyQuery() queryUrl = WebPropertyQuery('123', {'max-results': 100}) queryUrl = WebPropertyQuery(acct_id='123', query={'max-results': 100}) queryUrl2 = WebPropertyQuery() queryUrl2.acct_id = '1234' queryUrl2.query['max-results'] = 100 Args: acct_id: string (optional) The account ID to filter results. Default is ~all. query: dict (optional) A dictionary of query parameters. """ def __init__(self, acct_id='~all', query={}, **kwargs): self.acct_id = acct_id self.query = query gdata.client.GDQuery(self, **kwargs) @property def path(self): """Wrapper for path attribute.""" return ('/analytics/feeds/datasources/ga/accounts/%s/webproperties' % self.acct_id) class ProfileQuery(AnalyticsBaseQuery): """Management API Profile Feed query class. Example Usage: queryUrl = ProfileQuery() queryUrl = ProfileQuery('123', 'UA-123-1', {'max-results': 100}) queryUrl = ProfileQuery(acct_id='123', web_prop_id='UA-123-1', query={'max-results': 100}) queryUrl2 = ProfileQuery() queryUrl2.acct_id = '123' queryUrl2.web_prop_id = 'UA-123-1' queryUrl2.query['max-results'] = 100 Args: acct_id: string (optional) The account ID to filter results. Default is ~all. web_prop_id: string (optional) The web property ID to filter results. Default is ~all. query: dict (optional) A dictionary of query parameters. """ def __init__(self, acct_id='~all', web_prop_id='~all', query={}, **kwargs): self.acct_id = acct_id self.web_prop_id = web_prop_id self.query = query gdata.client.GDQuery(self, **kwargs) @property def path(self): """Wrapper for path attribute.""" return ('/analytics/feeds/datasources/ga/accounts/%s/webproperties' '/%s/profiles' % (self.acct_id, self.web_prop_id)) class GoalQuery(AnalyticsBaseQuery): """Management API Goal Feed query class. Example Usage: queryUrl = GoalQuery() queryUrl = GoalQuery('123', 'UA-123-1', '555', {'max-results': 100}) queryUrl = GoalQuery(acct_id='123', web_prop_id='UA-123-1', profile_id='555', query={'max-results': 100}) queryUrl2 = GoalQuery() queryUrl2.acct_id = '123' queryUrl2.web_prop_id = 'UA-123-1' queryUrl2.query['max-results'] = 100 Args: acct_id: string (optional) The account ID to filter results. Default is ~all. web_prop_id: string (optional) The web property ID to filter results. Default is ~all. profile_id: string (optional) The profile ID to filter results. Default is ~all. query: dict (optional) A dictionary of query parameters. """ def __init__(self, acct_id='~all', web_prop_id='~all', profile_id='~all', query={}, **kwargs): self.acct_id = acct_id self.web_prop_id = web_prop_id self.profile_id = profile_id self.query = query or {} gdata.client.GDQuery(self, **kwargs) @property def path(self): """Wrapper for path attribute.""" return ('/analytics/feeds/datasources/ga/accounts/%s/webproperties' '/%s/profiles/%s/goals' % (self.acct_id, self.web_prop_id, self.profile_id)) class AdvSegQuery(AnalyticsBaseQuery): """Management API Goal Feed query class. Example Usage: queryUrl = AdvSegQuery() queryUrl = AdvSegQuery({'max-results': 100}) queryUrl1 = AdvSegQuery() queryUrl1.query['max-results'] = 100 Args: query: dict (optional) A dictionary of query parameters. """ path = '/analytics/feeds/datasources/ga/segments' def __init__(self, query={}, **kwargs): self.query = query gdata.client.GDQuery(self, **kwargs) gdata-2.0.18/src/gdata/analytics/service.py0000640036466300116100000003175512156622363020621 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright (C) 2006 Google Inc. # Refactored in 2009 to work for Google Analytics by Sal Uryasev at Juice 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. """ AccountsService extends the GDataService to streamline Google Analytics account information operations. AnalyticsDataService: Provides methods to query google analytics data feeds. Extends GDataService. DataQuery: Queries a Google Analytics Data list feed. AccountQuery: Queries a Google Analytics Account list feed. """ __author__ = 'api.suryasev (Sal Uryasev)' import urllib import atom import gdata.service import gdata.analytics class AccountsService(gdata.service.GDataService): """Client extension for the Google Analytics Account List feed.""" def __init__(self, email="", password=None, source=None, server='www.google.com/analytics', additional_headers=None, **kwargs): """Creates a client for the Google Analytics service. Args: email: string (optional) The user's email address, used for authentication. password: string (optional) The user's password. source: string (optional) The name of the user's application. server: string (optional) The name of the server to which a connection will be opened. **kwargs: The other parameters to pass to gdata.service.GDataService constructor. """ gdata.service.GDataService.__init__( self, email=email, password=password, service='analytics', source=source, server=server, additional_headers=additional_headers, **kwargs) def QueryAccountListFeed(self, uri): """Retrieves an AccountListFeed by retrieving a URI based off the Document List feed, including any query parameters. An AccountListFeed object can be used to construct these parameters. Args: uri: string The URI of the feed being retrieved possibly with query parameters. Returns: An AccountListFeed object representing the feed returned by the server. """ return self.Get(uri, converter=gdata.analytics.AccountListFeedFromString) def GetAccountListEntry(self, uri): """Retrieves a particular AccountListEntry by its unique URI. Args: uri: string The unique URI of an entry in an Account List feed. Returns: An AccountLisFeed object representing the retrieved entry. """ return self.Get(uri, converter=gdata.analytics.AccountListEntryFromString) def GetAccountList(self, max_results=1000, text_query=None, params=None, categories=None): """Retrieves a feed containing all of a user's accounts and profiles.""" q = gdata.analytics.service.AccountQuery(max_results=max_results, text_query=text_query, params=params, categories=categories); return self.QueryAccountListFeed(q.ToUri()) class AnalyticsDataService(gdata.service.GDataService): """Client extension for the Google Analytics service Data List feed.""" def __init__(self, email=None, password=None, source=None, server='www.google.com/analytics', additional_headers=None, **kwargs): """Creates a client for the Google Analytics service. Args: email: string (optional) The user's email address, used for authentication. password: string (optional) The user's password. source: string (optional) The name of the user's application. server: string (optional) The name of the server to which a connection will be opened. Default value: 'docs.google.com'. **kwargs: The other parameters to pass to gdata.service.GDataService constructor. """ gdata.service.GDataService.__init__(self, email=email, password=password, service='analytics', source=source, server=server, additional_headers=additional_headers, **kwargs) def GetData(self, ids='', dimensions='', metrics='', sort='', filters='', start_date='', end_date='', start_index='', max_results=''): """Retrieves a feed containing a user's data ids: comma-separated string of analytics accounts. dimensions: comma-separated string of dimensions. metrics: comma-separated string of metrics. sort: comma-separated string of dimensions and metrics for sorting. This may be previxed with a minus to sort in reverse order. (e.g. '-ga:keyword') If ommited, the first dimension passed in will be used. filters: comma-separated string of filter parameters. (e.g. 'ga:keyword==google') start_date: start date for data pull. end_date: end date for data pull. start_index: used in combination with max_results to pull more than 1000 entries. This defaults to 1. max_results: maximum results that the pull will return. This defaults to, and maxes out at 1000. """ q = gdata.analytics.service.DataQuery(ids=ids, dimensions=dimensions, metrics=metrics, filters=filters, sort=sort, start_date=start_date, end_date=end_date, start_index=start_index, max_results=max_results); return self.AnalyticsDataFeed(q.ToUri()) def AnalyticsDataFeed(self, uri): """Retrieves an AnalyticsListFeed by retrieving a URI based off the Document List feed, including any query parameters. An AnalyticsListFeed object can be used to construct these parameters. Args: uri: string The URI of the feed being retrieved possibly with query parameters. Returns: An AnalyticsListFeed object representing the feed returned by the server. """ return self.Get(uri, converter=gdata.analytics.AnalyticsDataFeedFromString) """ Account Fetching """ def QueryAccountListFeed(self, uri): """Retrieves an Account ListFeed by retrieving a URI based off the Account List feed, including any query parameters. A AccountQuery object can be used to construct these parameters. Args: uri: string The URI of the feed being retrieved possibly with query parameters. Returns: An AccountListFeed object representing the feed returned by the server. """ return self.Get(uri, converter=gdata.analytics.AccountListFeedFromString) def GetAccountListEntry(self, uri): """Retrieves a particular AccountListEntry by its unique URI. Args: uri: string The unique URI of an entry in an Account List feed. Returns: An AccountListEntry object representing the retrieved entry. """ return self.Get(uri, converter=gdata.analytics.AccountListEntryFromString) def GetAccountList(self, username="default", max_results=1000, start_index=1): """Retrieves a feed containing all of a user's accounts and profiles. The username parameter is soon to be deprecated, with 'default' becoming the only allowed parameter. """ if not username: raise Exception("username is a required parameter") q = gdata.analytics.service.AccountQuery(username=username, max_results=max_results, start_index=start_index); return self.QueryAccountListFeed(q.ToUri()) class DataQuery(gdata.service.Query): """Object used to construct a URI to a data feed""" def __init__(self, feed='/feeds/data', text_query=None, params=None, categories=None, ids="", dimensions="", metrics="", sort="", filters="", start_date="", end_date="", start_index="", max_results=""): """Constructor for Analytics List Query Args: feed: string (optional) The path for the feed. (e.g. '/feeds/data') text_query: string (optional) The contents of the q query parameter. This string is URL escaped upon conversion to a URI. params: dict (optional) Parameter value string pairs which become URL params when translated to a URI. These parameters are added to the query's items. categories: list (optional) List of category strings which should be included as query categories. See gdata.service.Query for additional documentation. ids: comma-separated string of analytics accounts. dimensions: comma-separated string of dimensions. metrics: comma-separated string of metrics. sort: comma-separated string of dimensions and metrics. This may be previxed with a minus to sort in reverse order (e.g. '-ga:keyword'). If ommited, the first dimension passed in will be used. filters: comma-separated string of filter parameters (e.g. 'ga:keyword==google'). start_date: start date for data pull. end_date: end date for data pull. start_index: used in combination with max_results to pull more than 1000 entries. This defaults to 1. max_results: maximum results that the pull will return. This defaults to, and maxes out at 1000. Yields: A DocumentQuery object used to construct a URI based on the Document List feed. """ self.elements = {'ids': ids, 'dimensions': dimensions, 'metrics': metrics, 'sort': sort, 'filters': filters, 'start-date': start_date, 'end-date': end_date, 'start-index': start_index, 'max-results': max_results} gdata.service.Query.__init__(self, feed, text_query, params, categories) def ToUri(self): """Generates a URI from the query parameters set in the object. Returns: A string containing the URI used to retrieve entries from the Analytics List feed. """ old_feed = self.feed self.feed = '/'.join([old_feed]) + '?' + \ urllib.urlencode(dict([(key, value) for key, value in \ self.elements.iteritems() if value])) new_feed = gdata.service.Query.ToUri(self) self.feed = old_feed return new_feed class AccountQuery(gdata.service.Query): """Object used to construct a URI to query the Google Account List feed""" def __init__(self, feed='/feeds/accounts', start_index=1, max_results=1000, username='default', text_query=None, params=None, categories=None): """Constructor for Account List Query Args: feed: string (optional) The path for the feed. (e.g. '/feeds/documents') visibility: string (optional) The visibility chosen for the current feed. projection: string (optional) The projection chosen for the current feed. text_query: string (optional) The contents of the q query parameter. This string is URL escaped upon conversion to a URI. params: dict (optional) Parameter value string pairs which become URL params when translated to a URI. These parameters are added to the query's items. categories: list (optional) List of category strings which should be included as query categories. See gdata.service.Query for additional documentation. username: string (deprecated) This value should now always be passed as 'default'. Yields: A DocumentQuery object used to construct a URI based on the Document List feed. """ self.max_results = max_results self.start_index = start_index self.username = username gdata.service.Query.__init__(self, feed, text_query, params, categories) def ToUri(self): """Generates a URI from the query parameters set in the object. Returns: A string containing the URI used to retrieve entries from the Account List feed. """ old_feed = self.feed self.feed = '/'.join([old_feed, self.username]) + '?' + \ '&'.join(['max-results=' + str(self.max_results), 'start-index=' + str(self.start_index)]) new_feed = self.feed self.feed = old_feed return new_feed gdata-2.0.18/src/gdata/apps_property.py0000640036466300116100000000214112156622363020064 0ustar afshareng00000000000000#!/usr/bin/env python # # 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. # This module is used for version 2 of the Google Data APIs. """Provides a base class to represent property elements in feeds. This module is used for version 2 of the Google Data APIs. The primary class in this module is AppsProperty. """ __author__ = 'Vic Fryzel ' import atom.core import gdata.apps class AppsProperty(atom.core.XmlElement): """Represents an element in a feed.""" _qname = gdata.apps.APPS_TEMPLATE % 'property' name = 'name' value = 'value' gdata-2.0.18/src/gdata/alt/0000750036466300116100000000000012156625015015361 5ustar afshareng00000000000000gdata-2.0.18/src/gdata/alt/app_engine.py0000640036466300116100000000646012156622363020052 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright (C) 2009 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. """Provides functions to persist serialized auth tokens in the datastore. The get_token and set_token functions should be used in conjunction with gdata.gauth's token_from_blob and token_to_blob to allow auth token objects to be reused across requests. It is up to your own code to ensure that the token key's are unique. """ __author__ = 'j.s@google.com (Jeff Scudder)' from google.appengine.ext import db from google.appengine.api import memcache class Token(db.Model): """Datastore Model which stores a serialized auth token.""" t = db.BlobProperty() def get_token(unique_key): """Searches for a stored token with the desired key. Checks memcache and then the datastore if required. Args: unique_key: str which uniquely identifies the desired auth token. Returns: A string encoding the auth token data. Use gdata.gauth.token_from_blob to convert back into a usable token object. None if the token was not found in memcache or the datastore. """ token_string = memcache.get(unique_key) if token_string is None: # The token wasn't in memcache, so look in the datastore. token = Token.get_by_key_name(unique_key) if token is None: return None return token.t return token_string def set_token(unique_key, token_str): """Saves the serialized auth token in the datastore. The token is also stored in memcache to speed up retrieval on a cache hit. Args: unique_key: The unique name for this token as a string. It is up to your code to ensure that this token value is unique in your application. Previous values will be silently overwitten. token_str: A serialized auth token as a string. I expect that this string will be generated by gdata.gauth.token_to_blob. Returns: True if the token was stored sucessfully, False if the token could not be safely cached (if an old value could not be cleared). If the token was set in memcache, but not in the datastore, this function will return None. However, in that situation an exception will likely be raised. Raises: Datastore exceptions may be raised from the App Engine SDK in the event of failure. """ # First try to save in memcache. result = memcache.set(unique_key, token_str) # If memcache fails to save the value, clear the cached value. if not result: result = memcache.delete(unique_key) # If we could not clear the cached value for this token, refuse to save. if result == 0: return False # Save to the datastore. if Token(key_name=unique_key, t=token_str).put(): return True return None def delete_token(unique_key): # Clear from memcache. memcache.delete(unique_key) # Clear from the datastore. Token(key_name=unique_key).delete() gdata-2.0.18/src/gdata/alt/__init__.py0000640036466300116100000000147312156622363017503 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright (C) 2008 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. """This package's modules adapt the gdata library to run in other environments The first example is the appengine module which contains functions and classes which modify a GDataService object to run on Google App Engine. """ gdata-2.0.18/src/gdata/alt/appengine.py0000640036466300116100000002662212156622363017715 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright (C) 2008 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. """Provides HTTP functions for gdata.service to use on Google App Engine AppEngineHttpClient: Provides an HTTP request method which uses App Engine's urlfetch API. Set the http_client member of a GDataService object to an instance of an AppEngineHttpClient to allow the gdata library to run on Google App Engine. run_on_appengine: Function which will modify an existing GDataService object to allow it to run on App Engine. It works by creating a new instance of the AppEngineHttpClient and replacing the GDataService object's http_client. """ __author__ = 'api.jscudder (Jeff Scudder)' import StringIO import pickle import atom.http_interface import atom.token_store from google.appengine.api import urlfetch from google.appengine.ext import db from google.appengine.api import users from google.appengine.api import memcache def run_on_appengine(gdata_service, store_tokens=True, single_user_mode=False, deadline=None): """Modifies a GDataService object to allow it to run on App Engine. Args: gdata_service: An instance of AtomService, GDataService, or any of their subclasses which has an http_client member and a token_store member. store_tokens: Boolean, defaults to True. If True, the gdata_service will attempt to add each token to it's token_store when SetClientLoginToken or SetAuthSubToken is called. If False the tokens will not automatically be added to the token_store. single_user_mode: Boolean, defaults to False. If True, the current_token member of gdata_service will be set when SetClientLoginToken or SetAuthTubToken is called. If set to True, the current_token is set in the gdata_service and anyone who accesses the object will use the same token. Note: If store_tokens is set to False and single_user_mode is set to False, all tokens will be ignored, since the library assumes: the tokens should not be stored in the datastore and they should not be stored in the gdata_service object. This will make it impossible to make requests which require authorization. deadline: int (optional) The number of seconds to wait for a response before timing out on the HTTP request. If no deadline is specified, the deafault deadline for HTTP requests from App Engine is used. The maximum is currently 10 (for 10 seconds). The default deadline for App Engine is 5 seconds. """ gdata_service.http_client = AppEngineHttpClient(deadline=deadline) gdata_service.token_store = AppEngineTokenStore() gdata_service.auto_store_tokens = store_tokens gdata_service.auto_set_current_token = single_user_mode return gdata_service class AppEngineHttpClient(atom.http_interface.GenericHttpClient): def __init__(self, headers=None, deadline=None): self.debug = False self.headers = headers or {} self.deadline = deadline def request(self, operation, url, data=None, headers=None): """Performs an HTTP call to the server, supports GET, POST, PUT, and DELETE. Usage example, perform and HTTP GET on http://www.google.com/: import atom.http client = atom.http.HttpClient() http_response = client.request('GET', 'http://www.google.com/') Args: operation: str The HTTP operation to be performed. This is usually one of 'GET', 'POST', 'PUT', or 'DELETE' data: filestream, list of parts, or other object which can be converted to a string. Should be set to None when performing a GET or DELETE. If data is a file-like object which can be read, this method will read a chunk of 100K bytes at a time and send them. If the data is a list of parts to be sent, each part will be evaluated and sent. url: The full URL to which the request should be sent. Can be a string or atom.url.Url. headers: dict of strings. HTTP headers which should be sent in the request. """ all_headers = self.headers.copy() if headers: all_headers.update(headers) # Construct the full payload. # Assume that data is None or a string. data_str = data if data: if isinstance(data, list): # If data is a list of different objects, convert them all to strings # and join them together. converted_parts = [_convert_data_part(x) for x in data] data_str = ''.join(converted_parts) else: data_str = _convert_data_part(data) # If the list of headers does not include a Content-Length, attempt to # calculate it based on the data object. if data and 'Content-Length' not in all_headers: all_headers['Content-Length'] = str(len(data_str)) # Set the content type to the default value if none was set. if 'Content-Type' not in all_headers: all_headers['Content-Type'] = 'application/atom+xml' # Lookup the urlfetch operation which corresponds to the desired HTTP verb. if operation == 'GET': method = urlfetch.GET elif operation == 'POST': method = urlfetch.POST elif operation == 'PUT': method = urlfetch.PUT elif operation == 'DELETE': method = urlfetch.DELETE else: method = None if self.deadline is None: return HttpResponse(urlfetch.Fetch(url=str(url), payload=data_str, method=method, headers=all_headers, follow_redirects=False)) return HttpResponse(urlfetch.Fetch(url=str(url), payload=data_str, method=method, headers=all_headers, follow_redirects=False, deadline=self.deadline)) def _convert_data_part(data): if not data or isinstance(data, str): return data elif hasattr(data, 'read'): # data is a file like object, so read it completely. return data.read() # The data object was not a file. # Try to convert to a string and send the data. return str(data) class HttpResponse(object): """Translates a urlfetch resoinse to look like an hhtplib resoinse. Used to allow the resoinse from HttpRequest to be usable by gdata.service methods. """ def __init__(self, urlfetch_response): self.body = StringIO.StringIO(urlfetch_response.content) self.headers = urlfetch_response.headers self.status = urlfetch_response.status_code self.reason = '' def read(self, length=None): if not length: return self.body.read() else: return self.body.read(length) def getheader(self, name): if not self.headers.has_key(name): return self.headers[name.lower()] return self.headers[name] class TokenCollection(db.Model): """Datastore Model which associates auth tokens with the current user.""" user = db.UserProperty() pickled_tokens = db.BlobProperty() class AppEngineTokenStore(atom.token_store.TokenStore): """Stores the user's auth tokens in the App Engine datastore. Tokens are only written to the datastore if a user is signed in (if users.get_current_user() returns a user object). """ def __init__(self): self.user = None def add_token(self, token): """Associates the token with the current user and stores it. If there is no current user, the token will not be stored. Returns: False if the token was not stored. """ tokens = load_auth_tokens(self.user) if not hasattr(token, 'scopes') or not token.scopes: return False for scope in token.scopes: tokens[str(scope)] = token key = save_auth_tokens(tokens, self.user) if key: return True return False def find_token(self, url): """Searches the current user's collection of token for a token which can be used for a request to the url. Returns: The stored token which belongs to the current user and is valid for the desired URL. If there is no current user, or there is no valid user token in the datastore, a atom.http_interface.GenericToken is returned. """ if url is None: return None if isinstance(url, (str, unicode)): url = atom.url.parse_url(url) tokens = load_auth_tokens(self.user) if url in tokens: token = tokens[url] if token.valid_for_scope(url): return token else: del tokens[url] save_auth_tokens(tokens, self.user) for scope, token in tokens.iteritems(): if token.valid_for_scope(url): return token return atom.http_interface.GenericToken() def remove_token(self, token): """Removes the token from the current user's collection in the datastore. Returns: False if the token was not removed, this could be because the token was not in the datastore, or because there is no current user. """ token_found = False scopes_to_delete = [] tokens = load_auth_tokens(self.user) for scope, stored_token in tokens.iteritems(): if stored_token == token: scopes_to_delete.append(scope) token_found = True for scope in scopes_to_delete: del tokens[scope] if token_found: save_auth_tokens(tokens, self.user) return token_found def remove_all_tokens(self): """Removes all of the current user's tokens from the datastore.""" save_auth_tokens({}, self.user) def save_auth_tokens(token_dict, user=None): """Associates the tokens with the current user and writes to the datastore. If there us no current user, the tokens are not written and this function returns None. Returns: The key of the datastore entity containing the user's tokens, or None if there was no current user. """ if user is None: user = users.get_current_user() if user is None: return None memcache.set('gdata_pickled_tokens:%s' % user, pickle.dumps(token_dict)) user_tokens = TokenCollection.all().filter('user =', user).get() if user_tokens: user_tokens.pickled_tokens = pickle.dumps(token_dict) return user_tokens.put() else: user_tokens = TokenCollection( user=user, pickled_tokens=pickle.dumps(token_dict)) return user_tokens.put() def load_auth_tokens(user=None): """Reads a dictionary of the current user's tokens from the datastore. If there is no current user (a user is not signed in to the app) or the user does not have any tokens, an empty dictionary is returned. """ if user is None: user = users.get_current_user() if user is None: return {} pickled_tokens = memcache.get('gdata_pickled_tokens:%s' % user) if pickled_tokens: return pickle.loads(pickled_tokens) user_tokens = TokenCollection.all().filter('user =', user).get() if user_tokens: memcache.set('gdata_pickled_tokens:%s' % user, user_tokens.pickled_tokens) return pickle.loads(user_tokens.pickled_tokens) return {} gdata-2.0.18/src/gdata/core.py0000640036466300116100000002005712156622363016113 0ustar afshareng00000000000000#!/usr/bin/env python # # 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. # This module is used for version 2 of the Google Data APIs. __author__ = 'j.s@google.com (Jeff Scudder)' """Provides classes and methods for working with JSON-C. This module is experimental and subject to backwards incompatible changes. Jsonc: Class which represents JSON-C data and provides pythonic member access which is a bit cleaner than working with plain old dicts. parse_json: Converts a JSON-C string into a Jsonc object. jsonc_to_string: Converts a Jsonc object into a string of JSON-C. """ try: import simplejson except ImportError: try: # Try to import from django, should work on App Engine from django.utils import simplejson except ImportError: # Should work for Python2.6 and higher. import json as simplejson def _convert_to_jsonc(x): """Builds a Jsonc objects which wraps the argument's members.""" if isinstance(x, dict): jsonc_obj = Jsonc() # Recursively transform all members of the dict. # When converting a dict, we do not convert _name items into private # Jsonc members. for key, value in x.iteritems(): jsonc_obj._dict[key] = _convert_to_jsonc(value) return jsonc_obj elif isinstance(x, list): # Recursively transform all members of the list. members = [] for item in x: members.append(_convert_to_jsonc(item)) return members else: # Return the base object. return x def parse_json(json_string): """Converts a JSON-C string into a Jsonc object. Args: json_string: str or unicode The JSON to be parsed. Returns: A new Jsonc object. """ return _convert_to_jsonc(simplejson.loads(json_string)) def parse_json_file(json_file): return _convert_to_jsonc(simplejson.load(json_file)) def jsonc_to_string(jsonc_obj): """Converts a Jsonc object into a string of JSON-C.""" return simplejson.dumps(_convert_to_object(jsonc_obj)) def prettify_jsonc(jsonc_obj, indentation=2): """Converts a Jsonc object to a pretified (intented) JSON string.""" return simplejson.dumps(_convert_to_object(jsonc_obj), indent=indentation) def _convert_to_object(jsonc_obj): """Creates a new dict or list which has the data in the Jsonc object. Used to convert the Jsonc object to a plain old Python object to simplify conversion to a JSON-C string. Args: jsonc_obj: A Jsonc object to be converted into simple Python objects (dicts, lists, etc.) Returns: Either a dict, list, or other object with members converted from Jsonc objects to the corresponding simple Python object. """ if isinstance(jsonc_obj, Jsonc): plain = {} for key, value in jsonc_obj._dict.iteritems(): plain[key] = _convert_to_object(value) return plain elif isinstance(jsonc_obj, list): plain = [] for item in jsonc_obj: plain.append(_convert_to_object(item)) return plain else: return jsonc_obj def _to_jsonc_name(member_name): """Converts a Python style member name to a JSON-C style name. JSON-C uses camelCaseWithLower while Python tends to use lower_with_underscores so this method converts as follows: spam becomes spam spam_and_eggs becomes spamAndEggs Args: member_name: str or unicode The Python syle name which should be converted to JSON-C style. Returns: The JSON-C style name as a str or unicode. """ characters = [] uppercase_next = False for character in member_name: if character == '_': uppercase_next = True elif uppercase_next: characters.append(character.upper()) uppercase_next = False else: characters.append(character) return ''.join(characters) class Jsonc(object): """Represents JSON-C data in an easy to access object format. To access the members of a JSON structure which looks like this: { "data": { "totalItems": 800, "items": [ { "content": { "1": "rtsp://v5.cache3.c.youtube.com/CiILENy.../0/0/0/video.3gp" }, "viewCount": 220101, "commentCount": 22, "favoriteCount": 201 } ] }, "apiVersion": "2.0" } You would do the following: x = gdata.core.parse_json(the_above_string) # Gives you 800 x.data.total_items # Should be 22 x.data.items[0].comment_count # The apiVersion is '2.0' x.api_version To create a Jsonc object which would produce the above JSON, you would do: gdata.core.Jsonc( api_version='2.0', data=gdata.core.Jsonc( total_items=800, items=[ gdata.core.Jsonc( view_count=220101, comment_count=22, favorite_count=201, content={ '1': ('rtsp://v5.cache3.c.youtube.com' '/CiILENy.../0/0/0/video.3gp')})])) or x = gdata.core.Jsonc() x.api_version = '2.0' x.data = gdata.core.Jsonc() x.data.total_items = 800 x.data.items = [] # etc. How it works: The JSON-C data is stored in an internal dictionary (._dict) and the getattr, setattr, and delattr methods rewrite the name which you provide to mirror the expected format in JSON-C. (For more details on name conversion see _to_jsonc_name.) You may also access members using getitem, setitem, delitem as you would for a dictionary. For example x.data.total_items is equivalent to x['data']['totalItems'] (Not all dict methods are supported so if you need something other than the item operations, then you will want to use the ._dict member). You may need to use getitem or the _dict member to access certain properties in cases where the JSON-C syntax does not map neatly to Python objects. For example the YouTube Video feed has some JSON like this: "content": {"1": "rtsp://v5.cache3.c.youtube.com..."...} You cannot do x.content.1 in Python, so you would use the getitem as follows: x.content['1'] or you could use the _dict member as follows: x.content._dict['1'] If you need to create a new object with such a mapping you could use. x.content = gdata.core.Jsonc(_dict={'1': 'rtsp://cache3.c.youtube.com...'}) """ def __init__(self, _dict=None, **kwargs): json = _dict or {} for key, value in kwargs.iteritems(): if key.startswith('_'): object.__setattr__(self, key, value) else: json[_to_jsonc_name(key)] = _convert_to_jsonc(value) object.__setattr__(self, '_dict', json) def __setattr__(self, name, value): if name.startswith('_'): object.__setattr__(self, name, value) else: object.__getattribute__( self, '_dict')[_to_jsonc_name(name)] = _convert_to_jsonc(value) def __getattr__(self, name): if name.startswith('_'): object.__getattribute__(self, name) else: try: return object.__getattribute__(self, '_dict')[_to_jsonc_name(name)] except KeyError: raise AttributeError( 'No member for %s or [\'%s\']' % (name, _to_jsonc_name(name))) def __delattr__(self, name): if name.startswith('_'): object.__delattr__(self, name) else: try: del object.__getattribute__(self, '_dict')[_to_jsonc_name(name)] except KeyError: raise AttributeError( 'No member for %s (or [\'%s\'])' % (name, _to_jsonc_name(name))) # For container methods pass-through to the underlying dict. def __getitem__(self, key): return self._dict[key] def __setitem__(self, key, value): self._dict[key] = value def __delitem__(self, key): del self._dict[key] gdata-2.0.18/src/gdata/youtube/0000750036466300116100000000000012156625015016275 5ustar afshareng00000000000000gdata-2.0.18/src/gdata/youtube/data.py0000640036466300116100000002710112156622363017565 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright (C) 2009 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. """Contains the data classes of the YouTube Data API""" __author__ = 'j.s@google.com (Jeff Scudder)' import atom.core import atom.data import gdata.data import gdata.geo.data import gdata.media.data import gdata.opensearch.data import gdata.youtube.data YT_TEMPLATE = '{http://gdata.youtube.com/schemas/2007/}%s' class ComplaintEntry(gdata.data.GDEntry): """Describes a complaint about a video""" class ComplaintFeed(gdata.data.GDFeed): """Describes complaints about a video""" entry = [ComplaintEntry] class RatingEntry(gdata.data.GDEntry): """A rating about a video""" rating = gdata.data.Rating class RatingFeed(gdata.data.GDFeed): """Describes ratings for a video""" entry = [RatingEntry] class YouTubeMediaContent(gdata.media.data.MediaContent): """Describes a you tube media content""" _qname = gdata.media.data.MEDIA_TEMPLATE % 'content' format = 'format' class YtAge(atom.core.XmlElement): """User's age""" _qname = YT_TEMPLATE % 'age' class YtBooks(atom.core.XmlElement): """User's favorite books""" _qname = YT_TEMPLATE % 'books' class YtCompany(atom.core.XmlElement): """User's company""" _qname = YT_TEMPLATE % 'company' class YtDescription(atom.core.XmlElement): """Description""" _qname = YT_TEMPLATE % 'description' class YtDuration(atom.core.XmlElement): """Video duration""" _qname = YT_TEMPLATE % 'duration' seconds = 'seconds' class YtFirstName(atom.core.XmlElement): """User's first name""" _qname = YT_TEMPLATE % 'firstName' class YtGender(atom.core.XmlElement): """User's gender""" _qname = YT_TEMPLATE % 'gender' class YtHobbies(atom.core.XmlElement): """User's hobbies""" _qname = YT_TEMPLATE % 'hobbies' class YtHometown(atom.core.XmlElement): """User's hometown""" _qname = YT_TEMPLATE % 'hometown' class YtLastName(atom.core.XmlElement): """User's last name""" _qname = YT_TEMPLATE % 'lastName' class YtLocation(atom.core.XmlElement): """Location""" _qname = YT_TEMPLATE % 'location' class YtMovies(atom.core.XmlElement): """User's favorite movies""" _qname = YT_TEMPLATE % 'movies' class YtMusic(atom.core.XmlElement): """User's favorite music""" _qname = YT_TEMPLATE % 'music' class YtNoEmbed(atom.core.XmlElement): """Disables embedding for the video""" _qname = YT_TEMPLATE % 'noembed' class YtOccupation(atom.core.XmlElement): """User's occupation""" _qname = YT_TEMPLATE % 'occupation' class YtPlaylistId(atom.core.XmlElement): """Playlist id""" _qname = YT_TEMPLATE % 'playlistId' class YtPosition(atom.core.XmlElement): """Video position on the playlist""" _qname = YT_TEMPLATE % 'position' class YtPrivate(atom.core.XmlElement): """Flags the entry as private""" _qname = YT_TEMPLATE % 'private' class YtQueryString(atom.core.XmlElement): """Keywords or query string associated with a subscription""" _qname = YT_TEMPLATE % 'queryString' class YtRacy(atom.core.XmlElement): """Mature content""" _qname = YT_TEMPLATE % 'racy' class YtRecorded(atom.core.XmlElement): """Date when the video was recorded""" _qname = YT_TEMPLATE % 'recorded' class YtRelationship(atom.core.XmlElement): """User's relationship status""" _qname = YT_TEMPLATE % 'relationship' class YtSchool(atom.core.XmlElement): """User's school""" _qname = YT_TEMPLATE % 'school' class YtStatistics(atom.core.XmlElement): """Video and user statistics""" _qname = YT_TEMPLATE % 'statistics' favorite_count = 'favoriteCount' video_watch_count = 'videoWatchCount' view_count = 'viewCount' last_web_access = 'lastWebAccess' subscriber_count = 'subscriberCount' class YtStatus(atom.core.XmlElement): """Status of a contact""" _qname = YT_TEMPLATE % 'status' class YtUserProfileStatistics(YtStatistics): """User statistics""" _qname = YT_TEMPLATE % 'statistics' class YtUsername(atom.core.XmlElement): """Youtube username""" _qname = YT_TEMPLATE % 'username' class FriendEntry(gdata.data.BatchEntry): """Describes a contact in friend list""" username = YtUsername status = YtStatus email = gdata.data.Email class FriendFeed(gdata.data.BatchFeed): """Describes user's friends""" entry = [FriendEntry] class YtVideoStatistics(YtStatistics): """Video statistics""" _qname = YT_TEMPLATE % 'statistics' class ChannelEntry(gdata.data.GDEntry): """Describes a video channel""" class ChannelFeed(gdata.data.GDFeed): """Describes channels""" entry = [ChannelEntry] class FavoriteEntry(gdata.data.BatchEntry): """Describes a favorite video""" class FavoriteFeed(gdata.data.BatchFeed): """Describes favorite videos""" entry = [FavoriteEntry] class YouTubeMediaCredit(gdata.media.data.MediaCredit): """Describes a you tube media credit""" _qname = gdata.media.data.MEDIA_TEMPLATE % 'credit' type = 'type' class YouTubeMediaRating(gdata.media.data.MediaRating): """Describes a you tube media rating""" _qname = gdata.media.data.MEDIA_TEMPLATE % 'rating' country = 'country' class YtAboutMe(atom.core.XmlElement): """User's self description""" _qname = YT_TEMPLATE % 'aboutMe' class UserProfileEntry(gdata.data.BatchEntry): """Describes an user's profile""" relationship = YtRelationship description = YtDescription location = YtLocation statistics = YtUserProfileStatistics school = YtSchool music = YtMusic first_name = YtFirstName gender = YtGender occupation = YtOccupation hometown = YtHometown company = YtCompany movies = YtMovies books = YtBooks username = YtUsername about_me = YtAboutMe last_name = YtLastName age = YtAge thumbnail = gdata.media.data.MediaThumbnail hobbies = YtHobbies class UserProfileFeed(gdata.data.BatchFeed): """Describes a feed of user's profile""" entry = [UserProfileEntry] class YtAspectRatio(atom.core.XmlElement): """The aspect ratio of a media file""" _qname = YT_TEMPLATE % 'aspectRatio' class YtBasePublicationState(atom.core.XmlElement): """Status of an unpublished entry""" _qname = YT_TEMPLATE % 'state' help_url = 'helpUrl' class YtPublicationState(YtBasePublicationState): """Status of an unpublished video""" _qname = YT_TEMPLATE % 'state' name = 'name' reason_code = 'reasonCode' class YouTubeAppControl(atom.data.Control): """Describes a you tube app control""" _qname = (atom.data.APP_TEMPLATE_V1 % 'control', atom.data.APP_TEMPLATE_V2 % 'control') state = YtPublicationState class YtCaptionPublicationState(YtBasePublicationState): """Status of an unpublished caption track""" _qname = YT_TEMPLATE % 'state' reason_code = 'reasonCode' name = 'name' class YouTubeCaptionAppControl(atom.data.Control): """Describes a you tube caption app control""" _qname = atom.data.APP_TEMPLATE_V2 % 'control' state = YtCaptionPublicationState class CaptionTrackEntry(gdata.data.GDEntry): """Describes a caption track""" class CaptionTrackFeed(gdata.data.GDFeed): """Describes caption tracks""" entry = [CaptionTrackEntry] class YtCountHint(atom.core.XmlElement): """Hint as to how many entries the linked feed contains""" _qname = YT_TEMPLATE % 'countHint' class PlaylistLinkEntry(gdata.data.BatchEntry): """Describes a playlist""" description = YtDescription playlist_id = YtPlaylistId count_hint = YtCountHint private = YtPrivate class PlaylistLinkFeed(gdata.data.BatchFeed): """Describes list of playlists""" entry = [PlaylistLinkEntry] class YtModerationStatus(atom.core.XmlElement): """Moderation status""" _qname = YT_TEMPLATE % 'moderationStatus' class YtPlaylistTitle(atom.core.XmlElement): """Playlist title""" _qname = YT_TEMPLATE % 'playlistTitle' class SubscriptionEntry(gdata.data.BatchEntry): """Describes user's channel subscritpions""" count_hint = YtCountHint playlist_title = YtPlaylistTitle thumbnail = gdata.media.data.MediaThumbnail username = YtUsername query_string = YtQueryString playlist_id = YtPlaylistId class SubscriptionFeed(gdata.data.BatchFeed): """Describes list of user's video subscriptions""" entry = [SubscriptionEntry] class YtSpam(atom.core.XmlElement): """Indicates that the entry probably contains spam""" _qname = YT_TEMPLATE % 'spam' class CommentEntry(gdata.data.BatchEntry): """Describes a comment for a video""" spam = YtSpam class CommentFeed(gdata.data.BatchFeed): """Describes comments for a video""" entry = [CommentEntry] class YtUploaded(atom.core.XmlElement): """Date/Time at which the video was uploaded""" _qname = YT_TEMPLATE % 'uploaded' class YtVideoId(atom.core.XmlElement): """Video id""" _qname = YT_TEMPLATE % 'videoid' class YouTubeMediaGroup(gdata.media.data.MediaGroup): """Describes a you tube media group""" _qname = gdata.media.data.MEDIA_TEMPLATE % 'group' videoid = YtVideoId private = YtPrivate duration = YtDuration aspect_ratio = YtAspectRatio uploaded = YtUploaded class VideoEntryBase(gdata.data.GDEntry): """Elements that describe or contain videos""" group = YouTubeMediaGroup statistics = YtVideoStatistics racy = YtRacy recorded = YtRecorded where = gdata.geo.data.GeoRssWhere rating = gdata.data.Rating noembed = YtNoEmbed location = YtLocation comments = gdata.data.Comments class PlaylistEntry(gdata.data.BatchEntry): """Describes a video in a playlist""" description = YtDescription position = YtPosition class PlaylistFeed(gdata.data.BatchFeed): """Describes videos in a playlist""" private = YtPrivate group = YouTubeMediaGroup playlist_id = YtPlaylistId entry = [PlaylistEntry] class VideoEntry(gdata.data.BatchEntry): """Describes a video""" class VideoFeed(gdata.data.BatchFeed): """Describes a video feed""" entry = [VideoEntry] class VideoMessageEntry(gdata.data.BatchEntry): """Describes a video message""" description = YtDescription class VideoMessageFeed(gdata.data.BatchFeed): """Describes videos in a videoMessage""" entry = [VideoMessageEntry] class UserEventEntry(gdata.data.GDEntry): """Describes a user event""" playlist_id = YtPlaylistId videoid = YtVideoId username = YtUsername query_string = YtQueryString rating = gdata.data.Rating class UserEventFeed(gdata.data.GDFeed): """Describes list of events""" entry = [UserEventEntry] class VideoModerationEntry(gdata.data.GDEntry): """Describes video moderation""" moderation_status = YtModerationStatus videoid = YtVideoId class VideoModerationFeed(gdata.data.GDFeed): """Describes a video moderation feed""" entry = [VideoModerationEntry] class TrackContent(atom.data.Content): lang = atom.data.XML_TEMPLATE % 'lang' class TrackEntry(gdata.data.GDEntry): """Represents the URL for a caption track""" content = TrackContent def get_caption_track_id(self): """Extracts the ID of this caption track. Returns: The caption track's id as a string. """ if self.id.text: match = CAPTION_TRACK_ID_PATTERN.match(self.id.text) if match: return match.group(2) return None GetCaptionTrackId = get_caption_track_id class CaptionFeed(gdata.data.GDFeed): """Represents a caption feed for a video on YouTube.""" entry = [TrackEntry] gdata-2.0.18/src/gdata/youtube/__init__.py0000750036466300116100000006202712156622363020423 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright (C) 2008 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. __author__ = ('api.stephaniel@gmail.com (Stephanie Liu)' ', api.jhartmann@gmail.com (Jochen Hartmann)') import atom import gdata import gdata.media as Media import gdata.geo as Geo YOUTUBE_NAMESPACE = 'http://gdata.youtube.com/schemas/2007' YOUTUBE_FORMAT = '{http://gdata.youtube.com/schemas/2007}format' YOUTUBE_DEVELOPER_TAG_SCHEME = '%s/%s' % (YOUTUBE_NAMESPACE, 'developertags.cat') YOUTUBE_SUBSCRIPTION_TYPE_SCHEME = '%s/%s' % (YOUTUBE_NAMESPACE, 'subscriptiontypes.cat') class Username(atom.AtomBase): """The YouTube Username element""" _tag = 'username' _namespace = YOUTUBE_NAMESPACE class QueryString(atom.AtomBase): """The YouTube QueryString element""" _tag = 'queryString' _namespace = YOUTUBE_NAMESPACE class FirstName(atom.AtomBase): """The YouTube FirstName element""" _tag = 'firstName' _namespace = YOUTUBE_NAMESPACE class LastName(atom.AtomBase): """The YouTube LastName element""" _tag = 'lastName' _namespace = YOUTUBE_NAMESPACE class Age(atom.AtomBase): """The YouTube Age element""" _tag = 'age' _namespace = YOUTUBE_NAMESPACE class Books(atom.AtomBase): """The YouTube Books element""" _tag = 'books' _namespace = YOUTUBE_NAMESPACE class Gender(atom.AtomBase): """The YouTube Gender element""" _tag = 'gender' _namespace = YOUTUBE_NAMESPACE class Company(atom.AtomBase): """The YouTube Company element""" _tag = 'company' _namespace = YOUTUBE_NAMESPACE class Hobbies(atom.AtomBase): """The YouTube Hobbies element""" _tag = 'hobbies' _namespace = YOUTUBE_NAMESPACE class Hometown(atom.AtomBase): """The YouTube Hometown element""" _tag = 'hometown' _namespace = YOUTUBE_NAMESPACE class Location(atom.AtomBase): """The YouTube Location element""" _tag = 'location' _namespace = YOUTUBE_NAMESPACE class Movies(atom.AtomBase): """The YouTube Movies element""" _tag = 'movies' _namespace = YOUTUBE_NAMESPACE class Music(atom.AtomBase): """The YouTube Music element""" _tag = 'music' _namespace = YOUTUBE_NAMESPACE class Occupation(atom.AtomBase): """The YouTube Occupation element""" _tag = 'occupation' _namespace = YOUTUBE_NAMESPACE class School(atom.AtomBase): """The YouTube School element""" _tag = 'school' _namespace = YOUTUBE_NAMESPACE class Relationship(atom.AtomBase): """The YouTube Relationship element""" _tag = 'relationship' _namespace = YOUTUBE_NAMESPACE class Recorded(atom.AtomBase): """The YouTube Recorded element""" _tag = 'recorded' _namespace = YOUTUBE_NAMESPACE class Statistics(atom.AtomBase): """The YouTube Statistics element.""" _tag = 'statistics' _namespace = YOUTUBE_NAMESPACE _attributes = atom.AtomBase._attributes.copy() _attributes['viewCount'] = 'view_count' _attributes['videoWatchCount'] = 'video_watch_count' _attributes['subscriberCount'] = 'subscriber_count' _attributes['lastWebAccess'] = 'last_web_access' _attributes['favoriteCount'] = 'favorite_count' def __init__(self, view_count=None, video_watch_count=None, favorite_count=None, subscriber_count=None, last_web_access=None, extension_elements=None, extension_attributes=None, text=None): self.view_count = view_count self.video_watch_count = video_watch_count self.subscriber_count = subscriber_count self.last_web_access = last_web_access self.favorite_count = favorite_count atom.AtomBase.__init__(self, extension_elements=extension_elements, extension_attributes=extension_attributes, text=text) class Status(atom.AtomBase): """The YouTube Status element""" _tag = 'status' _namespace = YOUTUBE_NAMESPACE class Position(atom.AtomBase): """The YouTube Position element. The position in a playlist feed.""" _tag = 'position' _namespace = YOUTUBE_NAMESPACE class Racy(atom.AtomBase): """The YouTube Racy element.""" _tag = 'racy' _namespace = YOUTUBE_NAMESPACE class Description(atom.AtomBase): """The YouTube Description element.""" _tag = 'description' _namespace = YOUTUBE_NAMESPACE class Private(atom.AtomBase): """The YouTube Private element.""" _tag = 'private' _namespace = YOUTUBE_NAMESPACE class NoEmbed(atom.AtomBase): """The YouTube VideoShare element. Whether a video can be embedded or not.""" _tag = 'noembed' _namespace = YOUTUBE_NAMESPACE class Comments(atom.AtomBase): """The GData Comments element""" _tag = 'comments' _namespace = gdata.GDATA_NAMESPACE _children = atom.AtomBase._children.copy() _attributes = atom.AtomBase._attributes.copy() _children['{%s}feedLink' % gdata.GDATA_NAMESPACE] = ('feed_link', [gdata.FeedLink]) def __init__(self, feed_link=None, extension_elements=None, extension_attributes=None, text=None): self.feed_link = feed_link atom.AtomBase.__init__(self, extension_elements=extension_elements, extension_attributes=extension_attributes, text=text) class Rating(atom.AtomBase): """The GData Rating element""" _tag = 'rating' _namespace = gdata.GDATA_NAMESPACE _attributes = atom.AtomBase._attributes.copy() _attributes['min'] = 'min' _attributes['max'] = 'max' _attributes['numRaters'] = 'num_raters' _attributes['average'] = 'average' def __init__(self, min=None, max=None, num_raters=None, average=None, extension_elements=None, extension_attributes=None, text=None): self.min = min self.max = max self.num_raters = num_raters self.average = average atom.AtomBase.__init__(self, extension_elements=extension_elements, extension_attributes=extension_attributes, text=text) class YouTubePlaylistVideoEntry(gdata.GDataEntry): """Represents a YouTubeVideoEntry on a YouTubePlaylist.""" _tag = gdata.GDataEntry._tag _namespace = gdata.GDataEntry._namespace _children = gdata.GDataEntry._children.copy() _attributes = gdata.GDataEntry._attributes.copy() _children['{%s}feedLink' % gdata.GDATA_NAMESPACE] = ('feed_link', [gdata.FeedLink]) _children['{%s}description' % YOUTUBE_NAMESPACE] = ('description', Description) _children['{%s}rating' % gdata.GDATA_NAMESPACE] = ('rating', Rating) _children['{%s}comments' % gdata.GDATA_NAMESPACE] = ('comments', Comments) _children['{%s}statistics' % YOUTUBE_NAMESPACE] = ('statistics', Statistics) _children['{%s}location' % YOUTUBE_NAMESPACE] = ('location', Location) _children['{%s}position' % YOUTUBE_NAMESPACE] = ('position', Position) _children['{%s}group' % gdata.media.MEDIA_NAMESPACE] = ('media', Media.Group) def __init__(self, author=None, category=None, content=None, atom_id=None, link=None, published=None, title=None, updated=None, feed_link=None, description=None, rating=None, comments=None, statistics=None, location=None, position=None, media=None, extension_elements=None, extension_attributes=None): self.feed_link = feed_link self.description = description self.rating = rating self.comments = comments self.statistics = statistics self.location = location self.position = position self.media = media gdata.GDataEntry.__init__(self, author=author, category=category, content=content, atom_id=atom_id, link=link, published=published, title=title, updated=updated, extension_elements=extension_elements, extension_attributes=extension_attributes) class YouTubeVideoCommentEntry(gdata.GDataEntry): """Represents a comment on YouTube.""" _tag = gdata.GDataEntry._tag _namespace = gdata.GDataEntry._namespace _children = gdata.GDataEntry._children.copy() _attributes = gdata.GDataEntry._attributes.copy() class YouTubeSubscriptionEntry(gdata.GDataEntry): """Represents a subscription entry on YouTube.""" _tag = gdata.GDataEntry._tag _namespace = gdata.GDataEntry._namespace _children = gdata.GDataEntry._children.copy() _attributes = gdata.GDataEntry._attributes.copy() _children['{%s}username' % YOUTUBE_NAMESPACE] = ('username', Username) _children['{%s}queryString' % YOUTUBE_NAMESPACE] = ( 'query_string', QueryString) _children['{%s}feedLink' % gdata.GDATA_NAMESPACE] = ('feed_link', [gdata.FeedLink]) def __init__(self, author=None, category=None, content=None, atom_id=None, link=None, published=None, title=None, updated=None, username=None, query_string=None, feed_link=None, extension_elements=None, extension_attributes=None): gdata.GDataEntry.__init__(self, author=author, category=category, content=content, atom_id=atom_id, link=link, published=published, title=title, updated=updated) self.username = username self.query_string = query_string self.feed_link = feed_link def GetSubscriptionType(self): """Retrieve the type of this subscription. Returns: A string that is either 'channel, 'query' or 'favorites' """ for category in self.category: if category.scheme == YOUTUBE_SUBSCRIPTION_TYPE_SCHEME: return category.term class YouTubeVideoResponseEntry(gdata.GDataEntry): """Represents a video response. """ _tag = gdata.GDataEntry._tag _namespace = gdata.GDataEntry._namespace _children = gdata.GDataEntry._children.copy() _attributes = gdata.GDataEntry._attributes.copy() _children['{%s}rating' % gdata.GDATA_NAMESPACE] = ('rating', Rating) _children['{%s}noembed' % YOUTUBE_NAMESPACE] = ('noembed', NoEmbed) _children['{%s}statistics' % YOUTUBE_NAMESPACE] = ('statistics', Statistics) _children['{%s}racy' % YOUTUBE_NAMESPACE] = ('racy', Racy) _children['{%s}group' % gdata.media.MEDIA_NAMESPACE] = ('media', Media.Group) def __init__(self, author=None, category=None, content=None, atom_id=None, link=None, published=None, title=None, updated=None, rating=None, noembed=None, statistics=None, racy=None, media=None, extension_elements=None, extension_attributes=None): gdata.GDataEntry.__init__(self, author=author, category=category, content=content, atom_id=atom_id, link=link, published=published, title=title, updated=updated) self.rating = rating self.noembed = noembed self.statistics = statistics self.racy = racy self.media = media or Media.Group() class YouTubeContactEntry(gdata.GDataEntry): """Represents a contact entry.""" _tag = gdata.GDataEntry._tag _namespace = gdata.GDataEntry._namespace _children = gdata.GDataEntry._children.copy() _attributes = gdata.GDataEntry._attributes.copy() _children['{%s}username' % YOUTUBE_NAMESPACE] = ('username', Username) _children['{%s}status' % YOUTUBE_NAMESPACE] = ('status', Status) def __init__(self, author=None, category=None, content=None, atom_id=None, link=None, published=None, title=None, updated=None, username=None, status=None, extension_elements=None, extension_attributes=None, text=None): gdata.GDataEntry.__init__(self, author=author, category=category, content=content, atom_id=atom_id, link=link, published=published, title=title, updated=updated) self.username = username self.status = status class YouTubeVideoEntry(gdata.GDataEntry): """Represents a video on YouTube.""" _tag = gdata.GDataEntry._tag _namespace = gdata.GDataEntry._namespace _children = gdata.GDataEntry._children.copy() _attributes = gdata.GDataEntry._attributes.copy() _children['{%s}rating' % gdata.GDATA_NAMESPACE] = ('rating', Rating) _children['{%s}comments' % gdata.GDATA_NAMESPACE] = ('comments', Comments) _children['{%s}noembed' % YOUTUBE_NAMESPACE] = ('noembed', NoEmbed) _children['{%s}statistics' % YOUTUBE_NAMESPACE] = ('statistics', Statistics) _children['{%s}recorded' % YOUTUBE_NAMESPACE] = ('recorded', Recorded) _children['{%s}racy' % YOUTUBE_NAMESPACE] = ('racy', Racy) _children['{%s}group' % gdata.media.MEDIA_NAMESPACE] = ('media', Media.Group) _children['{%s}where' % gdata.geo.GEORSS_NAMESPACE] = ('geo', Geo.Where) def __init__(self, author=None, category=None, content=None, atom_id=None, link=None, published=None, title=None, updated=None, rating=None, noembed=None, statistics=None, racy=None, media=None, geo=None, recorded=None, comments=None, extension_elements=None, extension_attributes=None): self.rating = rating self.noembed = noembed self.statistics = statistics self.racy = racy self.comments = comments self.media = media or Media.Group() self.geo = geo self.recorded = recorded gdata.GDataEntry.__init__(self, author=author, category=category, content=content, atom_id=atom_id, link=link, published=published, title=title, updated=updated, extension_elements=extension_elements, extension_attributes=extension_attributes) def GetSwfUrl(self): """Return the URL for the embeddable Video Returns: URL of the embeddable video """ if self.media.content: for content in self.media.content: if content.extension_attributes[YOUTUBE_FORMAT] == '5': return content.url else: return None def AddDeveloperTags(self, developer_tags): """Add a developer tag for this entry. Developer tags can only be set during the initial upload. Arguments: developer_tags: A list of developer tags as strings. Returns: A list of all developer tags for this video entry. """ for tag_text in developer_tags: self.media.category.append(gdata.media.Category( text=tag_text, label=tag_text, scheme=YOUTUBE_DEVELOPER_TAG_SCHEME)) return self.GetDeveloperTags() def GetDeveloperTags(self): """Retrieve developer tags for this video entry.""" developer_tags = [] for category in self.media.category: if category.scheme == YOUTUBE_DEVELOPER_TAG_SCHEME: developer_tags.append(category) if len(developer_tags) > 0: return developer_tags def GetYouTubeCategoryAsString(self): """Convenience method to return the YouTube category as string. YouTubeVideoEntries can contain multiple Category objects with differing schemes. This method returns only the category with the correct scheme, ignoring developer tags. """ for category in self.media.category: if category.scheme != YOUTUBE_DEVELOPER_TAG_SCHEME: return category.text class YouTubeUserEntry(gdata.GDataEntry): """Represents a user on YouTube.""" _tag = gdata.GDataEntry._tag _namespace = gdata.GDataEntry._namespace _children = gdata.GDataEntry._children.copy() _attributes = gdata.GDataEntry._attributes.copy() _children['{%s}username' % YOUTUBE_NAMESPACE] = ('username', Username) _children['{%s}firstName' % YOUTUBE_NAMESPACE] = ('first_name', FirstName) _children['{%s}lastName' % YOUTUBE_NAMESPACE] = ('last_name', LastName) _children['{%s}age' % YOUTUBE_NAMESPACE] = ('age', Age) _children['{%s}books' % YOUTUBE_NAMESPACE] = ('books', Books) _children['{%s}gender' % YOUTUBE_NAMESPACE] = ('gender', Gender) _children['{%s}company' % YOUTUBE_NAMESPACE] = ('company', Company) _children['{%s}description' % YOUTUBE_NAMESPACE] = ('description', Description) _children['{%s}hobbies' % YOUTUBE_NAMESPACE] = ('hobbies', Hobbies) _children['{%s}hometown' % YOUTUBE_NAMESPACE] = ('hometown', Hometown) _children['{%s}location' % YOUTUBE_NAMESPACE] = ('location', Location) _children['{%s}movies' % YOUTUBE_NAMESPACE] = ('movies', Movies) _children['{%s}music' % YOUTUBE_NAMESPACE] = ('music', Music) _children['{%s}occupation' % YOUTUBE_NAMESPACE] = ('occupation', Occupation) _children['{%s}school' % YOUTUBE_NAMESPACE] = ('school', School) _children['{%s}relationship' % YOUTUBE_NAMESPACE] = ('relationship', Relationship) _children['{%s}statistics' % YOUTUBE_NAMESPACE] = ('statistics', Statistics) _children['{%s}feedLink' % gdata.GDATA_NAMESPACE] = ('feed_link', [gdata.FeedLink]) _children['{%s}thumbnail' % gdata.media.MEDIA_NAMESPACE] = ('thumbnail', Media.Thumbnail) def __init__(self, author=None, category=None, content=None, atom_id=None, link=None, published=None, title=None, updated=None, username=None, first_name=None, last_name=None, age=None, books=None, gender=None, company=None, description=None, hobbies=None, hometown=None, location=None, movies=None, music=None, occupation=None, school=None, relationship=None, statistics=None, feed_link=None, extension_elements=None, extension_attributes=None, text=None): self.username = username self.first_name = first_name self.last_name = last_name self.age = age self.books = books self.gender = gender self.company = company self.description = description self.hobbies = hobbies self.hometown = hometown self.location = location self.movies = movies self.music = music self.occupation = occupation self.school = school self.relationship = relationship self.statistics = statistics self.feed_link = feed_link gdata.GDataEntry.__init__(self, author=author, category=category, content=content, atom_id=atom_id, link=link, published=published, title=title, updated=updated, extension_elements=extension_elements, extension_attributes=extension_attributes, text=text) class YouTubeVideoFeed(gdata.GDataFeed, gdata.LinkFinder): """Represents a video feed on YouTube.""" _tag = gdata.GDataFeed._tag _namespace = gdata.GDataFeed._namespace _children = gdata.GDataFeed._children.copy() _attributes = gdata.GDataFeed._attributes.copy() _children['{%s}entry' % atom.ATOM_NAMESPACE] = ('entry', [YouTubeVideoEntry]) class YouTubePlaylistEntry(gdata.GDataEntry): """Represents a playlist in YouTube.""" _tag = gdata.GDataEntry._tag _namespace = gdata.GDataEntry._namespace _children = gdata.GDataEntry._children.copy() _attributes = gdata.GDataEntry._attributes.copy() _children['{%s}description' % YOUTUBE_NAMESPACE] = ('description', Description) _children['{%s}private' % YOUTUBE_NAMESPACE] = ('private', Private) _children['{%s}feedLink' % gdata.GDATA_NAMESPACE] = ('feed_link', [gdata.FeedLink]) def __init__(self, author=None, category=None, content=None, atom_id=None, link=None, published=None, title=None, updated=None, private=None, feed_link=None, description=None, extension_elements=None, extension_attributes=None): self.description = description self.private = private self.feed_link = feed_link gdata.GDataEntry.__init__(self, author=author, category=category, content=content, atom_id=atom_id, link=link, published=published, title=title, updated=updated, extension_elements=extension_elements, extension_attributes=extension_attributes) class YouTubePlaylistFeed(gdata.GDataFeed, gdata.LinkFinder): """Represents a feed of a user's playlists """ _tag = gdata.GDataFeed._tag _namespace = gdata.GDataFeed._namespace _children = gdata.GDataFeed._children.copy() _attributes = gdata.GDataFeed._attributes.copy() _children['{%s}entry' % atom.ATOM_NAMESPACE] = ('entry', [YouTubePlaylistEntry]) class YouTubePlaylistVideoFeed(gdata.GDataFeed, gdata.LinkFinder): """Represents a feed of video entry on a playlist.""" _tag = gdata.GDataFeed._tag _namespace = gdata.GDataFeed._namespace _children = gdata.GDataFeed._children.copy() _attributes = gdata.GDataFeed._attributes.copy() _children['{%s}entry' % atom.ATOM_NAMESPACE] = ('entry', [YouTubePlaylistVideoEntry]) class YouTubeContactFeed(gdata.GDataFeed, gdata.LinkFinder): """Represents a feed of a users contacts.""" _tag = gdata.GDataFeed._tag _namespace = gdata.GDataFeed._namespace _children = gdata.GDataFeed._children.copy() _attributes = gdata.GDataFeed._attributes.copy() _children['{%s}entry' % atom.ATOM_NAMESPACE] = ('entry', [YouTubeContactEntry]) class YouTubeSubscriptionFeed(gdata.GDataFeed, gdata.LinkFinder): """Represents a feed of a users subscriptions.""" _tag = gdata.GDataFeed._tag _namespace = gdata.GDataFeed._namespace _children = gdata.GDataFeed._children.copy() _attributes = gdata.GDataFeed._attributes.copy() _children['{%s}entry' % atom.ATOM_NAMESPACE] = ('entry', [YouTubeSubscriptionEntry]) class YouTubeVideoCommentFeed(gdata.GDataFeed, gdata.LinkFinder): """Represents a feed of comments for a video.""" _tag = gdata.GDataFeed._tag _namespace = gdata.GDataFeed._namespace _children = gdata.GDataFeed._children.copy() _attributes = gdata.GDataFeed._attributes.copy() _children['{%s}entry' % atom.ATOM_NAMESPACE] = ('entry', [YouTubeVideoCommentEntry]) class YouTubeVideoResponseFeed(gdata.GDataFeed, gdata.LinkFinder): """Represents a feed of video responses.""" _tag = gdata.GDataFeed._tag _namespace = gdata.GDataFeed._namespace _children = gdata.GDataFeed._children.copy() _attributes = gdata.GDataFeed._attributes.copy() _children['{%s}entry' % atom.ATOM_NAMESPACE] = ('entry', [YouTubeVideoResponseEntry]) def YouTubeVideoFeedFromString(xml_string): return atom.CreateClassFromXMLString(YouTubeVideoFeed, xml_string) def YouTubeVideoEntryFromString(xml_string): return atom.CreateClassFromXMLString(YouTubeVideoEntry, xml_string) def YouTubeContactFeedFromString(xml_string): return atom.CreateClassFromXMLString(YouTubeContactFeed, xml_string) def YouTubeContactEntryFromString(xml_string): return atom.CreateClassFromXMLString(YouTubeContactEntry, xml_string) def YouTubeVideoCommentFeedFromString(xml_string): return atom.CreateClassFromXMLString(YouTubeVideoCommentFeed, xml_string) def YouTubeVideoCommentEntryFromString(xml_string): return atom.CreateClassFromXMLString(YouTubeVideoCommentEntry, xml_string) def YouTubeUserFeedFromString(xml_string): return atom.CreateClassFromXMLString(YouTubeVideoFeed, xml_string) def YouTubeUserEntryFromString(xml_string): return atom.CreateClassFromXMLString(YouTubeUserEntry, xml_string) def YouTubePlaylistFeedFromString(xml_string): return atom.CreateClassFromXMLString(YouTubePlaylistFeed, xml_string) def YouTubePlaylistVideoFeedFromString(xml_string): return atom.CreateClassFromXMLString(YouTubePlaylistVideoFeed, xml_string) def YouTubePlaylistEntryFromString(xml_string): return atom.CreateClassFromXMLString(YouTubePlaylistEntry, xml_string) def YouTubePlaylistVideoEntryFromString(xml_string): return atom.CreateClassFromXMLString(YouTubePlaylistVideoEntry, xml_string) def YouTubeSubscriptionFeedFromString(xml_string): return atom.CreateClassFromXMLString(YouTubeSubscriptionFeed, xml_string) def YouTubeSubscriptionEntryFromString(xml_string): return atom.CreateClassFromXMLString(YouTubeSubscriptionEntry, xml_string) def YouTubeVideoResponseFeedFromString(xml_string): return atom.CreateClassFromXMLString(YouTubeVideoResponseFeed, xml_string) def YouTubeVideoResponseEntryFromString(xml_string): return atom.CreateClassFromXMLString(YouTubeVideoResponseEntry, xml_string) gdata-2.0.18/src/gdata/youtube/client.py0000640036466300116100000002236712156622363020143 0ustar afshareng00000000000000#!/usr/bin/env python # # Copyright (C) 2009 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. """Contains a client to communicate with the YouTube servers. A quick and dirty port of the YouTube GDATA 1.0 Python client libraries to version 2.0 of the GDATA library. """ # __author__ = 's.@google.com (John Skidgel)' import logging import gdata.client import gdata.youtube.data import atom.data import atom.http_core # Constants # ----------------------------------------------------------------------------- YOUTUBE_CLIENTLOGIN_AUTHENTICATION_URL = 'https://www.google.com/youtube/accounts/ClientLogin' YOUTUBE_SUPPORTED_UPLOAD_TYPES = ('mov', 'avi', 'wmv', 'mpg', 'quicktime', 'flv') YOUTUBE_QUERY_VALID_TIME_PARAMETERS = ('today', 'this_week', 'this_month', 'all_time') YOUTUBE_QUERY_VALID_ORDERBY_PARAMETERS = ('published', 'viewCount', 'rating', 'relevance') YOUTUBE_QUERY_VALID_RACY_PARAMETERS = ('include', 'exclude') YOUTUBE_QUERY_VALID_FORMAT_PARAMETERS = ('1', '5', '6') YOUTUBE_STANDARDFEEDS = ('most_recent', 'recently_featured', 'top_rated', 'most_viewed','watch_on_mobile') YOUTUBE_UPLOAD_TOKEN_URI = 'http://gdata.youtube.com/action/GetUploadToken' YOUTUBE_SERVER = 'gdata.youtube.com/feeds/api' YOUTUBE_SERVICE = 'youtube' YOUTUBE_VIDEO_FEED_URI = 'http://%s/videos' % YOUTUBE_SERVER YOUTUBE_USER_FEED_URI = 'http://%s/users/' % YOUTUBE_SERVER # Takes a youtube video ID. YOUTUBE_CAPTION_FEED_URI = 'http://gdata.youtube.com/feeds/api/videos/%s/captions' # Takes a youtube video ID and a caption track ID. YOUTUBE_CAPTION_URI = 'http://gdata.youtube.com/feeds/api/videos/%s/captiondata/%s' YOUTUBE_CAPTION_MIME_TYPE = 'application/vnd.youtube.timedtext; charset=UTF-8' # Classes # ----------------------------------------------------------------------------- class Error(Exception): """Base class for errors within the YouTube service.""" pass class RequestError(Error): """Error class that is thrown in response to an invalid HTTP Request.""" pass class YouTubeError(Error): """YouTube service specific error class.""" pass class YouTubeClient(gdata.client.GDClient): """Client for the YouTube service. Performs a partial list of Google Data YouTube API functions, such as retrieving the videos feed for a user and the feed for a video. YouTube Service requires authentication for any write, update or delete actions. """ api_version = '2' auth_service = YOUTUBE_SERVICE auth_scopes = ['https://%s' % YOUTUBE_SERVER] ssl = True def get_videos(self, uri=YOUTUBE_VIDEO_FEED_URI, auth_token=None, desired_class=gdata.youtube.data.VideoFeed, **kwargs): """Retrieves a YouTube video feed. Args: uri: A string representing the URI of the feed that is to be retrieved. Returns: A YouTubeVideoFeed if successfully retrieved. """ return self.get_feed(uri, auth_token=auth_token, desired_class=desired_class, **kwargs) GetVideos = get_videos def get_user_feed(self, uri=None, username=None): """Retrieve a YouTubeVideoFeed of user uploaded videos. Either a uri or a username must be provided. This will retrieve list of videos uploaded by specified user. The uri will be of format "http://gdata.youtube.com/feeds/api/users/{username}/uploads". Args: uri: An optional string representing the URI of the user feed that is to be retrieved. username: An optional string representing the username. Returns: A YouTubeUserFeed if successfully retrieved. Raises: YouTubeError: You must provide at least a uri or a username to the GetYouTubeUserFeed() method. """ if uri is None and username is None: raise YouTubeError('You must provide at least a uri or a username ' 'to the GetYouTubeUserFeed() method') elif username and not uri: uri = '%s%s/%s' % (YOUTUBE_USER_FEED_URI, username, 'uploads') return self.get_feed(uri, desired_class=gdata.youtube.data.VideoFeed) GetUserFeed = get_user_feed def get_video_entry(self, uri=None, video_id=None, auth_token=None, **kwargs): """Retrieve a YouTubeVideoEntry. Either a uri or a video_id must be provided. Args: uri: An optional string representing the URI of the entry that is to be retrieved. video_id: An optional string representing the ID of the video. Returns: A YouTubeVideoFeed if successfully retrieved. Raises: YouTubeError: You must provide at least a uri or a video_id to the GetYouTubeVideoEntry() method. """ if uri is None and video_id is None: raise YouTubeError('You must provide at least a uri or a video_id ' 'to the get_youtube_video_entry() method') elif video_id and uri is None: uri = '%s/%s' % (YOUTUBE_VIDEO_FEED_URI, video_id) return self.get_feed(uri, desired_class=gdata.youtube.data.VideoEntry, auth_token=auth_token, **kwargs) GetVideoEntry = get_video_entry def get_caption_feed(self, uri): """Retrieve a Caption feed of tracks. Args: uri: A string representing the caption feed's URI to be retrieved. Returns: A YouTube CaptionFeed if successfully retrieved. """ return self.get_feed(uri, desired_class=gdata.youtube.data.CaptionFeed) GetCaptionFeed = get_caption_feed def get_caption_track(self, track_url, client_id, developer_key, auth_token=None, **kwargs): http_request = atom.http_core.HttpRequest(uri = track_url, method = 'GET') dev_key = 'key=' + developer_key authsub = 'AuthSub token="' + str(auth_token) + '"' http_request.headers = { 'Authorization': authsub, 'X-GData-Client': client_id, 'X-GData-Key': dev_key } return self.request(http_request=http_request, **kwargs) GetCaptionTrack = get_caption_track def create_track(self, video_id, title, language, body, client_id, developer_key, auth_token=None, title_type='text', **kwargs): """Creates a closed-caption track and adds to an existing YouTube video. """ new_entry = gdata.youtube.data.TrackEntry( content = gdata.youtube.data.TrackContent(text = body, lang = language)) uri = YOUTUBE_CAPTION_FEED_URI % video_id http_request = atom.http_core.HttpRequest(uri = uri, method = 'POST') dev_key = 'key=' + developer_key authsub = 'AuthSub token="' + str(auth_token) + '"' http_request.headers = { 'Content-Type': YOUTUBE_CAPTION_MIME_TYPE, 'Content-Language': language, 'Slug': title, 'Authorization': authsub, 'GData-Version': self.api_version, 'X-GData-Client': client_id, 'X-GData-Key': dev_key } http_request.add_body_part(body, http_request.headers['Content-Type']) return self.request(http_request = http_request, desired_class = new_entry.__class__, **kwargs) CreateTrack = create_track def delete_track(self, video_id, track, client_id, developer_key, auth_token=None, **kwargs): """Deletes a track.""" if isinstance(track, gdata.youtube.data.TrackEntry): track_id_text_node = track.get_id().split(':') track_id = track_id_text_node[3] else: track_id = track uri = YOUTUBE_CAPTION_URI % (video_id, track_id) http_request = atom.http_core.HttpRequest(uri = uri, method = 'DELETE') dev_key = 'key=' + developer_key authsub = 'AuthSub token="' + str(auth_token) + '"' http_request.headers = { 'Authorization': authsub, 'GData-Version': self.api_version, 'X-GData-Client': client_id, 'X-GData-Key': dev_key } return self.request(http_request=http_request, **kwargs) DeleteTrack = delete_track def update_track(self, video_id, track, body, client_id, developer_key, auth_token=None, **kwargs): """Updates a closed-caption track for an existing YouTube video. """ track_id_text_node = track.get_id().split(':') track_id = track_id_text_node[3] uri = YOUTUBE_CAPTION_URI % (video_id, track_id) http_request = atom.http_core.HttpRequest(uri = uri, method = 'PUT') dev_key = 'key=' + developer_key authsub = 'AuthSub token="' + str(auth_token) + '"' http_request.headers = { 'Content-Type': YOUTUBE_CAPTION_MIME_TYPE, 'Authorization': authsub, 'GData-Version': self.api_version, 'X-GData-Client': client_id, 'X-GData-Key': dev_key } http_request.add_body_part(body, http_request.headers['Content-Type']) return self.request(http_request = http_request, desired_class = track.__class__, **kwargs) UpdateTrack = update_track gdata-2.0.18/src/gdata/youtube/service.py0000640036466300116100000016145712156622363020331 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright (C) 2008 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. """YouTubeService extends GDataService to streamline YouTube operations. YouTubeService: Provides methods to perform CRUD operations on YouTube feeds. Extends GDataService. """ __author__ = ('api.stephaniel@gmail.com (Stephanie Liu), ' 'api.jhartmann@gmail.com (Jochen Hartmann)') try: from xml.etree import cElementTree as ElementTree except ImportError: try: import cElementTree as ElementTree except ImportError: try: from xml.etree import ElementTree except ImportError: from elementtree import ElementTree import os import atom import gdata import gdata.service import gdata.youtube YOUTUBE_SERVER = 'gdata.youtube.com' YOUTUBE_SERVICE = 'youtube' YOUTUBE_CLIENTLOGIN_AUTHENTICATION_URL = 'https://www.google.com/accounts/ClientLogin' YOUTUBE_SUPPORTED_UPLOAD_TYPES = ('mov', 'avi', 'wmv', 'mpg', 'quicktime', 'flv', 'mp4', 'x-flv') YOUTUBE_QUERY_VALID_TIME_PARAMETERS = ('today', 'this_week', 'this_month', 'all_time') YOUTUBE_QUERY_VALID_ORDERBY_PARAMETERS = ('published', 'viewCount', 'rating', 'relevance') YOUTUBE_QUERY_VALID_RACY_PARAMETERS = ('include', 'exclude') YOUTUBE_QUERY_VALID_FORMAT_PARAMETERS = ('1', '5', '6') YOUTUBE_STANDARDFEEDS = ('most_recent', 'recently_featured', 'top_rated', 'most_viewed','watch_on_mobile') YOUTUBE_UPLOAD_URI = 'https://uploads.gdata.youtube.com/feeds/api/users' YOUTUBE_UPLOAD_TOKEN_URI = 'https://gdata.youtube.com/action/GetUploadToken' YOUTUBE_VIDEO_URI = 'https://gdata.youtube.com/feeds/api/videos' YOUTUBE_USER_FEED_URI = 'https://gdata.youtube.com/feeds/api/users' YOUTUBE_PLAYLIST_FEED_URI = 'https://gdata.youtube.com/feeds/api/playlists' YOUTUBE_STANDARD_FEEDS = 'https://gdata.youtube.com/feeds/api/standardfeeds' YOUTUBE_STANDARD_TOP_RATED_URI = '%s/%s' % (YOUTUBE_STANDARD_FEEDS, 'top_rated') YOUTUBE_STANDARD_MOST_VIEWED_URI = '%s/%s' % (YOUTUBE_STANDARD_FEEDS, 'most_viewed') YOUTUBE_STANDARD_RECENTLY_FEATURED_URI = '%s/%s' % (YOUTUBE_STANDARD_FEEDS, 'recently_featured') YOUTUBE_STANDARD_WATCH_ON_MOBILE_URI = '%s/%s' % (YOUTUBE_STANDARD_FEEDS, 'watch_on_mobile') YOUTUBE_STANDARD_TOP_FAVORITES_URI = '%s/%s' % (YOUTUBE_STANDARD_FEEDS, 'top_favorites') YOUTUBE_STANDARD_MOST_RECENT_URI = '%s/%s' % (YOUTUBE_STANDARD_FEEDS, 'most_recent') YOUTUBE_STANDARD_MOST_DISCUSSED_URI = '%s/%s' % (YOUTUBE_STANDARD_FEEDS, 'most_discussed') YOUTUBE_STANDARD_MOST_LINKED_URI = '%s/%s' % (YOUTUBE_STANDARD_FEEDS, 'most_linked') YOUTUBE_STANDARD_MOST_RESPONDED_URI = '%s/%s' % (YOUTUBE_STANDARD_FEEDS, 'most_responded') YOUTUBE_SCHEMA = 'http://gdata.youtube.com/schemas' YOUTUBE_RATING_LINK_REL = '%s#video.ratings' % YOUTUBE_SCHEMA YOUTUBE_COMPLAINT_CATEGORY_SCHEME = '%s/%s' % (YOUTUBE_SCHEMA, 'complaint-reasons.cat') YOUTUBE_SUBSCRIPTION_CATEGORY_SCHEME = '%s/%s' % (YOUTUBE_SCHEMA, 'subscriptiontypes.cat') YOUTUBE_COMPLAINT_CATEGORY_TERMS = ('PORN', 'VIOLENCE', 'HATE', 'DANGEROUS', 'RIGHTS', 'SPAM') YOUTUBE_CONTACT_STATUS = ('accepted', 'rejected') YOUTUBE_CONTACT_CATEGORY = ('Friends', 'Family') UNKOWN_ERROR = 1000 YOUTUBE_BAD_REQUEST = 400 YOUTUBE_CONFLICT = 409 YOUTUBE_INTERNAL_SERVER_ERROR = 500 YOUTUBE_INVALID_ARGUMENT = 601 YOUTUBE_INVALID_CONTENT_TYPE = 602 YOUTUBE_NOT_A_VIDEO = 603 YOUTUBE_INVALID_KIND = 604 class Error(Exception): """Base class for errors within the YouTube service.""" pass class RequestError(Error): """Error class that is thrown in response to an invalid HTTP Request.""" pass class YouTubeError(Error): """YouTube service specific error class.""" pass class YouTubeService(gdata.service.GDataService): """Client for the YouTube service. Performs all documented Google Data YouTube API functions, such as inserting, updating and deleting videos, comments, playlist, subscriptions etc. YouTube Service requires authentication for any write, update or delete actions. Attributes: email: An optional string identifying the user. Required only for authenticated actions. password: An optional string identifying the user's password. source: An optional string identifying the name of your application. server: An optional address of the YouTube API server. gdata.youtube.com is provided as the default value. additional_headers: An optional dictionary containing additional headers to be passed along with each request. Use to store developer key. client_id: An optional string identifying your application, required for authenticated requests, along with a developer key. developer_key: An optional string value. Register your application at http://code.google.com/apis/youtube/dashboard to obtain a (free) key. """ ssl = True def __init__(self, email=None, password=None, source=None, server=YOUTUBE_SERVER, additional_headers=None, client_id=None, developer_key=None, **kwargs): """Creates a client for the YouTube service. Args: email: string (optional) The user's email address, used for authentication. password: string (optional) The user's password. source: string (optional) The name of the user's application. server: string (optional) The name of the server to which a connection will be opened. Default value: 'gdata.youtube.com'. client_id: string (optional) Identifies your application, required for authenticated requests, along with a developer key. developer_key: string (optional) Register your application at http://code.google.com/apis/youtube/dashboard to obtain a (free) key. **kwargs: The other parameters to pass to gdata.service.GDataService constructor. """ gdata.service.GDataService.__init__( self, email=email, password=password, service=YOUTUBE_SERVICE, source=source, server=server, additional_headers=additional_headers, **kwargs) if client_id is not None: self.additional_headers['X-Gdata-Client'] = client_id if developer_key is not None: self.additional_headers['X-GData-Key'] = 'key=%s' % developer_key if 'GData-Version' not in self.additional_headers: self.additional_headers['GData-Version'] = 1 self.auth_service_url = YOUTUBE_CLIENTLOGIN_AUTHENTICATION_URL def GetYouTubeVideoFeed(self, uri): """Retrieve a YouTubeVideoFeed. Args: uri: A string representing the URI of the feed that is to be retrieved. Returns: A YouTubeVideoFeed if successfully retrieved. """ return self.Get(uri, converter=gdata.youtube.YouTubeVideoFeedFromString) def GetYouTubeVideoEntry(self, uri=None, video_id=None): """Retrieve a YouTubeVideoEntry. Either a uri or a video_id must be provided. Args: uri: An optional string representing the URI of the entry that is to be retrieved. video_id: An optional string representing the ID of the video. Returns: A YouTubeVideoFeed if successfully retrieved. Raises: YouTubeError: You must provide at least a uri or a video_id to the GetYouTubeVideoEntry() method. """ if uri is None and video_id is None: raise YouTubeError('You must provide at least a uri or a video_id ' 'to the GetYouTubeVideoEntry() method') elif video_id and not uri: uri = '%s/%s' % (YOUTUBE_VIDEO_URI, video_id) return self.Get(uri, converter=gdata.youtube.YouTubeVideoEntryFromString) def GetYouTubeContactFeed(self, uri=None, username='default'): """Retrieve a YouTubeContactFeed. Either a uri or a username must be provided. Args: uri: An optional string representing the URI of the contact feed that is to be retrieved. username: An optional string representing the username. Defaults to the currently authenticated user. Returns: A YouTubeContactFeed if successfully retrieved. Raises: YouTubeError: You must provide at least a uri or a username to the GetYouTubeContactFeed() method. """ if uri is None: uri = '%s/%s/%s' % (YOUTUBE_USER_FEED_URI, username, 'contacts') return self.Get(uri, converter=gdata.youtube.YouTubeContactFeedFromString) def GetYouTubeContactEntry(self, uri): """Retrieve a YouTubeContactEntry. Args: uri: A string representing the URI of the contact entry that is to be retrieved. Returns: A YouTubeContactEntry if successfully retrieved. """ return self.Get(uri, converter=gdata.youtube.YouTubeContactEntryFromString) def GetYouTubeVideoCommentFeed(self, uri=None, video_id=None): """Retrieve a YouTubeVideoCommentFeed. Either a uri or a video_id must be provided. Args: uri: An optional string representing the URI of the comment feed that is to be retrieved. video_id: An optional string representing the ID of the video for which to retrieve the comment feed. Returns: A YouTubeVideoCommentFeed if successfully retrieved. Raises: YouTubeError: You must provide at least a uri or a video_id to the GetYouTubeVideoCommentFeed() method. """ if uri is None and video_id is None: raise YouTubeError('You must provide at least a uri or a video_id ' 'to the GetYouTubeVideoCommentFeed() method') elif video_id and not uri: uri = '%s/%s/%s' % (YOUTUBE_VIDEO_URI, video_id, 'comments') return self.Get( uri, converter=gdata.youtube.YouTubeVideoCommentFeedFromString) def GetYouTubeVideoCommentEntry(self, uri): """Retrieve a YouTubeVideoCommentEntry. Args: uri: A string representing the URI of the comment entry that is to be retrieved. Returns: A YouTubeCommentEntry if successfully retrieved. """ return self.Get( uri, converter=gdata.youtube.YouTubeVideoCommentEntryFromString) def GetYouTubeUserFeed(self, uri=None, username=None): """Retrieve a YouTubeVideoFeed of user uploaded videos Either a uri or a username must be provided. This will retrieve list of videos uploaded by specified user. The uri will be of format "http://gdata.youtube.com/feeds/api/users/{username}/uploads". Args: uri: An optional string representing the URI of the user feed that is to be retrieved. username: An optional string representing the username. Returns: A YouTubeUserFeed if successfully retrieved. Raises: YouTubeError: You must provide at least a uri or a username to the GetYouTubeUserFeed() method. """ if uri is None and username is None: raise YouTubeError('You must provide at least a uri or a username ' 'to the GetYouTubeUserFeed() method') elif username and not uri: uri = '%s/%s/%s' % (YOUTUBE_USER_FEED_URI, username, 'uploads') return self.Get(uri, converter=gdata.youtube.YouTubeUserFeedFromString) def GetYouTubeUserEntry(self, uri=None, username=None): """Retrieve a YouTubeUserEntry. Either a uri or a username must be provided. Args: uri: An optional string representing the URI of the user entry that is to be retrieved. username: An optional string representing the username. Returns: A YouTubeUserEntry if successfully retrieved. Raises: YouTubeError: You must provide at least a uri or a username to the GetYouTubeUserEntry() method. """ if uri is None and username is None: raise YouTubeError('You must provide at least a uri or a username ' 'to the GetYouTubeUserEntry() method') elif username and not uri: uri = '%s/%s' % (YOUTUBE_USER_FEED_URI, username) return self.Get(uri, converter=gdata.youtube.YouTubeUserEntryFromString) def GetYouTubePlaylistFeed(self, uri=None, username='default'): """Retrieve a YouTubePlaylistFeed (a feed of playlists for a user). Either a uri or a username must be provided. Args: uri: An optional string representing the URI of the playlist feed that is to be retrieved. username: An optional string representing the username. Defaults to the currently authenticated user. Returns: A YouTubePlaylistFeed if successfully retrieved. Raises: YouTubeError: You must provide at least a uri or a username to the GetYouTubePlaylistFeed() method. """ if uri is None: uri = '%s/%s/%s' % (YOUTUBE_USER_FEED_URI, username, 'playlists') return self.Get(uri, converter=gdata.youtube.YouTubePlaylistFeedFromString) def GetYouTubePlaylistEntry(self, uri): """Retrieve a YouTubePlaylistEntry. Args: uri: A string representing the URI of the playlist feed that is to be retrieved. Returns: A YouTubePlaylistEntry if successfully retrieved. """ return self.Get(uri, converter=gdata.youtube.YouTubePlaylistEntryFromString) def GetYouTubePlaylistVideoFeed(self, uri=None, playlist_id=None): """Retrieve a YouTubePlaylistVideoFeed (a feed of videos on a playlist). Either a uri or a playlist_id must be provided. Args: uri: An optional string representing the URI of the playlist video feed that is to be retrieved. playlist_id: An optional string representing the Id of the playlist whose playlist video feed is to be retrieved. Returns: A YouTubePlaylistVideoFeed if successfully retrieved. Raises: YouTubeError: You must provide at least a uri or a playlist_id to the GetYouTubePlaylistVideoFeed() method. """ if uri is None and playlist_id is None: raise YouTubeError('You must provide at least a uri or a playlist_id ' 'to the GetYouTubePlaylistVideoFeed() method') elif playlist_id and not uri: uri = '%s/%s' % (YOUTUBE_PLAYLIST_FEED_URI, playlist_id) return self.Get( uri, converter=gdata.youtube.YouTubePlaylistVideoFeedFromString) def GetYouTubeVideoResponseFeed(self, uri=None, video_id=None): """Retrieve a YouTubeVideoResponseFeed. Either a uri or a playlist_id must be provided. Args: uri: An optional string representing the URI of the video response feed that is to be retrieved. video_id: An optional string representing the ID of the video whose response feed is to be retrieved. Returns: A YouTubeVideoResponseFeed if successfully retrieved. Raises: YouTubeError: You must provide at least a uri or a video_id to the GetYouTubeVideoResponseFeed() method. """ if uri is None and video_id is None: raise YouTubeError('You must provide at least a uri or a video_id ' 'to the GetYouTubeVideoResponseFeed() method') elif video_id and not uri: uri = '%s/%s/%s' % (YOUTUBE_VIDEO_URI, video_id, 'responses') return self.Get( uri, converter=gdata.youtube.YouTubeVideoResponseFeedFromString) def GetYouTubeVideoResponseEntry(self, uri): """Retrieve a YouTubeVideoResponseEntry. Args: uri: A string representing the URI of the video response entry that is to be retrieved. Returns: A YouTubeVideoResponseEntry if successfully retrieved. """ return self.Get( uri, converter=gdata.youtube.YouTubeVideoResponseEntryFromString) def GetYouTubeSubscriptionFeed(self, uri=None, username='default'): """Retrieve a YouTubeSubscriptionFeed. Either the uri of the feed or a username must be provided. Args: uri: An optional string representing the URI of the feed that is to be retrieved. username: An optional string representing the username whose subscription feed is to be retrieved. Defaults to the currently authenticted user. Returns: A YouTubeVideoSubscriptionFeed if successfully retrieved. """ if uri is None: uri = '%s/%s/%s' % (YOUTUBE_USER_FEED_URI, username, 'subscriptions') return self.Get( uri, converter=gdata.youtube.YouTubeSubscriptionFeedFromString) def GetYouTubeSubscriptionEntry(self, uri): """Retrieve a YouTubeSubscriptionEntry. Args: uri: A string representing the URI of the entry that is to be retrieved. Returns: A YouTubeVideoSubscriptionEntry if successfully retrieved. """ return self.Get( uri, converter=gdata.youtube.YouTubeSubscriptionEntryFromString) def GetYouTubeRelatedVideoFeed(self, uri=None, video_id=None): """Retrieve a YouTubeRelatedVideoFeed. Either a uri for the feed or a video_id is required. Args: uri: An optional string representing the URI of the feed that is to be retrieved. video_id: An optional string representing the ID of the video for which to retrieve the related video feed. Returns: A YouTubeRelatedVideoFeed if successfully retrieved. Raises: YouTubeError: You must provide at least a uri or a video_id to the GetYouTubeRelatedVideoFeed() method. """ if uri is None and video_id is None: raise YouTubeError('You must provide at least a uri or a video_id ' 'to the GetYouTubeRelatedVideoFeed() method') elif video_id and not uri: uri = '%s/%s/%s' % (YOUTUBE_VIDEO_URI, video_id, 'related') return self.Get( uri, converter=gdata.youtube.YouTubeVideoFeedFromString) def GetTopRatedVideoFeed(self): """Retrieve the 'top_rated' standard video feed. Returns: A YouTubeVideoFeed if successfully retrieved. """ return self.GetYouTubeVideoFeed(YOUTUBE_STANDARD_TOP_RATED_URI) def GetMostViewedVideoFeed(self): """Retrieve the 'most_viewed' standard video feed. Returns: A YouTubeVideoFeed if successfully retrieved. """ return self.GetYouTubeVideoFeed(YOUTUBE_STANDARD_MOST_VIEWED_URI) def GetRecentlyFeaturedVideoFeed(self): """Retrieve the 'recently_featured' standard video feed. Returns: A YouTubeVideoFeed if successfully retrieved. """ return self.GetYouTubeVideoFeed(YOUTUBE_STANDARD_RECENTLY_FEATURED_URI) def GetWatchOnMobileVideoFeed(self): """Retrieve the 'watch_on_mobile' standard video feed. Returns: A YouTubeVideoFeed if successfully retrieved. """ return self.GetYouTubeVideoFeed(YOUTUBE_STANDARD_WATCH_ON_MOBILE_URI) def GetTopFavoritesVideoFeed(self): """Retrieve the 'top_favorites' standard video feed. Returns: A YouTubeVideoFeed if successfully retrieved. """ return self.GetYouTubeVideoFeed(YOUTUBE_STANDARD_TOP_FAVORITES_URI) def GetMostRecentVideoFeed(self): """Retrieve the 'most_recent' standard video feed. Returns: A YouTubeVideoFeed if successfully retrieved. """ return self.GetYouTubeVideoFeed(YOUTUBE_STANDARD_MOST_RECENT_URI) def GetMostDiscussedVideoFeed(self): """Retrieve the 'most_discussed' standard video feed. Returns: A YouTubeVideoFeed if successfully retrieved. """ return self.GetYouTubeVideoFeed(YOUTUBE_STANDARD_MOST_DISCUSSED_URI) def GetMostLinkedVideoFeed(self): """Retrieve the 'most_linked' standard video feed. Returns: A YouTubeVideoFeed if successfully retrieved. """ return self.GetYouTubeVideoFeed(YOUTUBE_STANDARD_MOST_LINKED_URI) def GetMostRespondedVideoFeed(self): """Retrieve the 'most_responded' standard video feed. Returns: A YouTubeVideoFeed if successfully retrieved. """ return self.GetYouTubeVideoFeed(YOUTUBE_STANDARD_MOST_RESPONDED_URI) def GetUserFavoritesFeed(self, username='default'): """Retrieve the favorites feed for a given user. Args: username: An optional string representing the username whose favorites feed is to be retrieved. Defaults to the currently authenticated user. Returns: A YouTubeVideoFeed if successfully retrieved. """ favorites_feed_uri = '%s/%s/%s' % (YOUTUBE_USER_FEED_URI, username, 'favorites') return self.GetYouTubeVideoFeed(favorites_feed_uri) def InsertVideoEntry(self, video_entry, filename_or_handle, youtube_username='default', content_type='video/quicktime'): """Upload a new video to YouTube using the direct upload mechanism. Needs authentication. Args: video_entry: The YouTubeVideoEntry to upload. filename_or_handle: A file-like object or file name where the video will be read from. youtube_username: An optional string representing the username into whose account this video is to be uploaded to. Defaults to the currently authenticated user. content_type: An optional string representing internet media type (a.k.a. mime type) of the media object. Currently the YouTube API supports these types: o video/mpeg o video/quicktime o video/x-msvideo o video/mp4 o video/x-flv Returns: The newly created YouTubeVideoEntry if successful. Raises: AssertionError: video_entry must be a gdata.youtube.VideoEntry instance. YouTubeError: An error occurred trying to read the video file provided. gdata.service.RequestError: An error occurred trying to upload the video to the API server. """ # We need to perform a series of checks on the video_entry and on the # file that we plan to upload, such as checking whether we have a valid # video_entry and that the file is the correct type and readable, prior # to performing the actual POST request. try: assert(isinstance(video_entry, gdata.youtube.YouTubeVideoEntry)) except AssertionError: raise YouTubeError({'status':YOUTUBE_INVALID_ARGUMENT, 'body':'`video_entry` must be a gdata.youtube.VideoEntry instance', 'reason':'Found %s, not VideoEntry' % type(video_entry) }) #majtype, mintype = content_type.split('/') # #try: # assert(mintype in YOUTUBE_SUPPORTED_UPLOAD_TYPES) #except (ValueError, AssertionError): # raise YouTubeError({'status':YOUTUBE_INVALID_CONTENT_TYPE, # 'body':'This is not a valid content type: %s' % content_type, # 'reason':'Accepted content types: %s' % # ['video/%s' % (t) for t in YOUTUBE_SUPPORTED_UPLOAD_TYPES]}) if (isinstance(filename_or_handle, (str, unicode)) and os.path.exists(filename_or_handle)): mediasource = gdata.MediaSource() mediasource.setFile(filename_or_handle, content_type) elif hasattr(filename_or_handle, 'read'): import StringIO if hasattr(filename_or_handle, 'seek'): filename_or_handle.seek(0) file_handle = filename_or_handle name = 'video' if hasattr(filename_or_handle, 'name'): name = filename_or_handle.name mediasource = gdata.MediaSource(file_handle, content_type, content_length=file_handle.len, file_name=name) else: raise YouTubeError({'status':YOUTUBE_INVALID_ARGUMENT, 'body': '`filename_or_handle` must be a path name or a file-like object', 'reason': ('Found %s, not path name or object ' 'with a .read() method' % type(filename_or_handle))}) upload_uri = '%s/%s/%s' % (YOUTUBE_UPLOAD_URI, youtube_username, 'uploads') self.additional_headers['Slug'] = mediasource.file_name # Using a nested try statement to retain Python 2.4 compatibility try: try: return self.Post(video_entry, uri=upload_uri, media_source=mediasource, converter=gdata.youtube.YouTubeVideoEntryFromString) except gdata.service.RequestError, e: raise YouTubeError(e.args[0]) finally: del(self.additional_headers['Slug']) def CheckUploadStatus(self, video_entry=None, video_id=None): """Check upload status on a recently uploaded video entry. Needs authentication. Either video_entry or video_id must be provided. Args: video_entry: An optional YouTubeVideoEntry whose upload status to check video_id: An optional string representing the ID of the uploaded video whose status is to be checked. Returns: A tuple containing (video_upload_state, detailed_message) or None if no status information is found. Raises: YouTubeError: You must provide at least a video_entry or a video_id to the CheckUploadStatus() method. """ if video_entry is None and video_id is None: raise YouTubeError('You must provide at least a uri or a video_id ' 'to the CheckUploadStatus() method') elif video_id and not video_entry: video_entry = self.GetYouTubeVideoEntry(video_id=video_id) control = video_entry.control if control is not None: draft = control.draft if draft is not None: if draft.text == 'yes': yt_state = control.extension_elements[0] if yt_state is not None: state_value = yt_state.attributes['name'] message = '' if yt_state.text is not None: message = yt_state.text return (state_value, message) def GetFormUploadToken(self, video_entry, uri=YOUTUBE_UPLOAD_TOKEN_URI): """Receives a YouTube Token and a YouTube PostUrl from a YouTubeVideoEntry. Needs authentication. Args: video_entry: The YouTubeVideoEntry to upload (meta-data only). uri: An optional string representing the URI from where to fetch the token information. Defaults to the YOUTUBE_UPLOADTOKEN_URI. Returns: A tuple containing the URL to which to post your video file, along with the youtube token that must be included with your upload in the form of: (post_url, youtube_token). """ try: response = self.Post(video_entry, uri) except gdata.service.RequestError, e: raise YouTubeError(e.args[0]) tree = ElementTree.fromstring(response) for child in tree: if child.tag == 'url': post_url = child.text elif child.tag == 'token': youtube_token = child.text return (post_url, youtube_token) def UpdateVideoEntry(self, video_entry): """Updates a video entry's meta-data. Needs authentication. Args: video_entry: The YouTubeVideoEntry to update, containing updated meta-data. Returns: An updated YouTubeVideoEntry on success or None. """ for link in video_entry.link: if link.rel == 'edit': edit_uri = link.href return self.Put(video_entry, uri=edit_uri, converter=gdata.youtube.YouTubeVideoEntryFromString) def DeleteVideoEntry(self, video_entry): """Deletes a video entry. Needs authentication. Args: video_entry: The YouTubeVideoEntry to be deleted. Returns: True if entry was deleted successfully. """ for link in video_entry.link: if link.rel == 'edit': edit_uri = link.href return self.Delete(edit_uri) def AddRating(self, rating_value, video_entry): """Add a rating to a video entry. Needs authentication. Args: rating_value: The integer value for the rating (between 1 and 5). video_entry: The YouTubeVideoEntry to be rated. Returns: True if the rating was added successfully. Raises: YouTubeError: rating_value must be between 1 and 5 in AddRating(). """ if rating_value < 1 or rating_value > 5: raise YouTubeError('rating_value must be between 1 and 5 in AddRating()') entry = gdata.GDataEntry() rating = gdata.youtube.Rating(min='1', max='5') rating.extension_attributes['name'] = 'value' rating.extension_attributes['value'] = str(rating_value) entry.extension_elements.append(rating) for link in video_entry.link: if link.rel == YOUTUBE_RATING_LINK_REL: rating_uri = link.href return self.Post(entry, uri=rating_uri) def AddComment(self, comment_text, video_entry): """Add a comment to a video entry. Needs authentication. Note that each comment that is posted must contain the video entry that it is to be posted to. Args: comment_text: A string representing the text of the comment. video_entry: The YouTubeVideoEntry to be commented on. Returns: True if the comment was added successfully. """ content = atom.Content(text=comment_text) comment_entry = gdata.youtube.YouTubeVideoCommentEntry(content=content) comment_post_uri = video_entry.comments.feed_link[0].href return self.Post(comment_entry, uri=comment_post_uri) def AddVideoResponse(self, video_id_to_respond_to, video_response): """Add a video response. Needs authentication. Args: video_id_to_respond_to: A string representing the ID of the video to be responded to. video_response: YouTubeVideoEntry to be posted as a response. Returns: True if video response was posted successfully. """ post_uri = '%s/%s/%s' % (YOUTUBE_VIDEO_URI, video_id_to_respond_to, 'responses') return self.Post(video_response, uri=post_uri) def DeleteVideoResponse(self, video_id, response_video_id): """Delete a video response. Needs authentication. Args: video_id: A string representing the ID of video that contains the response. response_video_id: A string representing the ID of the video that was posted as a response. Returns: True if video response was deleted succcessfully. """ delete_uri = '%s/%s/%s/%s' % (YOUTUBE_VIDEO_URI, video_id, 'responses', response_video_id) return self.Delete(delete_uri) def AddComplaint(self, complaint_text, complaint_term, video_id): """Add a complaint for a particular video entry. Needs authentication. Args: complaint_text: A string representing the complaint text. complaint_term: A string representing the complaint category term. video_id: A string representing the ID of YouTubeVideoEntry to complain about. Returns: True if posted successfully. Raises: YouTubeError: Your complaint_term is not valid. """ if complaint_term not in YOUTUBE_COMPLAINT_CATEGORY_TERMS: raise YouTubeError('Your complaint_term is not valid') content = atom.Content(text=complaint_text) category = atom.Category(term=complaint_term, scheme=YOUTUBE_COMPLAINT_CATEGORY_SCHEME) complaint_entry = gdata.GDataEntry(content=content, category=[category]) post_uri = '%s/%s/%s' % (YOUTUBE_VIDEO_URI, video_id, 'complaints') return self.Post(complaint_entry, post_uri) def AddVideoEntryToFavorites(self, video_entry, username='default'): """Add a video entry to a users favorite feed. Needs authentication. Args: video_entry: The YouTubeVideoEntry to add. username: An optional string representing the username to whose favorite feed you wish to add the entry. Defaults to the currently authenticated user. Returns: The posted YouTubeVideoEntry if successfully posted. """ post_uri = '%s/%s/%s' % (YOUTUBE_USER_FEED_URI, username, 'favorites') return self.Post(video_entry, post_uri, converter=gdata.youtube.YouTubeVideoEntryFromString) def DeleteVideoEntryFromFavorites(self, video_id, username='default'): """Delete a video entry from the users favorite feed. Needs authentication. Args: video_id: A string representing the ID of the video that is to be removed username: An optional string representing the username of the user's favorite feed. Defaults to the currently authenticated user. Returns: True if entry was successfully deleted. """ edit_link = '%s/%s/%s/%s' % (YOUTUBE_USER_FEED_URI, username, 'favorites', video_id) return self.Delete(edit_link) def AddPlaylist(self, playlist_title, playlist_description, playlist_private=None): """Add a new playlist to the currently authenticated users account. Needs authentication. Args: playlist_title: A string representing the title for the new playlist. playlist_description: A string representing the description of the playlist. playlist_private: An optional boolean, set to True if the playlist is to be private. Returns: The YouTubePlaylistEntry if successfully posted. """ playlist_entry = gdata.youtube.YouTubePlaylistEntry( title=atom.Title(text=playlist_title), description=gdata.youtube.Description(text=playlist_description)) if playlist_private: playlist_entry.private = gdata.youtube.Private() playlist_post_uri = '%s/%s/%s' % (YOUTUBE_USER_FEED_URI, 'default', 'playlists') return self.Post(playlist_entry, playlist_post_uri, converter=gdata.youtube.YouTubePlaylistEntryFromString) def UpdatePlaylist(self, playlist_id, new_playlist_title, new_playlist_description, playlist_private=None, username='default'): """Update a playlist with new meta-data. Needs authentication. Args: playlist_id: A string representing the ID of the playlist to be updated. new_playlist_title: A string representing a new title for the playlist. new_playlist_description: A string representing a new description for the playlist. playlist_private: An optional boolean, set to True if the playlist is to be private. username: An optional string representing the username whose playlist is to be updated. Defaults to the currently authenticated user. Returns: A YouTubePlaylistEntry if the update was successful. """ updated_playlist = gdata.youtube.YouTubePlaylistEntry( title=atom.Title(text=new_playlist_title), description=gdata.youtube.Description(text=new_playlist_description)) if playlist_private: updated_playlist.private = gdata.youtube.Private() playlist_put_uri = '%s/%s/playlists/%s' % (YOUTUBE_USER_FEED_URI, username, playlist_id) return self.Put(updated_playlist, playlist_put_uri, converter=gdata.youtube.YouTubePlaylistEntryFromString) def DeletePlaylist(self, playlist_uri): """Delete a playlist from the currently authenticated users playlists. Needs authentication. Args: playlist_uri: A string representing the URI of the playlist that is to be deleted. Returns: True if successfully deleted. """ return self.Delete(playlist_uri) def AddPlaylistVideoEntryToPlaylist( self, playlist_uri, video_id, custom_video_title=None, custom_video_description=None): """Add a video entry to a playlist, optionally providing a custom title and description. Needs authentication. Args: playlist_uri: A string representing the URI of the playlist to which this video entry is to be added. video_id: A string representing the ID of the video entry to add. custom_video_title: An optional string representing a custom title for the video (only shown on the playlist). custom_video_description: An optional string representing a custom description for the video (only shown on the playlist). Returns: A YouTubePlaylistVideoEntry if successfully posted. """ playlist_video_entry = gdata.youtube.YouTubePlaylistVideoEntry( atom_id=atom.Id(text=video_id)) if custom_video_title: playlist_video_entry.title = atom.Title(text=custom_video_title) if custom_video_description: playlist_video_entry.description = gdata.youtube.Description( text=custom_video_description) return self.Post(playlist_video_entry, playlist_uri, converter=gdata.youtube.YouTubePlaylistVideoEntryFromString) def UpdatePlaylistVideoEntryMetaData( self, playlist_uri, playlist_entry_id, new_video_title, new_video_description, new_video_position): """Update the meta data for a YouTubePlaylistVideoEntry. Needs authentication. Args: playlist_uri: A string representing the URI of the playlist that contains the entry to be updated. playlist_entry_id: A string representing the ID of the entry to be updated. new_video_title: A string representing the new title for the video entry. new_video_description: A string representing the new description for the video entry. new_video_position: An integer representing the new position on the playlist for the video. Returns: A YouTubePlaylistVideoEntry if the update was successful. """ playlist_video_entry = gdata.youtube.YouTubePlaylistVideoEntry( title=atom.Title(text=new_video_title), description=gdata.youtube.Description(text=new_video_description), position=gdata.youtube.Position(text=str(new_video_position))) playlist_put_uri = playlist_uri + '/' + playlist_entry_id return self.Put(playlist_video_entry, playlist_put_uri, converter=gdata.youtube.YouTubePlaylistVideoEntryFromString) def DeletePlaylistVideoEntry(self, playlist_uri, playlist_video_entry_id): """Delete a playlist video entry from a playlist. Needs authentication. Args: playlist_uri: A URI representing the playlist from which the playlist video entry is to be removed from. playlist_video_entry_id: A string representing id of the playlist video entry that is to be removed. Returns: True if entry was successfully deleted. """ delete_uri = '%s/%s' % (playlist_uri, playlist_video_entry_id) return self.Delete(delete_uri) def AddSubscriptionToChannel(self, username_to_subscribe_to, my_username = 'default'): """Add a new channel subscription to the currently authenticated users account. Needs authentication. Args: username_to_subscribe_to: A string representing the username of the channel to which we want to subscribe to. my_username: An optional string representing the name of the user which we want to subscribe. Defaults to currently authenticated user. Returns: A new YouTubeSubscriptionEntry if successfully posted. """ subscription_category = atom.Category( scheme=YOUTUBE_SUBSCRIPTION_CATEGORY_SCHEME, term='channel') subscription_username = gdata.youtube.Username( text=username_to_subscribe_to) subscription_entry = gdata.youtube.YouTubeSubscriptionEntry( category=subscription_category, username=subscription_username) post_uri = '%s/%s/%s' % (YOUTUBE_USER_FEED_URI, my_username, 'subscriptions') return self.Post(subscription_entry, post_uri, converter=gdata.youtube.YouTubeSubscriptionEntryFromString) def AddSubscriptionToFavorites(self, username, my_username = 'default'): """Add a new subscription to a users favorites to the currently authenticated user's account. Needs authentication Args: username: A string representing the username of the user's favorite feed to subscribe to. my_username: An optional string representing the username of the user that is to be subscribed. Defaults to currently authenticated user. Returns: A new YouTubeSubscriptionEntry if successful. """ subscription_category = atom.Category( scheme=YOUTUBE_SUBSCRIPTION_CATEGORY_SCHEME, term='favorites') subscription_username = gdata.youtube.Username(text=username) subscription_entry = gdata.youtube.YouTubeSubscriptionEntry( category=subscription_category, username=subscription_username) post_uri = '%s/%s/%s' % (YOUTUBE_USER_FEED_URI, my_username, 'subscriptions') return self.Post(subscription_entry, post_uri, converter=gdata.youtube.YouTubeSubscriptionEntryFromString) def AddSubscriptionToQuery(self, query, my_username = 'default'): """Add a new subscription to a specific keyword query to the currently authenticated user's account. Needs authentication Args: query: A string representing the keyword query to subscribe to. my_username: An optional string representing the username of the user that is to be subscribed. Defaults to currently authenticated user. Returns: A new YouTubeSubscriptionEntry if successful. """ subscription_category = atom.Category( scheme=YOUTUBE_SUBSCRIPTION_CATEGORY_SCHEME, term='query') subscription_query_string = gdata.youtube.QueryString(text=query) subscription_entry = gdata.youtube.YouTubeSubscriptionEntry( category=subscription_category, query_string=subscription_query_string) post_uri = '%s/%s/%s' % (YOUTUBE_USER_FEED_URI, my_username, 'subscriptions') return self.Post(subscription_entry, post_uri, converter=gdata.youtube.YouTubeSubscriptionEntryFromString) def DeleteSubscription(self, subscription_uri): """Delete a subscription from the currently authenticated user's account. Needs authentication. Args: subscription_uri: A string representing the URI of the subscription that is to be deleted. Returns: True if deleted successfully. """ return self.Delete(subscription_uri) def AddContact(self, contact_username, my_username='default'): """Add a new contact to the currently authenticated user's contact feed. Needs authentication. Args: contact_username: A string representing the username of the contact that you wish to add. my_username: An optional string representing the username to whose contact the new contact is to be added. Returns: A YouTubeContactEntry if added successfully. """ contact_category = atom.Category( scheme = 'http://gdata.youtube.com/schemas/2007/contact.cat', term = 'Friends') contact_username = gdata.youtube.Username(text=contact_username) contact_entry = gdata.youtube.YouTubeContactEntry( category=contact_category, username=contact_username) contact_post_uri = '%s/%s/%s' % (YOUTUBE_USER_FEED_URI, my_username, 'contacts') return self.Post(contact_entry, contact_post_uri, converter=gdata.youtube.YouTubeContactEntryFromString) def UpdateContact(self, contact_username, new_contact_status, new_contact_category, my_username='default'): """Update a contact, providing a new status and a new category. Needs authentication. Args: contact_username: A string representing the username of the contact that is to be updated. new_contact_status: A string representing the new status of the contact. This can either be set to 'accepted' or 'rejected'. new_contact_category: A string representing the new category for the contact, either 'Friends' or 'Family'. my_username: An optional string representing the username of the user whose contact feed we are modifying. Defaults to the currently authenticated user. Returns: A YouTubeContactEntry if updated succesfully. Raises: YouTubeError: New contact status must be within the accepted values. Or new contact category must be within the accepted categories. """ if new_contact_status not in YOUTUBE_CONTACT_STATUS: raise YouTubeError('New contact status must be one of %s' % (' '.join(YOUTUBE_CONTACT_STATUS))) if new_contact_category not in YOUTUBE_CONTACT_CATEGORY: raise YouTubeError('New contact category must be one of %s' % (' '.join(YOUTUBE_CONTACT_CATEGORY))) contact_category = atom.Category( scheme='http://gdata.youtube.com/schemas/2007/contact.cat', term=new_contact_category) contact_status = gdata.youtube.Status(text=new_contact_status) contact_entry = gdata.youtube.YouTubeContactEntry( category=contact_category, status=contact_status) contact_put_uri = '%s/%s/%s/%s' % (YOUTUBE_USER_FEED_URI, my_username, 'contacts', contact_username) return self.Put(contact_entry, contact_put_uri, converter=gdata.youtube.YouTubeContactEntryFromString) def DeleteContact(self, contact_username, my_username='default'): """Delete a contact from a users contact feed. Needs authentication. Args: contact_username: A string representing the username of the contact that is to be deleted. my_username: An optional string representing the username of the user's contact feed from which to delete the contact. Defaults to the currently authenticated user. Returns: True if the contact was deleted successfully """ contact_edit_uri = '%s/%s/%s/%s' % (YOUTUBE_USER_FEED_URI, my_username, 'contacts', contact_username) return self.Delete(contact_edit_uri) def _GetDeveloperKey(self): """Getter for Developer Key property. Returns: If the developer key has been set, a string representing the developer key is returned or None. """ if 'X-GData-Key' in self.additional_headers: return self.additional_headers['X-GData-Key'][4:] else: return None def _SetDeveloperKey(self, developer_key): """Setter for Developer Key property. Sets the developer key in the 'X-GData-Key' header. The actual value that is set is 'key=' plus the developer_key that was passed. """ self.additional_headers['X-GData-Key'] = 'key=' + developer_key developer_key = property(_GetDeveloperKey, _SetDeveloperKey, doc="""The Developer Key property""") def _GetClientId(self): """Getter for Client Id property. Returns: If the client_id has been set, a string representing it is returned or None. """ if 'X-Gdata-Client' in self.additional_headers: return self.additional_headers['X-Gdata-Client'] else: return None def _SetClientId(self, client_id): """Setter for Client Id property. Sets the 'X-Gdata-Client' header. """ self.additional_headers['X-Gdata-Client'] = client_id client_id = property(_GetClientId, _SetClientId, doc="""The ClientId property""") def Query(self, uri): """Performs a query and returns a resulting feed or entry. Args: uri: A string representing the URI of the feed that is to be queried. Returns: On success, a tuple in the form: (boolean succeeded=True, ElementTree._Element result) On failure, a tuple in the form: (boolean succeeded=False, {'status': HTTP status code from server, 'reason': HTTP reason from the server, 'body': HTTP body of the server's response}) """ result = self.Get(uri) return result def YouTubeQuery(self, query): """Performs a YouTube specific query and returns a resulting feed or entry. Args: query: A Query object or one if its sub-classes (YouTubeVideoQuery, YouTubeUserQuery or YouTubePlaylistQuery). Returns: Depending on the type of Query object submitted returns either a YouTubeVideoFeed, a YouTubeUserFeed, a YouTubePlaylistFeed. If the Query object provided was not YouTube-related, a tuple is returned. On success the tuple will be in this form: (boolean succeeded=True, ElementTree._Element result) On failure, the tuple will be in this form: (boolean succeeded=False, {'status': HTTP status code from server, 'reason': HTTP reason from the server, 'body': HTTP body of the server response}) """ result = self.Query(query.ToUri()) if isinstance(query, YouTubeUserQuery): return gdata.youtube.YouTubeUserFeedFromString(result.ToString()) elif isinstance(query, YouTubePlaylistQuery): return gdata.youtube.YouTubePlaylistFeedFromString(result.ToString()) elif isinstance(query, YouTubeVideoQuery): return gdata.youtube.YouTubeVideoFeedFromString(result.ToString()) else: return result class YouTubeVideoQuery(gdata.service.Query): """Subclasses gdata.service.Query to represent a YouTube Data API query. Attributes are set dynamically via properties. Properties correspond to the standard Google Data API query parameters with YouTube Data API extensions. Please refer to the API documentation for details. Attributes: vq: The vq parameter, which is only supported for video feeds, specifies a search query term. Refer to API documentation for further details. orderby: The orderby parameter, which is only supported for video feeds, specifies the value that will be used to sort videos in the search result set. Valid values for this parameter are relevance, published, viewCount and rating. time: The time parameter, which is only available for the top_rated, top_favorites, most_viewed, most_discussed, most_linked and most_responded standard feeds, restricts the search to videos uploaded within the specified time. Valid values for this parameter are today (1 day), this_week (7 days), this_month (1 month) and all_time. The default value for this parameter is all_time. format: The format parameter specifies that videos must be available in a particular video format. Refer to the API documentation for details. racy: The racy parameter allows a search result set to include restricted content as well as standard content. Valid values for this parameter are include and exclude. By default, restricted content is excluded. lr: The lr parameter restricts the search to videos that have a title, description or keywords in a specific language. Valid values for the lr parameter are ISO 639-1 two-letter language codes. restriction: The restriction parameter identifies the IP address that should be used to filter videos that can only be played in specific countries. location: A string of geo coordinates. Note that this is not used when the search is performed but rather to filter the returned videos for ones that match to the location entered. feed: str (optional) The base URL which is the beginning of the query URL. defaults to 'http://%s/feeds/videos' % (YOUTUBE_SERVER) """ def __init__(self, video_id=None, feed_type=None, text_query=None, params=None, categories=None, feed=None): if feed_type in YOUTUBE_STANDARDFEEDS and feed is None: feed = 'http://%s/feeds/standardfeeds/%s' % (YOUTUBE_SERVER, feed_type) elif (feed_type is 'responses' or feed_type is 'comments' and video_id and feed is None): feed = 'http://%s/feeds/videos/%s/%s' % (YOUTUBE_SERVER, video_id, feed_type) elif feed is None: feed = 'http://%s/feeds/videos' % (YOUTUBE_SERVER) gdata.service.Query.__init__(self, feed, text_query=text_query, params=params, categories=categories) def _GetVideoQuery(self): if 'vq' in self: return self['vq'] else: return None def _SetVideoQuery(self, val): self['vq'] = val vq = property(_GetVideoQuery, _SetVideoQuery, doc="""The video query (vq) query parameter""") def _GetOrderBy(self): if 'orderby' in self: return self['orderby'] else: return None def _SetOrderBy(self, val): if val not in YOUTUBE_QUERY_VALID_ORDERBY_PARAMETERS: if val.startswith('relevance_lang_') is False: raise YouTubeError('OrderBy must be one of: %s ' % ' '.join(YOUTUBE_QUERY_VALID_ORDERBY_PARAMETERS)) self['orderby'] = val orderby = property(_GetOrderBy, _SetOrderBy, doc="""The orderby query parameter""") def _GetTime(self): if 'time' in self: return self['time'] else: return None def _SetTime(self, val): if val not in YOUTUBE_QUERY_VALID_TIME_PARAMETERS: raise YouTubeError('Time must be one of: %s ' % ' '.join(YOUTUBE_QUERY_VALID_TIME_PARAMETERS)) self['time'] = val time = property(_GetTime, _SetTime, doc="""The time query parameter""") def _GetFormat(self): if 'format' in self: return self['format'] else: return None def _SetFormat(self, val): if val not in YOUTUBE_QUERY_VALID_FORMAT_PARAMETERS: raise YouTubeError('Format must be one of: %s ' % ' '.join(YOUTUBE_QUERY_VALID_FORMAT_PARAMETERS)) self['format'] = val format = property(_GetFormat, _SetFormat, doc="""The format query parameter""") def _GetRacy(self): if 'racy' in self: return self['racy'] else: return None def _SetRacy(self, val): if val not in YOUTUBE_QUERY_VALID_RACY_PARAMETERS: raise YouTubeError('Racy must be one of: %s ' % ' '.join(YOUTUBE_QUERY_VALID_RACY_PARAMETERS)) self['racy'] = val racy = property(_GetRacy, _SetRacy, doc="""The racy query parameter""") def _GetLanguageRestriction(self): if 'lr' in self: return self['lr'] else: return None def _SetLanguageRestriction(self, val): self['lr'] = val lr = property(_GetLanguageRestriction, _SetLanguageRestriction, doc="""The lr (language restriction) query parameter""") def _GetIPRestriction(self): if 'restriction' in self: return self['restriction'] else: return None def _SetIPRestriction(self, val): self['restriction'] = val restriction = property(_GetIPRestriction, _SetIPRestriction, doc="""The restriction query parameter""") def _GetLocation(self): if 'location' in self: return self['location'] else: return None def _SetLocation(self, val): self['location'] = val location = property(_GetLocation, _SetLocation, doc="""The location query parameter""") class YouTubeUserQuery(YouTubeVideoQuery): """Subclasses YouTubeVideoQuery to perform user-specific queries. Attributes are set dynamically via properties. Properties correspond to the standard Google Data API query parameters with YouTube Data API extensions. """ def __init__(self, username=None, feed_type=None, subscription_id=None, text_query=None, params=None, categories=None): uploads_favorites_playlists = ('uploads', 'favorites', 'playlists') if feed_type is 'subscriptions' and subscription_id and username: feed = "http://%s/feeds/users/%s/%s/%s" % (YOUTUBE_SERVER, username, feed_type, subscription_id) elif feed_type is 'subscriptions' and not subscription_id and username: feed = "http://%s/feeds/users/%s/%s" % (YOUTUBE_SERVER, username, feed_type) elif feed_type in uploads_favorites_playlists: feed = "http://%s/feeds/users/%s/%s" % (YOUTUBE_SERVER, username, feed_type) else: feed = "http://%s/feeds/users" % (YOUTUBE_SERVER) YouTubeVideoQuery.__init__(self, feed=feed, text_query=text_query, params=params, categories=categories) class YouTubePlaylistQuery(YouTubeVideoQuery): """Subclasses YouTubeVideoQuery to perform playlist-specific queries. Attributes are set dynamically via properties. Properties correspond to the standard Google Data API query parameters with YouTube Data API extensions. """ def __init__(self, playlist_id, text_query=None, params=None, categories=None): if playlist_id: feed = "http://%s/feeds/playlists/%s" % (YOUTUBE_SERVER, playlist_id) else: feed = "http://%s/feeds/playlists" % (YOUTUBE_SERVER) YouTubeVideoQuery.__init__(self, feed=feed, text_query=text_query, params=params, categories=categories) gdata-2.0.18/src/gdata/data.py0000640036466300116100000011601312156622363016072 0ustar afshareng00000000000000#!/usr/bin/env python # # Copyright (C) 2009 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. # This module is used for version 2 of the Google Data APIs. """Provides classes and constants for the XML in the Google Data namespace. Documentation for the raw XML which these classes represent can be found here: http://code.google.com/apis/gdata/docs/2.0/elements.html """ __author__ = 'j.s@google.com (Jeff Scudder)' import os import atom.core import atom.data GDATA_TEMPLATE = '{http://schemas.google.com/g/2005}%s' GD_TEMPLATE = GDATA_TEMPLATE OPENSEARCH_TEMPLATE_V1 = '{http://a9.com/-/spec/opensearchrss/1.0/}%s' OPENSEARCH_TEMPLATE_V2 = '{http://a9.com/-/spec/opensearch/1.1/}%s' BATCH_TEMPLATE = '{http://schemas.google.com/gdata/batch}%s' # Labels used in batch request entries to specify the desired CRUD operation. BATCH_INSERT = 'insert' BATCH_UPDATE = 'update' BATCH_DELETE = 'delete' BATCH_QUERY = 'query' EVENT_LOCATION = 'http://schemas.google.com/g/2005#event' ALTERNATE_LOCATION = 'http://schemas.google.com/g/2005#event.alternate' PARKING_LOCATION = 'http://schemas.google.com/g/2005#event.parking' CANCELED_EVENT = 'http://schemas.google.com/g/2005#event.canceled' CONFIRMED_EVENT = 'http://schemas.google.com/g/2005#event.confirmed' TENTATIVE_EVENT = 'http://schemas.google.com/g/2005#event.tentative' CONFIDENTIAL_EVENT = 'http://schemas.google.com/g/2005#event.confidential' DEFAULT_EVENT = 'http://schemas.google.com/g/2005#event.default' PRIVATE_EVENT = 'http://schemas.google.com/g/2005#event.private' PUBLIC_EVENT = 'http://schemas.google.com/g/2005#event.public' OPAQUE_EVENT = 'http://schemas.google.com/g/2005#event.opaque' TRANSPARENT_EVENT = 'http://schemas.google.com/g/2005#event.transparent' CHAT_MESSAGE = 'http://schemas.google.com/g/2005#message.chat' INBOX_MESSAGE = 'http://schemas.google.com/g/2005#message.inbox' SENT_MESSAGE = 'http://schemas.google.com/g/2005#message.sent' SPAM_MESSAGE = 'http://schemas.google.com/g/2005#message.spam' STARRED_MESSAGE = 'http://schemas.google.com/g/2005#message.starred' UNREAD_MESSAGE = 'http://schemas.google.com/g/2005#message.unread' BCC_RECIPIENT = 'http://schemas.google.com/g/2005#message.bcc' CC_RECIPIENT = 'http://schemas.google.com/g/2005#message.cc' SENDER = 'http://schemas.google.com/g/2005#message.from' REPLY_TO = 'http://schemas.google.com/g/2005#message.reply-to' TO_RECIPIENT = 'http://schemas.google.com/g/2005#message.to' ASSISTANT_REL = 'http://schemas.google.com/g/2005#assistant' CALLBACK_REL = 'http://schemas.google.com/g/2005#callback' CAR_REL = 'http://schemas.google.com/g/2005#car' COMPANY_MAIN_REL = 'http://schemas.google.com/g/2005#company_main' FAX_REL = 'http://schemas.google.com/g/2005#fax' HOME_REL = 'http://schemas.google.com/g/2005#home' HOME_FAX_REL = 'http://schemas.google.com/g/2005#home_fax' ISDN_REL = 'http://schemas.google.com/g/2005#isdn' MAIN_REL = 'http://schemas.google.com/g/2005#main' MOBILE_REL = 'http://schemas.google.com/g/2005#mobile' OTHER_REL = 'http://schemas.google.com/g/2005#other' OTHER_FAX_REL = 'http://schemas.google.com/g/2005#other_fax' PAGER_REL = 'http://schemas.google.com/g/2005#pager' RADIO_REL = 'http://schemas.google.com/g/2005#radio' TELEX_REL = 'http://schemas.google.com/g/2005#telex' TTL_TDD_REL = 'http://schemas.google.com/g/2005#tty_tdd' WORK_REL = 'http://schemas.google.com/g/2005#work' WORK_FAX_REL = 'http://schemas.google.com/g/2005#work_fax' WORK_MOBILE_REL = 'http://schemas.google.com/g/2005#work_mobile' WORK_PAGER_REL = 'http://schemas.google.com/g/2005#work_pager' NETMEETING_REL = 'http://schemas.google.com/g/2005#netmeeting' OVERALL_REL = 'http://schemas.google.com/g/2005#overall' PRICE_REL = 'http://schemas.google.com/g/2005#price' QUALITY_REL = 'http://schemas.google.com/g/2005#quality' EVENT_REL = 'http://schemas.google.com/g/2005#event' EVENT_ALTERNATE_REL = 'http://schemas.google.com/g/2005#event.alternate' EVENT_PARKING_REL = 'http://schemas.google.com/g/2005#event.parking' AIM_PROTOCOL = 'http://schemas.google.com/g/2005#AIM' MSN_PROTOCOL = 'http://schemas.google.com/g/2005#MSN' YAHOO_MESSENGER_PROTOCOL = 'http://schemas.google.com/g/2005#YAHOO' SKYPE_PROTOCOL = 'http://schemas.google.com/g/2005#SKYPE' QQ_PROTOCOL = 'http://schemas.google.com/g/2005#QQ' GOOGLE_TALK_PROTOCOL = 'http://schemas.google.com/g/2005#GOOGLE_TALK' ICQ_PROTOCOL = 'http://schemas.google.com/g/2005#ICQ' JABBER_PROTOCOL = 'http://schemas.google.com/g/2005#JABBER' REGULAR_COMMENTS = 'http://schemas.google.com/g/2005#regular' REVIEW_COMMENTS = 'http://schemas.google.com/g/2005#reviews' MAIL_BOTH = 'http://schemas.google.com/g/2005#both' MAIL_LETTERS = 'http://schemas.google.com/g/2005#letters' MAIL_PARCELS = 'http://schemas.google.com/g/2005#parcels' MAIL_NEITHER = 'http://schemas.google.com/g/2005#neither' GENERAL_ADDRESS = 'http://schemas.google.com/g/2005#general' LOCAL_ADDRESS = 'http://schemas.google.com/g/2005#local' OPTIONAL_ATENDEE = 'http://schemas.google.com/g/2005#event.optional' REQUIRED_ATENDEE = 'http://schemas.google.com/g/2005#event.required' ATTENDEE_ACCEPTED = 'http://schemas.google.com/g/2005#event.accepted' ATTENDEE_DECLINED = 'http://schemas.google.com/g/2005#event.declined' ATTENDEE_INVITED = 'http://schemas.google.com/g/2005#event.invited' ATTENDEE_TENTATIVE = 'http://schemas.google.com/g/2005#event.tentative' FULL_PROJECTION = 'full' VALUES_PROJECTION = 'values' BASIC_PROJECTION = 'basic' PRIVATE_VISIBILITY = 'private' PUBLIC_VISIBILITY = 'public' OPAQUE_TRANSPARENCY = 'http://schemas.google.com/g/2005#event.opaque' TRANSPARENT_TRANSPARENCY = 'http://schemas.google.com/g/2005#event.transparent' CONFIDENTIAL_EVENT_VISIBILITY = 'http://schemas.google.com/g/2005#event.confidential' DEFAULT_EVENT_VISIBILITY = 'http://schemas.google.com/g/2005#event.default' PRIVATE_EVENT_VISIBILITY = 'http://schemas.google.com/g/2005#event.private' PUBLIC_EVENT_VISIBILITY = 'http://schemas.google.com/g/2005#event.public' CANCELED_EVENT_STATUS = 'http://schemas.google.com/g/2005#event.canceled' CONFIRMED_EVENT_STATUS = 'http://schemas.google.com/g/2005#event.confirmed' TENTATIVE_EVENT_STATUS = 'http://schemas.google.com/g/2005#event.tentative' ACL_REL = 'http://schemas.google.com/acl/2007#accessControlList' class Error(Exception): pass class MissingRequiredParameters(Error): pass class LinkFinder(atom.data.LinkFinder): """Mixin used in Feed and Entry classes to simplify link lookups by type. Provides lookup methods for edit, edit-media, post, ACL and other special links which are common across Google Data APIs. """ def find_html_link(self): """Finds the first link with rel of alternate and type of text/html.""" for link in self.link: if link.rel == 'alternate' and link.type == 'text/html': return link.href return None FindHtmlLink = find_html_link def get_html_link(self): for a_link in self.link: if a_link.rel == 'alternate' and a_link.type == 'text/html': return a_link return None GetHtmlLink = get_html_link def find_post_link(self): """Get the URL to which new entries should be POSTed. The POST target URL is used to insert new entries. Returns: A str for the URL in the link with a rel matching the POST type. """ return self.find_url('http://schemas.google.com/g/2005#post') FindPostLink = find_post_link def get_post_link(self): return self.get_link('http://schemas.google.com/g/2005#post') GetPostLink = get_post_link def find_acl_link(self): acl_link = self.get_acl_link() if acl_link: return acl_link.href return None FindAclLink = find_acl_link def get_acl_link(self): """Searches for a link or feed_link (if present) with the rel for ACL.""" acl_link = self.get_link(ACL_REL) if acl_link: return acl_link elif hasattr(self, 'feed_link'): for a_feed_link in self.feed_link: if a_feed_link.rel == ACL_REL: return a_feed_link return None GetAclLink = get_acl_link def find_feed_link(self): return self.find_url('http://schemas.google.com/g/2005#feed') FindFeedLink = find_feed_link def get_feed_link(self): return self.get_link('http://schemas.google.com/g/2005#feed') GetFeedLink = get_feed_link def find_previous_link(self): return self.find_url('previous') FindPreviousLink = find_previous_link def get_previous_link(self): return self.get_link('previous') GetPreviousLink = get_previous_link class TotalResults(atom.core.XmlElement): """opensearch:TotalResults for a GData feed.""" _qname = (OPENSEARCH_TEMPLATE_V1 % 'totalResults', OPENSEARCH_TEMPLATE_V2 % 'totalResults') class StartIndex(atom.core.XmlElement): """The opensearch:startIndex element in GData feed.""" _qname = (OPENSEARCH_TEMPLATE_V1 % 'startIndex', OPENSEARCH_TEMPLATE_V2 % 'startIndex') class ItemsPerPage(atom.core.XmlElement): """The opensearch:itemsPerPage element in GData feed.""" _qname = (OPENSEARCH_TEMPLATE_V1 % 'itemsPerPage', OPENSEARCH_TEMPLATE_V2 % 'itemsPerPage') class ExtendedProperty(atom.core.XmlElement): """The Google Data extendedProperty element. Used to store arbitrary key-value information specific to your application. The value can either be a text string stored as an XML attribute (.value), or an XML node (XmlBlob) as a child element. This element is used in the Google Calendar data API and the Google Contacts data API. """ _qname = GDATA_TEMPLATE % 'extendedProperty' name = 'name' value = 'value' def get_xml_blob(self): """Returns the XML blob as an atom.core.XmlElement. Returns: An XmlElement representing the blob's XML, or None if no blob was set. """ if self._other_elements: return self._other_elements[0] else: return None GetXmlBlob = get_xml_blob def set_xml_blob(self, blob): """Sets the contents of the extendedProperty to XML as a child node. Since the extendedProperty is only allowed one child element as an XML blob, setting the XML blob will erase any preexisting member elements in this object. Args: blob: str or atom.core.XmlElement representing the XML blob stored in the extendedProperty. """ # Erase any existing extension_elements, clears the child nodes from the # extendedProperty. if isinstance(blob, atom.core.XmlElement): self._other_elements = [blob] else: self._other_elements = [atom.core.parse(str(blob))] SetXmlBlob = set_xml_blob class GDEntry(atom.data.Entry, LinkFinder): """Extends Atom Entry to provide data processing""" etag = '{http://schemas.google.com/g/2005}etag' def get_id(self): if self.id is not None and self.id.text is not None: return self.id.text.strip() return None GetId = get_id def is_media(self): if self.find_edit_media_link(): return True return False IsMedia = is_media def find_media_link(self): """Returns the URL to the media content, if the entry is a media entry. Otherwise returns None. """ if self.is_media(): return self.content.src return None FindMediaLink = find_media_link class GDFeed(atom.data.Feed, LinkFinder): """A Feed from a GData service.""" etag = '{http://schemas.google.com/g/2005}etag' total_results = TotalResults start_index = StartIndex items_per_page = ItemsPerPage entry = [GDEntry] def get_id(self): if self.id is not None and self.id.text is not None: return self.id.text.strip() return None GetId = get_id def get_generator(self): if self.generator and self.generator.text: return self.generator.text.strip() return None class BatchId(atom.core.XmlElement): """Identifies a single operation in a batch request.""" _qname = BATCH_TEMPLATE % 'id' class BatchOperation(atom.core.XmlElement): """The CRUD operation which this batch entry represents.""" _qname = BATCH_TEMPLATE % 'operation' type = 'type' class BatchStatus(atom.core.XmlElement): """The batch:status element present in a batch response entry. A status element contains the code (HTTP response code) and reason as elements. In a single request these fields would be part of the HTTP response, but in a batch request each Entry operation has a corresponding Entry in the response feed which includes status information. See http://code.google.com/apis/gdata/batch.html#Handling_Errors """ _qname = BATCH_TEMPLATE % 'status' code = 'code' reason = 'reason' content_type = 'content-type' class BatchEntry(GDEntry): """An atom:entry for use in batch requests. The BatchEntry contains additional members to specify the operation to be performed on this entry and a batch ID so that the server can reference individual operations in the response feed. For more information, see: http://code.google.com/apis/gdata/batch.html """ batch_operation = BatchOperation batch_id = BatchId batch_status = BatchStatus class BatchInterrupted(atom.core.XmlElement): """The batch:interrupted element sent if batch request was interrupted. Only appears in a feed if some of the batch entries could not be processed. See: http://code.google.com/apis/gdata/batch.html#Handling_Errors """ _qname = BATCH_TEMPLATE % 'interrupted' reason = 'reason' success = 'success' failures = 'failures' parsed = 'parsed' class BatchFeed(GDFeed): """A feed containing a list of batch request entries.""" interrupted = BatchInterrupted entry = [BatchEntry] def add_batch_entry(self, entry=None, id_url_string=None, batch_id_string=None, operation_string=None): """Logic for populating members of a BatchEntry and adding to the feed. If the entry is not a BatchEntry, it is converted to a BatchEntry so that the batch specific members will be present. The id_url_string can be used in place of an entry if the batch operation applies to a URL. For example query and delete operations require just the URL of an entry, no body is sent in the HTTP request. If an id_url_string is sent instead of an entry, a BatchEntry is created and added to the feed. This method also assigns the desired batch id to the entry so that it can be referenced in the server's response. If the batch_id_string is None, this method will assign a batch_id to be the index at which this entry will be in the feed's entry list. Args: entry: BatchEntry, atom.data.Entry, or another Entry flavor (optional) The entry which will be sent to the server as part of the batch request. The item must have a valid atom id so that the server knows which entry this request references. id_url_string: str (optional) The URL of the entry to be acted on. You can find this URL in the text member of the atom id for an entry. If an entry is not sent, this id will be used to construct a new BatchEntry which will be added to the request feed. batch_id_string: str (optional) The batch ID to be used to reference this batch operation in the results feed. If this parameter is None, the current length of the feed's entry array will be used as a count. Note that batch_ids should either always be specified or never, mixing could potentially result in duplicate batch ids. operation_string: str (optional) The desired batch operation which will set the batch_operation.type member of the entry. Options are 'insert', 'update', 'delete', and 'query' Raises: MissingRequiredParameters: Raised if neither an id_ url_string nor an entry are provided in the request. Returns: The added entry. """ if entry is None and id_url_string is None: raise MissingRequiredParameters('supply either an entry or URL string') if entry is None and id_url_string is not None: entry = BatchEntry(id=atom.data.Id(text=id_url_string)) if batch_id_string is not None: entry.batch_id = BatchId(text=batch_id_string) elif entry.batch_id is None or entry.batch_id.text is None: entry.batch_id = BatchId(text=str(len(self.entry))) if operation_string is not None: entry.batch_operation = BatchOperation(type=operation_string) self.entry.append(entry) return entry AddBatchEntry = add_batch_entry def add_insert(self, entry, batch_id_string=None): """Add an insert request to the operations in this batch request feed. If the entry doesn't yet have an operation or a batch id, these will be set to the insert operation and a batch_id specified as a parameter. Args: entry: BatchEntry The entry which will be sent in the batch feed as an insert request. batch_id_string: str (optional) The batch ID to be used to reference this batch operation in the results feed. If this parameter is None, the current length of the feed's entry array will be used as a count. Note that batch_ids should either always be specified or never, mixing could potentially result in duplicate batch ids. """ self.add_batch_entry(entry=entry, batch_id_string=batch_id_string, operation_string=BATCH_INSERT) AddInsert = add_insert def add_update(self, entry, batch_id_string=None): """Add an update request to the list of batch operations in this feed. Sets the operation type of the entry to insert if it is not already set and assigns the desired batch id to the entry so that it can be referenced in the server's response. Args: entry: BatchEntry The entry which will be sent to the server as an update (HTTP PUT) request. The item must have a valid atom id so that the server knows which entry to replace. batch_id_string: str (optional) The batch ID to be used to reference this batch operation in the results feed. If this parameter is None, the current length of the feed's entry array will be used as a count. See also comments for AddInsert. """ self.add_batch_entry(entry=entry, batch_id_string=batch_id_string, operation_string=BATCH_UPDATE) AddUpdate = add_update def add_delete(self, url_string=None, entry=None, batch_id_string=None): """Adds a delete request to the batch request feed. This method takes either the url_string which is the atom id of the item to be deleted, or the entry itself. The atom id of the entry must be present so that the server knows which entry should be deleted. Args: url_string: str (optional) The URL of the entry to be deleted. You can find this URL in the text member of the atom id for an entry. entry: BatchEntry (optional) The entry to be deleted. batch_id_string: str (optional) Raises: MissingRequiredParameters: Raised if neither a url_string nor an entry are provided in the request. """ self.add_batch_entry(entry=entry, id_url_string=url_string, batch_id_string=batch_id_string, operation_string=BATCH_DELETE) AddDelete = add_delete def add_query(self, url_string=None, entry=None, batch_id_string=None): """Adds a query request to the batch request feed. This method takes either the url_string which is the query URL whose results will be added to the result feed. The query URL will be encapsulated in a BatchEntry, and you may pass in the BatchEntry with a query URL instead of sending a url_string. Args: url_string: str (optional) entry: BatchEntry (optional) batch_id_string: str (optional) Raises: MissingRequiredParameters """ self.add_batch_entry(entry=entry, id_url_string=url_string, batch_id_string=batch_id_string, operation_string=BATCH_QUERY) AddQuery = add_query def find_batch_link(self): return self.find_url('http://schemas.google.com/g/2005#batch') FindBatchLink = find_batch_link class EntryLink(atom.core.XmlElement): """The gd:entryLink element. Represents a logically nested entry. For example, a representing a contact might have a nested entry from a contact feed. """ _qname = GDATA_TEMPLATE % 'entryLink' entry = GDEntry rel = 'rel' read_only = 'readOnly' href = 'href' class FeedLink(atom.core.XmlElement): """The gd:feedLink element. Represents a logically nested feed. For example, a calendar feed might have a nested feed representing all comments on entries. """ _qname = GDATA_TEMPLATE % 'feedLink' feed = GDFeed rel = 'rel' read_only = 'readOnly' count_hint = 'countHint' href = 'href' class AdditionalName(atom.core.XmlElement): """The gd:additionalName element. Specifies additional (eg. middle) name of the person. Contains an attribute for the phonetic representaton of the name. """ _qname = GDATA_TEMPLATE % 'additionalName' yomi = 'yomi' class Comments(atom.core.XmlElement): """The gd:comments element. Contains a comments feed for the enclosing entry (such as a calendar event). """ _qname = GDATA_TEMPLATE % 'comments' rel = 'rel' feed_link = FeedLink class Country(atom.core.XmlElement): """The gd:country element. Country name along with optional country code. The country code is given in accordance with ISO 3166-1 alpha-2: http://www.iso.org/iso/iso-3166-1_decoding_table """ _qname = GDATA_TEMPLATE % 'country' code = 'code' class EmailImParent(atom.core.XmlElement): address = 'address' label = 'label' rel = 'rel' primary = 'primary' class Email(EmailImParent): """The gd:email element. An email address associated with the containing entity (which is usually an entity representing a person or a location). """ _qname = GDATA_TEMPLATE % 'email' display_name = 'displayName' class FamilyName(atom.core.XmlElement): """The gd:familyName element. Specifies family name of the person, eg. "Smith". """ _qname = GDATA_TEMPLATE % 'familyName' yomi = 'yomi' class Im(EmailImParent): """The gd:im element. An instant messaging address associated with the containing entity. """ _qname = GDATA_TEMPLATE % 'im' protocol = 'protocol' class GivenName(atom.core.XmlElement): """The gd:givenName element. Specifies given name of the person, eg. "John". """ _qname = GDATA_TEMPLATE % 'givenName' yomi = 'yomi' class NamePrefix(atom.core.XmlElement): """The gd:namePrefix element. Honorific prefix, eg. 'Mr' or 'Mrs'. """ _qname = GDATA_TEMPLATE % 'namePrefix' class NameSuffix(atom.core.XmlElement): """The gd:nameSuffix element. Honorific suffix, eg. 'san' or 'III'. """ _qname = GDATA_TEMPLATE % 'nameSuffix' class FullName(atom.core.XmlElement): """The gd:fullName element. Unstructured representation of the name. """ _qname = GDATA_TEMPLATE % 'fullName' class Name(atom.core.XmlElement): """The gd:name element. Allows storing person's name in a structured way. Consists of given name, additional name, family name, prefix, suffix and full name. """ _qname = GDATA_TEMPLATE % 'name' given_name = GivenName additional_name = AdditionalName family_name = FamilyName name_prefix = NamePrefix name_suffix = NameSuffix full_name = FullName class OrgDepartment(atom.core.XmlElement): """The gd:orgDepartment element. Describes a department within an organization. Must appear within a gd:organization element. """ _qname = GDATA_TEMPLATE % 'orgDepartment' class OrgJobDescription(atom.core.XmlElement): """The gd:orgJobDescription element. Describes a job within an organization. Must appear within a gd:organization element. """ _qname = GDATA_TEMPLATE % 'orgJobDescription' class OrgName(atom.core.XmlElement): """The gd:orgName element. The name of the organization. Must appear within a gd:organization element. Contains a Yomigana attribute (Japanese reading aid) for the organization name. """ _qname = GDATA_TEMPLATE % 'orgName' yomi = 'yomi' class OrgSymbol(atom.core.XmlElement): """The gd:orgSymbol element. Provides a symbol of an organization. Must appear within a gd:organization element. """ _qname = GDATA_TEMPLATE % 'orgSymbol' class OrgTitle(atom.core.XmlElement): """The gd:orgTitle element. The title of a person within an organization. Must appear within a gd:organization element. """ _qname = GDATA_TEMPLATE % 'orgTitle' class Organization(atom.core.XmlElement): """The gd:organization element. An organization, typically associated with a contact. """ _qname = GDATA_TEMPLATE % 'organization' label = 'label' primary = 'primary' rel = 'rel' department = OrgDepartment job_description = OrgJobDescription name = OrgName symbol = OrgSymbol title = OrgTitle class When(atom.core.XmlElement): """The gd:when element. Represents a period of time or an instant. """ _qname = GDATA_TEMPLATE % 'when' end = 'endTime' start = 'startTime' value = 'valueString' class OriginalEvent(atom.core.XmlElement): """The gd:originalEvent element. Equivalent to the Recurrence ID property specified in section 4.8.4.4 of RFC 2445. Appears in every instance of a recurring event, to identify the original event. Contains a element specifying the original start time of the instance that has become an exception. """ _qname = GDATA_TEMPLATE % 'originalEvent' id = 'id' href = 'href' when = When class PhoneNumber(atom.core.XmlElement): """The gd:phoneNumber element. A phone number associated with the containing entity (which is usually an entity representing a person or a location). """ _qname = GDATA_TEMPLATE % 'phoneNumber' label = 'label' rel = 'rel' uri = 'uri' primary = 'primary' class PostalAddress(atom.core.XmlElement): """The gd:postalAddress element.""" _qname = GDATA_TEMPLATE % 'postalAddress' label = 'label' rel = 'rel' uri = 'uri' primary = 'primary' class Rating(atom.core.XmlElement): """The gd:rating element. Represents a numeric rating of the enclosing entity, such as a comment. Each rating supplies its own scale, although it may be normalized by a service; for example, some services might convert all ratings to a scale from 1 to 5. """ _qname = GDATA_TEMPLATE % 'rating' average = 'average' max = 'max' min = 'min' num_raters = 'numRaters' rel = 'rel' value = 'value' class Recurrence(atom.core.XmlElement): """The gd:recurrence element. Represents the dates and times when a recurring event takes place. The string that defines the recurrence consists of a set of properties, each of which is defined in the iCalendar standard (RFC 2445). Specifically, the string usually begins with a DTSTART property that indicates the starting time of the first instance of the event, and often a DTEND property or a DURATION property to indicate when the first instance ends. Next come RRULE, RDATE, EXRULE, and/or EXDATE properties, which collectively define a recurring event and its exceptions (but see below). (See section 4.8.5 of RFC 2445 for more information about these recurrence component properties.) Last comes a VTIMEZONE component, providing detailed timezone rules for any timezone ID mentioned in the preceding properties. Google services like Google Calendar don't generally generate EXRULE and EXDATE properties to represent exceptions to recurring events; instead, they generate elements. However, Google services may include EXRULE and/or EXDATE properties anyway; for example, users can import events and exceptions into Calendar, and if those imported events contain EXRULE or EXDATE properties, then Calendar will provide those properties when it sends a element. Note the the use of means that you can't be sure just from examining a element whether there are any exceptions to the recurrence description. To ensure that you find all exceptions, look for elements in the feed, and use their elements to match them up with elements. """ _qname = GDATA_TEMPLATE % 'recurrence' class RecurrenceException(atom.core.XmlElement): """The gd:recurrenceException element. Represents an event that's an exception to a recurring event-that is, an instance of a recurring event in which one or more aspects of the recurring event (such as attendance list, time, or location) have been changed. Contains a element that specifies the original recurring event that this event is an exception to. When you change an instance of a recurring event, that instance becomes an exception. Depending on what change you made to it, the exception behaves in either of two different ways when the original recurring event is changed: - If you add, change, or remove comments, attendees, or attendee responses, then the exception remains tied to the original event, and changes to the original event also change the exception. - If you make any other changes to the exception (such as changing the time or location) then the instance becomes "specialized," which means that it's no longer as tightly tied to the original event. If you change the original event, specialized exceptions don't change. But see below. For example, say you have a meeting every Tuesday and Thursday at 2:00 p.m. If you change the attendance list for this Thursday's meeting (but not for the regularly scheduled meeting), then it becomes an exception. If you change the time for this Thursday's meeting (but not for the regularly scheduled meeting), then it becomes specialized. Regardless of whether an exception is specialized or not, if you do something that deletes the instance that the exception was derived from, then the exception is deleted. Note that changing the day or time of a recurring event deletes all instances, and creates new ones. For example, after you've specialized this Thursday's meeting, say you change the recurring meeting to happen on Monday, Wednesday, and Friday. That change deletes all of the recurring instances of the Tuesday/Thursday meeting, including the specialized one. If a particular instance of a recurring event is deleted, then that instance appears as a containing a that has its set to "http://schemas.google.com/g/2005#event.canceled". (For more information about canceled events, see RFC 2445.) """ _qname = GDATA_TEMPLATE % 'recurrenceException' specialized = 'specialized' entry_link = EntryLink original_event = OriginalEvent class Reminder(atom.core.XmlElement): """The gd:reminder element. A time interval, indicating how long before the containing entity's start time or due time attribute a reminder should be issued. Alternatively, may specify an absolute time at which a reminder should be issued. Also specifies a notification method, indicating what medium the system should use to remind the user. """ _qname = GDATA_TEMPLATE % 'reminder' absolute_time = 'absoluteTime' method = 'method' days = 'days' hours = 'hours' minutes = 'minutes' class Transparency(atom.core.XmlElement): """The gd:transparency element: Extensible enum corresponding to the TRANSP property defined in RFC 244. """ _qname = GDATA_TEMPLATE % 'transparency' value = 'value' class Agent(atom.core.XmlElement): """The gd:agent element. The agent who actually receives the mail. Used in work addresses. Also for 'in care of' or 'c/o'. """ _qname = GDATA_TEMPLATE % 'agent' class HouseName(atom.core.XmlElement): """The gd:housename element. Used in places where houses or buildings have names (and not necessarily numbers), eg. "The Pillars". """ _qname = GDATA_TEMPLATE % 'housename' class Street(atom.core.XmlElement): """The gd:street element. Can be street, avenue, road, etc. This element also includes the house number and room/apartment/flat/floor number. """ _qname = GDATA_TEMPLATE % 'street' class PoBox(atom.core.XmlElement): """The gd:pobox element. Covers actual P.O. boxes, drawers, locked bags, etc. This is usually but not always mutually exclusive with street. """ _qname = GDATA_TEMPLATE % 'pobox' class Neighborhood(atom.core.XmlElement): """The gd:neighborhood element. This is used to disambiguate a street address when a city contains more than one street with the same name, or to specify a small place whose mail is routed through a larger postal town. In China it could be a county or a minor city. """ _qname = GDATA_TEMPLATE % 'neighborhood' class City(atom.core.XmlElement): """The gd:city element. Can be city, village, town, borough, etc. This is the postal town and not necessarily the place of residence or place of business. """ _qname = GDATA_TEMPLATE % 'city' class Subregion(atom.core.XmlElement): """The gd:subregion element. Handles administrative districts such as U.S. or U.K. counties that are not used for mail addressing purposes. Subregion is not intended for delivery addresses. """ _qname = GDATA_TEMPLATE % 'subregion' class Region(atom.core.XmlElement): """The gd:region element. A state, province, county (in Ireland), Land (in Germany), departement (in France), etc. """ _qname = GDATA_TEMPLATE % 'region' class Postcode(atom.core.XmlElement): """The gd:postcode element. Postal code. Usually country-wide, but sometimes specific to the city (e.g. "2" in "Dublin 2, Ireland" addresses). """ _qname = GDATA_TEMPLATE % 'postcode' class Country(atom.core.XmlElement): """The gd:country element. The name or code of the country. """ _qname = GDATA_TEMPLATE % 'country' class FormattedAddress(atom.core.XmlElement): """The gd:formattedAddress element. The full, unstructured postal address. """ _qname = GDATA_TEMPLATE % 'formattedAddress' class StructuredPostalAddress(atom.core.XmlElement): """The gd:structuredPostalAddress element. Postal address split into components. It allows to store the address in locale independent format. The fields can be interpreted and used to generate formatted, locale dependent address. The following elements reperesent parts of the address: agent, house name, street, P.O. box, neighborhood, city, subregion, region, postal code, country. The subregion element is not used for postal addresses, it is provided for extended uses of addresses only. In order to store postal address in an unstructured form formatted address field is provided. """ _qname = GDATA_TEMPLATE % 'structuredPostalAddress' rel = 'rel' mail_class = 'mailClass' usage = 'usage' label = 'label' primary = 'primary' agent = Agent house_name = HouseName street = Street po_box = PoBox neighborhood = Neighborhood city = City subregion = Subregion region = Region postcode = Postcode country = Country formatted_address = FormattedAddress class Where(atom.core.XmlElement): """The gd:where element. A place (such as an event location) associated with the containing entity. The type of the association is determined by the rel attribute; the details of the location are contained in an embedded or linked-to Contact entry. A element is more general than a element. The former identifies a place using a text description and/or a Contact entry, while the latter identifies a place using a specific geographic location. """ _qname = GDATA_TEMPLATE % 'where' label = 'label' rel = 'rel' value = 'valueString' entry_link = EntryLink class AttendeeType(atom.core.XmlElement): """The gd:attendeeType element.""" _qname = GDATA_TEMPLATE % 'attendeeType' value = 'value' class AttendeeStatus(atom.core.XmlElement): """The gd:attendeeStatus element.""" _qname = GDATA_TEMPLATE % 'attendeeStatus' value = 'value' class EventStatus(atom.core.XmlElement): """The gd:eventStatus element.""" _qname = GDATA_TEMPLATE % 'eventStatus' value = 'value' class Visibility(atom.core.XmlElement): """The gd:visibility element.""" _qname = GDATA_TEMPLATE % 'visibility' value = 'value' class Who(atom.core.XmlElement): """The gd:who element. A person associated with the containing entity. The type of the association is determined by the rel attribute; the details about the person are contained in an embedded or linked-to Contact entry. The element can be used to specify email senders and recipients, calendar event organizers, and so on. """ _qname = GDATA_TEMPLATE % 'who' email = 'email' rel = 'rel' value = 'valueString' attendee_status = AttendeeStatus attendee_type = AttendeeType entry_link = EntryLink class Deleted(atom.core.XmlElement): """gd:deleted when present, indicates the containing entry is deleted.""" _qname = GD_TEMPLATE % 'deleted' class Money(atom.core.XmlElement): """Describes money""" _qname = GD_TEMPLATE % 'money' amount = 'amount' currency_code = 'currencyCode' class MediaSource(object): """GData Entries can refer to media sources, so this class provides a place to store references to these objects along with some metadata. """ def __init__(self, file_handle=None, content_type=None, content_length=None, file_path=None, file_name=None): """Creates an object of type MediaSource. Args: file_handle: A file handle pointing to the file to be encapsulated in the MediaSource. content_type: string The MIME type of the file. Required if a file_handle is given. content_length: int The size of the file. Required if a file_handle is given. file_path: string (optional) A full path name to the file. Used in place of a file_handle. file_name: string The name of the file without any path information. Required if a file_handle is given. """ self.file_handle = file_handle self.content_type = content_type self.content_length = content_length self.file_name = file_name if (file_handle is None and content_type is not None and file_path is not None): self.set_file_handle(file_path, content_type) def set_file_handle(self, file_name, content_type): """A helper function which can create a file handle from a given filename and set the content type and length all at once. Args: file_name: string The path and file name to the file containing the media content_type: string A MIME type representing the type of the media """ self.file_handle = open(file_name, 'rb') self.content_type = content_type self.content_length = os.path.getsize(file_name) self.file_name = os.path.basename(file_name) SetFileHandle = set_file_handle def modify_request(self, http_request): http_request.add_body_part(self.file_handle, self.content_type, self.content_length) return http_request ModifyRequest = modify_request gdata-2.0.18/src/gdata/__init__.py0000750036466300116100000007225112156622363016727 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright (C) 2006 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. """Contains classes representing Google Data elements. Extends Atom classes to add Google Data specific elements. """ __author__ = 'j.s@google.com (Jeffrey Scudder)' import os import atom try: from xml.etree import cElementTree as ElementTree except ImportError: try: import cElementTree as ElementTree except ImportError: try: from xml.etree import ElementTree except ImportError: from elementtree import ElementTree # XML namespaces which are often used in GData entities. GDATA_NAMESPACE = 'http://schemas.google.com/g/2005' GDATA_TEMPLATE = '{http://schemas.google.com/g/2005}%s' OPENSEARCH_NAMESPACE = 'http://a9.com/-/spec/opensearchrss/1.0/' OPENSEARCH_TEMPLATE = '{http://a9.com/-/spec/opensearchrss/1.0/}%s' BATCH_NAMESPACE = 'http://schemas.google.com/gdata/batch' GACL_NAMESPACE = 'http://schemas.google.com/acl/2007' GACL_TEMPLATE = '{http://schemas.google.com/acl/2007}%s' # Labels used in batch request entries to specify the desired CRUD operation. BATCH_INSERT = 'insert' BATCH_UPDATE = 'update' BATCH_DELETE = 'delete' BATCH_QUERY = 'query' class Error(Exception): pass class MissingRequiredParameters(Error): pass class MediaSource(object): """GData Entries can refer to media sources, so this class provides a place to store references to these objects along with some metadata. """ def __init__(self, file_handle=None, content_type=None, content_length=None, file_path=None, file_name=None): """Creates an object of type MediaSource. Args: file_handle: A file handle pointing to the file to be encapsulated in the MediaSource content_type: string The MIME type of the file. Required if a file_handle is given. content_length: int The size of the file. Required if a file_handle is given. file_path: string (optional) A full path name to the file. Used in place of a file_handle. file_name: string The name of the file without any path information. Required if a file_handle is given. """ self.file_handle = file_handle self.content_type = content_type self.content_length = content_length self.file_name = file_name if (file_handle is None and content_type is not None and file_path is not None): self.setFile(file_path, content_type) def setFile(self, file_name, content_type): """A helper function which can create a file handle from a given filename and set the content type and length all at once. Args: file_name: string The path and file name to the file containing the media content_type: string A MIME type representing the type of the media """ self.file_handle = open(file_name, 'rb') self.content_type = content_type self.content_length = os.path.getsize(file_name) self.file_name = os.path.basename(file_name) class LinkFinder(atom.LinkFinder): """An "interface" providing methods to find link elements GData Entry elements often contain multiple links which differ in the rel attribute or content type. Often, developers are interested in a specific type of link so this class provides methods to find specific classes of links. This class is used as a mixin in GData entries. """ def GetSelfLink(self): """Find the first link with rel set to 'self' Returns: An atom.Link or none if none of the links had rel equal to 'self' """ for a_link in self.link: if a_link.rel == 'self': return a_link return None def GetEditLink(self): for a_link in self.link: if a_link.rel == 'edit': return a_link return None def GetEditMediaLink(self): """The Picasa API mistakenly returns media-edit rather than edit-media, but this may change soon. """ for a_link in self.link: if a_link.rel == 'edit-media': return a_link if a_link.rel == 'media-edit': return a_link return None def GetHtmlLink(self): """Find the first link with rel of alternate and type of text/html Returns: An atom.Link or None if no links matched """ for a_link in self.link: if a_link.rel == 'alternate' and a_link.type == 'text/html': return a_link return None def GetPostLink(self): """Get a link containing the POST target URL. The POST target URL is used to insert new entries. Returns: A link object with a rel matching the POST type. """ for a_link in self.link: if a_link.rel == 'http://schemas.google.com/g/2005#post': return a_link return None def GetAclLink(self): for a_link in self.link: if a_link.rel == 'http://schemas.google.com/acl/2007#accessControlList': return a_link return None def GetFeedLink(self): for a_link in self.link: if a_link.rel == 'http://schemas.google.com/g/2005#feed': return a_link return None def GetNextLink(self): for a_link in self.link: if a_link.rel == 'next': return a_link return None def GetPrevLink(self): for a_link in self.link: if a_link.rel == 'previous': return a_link return None class TotalResults(atom.AtomBase): """opensearch:TotalResults for a GData feed""" _tag = 'totalResults' _namespace = OPENSEARCH_NAMESPACE _children = atom.AtomBase._children.copy() _attributes = atom.AtomBase._attributes.copy() def __init__(self, extension_elements=None, extension_attributes=None, text=None): self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} def TotalResultsFromString(xml_string): return atom.CreateClassFromXMLString(TotalResults, xml_string) class StartIndex(atom.AtomBase): """The opensearch:startIndex element in GData feed""" _tag = 'startIndex' _namespace = OPENSEARCH_NAMESPACE _children = atom.AtomBase._children.copy() _attributes = atom.AtomBase._attributes.copy() def __init__(self, extension_elements=None, extension_attributes=None, text=None): self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} def StartIndexFromString(xml_string): return atom.CreateClassFromXMLString(StartIndex, xml_string) class ItemsPerPage(atom.AtomBase): """The opensearch:itemsPerPage element in GData feed""" _tag = 'itemsPerPage' _namespace = OPENSEARCH_NAMESPACE _children = atom.AtomBase._children.copy() _attributes = atom.AtomBase._attributes.copy() def __init__(self, extension_elements=None, extension_attributes=None, text=None): self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} def ItemsPerPageFromString(xml_string): return atom.CreateClassFromXMLString(ItemsPerPage, xml_string) class ExtendedProperty(atom.AtomBase): """The Google Data extendedProperty element. Used to store arbitrary key-value information specific to your application. The value can either be a text string stored as an XML attribute (.value), or an XML node (XmlBlob) as a child element. This element is used in the Google Calendar data API and the Google Contacts data API. """ _tag = 'extendedProperty' _namespace = GDATA_NAMESPACE _children = atom.AtomBase._children.copy() _attributes = atom.AtomBase._attributes.copy() _attributes['name'] = 'name' _attributes['value'] = 'value' def __init__(self, name=None, value=None, extension_elements=None, extension_attributes=None, text=None): self.name = name self.value = value self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} def GetXmlBlobExtensionElement(self): """Returns the XML blob as an atom.ExtensionElement. Returns: An atom.ExtensionElement representing the blob's XML, or None if no blob was set. """ if len(self.extension_elements) < 1: return None else: return self.extension_elements[0] def GetXmlBlobString(self): """Returns the XML blob as a string. Returns: A string containing the blob's XML, or None if no blob was set. """ blob = self.GetXmlBlobExtensionElement() if blob: return blob.ToString() return None def SetXmlBlob(self, blob): """Sets the contents of the extendedProperty to XML as a child node. Since the extendedProperty is only allowed one child element as an XML blob, setting the XML blob will erase any preexisting extension elements in this object. Args: blob: str, ElementTree Element or atom.ExtensionElement representing the XML blob stored in the extendedProperty. """ # Erase any existing extension_elements, clears the child nodes from the # extendedProperty. self.extension_elements = [] if isinstance(blob, atom.ExtensionElement): self.extension_elements.append(blob) elif ElementTree.iselement(blob): self.extension_elements.append(atom._ExtensionElementFromElementTree( blob)) else: self.extension_elements.append(atom.ExtensionElementFromString(blob)) def ExtendedPropertyFromString(xml_string): return atom.CreateClassFromXMLString(ExtendedProperty, xml_string) class GDataEntry(atom.Entry, LinkFinder): """Extends Atom Entry to provide data processing""" _tag = atom.Entry._tag _namespace = atom.Entry._namespace _children = atom.Entry._children.copy() _attributes = atom.Entry._attributes.copy() def __GetId(self): return self.__id # This method was created to strip the unwanted whitespace from the id's # text node. def __SetId(self, id): self.__id = id if id is not None and id.text is not None: self.__id.text = id.text.strip() id = property(__GetId, __SetId) def IsMedia(self): """Determines whether or not an entry is a GData Media entry. """ if (self.GetEditMediaLink()): return True else: return False def GetMediaURL(self): """Returns the URL to the media content, if the entry is a media entry. Otherwise returns None. """ if not self.IsMedia(): return None else: return self.content.src def GDataEntryFromString(xml_string): """Creates a new GDataEntry instance given a string of XML.""" return atom.CreateClassFromXMLString(GDataEntry, xml_string) class GDataFeed(atom.Feed, LinkFinder): """A Feed from a GData service""" _tag = 'feed' _namespace = atom.ATOM_NAMESPACE _children = atom.Feed._children.copy() _attributes = atom.Feed._attributes.copy() _children['{%s}totalResults' % OPENSEARCH_NAMESPACE] = ('total_results', TotalResults) _children['{%s}startIndex' % OPENSEARCH_NAMESPACE] = ('start_index', StartIndex) _children['{%s}itemsPerPage' % OPENSEARCH_NAMESPACE] = ('items_per_page', ItemsPerPage) # Add a conversion rule for atom:entry to make it into a GData # Entry. _children['{%s}entry' % atom.ATOM_NAMESPACE] = ('entry', [GDataEntry]) def __GetId(self): return self.__id def __SetId(self, id): self.__id = id if id is not None and id.text is not None: self.__id.text = id.text.strip() id = property(__GetId, __SetId) def __GetGenerator(self): return self.__generator def __SetGenerator(self, generator): self.__generator = generator if generator is not None: self.__generator.text = generator.text.strip() generator = property(__GetGenerator, __SetGenerator) def __init__(self, author=None, category=None, contributor=None, generator=None, icon=None, atom_id=None, link=None, logo=None, rights=None, subtitle=None, title=None, updated=None, entry=None, total_results=None, start_index=None, items_per_page=None, extension_elements=None, extension_attributes=None, text=None): """Constructor for Source Args: author: list (optional) A list of Author instances which belong to this class. category: list (optional) A list of Category instances contributor: list (optional) A list on Contributor instances generator: Generator (optional) icon: Icon (optional) id: Id (optional) The entry's Id element link: list (optional) A list of Link instances logo: Logo (optional) rights: Rights (optional) The entry's Rights element subtitle: Subtitle (optional) The entry's subtitle element title: Title (optional) the entry's title element updated: Updated (optional) the entry's updated element entry: list (optional) A list of the Entry instances contained in the feed. text: String (optional) The text contents of the element. This is the contents of the Entry's XML text node. (Example: This is the text) extension_elements: list (optional) A list of ExtensionElement instances which are children of this element. extension_attributes: dict (optional) A dictionary of strings which are the values for additional XML attributes of this element. """ self.author = author or [] self.category = category or [] self.contributor = contributor or [] self.generator = generator self.icon = icon self.id = atom_id self.link = link or [] self.logo = logo self.rights = rights self.subtitle = subtitle self.title = title self.updated = updated self.entry = entry or [] self.total_results = total_results self.start_index = start_index self.items_per_page = items_per_page self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} def GDataFeedFromString(xml_string): return atom.CreateClassFromXMLString(GDataFeed, xml_string) class BatchId(atom.AtomBase): _tag = 'id' _namespace = BATCH_NAMESPACE _children = atom.AtomBase._children.copy() _attributes = atom.AtomBase._attributes.copy() def BatchIdFromString(xml_string): return atom.CreateClassFromXMLString(BatchId, xml_string) class BatchOperation(atom.AtomBase): _tag = 'operation' _namespace = BATCH_NAMESPACE _children = atom.AtomBase._children.copy() _attributes = atom.AtomBase._attributes.copy() _attributes['type'] = 'type' def __init__(self, op_type=None, extension_elements=None, extension_attributes=None, text=None): self.type = op_type atom.AtomBase.__init__(self, extension_elements=extension_elements, extension_attributes=extension_attributes, text=text) def BatchOperationFromString(xml_string): return atom.CreateClassFromXMLString(BatchOperation, xml_string) class BatchStatus(atom.AtomBase): """The batch:status element present in a batch response entry. A status element contains the code (HTTP response code) and reason as elements. In a single request these fields would be part of the HTTP response, but in a batch request each Entry operation has a corresponding Entry in the response feed which includes status information. See http://code.google.com/apis/gdata/batch.html#Handling_Errors """ _tag = 'status' _namespace = BATCH_NAMESPACE _children = atom.AtomBase._children.copy() _attributes = atom.AtomBase._attributes.copy() _attributes['code'] = 'code' _attributes['reason'] = 'reason' _attributes['content-type'] = 'content_type' def __init__(self, code=None, reason=None, content_type=None, extension_elements=None, extension_attributes=None, text=None): self.code = code self.reason = reason self.content_type = content_type atom.AtomBase.__init__(self, extension_elements=extension_elements, extension_attributes=extension_attributes, text=text) def BatchStatusFromString(xml_string): return atom.CreateClassFromXMLString(BatchStatus, xml_string) class BatchEntry(GDataEntry): """An atom:entry for use in batch requests. The BatchEntry contains additional members to specify the operation to be performed on this entry and a batch ID so that the server can reference individual operations in the response feed. For more information, see: http://code.google.com/apis/gdata/batch.html """ _tag = GDataEntry._tag _namespace = GDataEntry._namespace _children = GDataEntry._children.copy() _children['{%s}operation' % BATCH_NAMESPACE] = ('batch_operation', BatchOperation) _children['{%s}id' % BATCH_NAMESPACE] = ('batch_id', BatchId) _children['{%s}status' % BATCH_NAMESPACE] = ('batch_status', BatchStatus) _attributes = GDataEntry._attributes.copy() def __init__(self, author=None, category=None, content=None, contributor=None, atom_id=None, link=None, published=None, rights=None, source=None, summary=None, control=None, title=None, updated=None, batch_operation=None, batch_id=None, batch_status=None, extension_elements=None, extension_attributes=None, text=None): self.batch_operation = batch_operation self.batch_id = batch_id self.batch_status = batch_status GDataEntry.__init__(self, author=author, category=category, content=content, contributor=contributor, atom_id=atom_id, link=link, published=published, rights=rights, source=source, summary=summary, control=control, title=title, updated=updated, extension_elements=extension_elements, extension_attributes=extension_attributes, text=text) def BatchEntryFromString(xml_string): return atom.CreateClassFromXMLString(BatchEntry, xml_string) class BatchInterrupted(atom.AtomBase): """The batch:interrupted element sent if batch request was interrupted. Only appears in a feed if some of the batch entries could not be processed. See: http://code.google.com/apis/gdata/batch.html#Handling_Errors """ _tag = 'interrupted' _namespace = BATCH_NAMESPACE _children = atom.AtomBase._children.copy() _attributes = atom.AtomBase._attributes.copy() _attributes['reason'] = 'reason' _attributes['success'] = 'success' _attributes['failures'] = 'failures' _attributes['parsed'] = 'parsed' def __init__(self, reason=None, success=None, failures=None, parsed=None, extension_elements=None, extension_attributes=None, text=None): self.reason = reason self.success = success self.failures = failures self.parsed = parsed atom.AtomBase.__init__(self, extension_elements=extension_elements, extension_attributes=extension_attributes, text=text) def BatchInterruptedFromString(xml_string): return atom.CreateClassFromXMLString(BatchInterrupted, xml_string) class BatchFeed(GDataFeed): """A feed containing a list of batch request entries.""" _tag = GDataFeed._tag _namespace = GDataFeed._namespace _children = GDataFeed._children.copy() _attributes = GDataFeed._attributes.copy() _children['{%s}entry' % atom.ATOM_NAMESPACE] = ('entry', [BatchEntry]) _children['{%s}interrupted' % BATCH_NAMESPACE] = ('interrupted', BatchInterrupted) def __init__(self, author=None, category=None, contributor=None, generator=None, icon=None, atom_id=None, link=None, logo=None, rights=None, subtitle=None, title=None, updated=None, entry=None, total_results=None, start_index=None, items_per_page=None, interrupted=None, extension_elements=None, extension_attributes=None, text=None): self.interrupted = interrupted GDataFeed.__init__(self, author=author, category=category, contributor=contributor, generator=generator, icon=icon, atom_id=atom_id, link=link, logo=logo, rights=rights, subtitle=subtitle, title=title, updated=updated, entry=entry, total_results=total_results, start_index=start_index, items_per_page=items_per_page, extension_elements=extension_elements, extension_attributes=extension_attributes, text=text) def AddBatchEntry(self, entry=None, id_url_string=None, batch_id_string=None, operation_string=None): """Logic for populating members of a BatchEntry and adding to the feed. If the entry is not a BatchEntry, it is converted to a BatchEntry so that the batch specific members will be present. The id_url_string can be used in place of an entry if the batch operation applies to a URL. For example query and delete operations require just the URL of an entry, no body is sent in the HTTP request. If an id_url_string is sent instead of an entry, a BatchEntry is created and added to the feed. This method also assigns the desired batch id to the entry so that it can be referenced in the server's response. If the batch_id_string is None, this method will assign a batch_id to be the index at which this entry will be in the feed's entry list. Args: entry: BatchEntry, atom.Entry, or another Entry flavor (optional) The entry which will be sent to the server as part of the batch request. The item must have a valid atom id so that the server knows which entry this request references. id_url_string: str (optional) The URL of the entry to be acted on. You can find this URL in the text member of the atom id for an entry. If an entry is not sent, this id will be used to construct a new BatchEntry which will be added to the request feed. batch_id_string: str (optional) The batch ID to be used to reference this batch operation in the results feed. If this parameter is None, the current length of the feed's entry array will be used as a count. Note that batch_ids should either always be specified or never, mixing could potentially result in duplicate batch ids. operation_string: str (optional) The desired batch operation which will set the batch_operation.type member of the entry. Options are 'insert', 'update', 'delete', and 'query' Raises: MissingRequiredParameters: Raised if neither an id_ url_string nor an entry are provided in the request. Returns: The added entry. """ if entry is None and id_url_string is None: raise MissingRequiredParameters('supply either an entry or URL string') if entry is None and id_url_string is not None: entry = BatchEntry(atom_id=atom.Id(text=id_url_string)) # TODO: handle cases in which the entry lacks batch_... members. #if not isinstance(entry, BatchEntry): # Convert the entry to a batch entry. if batch_id_string is not None: entry.batch_id = BatchId(text=batch_id_string) elif entry.batch_id is None or entry.batch_id.text is None: entry.batch_id = BatchId(text=str(len(self.entry))) if operation_string is not None: entry.batch_operation = BatchOperation(op_type=operation_string) self.entry.append(entry) return entry def AddInsert(self, entry, batch_id_string=None): """Add an insert request to the operations in this batch request feed. If the entry doesn't yet have an operation or a batch id, these will be set to the insert operation and a batch_id specified as a parameter. Args: entry: BatchEntry The entry which will be sent in the batch feed as an insert request. batch_id_string: str (optional) The batch ID to be used to reference this batch operation in the results feed. If this parameter is None, the current length of the feed's entry array will be used as a count. Note that batch_ids should either always be specified or never, mixing could potentially result in duplicate batch ids. """ entry = self.AddBatchEntry(entry=entry, batch_id_string=batch_id_string, operation_string=BATCH_INSERT) def AddUpdate(self, entry, batch_id_string=None): """Add an update request to the list of batch operations in this feed. Sets the operation type of the entry to insert if it is not already set and assigns the desired batch id to the entry so that it can be referenced in the server's response. Args: entry: BatchEntry The entry which will be sent to the server as an update (HTTP PUT) request. The item must have a valid atom id so that the server knows which entry to replace. batch_id_string: str (optional) The batch ID to be used to reference this batch operation in the results feed. If this parameter is None, the current length of the feed's entry array will be used as a count. See also comments for AddInsert. """ entry = self.AddBatchEntry(entry=entry, batch_id_string=batch_id_string, operation_string=BATCH_UPDATE) def AddDelete(self, url_string=None, entry=None, batch_id_string=None): """Adds a delete request to the batch request feed. This method takes either the url_string which is the atom id of the item to be deleted, or the entry itself. The atom id of the entry must be present so that the server knows which entry should be deleted. Args: url_string: str (optional) The URL of the entry to be deleted. You can find this URL in the text member of the atom id for an entry. entry: BatchEntry (optional) The entry to be deleted. batch_id_string: str (optional) Raises: MissingRequiredParameters: Raised if neither a url_string nor an entry are provided in the request. """ entry = self.AddBatchEntry(entry=entry, id_url_string=url_string, batch_id_string=batch_id_string, operation_string=BATCH_DELETE) def AddQuery(self, url_string=None, entry=None, batch_id_string=None): """Adds a query request to the batch request feed. This method takes either the url_string which is the query URL whose results will be added to the result feed. The query URL will be encapsulated in a BatchEntry, and you may pass in the BatchEntry with a query URL instead of sending a url_string. Args: url_string: str (optional) entry: BatchEntry (optional) batch_id_string: str (optional) Raises: MissingRequiredParameters """ entry = self.AddBatchEntry(entry=entry, id_url_string=url_string, batch_id_string=batch_id_string, operation_string=BATCH_QUERY) def GetBatchLink(self): for link in self.link: if link.rel == 'http://schemas.google.com/g/2005#batch': return link return None def BatchFeedFromString(xml_string): return atom.CreateClassFromXMLString(BatchFeed, xml_string) class EntryLink(atom.AtomBase): """The gd:entryLink element""" _tag = 'entryLink' _namespace = GDATA_NAMESPACE _children = atom.AtomBase._children.copy() _attributes = atom.AtomBase._attributes.copy() # The entry used to be an atom.Entry, now it is a GDataEntry. _children['{%s}entry' % atom.ATOM_NAMESPACE] = ('entry', GDataEntry) _attributes['rel'] = 'rel' _attributes['readOnly'] = 'read_only' _attributes['href'] = 'href' def __init__(self, href=None, read_only=None, rel=None, entry=None, extension_elements=None, extension_attributes=None, text=None): self.href = href self.read_only = read_only self.rel = rel self.entry = entry self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} def EntryLinkFromString(xml_string): return atom.CreateClassFromXMLString(EntryLink, xml_string) class FeedLink(atom.AtomBase): """The gd:feedLink element""" _tag = 'feedLink' _namespace = GDATA_NAMESPACE _children = atom.AtomBase._children.copy() _attributes = atom.AtomBase._attributes.copy() _children['{%s}feed' % atom.ATOM_NAMESPACE] = ('feed', GDataFeed) _attributes['rel'] = 'rel' _attributes['readOnly'] = 'read_only' _attributes['countHint'] = 'count_hint' _attributes['href'] = 'href' def __init__(self, count_hint=None, href=None, read_only=None, rel=None, feed=None, extension_elements=None, extension_attributes=None, text=None): self.count_hint = count_hint self.href = href self.read_only = read_only self.rel = rel self.feed = feed self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} def FeedLinkFromString(xml_string): return atom.CreateClassFromXMLString(FeedLink, xml_string) gdata-2.0.18/src/gdata/client.py0000640036466300116100000014253012156622363016442 0ustar afshareng00000000000000#!/usr/bin/env python # # Copyright (C) 2008, 2009 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. # This module is used for version 2 of the Google Data APIs. """Provides a client to interact with Google Data API servers. This module is used for version 2 of the Google Data APIs. The primary class in this module is GDClient. GDClient: handles auth and CRUD operations when communicating with servers. GDataClient: deprecated client for version one services. Will be removed. """ __author__ = 'j.s@google.com (Jeff Scudder)' import re import atom.client import atom.core import atom.http_core import gdata.gauth import gdata.data class Error(Exception): pass class RequestError(Error): status = None reason = None body = None headers = None class RedirectError(RequestError): pass class CaptchaChallenge(RequestError): captcha_url = None captcha_token = None class ClientLoginTokenMissing(Error): pass class MissingOAuthParameters(Error): pass class ClientLoginFailed(RequestError): pass class UnableToUpgradeToken(RequestError): pass class Unauthorized(Error): pass class BadAuthenticationServiceURL(RedirectError): pass class BadAuthentication(RequestError): pass class NotModified(RequestError): pass class NotImplemented(RequestError): pass def error_from_response(message, http_response, error_class, response_body=None): """Creates a new exception and sets the HTTP information in the error. Args: message: str human readable message to be displayed if the exception is not caught. http_response: The response from the server, contains error information. error_class: The exception to be instantiated and populated with information from the http_response response_body: str (optional) specify if the response has already been read from the http_response object. """ if response_body is None: body = http_response.read() else: body = response_body error = error_class('%s: %i, %s' % (message, http_response.status, body)) error.status = http_response.status error.reason = http_response.reason error.body = body error.headers = atom.http_core.get_headers(http_response) return error def get_xml_version(version): """Determines which XML schema to use based on the client API version. Args: version: string which is converted to an int. The version string is in the form 'Major.Minor.x.y.z' and only the major version number is considered. If None is provided assume version 1. """ if version is None: return 1 return int(version.split('.')[0]) class GDClient(atom.client.AtomPubClient): """Communicates with Google Data servers to perform CRUD operations. This class is currently experimental and may change in backwards incompatible ways. This class exists to simplify the following three areas involved in using the Google Data APIs. CRUD Operations: The client provides a generic 'request' method for making HTTP requests. There are a number of convenience methods which are built on top of request, which include get_feed, get_entry, get_next, post, update, and delete. These methods contact the Google Data servers. Auth: Reading user-specific private data requires authorization from the user as do any changes to user data. An auth_token object can be passed into any of the HTTP requests to set the Authorization header in the request. You may also want to set the auth_token member to a an object which can use modify_request to set the Authorization header in the HTTP request. If you are authenticating using the email address and password, you can use the client_login method to obtain an auth token and set the auth_token member. If you are using browser redirects, specifically AuthSub, you will want to use gdata.gauth.AuthSubToken.from_url to obtain the token after the redirect, and you will probably want to updgrade this since use token to a multiple use (session) token using the upgrade_token method. API Versions: This client is multi-version capable and can be used with Google Data API version 1 and version 2. The version should be specified by setting the api_version member to a string, either '1' or '2'. """ # The gsessionid is used by Google Calendar to prevent redirects. __gsessionid = None api_version = None # Name of the Google Data service when making a ClientLogin request. auth_service = None # URL prefixes which should be requested for AuthSub and OAuth. auth_scopes = None # Name of alternate auth service to use in certain cases alt_auth_service = None def request(self, method=None, uri=None, auth_token=None, http_request=None, converter=None, desired_class=None, redirects_remaining=4, **kwargs): """Make an HTTP request to the server. See also documentation for atom.client.AtomPubClient.request. If a 302 redirect is sent from the server to the client, this client assumes that the redirect is in the form used by the Google Calendar API. The same request URI and method will be used as in the original request, but a gsessionid URL parameter will be added to the request URI with the value provided in the server's 302 redirect response. If the 302 redirect is not in the format specified by the Google Calendar API, a RedirectError will be raised containing the body of the server's response. The method calls the client's modify_request method to make any changes required by the client before the request is made. For example, a version 2 client could add a GData-Version: 2 header to the request in its modify_request method. Args: method: str The HTTP verb for this request, usually 'GET', 'POST', 'PUT', or 'DELETE' uri: atom.http_core.Uri, str, or unicode The URL being requested. auth_token: An object which sets the Authorization HTTP header in its modify_request method. Recommended classes include gdata.gauth.ClientLoginToken and gdata.gauth.AuthSubToken among others. http_request: (optional) atom.http_core.HttpRequest converter: function which takes the body of the response as its only argument and returns the desired object. desired_class: class descended from atom.core.XmlElement to which a successful response should be converted. If there is no converter function specified (converter=None) then the desired_class will be used in calling the atom.core.parse function. If neither the desired_class nor the converter is specified, an HTTP reponse object will be returned. redirects_remaining: (optional) int, if this number is 0 and the server sends a 302 redirect, the request method will raise an exception. This parameter is used in recursive request calls to avoid an infinite loop. Any additional arguments are passed through to atom.client.AtomPubClient.request. Returns: An HTTP response object (see atom.http_core.HttpResponse for a description of the object's interface) if no converter was specified and no desired_class was specified. If a converter function was provided, the results of calling the converter are returned. If no converter was specified but a desired_class was provided, the response body will be converted to the class using atom.core.parse. """ if isinstance(uri, (str, unicode)): uri = atom.http_core.Uri.parse_uri(uri) # Add the gsession ID to the URL to prevent further redirects. # TODO: If different sessions are using the same client, there will be a # multitude of redirects and session ID shuffling. # If the gsession ID is in the URL, adopt it as the standard location. if uri is not None and uri.query is not None and 'gsessionid' in uri.query: self.__gsessionid = uri.query['gsessionid'] # The gsession ID could also be in the HTTP request. elif (http_request is not None and http_request.uri is not None and http_request.uri.query is not None and 'gsessionid' in http_request.uri.query): self.__gsessionid = http_request.uri.query['gsessionid'] # If the gsession ID is stored in the client, and was not present in the # URI then add it to the URI. elif self.__gsessionid is not None: uri.query['gsessionid'] = self.__gsessionid # The AtomPubClient should call this class' modify_request before # performing the HTTP request. #http_request = self.modify_request(http_request) response = atom.client.AtomPubClient.request(self, method=method, uri=uri, auth_token=auth_token, http_request=http_request, **kwargs) # On success, convert the response body using the desired converter # function if present. if response is None: return None if response.status == 200 or response.status == 201: if converter is not None: return converter(response) elif desired_class is not None: if self.api_version is not None: return atom.core.parse(response.read(), desired_class, version=get_xml_version(self.api_version)) else: # No API version was specified, so allow parse to # use the default version. return atom.core.parse(response.read(), desired_class) else: return response # TODO: move the redirect logic into the Google Calendar client once it # exists since the redirects are only used in the calendar API. elif response.status == 302: if redirects_remaining > 0: location = (response.getheader('Location') or response.getheader('location')) if location is not None: # Make a recursive call with the gsession ID in the URI to follow # the redirect. return self.request(method=method, uri=location, auth_token=auth_token, http_request=http_request, converter=converter, desired_class=desired_class, redirects_remaining=redirects_remaining-1, **kwargs) else: raise error_from_response('302 received without Location header', response, RedirectError) else: raise error_from_response('Too many redirects from server', response, RedirectError) elif response.status == 401: raise error_from_response('Unauthorized - Server responded with', response, Unauthorized) elif response.status == 304: raise error_from_response('Entry Not Modified - Server responded with', response, NotModified) elif response.status == 501: raise error_from_response( 'This API operation is not implemented. - Server responded with', response, NotImplemented) # If the server's response was not a 200, 201, 302, 304, 401, or 501, raise # an exception. else: raise error_from_response('Server responded with', response, RequestError) Request = request def request_client_login_token( self, email, password, source, service=None, account_type='HOSTED_OR_GOOGLE', auth_url=atom.http_core.Uri.parse_uri( 'https://www.google.com/accounts/ClientLogin'), captcha_token=None, captcha_response=None): service = service or self.auth_service # Set the target URL. http_request = atom.http_core.HttpRequest(uri=auth_url, method='POST') http_request.add_body_part( gdata.gauth.generate_client_login_request_body(email=email, password=password, service=service, source=source, account_type=account_type, captcha_token=captcha_token, captcha_response=captcha_response), 'application/x-www-form-urlencoded') # Use the underlying http_client to make the request. response = self.http_client.request(http_request) response_body = response.read() if response.status == 200: token_string = gdata.gauth.get_client_login_token_string(response_body) if token_string is not None: return gdata.gauth.ClientLoginToken(token_string) else: raise ClientLoginTokenMissing( 'Recieved a 200 response to client login request,' ' but no token was present. %s' % (response_body,)) elif response.status == 403: captcha_challenge = gdata.gauth.get_captcha_challenge(response_body) if captcha_challenge: challenge = CaptchaChallenge('CAPTCHA required') challenge.captcha_url = captcha_challenge['url'] challenge.captcha_token = captcha_challenge['token'] raise challenge elif response_body.splitlines()[0] == 'Error=BadAuthentication': raise BadAuthentication('Incorrect username or password') else: raise error_from_response('Server responded with a 403 code', response, RequestError, response_body) elif response.status == 302: # Google tries to redirect all bad URLs back to # http://www.google.. If a redirect # attempt is made, assume the user has supplied an incorrect # authentication URL raise error_from_response('Server responded with a redirect', response, BadAuthenticationServiceURL, response_body) else: raise error_from_response('Server responded to ClientLogin request', response, ClientLoginFailed, response_body) RequestClientLoginToken = request_client_login_token def client_login(self, email, password, source, service=None, account_type='HOSTED_OR_GOOGLE', auth_url=atom.http_core.Uri.parse_uri( 'https://www.google.com/accounts/ClientLogin'), captcha_token=None, captcha_response=None): """Performs an auth request using the user's email address and password. In order to modify user specific data and read user private data, your application must be authorized by the user. One way to demonstrage authorization is by including a Client Login token in the Authorization HTTP header of all requests. This method requests the Client Login token by sending the user's email address, password, the name of the application, and the service code for the service which will be accessed by the application. If the username and password are correct, the server will respond with the client login code and a new ClientLoginToken object will be set in the client's auth_token member. With the auth_token set, future requests from this client will include the Client Login token. For a list of service names, see http://code.google.com/apis/gdata/faq.html#clientlogin For more information on Client Login, see: http://code.google.com/apis/accounts/docs/AuthForInstalledApps.html Args: email: str The user's email address or username. password: str The password for the user's account. source: str The name of your application. This can be anything you like but should should give some indication of which app is making the request. service: str The service code for the service you would like to access. For example, 'cp' for contacts, 'cl' for calendar. For a full list see http://code.google.com/apis/gdata/faq.html#clientlogin If you are using a subclass of the gdata.client.GDClient, the service will usually be filled in for you so you do not need to specify it. For example see BloggerClient, SpreadsheetsClient, etc. account_type: str (optional) The type of account which is being authenticated. This can be either 'GOOGLE' for a Google Account, 'HOSTED' for a Google Apps Account, or the default 'HOSTED_OR_GOOGLE' which will select the Google Apps Account if the same email address is used for both a Google Account and a Google Apps Account. auth_url: str (optional) The URL to which the login request should be sent. captcha_token: str (optional) If a previous login attempt was reponded to with a CAPTCHA challenge, this is the token which identifies the challenge (from the CAPTCHA's URL). captcha_response: str (optional) If a previous login attempt was reponded to with a CAPTCHA challenge, this is the response text which was contained in the challenge. Returns: Generated token, which is also stored in this object. Raises: A RequestError or one of its suclasses: BadAuthentication, BadAuthenticationServiceURL, ClientLoginFailed, ClientLoginTokenMissing, or CaptchaChallenge """ service = service or self.auth_service self.auth_token = self.request_client_login_token(email, password, source, service=service, account_type=account_type, auth_url=auth_url, captcha_token=captcha_token, captcha_response=captcha_response) if self.alt_auth_service is not None: self.alt_auth_token = self.request_client_login_token( email, password, source, service=self.alt_auth_service, account_type=account_type, auth_url=auth_url, captcha_token=captcha_token, captcha_response=captcha_response) return self.auth_token ClientLogin = client_login def upgrade_token(self, token=None, url=atom.http_core.Uri.parse_uri( 'https://www.google.com/accounts/AuthSubSessionToken')): """Asks the Google auth server for a multi-use AuthSub token. For details on AuthSub, see: http://code.google.com/apis/accounts/docs/AuthSub.html Args: token: gdata.gauth.AuthSubToken or gdata.gauth.SecureAuthSubToken (optional) If no token is passed in, the client's auth_token member is used to request the new token. The token object will be modified to contain the new session token string. url: str or atom.http_core.Uri (optional) The URL to which the token upgrade request should be sent. Defaults to: https://www.google.com/accounts/AuthSubSessionToken Returns: The upgraded gdata.gauth.AuthSubToken object. """ # Default to using the auth_token member if no token is provided. if token is None: token = self.auth_token # We cannot upgrade a None token. if token is None: raise UnableToUpgradeToken('No token was provided.') if not isinstance(token, gdata.gauth.AuthSubToken): raise UnableToUpgradeToken( 'Cannot upgrade the token because it is not an AuthSubToken object.') http_request = atom.http_core.HttpRequest(uri=url, method='GET') token.modify_request(http_request) # Use the lower level HttpClient to make the request. response = self.http_client.request(http_request) if response.status == 200: token._upgrade_token(response.read()) return token else: raise UnableToUpgradeToken( 'Server responded to token upgrade request with %s: %s' % ( response.status, response.read())) UpgradeToken = upgrade_token def revoke_token(self, token=None, url=atom.http_core.Uri.parse_uri( 'https://www.google.com/accounts/AuthSubRevokeToken')): """Requests that the token be invalidated. This method can be used for both AuthSub and OAuth tokens (to invalidate a ClientLogin token, the user must change their password). Returns: True if the server responded with a 200. Raises: A RequestError if the server responds with a non-200 status. """ # Default to using the auth_token member if no token is provided. if token is None: token = self.auth_token http_request = atom.http_core.HttpRequest(uri=url, method='GET') token.modify_request(http_request) response = self.http_client.request(http_request) if response.status != 200: raise error_from_response('Server sent non-200 to revoke token', response, RequestError, response.read()) return True RevokeToken = revoke_token def get_oauth_token(self, scopes, next, consumer_key, consumer_secret=None, rsa_private_key=None, url=gdata.gauth.REQUEST_TOKEN_URL): """Obtains an OAuth request token to allow the user to authorize this app. Once this client has a request token, the user can authorize the request token by visiting the authorization URL in their browser. After being redirected back to this app at the 'next' URL, this app can then exchange the authorized request token for an access token. For more information see the documentation on Google Accounts with OAuth: http://code.google.com/apis/accounts/docs/OAuth.html#AuthProcess Args: scopes: list of strings or atom.http_core.Uri objects which specify the URL prefixes which this app will be accessing. For example, to access the Google Calendar API, you would want to use scopes: ['https://www.google.com/calendar/feeds/', 'http://www.google.com/calendar/feeds/'] next: str or atom.http_core.Uri object, The URL which the user's browser should be sent to after they authorize access to their data. This should be a URL in your application which will read the token information from the URL and upgrade the request token to an access token. consumer_key: str This is the identifier for this application which you should have received when you registered your application with Google to use OAuth. consumer_secret: str (optional) The shared secret between your app and Google which provides evidence that this request is coming from you application and not another app. If present, this libraries assumes you want to use an HMAC signature to verify requests. Keep this data a secret. rsa_private_key: str (optional) The RSA private key which is used to generate a digital signature which is checked by Google's server. If present, this library assumes that you want to use an RSA signature to verify requests. Keep this data a secret. url: The URL to which a request for a token should be made. The default is Google's OAuth request token provider. """ http_request = None if rsa_private_key is not None: http_request = gdata.gauth.generate_request_for_request_token( consumer_key, gdata.gauth.RSA_SHA1, scopes, rsa_key=rsa_private_key, auth_server_url=url, next=next) elif consumer_secret is not None: http_request = gdata.gauth.generate_request_for_request_token( consumer_key, gdata.gauth.HMAC_SHA1, scopes, consumer_secret=consumer_secret, auth_server_url=url, next=next) else: raise MissingOAuthParameters( 'To request an OAuth token, you must provide your consumer secret' ' or your private RSA key.') response = self.http_client.request(http_request) response_body = response.read() if response.status != 200: raise error_from_response('Unable to obtain OAuth request token', response, RequestError, response_body) if rsa_private_key is not None: return gdata.gauth.rsa_token_from_body(response_body, consumer_key, rsa_private_key, gdata.gauth.REQUEST_TOKEN) elif consumer_secret is not None: return gdata.gauth.hmac_token_from_body(response_body, consumer_key, consumer_secret, gdata.gauth.REQUEST_TOKEN) GetOAuthToken = get_oauth_token def get_access_token(self, request_token, url=gdata.gauth.ACCESS_TOKEN_URL): """Exchanges an authorized OAuth request token for an access token. Contacts the Google OAuth server to upgrade a previously authorized request token. Once the request token is upgraded to an access token, the access token may be used to access the user's data. For more details, see the Google Accounts OAuth documentation: http://code.google.com/apis/accounts/docs/OAuth.html#AccessToken Args: request_token: An OAuth token which has been authorized by the user. url: (optional) The URL to which the upgrade request should be sent. Defaults to: https://www.google.com/accounts/OAuthAuthorizeToken """ http_request = gdata.gauth.generate_request_for_access_token( request_token, auth_server_url=url) response = self.http_client.request(http_request) response_body = response.read() if response.status != 200: raise error_from_response( 'Unable to upgrade OAuth request token to access token', response, RequestError, response_body) return gdata.gauth.upgrade_to_access_token(request_token, response_body) GetAccessToken = get_access_token def modify_request(self, http_request): """Adds or changes request before making the HTTP request. This client will add the API version if it is specified. Subclasses may override this method to add their own request modifications before the request is made. """ http_request = atom.client.AtomPubClient.modify_request(self, http_request) if self.api_version is not None: http_request.headers['GData-Version'] = self.api_version return http_request ModifyRequest = modify_request def get_feed(self, uri, auth_token=None, converter=None, desired_class=gdata.data.GDFeed, **kwargs): return self.request(method='GET', uri=uri, auth_token=auth_token, converter=converter, desired_class=desired_class, **kwargs) GetFeed = get_feed def get_entry(self, uri, auth_token=None, converter=None, desired_class=gdata.data.GDEntry, etag=None, **kwargs): http_request = atom.http_core.HttpRequest() # Conditional retrieval if etag is not None: http_request.headers['If-None-Match'] = etag return self.request(method='GET', uri=uri, auth_token=auth_token, http_request=http_request, converter=converter, desired_class=desired_class, **kwargs) GetEntry = get_entry def get_next(self, feed, auth_token=None, converter=None, desired_class=None, **kwargs): """Fetches the next set of results from the feed. When requesting a feed, the number of entries returned is capped at a service specific default limit (often 25 entries). You can specify your own entry-count cap using the max-results URL query parameter. If there are more results than could fit under max-results, the feed will contain a next link. This method performs a GET against this next results URL. Returns: A new feed object containing the next set of entries in this feed. """ if converter is None and desired_class is None: desired_class = feed.__class__ return self.get_feed(feed.find_next_link(), auth_token=auth_token, converter=converter, desired_class=desired_class, **kwargs) GetNext = get_next # TODO: add a refresh method to re-fetch the entry/feed from the server # if it has been updated. def post(self, entry, uri, auth_token=None, converter=None, desired_class=None, **kwargs): if converter is None and desired_class is None: desired_class = entry.__class__ http_request = atom.http_core.HttpRequest() http_request.add_body_part( entry.to_string(get_xml_version(self.api_version)), 'application/atom+xml') return self.request(method='POST', uri=uri, auth_token=auth_token, http_request=http_request, converter=converter, desired_class=desired_class, **kwargs) Post = post def update(self, entry, auth_token=None, force=False, uri=None, **kwargs): """Edits the entry on the server by sending the XML for this entry. Performs a PUT and converts the response to a new entry object with a matching class to the entry passed in. Args: entry: auth_token: force: boolean stating whether an update should be forced. Defaults to False. Normally, if a change has been made since the passed in entry was obtained, the server will not overwrite the entry since the changes were based on an obsolete version of the entry. Setting force to True will cause the update to silently overwrite whatever version is present. uri: The uri to put to. If provided, this uri is PUT to rather than the inferred uri from the entry's edit link. Returns: A new Entry object of a matching type to the entry which was passed in. """ http_request = atom.http_core.HttpRequest() http_request.add_body_part( entry.to_string(get_xml_version(self.api_version)), 'application/atom+xml') # Include the ETag in the request if present. if force: http_request.headers['If-Match'] = '*' elif hasattr(entry, 'etag') and entry.etag: http_request.headers['If-Match'] = entry.etag if uri is None: uri = entry.find_edit_link() return self.request(method='PUT', uri=uri, auth_token=auth_token, http_request=http_request, desired_class=entry.__class__, **kwargs) Update = update def delete(self, entry_or_uri, auth_token=None, force=False, **kwargs): http_request = atom.http_core.HttpRequest() # Include the ETag in the request if present. if force: http_request.headers['If-Match'] = '*' elif hasattr(entry_or_uri, 'etag') and entry_or_uri.etag: http_request.headers['If-Match'] = entry_or_uri.etag # If the user passes in a URL, just delete directly, may not work as # the service might require an ETag. if isinstance(entry_or_uri, (str, unicode, atom.http_core.Uri)): return self.request(method='DELETE', uri=entry_or_uri, http_request=http_request, auth_token=auth_token, **kwargs) return self.request(method='DELETE', uri=entry_or_uri.find_edit_link(), http_request=http_request, auth_token=auth_token, **kwargs) Delete = delete def batch(self, feed, uri=None, force=False, auth_token=None, **kwargs): """Sends a batch request to the server to execute operation entries. Args: feed: A batch feed containing batch entries, each is an operation. uri: (optional) The uri to which the batch request feed should be POSTed. If none is provided, then the feed's edit link will be used. force: (optional) boolean set to True if you want the batch update to clobber all data. If False, the version in the information in the feed object will cause the server to check to see that no changes intervened between when you fetched the data and when you sent the changes. auth_token: (optional) An object which sets the Authorization HTTP header in its modify_request method. Recommended classes include gdata.gauth.ClientLoginToken and gdata.gauth.AuthSubToken among others. """ http_request = atom.http_core.HttpRequest() http_request.add_body_part( feed.to_string(get_xml_version(self.api_version)), 'application/atom+xml') if force: http_request.headers['If-Match'] = '*' elif hasattr(feed, 'etag') and feed.etag: http_request.headers['If-Match'] = feed.etag if uri is None: uri = feed.find_edit_link() return self.request(method='POST', uri=uri, auth_token=auth_token, http_request=http_request, desired_class=feed.__class__, **kwargs) Batch = batch # TODO: add a refresh method to request a conditional update to an entry # or feed. def _add_query_param(param_string, value, http_request): if value: http_request.uri.query[param_string] = value class Query(object): def __init__(self, text_query=None, categories=None, author=None, alt=None, updated_min=None, updated_max=None, pretty_print=False, published_min=None, published_max=None, start_index=None, max_results=None, strict=False, **custom_parameters): """Constructs a Google Data Query to filter feed contents serverside. Args: text_query: Full text search str (optional) categories: list of strings (optional). Each string is a required category. To include an 'or' query, put a | in the string between terms. For example, to find everything in the Fitz category and the Laurie or Jane category (Fitz and (Laurie or Jane)) you would set categories to ['Fitz', 'Laurie|Jane']. author: str (optional) The service returns entries where the author name and/or email address match your query string. alt: str (optional) for the Alternative representation type you'd like the feed in. If you don't specify an alt parameter, the service returns an Atom feed. This is equivalent to alt='atom'. alt='rss' returns an RSS 2.0 result feed. alt='json' returns a JSON representation of the feed. alt='json-in-script' Requests a response that wraps JSON in a script tag. alt='atom-in-script' Requests an Atom response that wraps an XML string in a script tag. alt='rss-in-script' Requests an RSS response that wraps an XML string in a script tag. updated_min: str (optional), RFC 3339 timestamp format, lower bounds. For example: 2005-08-09T10:57:00-08:00 updated_max: str (optional) updated time must be earlier than timestamp. pretty_print: boolean (optional) If True the server's XML response will be indented to make it more human readable. Defaults to False. published_min: str (optional), Similar to updated_min but for published time. published_max: str (optional), Similar to updated_max but for published time. start_index: int or str (optional) 1-based index of the first result to be retrieved. Note that this isn't a general cursoring mechanism. If you first send a query with ?start-index=1&max-results=10 and then send another query with ?start-index=11&max-results=10, the service cannot guarantee that the results are equivalent to ?start-index=1&max-results=20, because insertions and deletions could have taken place in between the two queries. max_results: int or str (optional) Maximum number of results to be retrieved. Each service has a default max (usually 25) which can vary from service to service. There is also a service-specific limit to the max_results you can fetch in a request. strict: boolean (optional) If True, the server will return an error if the server does not recognize any of the parameters in the request URL. Defaults to False. custom_parameters: other query parameters that are not explicitly defined. """ self.text_query = text_query self.categories = categories or [] self.author = author self.alt = alt self.updated_min = updated_min self.updated_max = updated_max self.pretty_print = pretty_print self.published_min = published_min self.published_max = published_max self.start_index = start_index self.max_results = max_results self.strict = strict self.custom_parameters = custom_parameters def add_custom_parameter(self, key, value): self.custom_parameters[key] = value AddCustomParameter = add_custom_parameter def modify_request(self, http_request): _add_query_param('q', self.text_query, http_request) if self.categories: http_request.uri.query['category'] = ','.join(self.categories) _add_query_param('author', self.author, http_request) _add_query_param('alt', self.alt, http_request) _add_query_param('updated-min', self.updated_min, http_request) _add_query_param('updated-max', self.updated_max, http_request) if self.pretty_print: http_request.uri.query['prettyprint'] = 'true' _add_query_param('published-min', self.published_min, http_request) _add_query_param('published-max', self.published_max, http_request) if self.start_index is not None: http_request.uri.query['start-index'] = str(self.start_index) if self.max_results is not None: http_request.uri.query['max-results'] = str(self.max_results) if self.strict: http_request.uri.query['strict'] = 'true' http_request.uri.query.update(self.custom_parameters) ModifyRequest = modify_request class GDQuery(atom.http_core.Uri): def _get_text_query(self): return self.query['q'] def _set_text_query(self, value): self.query['q'] = value text_query = property(_get_text_query, _set_text_query, doc='The q parameter for searching for an exact text match on content') class ResumableUploader(object): """Resumable upload helper for the Google Data protocol.""" DEFAULT_CHUNK_SIZE = 5242880 # 5MB # Initial chunks which are smaller than 256KB might be dropped. The last # chunk for a file can be smaller tan this. MIN_CHUNK_SIZE = 262144 # 256KB def __init__(self, client, file_handle, content_type, total_file_size, chunk_size=None, desired_class=None): """Starts a resumable upload to a service that supports the protocol. Args: client: gdata.client.GDClient A Google Data API service. file_handle: object A file-like object containing the file to upload. content_type: str The mimetype of the file to upload. total_file_size: int The file's total size in bytes. chunk_size: int The size of each upload chunk. If None, the DEFAULT_CHUNK_SIZE will be used. desired_class: object (optional) The type of gdata.data.GDEntry to parse the completed entry as. This should be specific to the API. """ self.client = client self.file_handle = file_handle self.content_type = content_type self.total_file_size = total_file_size self.chunk_size = chunk_size or self.DEFAULT_CHUNK_SIZE if self.chunk_size < self.MIN_CHUNK_SIZE: self.chunk_size = self.MIN_CHUNK_SIZE self.desired_class = desired_class or gdata.data.GDEntry self.upload_uri = None # Send the entire file if the chunk size is less than fize's total size. if self.total_file_size <= self.chunk_size: self.chunk_size = total_file_size def _init_session(self, resumable_media_link, entry=None, headers=None, auth_token=None, method='POST'): """Starts a new resumable upload to a service that supports the protocol. The method makes a request to initiate a new upload session. The unique upload uri returned by the server (and set in this method) should be used to send upload chunks to the server. Args: resumable_media_link: str The full URL for the #resumable-create-media or #resumable-edit-media link for starting a resumable upload request or updating media using a resumable PUT. entry: A (optional) gdata.data.GDEntry containging metadata to create the upload from. headers: dict (optional) Additional headers to send in the initial request to create the resumable upload request. These headers will override any default headers sent in the request. For example: headers={'Slug': 'MyTitle'}. auth_token: (optional) An object which sets the Authorization HTTP header in its modify_request method. Recommended classes include gdata.gauth.ClientLoginToken and gdata.gauth.AuthSubToken among others. method: (optional) Type of HTTP request to start the session with. Defaults to 'POST', but may also be 'PUT'. Returns: Result of HTTP request to intialize the session. See atom.client.request. Raises: RequestError if the unique upload uri is not set or the server returns something other than an HTTP 308 when the upload is incomplete. """ http_request = atom.http_core.HttpRequest() # Send empty body if Atom XML wasn't specified. if entry is None: http_request.add_body_part('', self.content_type, size=0) else: http_request.add_body_part(str(entry), 'application/atom+xml', size=len(str(entry))) http_request.headers['X-Upload-Content-Type'] = self.content_type http_request.headers['X-Upload-Content-Length'] = str(self.total_file_size) if headers is not None: http_request.headers.update(headers) response = self.client.request(method=method, uri=resumable_media_link, auth_token=auth_token, http_request=http_request) self.upload_uri = (response.getheader('location') or response.getheader('Location')) return response _InitSession = _init_session def upload_chunk(self, start_byte, content_bytes): """Uploads a byte range (chunk) to the resumable upload server. Args: start_byte: int The byte offset of the total file where the byte range passed in lives. content_bytes: str The file contents of this chunk. Returns: The final Atom entry created on the server. The entry object's type will be the class specified in self.desired_class. Raises: RequestError if the unique upload uri is not set or the server returns something other than an HTTP 308 when the upload is incomplete. """ if self.upload_uri is None: raise RequestError('Resumable upload request not initialized.') # Adjustment if last byte range is less than defined chunk size. chunk_size = self.chunk_size if len(content_bytes) <= chunk_size: chunk_size = len(content_bytes) http_request = atom.http_core.HttpRequest() http_request.add_body_part(content_bytes, self.content_type, size=len(content_bytes)) http_request.headers['Content-Range'] = ('bytes %s-%s/%s' % (start_byte, start_byte + chunk_size - 1, self.total_file_size)) try: response = self.client.request(method='PUT', uri=self.upload_uri, http_request=http_request, desired_class=self.desired_class) return response except RequestError, error: if error.status == 308: return None else: raise error UploadChunk = upload_chunk def upload_file(self, resumable_media_link, entry=None, headers=None, auth_token=None, **kwargs): """Uploads an entire file in chunks using the resumable upload protocol. If you are interested in pausing an upload or controlling the chunking yourself, use the upload_chunk() method instead. Args: resumable_media_link: str The full URL for the #resumable-create-media for starting a resumable upload request. entry: A (optional) gdata.data.GDEntry containging metadata to create the upload from. headers: dict Additional headers to send in the initial request to create the resumable upload request. These headers will override any default headers sent in the request. For example: headers={'Slug': 'MyTitle'}. auth_token: (optional) An object which sets the Authorization HTTP header in its modify_request method. Recommended classes include gdata.gauth.ClientLoginToken and gdata.gauth.AuthSubToken among others. kwargs: (optional) Other args to pass to self._init_session. Returns: The final Atom entry created on the server. The entry object's type will be the class specified in self.desired_class. Raises: RequestError if anything other than a HTTP 308 is returned when the request raises an exception. """ self._init_session(resumable_media_link, headers=headers, auth_token=auth_token, entry=entry, **kwargs) start_byte = 0 entry = None while not entry: entry = self.upload_chunk( start_byte, self.file_handle.read(self.chunk_size)) start_byte += self.chunk_size return entry UploadFile = upload_file def update_file(self, entry_or_resumable_edit_link, headers=None, force=False, auth_token=None, update_metadata=False, uri_params=None): """Updates the contents of an existing file using the resumable protocol. If you are interested in pausing an upload or controlling the chunking yourself, use the upload_chunk() method instead. Args: entry_or_resumable_edit_link: object or string A gdata.data.GDEntry for the entry/file to update or the full uri of the link with rel #resumable-edit-media. headers: dict Additional headers to send in the initial request to create the resumable upload request. These headers will override any default headers sent in the request. For example: headers={'Slug': 'MyTitle'}. force boolean (optional) True to force an update and set the If-Match header to '*'. If False and entry_or_resumable_edit_link is a gdata.data.GDEntry object, its etag value is used. Otherwise this parameter should be set to True to force the update. auth_token: (optional) An object which sets the Authorization HTTP header in its modify_request method. Recommended classes include gdata.gauth.ClientLoginToken and gdata.gauth.AuthSubToken among others. update_metadata: (optional) True to also update the entry's metadata with that in the given GDEntry object in entry_or_resumable_edit_link. uri_params: (optional) Dict of additional parameters to attach to the URI. Some non-dict types are valid here, too, like list of tuple pairs. Returns: The final Atom entry created on the server. The entry object's type will be the class specified in self.desired_class. Raises: RequestError if anything other than a HTTP 308 is returned when the request raises an exception. """ custom_headers = {} if headers is not None: custom_headers.update(headers) uri = None entry = None if isinstance(entry_or_resumable_edit_link, gdata.data.GDEntry): uri = entry_or_resumable_edit_link.find_url( 'http://schemas.google.com/g/2005#resumable-edit-media') custom_headers['If-Match'] = entry_or_resumable_edit_link.etag if update_metadata: entry = entry_or_resumable_edit_link else: uri = entry_or_resumable_edit_link uri = atom.http_core.parse_uri(uri) if uri_params is not None: uri.query.update(uri_params) if force: custom_headers['If-Match'] = '*' return self.upload_file(str(uri), entry=entry, headers=custom_headers, auth_token=auth_token, method='PUT') UpdateFile = update_file def query_upload_status(self, uri=None): """Queries the current status of a resumable upload request. Args: uri: str (optional) A resumable upload uri to query and override the one that is set in this object. Returns: An integer representing the file position (byte) to resume the upload from or True if the upload is complete. Raises: RequestError if anything other than a HTTP 308 is returned when the request raises an exception. """ # Override object's unique upload uri. if uri is None: uri = self.upload_uri http_request = atom.http_core.HttpRequest() http_request.headers['Content-Length'] = '0' http_request.headers['Content-Range'] = 'bytes */%s' % self.total_file_size try: response = self.client.request( method='POST', uri=uri, http_request=http_request) if response.status == 201: return True else: raise error_from_response( '%s returned by server' % response.status, response, RequestError) except RequestError, error: if error.status == 308: for pair in error.headers: if pair[0].capitalize() == 'Range': return int(pair[1].split('-')[1]) + 1 else: raise error QueryUploadStatus = query_upload_status gdata-2.0.18/src/gdata/oauth/0000750036466300116100000000000012156625015015721 5ustar afshareng00000000000000gdata-2.0.18/src/gdata/oauth/CHANGES.txt0000750036466300116100000000145612156622363017546 0ustar afshareng000000000000001. Moved oauth.py to __init__.py 2. Refactored __init__.py for compatibility with python 2.2 (Issue 59) 3. Refactored rsa.py for compatibility with python 2.2 (Issue 59) 4. Refactored OAuthRequest.from_token_and_callback since the callback url was getting double url-encoding the callback url in place of single. (Issue 43) 5. Added build_signature_base_string method to rsa.py since it used the implementation of this method from oauth.OAuthSignatureMethod_HMAC_SHA1 which was incorrect since it enforced the presence of a consumer secret and a token secret. Also, changed its super class from oauth.OAuthSignatureMethod_HMAC_SHA1 to oauth.OAuthSignatureMethod (Issue 64) 6. Refactored .to_header method since it returned non-oauth params as well which was incorrect. (Issue 31)gdata-2.0.18/src/gdata/oauth/rsa.py0000750036466300116100000001110412156622363017063 0ustar afshareng00000000000000#!/usr/bin/python """ requires tlslite - http://trevp.net/tlslite/ """ import binascii try: from gdata.tlslite.utils import keyfactory except ImportError: from tlslite.tlslite.utils import keyfactory try: from gdata.tlslite.utils import cryptomath except ImportError: from tlslite.tlslite.utils import cryptomath # XXX andy: ugly local import due to module name, oauth.oauth import gdata.oauth as oauth class OAuthSignatureMethod_RSA_SHA1(oauth.OAuthSignatureMethod): def get_name(self): return "RSA-SHA1" def _fetch_public_cert(self, oauth_request): # not implemented yet, ideas are: # (1) do a lookup in a table of trusted certs keyed off of consumer # (2) fetch via http using a url provided by the requester # (3) some sort of specific discovery code based on request # # either way should return a string representation of the certificate raise NotImplementedError def _fetch_private_cert(self, oauth_request): # not implemented yet, ideas are: # (1) do a lookup in a table of trusted certs keyed off of consumer # # either way should return a string representation of the certificate raise NotImplementedError def build_signature_base_string(self, oauth_request, consumer, token): sig = ( oauth.escape(oauth_request.get_normalized_http_method()), oauth.escape(oauth_request.get_normalized_http_url()), oauth.escape(oauth_request.get_normalized_parameters()), ) key = '' raw = '&'.join(sig) return key, raw def build_signature(self, oauth_request, consumer, token): key, base_string = self.build_signature_base_string(oauth_request, consumer, token) # Fetch the private key cert based on the request cert = self._fetch_private_cert(oauth_request) # Pull the private key from the certificate privatekey = keyfactory.parsePrivateKey(cert) # Convert base_string to bytes #base_string_bytes = cryptomath.createByteArraySequence(base_string) # Sign using the key signed = privatekey.hashAndSign(base_string) return binascii.b2a_base64(signed)[:-1] def check_signature(self, oauth_request, consumer, token, signature): decoded_sig = base64.b64decode(signature); key, base_string = self.build_signature_base_string(oauth_request, consumer, token) # Fetch the public key cert based on the request cert = self._fetch_public_cert(oauth_request) # Pull the public key from the certificate publickey = keyfactory.parsePEMKey(cert, public=True) # Check the signature ok = publickey.hashAndVerify(decoded_sig, base_string) return ok class TestOAuthSignatureMethod_RSA_SHA1(OAuthSignatureMethod_RSA_SHA1): def _fetch_public_cert(self, oauth_request): cert = """ -----BEGIN CERTIFICATE----- MIIBpjCCAQ+gAwIBAgIBATANBgkqhkiG9w0BAQUFADAZMRcwFQYDVQQDDA5UZXN0 IFByaW5jaXBhbDAeFw03MDAxMDEwODAwMDBaFw0zODEyMzEwODAwMDBaMBkxFzAV BgNVBAMMDlRlc3QgUHJpbmNpcGFsMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB gQC0YjCwIfYoprq/FQO6lb3asXrxLlJFuCvtinTF5p0GxvQGu5O3gYytUvtC2JlY zypSRjVxwxrsuRcP3e641SdASwfrmzyvIgP08N4S0IFzEURkV1wp/IpH7kH41Etb mUmrXSwfNZsnQRE5SYSOhh+LcK2wyQkdgcMv11l4KoBkcwIDAQABMA0GCSqGSIb3 DQEBBQUAA4GBAGZLPEuJ5SiJ2ryq+CmEGOXfvlTtEL2nuGtr9PewxkgnOjZpUy+d 4TvuXJbNQc8f4AMWL/tO9w0Fk80rWKp9ea8/df4qMq5qlFWlx6yOLQxumNOmECKb WpkUQDIDJEoFUzKMVuJf4KO/FJ345+BNLGgbJ6WujreoM1X/gYfdnJ/J -----END CERTIFICATE----- """ return cert def _fetch_private_cert(self, oauth_request): cert = """ -----BEGIN PRIVATE KEY----- MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBALRiMLAh9iimur8V A7qVvdqxevEuUkW4K+2KdMXmnQbG9Aa7k7eBjK1S+0LYmVjPKlJGNXHDGuy5Fw/d 7rjVJ0BLB+ubPK8iA/Tw3hLQgXMRRGRXXCn8ikfuQfjUS1uZSatdLB81mydBETlJ hI6GH4twrbDJCR2Bwy/XWXgqgGRzAgMBAAECgYBYWVtleUzavkbrPjy0T5FMou8H X9u2AC2ry8vD/l7cqedtwMPp9k7TubgNFo+NGvKsl2ynyprOZR1xjQ7WgrgVB+mm uScOM/5HVceFuGRDhYTCObE+y1kxRloNYXnx3ei1zbeYLPCHdhxRYW7T0qcynNmw rn05/KO2RLjgQNalsQJBANeA3Q4Nugqy4QBUCEC09SqylT2K9FrrItqL2QKc9v0Z zO2uwllCbg0dwpVuYPYXYvikNHHg+aCWF+VXsb9rpPsCQQDWR9TT4ORdzoj+Nccn qkMsDmzt0EfNaAOwHOmVJ2RVBspPcxt5iN4HI7HNeG6U5YsFBb+/GZbgfBT3kpNG WPTpAkBI+gFhjfJvRw38n3g/+UeAkwMI2TJQS4n8+hid0uus3/zOjDySH3XHCUno cn1xOJAyZODBo47E+67R4jV1/gzbAkEAklJaspRPXP877NssM5nAZMU0/O/NGCZ+ 3jPgDUno6WbJn5cqm8MqWhW1xGkImgRk+fkDBquiq4gPiT898jusgQJAd5Zrr6Q8 AO/0isr/3aa6O6NLQxISLKcPDk2NOccAfS/xOtfOz4sJYM3+Bs4Io9+dZGSDCA54 Lw03eHTNQghS0A== -----END PRIVATE KEY----- """ return cert gdata-2.0.18/src/gdata/oauth/__init__.py0000750036466300116100000004627612156622363020057 0ustar afshareng00000000000000import cgi import urllib import time import random import urlparse import hmac import binascii VERSION = '1.0' # Hi Blaine! HTTP_METHOD = 'GET' SIGNATURE_METHOD = 'PLAINTEXT' # Generic exception class class OAuthError(RuntimeError): def __init__(self, message='OAuth error occured.'): self.message = message # optional WWW-Authenticate header (401 error) def build_authenticate_header(realm=''): return {'WWW-Authenticate': 'OAuth realm="%s"' % realm} # url escape def escape(s): # escape '/' too return urllib.quote(s, safe='~') # util function: current timestamp # seconds since epoch (UTC) def generate_timestamp(): return int(time.time()) # util function: nonce # pseudorandom number def generate_nonce(length=8): return ''.join([str(random.randint(0, 9)) for i in range(length)]) # OAuthConsumer is a data type that represents the identity of the Consumer # via its shared secret with the Service Provider. class OAuthConsumer(object): key = None secret = None def __init__(self, key, secret): self.key = key self.secret = secret # OAuthToken is a data type that represents an End User via either an access # or request token. class OAuthToken(object): # access tokens and request tokens key = None secret = None ''' key = the token secret = the token secret ''' def __init__(self, key, secret): self.key = key self.secret = secret def to_string(self): return urllib.urlencode({'oauth_token': self.key, 'oauth_token_secret': self.secret}) # return a token from something like: # oauth_token_secret=digg&oauth_token=digg def from_string(s): params = cgi.parse_qs(s, keep_blank_values=False) key = params['oauth_token'][0] secret = params['oauth_token_secret'][0] return OAuthToken(key, secret) from_string = staticmethod(from_string) def __str__(self): return self.to_string() # OAuthRequest represents the request and can be serialized class OAuthRequest(object): ''' OAuth parameters: - oauth_consumer_key - oauth_token - oauth_signature_method - oauth_signature - oauth_timestamp - oauth_nonce - oauth_version ... any additional parameters, as defined by the Service Provider. ''' parameters = None # oauth parameters http_method = HTTP_METHOD http_url = None version = VERSION def __init__(self, http_method=HTTP_METHOD, http_url=None, parameters=None): self.http_method = http_method self.http_url = http_url self.parameters = parameters or {} def set_parameter(self, parameter, value): self.parameters[parameter] = value def get_parameter(self, parameter): try: return self.parameters[parameter] except: raise OAuthError('Parameter not found: %s' % parameter) def _get_timestamp_nonce(self): return self.get_parameter('oauth_timestamp'), self.get_parameter('oauth_nonce') # get any non-oauth parameters def get_nonoauth_parameters(self): parameters = {} for k, v in self.parameters.iteritems(): # ignore oauth parameters if k.find('oauth_') < 0: parameters[k] = v return parameters # serialize as a header for an HTTPAuth request def to_header(self, realm=''): auth_header = 'OAuth realm="%s"' % realm # add the oauth parameters if self.parameters: for k, v in self.parameters.iteritems(): if k[:6] == 'oauth_': auth_header += ', %s="%s"' % (k, escape(str(v))) return {'Authorization': auth_header} # serialize as post data for a POST request def to_postdata(self): return '&'.join(['%s=%s' % (escape(str(k)), escape(str(v))) for k, v in self.parameters.iteritems()]) # serialize as a url for a GET request def to_url(self): return '%s?%s' % (self.get_normalized_http_url(), self.to_postdata()) # return a string that consists of all the parameters that need to be signed def get_normalized_parameters(self): params = self.parameters try: # exclude the signature if it exists del params['oauth_signature'] except: pass key_values = params.items() # sort lexicographically, first after key, then after value key_values.sort() # combine key value pairs in string and escape return '&'.join(['%s=%s' % (escape(str(k)), escape(str(v))) for k, v in key_values]) # just uppercases the http method def get_normalized_http_method(self): return self.http_method.upper() # parses the url and rebuilds it to be scheme://host/path def get_normalized_http_url(self): parts = urlparse.urlparse(self.http_url) host = parts[1].lower() if host.endswith(':80') or host.endswith(':443'): host = host.split(':')[0] url_string = '%s://%s%s' % (parts[0], host, parts[2]) # scheme, netloc, path return url_string # set the signature parameter to the result of build_signature def sign_request(self, signature_method, consumer, token): # set the signature method self.set_parameter('oauth_signature_method', signature_method.get_name()) # set the signature self.set_parameter('oauth_signature', self.build_signature(signature_method, consumer, token)) def build_signature(self, signature_method, consumer, token): # call the build signature method within the signature method return signature_method.build_signature(self, consumer, token) def from_request(http_method, http_url, headers=None, parameters=None, query_string=None): # combine multiple parameter sources if parameters is None: parameters = {} # headers if headers and 'Authorization' in headers: auth_header = headers['Authorization'] # check that the authorization header is OAuth if auth_header.index('OAuth') > -1: try: # get the parameters from the header header_params = OAuthRequest._split_header(auth_header) parameters.update(header_params) except: raise OAuthError('Unable to parse OAuth parameters from Authorization header.') # GET or POST query string if query_string: query_params = OAuthRequest._split_url_string(query_string) parameters.update(query_params) # URL parameters param_str = urlparse.urlparse(http_url)[4] # query url_params = OAuthRequest._split_url_string(param_str) parameters.update(url_params) if parameters: return OAuthRequest(http_method, http_url, parameters) return None from_request = staticmethod(from_request) def from_consumer_and_token(oauth_consumer, token=None, http_method=HTTP_METHOD, http_url=None, parameters=None): if not parameters: parameters = {} defaults = { 'oauth_consumer_key': oauth_consumer.key, 'oauth_timestamp': generate_timestamp(), 'oauth_nonce': generate_nonce(), 'oauth_version': OAuthRequest.version, } defaults.update(parameters) parameters = defaults if token: parameters['oauth_token'] = token.key return OAuthRequest(http_method, http_url, parameters) from_consumer_and_token = staticmethod(from_consumer_and_token) def from_token_and_callback(token, callback=None, http_method=HTTP_METHOD, http_url=None, parameters=None): if not parameters: parameters = {} parameters['oauth_token'] = token.key if callback: parameters['oauth_callback'] = callback return OAuthRequest(http_method, http_url, parameters) from_token_and_callback = staticmethod(from_token_and_callback) # util function: turn Authorization: header into parameters, has to do some unescaping def _split_header(header): params = {} parts = header[6:].split(',') for param in parts: # ignore realm parameter if param.find('realm') > -1: continue # remove whitespace param = param.strip() # split key-value param_parts = param.split('=', 1) # remove quotes and unescape the value params[param_parts[0]] = urllib.unquote(param_parts[1].strip('\"')) return params _split_header = staticmethod(_split_header) # util function: turn url string into parameters, has to do some unescaping # even empty values should be included def _split_url_string(param_str): parameters = cgi.parse_qs(param_str, keep_blank_values=True) for k, v in parameters.iteritems(): parameters[k] = urllib.unquote(v[0]) return parameters _split_url_string = staticmethod(_split_url_string) # OAuthServer is a worker to check a requests validity against a data store class OAuthServer(object): timestamp_threshold = 300 # in seconds, five minutes version = VERSION signature_methods = None data_store = None def __init__(self, data_store=None, signature_methods=None): self.data_store = data_store self.signature_methods = signature_methods or {} def set_data_store(self, oauth_data_store): self.data_store = oauth_data_store def get_data_store(self): return self.data_store def add_signature_method(self, signature_method): self.signature_methods[signature_method.get_name()] = signature_method return self.signature_methods # process a request_token request # returns the request token on success def fetch_request_token(self, oauth_request): try: # get the request token for authorization token = self._get_token(oauth_request, 'request') except OAuthError: # no token required for the initial token request version = self._get_version(oauth_request) consumer = self._get_consumer(oauth_request) self._check_signature(oauth_request, consumer, None) # fetch a new token token = self.data_store.fetch_request_token(consumer) return token # process an access_token request # returns the access token on success def fetch_access_token(self, oauth_request): version = self._get_version(oauth_request) consumer = self._get_consumer(oauth_request) # get the request token token = self._get_token(oauth_request, 'request') self._check_signature(oauth_request, consumer, token) new_token = self.data_store.fetch_access_token(consumer, token) return new_token # verify an api call, checks all the parameters def verify_request(self, oauth_request): # -> consumer and token version = self._get_version(oauth_request) consumer = self._get_consumer(oauth_request) # get the access token token = self._get_token(oauth_request, 'access') self._check_signature(oauth_request, consumer, token) parameters = oauth_request.get_nonoauth_parameters() return consumer, token, parameters # authorize a request token def authorize_token(self, token, user): return self.data_store.authorize_request_token(token, user) # get the callback url def get_callback(self, oauth_request): return oauth_request.get_parameter('oauth_callback') # optional support for the authenticate header def build_authenticate_header(self, realm=''): return {'WWW-Authenticate': 'OAuth realm="%s"' % realm} # verify the correct version request for this server def _get_version(self, oauth_request): try: version = oauth_request.get_parameter('oauth_version') except: version = VERSION if version and version != self.version: raise OAuthError('OAuth version %s not supported.' % str(version)) return version # figure out the signature with some defaults def _get_signature_method(self, oauth_request): try: signature_method = oauth_request.get_parameter('oauth_signature_method') except: signature_method = SIGNATURE_METHOD try: # get the signature method object signature_method = self.signature_methods[signature_method] except: signature_method_names = ', '.join(self.signature_methods.keys()) raise OAuthError('Signature method %s not supported try one of the following: %s' % (signature_method, signature_method_names)) return signature_method def _get_consumer(self, oauth_request): consumer_key = oauth_request.get_parameter('oauth_consumer_key') if not consumer_key: raise OAuthError('Invalid consumer key.') consumer = self.data_store.lookup_consumer(consumer_key) if not consumer: raise OAuthError('Invalid consumer.') return consumer # try to find the token for the provided request token key def _get_token(self, oauth_request, token_type='access'): token_field = oauth_request.get_parameter('oauth_token') consumer = self._get_consumer(oauth_request) token = self.data_store.lookup_token(consumer, token_type, token_field) if not token: raise OAuthError('Invalid %s token: %s' % (token_type, token_field)) return token def _check_signature(self, oauth_request, consumer, token): timestamp, nonce = oauth_request._get_timestamp_nonce() self._check_timestamp(timestamp) self._check_nonce(consumer, token, nonce) signature_method = self._get_signature_method(oauth_request) try: signature = oauth_request.get_parameter('oauth_signature') except: raise OAuthError('Missing signature.') # validate the signature valid_sig = signature_method.check_signature(oauth_request, consumer, token, signature) if not valid_sig: key, base = signature_method.build_signature_base_string(oauth_request, consumer, token) raise OAuthError('Invalid signature. Expected signature base string: %s' % base) built = signature_method.build_signature(oauth_request, consumer, token) def _check_timestamp(self, timestamp): # verify that timestamp is recentish timestamp = int(timestamp) now = int(time.time()) lapsed = now - timestamp if lapsed > self.timestamp_threshold: raise OAuthError('Expired timestamp: given %d and now %s has a greater difference than threshold %d' % (timestamp, now, self.timestamp_threshold)) def _check_nonce(self, consumer, token, nonce): # verify that the nonce is uniqueish nonce = self.data_store.lookup_nonce(consumer, token, nonce) if nonce: raise OAuthError('Nonce already used: %s' % str(nonce)) # OAuthClient is a worker to attempt to execute a request class OAuthClient(object): consumer = None token = None def __init__(self, oauth_consumer, oauth_token): self.consumer = oauth_consumer self.token = oauth_token def get_consumer(self): return self.consumer def get_token(self): return self.token def fetch_request_token(self, oauth_request): # -> OAuthToken raise NotImplementedError def fetch_access_token(self, oauth_request): # -> OAuthToken raise NotImplementedError def access_resource(self, oauth_request): # -> some protected resource raise NotImplementedError # OAuthDataStore is a database abstraction used to lookup consumers and tokens class OAuthDataStore(object): def lookup_consumer(self, key): # -> OAuthConsumer raise NotImplementedError def lookup_token(self, oauth_consumer, token_type, token_token): # -> OAuthToken raise NotImplementedError def lookup_nonce(self, oauth_consumer, oauth_token, nonce, timestamp): # -> OAuthToken raise NotImplementedError def fetch_request_token(self, oauth_consumer): # -> OAuthToken raise NotImplementedError def fetch_access_token(self, oauth_consumer, oauth_token): # -> OAuthToken raise NotImplementedError def authorize_request_token(self, oauth_token, user): # -> OAuthToken raise NotImplementedError # OAuthSignatureMethod is a strategy class that implements a signature method class OAuthSignatureMethod(object): def get_name(self): # -> str raise NotImplementedError def build_signature_base_string(self, oauth_request, oauth_consumer, oauth_token): # -> str key, str raw raise NotImplementedError def build_signature(self, oauth_request, oauth_consumer, oauth_token): # -> str raise NotImplementedError def check_signature(self, oauth_request, consumer, token, signature): built = self.build_signature(oauth_request, consumer, token) return built == signature class OAuthSignatureMethod_HMAC_SHA1(OAuthSignatureMethod): def get_name(self): return 'HMAC-SHA1' def build_signature_base_string(self, oauth_request, consumer, token): sig = ( escape(oauth_request.get_normalized_http_method()), escape(oauth_request.get_normalized_http_url()), escape(oauth_request.get_normalized_parameters()), ) key = '%s&' % escape(consumer.secret) if token: key += escape(token.secret) raw = '&'.join(sig) return key, raw def build_signature(self, oauth_request, consumer, token): # build the base signature string key, raw = self.build_signature_base_string(oauth_request, consumer, token) # hmac object try: import hashlib # 2.5 hashed = hmac.new(key, raw, hashlib.sha1) except: import sha # deprecated hashed = hmac.new(key, raw, sha) # calculate the digest base 64 return binascii.b2a_base64(hashed.digest())[:-1] class OAuthSignatureMethod_PLAINTEXT(OAuthSignatureMethod): def get_name(self): return 'PLAINTEXT' def build_signature_base_string(self, oauth_request, consumer, token): # concatenate the consumer key and secret sig = escape(consumer.secret) + '&' if token: sig = sig + escape(token.secret) return sig def build_signature(self, oauth_request, consumer, token): return self.build_signature_base_string(oauth_request, consumer, token) gdata-2.0.18/src/gdata/sample_util.py0000640036466300116100000002473212156622363017505 0ustar afshareng00000000000000#!/usr/bin/env python # # Copyright (C) 2009 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. """Provides utility functions used with command line samples.""" # This module is used for version 2 of the Google Data APIs. import sys import getpass import urllib import gdata.gauth __author__ = 'j.s@google.com (Jeff Scudder)' CLIENT_LOGIN = 1 AUTHSUB = 2 OAUTH = 3 HMAC = 1 RSA = 2 class SettingsUtil(object): """Gather's user preferences from flags or command prompts. An instance of this object stores the choices made by the user. At some point it might be useful to save the user's preferences so that they do not need to always set flags or answer preference prompts. """ def __init__(self, prefs=None): self.prefs = prefs or {} def get_param(self, name, prompt='', secret=False, ask=True, reuse=False): # First, check in this objects stored preferences. if name in self.prefs: return self.prefs[name] # Second, check for a command line parameter. value = None for i in xrange(len(sys.argv)): if sys.argv[i].startswith('--%s=' % name): value = sys.argv[i].split('=')[1] elif sys.argv[i] == '--%s' % name: value = sys.argv[i + 1] # Third, if it was not on the command line, ask the user to input the # value. if value is None and ask: prompt = '%s: ' % prompt if secret: value = getpass.getpass(prompt) else: value = raw_input(prompt) # If we want to save the preference for reuse in future requests, add it # to this object's prefs. if value is not None and reuse: self.prefs[name] = value return value def authorize_client(self, client, auth_type=None, service=None, source=None, scopes=None, oauth_type=None, consumer_key=None, consumer_secret=None): """Uses command line arguments, or prompts user for token values.""" if 'client_auth_token' in self.prefs: return if auth_type is None: auth_type = int(self.get_param( 'auth_type', 'Please choose the authorization mechanism you want' ' to use.\n' '1. to use your email address and password (ClientLogin)\n' '2. to use a web browser to visit an auth web page (AuthSub)\n' '3. if you have registed to use OAuth\n', reuse=True)) # Get the scopes for the services we want to access. if auth_type == AUTHSUB or auth_type == OAUTH: if scopes is None: scopes = self.get_param( 'scopes', 'Enter the URL prefixes (scopes) for the resources you ' 'would like to access.\nFor multiple scope URLs, place a comma ' 'between each URL.\n' 'Example: http://www.google.com/calendar/feeds/,' 'http://www.google.com/m8/feeds/\n', reuse=True).split(',') elif isinstance(scopes, (str, unicode)): scopes = scopes.split(',') if auth_type == CLIENT_LOGIN: email = self.get_param('email', 'Please enter your username', reuse=False) password = self.get_param('password', 'Password', True, reuse=False) if service is None: service = self.get_param( 'service', 'What is the name of the service you wish to access?' '\n(See list:' ' http://code.google.com/apis/gdata/faq.html#clientlogin)', reuse=True) if source is None: source = self.get_param('source', ask=False, reuse=True) client.client_login(email, password, source=source, service=service) elif auth_type == AUTHSUB: auth_sub_token = self.get_param('auth_sub_token', ask=False, reuse=True) session_token = self.get_param('session_token', ask=False, reuse=True) private_key = None auth_url = None single_use_token = None rsa_private_key = self.get_param( 'rsa_private_key', 'If you want to use secure mode AuthSub, please provide the\n' ' location of your RSA private key which corresponds to the\n' ' certificate you have uploaded for your domain. If you do not\n' ' have an RSA key, simply press enter', reuse=True) if rsa_private_key: try: private_key_file = open(rsa_private_key, 'rb') private_key = private_key_file.read() private_key_file.close() except IOError: print 'Unable to read private key from file' if private_key is not None: if client.auth_token is None: if session_token: client.auth_token = gdata.gauth.SecureAuthSubToken( session_token, private_key, scopes) self.prefs['client_auth_token'] = gdata.gauth.token_to_blob( client.auth_token) return elif auth_sub_token: client.auth_token = gdata.gauth.SecureAuthSubToken( auth_sub_token, private_key, scopes) client.upgrade_token() self.prefs['client_auth_token'] = gdata.gauth.token_to_blob( client.auth_token) return auth_url = gdata.gauth.generate_auth_sub_url( 'http://gauthmachine.appspot.com/authsub', scopes, True) print 'with a private key, get ready for this URL', auth_url else: if client.auth_token is None: if session_token: client.auth_token = gdata.gauth.AuthSubToken(session_token, scopes) self.prefs['client_auth_token'] = gdata.gauth.token_to_blob( client.auth_token) return elif auth_sub_token: client.auth_token = gdata.gauth.AuthSubToken(auth_sub_token, scopes) client.upgrade_token() self.prefs['client_auth_token'] = gdata.gauth.token_to_blob( client.auth_token) return auth_url = gdata.gauth.generate_auth_sub_url( 'http://gauthmachine.appspot.com/authsub', scopes) print 'Visit the following URL in your browser to authorize this app:' print str(auth_url) print 'After agreeing to authorize the app, copy the token value from' print ' the URL. Example: "www.google.com/?token=ab12" token value is' print ' ab12' token_value = raw_input('Please enter the token value: ') if private_key is not None: single_use_token = gdata.gauth.SecureAuthSubToken( token_value, private_key, scopes) else: single_use_token = gdata.gauth.AuthSubToken(token_value, scopes) client.auth_token = single_use_token client.upgrade_token() elif auth_type == OAUTH: if oauth_type is None: oauth_type = int(self.get_param( 'oauth_type', 'Please choose the authorization mechanism you want' ' to use.\n' '1. use an HMAC signature using your consumer key and secret\n' '2. use RSA with your private key to sign requests\n', reuse=True)) consumer_key = self.get_param( 'consumer_key', 'Please enter your OAuth conumer key ' 'which identifies your app', reuse=True) if oauth_type == HMAC: consumer_secret = self.get_param( 'consumer_secret', 'Please enter your OAuth conumer secret ' 'which you share with the OAuth provider', True, reuse=False) # Swap out this code once the client supports requesting an oauth # token. # Get a request token. request_token = client.get_oauth_token( scopes, 'http://gauthmachine.appspot.com/oauth', consumer_key, consumer_secret=consumer_secret) elif oauth_type == RSA: rsa_private_key = self.get_param( 'rsa_private_key', 'Please provide the location of your RSA private key which\n' ' corresponds to the certificate you have uploaded for your' ' domain.', reuse=True) try: private_key_file = open(rsa_private_key, 'rb') private_key = private_key_file.read() private_key_file.close() except IOError: print 'Unable to read private key from file' request_token = client.get_oauth_token( scopes, 'http://gauthmachine.appspot.com/oauth', consumer_key, rsa_private_key=private_key) else: print 'Invalid OAuth signature type' return None # Authorize the request token in the browser. print 'Visit the following URL in your browser to authorize this app:' print str(request_token.generate_authorization_url()) print 'After agreeing to authorize the app, copy URL from the browser\'s' print ' address bar.' url = raw_input('Please enter the url: ') gdata.gauth.authorize_request_token(request_token, url) # Exchange for an access token. client.auth_token = client.get_access_token(request_token) else: print 'Invalid authorization type.' return None if client.auth_token: self.prefs['client_auth_token'] = gdata.gauth.token_to_blob( client.auth_token) def get_param(name, prompt='', secret=False, ask=True): settings = SettingsUtil() return settings.get_param(name=name, prompt=prompt, secret=secret, ask=ask) def authorize_client(client, auth_type=None, service=None, source=None, scopes=None, oauth_type=None, consumer_key=None, consumer_secret=None): """Uses command line arguments, or prompts user for token values.""" settings = SettingsUtil() return settings.authorize_client(client=client, auth_type=auth_type, service=service, source=source, scopes=scopes, oauth_type=oauth_type, consumer_key=consumer_key, consumer_secret=consumer_secret) def print_options(): """Displays usage information, available command line params.""" # TODO: fill in the usage description for authorizing the client. print '' gdata-2.0.18/src/gdata/contacts/0000750036466300116100000000000012156625015016417 5ustar afshareng00000000000000gdata-2.0.18/src/gdata/contacts/data.py0000640036466300116100000002677712156622363017731 0ustar afshareng00000000000000#!/usr/bin/env python # # Copyright (C) 2009 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. """Data model classes for parsing and generating XML for the Contacts API.""" __author__ = 'vinces1979@gmail.com (Vince Spicer)' import atom.core import gdata import gdata.data PHOTO_LINK_REL = 'http://schemas.google.com/contacts/2008/rel#photo' PHOTO_EDIT_LINK_REL = 'http://schemas.google.com/contacts/2008/rel#edit-photo' EXTERNAL_ID_ORGANIZATION = 'organization' RELATION_MANAGER = 'manager' CONTACTS_NAMESPACE = 'http://schemas.google.com/contact/2008' CONTACTS_TEMPLATE = '{%s}%%s' % CONTACTS_NAMESPACE class BillingInformation(atom.core.XmlElement): """ gContact:billingInformation Specifies billing information of the entity represented by the contact. The element cannot be repeated. """ _qname = CONTACTS_TEMPLATE % 'billingInformation' class Birthday(atom.core.XmlElement): """ Stores birthday date of the person represented by the contact. The element cannot be repeated. """ _qname = CONTACTS_TEMPLATE % 'birthday' when = 'when' class ContactLink(atom.data.Link): """ Extends atom.data.Link to add gd:etag attribute for photo link. """ etag = gdata.data.GD_TEMPLATE % 'etag' class CalendarLink(atom.core.XmlElement): """ Storage for URL of the contact's calendar. The element can be repeated. """ _qname = CONTACTS_TEMPLATE % 'calendarLink' rel = 'rel' label = 'label' primary = 'primary' href = 'href' class DirectoryServer(atom.core.XmlElement): """ A directory server associated with this contact. May not be repeated. """ _qname = CONTACTS_TEMPLATE % 'directoryServer' class Event(atom.core.XmlElement): """ These elements describe events associated with a contact. They may be repeated """ _qname = CONTACTS_TEMPLATE % 'event' label = 'label' rel = 'rel' when = gdata.data.When class ExternalId(atom.core.XmlElement): """ Describes an ID of the contact in an external system of some kind. This element may be repeated. """ _qname = CONTACTS_TEMPLATE % 'externalId' label = 'label' rel = 'rel' value = 'value' def ExternalIdFromString(xml_string): return atom.core.parse(ExternalId, xml_string) class Gender(atom.core.XmlElement): """ Specifies the gender of the person represented by the contact. The element cannot be repeated. """ _qname = CONTACTS_TEMPLATE % 'gender' value = 'value' class Hobby(atom.core.XmlElement): """ Describes an ID of the contact in an external system of some kind. This element may be repeated. """ _qname = CONTACTS_TEMPLATE % 'hobby' class Initials(atom.core.XmlElement): """ Specifies the initials of the person represented by the contact. The element cannot be repeated. """ _qname = CONTACTS_TEMPLATE % 'initials' class Jot(atom.core.XmlElement): """ Storage for arbitrary pieces of information about the contact. Each jot has a type specified by the rel attribute and a text value. The element can be repeated. """ _qname = CONTACTS_TEMPLATE % 'jot' rel = 'rel' class Language(atom.core.XmlElement): """ Specifies the preferred languages of the contact. The element can be repeated. The language must be specified using one of two mutually exclusive methods: using the freeform @label attribute, or using the @code attribute, whose value must conform to the IETF BCP 47 specification. """ _qname = CONTACTS_TEMPLATE % 'language' code = 'code' label = 'label' class MaidenName(atom.core.XmlElement): """ Specifies maiden name of the person represented by the contact. The element cannot be repeated. """ _qname = CONTACTS_TEMPLATE % 'maidenName' class Mileage(atom.core.XmlElement): """ Specifies the mileage for the entity represented by the contact. Can be used for example to document distance needed for reimbursement purposes. The value is not interpreted. The element cannot be repeated. """ _qname = CONTACTS_TEMPLATE % 'mileage' class NickName(atom.core.XmlElement): """ Specifies the nickname of the person represented by the contact. The element cannot be repeated. """ _qname = CONTACTS_TEMPLATE % 'nickname' class Occupation(atom.core.XmlElement): """ Specifies the occupation/profession of the person specified by the contact. The element cannot be repeated. """ _qname = CONTACTS_TEMPLATE % 'occupation' class Priority(atom.core.XmlElement): """ Classifies importance of the contact into 3 categories: * Low * Normal * High The priority element cannot be repeated. """ _qname = CONTACTS_TEMPLATE % 'priority' class Relation(atom.core.XmlElement): """ This element describe another entity (usually a person) that is in a relation of some kind with the contact. """ _qname = CONTACTS_TEMPLATE % 'relation' rel = 'rel' label = 'label' class Sensitivity(atom.core.XmlElement): """ Classifies sensitivity of the contact into the following categories: * Confidential * Normal * Personal * Private The sensitivity element cannot be repeated. """ _qname = CONTACTS_TEMPLATE % 'sensitivity' rel = 'rel' class UserDefinedField(atom.core.XmlElement): """ Represents an arbitrary key-value pair attached to the contact. """ _qname = CONTACTS_TEMPLATE % 'userDefinedField' key = 'key' value = 'value' def UserDefinedFieldFromString(xml_string): return atom.core.parse(UserDefinedField, xml_string) class Website(atom.core.XmlElement): """ Describes websites associated with the contact, including links. May be repeated. """ _qname = CONTACTS_TEMPLATE % 'website' href = 'href' label = 'label' primary = 'primary' rel = 'rel' def WebsiteFromString(xml_string): return atom.core.parse(Website, xml_string) class HouseName(atom.core.XmlElement): """ Used in places where houses or buildings have names (and not necessarily numbers), eg. "The Pillars". """ _qname = CONTACTS_TEMPLATE % 'housename' class Street(atom.core.XmlElement): """ Can be street, avenue, road, etc. This element also includes the house number and room/apartment/flat/floor number. """ _qname = CONTACTS_TEMPLATE % 'street' class POBox(atom.core.XmlElement): """ Covers actual P.O. boxes, drawers, locked bags, etc. This is usually but not always mutually exclusive with street """ _qname = CONTACTS_TEMPLATE % 'pobox' class Neighborhood(atom.core.XmlElement): """ This is used to disambiguate a street address when a city contains more than one street with the same name, or to specify a small place whose mail is routed through a larger postal town. In China it could be a county or a minor city. """ _qname = CONTACTS_TEMPLATE % 'neighborhood' class City(atom.core.XmlElement): """ Can be city, village, town, borough, etc. This is the postal town and not necessarily the place of residence or place of business. """ _qname = CONTACTS_TEMPLATE % 'city' class SubRegion(atom.core.XmlElement): """ Handles administrative districts such as U.S. or U.K. counties that are not used for mail addressing purposes. Subregion is not intended for delivery addresses. """ _qname = CONTACTS_TEMPLATE % 'subregion' class Region(atom.core.XmlElement): """ A state, province, county (in Ireland), Land (in Germany), departement (in France), etc. """ _qname = CONTACTS_TEMPLATE % 'region' class PostalCode(atom.core.XmlElement): """ Postal code. Usually country-wide, but sometimes specific to the city (e.g. "2" in "Dublin 2, Ireland" addresses). """ _qname = CONTACTS_TEMPLATE % 'postcode' class Country(atom.core.XmlElement): """ The name or code of the country. """ _qname = CONTACTS_TEMPLATE % 'country' class Status(atom.core.XmlElement): """Person's status element.""" _qname = CONTACTS_TEMPLATE % 'status' indexed = 'indexed' class PersonEntry(gdata.data.BatchEntry): """Represents a google contact""" link = [ContactLink] billing_information = BillingInformation birthday = Birthday calendar_link = [CalendarLink] directory_server = DirectoryServer event = [Event] external_id = [ExternalId] gender = Gender hobby = [Hobby] initials = Initials jot = [Jot] language= [Language] maiden_name = MaidenName mileage = Mileage nickname = NickName occupation = Occupation priority = Priority relation = [Relation] sensitivity = Sensitivity user_defined_field = [UserDefinedField] website = [Website] name = gdata.data.Name phone_number = [gdata.data.PhoneNumber] organization = gdata.data.Organization postal_address = [gdata.data.PostalAddress] email = [gdata.data.Email] im = [gdata.data.Im] structured_postal_address = [gdata.data.StructuredPostalAddress] extended_property = [gdata.data.ExtendedProperty] status = Status class Deleted(atom.core.XmlElement): """If present, indicates that this contact has been deleted.""" _qname = gdata.GDATA_TEMPLATE % 'deleted' class GroupMembershipInfo(atom.core.XmlElement): """ Identifies the group to which the contact belongs or belonged. The group is referenced by its id. """ _qname = CONTACTS_TEMPLATE % 'groupMembershipInfo' href = 'href' deleted = 'deleted' class ContactEntry(PersonEntry): """A Google Contacts flavor of an Atom Entry.""" deleted = Deleted group_membership_info = [GroupMembershipInfo] organization = gdata.data.Organization def GetPhotoLink(self): for a_link in self.link: if a_link.rel == PHOTO_LINK_REL: return a_link return None def GetPhotoEditLink(self): for a_link in self.link: if a_link.rel == PHOTO_EDIT_LINK_REL: return a_link return None class ContactsFeed(gdata.data.BatchFeed): """A collection of Contacts.""" entry = [ContactEntry] class SystemGroup(atom.core.XmlElement): """The contacts systemGroup element. When used within a contact group entry, indicates that the group in question is one of the predefined system groups.""" _qname = CONTACTS_TEMPLATE % 'systemGroup' id = 'id' class GroupEntry(gdata.data.BatchEntry): """Represents a contact group.""" extended_property = [gdata.data.ExtendedProperty] system_group = SystemGroup class GroupsFeed(gdata.data.BatchFeed): """A Google contact groups feed flavor of an Atom Feed.""" entry = [GroupEntry] class ProfileEntry(PersonEntry): """A Google Profiles flavor of an Atom Entry.""" def ProfileEntryFromString(xml_string): """Converts an XML string into a ProfileEntry object. Args: xml_string: string The XML describing a Profile entry. Returns: A ProfileEntry object corresponding to the given XML. """ return atom.core.parse(ProfileEntry, xml_string) class ProfilesFeed(gdata.data.BatchFeed): """A Google Profiles feed flavor of an Atom Feed.""" _qname = atom.data.ATOM_TEMPLATE % 'feed' entry = [ProfileEntry] def ProfilesFeedFromString(xml_string): """Converts an XML string into a ProfilesFeed object. Args: xml_string: string The XML describing a Profiles feed. Returns: A ProfilesFeed object corresponding to the given XML. """ return atom.core.parse(ProfilesFeed, xml_string) gdata-2.0.18/src/gdata/contacts/__init__.py0000640036466300116100000006706012156622363020545 0ustar afshareng00000000000000#!/usr/bin/env python # # Copyright 2009 Google Inc. All Rights Reserved. # # 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. """Contains extensions to ElementWrapper objects used with Google Contacts.""" __author__ = 'dbrattli (Dag Brattli)' import atom import gdata ## Constants from http://code.google.com/apis/gdata/elements.html ## REL_HOME = 'http://schemas.google.com/g/2005#home' REL_WORK = 'http://schemas.google.com/g/2005#work' REL_OTHER = 'http://schemas.google.com/g/2005#other' # AOL Instant Messenger protocol IM_AIM = 'http://schemas.google.com/g/2005#AIM' IM_MSN = 'http://schemas.google.com/g/2005#MSN' # MSN Messenger protocol IM_YAHOO = 'http://schemas.google.com/g/2005#YAHOO' # Yahoo Messenger protocol IM_SKYPE = 'http://schemas.google.com/g/2005#SKYPE' # Skype protocol IM_QQ = 'http://schemas.google.com/g/2005#QQ' # QQ protocol # Google Talk protocol IM_GOOGLE_TALK = 'http://schemas.google.com/g/2005#GOOGLE_TALK' IM_ICQ = 'http://schemas.google.com/g/2005#ICQ' # ICQ protocol IM_JABBER = 'http://schemas.google.com/g/2005#JABBER' # Jabber protocol IM_NETMEETING = 'http://schemas.google.com/g/2005#netmeeting' # NetMeeting PHOTO_LINK_REL = 'http://schemas.google.com/contacts/2008/rel#photo' PHOTO_EDIT_LINK_REL = 'http://schemas.google.com/contacts/2008/rel#edit-photo' # Different phone types, for more info see: # http://code.google.com/apis/gdata/docs/2.0/elements.html#gdPhoneNumber PHONE_CAR = 'http://schemas.google.com/g/2005#car' PHONE_FAX = 'http://schemas.google.com/g/2005#fax' PHONE_GENERAL = 'http://schemas.google.com/g/2005#general' PHONE_HOME = REL_HOME PHONE_HOME_FAX = 'http://schemas.google.com/g/2005#home_fax' PHONE_INTERNAL = 'http://schemas.google.com/g/2005#internal-extension' PHONE_MOBILE = 'http://schemas.google.com/g/2005#mobile' PHONE_OTHER = REL_OTHER PHONE_PAGER = 'http://schemas.google.com/g/2005#pager' PHONE_SATELLITE = 'http://schemas.google.com/g/2005#satellite' PHONE_VOIP = 'http://schemas.google.com/g/2005#voip' PHONE_WORK = REL_WORK PHONE_WORK_FAX = 'http://schemas.google.com/g/2005#work_fax' PHONE_WORK_MOBILE = 'http://schemas.google.com/g/2005#work_mobile' PHONE_WORK_PAGER = 'http://schemas.google.com/g/2005#work_pager' PHONE_MAIN = 'http://schemas.google.com/g/2005#main' PHONE_ASSISTANT = 'http://schemas.google.com/g/2005#assistant' PHONE_CALLBACK = 'http://schemas.google.com/g/2005#callback' PHONE_COMPANY_MAIN = 'http://schemas.google.com/g/2005#company_main' PHONE_ISDN = 'http://schemas.google.com/g/2005#isdn' PHONE_OTHER_FAX = 'http://schemas.google.com/g/2005#other_fax' PHONE_RADIO = 'http://schemas.google.com/g/2005#radio' PHONE_TELEX = 'http://schemas.google.com/g/2005#telex' PHONE_TTY_TDD = 'http://schemas.google.com/g/2005#tty_tdd' EXTERNAL_ID_ORGANIZATION = 'organization' RELATION_MANAGER = 'manager' CONTACTS_NAMESPACE = 'http://schemas.google.com/contact/2008' class GDataBase(atom.AtomBase): """The Google Contacts intermediate class from atom.AtomBase.""" _namespace = gdata.GDATA_NAMESPACE _children = atom.AtomBase._children.copy() _attributes = atom.AtomBase._attributes.copy() def __init__(self, text=None, extension_elements=None, extension_attributes=None): self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} class ContactsBase(GDataBase): """The Google Contacts intermediate class for Contacts namespace.""" _namespace = CONTACTS_NAMESPACE class OrgName(GDataBase): """The Google Contacts OrgName element.""" _tag = 'orgName' class OrgTitle(GDataBase): """The Google Contacts OrgTitle element.""" _tag = 'orgTitle' class OrgDepartment(GDataBase): """The Google Contacts OrgDepartment element.""" _tag = 'orgDepartment' class OrgJobDescription(GDataBase): """The Google Contacts OrgJobDescription element.""" _tag = 'orgJobDescription' class Where(GDataBase): """The Google Contacts Where element.""" _tag = 'where' _children = GDataBase._children.copy() _attributes = GDataBase._attributes.copy() _attributes['rel'] = 'rel' _attributes['label'] = 'label' _attributes['valueString'] = 'value_string' def __init__(self, value_string=None, rel=None, label=None, text=None, extension_elements=None, extension_attributes=None): GDataBase.__init__(self, text=text, extension_elements=extension_elements, extension_attributes=extension_attributes) self.rel = rel self.label = label self.value_string = value_string class When(GDataBase): """The Google Contacts When element.""" _tag = 'when' _children = GDataBase._children.copy() _attributes = GDataBase._attributes.copy() _attributes['startTime'] = 'start_time' _attributes['endTime'] = 'end_time' _attributes['label'] = 'label' def __init__(self, start_time=None, end_time=None, label=None, text=None, extension_elements=None, extension_attributes=None): GDataBase.__init__(self, text=text, extension_elements=extension_elements, extension_attributes=extension_attributes) self.start_time = start_time self.end_time = end_time self.label = label class Organization(GDataBase): """The Google Contacts Organization element.""" _tag = 'organization' _children = GDataBase._children.copy() _attributes = GDataBase._attributes.copy() _attributes['label'] = 'label' _attributes['rel'] = 'rel' _attributes['primary'] = 'primary' _children['{%s}orgName' % GDataBase._namespace] = ( 'org_name', OrgName) _children['{%s}orgTitle' % GDataBase._namespace] = ( 'org_title', OrgTitle) _children['{%s}orgDepartment' % GDataBase._namespace] = ( 'org_department', OrgDepartment) _children['{%s}orgJobDescription' % GDataBase._namespace] = ( 'org_job_description', OrgJobDescription) #_children['{%s}where' % GDataBase._namespace] = ('where', Where) def __init__(self, label=None, rel=None, primary='false', org_name=None, org_title=None, org_department=None, org_job_description=None, where=None, text=None, extension_elements=None, extension_attributes=None): GDataBase.__init__(self, text=text, extension_elements=extension_elements, extension_attributes=extension_attributes) self.label = label self.rel = rel or REL_OTHER self.primary = primary self.org_name = org_name self.org_title = org_title self.org_department = org_department self.org_job_description = org_job_description self.where = where class PostalAddress(GDataBase): """The Google Contacts PostalAddress element.""" _tag = 'postalAddress' _children = GDataBase._children.copy() _attributes = GDataBase._attributes.copy() _attributes['rel'] = 'rel' _attributes['primary'] = 'primary' def __init__(self, primary=None, rel=None, text=None, extension_elements=None, extension_attributes=None): GDataBase.__init__(self, text=text, extension_elements=extension_elements, extension_attributes=extension_attributes) self.rel = rel or REL_OTHER self.primary = primary class FormattedAddress(GDataBase): """The Google Contacts FormattedAddress element.""" _tag = 'formattedAddress' class StructuredPostalAddress(GDataBase): """The Google Contacts StructuredPostalAddress element.""" _tag = 'structuredPostalAddress' _children = GDataBase._children.copy() _attributes = GDataBase._attributes.copy() _attributes['rel'] = 'rel' _attributes['primary'] = 'primary' _children['{%s}formattedAddress' % GDataBase._namespace] = ( 'formatted_address', FormattedAddress) def __init__(self, rel=None, primary=None, formatted_address=None, text=None, extension_elements=None, extension_attributes=None): GDataBase.__init__(self, text=text, extension_elements=extension_elements, extension_attributes=extension_attributes) self.rel = rel or REL_OTHER self.primary = primary self.formatted_address = formatted_address class IM(GDataBase): """The Google Contacts IM element.""" _tag = 'im' _children = GDataBase._children.copy() _attributes = GDataBase._attributes.copy() _attributes['address'] = 'address' _attributes['primary'] = 'primary' _attributes['protocol'] = 'protocol' _attributes['label'] = 'label' _attributes['rel'] = 'rel' def __init__(self, primary='false', rel=None, address=None, protocol=None, label=None, text=None, extension_elements=None, extension_attributes=None): GDataBase.__init__(self, text=text, extension_elements=extension_elements, extension_attributes=extension_attributes) self.protocol = protocol self.address = address self.primary = primary self.rel = rel or REL_OTHER self.label = label class Email(GDataBase): """The Google Contacts Email element.""" _tag = 'email' _children = GDataBase._children.copy() _attributes = GDataBase._attributes.copy() _attributes['address'] = 'address' _attributes['primary'] = 'primary' _attributes['rel'] = 'rel' _attributes['label'] = 'label' def __init__(self, label=None, rel=None, address=None, primary='false', text=None, extension_elements=None, extension_attributes=None): GDataBase.__init__(self, text=text, extension_elements=extension_elements, extension_attributes=extension_attributes) self.label = label self.rel = rel or REL_OTHER self.address = address self.primary = primary class PhoneNumber(GDataBase): """The Google Contacts PhoneNumber element.""" _tag = 'phoneNumber' _children = GDataBase._children.copy() _attributes = GDataBase._attributes.copy() _attributes['label'] = 'label' _attributes['rel'] = 'rel' _attributes['uri'] = 'uri' _attributes['primary'] = 'primary' def __init__(self, label=None, rel=None, uri=None, primary='false', text=None, extension_elements=None, extension_attributes=None): GDataBase.__init__(self, text=text, extension_elements=extension_elements, extension_attributes=extension_attributes) self.label = label self.rel = rel or REL_OTHER self.uri = uri self.primary = primary class Nickname(ContactsBase): """The Google Contacts Nickname element.""" _tag = 'nickname' class Occupation(ContactsBase): """The Google Contacts Occupation element.""" _tag = 'occupation' class Gender(ContactsBase): """The Google Contacts Gender element.""" _tag = 'gender' _children = ContactsBase._children.copy() _attributes = ContactsBase._attributes.copy() _attributes['value'] = 'value' def __init__(self, value=None, text=None, extension_elements=None, extension_attributes=None): ContactsBase.__init__(self, text=text, extension_elements=extension_elements, extension_attributes=extension_attributes) self.value = value class Birthday(ContactsBase): """The Google Contacts Birthday element.""" _tag = 'birthday' _children = ContactsBase._children.copy() _attributes = ContactsBase._attributes.copy() _attributes['when'] = 'when' def __init__(self, when=None, text=None, extension_elements=None, extension_attributes=None): ContactsBase.__init__(self, text=text, extension_elements=extension_elements, extension_attributes=extension_attributes) self.when = when class Relation(ContactsBase): """The Google Contacts Relation element.""" _tag = 'relation' _children = ContactsBase._children.copy() _attributes = ContactsBase._attributes.copy() _attributes['label'] = 'label' _attributes['rel'] = 'rel' def __init__(self, label=None, rel=None, text=None, extension_elements=None, extension_attributes=None): ContactsBase.__init__(self, text=text, extension_elements=extension_elements, extension_attributes=extension_attributes) self.label = label self.rel = rel def RelationFromString(xml_string): return atom.CreateClassFromXMLString(Relation, xml_string) class UserDefinedField(ContactsBase): """The Google Contacts UserDefinedField element.""" _tag = 'userDefinedField' _children = ContactsBase._children.copy() _attributes = ContactsBase._attributes.copy() _attributes['key'] = 'key' _attributes['value'] = 'value' def __init__(self, key=None, value=None, text=None, extension_elements=None, extension_attributes=None): ContactsBase.__init__(self, text=text, extension_elements=extension_elements, extension_attributes=extension_attributes) self.key = key self.value = value def UserDefinedFieldFromString(xml_string): return atom.CreateClassFromXMLString(UserDefinedField, xml_string) class Website(ContactsBase): """The Google Contacts Website element.""" _tag = 'website' _children = ContactsBase._children.copy() _attributes = ContactsBase._attributes.copy() _attributes['href'] = 'href' _attributes['label'] = 'label' _attributes['primary'] = 'primary' _attributes['rel'] = 'rel' def __init__(self, href=None, label=None, primary='false', rel=None, text=None, extension_elements=None, extension_attributes=None): ContactsBase.__init__(self, text=text, extension_elements=extension_elements, extension_attributes=extension_attributes) self.href = href self.label = label self.primary = primary self.rel = rel def WebsiteFromString(xml_string): return atom.CreateClassFromXMLString(Website, xml_string) class ExternalId(ContactsBase): """The Google Contacts ExternalId element.""" _tag = 'externalId' _children = ContactsBase._children.copy() _attributes = ContactsBase._attributes.copy() _attributes['label'] = 'label' _attributes['rel'] = 'rel' _attributes['value'] = 'value' def __init__(self, label=None, rel=None, value=None, text=None, extension_elements=None, extension_attributes=None): ContactsBase.__init__(self, text=text, extension_elements=extension_elements, extension_attributes=extension_attributes) self.label = label self.rel = rel self.value = value def ExternalIdFromString(xml_string): return atom.CreateClassFromXMLString(ExternalId, xml_string) class Event(ContactsBase): """The Google Contacts Event element.""" _tag = 'event' _children = ContactsBase._children.copy() _attributes = ContactsBase._attributes.copy() _attributes['label'] = 'label' _attributes['rel'] = 'rel' _children['{%s}when' % ContactsBase._namespace] = ('when', When) def __init__(self, label=None, rel=None, when=None, text=None, extension_elements=None, extension_attributes=None): ContactsBase.__init__(self, text=text, extension_elements=extension_elements, extension_attributes=extension_attributes) self.label = label self.rel = rel self.when = when def EventFromString(xml_string): return atom.CreateClassFromXMLString(Event, xml_string) class Deleted(GDataBase): """The Google Contacts Deleted element.""" _tag = 'deleted' class GroupMembershipInfo(ContactsBase): """The Google Contacts GroupMembershipInfo element.""" _tag = 'groupMembershipInfo' _children = ContactsBase._children.copy() _attributes = ContactsBase._attributes.copy() _attributes['deleted'] = 'deleted' _attributes['href'] = 'href' def __init__(self, deleted=None, href=None, text=None, extension_elements=None, extension_attributes=None): ContactsBase.__init__(self, text=text, extension_elements=extension_elements, extension_attributes=extension_attributes) self.deleted = deleted self.href = href class PersonEntry(gdata.BatchEntry): """Base class for ContactEntry and ProfileEntry.""" _children = gdata.BatchEntry._children.copy() _children['{%s}organization' % gdata.GDATA_NAMESPACE] = ( 'organization', [Organization]) _children['{%s}phoneNumber' % gdata.GDATA_NAMESPACE] = ( 'phone_number', [PhoneNumber]) _children['{%s}nickname' % CONTACTS_NAMESPACE] = ('nickname', Nickname) _children['{%s}occupation' % CONTACTS_NAMESPACE] = ('occupation', Occupation) _children['{%s}gender' % CONTACTS_NAMESPACE] = ('gender', Gender) _children['{%s}birthday' % CONTACTS_NAMESPACE] = ('birthday', Birthday) _children['{%s}postalAddress' % gdata.GDATA_NAMESPACE] = ('postal_address', [PostalAddress]) _children['{%s}structuredPostalAddress' % gdata.GDATA_NAMESPACE] = ( 'structured_postal_address', [StructuredPostalAddress]) _children['{%s}email' % gdata.GDATA_NAMESPACE] = ('email', [Email]) _children['{%s}im' % gdata.GDATA_NAMESPACE] = ('im', [IM]) _children['{%s}relation' % CONTACTS_NAMESPACE] = ('relation', [Relation]) _children['{%s}userDefinedField' % CONTACTS_NAMESPACE] = ( 'user_defined_field', [UserDefinedField]) _children['{%s}website' % CONTACTS_NAMESPACE] = ('website', [Website]) _children['{%s}externalId' % CONTACTS_NAMESPACE] = ( 'external_id', [ExternalId]) _children['{%s}event' % CONTACTS_NAMESPACE] = ('event', [Event]) # The following line should be removed once the Python support # for GData 2.0 is mature. _attributes = gdata.BatchEntry._attributes.copy() _attributes['{%s}etag' % gdata.GDATA_NAMESPACE] = 'etag' def __init__(self, author=None, category=None, content=None, atom_id=None, link=None, published=None, title=None, updated=None, organization=None, phone_number=None, nickname=None, occupation=None, gender=None, birthday=None, postal_address=None, structured_postal_address=None, email=None, im=None, relation=None, user_defined_field=None, website=None, external_id=None, event=None, batch_operation=None, batch_id=None, batch_status=None, text=None, extension_elements=None, extension_attributes=None, etag=None): gdata.BatchEntry.__init__(self, author=author, category=category, content=content, atom_id=atom_id, link=link, published=published, batch_operation=batch_operation, batch_id=batch_id, batch_status=batch_status, title=title, updated=updated) self.organization = organization or [] self.phone_number = phone_number or [] self.nickname = nickname self.occupation = occupation self.gender = gender self.birthday = birthday self.postal_address = postal_address or [] self.structured_postal_address = structured_postal_address or [] self.email = email or [] self.im = im or [] self.relation = relation or [] self.user_defined_field = user_defined_field or [] self.website = website or [] self.external_id = external_id or [] self.event = event or [] self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} # The following line should be removed once the Python support # for GData 2.0 is mature. self.etag = etag class ContactEntry(PersonEntry): """A Google Contact flavor of an Atom Entry.""" _children = PersonEntry._children.copy() _children['{%s}deleted' % gdata.GDATA_NAMESPACE] = ('deleted', Deleted) _children['{%s}groupMembershipInfo' % CONTACTS_NAMESPACE] = ( 'group_membership_info', [GroupMembershipInfo]) _children['{%s}extendedProperty' % gdata.GDATA_NAMESPACE] = ( 'extended_property', [gdata.ExtendedProperty]) # Overwrite the organization rule in PersonEntry so that a ContactEntry # may only contain one element. _children['{%s}organization' % gdata.GDATA_NAMESPACE] = ( 'organization', Organization) def __init__(self, author=None, category=None, content=None, atom_id=None, link=None, published=None, title=None, updated=None, organization=None, phone_number=None, nickname=None, occupation=None, gender=None, birthday=None, postal_address=None, structured_postal_address=None, email=None, im=None, relation=None, user_defined_field=None, website=None, external_id=None, event=None, batch_operation=None, batch_id=None, batch_status=None, text=None, extension_elements=None, extension_attributes=None, etag=None, deleted=None, extended_property=None, group_membership_info=None): PersonEntry.__init__(self, author=author, category=category, content=content, atom_id=atom_id, link=link, published=published, title=title, updated=updated, organization=organization, phone_number=phone_number, nickname=nickname, occupation=occupation, gender=gender, birthday=birthday, postal_address=postal_address, structured_postal_address=structured_postal_address, email=email, im=im, relation=relation, user_defined_field=user_defined_field, website=website, external_id=external_id, event=event, batch_operation=batch_operation, batch_id=batch_id, batch_status=batch_status, text=text, extension_elements=extension_elements, extension_attributes=extension_attributes, etag=etag) self.deleted = deleted self.extended_property = extended_property or [] self.group_membership_info = group_membership_info or [] def GetPhotoLink(self): for a_link in self.link: if a_link.rel == PHOTO_LINK_REL: return a_link return None def GetPhotoEditLink(self): for a_link in self.link: if a_link.rel == PHOTO_EDIT_LINK_REL: return a_link return None def ContactEntryFromString(xml_string): return atom.CreateClassFromXMLString(ContactEntry, xml_string) class ContactsFeed(gdata.BatchFeed, gdata.LinkFinder): """A Google Contacts feed flavor of an Atom Feed.""" _children = gdata.BatchFeed._children.copy() _children['{%s}entry' % atom.ATOM_NAMESPACE] = ('entry', [ContactEntry]) def __init__(self, author=None, category=None, contributor=None, generator=None, icon=None, atom_id=None, link=None, logo=None, rights=None, subtitle=None, title=None, updated=None, entry=None, total_results=None, start_index=None, items_per_page=None, extension_elements=None, extension_attributes=None, text=None): gdata.BatchFeed.__init__(self, author=author, category=category, contributor=contributor, generator=generator, icon=icon, atom_id=atom_id, link=link, logo=logo, rights=rights, subtitle=subtitle, title=title, updated=updated, entry=entry, total_results=total_results, start_index=start_index, items_per_page=items_per_page, extension_elements=extension_elements, extension_attributes=extension_attributes, text=text) def ContactsFeedFromString(xml_string): return atom.CreateClassFromXMLString(ContactsFeed, xml_string) class GroupEntry(gdata.BatchEntry): """Represents a contact group.""" _children = gdata.BatchEntry._children.copy() _children['{%s}extendedProperty' % gdata.GDATA_NAMESPACE] = ( 'extended_property', [gdata.ExtendedProperty]) def __init__(self, author=None, category=None, content=None, contributor=None, atom_id=None, link=None, published=None, rights=None, source=None, summary=None, control=None, title=None, updated=None, extended_property=None, batch_operation=None, batch_id=None, batch_status=None, extension_elements=None, extension_attributes=None, text=None): gdata.BatchEntry.__init__(self, author=author, category=category, content=content, atom_id=atom_id, link=link, published=published, batch_operation=batch_operation, batch_id=batch_id, batch_status=batch_status, title=title, updated=updated) self.extended_property = extended_property or [] def GroupEntryFromString(xml_string): return atom.CreateClassFromXMLString(GroupEntry, xml_string) class GroupsFeed(gdata.BatchFeed): """A Google contact groups feed flavor of an Atom Feed.""" _children = gdata.BatchFeed._children.copy() _children['{%s}entry' % atom.ATOM_NAMESPACE] = ('entry', [GroupEntry]) def GroupsFeedFromString(xml_string): return atom.CreateClassFromXMLString(GroupsFeed, xml_string) class ProfileEntry(PersonEntry): """A Google Profiles flavor of an Atom Entry.""" def ProfileEntryFromString(xml_string): """Converts an XML string into a ProfileEntry object. Args: xml_string: string The XML describing a Profile entry. Returns: A ProfileEntry object corresponding to the given XML. """ return atom.CreateClassFromXMLString(ProfileEntry, xml_string) class ProfilesFeed(gdata.BatchFeed, gdata.LinkFinder): """A Google Profiles feed flavor of an Atom Feed.""" _children = gdata.BatchFeed._children.copy() _children['{%s}entry' % atom.ATOM_NAMESPACE] = ('entry', [ProfileEntry]) def __init__(self, author=None, category=None, contributor=None, generator=None, icon=None, atom_id=None, link=None, logo=None, rights=None, subtitle=None, title=None, updated=None, entry=None, total_results=None, start_index=None, items_per_page=None, extension_elements=None, extension_attributes=None, text=None): gdata.BatchFeed.__init__(self, author=author, category=category, contributor=contributor, generator=generator, icon=icon, atom_id=atom_id, link=link, logo=logo, rights=rights, subtitle=subtitle, title=title, updated=updated, entry=entry, total_results=total_results, start_index=start_index, items_per_page=items_per_page, extension_elements=extension_elements, extension_attributes=extension_attributes, text=text) def ProfilesFeedFromString(xml_string): """Converts an XML string into a ProfilesFeed object. Args: xml_string: string The XML describing a Profiles feed. Returns: A ProfilesFeed object corresponding to the given XML. """ return atom.CreateClassFromXMLString(ProfilesFeed, xml_string) gdata-2.0.18/src/gdata/contacts/client.py0000640036466300116100000005357012156622363020265 0ustar afshareng00000000000000#!/usr/bin/env python # # Copyright (C) 2009 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. from types import ListType, DictionaryType """Contains a client to communicate with the Contacts servers. For documentation on the Contacts API, see: http://code.google.com/apis/contatcs/ """ __author__ = 'vinces1979@gmail.com (Vince Spicer)' import gdata.client import gdata.contacts.data import atom.client import atom.data import atom.http_core import gdata.gauth DEFAULT_BATCH_URL = ('https://www.google.com/m8/feeds/contacts/default/full' '/batch') DEFAULT_PROFILES_BATCH_URL = ('https://www.google.com/m8/feeds/profiles/domain/' '%s/full/batch') class ContactsClient(gdata.client.GDClient): api_version = '3' auth_service = 'cp' server = "www.google.com" contact_list = "default" auth_scopes = gdata.gauth.AUTH_SCOPES['cp'] ssl = True def __init__(self, domain=None, auth_token=None, **kwargs): """Constructs a new client for the Email Settings API. Args: domain: string The Google Apps domain (if any). kwargs: The other parameters to pass to the gdata.client.GDClient constructor. """ gdata.client.GDClient.__init__(self, auth_token=auth_token, **kwargs) self.domain = domain def get_feed_uri(self, kind='contacts', contact_list=None, projection='full', scheme="https"): """Builds a feed URI. Args: kind: The type of feed to return, typically 'groups' or 'contacts'. Default value: 'contacts'. contact_list: The contact list to return a feed for. Default value: self.contact_list. projection: The projection to apply to the feed contents, for example 'full', 'base', 'base/12345', 'full/batch'. Default value: 'full'. scheme: The URL scheme such as 'http' or 'https', None to return a relative URI without hostname. Returns: A feed URI using the given kind, contact list, and projection. Example: '/m8/feeds/contacts/default/full'. """ contact_list = contact_list or self.contact_list if kind == 'profiles': contact_list = 'domain/%s' % self.domain prefix = scheme and '%s://%s' % (scheme, self.server) or '' return '%s/m8/feeds/%s/%s/%s' % (prefix, kind, contact_list, projection) GetFeedUri = get_feed_uri def get_contact(self, uri, desired_class=gdata.contacts.data.ContactEntry, auth_token=None, **kwargs): return self.get_entry(uri, auth_token=auth_token, desired_class=desired_class, **kwargs) GetContact = get_contact def create_contact(self, new_contact, insert_uri=None, auth_token=None, **kwargs): """Adds an new contact to Google Contacts. Args: new_contact: atom.Entry or subclass A new contact which is to be added to Google Contacts. insert_uri: the URL to post new contacts to the feed url_params: dict (optional) Additional URL parameters to be included in the insertion request. escape_params: boolean (optional) If true, the url_parameters will be escaped before they are included in the request. Returns: On successful insert, an entry containing the contact created On failure, a RequestError is raised of the form: {'status': HTTP status code from server, 'reason': HTTP reason from the server, 'body': HTTP body of the server's response} """ insert_uri = insert_uri or self.GetFeedUri() return self.Post(new_contact, insert_uri, auth_token=auth_token, **kwargs) CreateContact = create_contact def add_contact(self, new_contact, insert_uri=None, auth_token=None, billing_information=None, birthday=None, calendar_link=None, **kwargs): """Adds an new contact to Google Contacts. Args: new_contact: atom.Entry or subclass A new contact which is to be added to Google Contacts. insert_uri: the URL to post new contacts to the feed url_params: dict (optional) Additional URL parameters to be included in the insertion request. escape_params: boolean (optional) If true, the url_parameters will be escaped before they are included in the request. Returns: On successful insert, an entry containing the contact created On failure, a RequestError is raised of the form: {'status': HTTP status code from server, 'reason': HTTP reason from the server, 'body': HTTP body of the server's response} """ contact = gdata.contacts.data.ContactEntry() if billing_information is not None: if not isinstance(billing_information, gdata.contacts.data.BillingInformation): billing_information = gdata.contacts.data.BillingInformation(text=billing_information) contact.billing_information = billing_information if birthday is not None: if not isinstance(birthday, gdata.contacts.data.Birthday): birthday = gdata.contacts.data.Birthday(when=birthday) contact.birthday = birthday if calendar_link is not None: if type(calendar_link) is not ListType: calendar_link = [calendar_link] for link in calendar_link: if not isinstance(link, gdata.contacts.data.CalendarLink): if type(link) is not DictionaryType: raise TypeError, "calendar_link Requires dictionary not %s" % type(link) link = gdata.contacts.data.CalendarLink( rel=link.get("rel", None), label=link.get("label", None), primary=link.get("primary", None), href=link.get("href", None), ) contact.calendar_link.append(link) insert_uri = insert_uri or self.GetFeedUri() return self.Post(contact, insert_uri, auth_token=auth_token, **kwargs) AddContact = add_contact def get_contacts(self, uri=None, desired_class=gdata.contacts.data.ContactsFeed, auth_token=None, **kwargs): """Obtains a feed with the contacts belonging to the current user. Args: auth_token: An object which sets the Authorization HTTP header in its modify_request method. Recommended classes include gdata.gauth.ClientLoginToken and gdata.gauth.AuthSubToken among others. Represents the current user. Defaults to None and if None, this method will look for a value in the auth_token member of SpreadsheetsClient. desired_class: class descended from atom.core.XmlElement to which a successful response should be converted. If there is no converter function specified (desired_class=None) then the desired_class will be used in calling the atom.core.parse function. If neither the desired_class nor the converter is specified, an HTTP reponse object will be returned. Defaults to gdata.spreadsheets.data.SpreadsheetsFeed. """ uri = uri or self.GetFeedUri() return self.get_feed(uri, auth_token=auth_token, desired_class=desired_class, **kwargs) GetContacts = get_contacts def get_group(self, uri=None, desired_class=gdata.contacts.data.GroupEntry, auth_token=None, **kwargs): """ Get a single groups details Args: uri: the group uri or id """ return self.get_entry(uri, desired_class=desired_class, auth_token=auth_token, **kwargs) GetGroup = get_group def get_groups(self, uri=None, desired_class=gdata.contacts.data.GroupsFeed, auth_token=None, **kwargs): uri = uri or self.GetFeedUri('groups') return self.get_feed(uri, desired_class=desired_class, auth_token=auth_token, **kwargs) GetGroups = get_groups def create_group(self, new_group, insert_uri=None, url_params=None, desired_class=None, **kwargs): insert_uri = insert_uri or self.GetFeedUri('groups') return self.Post(new_group, insert_uri, url_params=url_params, desired_class=desired_class, **kwargs) CreateGroup = create_group def update_group(self, edit_uri, updated_group, url_params=None, escape_params=True, desired_class=None, auth_token=None, **kwargs): return self.Put(updated_group, self._CleanUri(edit_uri), url_params=url_params, escape_params=escape_params, desired_class=desired_class, auth_token=auth_token, **kwargs) UpdateGroup = update_group def delete_group(self, group_object, auth_token=None, force=False, **kws): return self.Delete(group_object, auth_token=auth_token, force=force, **kws) DeleteGroup = delete_group def change_photo(self, media, contact_entry_or_url, content_type=None, content_length=None, auth_token=None, **kwargs): """Change the photo for the contact by uploading a new photo. Performs a PUT against the photo edit URL to send the binary data for the photo. Args: media: filename, file-like-object, or a gdata.data.MediaSource object to send. contact_entry_or_url: ContactEntry or str If it is a ContactEntry, this method will search for an edit photo link URL and perform a PUT to the URL. content_type: str (optional) the mime type for the photo data. This is necessary if media is a file or file name, but if media is a MediaSource object then the media object can contain the mime type. If media_type is set, it will override the mime type in the media object. content_length: int or str (optional) Specifying the content length is only required if media is a file-like object. If media is a filename, the length is determined using os.path.getsize. If media is a MediaSource object, it is assumed that it already contains the content length. """ ifmatch_header = None if isinstance(contact_entry_or_url, gdata.contacts.data.ContactEntry): photo_link = contact_entry_or_url.GetPhotoLink() uri = photo_link.href ifmatch_header = atom.client.CustomHeaders( **{'if-match': photo_link.etag}) else: uri = contact_entry_or_url if isinstance(media, gdata.data.MediaSource): payload = media # If the media object is a file-like object, then use it as the file # handle in the in the MediaSource. elif hasattr(media, 'read'): payload = gdata.data.MediaSource(file_handle=media, content_type=content_type, content_length=content_length) # Assume that the media object is a file name. else: payload = gdata.data.MediaSource(content_type=content_type, content_length=content_length, file_path=media) return self.Put(uri=uri, data=payload, auth_token=auth_token, ifmatch_header=ifmatch_header, **kwargs) ChangePhoto = change_photo def get_photo(self, contact_entry_or_url, auth_token=None, **kwargs): """Retrives the binary data for the contact's profile photo as a string. Args: contact_entry_or_url: a gdata.contacts.ContactEntry object or a string containing the photo link's URL. If the contact entry does not contain a photo link, the image will not be fetched and this method will return None. """ # TODO: add the ability to write out the binary image data to a file, # reading and writing a chunk at a time to avoid potentially using up # large amounts of memory. url = None if isinstance(contact_entry_or_url, gdata.contacts.data.ContactEntry): photo_link = contact_entry_or_url.GetPhotoLink() if photo_link: url = photo_link.href else: url = contact_entry_or_url if url: return self.Get(url, auth_token=auth_token, **kwargs).read() else: return None GetPhoto = get_photo def delete_photo(self, contact_entry_or_url, auth_token=None, **kwargs): """Delete the contact's profile photo. Args: contact_entry_or_url: a gdata.contacts.ContactEntry object or a string containing the photo link's URL. """ uri = None ifmatch_header = None if isinstance(contact_entry_or_url, gdata.contacts.data.ContactEntry): photo_link = contact_entry_or_url.GetPhotoLink() if photo_link.etag: uri = photo_link.href ifmatch_header = atom.client.CustomHeaders( **{'if-match': photo_link.etag}) else: # No etag means no photo has been assigned to this contact. return else: uri = contact_entry_or_url if uri: self.Delete(entry_or_uri=uri, auth_token=auth_token, ifmatch_header=ifmatch_header, **kwargs) DeletePhoto = delete_photo def get_profiles_feed(self, uri=None, auth_token=None, **kwargs): """Retrieves a feed containing all domain's profiles. Args: uri: string (optional) the URL to retrieve the profiles feed, for example /m8/feeds/profiles/default/full Returns: On success, a ProfilesFeed containing the profiles. On failure, raises a RequestError. """ uri = uri or self.GetFeedUri('profiles') return self.get_feed(uri, auth_token=auth_token, desired_class=gdata.contacts.data.ProfilesFeed, **kwargs) GetProfilesFeed = get_profiles_feed def get_profile(self, uri, auth_token=None, **kwargs): """Retrieves a domain's profile for the user. Args: uri: string the URL to retrieve the profiles feed, for example /m8/feeds/profiles/default/full/username Returns: On success, a ProfileEntry containing the profile for the user. On failure, raises a RequestError """ return self.get_entry(uri, desired_class=gdata.contacts.data.ProfileEntry, auth_token=auth_token, **kwargs) GetProfile = get_profile def update_profile(self, updated_profile, auth_token=None, force=False, **kwargs): """Updates an existing profile. Args: updated_profile: atom.Entry or subclass containing the Atom Entry which will replace the profile which is stored at the edit_url. auth_token: An object which sets the Authorization HTTP header in its modify_request method. Recommended classes include gdata.gauth.ClientLoginToken and gdata.gauth.AuthSubToken among others. Represents the current user. Defaults to None and if None, this method will look for a value in the auth_token member of ContactsClient. force: boolean stating whether an update should be forced. Defaults to False. Normally, if a change has been made since the passed in entry was obtained, the server will not overwrite the entry since the changes were based on an obsolete version of the entry. Setting force to True will cause the update to silently overwrite whatever version is present. url_params: dict (optional) Additional URL parameters to be included in the insertion request. escape_params: boolean (optional) If true, the url_parameters will be escaped before they are included in the request. Returns: On successful update, a httplib.HTTPResponse containing the server's response to the PUT request. On failure, raises a RequestError. """ return self.Update(updated_profile, auth_token=auth_token, force=force, **kwargs) UpdateProfile = update_profile def execute_batch(self, batch_feed, url=DEFAULT_BATCH_URL, desired_class=None, auth_token=None, **kwargs): """Sends a batch request feed to the server. Args: batch_feed: gdata.contacts.ContactFeed A feed containing batch request entries. Each entry contains the operation to be performed on the data contained in the entry. For example an entry with an operation type of insert will be used as if the individual entry had been inserted. url: str The batch URL to which these operations should be applied. converter: Function (optional) The function used to convert the server's response to an object. Returns: The results of the batch request's execution on the server. If the default converter is used, this is stored in a ContactsFeed. """ return self.Post(batch_feed, url, desired_class=desired_class, auth_token=None, **kwargs) ExecuteBatch = execute_batch def execute_batch_profiles(self, batch_feed, url=None, desired_class=gdata.contacts.data.ProfilesFeed, auth_token=None, **kwargs): """Sends a batch request feed to the server. Args: batch_feed: gdata.profiles.ProfilesFeed A feed containing batch request entries. Each entry contains the operation to be performed on the data contained in the entry. For example an entry with an operation type of insert will be used as if the individual entry had been inserted. url: string The batch URL to which these operations should be applied. converter: Function (optional) The function used to convert the server's response to an object. The default value is gdata.profiles.ProfilesFeedFromString. Returns: The results of the batch request's execution on the server. If the default converter is used, this is stored in a ProfilesFeed. """ url = url or (DEFAULT_PROFILES_BATCH_URL % self.domain) return self.Post(batch_feed, url, desired_class=desired_class, auth_token=auth_token, **kwargs) ExecuteBatchProfiles = execute_batch_profiles def _CleanUri(self, uri): """Sanitizes a feed URI. Args: uri: The URI to sanitize, can be relative or absolute. Returns: The given URI without its http://server prefix, if any. Keeps the leading slash of the URI. """ url_prefix = 'http://%s' % self.server if uri.startswith(url_prefix): uri = uri[len(url_prefix):] return uri class ContactsQuery(gdata.client.Query): """ Create a custom Contacts Query Full specs can be found at: U{Contacts query parameters reference } """ def __init__(self, feed=None, group=None, orderby=None, showdeleted=None, sortorder=None, requirealldeleted=None, **kwargs): """ @param max_results: The maximum number of entries to return. If you want to receive all of the contacts, rather than only the default maximum, you can specify a very large number for max-results. @param start-index: The 1-based index of the first result to be retrieved. @param updated-min: The lower bound on entry update dates. @param group: Constrains the results to only the contacts belonging to the group specified. Value of this parameter specifies group ID @param orderby: Sorting criterion. The only supported value is lastmodified. @param showdeleted: Include deleted contacts in the returned contacts feed @pram sortorder: Sorting order direction. Can be either ascending or descending. @param requirealldeleted: Only relevant if showdeleted and updated-min are also provided. It dictates the behavior of the server in case it detects that placeholders of some entries deleted since the point in time specified as updated-min may have been lost. """ gdata.client.Query.__init__(self, **kwargs) self.group = group self.orderby = orderby self.sortorder = sortorder self.showdeleted = showdeleted def modify_request(self, http_request): if self.group: gdata.client._add_query_param('group', self.group, http_request) if self.orderby: gdata.client._add_query_param('orderby', self.orderby, http_request) if self.sortorder: gdata.client._add_query_param('sortorder', self.sortorder, http_request) if self.showdeleted: gdata.client._add_query_param('showdeleted', self.showdeleted, http_request) gdata.client.Query.modify_request(self, http_request) ModifyRequest = modify_request class ProfilesQuery(gdata.client.Query): """ Create a custom Profiles Query Full specs can be found at: U{Profiless query parameters reference } """ def __init__(self, feed=None, start_key=None, **kwargs): """ @param start_key: Opaque key of the first element to retrieve. Present in the next link of an earlier request, if further pages of response are available. """ gdata.client.Query.__init__(self, **kwargs) self.feed = feed or 'https://www.google.com/m8/feeds/profiles/default/full' self.start_key = start_key def modify_request(self, http_request): if self.start_key: gdata.client._add_query_param('start-key', self.start_key, http_request) gdata.client.Query.modify_request(self, http_request) ModifyRequest = modify_request gdata-2.0.18/src/gdata/contacts/service.py0000640036466300116100000004170112156622363020440 0ustar afshareng00000000000000#!/usr/bin/env python # # Copyright 2009 Google Inc. All Rights Reserved. # # 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. """ContactsService extends the GDataService for Google Contacts operations. ContactsService: Provides methods to query feeds and manipulate items. Extends GDataService. DictionaryToParamList: Function which converts a dictionary into a list of URL arguments (represented as strings). This is a utility function used in CRUD operations. """ __author__ = 'dbrattli (Dag Brattli)' import gdata import gdata.calendar import gdata.service DEFAULT_BATCH_URL = ('http://www.google.com/m8/feeds/contacts/default/full' '/batch') DEFAULT_PROFILES_BATCH_URL = ('http://www.google.com' '/m8/feeds/profiles/default/full/batch') GDATA_VER_HEADER = 'GData-Version' class Error(Exception): pass class RequestError(Error): pass class ContactsService(gdata.service.GDataService): """Client for the Google Contacts service.""" def __init__(self, email=None, password=None, source=None, server='www.google.com', additional_headers=None, contact_list='default', **kwargs): """Creates a client for the Contacts service. Args: email: string (optional) The user's email address, used for authentication. password: string (optional) The user's password. source: string (optional) The name of the user's application. server: string (optional) The name of the server to which a connection will be opened. Default value: 'www.google.com'. contact_list: string (optional) The name of the default contact list to use when no URI is specified to the methods of the service. Default value: 'default' (the logged in user's contact list). **kwargs: The other parameters to pass to gdata.service.GDataService constructor. """ self.contact_list = contact_list gdata.service.GDataService.__init__( self, email=email, password=password, service='cp', source=source, server=server, additional_headers=additional_headers, **kwargs) def GetFeedUri(self, kind='contacts', contact_list=None, projection='full', scheme=None): """Builds a feed URI. Args: kind: The type of feed to return, typically 'groups' or 'contacts'. Default value: 'contacts'. contact_list: The contact list to return a feed for. Default value: self.contact_list. projection: The projection to apply to the feed contents, for example 'full', 'base', 'base/12345', 'full/batch'. Default value: 'full'. scheme: The URL scheme such as 'http' or 'https', None to return a relative URI without hostname. Returns: A feed URI using the given kind, contact list, and projection. Example: '/m8/feeds/contacts/default/full'. """ contact_list = contact_list or self.contact_list if kind == 'profiles': contact_list = 'domain/%s' % contact_list prefix = scheme and '%s://%s' % (scheme, self.server) or '' return '%s/m8/feeds/%s/%s/%s' % (prefix, kind, contact_list, projection) def GetContactsFeed(self, uri=None): uri = uri or self.GetFeedUri() return self.Get(uri, converter=gdata.contacts.ContactsFeedFromString) def GetContact(self, uri): return self.Get(uri, converter=gdata.contacts.ContactEntryFromString) def CreateContact(self, new_contact, insert_uri=None, url_params=None, escape_params=True): """Adds an new contact to Google Contacts. Args: new_contact: atom.Entry or subclass A new contact which is to be added to Google Contacts. insert_uri: the URL to post new contacts to the feed url_params: dict (optional) Additional URL parameters to be included in the insertion request. escape_params: boolean (optional) If true, the url_parameters will be escaped before they are included in the request. Returns: On successful insert, an entry containing the contact created On failure, a RequestError is raised of the form: {'status': HTTP status code from server, 'reason': HTTP reason from the server, 'body': HTTP body of the server's response} """ insert_uri = insert_uri or self.GetFeedUri() return self.Post(new_contact, insert_uri, url_params=url_params, escape_params=escape_params, converter=gdata.contacts.ContactEntryFromString) def UpdateContact(self, edit_uri, updated_contact, url_params=None, escape_params=True): """Updates an existing contact. Args: edit_uri: string The edit link URI for the element being updated updated_contact: string, atom.Entry or subclass containing the Atom Entry which will replace the contact which is stored at the edit_url url_params: dict (optional) Additional URL parameters to be included in the update request. escape_params: boolean (optional) If true, the url_parameters will be escaped before they are included in the request. Returns: On successful update, a httplib.HTTPResponse containing the server's response to the PUT request. On failure, a RequestError is raised of the form: {'status': HTTP status code from server, 'reason': HTTP reason from the server, 'body': HTTP body of the server's response} """ return self.Put(updated_contact, self._CleanUri(edit_uri), url_params=url_params, escape_params=escape_params, converter=gdata.contacts.ContactEntryFromString) def DeleteContact(self, edit_uri, extra_headers=None, url_params=None, escape_params=True): """Removes an contact with the specified ID from Google Contacts. Args: edit_uri: string The edit URL of the entry to be deleted. Example: '/m8/feeds/contacts/default/full/xxx/yyy' url_params: dict (optional) Additional URL parameters to be included in the deletion request. escape_params: boolean (optional) If true, the url_parameters will be escaped before they are included in the request. Returns: On successful delete, a httplib.HTTPResponse containing the server's response to the DELETE request. On failure, a RequestError is raised of the form: {'status': HTTP status code from server, 'reason': HTTP reason from the server, 'body': HTTP body of the server's response} """ return self.Delete(self._CleanUri(edit_uri), url_params=url_params, escape_params=escape_params) def GetGroupsFeed(self, uri=None): uri = uri or self.GetFeedUri('groups') return self.Get(uri, converter=gdata.contacts.GroupsFeedFromString) def CreateGroup(self, new_group, insert_uri=None, url_params=None, escape_params=True): insert_uri = insert_uri or self.GetFeedUri('groups') return self.Post(new_group, insert_uri, url_params=url_params, escape_params=escape_params, converter=gdata.contacts.GroupEntryFromString) def UpdateGroup(self, edit_uri, updated_group, url_params=None, escape_params=True): return self.Put(updated_group, self._CleanUri(edit_uri), url_params=url_params, escape_params=escape_params, converter=gdata.contacts.GroupEntryFromString) def DeleteGroup(self, edit_uri, extra_headers=None, url_params=None, escape_params=True): return self.Delete(self._CleanUri(edit_uri), url_params=url_params, escape_params=escape_params) def ChangePhoto(self, media, contact_entry_or_url, content_type=None, content_length=None): """Change the photo for the contact by uploading a new photo. Performs a PUT against the photo edit URL to send the binary data for the photo. Args: media: filename, file-like-object, or a gdata.MediaSource object to send. contact_entry_or_url: ContactEntry or str If it is a ContactEntry, this method will search for an edit photo link URL and perform a PUT to the URL. content_type: str (optional) the mime type for the photo data. This is necessary if media is a file or file name, but if media is a MediaSource object then the media object can contain the mime type. If media_type is set, it will override the mime type in the media object. content_length: int or str (optional) Specifying the content length is only required if media is a file-like object. If media is a filename, the length is determined using os.path.getsize. If media is a MediaSource object, it is assumed that it already contains the content length. """ if isinstance(contact_entry_or_url, gdata.contacts.ContactEntry): url = contact_entry_or_url.GetPhotoEditLink().href else: url = contact_entry_or_url if isinstance(media, gdata.MediaSource): payload = media # If the media object is a file-like object, then use it as the file # handle in the in the MediaSource. elif hasattr(media, 'read'): payload = gdata.MediaSource(file_handle=media, content_type=content_type, content_length=content_length) # Assume that the media object is a file name. else: payload = gdata.MediaSource(content_type=content_type, content_length=content_length, file_path=media) return self.Put(payload, url) def GetPhoto(self, contact_entry_or_url): """Retrives the binary data for the contact's profile photo as a string. Args: contact_entry_or_url: a gdata.contacts.ContactEntry objecr or a string containing the photo link's URL. If the contact entry does not contain a photo link, the image will not be fetched and this method will return None. """ # TODO: add the ability to write out the binary image data to a file, # reading and writing a chunk at a time to avoid potentially using up # large amounts of memory. url = None if isinstance(contact_entry_or_url, gdata.contacts.ContactEntry): photo_link = contact_entry_or_url.GetPhotoLink() if photo_link: url = photo_link.href else: url = contact_entry_or_url if url: return self.Get(url, converter=str) else: return None def DeletePhoto(self, contact_entry_or_url): url = None if isinstance(contact_entry_or_url, gdata.contacts.ContactEntry): url = contact_entry_or_url.GetPhotoEditLink().href else: url = contact_entry_or_url if url: self.Delete(url) def GetProfilesFeed(self, uri=None): """Retrieves a feed containing all domain's profiles. Args: uri: string (optional) the URL to retrieve the profiles feed, for example /m8/feeds/profiles/default/full Returns: On success, a ProfilesFeed containing the profiles. On failure, raises a RequestError. """ uri = uri or self.GetFeedUri('profiles') return self.Get(uri, converter=gdata.contacts.ProfilesFeedFromString) def GetProfile(self, uri): """Retrieves a domain's profile for the user. Args: uri: string the URL to retrieve the profiles feed, for example /m8/feeds/profiles/default/full/username Returns: On success, a ProfileEntry containing the profile for the user. On failure, raises a RequestError """ return self.Get(uri, converter=gdata.contacts.ProfileEntryFromString) def UpdateProfile(self, edit_uri, updated_profile, url_params=None, escape_params=True): """Updates an existing profile. Args: edit_uri: string The edit link URI for the element being updated updated_profile: string atom.Entry or subclass containing the Atom Entry which will replace the profile which is stored at the edit_url. url_params: dict (optional) Additional URL parameters to be included in the update request. escape_params: boolean (optional) If true, the url_params will be escaped before they are included in the request. Returns: On successful update, a httplib.HTTPResponse containing the server's response to the PUT request. On failure, raises a RequestError. """ return self.Put(updated_profile, self._CleanUri(edit_uri), url_params=url_params, escape_params=escape_params, converter=gdata.contacts.ProfileEntryFromString) def ExecuteBatch(self, batch_feed, url, converter=gdata.contacts.ContactsFeedFromString): """Sends a batch request feed to the server. Args: batch_feed: gdata.contacts.ContactFeed A feed containing batch request entries. Each entry contains the operation to be performed on the data contained in the entry. For example an entry with an operation type of insert will be used as if the individual entry had been inserted. url: str The batch URL to which these operations should be applied. converter: Function (optional) The function used to convert the server's response to an object. The default value is ContactsFeedFromString. Returns: The results of the batch request's execution on the server. If the default converter is used, this is stored in a ContactsFeed. """ return self.Post(batch_feed, url, converter=converter) def ExecuteBatchProfiles(self, batch_feed, url, converter=gdata.contacts.ProfilesFeedFromString): """Sends a batch request feed to the server. Args: batch_feed: gdata.profiles.ProfilesFeed A feed containing batch request entries. Each entry contains the operation to be performed on the data contained in the entry. For example an entry with an operation type of insert will be used as if the individual entry had been inserted. url: string The batch URL to which these operations should be applied. converter: Function (optional) The function used to convert the server's response to an object. The default value is gdata.profiles.ProfilesFeedFromString. Returns: The results of the batch request's execution on the server. If the default converter is used, this is stored in a ProfilesFeed. """ return self.Post(batch_feed, url, converter=converter) def _CleanUri(self, uri): """Sanitizes a feed URI. Args: uri: The URI to sanitize, can be relative or absolute. Returns: The given URI without its http://server prefix, if any. Keeps the leading slash of the URI. """ url_prefix = 'http://%s' % self.server if uri.startswith(url_prefix): uri = uri[len(url_prefix):] return uri class ContactsQuery(gdata.service.Query): def __init__(self, feed=None, text_query=None, params=None, categories=None, group=None): self.feed = feed or '/m8/feeds/contacts/default/full' if group: self._SetGroup(group) gdata.service.Query.__init__(self, feed=self.feed, text_query=text_query, params=params, categories=categories) def _GetGroup(self): if 'group' in self: return self['group'] else: return None def _SetGroup(self, group_id): self['group'] = group_id group = property(_GetGroup, _SetGroup, doc='The group query parameter to find only contacts in this group') class GroupsQuery(gdata.service.Query): def __init__(self, feed=None, text_query=None, params=None, categories=None): self.feed = feed or '/m8/feeds/groups/default/full' gdata.service.Query.__init__(self, feed=self.feed, text_query=text_query, params=params, categories=categories) class ProfilesQuery(gdata.service.Query): """Constructs a query object for the profiles feed.""" def __init__(self, feed=None, text_query=None, params=None, categories=None): self.feed = feed or '/m8/feeds/profiles/default/full' gdata.service.Query.__init__(self, feed=self.feed, text_query=text_query, params=params, categories=categories) gdata-2.0.18/src/gdata/auth.py0000640036466300116100000011226512156622363016127 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright (C) 2007 - 2009 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. import cgi import math import random import re import time import types import urllib import atom.http_interface import atom.token_store import atom.url import gdata.oauth as oauth import gdata.oauth.rsa as oauth_rsa try: import gdata.tlslite.utils.keyfactory as keyfactory except ImportError: from tlslite.tlslite.utils import keyfactory try: import gdata.tlslite.utils.cryptomath as cryptomath except ImportError: from tlslite.tlslite.utils import cryptomath import gdata.gauth __author__ = 'api.jscudder (Jeff Scudder)' PROGRAMMATIC_AUTH_LABEL = 'GoogleLogin auth=' AUTHSUB_AUTH_LABEL = 'AuthSub token=' """This module provides functions and objects used with Google authentication. Details on Google authorization mechanisms used with the Google Data APIs can be found here: http://code.google.com/apis/gdata/auth.html http://code.google.com/apis/accounts/ The essential functions are the following. Related to ClientLogin: generate_client_login_request_body: Constructs the body of an HTTP request to obtain a ClientLogin token for a specific service. extract_client_login_token: Creates a ClientLoginToken with the token from a success response to a ClientLogin request. get_captcha_challenge: If the server responded to the ClientLogin request with a CAPTCHA challenge, this method extracts the CAPTCHA URL and identifying CAPTCHA token. Related to AuthSub: generate_auth_sub_url: Constructs a full URL for a AuthSub request. The user's browser must be sent to this Google Accounts URL and redirected back to the app to obtain the AuthSub token. extract_auth_sub_token_from_url: Once the user's browser has been redirected back to the web app, use this function to create an AuthSubToken with the correct authorization token and scope. token_from_http_body: Extracts the AuthSubToken value string from the server's response to an AuthSub session token upgrade request. """ def generate_client_login_request_body(email, password, service, source, account_type='HOSTED_OR_GOOGLE', captcha_token=None, captcha_response=None): """Creates the body of the autentication request See http://code.google.com/apis/accounts/AuthForInstalledApps.html#Request for more details. Args: email: str password: str service: str source: str account_type: str (optional) Defaul is 'HOSTED_OR_GOOGLE', other valid values are 'GOOGLE' and 'HOSTED' captcha_token: str (optional) captcha_response: str (optional) Returns: The HTTP body to send in a request for a client login token. """ return gdata.gauth.generate_client_login_request_body(email, password, service, source, account_type, captcha_token, captcha_response) GenerateClientLoginRequestBody = generate_client_login_request_body def GenerateClientLoginAuthToken(http_body): """Returns the token value to use in Authorization headers. Reads the token from the server's response to a Client Login request and creates header value to use in requests. Args: http_body: str The body of the server's HTTP response to a Client Login request Returns: The value half of an Authorization header. """ token = get_client_login_token(http_body) if token: return 'GoogleLogin auth=%s' % token return None def get_client_login_token(http_body): """Returns the token value for a ClientLoginToken. Reads the token from the server's response to a Client Login request and creates the token value string to use in requests. Args: http_body: str The body of the server's HTTP response to a Client Login request Returns: The token value string for a ClientLoginToken. """ return gdata.gauth.get_client_login_token_string(http_body) def extract_client_login_token(http_body, scopes): """Parses the server's response and returns a ClientLoginToken. Args: http_body: str The body of the server's HTTP response to a Client Login request. It is assumed that the login request was successful. scopes: list containing atom.url.Urls or strs. The scopes list contains all of the partial URLs under which the client login token is valid. For example, if scopes contains ['http://example.com/foo'] then the client login token would be valid for http://example.com/foo/bar/baz Returns: A ClientLoginToken which is valid for the specified scopes. """ token_string = get_client_login_token(http_body) token = ClientLoginToken(scopes=scopes) token.set_token_string(token_string) return token def get_captcha_challenge(http_body, captcha_base_url='http://www.google.com/accounts/'): """Returns the URL and token for a CAPTCHA challenge issued by the server. Args: http_body: str The body of the HTTP response from the server which contains the CAPTCHA challenge. captcha_base_url: str This function returns a full URL for viewing the challenge image which is built from the server's response. This base_url is used as the beginning of the URL because the server only provides the end of the URL. For example the server provides 'Captcha?ctoken=Hi...N' and the URL for the image is 'http://www.google.com/accounts/Captcha?ctoken=Hi...N' Returns: A dictionary containing the information needed to repond to the CAPTCHA challenge, the image URL and the ID token of the challenge. The dictionary is in the form: {'token': string identifying the CAPTCHA image, 'url': string containing the URL of the image} Returns None if there was no CAPTCHA challenge in the response. """ return gdata.gauth.get_captcha_challenge(http_body, captcha_base_url) GetCaptchaChallenge = get_captcha_challenge def GenerateOAuthRequestTokenUrl( oauth_input_params, scopes, request_token_url='https://www.google.com/accounts/OAuthGetRequestToken', extra_parameters=None): """Generate a URL at which a request for OAuth request token is to be sent. Args: oauth_input_params: OAuthInputParams OAuth input parameters. scopes: list of strings The URLs of the services to be accessed. request_token_url: string The beginning of the request token URL. This is normally 'https://www.google.com/accounts/OAuthGetRequestToken' or '/accounts/OAuthGetRequestToken' extra_parameters: dict (optional) key-value pairs as any additional parameters to be included in the URL and signature while making a request for fetching an OAuth request token. All the OAuth parameters are added by default. But if provided through this argument, any default parameters will be overwritten. For e.g. a default parameter oauth_version 1.0 can be overwritten if extra_parameters = {'oauth_version': '2.0'} Returns: atom.url.Url OAuth request token URL. """ scopes_string = ' '.join([str(scope) for scope in scopes]) parameters = {'scope': scopes_string} if extra_parameters: parameters.update(extra_parameters) oauth_request = oauth.OAuthRequest.from_consumer_and_token( oauth_input_params.GetConsumer(), http_url=request_token_url, parameters=parameters) oauth_request.sign_request(oauth_input_params.GetSignatureMethod(), oauth_input_params.GetConsumer(), None) return atom.url.parse_url(oauth_request.to_url()) def GenerateOAuthAuthorizationUrl( request_token, authorization_url='https://www.google.com/accounts/OAuthAuthorizeToken', callback_url=None, extra_params=None, include_scopes_in_callback=False, scopes_param_prefix='oauth_token_scope'): """Generates URL at which user will login to authorize the request token. Args: request_token: gdata.auth.OAuthToken OAuth request token. authorization_url: string The beginning of the authorization URL. This is normally 'https://www.google.com/accounts/OAuthAuthorizeToken' or '/accounts/OAuthAuthorizeToken' callback_url: string (optional) The URL user will be sent to after logging in and granting access. extra_params: dict (optional) Additional parameters to be sent. include_scopes_in_callback: Boolean (default=False) if set to True, and if 'callback_url' is present, the 'callback_url' will be modified to include the scope(s) from the request token as a URL parameter. The key for the 'callback' URL's scope parameter will be OAUTH_SCOPE_URL_PARAM_NAME. The benefit of including the scope URL as a parameter to the 'callback' URL, is that the page which receives the OAuth token will be able to tell which URLs the token grants access to. scopes_param_prefix: string (default='oauth_token_scope') The URL parameter key which maps to the list of valid scopes for the token. This URL parameter will be included in the callback URL along with the scopes of the token as value if include_scopes_in_callback=True. Returns: atom.url.Url OAuth authorization URL. """ scopes = request_token.scopes if isinstance(scopes, list): scopes = ' '.join(scopes) if include_scopes_in_callback and callback_url: if callback_url.find('?') > -1: callback_url += '&' else: callback_url += '?' callback_url += urllib.urlencode({scopes_param_prefix:scopes}) oauth_token = oauth.OAuthToken(request_token.key, request_token.secret) oauth_request = oauth.OAuthRequest.from_token_and_callback( token=oauth_token, callback=callback_url, http_url=authorization_url, parameters=extra_params) return atom.url.parse_url(oauth_request.to_url()) def GenerateOAuthAccessTokenUrl( authorized_request_token, oauth_input_params, access_token_url='https://www.google.com/accounts/OAuthGetAccessToken', oauth_version='1.0', oauth_verifier=None): """Generates URL at which user will login to authorize the request token. Args: authorized_request_token: gdata.auth.OAuthToken OAuth authorized request token. oauth_input_params: OAuthInputParams OAuth input parameters. access_token_url: string The beginning of the authorization URL. This is normally 'https://www.google.com/accounts/OAuthGetAccessToken' or '/accounts/OAuthGetAccessToken' oauth_version: str (default='1.0') oauth_version parameter. oauth_verifier: str (optional) If present, it is assumed that the client will use the OAuth v1.0a protocol which includes passing the oauth_verifier (as returned by the SP) in the access token step. Returns: atom.url.Url OAuth access token URL. """ oauth_token = oauth.OAuthToken(authorized_request_token.key, authorized_request_token.secret) parameters = {'oauth_version': oauth_version} if oauth_verifier is not None: parameters['oauth_verifier'] = oauth_verifier oauth_request = oauth.OAuthRequest.from_consumer_and_token( oauth_input_params.GetConsumer(), token=oauth_token, http_url=access_token_url, parameters=parameters) oauth_request.sign_request(oauth_input_params.GetSignatureMethod(), oauth_input_params.GetConsumer(), oauth_token) return atom.url.parse_url(oauth_request.to_url()) def GenerateAuthSubUrl(next, scope, secure=False, session=True, request_url='https://www.google.com/accounts/AuthSubRequest', domain='default'): """Generate a URL at which the user will login and be redirected back. Users enter their credentials on a Google login page and a token is sent to the URL specified in next. See documentation for AuthSub login at: http://code.google.com/apis/accounts/AuthForWebApps.html Args: request_url: str The beginning of the request URL. This is normally 'http://www.google.com/accounts/AuthSubRequest' or '/accounts/AuthSubRequest' next: string The URL user will be sent to after logging in. scope: string The URL of the service to be accessed. secure: boolean (optional) Determines whether or not the issued token is a secure token. session: boolean (optional) Determines whether or not the issued token can be upgraded to a session token. domain: str (optional) The Google Apps domain for this account. If this is not a Google Apps account, use 'default' which is the default value. """ # Translate True/False values for parameters into numeric values acceoted # by the AuthSub service. if secure: secure = 1 else: secure = 0 if session: session = 1 else: session = 0 request_params = urllib.urlencode({'next': next, 'scope': scope, 'secure': secure, 'session': session, 'hd': domain}) if request_url.find('?') == -1: return '%s?%s' % (request_url, request_params) else: # The request URL already contained url parameters so we should add # the parameters using the & seperator return '%s&%s' % (request_url, request_params) def generate_auth_sub_url(next, scopes, secure=False, session=True, request_url='https://www.google.com/accounts/AuthSubRequest', domain='default', scopes_param_prefix='auth_sub_scopes'): """Constructs a URL string for requesting a multiscope AuthSub token. The generated token will contain a URL parameter to pass along the requested scopes to the next URL. When the Google Accounts page redirects the broswser to the 'next' URL, it appends the single use AuthSub token value to the URL as a URL parameter with the key 'token'. However, the information about which scopes were requested is not included by Google Accounts. This method adds the scopes to the next URL before making the request so that the redirect will be sent to a page, and both the token value and the list of scopes can be extracted from the request URL. Args: next: atom.url.URL or string The URL user will be sent to after authorizing this web application to access their data. scopes: list containint strings The URLs of the services to be accessed. secure: boolean (optional) Determines whether or not the issued token is a secure token. session: boolean (optional) Determines whether or not the issued token can be upgraded to a session token. request_url: atom.url.Url or str The beginning of the request URL. This is normally 'http://www.google.com/accounts/AuthSubRequest' or '/accounts/AuthSubRequest' domain: The domain which the account is part of. This is used for Google Apps accounts, the default value is 'default' which means that the requested account is a Google Account (@gmail.com for example) scopes_param_prefix: str (optional) The requested scopes are added as a URL parameter to the next URL so that the page at the 'next' URL can extract the token value and the valid scopes from the URL. The key for the URL parameter defaults to 'auth_sub_scopes' Returns: An atom.url.Url which the user's browser should be directed to in order to authorize this application to access their information. """ if isinstance(next, (str, unicode)): next = atom.url.parse_url(next) scopes_string = ' '.join([str(scope) for scope in scopes]) next.params[scopes_param_prefix] = scopes_string if isinstance(request_url, (str, unicode)): request_url = atom.url.parse_url(request_url) request_url.params['next'] = str(next) request_url.params['scope'] = scopes_string if session: request_url.params['session'] = 1 else: request_url.params['session'] = 0 if secure: request_url.params['secure'] = 1 else: request_url.params['secure'] = 0 request_url.params['hd'] = domain return request_url def AuthSubTokenFromUrl(url): """Extracts the AuthSub token from the URL. Used after the AuthSub redirect has sent the user to the 'next' page and appended the token to the URL. This function returns the value to be used in the Authorization header. Args: url: str The URL of the current page which contains the AuthSub token as a URL parameter. """ token = TokenFromUrl(url) if token: return 'AuthSub token=%s' % token return None def TokenFromUrl(url): """Extracts the AuthSub token from the URL. Returns the raw token value. Args: url: str The URL or the query portion of the URL string (after the ?) of the current page which contains the AuthSub token as a URL parameter. """ if url.find('?') > -1: query_params = url.split('?')[1] else: query_params = url for pair in query_params.split('&'): if pair.startswith('token='): return pair[6:] return None def extract_auth_sub_token_from_url(url, scopes_param_prefix='auth_sub_scopes', rsa_key=None): """Creates an AuthSubToken and sets the token value and scopes from the URL. After the Google Accounts AuthSub pages redirect the user's broswer back to the web application (using the 'next' URL from the request) the web app must extract the token from the current page's URL. The token is provided as a URL parameter named 'token' and if generate_auth_sub_url was used to create the request, the token's valid scopes are included in a URL parameter whose name is specified in scopes_param_prefix. Args: url: atom.url.Url or str representing the current URL. The token value and valid scopes should be included as URL parameters. scopes_param_prefix: str (optional) The URL parameter key which maps to the list of valid scopes for the token. Returns: An AuthSubToken with the token value from the URL and set to be valid for the scopes passed in on the URL. If no scopes were included in the URL, the AuthSubToken defaults to being valid for no scopes. If there was no 'token' parameter in the URL, this function returns None. """ if isinstance(url, (str, unicode)): url = atom.url.parse_url(url) if 'token' not in url.params: return None scopes = [] if scopes_param_prefix in url.params: scopes = url.params[scopes_param_prefix].split(' ') token_value = url.params['token'] if rsa_key: token = SecureAuthSubToken(rsa_key, scopes=scopes) else: token = AuthSubToken(scopes=scopes) token.set_token_string(token_value) return token def AuthSubTokenFromHttpBody(http_body): """Extracts the AuthSub token from an HTTP body string. Used to find the new session token after making a request to upgrade a single use AuthSub token. Args: http_body: str The repsonse from the server which contains the AuthSub key. For example, this function would find the new session token from the server's response to an upgrade token request. Returns: The header value to use for Authorization which contains the AuthSub token. """ token_value = token_from_http_body(http_body) if token_value: return '%s%s' % (AUTHSUB_AUTH_LABEL, token_value) return None def token_from_http_body(http_body): """Extracts the AuthSub token from an HTTP body string. Used to find the new session token after making a request to upgrade a single use AuthSub token. Args: http_body: str The repsonse from the server which contains the AuthSub key. For example, this function would find the new session token from the server's response to an upgrade token request. Returns: The raw token value to use in an AuthSubToken object. """ for response_line in http_body.splitlines(): if response_line.startswith('Token='): # Strip off Token= and return the token value string. return response_line[6:] return None TokenFromHttpBody = token_from_http_body def OAuthTokenFromUrl(url, scopes_param_prefix='oauth_token_scope'): """Creates an OAuthToken and sets token key and scopes (if present) from URL. After the Google Accounts OAuth pages redirect the user's broswer back to the web application (using the 'callback' URL from the request) the web app can extract the token from the current page's URL. The token is same as the request token, but it is either authorized (if user grants access) or unauthorized (if user denies access). The token is provided as a URL parameter named 'oauth_token' and if it was chosen to use GenerateOAuthAuthorizationUrl with include_scopes_in_param=True, the token's valid scopes are included in a URL parameter whose name is specified in scopes_param_prefix. Args: url: atom.url.Url or str representing the current URL. The token value and valid scopes should be included as URL parameters. scopes_param_prefix: str (optional) The URL parameter key which maps to the list of valid scopes for the token. Returns: An OAuthToken with the token key from the URL and set to be valid for the scopes passed in on the URL. If no scopes were included in the URL, the OAuthToken defaults to being valid for no scopes. If there was no 'oauth_token' parameter in the URL, this function returns None. """ if isinstance(url, (str, unicode)): url = atom.url.parse_url(url) if 'oauth_token' not in url.params: return None scopes = [] if scopes_param_prefix in url.params: scopes = url.params[scopes_param_prefix].split(' ') token_key = url.params['oauth_token'] token = OAuthToken(key=token_key, scopes=scopes) return token def OAuthTokenFromHttpBody(http_body): """Parses the HTTP response body and returns an OAuth token. The returned OAuth token will just have key and secret parameters set. It won't have any knowledge about the scopes or oauth_input_params. It is your responsibility to make it aware of the remaining parameters. Returns: OAuthToken OAuth token. """ token = oauth.OAuthToken.from_string(http_body) oauth_token = OAuthToken(key=token.key, secret=token.secret) return oauth_token class OAuthSignatureMethod(object): """Holds valid OAuth signature methods. RSA_SHA1: Class to build signature according to RSA-SHA1 algorithm. HMAC_SHA1: Class to build signature according to HMAC-SHA1 algorithm. """ HMAC_SHA1 = oauth.OAuthSignatureMethod_HMAC_SHA1 class RSA_SHA1(oauth_rsa.OAuthSignatureMethod_RSA_SHA1): """Provides implementation for abstract methods to return RSA certs.""" def __init__(self, private_key, public_cert): self.private_key = private_key self.public_cert = public_cert def _fetch_public_cert(self, unused_oauth_request): return self.public_cert def _fetch_private_cert(self, unused_oauth_request): return self.private_key class OAuthInputParams(object): """Stores OAuth input parameters. This class is a store for OAuth input parameters viz. consumer key and secret, signature method and RSA key. """ def __init__(self, signature_method, consumer_key, consumer_secret=None, rsa_key=None, requestor_id=None): """Initializes object with parameters required for using OAuth mechanism. NOTE: Though consumer_secret and rsa_key are optional, either of the two is required depending on the value of the signature_method. Args: signature_method: class which provides implementation for strategy class oauth.oauth.OAuthSignatureMethod. Signature method to be used for signing each request. Valid implementations are provided as the constants defined by gdata.auth.OAuthSignatureMethod. Currently they are gdata.auth.OAuthSignatureMethod.RSA_SHA1 and gdata.auth.OAuthSignatureMethod.HMAC_SHA1. Instead of passing in the strategy class, you may pass in a string for 'RSA_SHA1' or 'HMAC_SHA1'. If you plan to use OAuth on App Engine (or another WSGI environment) I recommend specifying signature method using a string (the only options are 'RSA_SHA1' and 'HMAC_SHA1'). In these environments there are sometimes issues with pickling an object in which a member references a class or function. Storing a string to refer to the signature method mitigates complications when pickling. consumer_key: string Domain identifying third_party web application. consumer_secret: string (optional) Secret generated during registration. Required only for HMAC_SHA1 signature method. rsa_key: string (optional) Private key required for RSA_SHA1 signature method. requestor_id: string (optional) User email adress to make requests on their behalf. This parameter should only be set when performing 2 legged OAuth requests. """ if (signature_method == OAuthSignatureMethod.RSA_SHA1 or signature_method == 'RSA_SHA1'): self.__signature_strategy = 'RSA_SHA1' elif (signature_method == OAuthSignatureMethod.HMAC_SHA1 or signature_method == 'HMAC_SHA1'): self.__signature_strategy = 'HMAC_SHA1' else: self.__signature_strategy = signature_method self.rsa_key = rsa_key self._consumer = oauth.OAuthConsumer(consumer_key, consumer_secret) self.requestor_id = requestor_id def __get_signature_method(self): if self.__signature_strategy == 'RSA_SHA1': return OAuthSignatureMethod.RSA_SHA1(self.rsa_key, None) elif self.__signature_strategy == 'HMAC_SHA1': return OAuthSignatureMethod.HMAC_SHA1() else: return self.__signature_strategy() def __set_signature_method(self, signature_method): if (signature_method == OAuthSignatureMethod.RSA_SHA1 or signature_method == 'RSA_SHA1'): self.__signature_strategy = 'RSA_SHA1' elif (signature_method == OAuthSignatureMethod.HMAC_SHA1 or signature_method == 'HMAC_SHA1'): self.__signature_strategy = 'HMAC_SHA1' else: self.__signature_strategy = signature_method _signature_method = property(__get_signature_method, __set_signature_method, doc="""Returns object capable of signing the request using RSA of HMAC. Replaces the _signature_method member to avoid pickle errors.""") def GetSignatureMethod(self): """Gets the OAuth signature method. Returns: object of supertype """ return self._signature_method def GetConsumer(self): """Gets the OAuth consumer. Returns: object of type """ return self._consumer class ClientLoginToken(atom.http_interface.GenericToken): """Stores the Authorization header in auth_header and adds to requests. This token will add it's Authorization header to an HTTP request as it is made. Ths token class is simple but some Token classes must calculate portions of the Authorization header based on the request being made, which is why the token is responsible for making requests via an http_client parameter. Args: auth_header: str The value for the Authorization header. scopes: list of str or atom.url.Url specifying the beginnings of URLs for which this token can be used. For example, if scopes contains 'http://example.com/foo', then this token can be used for a request to 'http://example.com/foo/bar' but it cannot be used for a request to 'http://example.com/baz' """ def __init__(self, auth_header=None, scopes=None): self.auth_header = auth_header self.scopes = scopes or [] def __str__(self): return self.auth_header def perform_request(self, http_client, operation, url, data=None, headers=None): """Sets the Authorization header and makes the HTTP request.""" if headers is None: headers = {'Authorization':self.auth_header} else: headers['Authorization'] = self.auth_header return http_client.request(operation, url, data=data, headers=headers) def get_token_string(self): """Removes PROGRAMMATIC_AUTH_LABEL to give just the token value.""" return self.auth_header[len(PROGRAMMATIC_AUTH_LABEL):] def set_token_string(self, token_string): self.auth_header = '%s%s' % (PROGRAMMATIC_AUTH_LABEL, token_string) def valid_for_scope(self, url): """Tells the caller if the token authorizes access to the desired URL. """ if isinstance(url, (str, unicode)): url = atom.url.parse_url(url) for scope in self.scopes: if scope == atom.token_store.SCOPE_ALL: return True if isinstance(scope, (str, unicode)): scope = atom.url.parse_url(scope) if scope == url: return True # Check the host and the path, but ignore the port and protocol. elif scope.host == url.host and not scope.path: return True elif scope.host == url.host and scope.path and not url.path: continue elif scope.host == url.host and url.path.startswith(scope.path): return True return False class AuthSubToken(ClientLoginToken): def get_token_string(self): """Removes AUTHSUB_AUTH_LABEL to give just the token value.""" return self.auth_header[len(AUTHSUB_AUTH_LABEL):] def set_token_string(self, token_string): self.auth_header = '%s%s' % (AUTHSUB_AUTH_LABEL, token_string) class OAuthToken(atom.http_interface.GenericToken): """Stores the token key, token secret and scopes for which token is valid. This token adds the authorization header to each request made. It re-calculates authorization header for every request since the OAuth signature to be added to the authorization header is dependent on the request parameters. Attributes: key: str The value for the OAuth token i.e. token key. secret: str The value for the OAuth token secret. scopes: list of str or atom.url.Url specifying the beginnings of URLs for which this token can be used. For example, if scopes contains 'http://example.com/foo', then this token can be used for a request to 'http://example.com/foo/bar' but it cannot be used for a request to 'http://example.com/baz' oauth_input_params: OAuthInputParams OAuth input parameters. """ def __init__(self, key=None, secret=None, scopes=None, oauth_input_params=None): self.key = key self.secret = secret self.scopes = scopes or [] self.oauth_input_params = oauth_input_params def __str__(self): return self.get_token_string() def get_token_string(self): """Returns the token string. The token string returned is of format oauth_token=[0]&oauth_token_secret=[1], where [0] and [1] are some strings. Returns: A token string of format oauth_token=[0]&oauth_token_secret=[1], where [0] and [1] are some strings. If self.secret is absent, it just returns oauth_token=[0]. If self.key is absent, it just returns oauth_token_secret=[1]. If both are absent, it returns None. """ if self.key and self.secret: return urllib.urlencode({'oauth_token': self.key, 'oauth_token_secret': self.secret}) elif self.key: return 'oauth_token=%s' % self.key elif self.secret: return 'oauth_token_secret=%s' % self.secret else: return None def set_token_string(self, token_string): """Sets the token key and secret from the token string. Args: token_string: str Token string of form oauth_token=[0]&oauth_token_secret=[1]. If oauth_token is not present, self.key will be None. If oauth_token_secret is not present, self.secret will be None. """ token_params = cgi.parse_qs(token_string, keep_blank_values=False) if 'oauth_token' in token_params: self.key = token_params['oauth_token'][0] if 'oauth_token_secret' in token_params: self.secret = token_params['oauth_token_secret'][0] def GetAuthHeader(self, http_method, http_url, realm=''): """Get the authentication header. Args: http_method: string HTTP method i.e. operation e.g. GET, POST, PUT, etc. http_url: string or atom.url.Url HTTP URL to which request is made. realm: string (default='') realm parameter to be included in the authorization header. Returns: dict Header to be sent with every subsequent request after authentication. """ if isinstance(http_url, types.StringTypes): http_url = atom.url.parse_url(http_url) header = None token = None if self.key or self.secret: token = oauth.OAuthToken(self.key, self.secret) oauth_request = oauth.OAuthRequest.from_consumer_and_token( self.oauth_input_params.GetConsumer(), token=token, http_url=str(http_url), http_method=http_method, parameters=http_url.params) oauth_request.sign_request(self.oauth_input_params.GetSignatureMethod(), self.oauth_input_params.GetConsumer(), token) header = oauth_request.to_header(realm=realm) header['Authorization'] = header['Authorization'].replace('+', '%2B') return header def perform_request(self, http_client, operation, url, data=None, headers=None): """Sets the Authorization header and makes the HTTP request.""" if not headers: headers = {} if self.oauth_input_params.requestor_id: url.params['xoauth_requestor_id'] = self.oauth_input_params.requestor_id headers.update(self.GetAuthHeader(operation, url)) return http_client.request(operation, url, data=data, headers=headers) def valid_for_scope(self, url): if isinstance(url, (str, unicode)): url = atom.url.parse_url(url) for scope in self.scopes: if scope == atom.token_store.SCOPE_ALL: return True if isinstance(scope, (str, unicode)): scope = atom.url.parse_url(scope) if scope == url: return True # Check the host and the path, but ignore the port and protocol. elif scope.host == url.host and not scope.path: return True elif scope.host == url.host and scope.path and not url.path: continue elif scope.host == url.host and url.path.startswith(scope.path): return True return False class SecureAuthSubToken(AuthSubToken): """Stores the rsa private key, token, and scopes for the secure AuthSub token. This token adds the authorization header to each request made. It re-calculates authorization header for every request since the secure AuthSub signature to be added to the authorization header is dependent on the request parameters. Attributes: rsa_key: string The RSA private key in PEM format that the token will use to sign requests token_string: string (optional) The value for the AuthSub token. scopes: list of str or atom.url.Url specifying the beginnings of URLs for which this token can be used. For example, if scopes contains 'http://example.com/foo', then this token can be used for a request to 'http://example.com/foo/bar' but it cannot be used for a request to 'http://example.com/baz' """ def __init__(self, rsa_key, token_string=None, scopes=None): self.rsa_key = keyfactory.parsePEMKey(rsa_key) self.token_string = token_string or '' self.scopes = scopes or [] def __str__(self): return self.get_token_string() def get_token_string(self): return str(self.token_string) def set_token_string(self, token_string): self.token_string = token_string def GetAuthHeader(self, http_method, http_url): """Generates the Authorization header. The form of the secure AuthSub Authorization header is Authorization: AuthSub token="token" sigalg="sigalg" data="data" sig="sig" and data represents a string in the form data = http_method http_url timestamp nonce Args: http_method: string HTTP method i.e. operation e.g. GET, POST, PUT, etc. http_url: string or atom.url.Url HTTP URL to which request is made. Returns: dict Header to be sent with every subsequent request after authentication. """ timestamp = int(math.floor(time.time())) nonce = '%lu' % random.randrange(1, 2**64) data = '%s %s %d %s' % (http_method, str(http_url), timestamp, nonce) sig = cryptomath.bytesToBase64(self.rsa_key.hashAndSign(data)) header = {'Authorization': '%s"%s" data="%s" sig="%s" sigalg="rsa-sha1"' % (AUTHSUB_AUTH_LABEL, self.token_string, data, sig)} return header def perform_request(self, http_client, operation, url, data=None, headers=None): """Sets the Authorization header and makes the HTTP request.""" if not headers: headers = {} headers.update(self.GetAuthHeader(operation, url)) return http_client.request(operation, url, data=data, headers=headers) gdata-2.0.18/src/gdata/sites/0000750036466300116100000000000012156625015015730 5ustar afshareng00000000000000gdata-2.0.18/src/gdata/sites/data.py0000640036466300116100000002244712156622363017230 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright 2009 Google Inc. All Rights Reserved. # # 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. """Data model classes for parsing and generating XML for the Sites Data API.""" __author__ = 'e.bidelman (Eric Bidelman)' import atom.core import atom.data import gdata.acl.data import gdata.data # XML Namespaces used in Google Sites entities. SITES_NAMESPACE = 'http://schemas.google.com/sites/2008' SITES_TEMPLATE = '{http://schemas.google.com/sites/2008}%s' SPREADSHEETS_NAMESPACE = 'http://schemas.google.com/spreadsheets/2006' SPREADSHEETS_TEMPLATE = '{http://schemas.google.com/spreadsheets/2006}%s' DC_TERMS_TEMPLATE = '{http://purl.org/dc/terms}%s' THR_TERMS_TEMPLATE = '{http://purl.org/syndication/thread/1.0}%s' XHTML_NAMESPACE = 'http://www.w3.org/1999/xhtml' XHTML_TEMPLATE = '{http://www.w3.org/1999/xhtml}%s' SITES_PARENT_LINK_REL = SITES_NAMESPACE + '#parent' SITES_REVISION_LINK_REL = SITES_NAMESPACE + '#revision' SITES_SOURCE_LINK_REL = SITES_NAMESPACE + '#source' SITES_TEMPLATE_LINK_REL = SITES_NAMESPACE + '#template' SITES_KIND_SCHEME = 'http://schemas.google.com/g/2005#kind' ANNOUNCEMENT_KIND_TERM = SITES_NAMESPACE + '#announcement' ANNOUNCEMENT_PAGE_KIND_TERM = SITES_NAMESPACE + '#announcementspage' ATTACHMENT_KIND_TERM = SITES_NAMESPACE + '#attachment' COMMENT_KIND_TERM = SITES_NAMESPACE + '#comment' FILECABINET_KIND_TERM = SITES_NAMESPACE + '#filecabinet' LISTITEM_KIND_TERM = SITES_NAMESPACE + '#listitem' LISTPAGE_KIND_TERM = SITES_NAMESPACE + '#listpage' WEBPAGE_KIND_TERM = SITES_NAMESPACE + '#webpage' WEBATTACHMENT_KIND_TERM = SITES_NAMESPACE + '#webattachment' FOLDER_KIND_TERM = SITES_NAMESPACE + '#folder' TAG_KIND_TERM = SITES_NAMESPACE + '#tag' SUPPORT_KINDS = [ 'announcement', 'announcementspage', 'attachment', 'comment', 'filecabinet', 'listitem', 'listpage', 'webpage', 'webattachment', 'tag' ] class Revision(atom.core.XmlElement): """Google Sites .""" _qname = SITES_TEMPLATE % 'revision' class PageName(atom.core.XmlElement): """Google Sites .""" _qname = SITES_TEMPLATE % 'pageName' class SiteName(atom.core.XmlElement): """Google Sites .""" _qname = SITES_TEMPLATE % 'siteName' class Theme(atom.core.XmlElement): """Google Sites .""" _qname = SITES_TEMPLATE % 'theme' class Deleted(atom.core.XmlElement): """Google Sites .""" _qname = gdata.data.GDATA_TEMPLATE % 'deleted' class Publisher(atom.core.XmlElement): """Google Sites .""" _qname = DC_TERMS_TEMPLATE % 'publisher' class Worksheet(atom.core.XmlElement): """Google Sites List Page .""" _qname = SPREADSHEETS_TEMPLATE % 'worksheet' name = 'name' class Header(atom.core.XmlElement): """Google Sites List Page .""" _qname = SPREADSHEETS_TEMPLATE % 'header' row = 'row' class Column(atom.core.XmlElement): """Google Sites List Page .""" _qname = SPREADSHEETS_TEMPLATE % 'column' index = 'index' name = 'name' class Data(atom.core.XmlElement): """Google Sites List Page .""" _qname = SPREADSHEETS_TEMPLATE % 'data' startRow = 'startRow' column = [Column] class Field(atom.core.XmlElement): """Google Sites List Item .""" _qname = SPREADSHEETS_TEMPLATE % 'field' index = 'index' name = 'name' class InReplyTo(atom.core.XmlElement): """Google Sites List Item .""" _qname = THR_TERMS_TEMPLATE % 'in-reply-to' href = 'href' ref = 'ref' source = 'source' type = 'type' class Content(atom.data.Content): """Google Sites version of that encapsulates XHTML.""" def __init__(self, html=None, type=None, **kwargs): if type is None and html: type = 'xhtml' super(Content, self).__init__(type=type, **kwargs) if html is not None: self.html = html def _get_html(self): if self.children: return self.children[0] else: return '' def _set_html(self, html): if not html: self.children = [] return if type(html) == str: html = atom.core.parse(html) if not html.namespace: html.namespace = XHTML_NAMESPACE self.children = [html] html = property(_get_html, _set_html) class Summary(atom.data.Summary): """Google Sites version of .""" def __init__(self, html=None, type=None, text=None, **kwargs): if type is None and html: type = 'xhtml' super(Summary, self).__init__(type=type, text=text, **kwargs) if html is not None: self.html = html def _get_html(self): if self.children: return self.children[0] else: return '' def _set_html(self, html): if not html: self.children = [] return if type(html) == str: html = atom.core.parse(html) if not html.namespace: html.namespace = XHTML_NAMESPACE self.children = [html] html = property(_get_html, _set_html) class BaseSiteEntry(gdata.data.GDEntry): """Google Sites Entry.""" def __init__(self, kind=None, **kwargs): super(BaseSiteEntry, self).__init__(**kwargs) if kind is not None: self.category.append( atom.data.Category(scheme=SITES_KIND_SCHEME, term='%s#%s' % (SITES_NAMESPACE, kind), label=kind)) def __find_category_scheme(self, scheme): for category in self.category: if category.scheme == scheme: return category return None def kind(self): kind = self.__find_category_scheme(SITES_KIND_SCHEME) if kind is not None: return kind.term[len(SITES_NAMESPACE) + 1:] else: return None Kind = kind def get_node_id(self): return self.id.text[self.id.text.rfind('/') + 1:] GetNodeId = get_node_id def find_parent_link(self): return self.find_url(SITES_PARENT_LINK_REL) FindParentLink = find_parent_link def is_deleted(self): return self.deleted is not None IsDeleted = is_deleted class ContentEntry(BaseSiteEntry): """Google Sites Content Entry.""" content = Content deleted = Deleted publisher = Publisher in_reply_to = InReplyTo worksheet = Worksheet header = Header data = Data field = [Field] revision = Revision page_name = PageName feed_link = gdata.data.FeedLink def find_revison_link(self): return self.find_url(SITES_REVISION_LINK_REL) FindRevisionLink = find_revison_link class ContentFeed(gdata.data.GDFeed): """Google Sites Content Feed. The Content feed is a feed containing the current, editable site content. """ entry = [ContentEntry] def __get_entry_type(self, kind): matches = [] for entry in self.entry: if entry.Kind() == kind: matches.append(entry) return matches def get_announcements(self): return self.__get_entry_type('announcement') GetAnnouncements = get_announcements def get_announcement_pages(self): return self.__get_entry_type('announcementspage') GetAnnouncementPages = get_announcement_pages def get_attachments(self): return self.__get_entry_type('attachment') GetAttachments = get_attachments def get_comments(self): return self.__get_entry_type('comment') GetComments = get_comments def get_file_cabinets(self): return self.__get_entry_type('filecabinet') GetFileCabinets = get_file_cabinets def get_list_items(self): return self.__get_entry_type('listitem') GetListItems = get_list_items def get_list_pages(self): return self.__get_entry_type('listpage') GetListPages = get_list_pages def get_webpages(self): return self.__get_entry_type('webpage') GetWebpages = get_webpages def get_webattachments(self): return self.__get_entry_type('webattachment') GetWebattachments = get_webattachments class ActivityEntry(BaseSiteEntry): """Google Sites Activity Entry.""" summary = Summary class ActivityFeed(gdata.data.GDFeed): """Google Sites Activity Feed. The Activity feed is a feed containing recent Site activity. """ entry = [ActivityEntry] class RevisionEntry(BaseSiteEntry): """Google Sites Revision Entry.""" content = Content class RevisionFeed(gdata.data.GDFeed): """Google Sites Revision Feed. The Activity feed is a feed containing recent Site activity. """ entry = [RevisionEntry] class SiteEntry(gdata.data.GDEntry): """Google Sites Site Feed Entry.""" site_name = SiteName theme = Theme def find_source_link(self): return self.find_url(SITES_SOURCE_LINK_REL) FindSourceLink = find_source_link class SiteFeed(gdata.data.GDFeed): """Google Sites Site Feed. The Site feed can be used to list a user's sites and create new sites. """ entry = [SiteEntry] class AclEntry(gdata.acl.data.AclEntry): """Google Sites ACL Entry.""" class AclFeed(gdata.acl.data.AclFeed): """Google Sites ACL Feed. The ACL feed can be used to modify the sharing permissions of a Site. """ entry = [AclEntry] gdata-2.0.18/src/gdata/sites/__init__.py0000750036466300116100000000000012156622363020035 0ustar afshareng00000000000000gdata-2.0.18/src/gdata/sites/client.py0000750036466300116100000004507412156622363017600 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright 2009 Google Inc. All Rights Reserved. # # 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. """SitesClient extends gdata.client.GDClient to streamline Sites API calls.""" __author__ = 'e.bidelman (Eric Bidelman)' import atom.data import gdata.client import gdata.sites.data import gdata.gauth # Feed URI templates CONTENT_FEED_TEMPLATE = '/feeds/content/%s/%s/' REVISION_FEED_TEMPLATE = '/feeds/revision/%s/%s/' ACTIVITY_FEED_TEMPLATE = '/feeds/activity/%s/%s/' SITE_FEED_TEMPLATE = '/feeds/site/%s/' ACL_FEED_TEMPLATE = '/feeds/acl/site/%s/%s/' class SitesClient(gdata.client.GDClient): """Client extension for the Google Sites API service.""" host = 'sites.google.com' # default server for the API domain = 'site' # default site domain name api_version = '1.1' # default major version for the service. auth_service = 'jotspot' auth_scopes = gdata.gauth.AUTH_SCOPES['jotspot'] ssl = True def __init__(self, site=None, domain=None, auth_token=None, **kwargs): """Constructs a new client for the Sites API. Args: site: string (optional) Name (webspace) of the Google Site domain: string (optional) Domain of the (Google Apps hosted) Site. If no domain is given, the Site is assumed to be a consumer Google Site, in which case the value 'site' is used. auth_token: (optional) gdata.gauth.ClientLoginToken, AuthSubToken, or OAuthToken which authorizes this client to edit the user's data. kwargs: The other parameters to pass to gdata.client.GDClient constructor. """ gdata.client.GDClient.__init__(self, auth_token=auth_token, **kwargs) self.site = site if domain is not None: self.domain = domain def __make_kind_category(self, label): if label is None: return None return atom.data.Category( scheme=gdata.sites.data.SITES_KIND_SCHEME, term='%s#%s' % (gdata.sites.data.SITES_NAMESPACE, label), label=label) __MakeKindCategory = __make_kind_category def __upload(self, entry, media_source, auth_token=None, **kwargs): """Uploads an attachment file to the Sites API. Args: entry: gdata.sites.data.ContentEntry The Atom XML to include. media_source: gdata.data.MediaSource The file payload to be uploaded. auth_token: (optional) gdata.gauth.ClientLoginToken, AuthSubToken, or OAuthToken which authorizes this client to edit the user's data. kwargs: Other parameters to pass to gdata.client.post(). Returns: The created entry. """ uri = self.make_content_feed_uri() return self.post(entry, uri, media_source=media_source, auth_token=auth_token, **kwargs) def _get_file_content(self, uri): """Fetches the file content from the specified URI. Args: uri: string The full URL to fetch the file contents from. Returns: The binary file content. Raises: gdata.client.RequestError: on error response from server. """ server_response = self.request('GET', uri) if server_response.status != 200: raise gdata.client.RequestError, {'status': server_response.status, 'reason': server_response.reason, 'body': server_response.read()} return server_response.read() _GetFileContent = _get_file_content def make_content_feed_uri(self): return CONTENT_FEED_TEMPLATE % (self.domain, self.site) MakeContentFeedUri = make_content_feed_uri def make_revision_feed_uri(self): return REVISION_FEED_TEMPLATE % (self.domain, self.site) MakeRevisionFeedUri = make_revision_feed_uri def make_activity_feed_uri(self): return ACTIVITY_FEED_TEMPLATE % (self.domain, self.site) MakeActivityFeedUri = make_activity_feed_uri def make_site_feed_uri(self, site_name=None): if site_name is not None: return (SITE_FEED_TEMPLATE % self.domain) + site_name else: return SITE_FEED_TEMPLATE % self.domain MakeSiteFeedUri = make_site_feed_uri def make_acl_feed_uri(self): return ACL_FEED_TEMPLATE % (self.domain, self.site) MakeAclFeedUri = make_acl_feed_uri def get_content_feed(self, uri=None, auth_token=None, **kwargs): """Retrieves the content feed containing the current state of site. Args: uri: string (optional) A full URI to query the Content feed with. auth_token: (optional) gdata.gauth.ClientLoginToken, AuthSubToken, or OAuthToken which authorizes this client to edit the user's data. kwargs: Other parameters to pass to self.get_feed(). Returns: gdata.sites.data.ContentFeed """ if uri is None: uri = self.make_content_feed_uri() return self.get_feed(uri, desired_class=gdata.sites.data.ContentFeed, auth_token=auth_token, **kwargs) GetContentFeed = get_content_feed def get_revision_feed(self, entry_or_uri_or_id, auth_token=None, **kwargs): """Retrieves the revision feed containing the revision history for a node. Args: entry_or_uri_or_id: string or gdata.sites.data.ContentEntry A full URI, content entry node ID, or a content entry object of the entry to retrieve revision information for. auth_token: (optional) gdata.gauth.ClientLoginToken, AuthSubToken, or OAuthToken which authorizes this client to edit the user's data. kwargs: Other parameters to pass to self.get_feed(). Returns: gdata.sites.data.RevisionFeed """ uri = self.make_revision_feed_uri() if isinstance(entry_or_uri_or_id, gdata.sites.data.ContentEntry): uri = entry_or_uri_or_id.FindRevisionLink() elif entry_or_uri_or_id.find('/') == -1: uri += entry_or_uri_or_id else: uri = entry_or_uri_or_id return self.get_feed(uri, desired_class=gdata.sites.data.RevisionFeed, auth_token=auth_token, **kwargs) GetRevisionFeed = get_revision_feed def get_activity_feed(self, uri=None, auth_token=None, **kwargs): """Retrieves the activity feed containing recent Site activity. Args: uri: string (optional) A full URI to query the Activity feed. auth_token: (optional) gdata.gauth.ClientLoginToken, AuthSubToken, or OAuthToken which authorizes this client to edit the user's data. kwargs: Other parameters to pass to self.get_feed(). Returns: gdata.sites.data.ActivityFeed """ if uri is None: uri = self.make_activity_feed_uri() return self.get_feed(uri, desired_class=gdata.sites.data.ActivityFeed, auth_token=auth_token, **kwargs) GetActivityFeed = get_activity_feed def get_site_feed(self, uri=None, auth_token=None, **kwargs): """Retrieves the site feed containing a list of sites a user has access to. Args: uri: string (optional) A full URI to query the site feed. auth_token: (optional) gdata.gauth.ClientLoginToken, AuthSubToken, or OAuthToken which authorizes this client to edit the user's data. kwargs: Other parameters to pass to self.get_feed(). Returns: gdata.sites.data.SiteFeed """ if uri is None: uri = self.make_site_feed_uri() return self.get_feed(uri, desired_class=gdata.sites.data.SiteFeed, auth_token=auth_token, **kwargs) GetSiteFeed = get_site_feed def get_acl_feed(self, uri=None, auth_token=None, **kwargs): """Retrieves the acl feed containing a site's sharing permissions. Args: uri: string (optional) A full URI to query the acl feed. auth_token: (optional) gdata.gauth.ClientLoginToken, AuthSubToken, or OAuthToken which authorizes this client to edit the user's data. kwargs: Other parameters to pass to self.get_feed(). Returns: gdata.sites.data.AclFeed """ if uri is None: uri = self.make_acl_feed_uri() return self.get_feed(uri, desired_class=gdata.sites.data.AclFeed, auth_token=auth_token, **kwargs) GetAclFeed = get_acl_feed def create_site(self, title, description=None, source_site=None, theme=None, uri=None, auth_token=None, **kwargs): """Creates a new Google Site. Note: This feature is only available to Google Apps domains. Args: title: string Title for the site. description: string (optional) A description/summary for the site. source_site: string (optional) The site feed URI of the site to copy. This parameter should only be specified when copying a site. theme: string (optional) The name of the theme to create the site with. uri: string (optional) A full site feed URI to override where the site is created/copied. By default, the site will be created under the currently set domain (e.g. self.domain). auth_token: (optional) gdata.gauth.ClientLoginToken, AuthSubToken, or OAuthToken which authorizes this client to edit the user's data. kwargs: Other parameters to pass to gdata.client.post(). Returns: gdata.sites.data.SiteEntry of the created site. """ new_entry = gdata.sites.data.SiteEntry(title=atom.data.Title(text=title)) if description is not None: new_entry.summary = gdata.sites.data.Summary(text=description) # Add the source link if we're making a copy of a site. if source_site is not None: source_link = atom.data.Link(rel=gdata.sites.data.SITES_SOURCE_LINK_REL, type='application/atom+xml', href=source_site) new_entry.link.append(source_link) if theme is not None: new_entry.theme = gdata.sites.data.Theme(text=theme) if uri is None: uri = self.make_site_feed_uri() return self.post(new_entry, uri, auth_token=auth_token, **kwargs) CreateSite = create_site def create_page(self, kind, title, html='', page_name=None, parent=None, auth_token=None, template=None, **kwargs): """Creates a new page (specified by kind) on a Google Site. Args: kind: string The type of page/item to create. For example, webpage, listpage, comment, announcementspage, filecabinet, etc. The full list of supported kinds can be found in gdata.sites.gdata.SUPPORT_KINDS. title: string Title for the page. html: string (optional) XHTML for the page's content body. page_name: string (optional) The URL page name to set. If not set, the title will be normalized and used as the page's URL path. parent: string or gdata.sites.data.ContentEntry (optional) The parent entry or parent link url to create the page under. auth_token: (optional) gdata.gauth.ClientLoginToken, AuthSubToken, or OAuthToken which authorizes this client to edit the user's data. template: string or gdata.sites.data.ContentEntry (optional) Create page using the given template. Any content elements will be discarded as they are not valid when creating a page from a template. kwargs: Other parameters to pass to gdata.client.post(). Returns: gdata.sites.data.ContentEntry of the created page. """ content = gdata.sites.data.Content(text=html) if template is not None: content = None new_entry = gdata.sites.data.ContentEntry( title=atom.data.Title(text=title), kind=kind, content=content) if page_name is not None: new_entry.page_name = gdata.sites.data.PageName(text=page_name) # Add parent link to entry if it should be uploaded as a subpage. if isinstance(parent, gdata.sites.data.ContentEntry): parent_link = atom.data.Link(rel=gdata.sites.data.SITES_PARENT_LINK_REL, type='application/atom+xml', href=parent.GetSelfLink().href) new_entry.link.append(parent_link) elif parent is not None: parent_link = atom.data.Link(rel=gdata.sites.data.SITES_PARENT_LINK_REL, type='application/atom+xml', href=parent) new_entry.link.append(parent_link) # Add template link to entry if present. if isinstance(template, gdata.sites.data.ContentEntry): template_link = atom.data.Link( rel=gdata.sites.data.SITES_TEMPLATE_LINK_REL, type='application/atom+xml', href=template.GetSelfLink().href) new_entry.link.append(template_link) elif template is not None: template_link = atom.data.Link( rel=gdata.sites.data.SITES_TEMPLATE_LINK_REL, type='application/atom+xml', href=template) new_entry.link.append(template_link) return self.post(new_entry, self.make_content_feed_uri(), auth_token=auth_token, **kwargs) CreatePage = create_page def create_webattachment(self, src, content_type, title, parent, description=None, auth_token=None, **kwargs): """Creates a new webattachment within a filecabinet. Args: src: string The url of the web attachment. content_type: string The MIME type of the web attachment. title: string The title to name the web attachment. parent: string or gdata.sites.data.ContentEntry (optional) The parent entry or url of the filecabinet to create the attachment under. description: string (optional) A summary/description for the attachment. auth_token: (optional) gdata.gauth.ClientLoginToken, AuthSubToken, or OAuthToken which authorizes this client to edit the user's data. kwargs: Other parameters to pass to gdata.client.post(). Returns: gdata.sites.data.ContentEntry of the created page. """ new_entry = gdata.sites.data.ContentEntry( title=atom.data.Title(text=title), kind='webattachment', content=gdata.sites.data.Content(src=src, type=content_type)) if isinstance(parent, gdata.sites.data.ContentEntry): link = atom.data.Link(rel=gdata.sites.data.SITES_PARENT_LINK_REL, type='application/atom+xml', href=parent.GetSelfLink().href) elif parent is not None: link = atom.data.Link(rel=gdata.sites.data.SITES_PARENT_LINK_REL, type='application/atom+xml', href=parent) new_entry.link.append(link) # Add file decription if it was specified if description is not None: new_entry.summary = gdata.sites.data.Summary(type='text', text=description) return self.post(new_entry, self.make_content_feed_uri(), auth_token=auth_token, **kwargs) CreateWebAttachment = create_webattachment def upload_attachment(self, file_handle, parent, content_type=None, title=None, description=None, folder_name=None, auth_token=None, **kwargs): """Uploads an attachment to a parent page. Args: file_handle: MediaSource or string A gdata.data.MediaSource object containing the file to be uploaded or the full path name to the file on disk. parent: gdata.sites.data.ContentEntry or string The parent page to upload the file to or the full URI of the entry's self link. content_type: string (optional) The MIME type of the file (e.g 'application/pdf'). This should be provided if file is not a MediaSource object. title: string (optional) The title to name the attachment. If not included, the filepath or media source's filename is used. description: string (optional) A summary/description for the attachment. folder_name: string (optional) The name of an existing folder to upload the attachment to. This only applies when the parent parameter points to a filecabinet entry. auth_token: (optional) gdata.gauth.ClientLoginToken, AuthSubToken, or OAuthToken which authorizes this client to edit the user's data. kwargs: Other parameters to pass to self.__upload(). Returns: A gdata.sites.data.ContentEntry containing information about the created attachment. """ if isinstance(parent, gdata.sites.data.ContentEntry): link = atom.data.Link(rel=gdata.sites.data.SITES_PARENT_LINK_REL, type='application/atom+xml', href=parent.GetSelfLink().href) else: link = atom.data.Link(rel=gdata.sites.data.SITES_PARENT_LINK_REL, type='application/atom+xml', href=parent) if not isinstance(file_handle, gdata.data.MediaSource): ms = gdata.data.MediaSource(file_path=file_handle, content_type=content_type) else: ms = file_handle # If no title specified, use the file name if title is None: title = ms.file_name new_entry = gdata.sites.data.ContentEntry(kind='attachment') new_entry.title = atom.data.Title(text=title) new_entry.link.append(link) # Add file decription if it was specified if description is not None: new_entry.summary = gdata.sites.data.Summary(type='text', text=description) # Upload the attachment to a filecabinet folder? if parent.Kind() == 'filecabinet' and folder_name is not None: folder_category = atom.data.Category( scheme=gdata.sites.data.FOLDER_KIND_TERM, term=folder_name) new_entry.category.append(folder_category) return self.__upload(new_entry, ms, auth_token=auth_token, **kwargs) UploadAttachment = upload_attachment def download_attachment(self, uri_or_entry, file_path): """Downloads an attachment file to disk. Args: uri_or_entry: string The full URL to download the file from. file_path: string The full path to save the file to. Raises: gdata.client.RequestError: on error response from server. """ uri = uri_or_entry if isinstance(uri_or_entry, gdata.sites.data.ContentEntry): uri = uri_or_entry.content.src f = open(file_path, 'wb') try: f.write(self._get_file_content(uri)) except gdata.client.RequestError, e: f.close() raise e f.flush() f.close() DownloadAttachment = download_attachment gdata-2.0.18/src/gdata/apps/0000750036466300116100000000000012156625015015544 5ustar afshareng00000000000000gdata-2.0.18/src/gdata/apps/migration/0000750036466300116100000000000012156625015017535 5ustar afshareng00000000000000gdata-2.0.18/src/gdata/apps/migration/__init__.py0000640036466300116100000002060612156622363021656 0ustar afshareng00000000000000#!/usr/bin/python2.4 # # Copyright 2008 Google Inc. All Rights Reserved. # # 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. """Contains objects used with Google Apps.""" __author__ = 'google-apps-apis@googlegroups.com' import atom import gdata # XML namespaces which are often used in Google Apps entity. APPS_NAMESPACE = 'http://schemas.google.com/apps/2006' APPS_TEMPLATE = '{http://schemas.google.com/apps/2006}%s' class Rfc822Msg(atom.AtomBase): """The Migration rfc822Msg element.""" _tag = 'rfc822Msg' _namespace = APPS_NAMESPACE _children = atom.AtomBase._children.copy() _attributes = atom.AtomBase._attributes.copy() _attributes['encoding'] = 'encoding' def __init__(self, extension_elements=None, extension_attributes=None, text=None): self.text = text self.encoding = 'base64' self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} def Rfc822MsgFromString(xml_string): """Parse in the Rrc822 message from the XML definition.""" return atom.CreateClassFromXMLString(Rfc822Msg, xml_string) class MailItemProperty(atom.AtomBase): """The Migration mailItemProperty element.""" _tag = 'mailItemProperty' _namespace = APPS_NAMESPACE _children = atom.AtomBase._children.copy() _attributes = atom.AtomBase._attributes.copy() _attributes['value'] = 'value' def __init__(self, value=None, extension_elements=None, extension_attributes=None, text=None): self.value = value self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} def MailItemPropertyFromString(xml_string): """Parse in the MailItemProperiy from the XML definition.""" return atom.CreateClassFromXMLString(MailItemProperty, xml_string) class Label(atom.AtomBase): """The Migration label element.""" _tag = 'label' _namespace = APPS_NAMESPACE _children = atom.AtomBase._children.copy() _attributes = atom.AtomBase._attributes.copy() _attributes['labelName'] = 'label_name' def __init__(self, label_name=None, extension_elements=None, extension_attributes=None, text=None): self.label_name = label_name self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} def LabelFromString(xml_string): """Parse in the mailItemProperty from the XML definition.""" return atom.CreateClassFromXMLString(Label, xml_string) class MailEntry(gdata.GDataEntry): """A Google Migration flavor of an Atom Entry.""" _tag = 'entry' _namespace = atom.ATOM_NAMESPACE _children = gdata.GDataEntry._children.copy() _attributes = gdata.GDataEntry._attributes.copy() _children['{%s}rfc822Msg' % APPS_NAMESPACE] = ('rfc822_msg', Rfc822Msg) _children['{%s}mailItemProperty' % APPS_NAMESPACE] = ('mail_item_property', [MailItemProperty]) _children['{%s}label' % APPS_NAMESPACE] = ('label', [Label]) def __init__(self, author=None, category=None, content=None, atom_id=None, link=None, published=None, title=None, updated=None, rfc822_msg=None, mail_item_property=None, label=None, extended_property=None, extension_elements=None, extension_attributes=None, text=None): gdata.GDataEntry.__init__(self, author=author, category=category, content=content, atom_id=atom_id, link=link, published=published, title=title, updated=updated) self.rfc822_msg = rfc822_msg self.mail_item_property = mail_item_property self.label = label self.extended_property = extended_property or [] self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} def MailEntryFromString(xml_string): """Parse in the MailEntry from the XML definition.""" return atom.CreateClassFromXMLString(MailEntry, xml_string) class BatchMailEntry(gdata.BatchEntry): """A Google Migration flavor of an Atom Entry.""" _tag = gdata.BatchEntry._tag _namespace = gdata.BatchEntry._namespace _children = gdata.BatchEntry._children.copy() _attributes = gdata.BatchEntry._attributes.copy() _children['{%s}rfc822Msg' % APPS_NAMESPACE] = ('rfc822_msg', Rfc822Msg) _children['{%s}mailItemProperty' % APPS_NAMESPACE] = ('mail_item_property', [MailItemProperty]) _children['{%s}label' % APPS_NAMESPACE] = ('label', [Label]) def __init__(self, author=None, category=None, content=None, atom_id=None, link=None, published=None, title=None, updated=None, rfc822_msg=None, mail_item_property=None, label=None, batch_operation=None, batch_id=None, batch_status=None, extended_property=None, extension_elements=None, extension_attributes=None, text=None): gdata.BatchEntry.__init__(self, author=author, category=category, content=content, atom_id=atom_id, link=link, published=published, batch_operation=batch_operation, batch_id=batch_id, batch_status=batch_status, title=title, updated=updated) self.rfc822_msg = rfc822_msg or None self.mail_item_property = mail_item_property or [] self.label = label or [] self.extended_property = extended_property or [] self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} def BatchMailEntryFromString(xml_string): """Parse in the BatchMailEntry from the XML definition.""" return atom.CreateClassFromXMLString(BatchMailEntry, xml_string) class BatchMailEventFeed(gdata.BatchFeed): """A Migration event feed flavor of an Atom Feed.""" _tag = gdata.BatchFeed._tag _namespace = gdata.BatchFeed._namespace _children = gdata.BatchFeed._children.copy() _attributes = gdata.BatchFeed._attributes.copy() _children['{%s}entry' % atom.ATOM_NAMESPACE] = ('entry', [BatchMailEntry]) def __init__(self, author=None, category=None, contributor=None, generator=None, icon=None, atom_id=None, link=None, logo=None, rights=None, subtitle=None, title=None, updated=None, entry=None, total_results=None, start_index=None, items_per_page=None, interrupted=None, extension_elements=None, extension_attributes=None, text=None): gdata.BatchFeed.__init__(self, author=author, category=category, contributor=contributor, generator=generator, icon=icon, atom_id=atom_id, link=link, logo=logo, rights=rights, subtitle=subtitle, title=title, updated=updated, entry=entry, total_results=total_results, start_index=start_index, items_per_page=items_per_page, interrupted=interrupted, extension_elements=extension_elements, extension_attributes=extension_attributes, text=text) class MailEntryProperties(object): """Represents a mail message and its attributes.""" def __init__(self, mail_message=None, mail_item_properties=None, mail_labels=None, identifier=None): self.mail_message = mail_message self.mail_item_properties = mail_item_properties or [] self.mail_labels = mail_labels or [] self.identifier = identifier def BatchMailEventFeedFromString(xml_string): """Parse in the BatchMailEventFeed from the XML definition.""" return atom.CreateClassFromXMLString(BatchMailEventFeed, xml_string) gdata-2.0.18/src/gdata/apps/migration/service.py0000640036466300116100000001704212156622363021557 0ustar afshareng00000000000000#!/usr/bin/python2.4 # # Copyright 2008 Google Inc. All Rights Reserved. # # 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. """Contains the methods to import mail via Google Apps Email Migration API. MigrationService: Provides methods to import mail. """ __author__ = ('google-apps-apis@googlegroups.com', 'pti@google.com (Prashant Tiwari)') import base64 import threading import time from atom.service import deprecation from gdata.apps import migration from gdata.apps.migration import MailEntryProperties import gdata.apps.service import gdata.service API_VER = '2.0' class MigrationService(gdata.apps.service.AppsService): """Client for the EMAPI migration service. Use either ImportMail to import one message at a time, or AddMailEntry and ImportMultipleMails to import a bunch of messages at a time. """ def __init__(self, email=None, password=None, domain=None, source=None, server='apps-apis.google.com', additional_headers=None): gdata.apps.service.AppsService.__init__( self, email=email, password=password, domain=domain, source=source, server=server, additional_headers=additional_headers) self.mail_batch = migration.BatchMailEventFeed() self.mail_entries = [] self.exceptions = 0 def _BaseURL(self): return '/a/feeds/migration/%s/%s' % (API_VER, self.domain) def ImportMail(self, user_name, mail_message, mail_item_properties, mail_labels): """Imports a single mail message. Args: user_name: The username to import messages to. mail_message: An RFC822 format email message. mail_item_properties: A list of Gmail properties to apply to the message. mail_labels: A list of labels to apply to the message. Returns: A MailEntry representing the successfully imported message. Raises: AppsForYourDomainException: An error occurred importing the message. """ uri = '%s/%s/mail' % (self._BaseURL(), user_name) mail_entry = migration.MailEntry() mail_entry.rfc822_msg = migration.Rfc822Msg(text=(base64.b64encode( mail_message))) mail_entry.rfc822_msg.encoding = 'base64' mail_entry.mail_item_property = map( lambda x: migration.MailItemProperty(value=x), mail_item_properties) mail_entry.label = map(lambda x: migration.Label(label_name=x), mail_labels) try: return migration.MailEntryFromString(str(self.Post(mail_entry, uri))) except gdata.service.RequestError, e: # Store the number of failed imports when importing several at a time self.exceptions += 1 raise gdata.apps.service.AppsForYourDomainException(e.args[0]) def AddBatchEntry(self, mail_message, mail_item_properties, mail_labels): """Adds a message to the current batch that you later will submit. Deprecated, use AddMailEntry instead Args: mail_message: An RFC822 format email message. mail_item_properties: A list of Gmail properties to apply to the message. mail_labels: A list of labels to apply to the message. Returns: The length of the MailEntry representing the message. """ deprecation("calling deprecated method AddBatchEntry") mail_entry = migration.BatchMailEntry() mail_entry.rfc822_msg = migration.Rfc822Msg(text=(base64.b64encode( mail_message))) mail_entry.rfc822_msg.encoding = 'base64' mail_entry.mail_item_property = map( lambda x: migration.MailItemProperty(value=x), mail_item_properties) mail_entry.label = map(lambda x: migration.Label(label_name=x), mail_labels) self.mail_batch.AddBatchEntry(mail_entry) return len(str(mail_entry)) def SubmitBatch(self, user_name): """Sends all the mail items you have added to the batch to the server. Deprecated, use ImportMultipleMails instead Args: user_name: The username to import messages to. Returns: An HTTPResponse from the web service call. Raises: AppsForYourDomainException: An error occurred importing the batch. """ deprecation("calling deprecated method SubmitBatch") uri = '%s/%s/mail/batch' % (self._BaseURL(), user_name) try: self.result = self.Post(self.mail_batch, uri, converter=migration.BatchMailEventFeedFromString) except gdata.service.RequestError, e: raise gdata.apps.service.AppsForYourDomainException(e.args[0]) self.mail_batch = migration.BatchMailEventFeed() return self.result def AddMailEntry(self, mail_message, mail_item_properties=None, mail_labels=None, identifier=None): """Prepares a list of mail messages to import using ImportMultipleMails. Args: mail_message: An RFC822 format email message as a string. mail_item_properties: List of Gmail properties to apply to the message. mail_labels: List of Gmail labels to apply to the message. identifier: The optional file identifier string Returns: The number of email messages to be imported. """ mail_entry_properties = MailEntryProperties( mail_message=mail_message, mail_item_properties=mail_item_properties, mail_labels=mail_labels, identifier=identifier) self.mail_entries.append(mail_entry_properties) return len(self.mail_entries) def ImportMultipleMails(self, user_name, threads_per_batch=20): """Launches separate threads to import every message added by AddMailEntry. Args: user_name: The user account name to import messages to. threads_per_batch: Number of messages to import at a time. Returns: The number of email messages that were successfully migrated. Raises: Exception: An error occurred while importing mails. """ num_entries = len(self.mail_entries) if not num_entries: return 0 threads = [] for mail_entry_properties in self.mail_entries: t = threading.Thread(name=mail_entry_properties.identifier, target=self.ImportMail, args=(user_name, mail_entry_properties.mail_message, mail_entry_properties.mail_item_properties, mail_entry_properties.mail_labels)) threads.append(t) try: # Determine the number of batches needed with threads_per_batch in each batches = num_entries / threads_per_batch + ( 0 if num_entries % threads_per_batch == 0 else 1) batch_min = 0 # Start the threads, one batch at a time for batch in range(batches): batch_max = ((batch + 1) * threads_per_batch if (batch + 1) * threads_per_batch < num_entries else num_entries) for i in range(batch_min, batch_max): threads[i].start() time.sleep(1) for i in range(batch_min, batch_max): threads[i].join() batch_min = batch_max self.mail_entries = [] except Exception, e: raise Exception(e.args[0]) else: return num_entries - self.exceptions gdata-2.0.18/src/gdata/apps/organization/0000750036466300116100000000000012156625015020250 5ustar afshareng00000000000000gdata-2.0.18/src/gdata/apps/organization/data.py0000640036466300116100000002763212156622363021551 0ustar afshareng00000000000000#!/usr/bin/python2.4 # # Copyright 2011 Google Inc. All Rights Reserved. # # 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. """Data model classes for the Organization Unit Provisioning API.""" __author__ = 'Gunjan Sharma ' import gdata.apps import gdata.apps.apps_property_entry import gdata.apps_property import gdata.data # This is required to work around a naming conflict between the Google # Spreadsheets API and Python's built-in property function pyproperty = property # The apps:property name of an organization unit ORG_UNIT_NAME = 'name' # The apps:property orgUnitPath of an organization unit ORG_UNIT_PATH = 'orgUnitPath' # The apps:property parentOrgUnitPath of an organization unit PARENT_ORG_UNIT_PATH = 'parentOrgUnitPath' # The apps:property description of an organization unit ORG_UNIT_DESCRIPTION = 'description' # The apps:property blockInheritance of an organization unit ORG_UNIT_BLOCK_INHERITANCE = 'blockInheritance' # The apps:property userEmail of a user entry USER_EMAIL = 'orgUserEmail' # The apps:property list of users to move USERS_TO_MOVE = 'usersToMove' # The apps:property list of moved users MOVED_USERS = 'usersMoved' # The apps:property customerId for the domain CUSTOMER_ID = 'customerId' # The apps:property name of the customer org unit CUSTOMER_ORG_UNIT_NAME = 'customerOrgUnitName' # The apps:property description of the customer org unit CUSTOMER_ORG_UNIT_DESCRIPTION = 'customerOrgUnitDescription' # The apps:property old organization unit's path for a user OLD_ORG_UNIT_PATH = 'oldOrgUnitPath' class CustomerIdEntry(gdata.apps.apps_property_entry.AppsPropertyEntry): """Represents a customerId entry in object form.""" def GetCustomerId(self): """Get the customer ID of the customerId object. Returns: The customer ID of this customerId object as a string or None. """ return self._GetProperty(CUSTOMER_ID) customer_id = pyproperty(GetCustomerId) def GetOrgUnitName(self): """Get the Organization Unit name of the customerId object. Returns: The Organization unit name of this customerId object as a string or None. """ return self._GetProperty(ORG_UNIT_NAME) org_unit_name = pyproperty(GetOrgUnitName) def GetCustomerOrgUnitName(self): """Get the Customer Organization Unit name of the customerId object. Returns: The Customer Organization unit name of this customerId object as a string or None. """ return self._GetProperty(CUSTOMER_ORG_UNIT_NAME) customer_org_unit_name = pyproperty(GetCustomerOrgUnitName) def GetOrgUnitDescription(self): """Get the Organization Unit Description of the customerId object. Returns: The Organization Unit Description of this customerId object as a string or None. """ return self._GetProperty(ORG_UNIT_DESCRIPTION) org_unit_description = pyproperty(GetOrgUnitDescription) def GetCustomerOrgUnitDescription(self): """Get the Customer Organization Unit Description of the customerId object. Returns: The Customer Organization Unit Description of this customerId object as a string or None. """ return self._GetProperty(CUSTOMER_ORG_UNIT_DESCRIPTION) customer_org_unit_description = pyproperty(GetCustomerOrgUnitDescription) class OrgUnitEntry(gdata.apps.apps_property_entry.AppsPropertyEntry): """Represents an OrganizationUnit in object form.""" def GetOrgUnitName(self): """Get the Organization Unit name of the OrganizationUnit object. Returns: The Organization unit name of this OrganizationUnit object as a string or None. """ return self._GetProperty(ORG_UNIT_NAME) def SetOrgUnitName(self, value): """Set the Organization Unit name of the OrganizationUnit object. Args: value: [string] The new Organization Unit name to give this object. """ self._SetProperty(ORG_UNIT_NAME, value) org_unit_name = pyproperty(GetOrgUnitName, SetOrgUnitName) def GetOrgUnitPath(self): """Get the Organization Unit Path of the OrganizationUnit object. Returns: The Organization Unit Path of this OrganizationUnit object as a string or None. """ return self._GetProperty(ORG_UNIT_PATH) def SetOrgUnitPath(self, value): """Set the Organization Unit path of the OrganizationUnit object. Args: value: [string] The new Organization Unit path to give this object. """ self._SetProperty(ORG_UNIT_PATH, value) org_unit_path = pyproperty(GetOrgUnitPath, SetOrgUnitPath) def GetParentOrgUnitPath(self): """Get the Parent Organization Unit Path of the OrganizationUnit object. Returns: The Parent Organization Unit Path of this OrganizationUnit object as a string or None. """ return self._GetProperty(PARENT_ORG_UNIT_PATH) def SetParentOrgUnitPath(self, value): """Set the Parent Organization Unit path of the OrganizationUnit object. Args: value: [string] The new Parent Organization Unit path to give this object. """ self._SetProperty(PARENT_ORG_UNIT_PATH, value) parent_org_unit_path = pyproperty(GetParentOrgUnitPath, SetParentOrgUnitPath) def GetOrgUnitDescription(self): """Get the Organization Unit Description of the OrganizationUnit object. Returns: The Organization Unit Description of this OrganizationUnit object as a string or None. """ return self._GetProperty(ORG_UNIT_DESCRIPTION) def SetOrgUnitDescription(self, value): """Set the Organization Unit Description of the OrganizationUnit object. Args: value: [string] The new Organization Unit Description to give this object. """ self._SetProperty(ORG_UNIT_DESCRIPTION, value) org_unit_description = pyproperty(GetOrgUnitDescription, SetOrgUnitDescription) def GetOrgUnitBlockInheritance(self): """Get the block_inheritance flag of the OrganizationUnit object. Returns: The the block_inheritance flag of this OrganizationUnit object as a string or None. """ return self._GetProperty(ORG_UNIT_BLOCK_INHERITANCE) def SetOrgUnitBlockInheritance(self, value): """Set the block_inheritance flag of the OrganizationUnit object. Args: value: [string] The new block_inheritance flag to give this object. """ self._SetProperty(ORG_UNIT_BLOCK_INHERITANCE, value) org_unit_block_inheritance = pyproperty(GetOrgUnitBlockInheritance, SetOrgUnitBlockInheritance) def GetMovedUsers(self): """Get the moved users of the OrganizationUnit object. Returns: The the moved users of this OrganizationUnit object as a string or None. """ return self._GetProperty(MOVED_USERS) def SetUsersToMove(self, value): """Set the Users to Move of the OrganizationUnit object. Args: value: [string] The comma seperated list of users to move to give this object. """ self._SetProperty(USERS_TO_MOVE, value) move_users = pyproperty(GetMovedUsers, SetUsersToMove) def __init__( self, org_unit_name=None, org_unit_path=None, parent_org_unit_path=None, org_unit_description=None, org_unit_block_inheritance=None, move_users=None, *args, **kwargs): """Constructs a new OrganizationUnit object with the given arguments. Args: org_unit_name: string (optional) The organization unit name for the object. org_unit_path: string (optional) The organization unit path for the object. parent_org_unit_path: string (optional) The parent organization unit path for the object. org_unit_description: string (optional) The organization unit description for the object. org_unit_block_inheritance: boolean (optional) weather or not inheritance from the organization unit is blocked. move_users: string (optional) comma seperated list of users to move. args: The other parameters to pass to gdata.entry.GDEntry constructor. kwargs: The other parameters to pass to gdata.entry.GDEntry constructor. """ super(OrgUnitEntry, self).__init__(*args, **kwargs) if org_unit_name: self.org_unit_name = org_unit_name if org_unit_path: self.org_unit_path = org_unit_path if parent_org_unit_path: self.parent_org_unit_path = parent_org_unit_path if org_unit_description: self.org_unit_description = org_unit_description if org_unit_block_inheritance is not None: self.org_unit_block_inheritance = str(org_unit_block_inheritance) if move_users: self.move_users = move_users class OrgUnitFeed(gdata.data.GDFeed): """Represents a feed of OrgUnitEntry objects.""" # Override entry so that this feed knows how to type its list of entries. entry = [OrgUnitEntry] class OrgUserEntry(gdata.apps.apps_property_entry.AppsPropertyEntry): """Represents an OrgUser in object form.""" def GetUserEmail(self): """Get the user email address of the OrgUser object. Returns: The user email address of this OrgUser object as a string or None. """ return self._GetProperty(USER_EMAIL) def SetUserEmail(self, value): """Set the user email address of this OrgUser object. Args: value: string The new user email address to give this object. """ self._SetProperty(USER_EMAIL, value) user_email = pyproperty(GetUserEmail, SetUserEmail) def GetOrgUnitPath(self): """Get the Organization Unit Path of the OrgUser object. Returns: The Organization Unit Path of this OrgUser object as a string or None. """ return self._GetProperty(ORG_UNIT_PATH) def SetOrgUnitPath(self, value): """Set the Organization Unit path of the OrgUser object. Args: value: [string] The new Organization Unit path to give this object. """ self._SetProperty(ORG_UNIT_PATH, value) org_unit_path = pyproperty(GetOrgUnitPath, SetOrgUnitPath) def GetOldOrgUnitPath(self): """Get the Old Organization Unit Path of the OrgUser object. Returns: The Old Organization Unit Path of this OrgUser object as a string or None. """ return self._GetProperty(OLD_ORG_UNIT_PATH) def SetOldOrgUnitPath(self, value): """Set the Old Organization Unit path of the OrgUser object. Args: value: [string] The new Old Organization Unit path to give this object. """ self._SetProperty(OLD_ORG_UNIT_PATH, value) old_org_unit_path = pyproperty(GetOldOrgUnitPath, SetOldOrgUnitPath) def __init__( self, user_email=None, org_unit_path=None, old_org_unit_path=None, *args, **kwargs): """Constructs a new OrgUser object with the given arguments. Args: user_email: string (optional) The user email address for the object. org_unit_path: string (optional) The organization unit path for the object. old_org_unit_path: string (optional) The old organization unit path for the object. args: The other parameters to pass to gdata.entry.GDEntry constructor. kwargs: The other parameters to pass to gdata.entry.GDEntry constructor. """ super(OrgUserEntry, self).__init__(*args, **kwargs) if user_email: self.user_email = user_email if org_unit_path: self.org_unit_path = org_unit_path if old_org_unit_path: self.old_org_unit_path = old_org_unit_path class OrgUserFeed(gdata.data.GDFeed): """Represents a feed of OrgUserEntry objects.""" # Override entry so that this feed knows how to type its list of entries. entry = [OrgUserEntry] gdata-2.0.18/src/gdata/apps/organization/__init__.py0000640036466300116100000000000012156622363022353 0ustar afshareng00000000000000gdata-2.0.18/src/gdata/apps/organization/client.py0000640036466300116100000004717612156622363022123 0ustar afshareng00000000000000#!/usr/bin/python2.4 # # Copyright 2011 Google Inc. All Rights Reserved. # # 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. """OrganizationUnitProvisioningClient simplifies OrgUnit Provisioning API calls. OrganizationUnitProvisioningClient extends gdata.client.GDClient to ease interaction with the Google Organization Unit Provisioning API. These interactions include the ability to create, retrieve, update and delete organization units, move users within organization units, retrieve customerId and update and retrieve users in organization units. """ __author__ = 'Gunjan Sharma ' import urllib import gdata.apps.organization.data import gdata.client CUSTOMER_ID_URI_TEMPLATE = '/a/feeds/customer/%s/customerId' # OrganizationUnit URI templates # The strings in this template are eventually replaced with the feed type # (orgunit/orguser), API version and Google Apps domain name, respectively. ORGANIZATION_UNIT_URI_TEMPLATE = '/a/feeds/%s/%s/%s' # The value for orgunit requests ORGANIZATION_UNIT_FEED = 'orgunit' # The value for orguser requests ORGANIZATION_USER_FEED = 'orguser' class OrganizationUnitProvisioningClient(gdata.client.GDClient): """Client extension for the Google Org Unit Provisioning API service. Attributes: host: string The hostname for the MultiDomain Provisioning API service. api_version: string The version of the MultiDomain Provisioning API. """ host = 'apps-apis.google.com' api_version = '2.0' auth_service = 'apps' auth_scopes = gdata.gauth.AUTH_SCOPES['apps'] ssl = True def __init__(self, domain, auth_token=None, **kwargs): """Constructs a new client for the Organization Unit Provisioning API. Args: domain: string The Google Apps domain with Organization Unit Provisioning. auth_token: (optional) gdata.gauth.ClientLoginToken, AuthSubToken, or OAuthToken which authorizes this client to edit the Organization Units. """ gdata.client.GDClient.__init__(self, auth_token=auth_token, **kwargs) self.domain = domain def make_organization_unit_provisioning_uri( self, feed_type, customer_id, org_unit_path_or_user_email=None, params=None): """Creates a resource feed URI for the Organization Unit Provisioning API. Using this client's Google Apps domain, create a feed URI for organization unit provisioning in that domain. If an org unit path or org user email address is provided, return a URI for that specific resource. If params are provided, append them as GET params. Args: feed_type: string The type of feed (orgunit/orguser) customer_id: string The customerId of the user. org_unit_path_or_user_email: string (optional) The org unit path or org user email address for which to make a feed URI. params: dict (optional) key -> value params to append as GET vars to the URI. Example: params={'start': 'my-resource-id'} Returns: A string giving the URI for organization unit provisioning for this client's Google Apps domain. """ uri = ORGANIZATION_UNIT_URI_TEMPLATE % (feed_type, self.api_version, customer_id) if org_unit_path_or_user_email: uri += '/' + org_unit_path_or_user_email if params: uri += '?' + urllib.urlencode(params) return uri MakeOrganizationUnitProvisioningUri = make_organization_unit_provisioning_uri def make_organization_unit_orgunit_provisioning_uri(self, customer_id, org_unit_path=None, params=None): """Creates a resource feed URI for the orgunit's Provisioning API calls. Using this client's Google Apps domain, create a feed URI for organization unit orgunit's provisioning in that domain. If an org_unit_path is provided, return a URI for that specific resource. If params are provided, append them as GET params. Args: customer_id: string The customerId of the user. org_unit_path: string (optional) The organization unit's path for which to make a feed URI. params: dict (optional) key -> value params to append as GET vars to the URI. Example: params={'start': 'my-resource-id'} Returns: A string giving the URI for organization unit provisioning for given org_unit_path """ return self.make_organization_unit_provisioning_uri( ORGANIZATION_UNIT_FEED, customer_id, org_unit_path, params) MakeOrganizationUnitOrgunitProvisioningUri = make_organization_unit_orgunit_provisioning_uri def make_organization_unit_orguser_provisioning_uri(self, customer_id, org_user_email=None, params=None): """Creates a resource feed URI for the orguser's Provisioning API calls. Using this client's Google Apps domain, create a feed URI for organization unit orguser's provisioning in that domain. If an org_user_email is provided, return a URI for that specific resource. If params are provided, append them as GET params. Args: customer_id: string The customerId of the user. org_user_email: string (optional) The organization unit's path for which to make a feed URI. params: dict (optional) key -> value params to append as GET vars to the URI. Example: params={'start': 'my-resource-id'} Returns: A string giving the URI for organization user provisioning for given org_user_email """ return self.make_organization_unit_provisioning_uri( ORGANIZATION_USER_FEED, customer_id, org_user_email, params) MakeOrganizationUnitOrguserProvisioningUri = make_organization_unit_orguser_provisioning_uri def make_customer_id_feed_uri(self): """Creates a feed uri for retrieving customerId of the user. Returns: A string giving the URI for retrieving customerId of the user. """ uri = CUSTOMER_ID_URI_TEMPLATE % (self.api_version) return uri MakeCustomerIdFeedUri = make_customer_id_feed_uri def retrieve_customer_id(self, **kwargs): """Retrieve the Customer ID for the customer domain. Returns: A gdata.apps.organization.data.CustomerIdEntry. """ uri = self.MakeCustomerIdFeedUri() return self.GetEntry( uri, desired_class=gdata.apps.organization.data.CustomerIdEntry, **kwargs) RetrieveCustomerId = retrieve_customer_id def create_org_unit(self, customer_id, name, parent_org_unit_path='/', description='', block_inheritance=False, **kwargs): """Create a Organization Unit. Args: customer_id: string The ID of the Google Apps customer. name: string The simple organization unit text name, not the full path name. parent_org_unit_path: string The full path of the parental tree to this organization unit (default: '/'). [Note: Each element of the path MUST be URL encoded (example: finance%2Forganization/suborganization)] description: string The human readable text description of the organization unit (optional). block_inheritance: boolean This parameter blocks policy setting inheritance from organization units higher in the organization tree (default: False). Returns: A gdata.apps.organization.data.OrgUnitEntry representing an organization unit. """ new_org_unit = gdata.apps.organization.data.OrgUnitEntry( org_unit_name=name, parent_org_unit_path=parent_org_unit_path, org_unit_description=description, org_unit_block_inheritance=block_inheritance) return self.post( new_org_unit, self.MakeOrganizationUnitOrgunitProvisioningUri(customer_id), **kwargs) CreateOrgUnit = create_org_unit def update_org_unit(self, customer_id, org_unit_path, org_unit_entry, **kwargs): """Update a Organization Unit. Args: customer_id: string The ID of the Google Apps customer. org_unit_path: string The organization's full path name. [Note: Each element of the path MUST be URL encoded (example: finance%2Forganization/suborganization)] org_unit_entry: gdata.apps.organization.data.OrgUnitEntry The updated organization unit entry. Returns: A gdata.apps.organization.data.OrgUnitEntry representing an organization unit. """ if not org_unit_entry.GetParentOrgUnitPath(): org_unit_entry.SetParentOrgUnitPath('/') return self.update(org_unit_entry, uri=self.MakeOrganizationUnitOrgunitProvisioningUri( customer_id, org_unit_path=org_unit_path), **kwargs) UpdateOrgUnit = update_org_unit def move_users_to_org_unit(self, customer_id, org_unit_path, users_to_move, **kwargs): """Move a user to an Organization Unit. Args: customer_id: string The ID of the Google Apps customer. org_unit_path: string The organization's full path name. [Note: Each element of the path MUST be URL encoded (example: finance%2Forganization/suborganization)] users_to_move: list Email addresses of users to move in list format. [Note: You can move a maximum of 25 users at one time.] Returns: A gdata.apps.organization.data.OrgUnitEntry representing an organization unit. """ org_unit_entry = self.retrieve_org_unit(customer_id, org_unit_path) org_unit_entry.SetUsersToMove(', '.join(users_to_move)) if not org_unit_entry.GetParentOrgUnitPath(): org_unit_entry.SetParentOrgUnitPath('/') return self.update(org_unit_entry, uri=self.MakeOrganizationUnitOrgunitProvisioningUri( customer_id, org_unit_path=org_unit_path), **kwargs) MoveUserToOrgUnit = move_users_to_org_unit def retrieve_org_unit(self, customer_id, org_unit_path, **kwargs): """Retrieve a Orgunit based on its path. Args: customer_id: string The ID of the Google Apps customer. org_unit_path: string The organization's full path name. [Note: Each element of the path MUST be URL encoded (example: finance%2Forganization/suborganization)] Returns: A gdata.apps.organization.data.OrgUnitEntry representing an organization unit. """ uri = self.MakeOrganizationUnitOrgunitProvisioningUri( customer_id, org_unit_path=org_unit_path) return self.GetEntry( uri, desired_class=gdata.apps.organization.data.OrgUnitEntry, **kwargs) RetrieveOrgUnit = retrieve_org_unit def retrieve_feed_from_uri(self, uri, desired_class, **kwargs): """Retrieve feed from given uri. Args: uri: string The uri from where to get the feed. desired_class: Feed The type of feed that if to be retrieved. Returns: Feed of type desired class. """ return self.GetFeed(uri, desired_class=desired_class, **kwargs) RetrieveFeedFromUri = retrieve_feed_from_uri def retrieve_all_org_units_from_uri(self, uri, **kwargs): """Retrieve all OrgUnits from given uri. Args: uri: string The uri from where to get the orgunits. Returns: gdata.apps.organisation.data.OrgUnitFeed object """ orgunit_feed = gdata.apps.organization.data.OrgUnitFeed() temp_feed = self.RetrieveFeedFromUri( uri, gdata.apps.organization.data.OrgUnitFeed) orgunit_feed.entry = temp_feed.entry next_link = temp_feed.GetNextLink() while next_link is not None: uri = next_link.GetAttributes()[0].value temp_feed = self.GetFeed( uri, desired_class=gdata.apps.organization.data.OrgUnitFeed, **kwargs) orgunit_feed.entry[0:0] = temp_feed.entry next_link = temp_feed.GetNextLink() return orgunit_feed RetrieveAllOrgUnitsFromUri = retrieve_all_org_units_from_uri def retrieve_all_org_units(self, customer_id, **kwargs): """Retrieve all OrgUnits in the customer's domain. Args: customer_id: string The ID of the Google Apps customer. Returns: gdata.apps.organisation.data.OrgUnitFeed object """ uri = self.MakeOrganizationUnitOrgunitProvisioningUri( customer_id, params={'get': 'all'}, **kwargs) return self.RetrieveAllOrgUnitsFromUri(uri) RetrieveAllOrgUnits = retrieve_all_org_units def retrieve_page_of_org_units(self, customer_id, startKey=None, **kwargs): """Retrieve one page of OrgUnits in the customer's domain. Args: customer_id: string The ID of the Google Apps customer. startKey: string The key to continue for pagination through all OrgUnits. Returns: gdata.apps.organisation.data.OrgUnitFeed object """ uri = '' if startKey is not None: uri = self.MakeOrganizationUnitOrgunitProvisioningUri( customer_id, params={'get': 'all', 'startKey': startKey}, **kwargs) else: uri = self.MakeOrganizationUnitOrgunitProvisioningUri( customer_id, params={'get': 'all'}, **kwargs) return self.GetFeed( uri, desired_class=gdata.apps.organization.data.OrgUnitFeed, **kwargs) RetrievePageOfOrgUnits = retrieve_page_of_org_units def retrieve_sub_org_units(self, customer_id, org_unit_path, **kwargs): """Retrieve all Sub-OrgUnits of the provided OrgUnit. Args: customer_id: string The ID of the Google Apps customer. org_unit_path: string The organization's full path name. [Note: Each element of the path MUST be URL encoded (example: finance%2Forganization/suborganization)] Returns: gdata.apps.organisation.data.OrgUnitFeed object """ uri = self.MakeOrganizationUnitOrgunitProvisioningUri( customer_id, params={'get': 'children', 'orgUnitPath': org_unit_path}, **kwargs) return self.RetrieveAllOrgUnitsFromUri(uri) RetrieveSubOrgUnits = retrieve_sub_org_units def delete_org_unit(self, customer_id, org_unit_path, **kwargs): """Delete a Orgunit based on its path. Args: customer_id: string The ID of the Google Apps customer. org_unit_path: string The organization's full path name. [Note: Each element of the path MUST be URL encoded (example: finance%2Forganization/suborganization)] Returns: An HTTP response object. See gdata.client.request(). """ return self.delete(self.MakeOrganizationUnitOrgunitProvisioningUri( customer_id, org_unit_path=org_unit_path), **kwargs) DeleteOrgUnit = delete_org_unit def update_org_user(self, customer_id, user_email, org_unit_path, **kwargs): """Update the OrgUnit of a OrgUser. Args: customer_id: string The ID of the Google Apps customer. user_email: string The email address of the user. org_unit_path: string The new organization's full path name. [Note: Each element of the path MUST be URL encoded (example: finance%2Forganization/suborganization)] Returns: A gdata.apps.organization.data.OrgUserEntry representing an organization user. """ old_user_entry = self.RetrieveOrgUser(customer_id, user_email) old_org_unit_path = old_user_entry.GetOrgUnitPath() if not old_org_unit_path: old_org_unit_path = '/' old_user_entry.SetOldOrgUnitPath(old_org_unit_path) old_user_entry.SetOrgUnitPath(org_unit_path) return self.update(old_user_entry, uri=self.MakeOrganizationUnitOrguserProvisioningUri( customer_id, user_email), **kwargs) UpdateOrgUser = update_org_user def retrieve_org_user(self, customer_id, user_email, **kwargs): """Retrieve an organization user. Args: customer_id: string The ID of the Google Apps customer. user_email: string The email address of the user. Returns: A gdata.apps.organization.data.OrgUserEntry representing an organization user. """ uri = self.MakeOrganizationUnitOrguserProvisioningUri(customer_id, user_email) return self.GetEntry( uri, desired_class=gdata.apps.organization.data.OrgUserEntry, **kwargs) RetrieveOrgUser = retrieve_org_user def retrieve_all_org_users_from_uri(self, uri, **kwargs): """Retrieve all OrgUsers from given uri. Args: uri: string The uri from where to get the orgusers. Returns: gdata.apps.organisation.data.OrgUserFeed object """ orguser_feed = gdata.apps.organization.data.OrgUserFeed() temp_feed = self.RetrieveFeedFromUri( uri, gdata.apps.organization.data.OrgUserFeed) orguser_feed.entry = temp_feed.entry next_link = temp_feed.GetNextLink() while next_link is not None: uri = next_link.GetAttributes()[0].value temp_feed = self.GetFeed( uri, desired_class=gdata.apps.organization.data.OrgUserFeed, **kwargs) orguser_feed.entry[0:0] = temp_feed.entry next_link = temp_feed.GetNextLink() return orguser_feed RetrieveAllOrgUsersFromUri = retrieve_all_org_users_from_uri def retrieve_all_org_users(self, customer_id, **kwargs): """Retrieve all OrgUsers in the customer's domain. Args: customer_id: string The ID of the Google Apps customer. Returns: gdata.apps.organisation.data.OrgUserFeed object """ uri = self.MakeOrganizationUnitOrguserProvisioningUri( customer_id, params={'get': 'all'}, **kwargs) return self.RetrieveAllOrgUsersFromUri(uri) RetrieveAllOrgUsers = retrieve_all_org_users def retrieve_page_of_org_users(self, customer_id, startKey=None, **kwargs): """Retrieve one page of OrgUsers in the customer's domain. Args: customer_id: string The ID of the Google Apps customer. startKey: The string key to continue for pagination through all OrgUnits. Returns: gdata.apps.organisation.data.OrgUserFeed object """ uri = '' if startKey is not None: uri = self.MakeOrganizationUnitOrguserProvisioningUri( customer_id, params={'get': 'all', 'startKey': startKey}, **kwargs) else: uri = self.MakeOrganizationUnitOrguserProvisioningUri( customer_id, params={'get': 'all'}) return self.GetFeed( uri, desired_class=gdata.apps.organization.data.OrgUserFeed, **kwargs) RetrievePageOfOrgUsers = retrieve_page_of_org_users def retrieve_org_unit_users(self, customer_id, org_unit_path, **kwargs): """Retrieve all OrgUsers of the provided OrgUnit. Args: customer_id: string The ID of the Google Apps customer. org_unit_path: string The organization's full path name. [Note: Each element of the path MUST be URL encoded (example: finance%2Forganization/suborganization)] Returns: gdata.apps.organisation.data.OrgUserFeed object """ uri = self.MakeOrganizationUnitOrguserProvisioningUri( customer_id, params={'get': 'children', 'orgUnitPath': org_unit_path}) return self.RetrieveAllOrgUsersFromUri(uri, **kwargs) RetrieveOrgUnitUsers = retrieve_org_unit_users gdata-2.0.18/src/gdata/apps/organization/service.py0000640036466300116100000002544112156622363022274 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright (C) 2008 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. """Allow Google Apps domain administrators to manage organization unit and organization user. OrganizationService: Provides methods to manage organization unit and organization user. """ __author__ = 'Alexandre Vivien (alex@simplecode.fr)' import gdata.apps import gdata.apps.service import gdata.service API_VER = '2.0' CUSTOMER_BASE_URL = '/a/feeds/customer/2.0/customerId' BASE_UNIT_URL = '/a/feeds/orgunit/' + API_VER + '/%s' UNIT_URL = BASE_UNIT_URL + '/%s' UNIT_ALL_URL = BASE_UNIT_URL + '?get=all' UNIT_CHILD_URL = BASE_UNIT_URL + '?get=children&orgUnitPath=%s' BASE_USER_URL = '/a/feeds/orguser/' + API_VER + '/%s' USER_URL = BASE_USER_URL + '/%s' USER_ALL_URL = BASE_USER_URL + '?get=all' USER_CHILD_URL = BASE_USER_URL + '?get=children&orgUnitPath=%s' class OrganizationService(gdata.apps.service.PropertyService): """Client for the Google Apps Organizations service.""" def _Bool2Str(self, b): if b is None: return None return str(b is True).lower() def RetrieveCustomerId(self): """Retrieve the Customer ID for the account of the authenticated administrator making this request. Args: None. Returns: A dict containing the result of the retrieve operation. """ uri = CUSTOMER_BASE_URL return self._GetProperties(uri) def CreateOrgUnit(self, customer_id, name, parent_org_unit_path='/', description='', block_inheritance=False): """Create a Organization Unit. Args: customer_id: The ID of the Google Apps customer. name: The simple organization unit text name, not the full path name. parent_org_unit_path: The full path of the parental tree to this organization unit (default: '/'). Note: Each element of the path MUST be URL encoded (example: finance%2Forganization/suborganization) description: The human readable text description of the organization unit (optional). block_inheritance: This parameter blocks policy setting inheritance from organization units higher in the organization tree (default: False). Returns: A dict containing the result of the create operation. """ uri = BASE_UNIT_URL % (customer_id) properties = {} properties['name'] = name properties['parentOrgUnitPath'] = parent_org_unit_path properties['description'] = description properties['blockInheritance'] = self._Bool2Str(block_inheritance) return self._PostProperties(uri, properties) def UpdateOrgUnit(self, customer_id, org_unit_path, name=None, parent_org_unit_path=None, description=None, block_inheritance=None): """Update a Organization Unit. Args: customer_id: The ID of the Google Apps customer. org_unit_path: The organization's full path name. Note: Each element of the path MUST be URL encoded (example: finance%2Forganization/suborganization) name: The simple organization unit text name, not the full path name. parent_org_unit_path: The full path of the parental tree to this organization unit. Note: Each element of the path MUST be URL encoded (example: finance%2Forganization/suborganization) description: The human readable text description of the organization unit. block_inheritance: This parameter blocks policy setting inheritance from organization units higher in the organization tree. Returns: A dict containing the result of the update operation. """ uri = UNIT_URL % (customer_id, org_unit_path) properties = {} if name: properties['name'] = name if parent_org_unit_path: properties['parentOrgUnitPath'] = parent_org_unit_path if description: properties['description'] = description if block_inheritance: properties['blockInheritance'] = self._Bool2Str(block_inheritance) return self._PutProperties(uri, properties) def MoveUserToOrgUnit(self, customer_id, org_unit_path, users_to_move): """Move a user to an Organization Unit. Args: customer_id: The ID of the Google Apps customer. org_unit_path: The organization's full path name. Note: Each element of the path MUST be URL encoded (example: finance%2Forganization/suborganization) users_to_move: Email addresses list of users to move. Note: You can move a maximum of 25 users at one time. Returns: A dict containing the result of the update operation. """ uri = UNIT_URL % (customer_id, org_unit_path) properties = {} if users_to_move and isinstance(users_to_move, list): properties['usersToMove'] = ', '.join(users_to_move) return self._PutProperties(uri, properties) def RetrieveOrgUnit(self, customer_id, org_unit_path): """Retrieve a Orgunit based on its path. Args: customer_id: The ID of the Google Apps customer. org_unit_path: The organization's full path name. Note: Each element of the path MUST be URL encoded (example: finance%2Forganization/suborganization) Returns: A dict containing the result of the retrieve operation. """ uri = UNIT_URL % (customer_id, org_unit_path) return self._GetProperties(uri) def DeleteOrgUnit(self, customer_id, org_unit_path): """Delete a Orgunit based on its path. Args: customer_id: The ID of the Google Apps customer. org_unit_path: The organization's full path name. Note: Each element of the path MUST be URL encoded (example: finance%2Forganization/suborganization) Returns: A dict containing the result of the delete operation. """ uri = UNIT_URL % (customer_id, org_unit_path) return self._DeleteProperties(uri) def RetrieveAllOrgUnits(self, customer_id): """Retrieve all OrgUnits in the customer's domain. Args: customer_id: The ID of the Google Apps customer. Returns: A list containing the result of the retrieve operation. """ uri = UNIT_ALL_URL % (customer_id) return self._GetPropertiesList(uri) def RetrievePageOfOrgUnits(self, customer_id, startKey=None): """Retrieve one page of OrgUnits in the customer's domain. Args: customer_id: The ID of the Google Apps customer. startKey: The key to continue for pagination through all OrgUnits. Returns: A feed object containing the result of the retrieve operation. """ uri = UNIT_ALL_URL % (customer_id) if startKey is not None: uri += "&startKey=" + startKey property_feed = self._GetPropertyFeed(uri) return property_feed def RetrieveSubOrgUnits(self, customer_id, org_unit_path): """Retrieve all Sub-OrgUnits of the provided OrgUnit. Args: customer_id: The ID of the Google Apps customer. org_unit_path: The organization's full path name. Note: Each element of the path MUST be URL encoded (example: finance%2Forganization/suborganization) Returns: A list containing the result of the retrieve operation. """ uri = UNIT_CHILD_URL % (customer_id, org_unit_path) return self._GetPropertiesList(uri) def RetrieveOrgUser(self, customer_id, user_email): """Retrieve the OrgUnit of the user. Args: customer_id: The ID of the Google Apps customer. user_email: The email address of the user. Returns: A dict containing the result of the retrieve operation. """ uri = USER_URL % (customer_id, user_email) return self._GetProperties(uri) def UpdateOrgUser(self, customer_id, user_email, org_unit_path): """Update the OrgUnit of a OrgUser. Args: customer_id: The ID of the Google Apps customer. user_email: The email address of the user. org_unit_path: The new organization's full path name. Note: Each element of the path MUST be URL encoded (example: finance%2Forganization/suborganization) Returns: A dict containing the result of the update operation. """ uri = USER_URL % (customer_id, user_email) properties = {} if org_unit_path: properties['orgUnitPath'] = org_unit_path return self._PutProperties(uri, properties) def RetrieveAllOrgUsers(self, customer_id): """Retrieve all OrgUsers in the customer's domain. Args: customer_id: The ID of the Google Apps customer. Returns: A list containing the result of the retrieve operation. """ uri = USER_ALL_URL % (customer_id) return self._GetPropertiesList(uri) def RetrievePageOfOrgUsers(self, customer_id, startKey=None): """Retrieve one page of OrgUsers in the customer's domain. Args: customer_id: The ID of the Google Apps customer. startKey: The key to continue for pagination through all OrgUnits. Returns: A feed object containing the result of the retrieve operation. """ uri = USER_ALL_URL % (customer_id) if startKey is not None: uri += "&startKey=" + startKey property_feed = self._GetPropertyFeed(uri) return property_feed def RetrieveOrgUnitUsers(self, customer_id, org_unit_path): """Retrieve all OrgUsers of the provided OrgUnit. Args: customer_id: The ID of the Google Apps customer. org_unit_path: The organization's full path name. Note: Each element of the path MUST be URL encoded (example: finance%2Forganization/suborganization) Returns: A list containing the result of the retrieve operation. """ uri = USER_CHILD_URL % (customer_id, org_unit_path) return self._GetPropertiesList(uri) def RetrieveOrgUnitPageOfUsers(self, customer_id, org_unit_path, startKey=None): """Retrieve one page of OrgUsers of the provided OrgUnit. Args: customer_id: The ID of the Google Apps customer. org_unit_path: The organization's full path name. Note: Each element of the path MUST be URL encoded (example: finance%2Forganization/suborganization) startKey: The key to continue for pagination through all OrgUsers. Returns: A feed object containing the result of the retrieve operation. """ uri = USER_CHILD_URL % (customer_id, org_unit_path) if startKey is not None: uri += "&startKey=" + startKey property_feed = self._GetPropertyFeed(uri) return property_feed gdata-2.0.18/src/gdata/apps/emailsettings/0000750036466300116100000000000012156625015020414 5ustar afshareng00000000000000gdata-2.0.18/src/gdata/apps/emailsettings/data.py0000640036466300116100000011303112156622363021702 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright 2010 Google Inc. All Rights Reserved. # # 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. """Data model classes for the Email Settings API.""" __author__ = 'Claudio Cherubino ' import atom.data import gdata.apps import gdata.apps_property import gdata.data # This is required to work around a naming conflict between the Google # Spreadsheets API and Python's built-in property function pyproperty = property # The apps:property label of the label property LABEL_NAME = 'label' # The apps:property from of the filter property FILTER_FROM_NAME = 'from' # The apps:property to of the filter property FILTER_TO_NAME = 'to' # The apps:property subject of the filter property FILTER_SUBJECT_NAME = 'subject' # The apps:property hasTheWord of the filter property FILTER_HAS_THE_WORD_NAME = 'hasTheWord' # The apps:property doesNotHaveTheWord of the filter property FILTER_DOES_NOT_HAVE_THE_WORD_NAME = 'doesNotHaveTheWord' # The apps:property hasAttachment of the filter property FILTER_HAS_ATTACHMENTS_NAME = 'hasAttachment' # The apps:property label of the filter action property FILTER_LABEL = 'label' # The apps:property shouldMarkAsRead of the filter action property FILTER_MARK_AS_READ = 'shouldMarkAsRead' # The apps:property shouldArchive of the filter action property FILTER_ARCHIVE = 'shouldArchive' # The apps:property name of the send-as alias property SENDAS_ALIAS_NAME = 'name' # The apps:property address of the send-as alias property SENDAS_ALIAS_ADDRESS = 'address' # The apps:property replyTo of the send-as alias property SENDAS_ALIAS_REPLY_TO = 'replyTo' # The apps:property makeDefault of the send-as alias property SENDAS_ALIAS_MAKE_DEFAULT = 'makeDefault' # The apps:property enable of the webclip property WEBCLIP_ENABLE = 'enable' # The apps:property enable of the forwarding property FORWARDING_ENABLE = 'enable' # The apps:property forwardTo of the forwarding property FORWARDING_TO = 'forwardTo' # The apps:property action of the forwarding property FORWARDING_ACTION = 'action' # The apps:property enable of the POP property POP_ENABLE = 'enable' # The apps:property enableFor of the POP property POP_ENABLE_FOR = 'enableFor' # The apps:property action of the POP property POP_ACTION = 'action' # The apps:property enable of the IMAP property IMAP_ENABLE = 'enable' # The apps:property enable of the vacation responder property VACATION_RESPONDER_ENABLE = 'enable' # The apps:property subject of the vacation responder property VACATION_RESPONDER_SUBJECT = 'subject' # The apps:property message of the vacation responder property VACATION_RESPONDER_MESSAGE = 'message' # The apps:property startDate of the vacation responder property VACATION_RESPONDER_STARTDATE = 'startDate' # The apps:property endDate of the vacation responder property VACATION_RESPONDER_ENDDATE = 'endDate' # The apps:property contactsOnly of the vacation responder property VACATION_RESPONDER_CONTACTS_ONLY = 'contactsOnly' # The apps:property domainOnly of the vacation responder property VACATION_RESPONDER_DOMAIN_ONLY = 'domainOnly' # The apps:property signature of the signature property SIGNATURE_VALUE = 'signature' # The apps:property language of the language property LANGUAGE_TAG = 'language' # The apps:property pageSize of the general settings property GENERAL_PAGE_SIZE = 'pageSize' # The apps:property shortcuts of the general settings property GENERAL_SHORTCUTS = 'shortcuts' # The apps:property arrows of the general settings property GENERAL_ARROWS = 'arrows' # The apps:prgdata.appsoperty snippets of the general settings property GENERAL_SNIPPETS = 'snippets' # The apps:property uniAppsProcode of the general settings property GENERAL_UNICODE = 'unicode' # The apps:property delegationId of the email delegation property DELEGATION_ID = 'delegationId' # The apps:property address of the email delegation property DELEGATION_ADDRESS = 'address' # The apps:property delegate of the email delegation property DELEGATION_DELEGATE = 'delegate' # The apps:property status of the email delegation property DELEGATION_STATUS = 'status' class EmailSettingsEntry(gdata.data.GDEntry): """Represents an Email Settings entry in object form.""" property = [gdata.apps_property.AppsProperty] def _GetProperty(self, name): """Get the apps:property value with the given name. Args: name: string Name of the apps:property value to get. Returns: The apps:property value with the given name, or None if the name was invalid. """ value = None for p in self.property: if p.name == name: value = p.value break return value def _SetProperty(self, name, value): """Set the apps:property value with the given name to the given value. Args: name: string Name of the apps:property value to set. value: string Value to give the apps:property value with the given name. """ found = False for i in range(len(self.property)): if self.property[i].name == name: self.property[i].value = value found = True break if not found: self.property.append( gdata.apps_property.AppsProperty(name=name, value=value)) def find_edit_link(self): return self.uri class EmailSettingsLabel(EmailSettingsEntry): """Represents a Label in object form.""" def GetName(self): """Get the name of the Label object. Returns: The name of this Label object as a string or None. """ return self._GetProperty(LABEL_NAME) def SetName(self, value): """Set the name of this Label object. Args: value: string The new label name to give this object. """ self._SetProperty(LABEL_NAME, value) name = pyproperty(GetName, SetName) def __init__(self, uri=None, name=None, *args, **kwargs): """Constructs a new EmailSettingsLabel object with the given arguments. Args: uri: string (optional) The uri of this object for HTTP requests. name: string (optional) The name to give this new object. args: The other parameters to pass to gdata.entry.GDEntry constructor. kwargs: The other parameters to pass to gdata.entry.GDEntry constructor. """ super(EmailSettingsLabel, self).__init__(*args, **kwargs) if uri: self.uri = uri if name: self.name = name class EmailSettingsFilter(EmailSettingsEntry): """Represents an Email Settings Filter in object form.""" def GetFrom(self): """Get the From value of the Filter object. Returns: The From value of this Filter object as a string or None. """ return self._GetProperty(FILTER_FROM_NAME) def SetFrom(self, value): """Set the From value of this Filter object. Args: value: string The new From value to give this object. """ self._SetProperty(FILTER_FROM_NAME, value) from_address = pyproperty(GetFrom, SetFrom) def GetTo(self): """Get the To value of the Filter object. Returns: The To value of this Filter object as a string or None. """ return self._GetProperty(FILTER_TO_NAME) def SetTo(self, value): """Set the To value of this Filter object. Args: value: string The new To value to give this object. """ self._SetProperty(FILTER_TO_NAME, value) to_address = pyproperty(GetTo, SetTo) def GetSubject(self): """Get the Subject value of the Filter object. Returns: The Subject value of this Filter object as a string or None. """ return self._GetProperty(FILTER_SUBJECT_NAME) def SetSubject(self, value): """Set the Subject value of this Filter object. Args: value: string The new Subject value to give this object. """ self._SetProperty(FILTER_SUBJECT_NAME, value) subject = pyproperty(GetSubject, SetSubject) def GetHasTheWord(self): """Get the HasTheWord value of the Filter object. Returns: The HasTheWord value of this Filter object as a string or None. """ return self._GetProperty(FILTER_HAS_THE_WORD_NAME) def SetHasTheWord(self, value): """Set the HasTheWord value of this Filter object. Args: value: string The new HasTheWord value to give this object. """ self._SetProperty(FILTER_HAS_THE_WORD_NAME, value) has_the_word = pyproperty(GetHasTheWord, SetHasTheWord) def GetDoesNotHaveTheWord(self): """Get the DoesNotHaveTheWord value of the Filter object. Returns: The DoesNotHaveTheWord value of this Filter object as a string or None. """ return self._GetProperty(FILTER_DOES_NOT_HAVE_THE_WORD_NAME) def SetDoesNotHaveTheWord(self, value): """Set the DoesNotHaveTheWord value of this Filter object. Args: value: string The new DoesNotHaveTheWord value to give this object. """ self._SetProperty(FILTER_DOES_NOT_HAVE_THE_WORD_NAME, value) does_not_have_the_word = pyproperty(GetDoesNotHaveTheWord, SetDoesNotHaveTheWord) def GetHasAttachments(self): """Get the HasAttachments value of the Filter object. Returns: The HasAttachments value of this Filter object as a string or None. """ return self._GetProperty(FILTER_HAS_ATTACHMENTS_NAME) def SetHasAttachments(self, value): """Set the HasAttachments value of this Filter object. Args: value: string The new HasAttachments value to give this object. """ self._SetProperty(FILTER_HAS_ATTACHMENTS_NAME, value) has_attachments = pyproperty(GetHasAttachments, SetHasAttachments) def GetLabel(self): """Get the Label value of the Filter object. Returns: The Label value of this Filter object as a string or None. """ return self._GetProperty(FILTER_LABEL) def SetLabel(self, value): """Set the Label value of this Filter object. Args: value: string The new Label value to give this object. """ self._SetProperty(FILTER_LABEL, value) label = pyproperty(GetLabel, SetLabel) def GetMarkAsRead(self): """Get the MarkAsRead value of the Filter object. Returns: The MarkAsRead value of this Filter object as a string or None. """ return self._GetProperty(FILTER_MARK_AS_READ) def SetMarkAsRead(self, value): """Set the MarkAsRead value of this Filter object. Args: value: string The new MarkAsRead value to give this object. """ self._SetProperty(FILTER_MARK_AS_READ, value) mark_as_read = pyproperty(GetMarkAsRead, SetMarkAsRead) def GetArchive(self): """Get the Archive value of the Filter object. Returns: The Archive value of this Filter object as a string or None. """ return self._GetProperty(FILTER_ARCHIVE) def SetArchive(self, value): """Set the Archive value of this Filter object. Args: value: string The new Archive value to give this object. """ self._SetProperty(FILTER_ARCHIVE, value) archive = pyproperty(GetArchive, SetArchive) def __init__(self, uri=None, from_address=None, to_address=None, subject=None, has_the_word=None, does_not_have_the_word=None, has_attachments=None, label=None, mark_as_read=None, archive=None, *args, **kwargs): """Constructs a new EmailSettingsFilter object with the given arguments. Args: uri: string (optional) The uri of this object for HTTP requests. from_address: string (optional) The source email address for the filter. to_address: string (optional) The destination email address for the filter. subject: string (optional) The value the email must have in its subject to be filtered. has_the_word: string (optional) The value the email must have in its subject or body to be filtered. does_not_have_the_word: string (optional) The value the email cannot have in its subject or body to be filtered. has_attachments: Boolean (optional) Whether or not the email must have an attachment to be filtered. label: string (optional) The name of the label to apply to messages matching the filter criteria. mark_as_read: Boolean (optional) Whether or not to mark messages matching the filter criteria as read. archive: Boolean (optional) Whether or not to move messages matching to Archived state. args: The other parameters to pass to gdata.entry.GDEntry constructor. kwargs: The other parameters to pass to gdata.entry.GDEntry constructor. """ super(EmailSettingsFilter, self).__init__(*args, **kwargs) if uri: self.uri = uri if from_address: self.from_address = from_address if to_address: self.to_address = to_address if subject: self.subject = subject if has_the_word: self.has_the_word = has_the_word if does_not_have_the_word: self.does_not_have_the_word = does_not_have_the_word if has_attachments is not None: self.has_attachments = str(has_attachments) if label: self.label = label if mark_as_read is not None: self.mark_as_read = str(mark_as_read) if archive is not None: self.archive = str(archive) class EmailSettingsSendAsAlias(EmailSettingsEntry): """Represents an Email Settings send-as Alias in object form.""" def GetName(self): """Get the Name of the send-as Alias object. Returns: The Name of this send-as Alias object as a string or None. """ return self._GetProperty(SENDAS_ALIAS_NAME) def SetName(self, value): """Set the Name of this send-as Alias object. Args: value: string The new Name to give this object. """ self._SetProperty(SENDAS_ALIAS_NAME, value) name = pyproperty(GetName, SetName) def GetAddress(self): """Get the Address of the send-as Alias object. Returns: The Address of this send-as Alias object as a string or None. """ return self._GetProperty(SENDAS_ALIAS_ADDRESS) def SetAddress(self, value): """Set the Address of this send-as Alias object. Args: value: string The new Address to give this object. """ self._SetProperty(SENDAS_ALIAS_ADDRESS, value) address = pyproperty(GetAddress, SetAddress) def GetReplyTo(self): """Get the ReplyTo address of the send-as Alias object. Returns: The ReplyTo address of this send-as Alias object as a string or None. """ return self._GetProperty(SENDAS_ALIAS_REPLY_TO) def SetReplyTo(self, value): """Set the ReplyTo address of this send-as Alias object. Args: value: string The new ReplyTo address to give this object. """ self._SetProperty(SENDAS_ALIAS_REPLY_TO, value) reply_to = pyproperty(GetReplyTo, SetReplyTo) def GetMakeDefault(self): """Get the MakeDefault value of the send-as Alias object. Returns: The MakeDefault value of this send-as Alias object as a string or None. """ return self._GetProperty(SENDAS_ALIAS_MAKE_DEFAULT) def SetMakeDefault(self, value): """Set the MakeDefault value of this send-as Alias object. Args: value: string The new MakeDefault value to give this object. """ self._SetProperty(SENDAS_ALIAS_MAKE_DEFAULT, value) make_default = pyproperty(GetMakeDefault, SetMakeDefault) def __init__(self, uri=None, name=None, address=None, reply_to=None, make_default=None, *args, **kwargs): """Constructs a new EmailSettingsSendAsAlias object with the given arguments. Args: uri: string (optional) The uri f this object for HTTP requests. name: string (optional) The name that will appear in the "From" field for this user. address: string (optional) The email address that appears as the origination address for emails sent by this user. reply_to: string (optional) The address to be used as the reply-to address in email sent using the alias. make_default: Boolean (optional) Whether or not this alias should become the default alias for this user. args: The other parameters to pass to gdata.entry.GDEntry constructor. kwargs: The other parameters to pass to gdata.entry.GDEntry constructor. """ super(EmailSettingsSendAsAlias, self).__init__(*args, **kwargs) if uri: self.uri = uri if name: self.name = name if address: self.address = address if reply_to: self.reply_to = reply_to if make_default is not None: self.make_default = str(make_default) class EmailSettingsWebClip(EmailSettingsEntry): """Represents a WebClip in object form.""" def GetEnable(self): """Get the Enable value of the WebClip object. Returns: The Enable value of this WebClip object as a string or None. """ return self._GetProperty(WEBCLIP_ENABLE) def SetEnable(self, value): """Set the Enable value of this WebClip object. Args: value: string The new Enable value to give this object. """ self._SetProperty(WEBCLIP_ENABLE, value) enable = pyproperty(GetEnable, SetEnable) def __init__(self, uri=None, enable=None, *args, **kwargs): """Constructs a new EmailSettingsWebClip object with the given arguments. Args: uri: string (optional) The uri of this object for HTTP requests. enable: Boolean (optional) Whether to enable showing Web clips. args: The other parameters to pass to gdata.entry.GDEntry constructor. kwargs: The other parameters to pass to gdata.entry.GDEntry constructor. """ super(EmailSettingsWebClip, self).__init__(*args, **kwargs) if uri: self.uri = uri if enable is not None: self.enable = str(enable) class EmailSettingsForwarding(EmailSettingsEntry): """Represents Forwarding settings in object form.""" def GetEnable(self): """Get the Enable value of the Forwarding object. Returns: The Enable value of this Forwarding object as a string or None. """ return self._GetProperty(FORWARDING_ENABLE) def SetEnable(self, value): """Set the Enable value of this Forwarding object. Args: value: string The new Enable value to give this object. """ self._SetProperty(FORWARDING_ENABLE, value) enable = pyproperty(GetEnable, SetEnable) def GetForwardTo(self): """Get the ForwardTo value of the Forwarding object. Returns: The ForwardTo value of this Forwarding object as a string or None. """ return self._GetProperty(FORWARDING_TO) def SetForwardTo(self, value): """Set the ForwardTo value of this Forwarding object. Args: value: string The new ForwardTo value to give this object. """ self._SetProperty(FORWARDING_TO, value) forward_to = pyproperty(GetForwardTo, SetForwardTo) def GetAction(self): """Get the Action value of the Forwarding object. Returns: The Action value of this Forwarding object as a string or None. """ return self._GetProperty(FORWARDING_ACTION) def SetAction(self, value): """Set the Action value of this Forwarding object. Args: value: string The new Action value to give this object. """ self._SetProperty(FORWARDING_ACTION, value) action = pyproperty(GetAction, SetAction) def __init__(self, uri=None, enable=None, forward_to=None, action=None, *args, **kwargs): """Constructs a new EmailSettingsForwarding object with the given arguments. Args: uri: string (optional) The uri of this object for HTTP requests. enable: Boolean (optional) Whether to enable incoming email forwarding. forward_to: string (optional) The address email will be forwarded to. action: string (optional) The action to perform after forwarding an email ("KEEP", "ARCHIVE", "DELETE"). args: The other parameters to pass to gdata.entry.GDEntry constructor. kwargs: The other parameters to pass to gdata.entry.GDEntry constructor. """ super(EmailSettingsForwarding, self).__init__(*args, **kwargs) if uri: self.uri = uri if enable is not None: self.enable = str(enable) if forward_to: self.forward_to = forward_to if action: self.action = action class EmailSettingsPop(EmailSettingsEntry): """Represents POP settings in object form.""" def GetEnable(self): """Get the Enable value of the POP object. Returns: The Enable value of this POP object as a string or None. """ return self._GetProperty(POP_ENABLE) def SetEnable(self, value): """Set the Enable value of this POP object. Args: value: string The new Enable value to give this object. """ self._SetProperty(POP_ENABLE, value) enable = pyproperty(GetEnable, SetEnable) def GetEnableFor(self): """Get the EnableFor value of the POP object. Returns: The EnableFor value of this POP object as a string or None. """ return self._GetProperty(POP_ENABLE_FOR) def SetEnableFor(self, value): """Set the EnableFor value of this POP object. Args: value: string The new EnableFor value to give this object. """ self._SetProperty(POP_ENABLE_FOR, value) enable_for = pyproperty(GetEnableFor, SetEnableFor) def GetPopAction(self): """Get the Action value of the POP object. Returns: The Action value of this POP object as a string or None. """ return self._GetProperty(POP_ACTION) def SetPopAction(self, value): """Set the Action value of this POP object. Args: value: string The new Action value to give this object. """ self._SetProperty(POP_ACTION, value) action = pyproperty(GetPopAction, SetPopAction) def __init__(self, uri=None, enable=None, enable_for=None, action=None, *args, **kwargs): """Constructs a new EmailSettingsPOP object with the given arguments. Args: uri: string (optional) The uri of this object for HTTP requests. enable: Boolean (optional) Whether to enable incoming POP3 access. enable_for: string (optional) Whether to enable POP3 for all mail ("ALL_MAIL"), or mail from now on ("MAIL_FROM_NOW_ON"). action: string (optional) What Google Mail should do with its copy of the email after it is retrieved using POP ("KEEP", "ARCHIVE", or "DELETE"). args: The other parameters to pass to gdata.entry.GDEntry constructor. kwargs: The other parameters to pass to gdata.entry.GDEntry constructor. """ super(EmailSettingsPop, self).__init__(*args, **kwargs) if uri: self.uri = uri if enable is not None: self.enable = str(enable) if enable_for: self.enable_for = enable_for if action: self.action = action class EmailSettingsImap(EmailSettingsEntry): """Represents IMAP settings in object form.""" def GetEnable(self): """Get the Enable value of the IMAP object. Returns: The Enable value of this IMAP object as a string or None. """ return self._GetProperty(IMAP_ENABLE) def SetEnable(self, value): """Set the Enable value of this IMAP object. Args: value: string The new Enable value to give this object. """ self._SetProperty(IMAP_ENABLE, value) enable = pyproperty(GetEnable, SetEnable) def __init__(self, uri=None, enable=None, *args, **kwargs): """Constructs a new EmailSettingsImap object with the given arguments. Args: uri: string (optional) The uri of this object for HTTP requests. enable: Boolean (optional) Whether to enable IMAP access. args: The other parameters to pass to gdata.entry.GDEntry constructor. kwargs: The other parameters to pass to gdata.entry.GDEntry constructor. """ super(EmailSettingsImap, self).__init__(*args, **kwargs) if uri: self.uri = uri if enable is not None: self.enable = str(enable) class EmailSettingsVacationResponder(EmailSettingsEntry): """Represents Vacation Responder settings in object form.""" def GetEnable(self): """Get the Enable value of the Vacation Responder object. Returns: The Enable value of this Vacation Responder object as a string or None. """ return self._GetProperty(VACATION_RESPONDER_ENABLE) def SetEnable(self, value): """Set the Enable value of this Vacation Responder object. Args: value: string The new Enable value to give this object. """ self._SetProperty(VACATION_RESPONDER_ENABLE, value) enable = pyproperty(GetEnable, SetEnable) def GetSubject(self): """Get the Subject value of the Vacation Responder object. Returns: The Subject value of this Vacation Responder object as a string or None. """ return self._GetProperty(VACATION_RESPONDER_SUBJECT) def SetSubject(self, value): """Set the Subject value of this Vacation Responder object. Args: value: string The new Subject value to give this object. """ self._SetProperty(VACATION_RESPONDER_SUBJECT, value) subject = pyproperty(GetSubject, SetSubject) def GetMessage(self): """Get the Message value of the Vacation Responder object. Returns: The Message value of this Vacation Responder object as a string or None. """ return self._GetProperty(VACATION_RESPONDER_MESSAGE) def SetMessage(self, value): """Set the Message value of this Vacation Responder object. Args: value: string The new Message value to give this object. """ self._SetProperty(VACATION_RESPONDER_MESSAGE, value) message = pyproperty(GetMessage, SetMessage) def GetStartDate(self): """Get the StartDate value of the Vacation Responder object. Returns: The StartDate value of this Vacation Responder object as a string(YYYY-MM-DD) or None. """ return self._GetProperty(VACATION_RESPONDER_STARTDATE) def SetStartDate(self, value): """Set the StartDate value of this Vacation Responder object. Args: value: string The new StartDate value to give this object. """ self._SetProperty(VACATION_RESPONDER_STARTDATE, value) start_date = pyproperty(GetStartDate, SetStartDate) def GetEndDate(self): """Get the EndDate value of the Vacation Responder object. Returns: The EndDate value of this Vacation Responder object as a string(YYYY-MM-DD) or None. """ return self._GetProperty(VACATION_RESPONDER_ENDDATE) def SetEndDate(self, value): """Set the EndDate value of this Vacation Responder object. Args: value: string The new EndDate value to give this object. """ self._SetProperty(VACATION_RESPONDER_ENDDATE, value) end_date = pyproperty(GetEndDate, SetEndDate) def GetContactsOnly(self): """Get the ContactsOnly value of the Vacation Responder object. Returns: The ContactsOnly value of this Vacation Responder object as a string or None. """ return self._GetProperty(VACATION_RESPONDER_CONTACTS_ONLY) def SetContactsOnly(self, value): """Set the ContactsOnly value of this Vacation Responder object. Args: value: string The new ContactsOnly value to give this object. """ self._SetProperty(VACATION_RESPONDER_CONTACTS_ONLY, value) contacts_only = pyproperty(GetContactsOnly, SetContactsOnly) def GetDomainOnly(self): """Get the DomainOnly value of the Vacation Responder object. Returns: The DomainOnly value of this Vacation Responder object as a string or None. """ return self._GetProperty(VACATION_RESPONDER_DOMAIN_ONLY) def SetDomainOnly(self, value): """Set the DomainOnly value of this Vacation Responder object. Args: value: string The new DomainOnly value to give this object. """ self._SetProperty(VACATION_RESPONDER_DOMAIN_ONLY, value) domain_only = pyproperty(GetDomainOnly, SetDomainOnly) def __init__(self, uri=None, enable=None, subject=None, message=None, start_date=None, end_date=None, contacts_only=None, domain_only=None, *args, **kwargs): """Constructs a new EmailSettingsVacationResponder object with the given arguments. Args: uri: string (optional) The uri of this object for HTTP requests. enable: Boolean (optional) Whether to enable the vacation responder. subject: string (optional) The subject line of the vacation responder autoresponse. message: string (optional) The message body of the vacation responder autoresponse. start_date: string (optional) The start date of the vacation responder autoresponse end_date: string (optional) The end date of the vacation responder autoresponse contacts_only: Boolean (optional) Whether to only send autoresponses to known contacts. domain_only: Boolean (optional) Whether to only send autoresponses to users in the same primary domain . args: The other parameters to pass to gdata.entry.GDEntry constructor. kwargs: The other parameters to pass to gdata.entry.GDEntry constructor. """ super(EmailSettingsVacationResponder, self).__init__(*args, **kwargs) if uri: self.uri = uri if enable is not None: self.enable = str(enable) if subject: self.subject = subject if message: self.message = message if start_date: self.start_date = start_date if end_date: self.end_date = end_date if contacts_only is not None: self.contacts_only = str(contacts_only) if domain_only is not None: self.domain_only = str(domain_only) class EmailSettingsSignature(EmailSettingsEntry): """Represents a Signature in object form.""" def GetValue(self): """Get the value of the Signature object. Returns: The value of this Signature object as a string or None. """ value = self._GetProperty(SIGNATURE_VALUE) if value == ' ': # hack to support empty signature return '' else: return value def SetValue(self, value): """Set the name of this Signature object. Args: value: string The new signature value to give this object. """ if value == '': # hack to support empty signature value = ' ' self._SetProperty(SIGNATURE_VALUE, value) signature_value = pyproperty(GetValue, SetValue) def __init__(self, uri=None, signature=None, *args, **kwargs): """Constructs a new EmailSettingsSignature object with the given arguments. Args: uri: string (optional) The uri of this object for HTTP requests. signature: string (optional) The signature to be appended to outgoing messages. args: The other parameters to pass to gdata.entry.GDEntry constructor. kwargs: The other parameters to pass to gdata.entry.GDEntry constructor. """ super(EmailSettingsSignature, self).__init__(*args, **kwargs) if uri: self.uri = uri if signature is not None: self.signature_value = signature class EmailSettingsLanguage(EmailSettingsEntry): """Represents Language Settings in object form.""" def GetLanguage(self): """Get the tag of the Language object. Returns: The tag of this Language object as a string or None. """ return self._GetProperty(LANGUAGE_TAG) def SetLanguage(self, value): """Set the tag of this Language object. Args: value: string The new tag value to give this object. """ self._SetProperty(LANGUAGE_TAG, value) language_tag = pyproperty(GetLanguage, SetLanguage) def __init__(self, uri=None, language=None, *args, **kwargs): """Constructs a new EmailSettingsLanguage object with the given arguments. Args: uri: string (optional) The uri of this object for HTTP requests. language: string (optional) The language tag for Google Mail's display language. args: The other parameters to pass to gdata.entry.GDEntry constructor. kwargs: The other parameters to pass to gdata.entry.GDEntry constructor. """ super(EmailSettingsLanguage, self).__init__(*args, **kwargs) if uri: self.uri = uri if language: self.language_tag = language class EmailSettingsGeneral(EmailSettingsEntry): """Represents General Settings in object form.""" def GetPageSize(self): """Get the Page Size value of the General Settings object. Returns: The Page Size value of this General Settings object as a string or None. """ return self._GetProperty(GENERAL_PAGE_SIZE) def SetPageSize(self, value): """Set the Page Size value of this General Settings object. Args: value: string The new Page Size value to give this object. """ self._SetProperty(GENERAL_PAGE_SIZE, value) page_size = pyproperty(GetPageSize, SetPageSize) def GetShortcuts(self): """Get the Shortcuts value of the General Settings object. Returns: The Shortcuts value of this General Settings object as a string or None. """ return self._GetProperty(GENERAL_SHORTCUTS) def SetShortcuts(self, value): """Set the Shortcuts value of this General Settings object. Args: value: string The new Shortcuts value to give this object. """ self._SetProperty(GENERAL_SHORTCUTS, value) shortcuts = pyproperty(GetShortcuts, SetShortcuts) def GetArrows(self): """Get the Arrows value of the General Settings object. Returns: The Arrows value of this General Settings object as a string or None. """ return self._GetProperty(GENERAL_ARROWS) def SetArrows(self, value): """Set the Arrows value of this General Settings object. Args: value: string The new Arrows value to give this object. """ self._SetProperty(GENERAL_ARROWS, value) arrows = pyproperty(GetArrows, SetArrows) def GetSnippets(self): """Get the Snippets value of the General Settings object. Returns: The Snippets value of this General Settings object as a string or None. """ return self._GetProperty(GENERAL_SNIPPETS) def SetSnippets(self, value): """Set the Snippets value of this General Settings object. Args: value: string The new Snippets value to give this object. """ self._SetProperty(GENERAL_SNIPPETS, value) snippets = pyproperty(GetSnippets, SetSnippets) def GetUnicode(self): """Get the Unicode value of the General Settings object. Returns: The Unicode value of this General Settings object as a string or None. """ return self._GetProperty(GENERAL_UNICODE) def SetUnicode(self, value): """Set the Unicode value of this General Settings object. Args: value: string The new Unicode value to give this object. """ self._SetProperty(GENERAL_UNICODE, value) use_unicode = pyproperty(GetUnicode, SetUnicode) def __init__(self, uri=None, page_size=None, shortcuts=None, arrows=None, snippets=None, use_unicode=None, *args, **kwargs): """Constructs a new EmailSettingsGeneral object with the given arguments. Args: uri: string (optional) The uri of this object for HTTP requests. page_size: int (optional) The number of conversations to be shown per page. shortcuts: Boolean (optional) Whether to enable keyboard shortcuts. arrows: Boolean (optional) Whether to display arrow-shaped personal indicators next to email sent specifically to the user. snippets: Boolean (optional) Whether to display snippets of the messages in the inbox and when searching. use_unicode: Boolean (optional) Whether to use UTF-8 (unicode) encoding for all outgoing messages. args: The other parameters to pass to gdata.entry.GDEntry constructor. kwargs: The other parameters to pass to gdata.entry.GDEntry constructor. """ super(EmailSettingsGeneral, self).__init__(*args, **kwargs) if uri: self.uri = uri if page_size is not None: self.page_size = str(page_size) if shortcuts is not None: self.shortcuts = str(shortcuts) if arrows is not None: self.arrows = str(arrows) if snippets is not None: self.snippets = str(snippets) if use_unicode is not None: self.use_unicode = str(use_unicode) class EmailSettingsDelegation(EmailSettingsEntry): """Represents an Email Settings delegation entry in object form.""" def GetAddress(self): """Get the email address of the delegated user. Returns: The email address of the delegated user as a string or None. """ return self._GetProperty(DELEGATION_ADDRESS) def SetAddress(self, value): """Set the email address of the delegated user. Args: value: string The email address of another user on the same domain """ self._SetProperty(DELEGATION_ADDRESS, value) address = pyproperty(GetAddress, SetAddress) def __init__(self, uri=None, address=None, *args, **kwargs): """Constructs a new EmailSettingsDelegation object with the given arguments. Args: uri: string (optional) The uri of this object for HTTP requests. address: string The email address of the delegated user. """ super(EmailSettingsDelegation, self).__init__(*args, **kwargs) if uri: self.uri = uri if address: self.address = address class EmailSettingsLabelFeed(gdata.data.GDFeed): """Main feed containing a list of labels.""" entry = [EmailSettingsLabel] class EmailSettingsSendAsAliasFeed(gdata.data.GDFeed): """Main feed containing a list of send-as aliases.""" entry = [EmailSettingsSendAsAlias] class EmailSettingsDelegationFeed(gdata.data.GDFeed): """Main feed containing a list of email delegation entries.""" entry = [EmailSettingsDelegation]gdata-2.0.18/src/gdata/apps/emailsettings/__init__.py0000640036466300116100000000112312156622363022526 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright (C) 2008 Google # # 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. gdata-2.0.18/src/gdata/apps/emailsettings/client.py0000640036466300116100000005544512156622363022265 0ustar afshareng00000000000000#!/usr/bin/python2.4 # # Copyright 2010 Google Inc. All Rights Reserved. # # 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. """EmailSettingsClient simplifies Email Settings API calls. EmailSettingsClient extends gdata.client.GDClient to ease interaction with the Google Apps Email Settings API. These interactions include the ability to create labels, filters, aliases, and update web-clip, forwarding, POP, IMAP, vacation-responder, signature, language, and general settings, and retrieve labels, send-as, forwarding, pop, imap, vacation and signature settings. """ __author__ = 'Claudio Cherubino ' import urllib import gdata.apps.emailsettings.data import gdata.client # Email Settings URI template # The strings in this template are eventually replaced with the API version, # Google Apps domain name, username, and settingID, respectively. EMAIL_SETTINGS_URI_TEMPLATE = '/a/feeds/emailsettings/%s/%s/%s/%s' # The settingID value for the label requests SETTING_ID_LABEL = 'label' # The settingID value for the filter requests SETTING_ID_FILTER = 'filter' # The settingID value for the send-as requests SETTING_ID_SENDAS = 'sendas' # The settingID value for the webclip requests SETTING_ID_WEBCLIP = 'webclip' # The settingID value for the forwarding requests SETTING_ID_FORWARDING = 'forwarding' # The settingID value for the POP requests SETTING_ID_POP = 'pop' # The settingID value for the IMAP requests SETTING_ID_IMAP = 'imap' # The settingID value for the vacation responder requests SETTING_ID_VACATION_RESPONDER = 'vacation' # The settingID value for the signature requests SETTING_ID_SIGNATURE = 'signature' # The settingID value for the language requests SETTING_ID_LANGUAGE = 'language' # The settingID value for the general requests SETTING_ID_GENERAL = 'general' # The settingID value for the delegation requests SETTING_ID_DELEGATION = 'delegation' # The KEEP action for the email settings ACTION_KEEP = 'KEEP' # The ARCHIVE action for the email settings ACTION_ARCHIVE = 'ARCHIVE' # The DELETE action for the email settings ACTION_DELETE = 'DELETE' # The ALL_MAIL setting for POP enable_for property POP_ENABLE_FOR_ALL_MAIL = 'ALL_MAIL' # The MAIL_FROM_NOW_ON setting for POP enable_for property POP_ENABLE_FOR_MAIL_FROM_NOW_ON = 'MAIL_FROM_NOW_ON' class EmailSettingsClient(gdata.client.GDClient): """Client extension for the Google Email Settings API service. Attributes: host: string The hostname for the Email Settings API service. api_version: string The version of the Email Settings API. """ host = 'apps-apis.google.com' api_version = '2.0' auth_service = 'apps' auth_scopes = gdata.gauth.AUTH_SCOPES['apps'] ssl = True def __init__(self, domain, auth_token=None, **kwargs): """Constructs a new client for the Email Settings API. Args: domain: string The Google Apps domain with Email Settings. auth_token: (optional) gdata.gauth.ClientLoginToken, AuthSubToken, or OAuthToken which authorizes this client to edit the email settings. kwargs: The other parameters to pass to the gdata.client.GDClient constructor. """ gdata.client.GDClient.__init__(self, auth_token=auth_token, **kwargs) self.domain = domain def make_email_settings_uri(self, username, setting_id): """Creates the URI for the Email Settings API call. Using this client's Google Apps domain, create the URI to setup email settings for the given user in that domain. If params are provided, append them as GET params. Args: username: string The name of the user affected by this setting. setting_id: string The key of the setting to be configured. Returns: A string giving the URI for Email Settings API calls for this client's Google Apps domain. """ if '@' in username: username, domain = username.split('@', 1) else: domain = self.domain uri = EMAIL_SETTINGS_URI_TEMPLATE % (self.api_version, domain, username, setting_id) return uri MakeEmailSettingsUri = make_email_settings_uri def create_label(self, username, name, **kwargs): """Creates a label with the given properties. Args: username: string The name of the user. name: string The name of the label. kwargs: The other parameters to pass to gdata.client.GDClient.post(). Returns: gdata.apps.emailsettings.data.EmailSettingsLabel of the new resource. """ uri = self.MakeEmailSettingsUri(username=username, setting_id=SETTING_ID_LABEL) new_label = gdata.apps.emailsettings.data.EmailSettingsLabel( uri=uri, name=name) return self.post(new_label, uri, **kwargs) CreateLabel = create_label def retrieve_labels(self, username, **kwargs): """Retrieves email labels for the specified username Args: username: string The name of the user to get the labels for Returns: A gdata.data.GDFeed of the user's email labels """ uri = self.MakeEmailSettingsUri(username=username, setting_id=SETTING_ID_LABEL) return self.GetFeed( uri, auth_token=None, query=None, desired_class=gdata.apps.emailsettings.data.EmailSettingsLabelFeed, **kwargs) RetrieveLabels = retrieve_labels def delete_label(self, username, label, **kwargs): """Delete a label from the specified account. Args: username: string Name of the user label: string Name of the label to be deleted Returns: An atom.http_core.HttpResponse() with the result of the request """ uri = self.MakeEmailSettingsUri(username=username, setting_id=SETTING_ID_LABEL) uri = '/'.join([uri, urllib.quote_plus(label)]) return self.delete(uri, **kwargs) DeleteLabel = delete_label def create_filter(self, username, from_address=None, to_address=None, subject=None, has_the_word=None, does_not_have_the_word=None, has_attachments=None, label=None, mark_as_read=None, archive=None, **kwargs): """Creates a filter with the given properties. Args: username: string The name of the user. from_address: string The source email address for the filter. to_address: string (optional) The destination email address for the filter. subject: string (optional) The value the email must have in its subject to be filtered. has_the_word: string (optional) The value the email must have in its subject or body to be filtered. does_not_have_the_word: string (optional) The value the email cannot have in its subject or body to be filtered. has_attachments: string (optional) A boolean string representing whether the email must have an attachment to be filtered. label: string (optional) The name of the label to apply to messages matching the filter criteria. mark_as_read: Boolean (optional) Whether or not to mark messages matching the filter criteria as read. archive: Boolean (optional) Whether or not to move messages matching to Archived state. kwargs: The other parameters to pass to gdata.client.GDClient.post(). Returns: gdata.apps.emailsettings.data.EmailSettingsFilter of the new resource. """ uri = self.MakeEmailSettingsUri(username=username, setting_id=SETTING_ID_FILTER) new_filter = gdata.apps.emailsettings.data.EmailSettingsFilter( uri=uri, from_address=from_address, to_address=to_address, subject=subject, has_the_word=has_the_word, does_not_have_the_word=does_not_have_the_word, has_attachments=has_attachments, label=label, mark_as_read=mark_as_read, archive=archive) return self.post(new_filter, uri, **kwargs) CreateFilter = create_filter def create_send_as(self, username, name, address, reply_to=None, make_default=None, **kwargs): """Creates a send-as alias with the given properties. Args: username: string The name of the user. name: string The name that will appear in the "From" field. address: string The email address that appears as the origination address for emails sent by this user. reply_to: string (optional) The address to be used as the reply-to address in email sent using the alias. make_default: Boolean (optional) Whether or not this alias should become the default alias for this user. kwargs: The other parameters to pass to gdata.client.GDClient.post(). Returns: gdata.apps.emailsettings.data.EmailSettingsSendAsAlias of the new resource. """ uri = self.MakeEmailSettingsUri(username=username, setting_id=SETTING_ID_SENDAS) new_alias = gdata.apps.emailsettings.data.EmailSettingsSendAsAlias( uri=uri, name=name, address=address, reply_to=reply_to, make_default=make_default) return self.post(new_alias, uri, **kwargs) CreateSendAs = create_send_as def retrieve_send_as(self, username, **kwargs): """Retrieves send-as aliases for the specified username Args: username: string The name of the user to get the send-as for Returns: A gdata.data.GDFeed of the user's send-as alias settings """ uri = self.MakeEmailSettingsUri(username=username, setting_id=SETTING_ID_SENDAS) return self.GetFeed( uri, auth_token=None, query=None, desired_class=gdata.apps.emailsettings.data.EmailSettingsSendAsAliasFeed, **kwargs) RetrieveSendAs = retrieve_send_as def update_webclip(self, username, enable, **kwargs): """Enable/Disable Google Mail web clip. Args: username: string The name of the user. enable: Boolean Whether to enable showing Web clips. kwargs: The other parameters to pass to the update method. Returns: gdata.apps.emailsettings.data.EmailSettingsWebClip of the updated resource. """ uri = self.MakeEmailSettingsUri(username=username, setting_id=SETTING_ID_WEBCLIP) new_webclip = gdata.apps.emailsettings.data.EmailSettingsWebClip( uri=uri, enable=enable) return self.update(new_webclip, **kwargs) UpdateWebclip = update_webclip def update_forwarding(self, username, enable, forward_to=None, action=None, **kwargs): """Update Google Mail Forwarding settings. Args: username: string The name of the user. enable: Boolean Whether to enable incoming email forwarding. forward_to: (optional) string The address email will be forwarded to. action: string (optional) The action to perform after forwarding an email (ACTION_KEEP, ACTION_ARCHIVE, ACTION_DELETE). kwargs: The other parameters to pass to the update method. Returns: gdata.apps.emailsettings.data.EmailSettingsForwarding of the updated resource """ uri = self.MakeEmailSettingsUri(username=username, setting_id=SETTING_ID_FORWARDING) new_forwarding = gdata.apps.emailsettings.data.EmailSettingsForwarding( uri=uri, enable=enable, forward_to=forward_to, action=action) return self.update(new_forwarding, **kwargs) UpdateForwarding = update_forwarding def retrieve_forwarding(self, username, **kwargs): """Retrieves forwarding settings for the specified username Args: username: string The name of the user to get the forwarding settings for Returns: A gdata.data.GDEntry of the user's email forwarding settings """ uri = self.MakeEmailSettingsUri(username=username, setting_id=SETTING_ID_FORWARDING) return self.GetEntry( uri, auth_token=None, query=None, desired_class=gdata.apps.emailsettings.data.EmailSettingsForwarding, **kwargs) RetrieveForwarding = retrieve_forwarding def update_pop(self, username, enable, enable_for=None, action=None, **kwargs): """Update Google Mail POP settings. Args: username: string The name of the user. enable: Boolean Whether to enable incoming POP3 access. enable_for: string (optional) Whether to enable POP3 for all mail (POP_ENABLE_FOR_ALL_MAIL), or mail from now on (POP_ENABLE_FOR_MAIL_FROM_NOW_ON). action: string (optional) What Google Mail should do with its copy of the email after it is retrieved using POP (ACTION_KEEP, ACTION_ARCHIVE, ACTION_DELETE). kwargs: The other parameters to pass to the update method. Returns: gdata.apps.emailsettings.data.EmailSettingsPop of the updated resource. """ uri = self.MakeEmailSettingsUri(username=username, setting_id=SETTING_ID_POP) new_pop = gdata.apps.emailsettings.data.EmailSettingsPop( uri=uri, enable=enable, enable_for=enable_for, action=action) return self.update(new_pop, **kwargs) UpdatePop = update_pop def retrieve_pop(self, username, **kwargs): """Retrieves POP settings for the specified username Args: username: string The name of the user to get the POP settings for Returns: A gdata.data.GDEntry of the user's POP settings """ uri = self.MakeEmailSettingsUri(username=username, setting_id=SETTING_ID_POP) return self.GetEntry( uri, auth_token=None, query=None, desired_class=gdata.apps.emailsettings.data.EmailSettingsPop, **kwargs) RetrievePop = retrieve_pop def update_imap(self, username, enable, **kwargs): """Update Google Mail IMAP settings. Args: username: string The name of the user. enable: Boolean Whether to enable IMAP access.language kwargs: The other parameters to pass to the update method. Returns: gdata.apps.emailsettings.data.EmailSettingsImap of the updated resource. """ uri = self.MakeEmailSettingsUri(username=username, setting_id=SETTING_ID_IMAP) new_imap = gdata.apps.emailsettings.data.EmailSettingsImap( uri=uri, enable=enable) return self.update(new_imap, **kwargs) UpdateImap = update_imap def retrieve_imap(self, username, **kwargs): """Retrieves imap settings for the specified username Args: username: string The name of the user to get the imap settings for Returns: A gdata.data.GDEntry of the user's IMAP settings """ uri = self.MakeEmailSettingsUri(username=username, setting_id=SETTING_ID_IMAP) return self.GetEntry( uri, auth_token=None, query=None, desired_class=gdata.apps.emailsettings.data.EmailSettingsImap, **kwargs) RetrieveImap = retrieve_imap def update_vacation(self, username, enable, subject=None, message=None, start_date=None, end_date=None, contacts_only=None, domain_only=None, **kwargs): """Update Google Mail vacation-responder settings. Args: username: string The name of the user. enable: Boolean Whether to enable the vacation responder. subject: string (optional) The subject line of the vacation responder autoresponse. message: string (optional) The message body of the vacation responder autoresponse. startDate: string (optional) The start date of the vacation responder autoresponse. endDate: string (optional) The end date of the vacation responder autoresponse. contacts_only: Boolean (optional) Whether to only send autoresponses to known contacts. domain_only: Boolean (optional) Whether to only send autoresponses to users in the primary domain. kwargs: The other parameters to pass to the update method. Returns: gdata.apps.emailsettings.data.EmailSettingsVacationResponder of the updated resource. """ uri = self.MakeEmailSettingsUri(username=username, setting_id=SETTING_ID_VACATION_RESPONDER) new_vacation = gdata.apps.emailsettings.data.EmailSettingsVacationResponder( uri=uri, enable=enable, subject=subject, message=message, start_date=start_date, end_date=end_date, contacts_only=contacts_only, domain_only=domain_only) return self.update(new_vacation, **kwargs) UpdateVacation = update_vacation def retrieve_vacation(self, username, **kwargs): """Retrieves vacation settings for the specified username Args: username: string The name of the user to get the vacation settings for Returns: A gdata.data.GDEntry of the user's vacation auto-responder settings """ uri = self.MakeEmailSettingsUri(username=username, setting_id=SETTING_ID_VACATION_RESPONDER) return self.GetEntry( uri, auth_token=None, query=None, desired_class= gdata.apps.emailsettings.data.EmailSettingsVacationResponder, **kwargs) RetrieveVacation = retrieve_vacation def update_signature(self, username, signature, **kwargs): """Update Google Mail signature. Args: username: string The name of the user. signature: string The signature to be appended to outgoing messages. kwargs: The other parameters to pass to the update method. Returns: gdata.apps.emailsettings.data.EmailSettingsSignature of the updated resource. """ uri = self.MakeEmailSettingsUri(username=username, setting_id=SETTING_ID_SIGNATURE) new_signature = gdata.apps.emailsettings.data.EmailSettingsSignature( uri=uri, signature=signature) return self.update(new_signature, **kwargs) UpdateSignature = update_signature def retrieve_signature(self, username, **kwargs): """Retrieves signature settings for the specified username Args: username: string The name of the user to get the signature settings for Returns: A gdata.data.GDEntry of the user's signature settings """ uri = self.MakeEmailSettingsUri(username=username, setting_id=SETTING_ID_SIGNATURE) return self.GetEntry( uri, auth_token=None, query=None, desired_class=gdata.apps.emailsettings.data.EmailSettingsSignature, **kwargs) RetrieveSignature = retrieve_signature def update_language(self, username, language, **kwargs): """Update Google Mail language settings. Args: username: string The name of the user. language: string The language tag for Google Mail's display language. kwargs: The other parameters to pass to the update method. Returns: gdata.apps.emailsettings.data.EmailSettingsLanguage of the updated resource. """ uri = self.MakeEmailSettingsUri(username=username, setting_id=SETTING_ID_LANGUAGE) new_language = gdata.apps.emailsettings.data.EmailSettingsLanguage( uri=uri, language=language) return self.update(new_language, **kwargs) UpdateLanguage = update_language def update_general_settings(self, username, page_size=None, shortcuts=None, arrows=None, snippets=None, use_unicode=None, **kwargs): """Update Google Mail general settings. Args: username: string The name of the user. page_size: int (optional) The number of conversations to be shown per page. shortcuts: Boolean (optional) Whether to enable keyboard shortcuts. arrows: Boolean (optional) Whether to display arrow-shaped personal indicators next to email sent specifically to the user. snippets: Boolean (optional) Whether to display snippets of the messages in the inbox and when searching. use_unicode: Boolean (optional) Whether to use UTF-8 (unicode) encoding for all outgoing messages. kwargs: The other parameters to pass to the update method. Returns: gdata.apps.emailsettings.data.EmailSettingsGeneral of the updated resource. """ uri = self.MakeEmailSettingsUri(username=username, setting_id=SETTING_ID_GENERAL) new_general = gdata.apps.emailsettings.data.EmailSettingsGeneral( uri=uri, page_size=page_size, shortcuts=shortcuts, arrows=arrows, snippets=snippets, use_unicode=use_unicode) return self.update(new_general, **kwargs) UpdateGeneralSettings = update_general_settings def add_email_delegate(self, username, address, **kwargs): """Add an email delegate to the mail account Args: username: string The name of the user address: string The email address of the delegated account """ uri = self.MakeEmailSettingsUri(username=username, setting_id=SETTING_ID_DELEGATION) new_delegation = gdata.apps.emailsettings.data.EmailSettingsDelegation( uri=uri, address=address) return self.post(new_delegation, uri, **kwargs) AddEmailDelegate = add_email_delegate def retrieve_email_delegates(self, username, **kwargs): """Retrieve a feed of the email delegates for the specified username Args: username: string The name of the user to get the email delegates for Returns: A gdata.data.GDFeed of the user's email delegates """ uri = self.MakeEmailSettingsUri(username=username, setting_id=SETTING_ID_DELEGATION) return self.GetFeed( uri, auth_token=None, query=None, desired_class=gdata.apps.emailsettings.data.EmailSettingsDelegationFeed, **kwargs) RetrieveEmailDelegates = retrieve_email_delegates def delete_email_delegate(self, username, address, **kwargs): """Delete an email delegate from the specified account Args: username: string The name of the user address: string The email address of the delegated account """ uri = self.MakeEmailSettingsUri(username=username, setting_id=SETTING_ID_DELEGATION) uri = uri + '/' + address return self.delete(uri, **kwargs) DeleteEmailDelegate = delete_email_delegate gdata-2.0.18/src/gdata/apps/emailsettings/service.py0000640036466300116100000002135312156622363022436 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright (C) 2008 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. """Allow Google Apps domain administrators to set users' email settings. EmailSettingsService: Set various email settings. """ __author__ = 'google-apps-apis@googlegroups.com' import gdata.apps import gdata.apps.service import gdata.service API_VER='2.0' # Forwarding and POP3 options KEEP='KEEP' ARCHIVE='ARCHIVE' DELETE='DELETE' ALL_MAIL='ALL_MAIL' MAIL_FROM_NOW_ON='MAIL_FROM_NOW_ON' class EmailSettingsService(gdata.apps.service.PropertyService): """Client for the Google Apps Email Settings service.""" def _serviceUrl(self, setting_id, username, domain=None): if domain is None: domain = self.domain return '/a/feeds/emailsettings/%s/%s/%s/%s' % (API_VER, domain, username, setting_id) def CreateLabel(self, username, label): """Create a label. Args: username: User to create label for. label: Label to create. Returns: A dict containing the result of the create operation. """ uri = self._serviceUrl('label', username) properties = {'label': label} return self._PostProperties(uri, properties) def CreateFilter(self, username, from_=None, to=None, subject=None, has_the_word=None, does_not_have_the_word=None, has_attachment=None, label=None, should_mark_as_read=None, should_archive=None): """Create a filter. Args: username: User to create filter for. from_: Filter from string. to: Filter to string. subject: Filter subject. has_the_word: Words to filter in. does_not_have_the_word: Words to filter out. has_attachment: Boolean for message having attachment. label: Label to apply. should_mark_as_read: Boolean for marking message as read. should_archive: Boolean for archiving message. Returns: A dict containing the result of the create operation. """ uri = self._serviceUrl('filter', username) properties = {} properties['from'] = from_ properties['to'] = to properties['subject'] = subject properties['hasTheWord'] = has_the_word properties['doesNotHaveTheWord'] = does_not_have_the_word properties['hasAttachment'] = gdata.apps.service._bool2str(has_attachment) properties['label'] = label properties['shouldMarkAsRead'] = gdata.apps.service._bool2str(should_mark_as_read) properties['shouldArchive'] = gdata.apps.service._bool2str(should_archive) return self._PostProperties(uri, properties) def CreateSendAsAlias(self, username, name, address, reply_to=None, make_default=None): """Create alias to send mail as. Args: username: User to create alias for. name: Name of alias. address: Email address to send from. reply_to: Email address to reply to. make_default: Boolean for whether this is the new default sending alias. Returns: A dict containing the result of the create operation. """ uri = self._serviceUrl('sendas', username) properties = {} properties['name'] = name properties['address'] = address properties['replyTo'] = reply_to properties['makeDefault'] = gdata.apps.service._bool2str(make_default) return self._PostProperties(uri, properties) def UpdateWebClipSettings(self, username, enable): """Update WebClip Settings Args: username: User to update forwarding for. enable: Boolean whether to enable Web Clip. Returns: A dict containing the result of the update operation. """ uri = self._serviceUrl('webclip', username) properties = {} properties['enable'] = gdata.apps.service._bool2str(enable) return self._PutProperties(uri, properties) def UpdateForwarding(self, username, enable, forward_to=None, action=None): """Update forwarding settings. Args: username: User to update forwarding for. enable: Boolean whether to enable this forwarding rule. forward_to: Email address to forward to. action: Action to take after forwarding. Returns: A dict containing the result of the update operation. """ uri = self._serviceUrl('forwarding', username) properties = {} properties['enable'] = gdata.apps.service._bool2str(enable) if enable is True: properties['forwardTo'] = forward_to properties['action'] = action return self._PutProperties(uri, properties) def UpdatePop(self, username, enable, enable_for=None, action=None): """Update POP3 settings. Args: username: User to update POP3 settings for. enable: Boolean whether to enable POP3. enable_for: Which messages to make available via POP3. action: Action to take after user retrieves email via POP3. Returns: A dict containing the result of the update operation. """ uri = self._serviceUrl('pop', username) properties = {} properties['enable'] = gdata.apps.service._bool2str(enable) if enable is True: properties['enableFor'] = enable_for properties['action'] = action return self._PutProperties(uri, properties) def UpdateImap(self, username, enable): """Update IMAP settings. Args: username: User to update IMAP settings for. enable: Boolean whether to enable IMAP. Returns: A dict containing the result of the update operation. """ uri = self._serviceUrl('imap', username) properties = {'enable': gdata.apps.service._bool2str(enable)} return self._PutProperties(uri, properties) def UpdateVacation(self, username, enable, subject=None, message=None, contacts_only=None): """Update vacation settings. Args: username: User to update vacation settings for. enable: Boolean whether to enable vacation responses. subject: Vacation message subject. message: Vacation message body. contacts_only: Boolean whether to send message only to contacts. Returns: A dict containing the result of the update operation. """ uri = self._serviceUrl('vacation', username) properties = {} properties['enable'] = gdata.apps.service._bool2str(enable) if enable is True: properties['subject'] = subject properties['message'] = message properties['contactsOnly'] = gdata.apps.service._bool2str(contacts_only) return self._PutProperties(uri, properties) def UpdateSignature(self, username, signature): """Update signature. Args: username: User to update signature for. signature: Signature string. Returns: A dict containing the result of the update operation. """ uri = self._serviceUrl('signature', username) properties = {'signature': signature} return self._PutProperties(uri, properties) def UpdateLanguage(self, username, language): """Update user interface language. Args: username: User to update language for. language: Language code. Returns: A dict containing the result of the update operation. """ uri = self._serviceUrl('language', username) properties = {'language': language} return self._PutProperties(uri, properties) def UpdateGeneral(self, username, page_size=None, shortcuts=None, arrows=None, snippets=None, unicode=None): """Update general settings. Args: username: User to update general settings for. page_size: Number of messages to show. shortcuts: Boolean whether shortcuts are enabled. arrows: Boolean whether arrows are enabled. snippets: Boolean whether snippets are enabled. unicode: Wheter unicode is enabled. Returns: A dict containing the result of the update operation. """ uri = self._serviceUrl('general', username) properties = {} if page_size != None: properties['pageSize'] = str(page_size) if shortcuts != None: properties['shortcuts'] = gdata.apps.service._bool2str(shortcuts) if arrows != None: properties['arrows'] = gdata.apps.service._bool2str(arrows) if snippets != None: properties['snippets'] = gdata.apps.service._bool2str(snippets) if unicode != None: properties['unicode'] = gdata.apps.service._bool2str(unicode) return self._PutProperties(uri, properties) gdata-2.0.18/src/gdata/apps/data.py0000640036466300116100000000350712156622363017040 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright 2010 Google Inc. All Rights Reserved. # # 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. """Data model classes for the Provisioning API.""" __author__ = 'Shraddha Gupta shraddhag@google.com>' import atom.core import atom.data import gdata.apps import gdata.data class Login(atom.core.XmlElement): _qname = gdata.apps.APPS_TEMPLATE % 'login' user_name = 'userName' password = 'password' hash_function_name = 'hashFunctionName' suspended = 'suspended' admin = 'admin' agreed_to_terms = 'agreedToTerms' change_password = 'changePasswordAtNextLogin' ip_whitelisted = 'ipWhitelisted' class Name(atom.core.XmlElement): _qname = gdata.apps.APPS_TEMPLATE % 'name' given_name = 'givenName' family_name = 'familyName' class Quota(atom.core.XmlElement): _qname = gdata.apps.APPS_TEMPLATE % 'quota' limit = 'limit' class UserEntry(gdata.data.GDEntry): _qname = atom.data.ATOM_TEMPLATE % 'entry' login = Login name = Name quota = Quota class UserFeed(gdata.data.GDFeed): entry = [UserEntry] class Nickname(atom.core.XmlElement): _qname = gdata.apps.APPS_TEMPLATE % 'nickname' name = 'name' class NicknameEntry(gdata.data.GDEntry): _qname = atom.data.ATOM_TEMPLATE % 'entry' nickname = Nickname login = Login class NicknameFeed(gdata.data.GDFeed): entry = [NicknameEntry] gdata-2.0.18/src/gdata/apps/__init__.py0000640036466300116100000005123012156622363017662 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright (C) 2007 SIOS Technology, 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. """Contains objects used with Google Apps.""" __author__ = 'tmatsuo@sios.com (Takashi MATSUO)' import atom import gdata # XML namespaces which are often used in Google Apps entity. APPS_NAMESPACE = 'http://schemas.google.com/apps/2006' APPS_TEMPLATE = '{http://schemas.google.com/apps/2006}%s' class EmailList(atom.AtomBase): """The Google Apps EmailList element""" _tag = 'emailList' _namespace = APPS_NAMESPACE _children = atom.AtomBase._children.copy() _attributes = atom.AtomBase._attributes.copy() _attributes['name'] = 'name' def __init__(self, name=None, extension_elements=None, extension_attributes=None, text=None): self.name = name self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} def EmailListFromString(xml_string): return atom.CreateClassFromXMLString(EmailList, xml_string) class Who(atom.AtomBase): """The Google Apps Who element""" _tag = 'who' _namespace = gdata.GDATA_NAMESPACE _children = atom.AtomBase._children.copy() _attributes = atom.AtomBase._attributes.copy() _attributes['rel'] = 'rel' _attributes['email'] = 'email' def __init__(self, rel=None, email=None, extension_elements=None, extension_attributes=None, text=None): self.rel = rel self.email = email self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} def WhoFromString(xml_string): return atom.CreateClassFromXMLString(Who, xml_string) class Login(atom.AtomBase): """The Google Apps Login element""" _tag = 'login' _namespace = APPS_NAMESPACE _children = atom.AtomBase._children.copy() _attributes = atom.AtomBase._attributes.copy() _attributes['userName'] = 'user_name' _attributes['password'] = 'password' _attributes['suspended'] = 'suspended' _attributes['admin'] = 'admin' _attributes['changePasswordAtNextLogin'] = 'change_password' _attributes['agreedToTerms'] = 'agreed_to_terms' _attributes['ipWhitelisted'] = 'ip_whitelisted' _attributes['hashFunctionName'] = 'hash_function_name' def __init__(self, user_name=None, password=None, suspended=None, ip_whitelisted=None, hash_function_name=None, admin=None, change_password=None, agreed_to_terms=None, extension_elements=None, extension_attributes=None, text=None): self.user_name = user_name self.password = password self.suspended = suspended self.admin = admin self.change_password = change_password self.agreed_to_terms = agreed_to_terms self.ip_whitelisted = ip_whitelisted self.hash_function_name = hash_function_name self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} def LoginFromString(xml_string): return atom.CreateClassFromXMLString(Login, xml_string) class Quota(atom.AtomBase): """The Google Apps Quota element""" _tag = 'quota' _namespace = APPS_NAMESPACE _children = atom.AtomBase._children.copy() _attributes = atom.AtomBase._attributes.copy() _attributes['limit'] = 'limit' def __init__(self, limit=None, extension_elements=None, extension_attributes=None, text=None): self.limit = limit self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} def QuotaFromString(xml_string): return atom.CreateClassFromXMLString(Quota, xml_string) class Name(atom.AtomBase): """The Google Apps Name element""" _tag = 'name' _namespace = APPS_NAMESPACE _children = atom.AtomBase._children.copy() _attributes = atom.AtomBase._attributes.copy() _attributes['familyName'] = 'family_name' _attributes['givenName'] = 'given_name' def __init__(self, family_name=None, given_name=None, extension_elements=None, extension_attributes=None, text=None): self.family_name = family_name self.given_name = given_name self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} def NameFromString(xml_string): return atom.CreateClassFromXMLString(Name, xml_string) class Nickname(atom.AtomBase): """The Google Apps Nickname element""" _tag = 'nickname' _namespace = APPS_NAMESPACE _children = atom.AtomBase._children.copy() _attributes = atom.AtomBase._attributes.copy() _attributes['name'] = 'name' def __init__(self, name=None, extension_elements=None, extension_attributes=None, text=None): self.name = name self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} def NicknameFromString(xml_string): return atom.CreateClassFromXMLString(Nickname, xml_string) class NicknameEntry(gdata.GDataEntry): """A Google Apps flavor of an Atom Entry for Nickname""" _tag = 'entry' _namespace = atom.ATOM_NAMESPACE _children = gdata.GDataEntry._children.copy() _attributes = gdata.GDataEntry._attributes.copy() _children['{%s}login' % APPS_NAMESPACE] = ('login', Login) _children['{%s}nickname' % APPS_NAMESPACE] = ('nickname', Nickname) def __init__(self, author=None, category=None, content=None, atom_id=None, link=None, published=None, title=None, updated=None, login=None, nickname=None, extended_property=None, extension_elements=None, extension_attributes=None, text=None): gdata.GDataEntry.__init__(self, author=author, category=category, content=content, atom_id=atom_id, link=link, published=published, title=title, updated=updated) self.login = login self.nickname = nickname self.extended_property = extended_property or [] self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} def NicknameEntryFromString(xml_string): return atom.CreateClassFromXMLString(NicknameEntry, xml_string) class NicknameFeed(gdata.GDataFeed, gdata.LinkFinder): """A Google Apps Nickname feed flavor of an Atom Feed""" _tag = 'feed' _namespace = atom.ATOM_NAMESPACE _children = gdata.GDataFeed._children.copy() _attributes = gdata.GDataFeed._attributes.copy() _children['{%s}entry' % atom.ATOM_NAMESPACE] = ('entry', [NicknameEntry]) def __init__(self, author=None, category=None, contributor=None, generator=None, icon=None, atom_id=None, link=None, logo=None, rights=None, subtitle=None, title=None, updated=None, entry=None, total_results=None, start_index=None, items_per_page=None, extension_elements=None, extension_attributes=None, text=None): gdata.GDataFeed.__init__(self, author=author, category=category, contributor=contributor, generator=generator, icon=icon, atom_id=atom_id, link=link, logo=logo, rights=rights, subtitle=subtitle, title=title, updated=updated, entry=entry, total_results=total_results, start_index=start_index, items_per_page=items_per_page, extension_elements=extension_elements, extension_attributes=extension_attributes, text=text) def NicknameFeedFromString(xml_string): return atom.CreateClassFromXMLString(NicknameFeed, xml_string) class UserEntry(gdata.GDataEntry): """A Google Apps flavor of an Atom Entry""" _tag = 'entry' _namespace = atom.ATOM_NAMESPACE _children = gdata.GDataEntry._children.copy() _attributes = gdata.GDataEntry._attributes.copy() _children['{%s}login' % APPS_NAMESPACE] = ('login', Login) _children['{%s}name' % APPS_NAMESPACE] = ('name', Name) _children['{%s}quota' % APPS_NAMESPACE] = ('quota', Quota) # This child may already be defined in GDataEntry, confirm before removing. _children['{%s}feedLink' % gdata.GDATA_NAMESPACE] = ('feed_link', [gdata.FeedLink]) _children['{%s}who' % gdata.GDATA_NAMESPACE] = ('who', Who) def __init__(self, author=None, category=None, content=None, atom_id=None, link=None, published=None, title=None, updated=None, login=None, name=None, quota=None, who=None, feed_link=None, extended_property=None, extension_elements=None, extension_attributes=None, text=None): gdata.GDataEntry.__init__(self, author=author, category=category, content=content, atom_id=atom_id, link=link, published=published, title=title, updated=updated) self.login = login self.name = name self.quota = quota self.who = who self.feed_link = feed_link or [] self.extended_property = extended_property or [] self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} def UserEntryFromString(xml_string): return atom.CreateClassFromXMLString(UserEntry, xml_string) class UserFeed(gdata.GDataFeed, gdata.LinkFinder): """A Google Apps User feed flavor of an Atom Feed""" _tag = 'feed' _namespace = atom.ATOM_NAMESPACE _children = gdata.GDataFeed._children.copy() _attributes = gdata.GDataFeed._attributes.copy() _children['{%s}entry' % atom.ATOM_NAMESPACE] = ('entry', [UserEntry]) def __init__(self, author=None, category=None, contributor=None, generator=None, icon=None, atom_id=None, link=None, logo=None, rights=None, subtitle=None, title=None, updated=None, entry=None, total_results=None, start_index=None, items_per_page=None, extension_elements=None, extension_attributes=None, text=None): gdata.GDataFeed.__init__(self, author=author, category=category, contributor=contributor, generator=generator, icon=icon, atom_id=atom_id, link=link, logo=logo, rights=rights, subtitle=subtitle, title=title, updated=updated, entry=entry, total_results=total_results, start_index=start_index, items_per_page=items_per_page, extension_elements=extension_elements, extension_attributes=extension_attributes, text=text) def UserFeedFromString(xml_string): return atom.CreateClassFromXMLString(UserFeed, xml_string) class EmailListEntry(gdata.GDataEntry): """A Google Apps EmailList flavor of an Atom Entry""" _tag = 'entry' _namespace = atom.ATOM_NAMESPACE _children = gdata.GDataEntry._children.copy() _attributes = gdata.GDataEntry._attributes.copy() _children['{%s}emailList' % APPS_NAMESPACE] = ('email_list', EmailList) # Might be able to remove this _children entry. _children['{%s}feedLink' % gdata.GDATA_NAMESPACE] = ('feed_link', [gdata.FeedLink]) def __init__(self, author=None, category=None, content=None, atom_id=None, link=None, published=None, title=None, updated=None, email_list=None, feed_link=None, extended_property=None, extension_elements=None, extension_attributes=None, text=None): gdata.GDataEntry.__init__(self, author=author, category=category, content=content, atom_id=atom_id, link=link, published=published, title=title, updated=updated) self.email_list = email_list self.feed_link = feed_link or [] self.extended_property = extended_property or [] self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} def EmailListEntryFromString(xml_string): return atom.CreateClassFromXMLString(EmailListEntry, xml_string) class EmailListFeed(gdata.GDataFeed, gdata.LinkFinder): """A Google Apps EmailList feed flavor of an Atom Feed""" _tag = 'feed' _namespace = atom.ATOM_NAMESPACE _children = gdata.GDataFeed._children.copy() _attributes = gdata.GDataFeed._attributes.copy() _children['{%s}entry' % atom.ATOM_NAMESPACE] = ('entry', [EmailListEntry]) def __init__(self, author=None, category=None, contributor=None, generator=None, icon=None, atom_id=None, link=None, logo=None, rights=None, subtitle=None, title=None, updated=None, entry=None, total_results=None, start_index=None, items_per_page=None, extension_elements=None, extension_attributes=None, text=None): gdata.GDataFeed.__init__(self, author=author, category=category, contributor=contributor, generator=generator, icon=icon, atom_id=atom_id, link=link, logo=logo, rights=rights, subtitle=subtitle, title=title, updated=updated, entry=entry, total_results=total_results, start_index=start_index, items_per_page=items_per_page, extension_elements=extension_elements, extension_attributes=extension_attributes, text=text) def EmailListFeedFromString(xml_string): return atom.CreateClassFromXMLString(EmailListFeed, xml_string) class EmailListRecipientEntry(gdata.GDataEntry): """A Google Apps EmailListRecipient flavor of an Atom Entry""" _tag = 'entry' _namespace = atom.ATOM_NAMESPACE _children = gdata.GDataEntry._children.copy() _attributes = gdata.GDataEntry._attributes.copy() _children['{%s}who' % gdata.GDATA_NAMESPACE] = ('who', Who) def __init__(self, author=None, category=None, content=None, atom_id=None, link=None, published=None, title=None, updated=None, who=None, extended_property=None, extension_elements=None, extension_attributes=None, text=None): gdata.GDataEntry.__init__(self, author=author, category=category, content=content, atom_id=atom_id, link=link, published=published, title=title, updated=updated) self.who = who self.extended_property = extended_property or [] self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} def EmailListRecipientEntryFromString(xml_string): return atom.CreateClassFromXMLString(EmailListRecipientEntry, xml_string) class EmailListRecipientFeed(gdata.GDataFeed, gdata.LinkFinder): """A Google Apps EmailListRecipient feed flavor of an Atom Feed""" _tag = 'feed' _namespace = atom.ATOM_NAMESPACE _children = gdata.GDataFeed._children.copy() _attributes = gdata.GDataFeed._attributes.copy() _children['{%s}entry' % atom.ATOM_NAMESPACE] = ('entry', [EmailListRecipientEntry]) def __init__(self, author=None, category=None, contributor=None, generator=None, icon=None, atom_id=None, link=None, logo=None, rights=None, subtitle=None, title=None, updated=None, entry=None, total_results=None, start_index=None, items_per_page=None, extension_elements=None, extension_attributes=None, text=None): gdata.GDataFeed.__init__(self, author=author, category=category, contributor=contributor, generator=generator, icon=icon, atom_id=atom_id, link=link, logo=logo, rights=rights, subtitle=subtitle, title=title, updated=updated, entry=entry, total_results=total_results, start_index=start_index, items_per_page=items_per_page, extension_elements=extension_elements, extension_attributes=extension_attributes, text=text) def EmailListRecipientFeedFromString(xml_string): return atom.CreateClassFromXMLString(EmailListRecipientFeed, xml_string) class Property(atom.AtomBase): """The Google Apps Property element""" _tag = 'property' _namespace = APPS_NAMESPACE _children = atom.AtomBase._children.copy() _attributes = atom.AtomBase._attributes.copy() _attributes['name'] = 'name' _attributes['value'] = 'value' def __init__(self, name=None, value=None, extension_elements=None, extension_attributes=None, text=None): self.name = name self.value = value self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} def PropertyFromString(xml_string): return atom.CreateClassFromXMLString(Property, xml_string) class PropertyEntry(gdata.GDataEntry): """A Google Apps Property flavor of an Atom Entry""" _tag = 'entry' _namespace = atom.ATOM_NAMESPACE _children = gdata.GDataEntry._children.copy() _attributes = gdata.GDataEntry._attributes.copy() _children['{%s}property' % APPS_NAMESPACE] = ('property', [Property]) def __init__(self, author=None, category=None, content=None, atom_id=None, link=None, published=None, title=None, updated=None, property=None, extended_property=None, extension_elements=None, extension_attributes=None, text=None): gdata.GDataEntry.__init__(self, author=author, category=category, content=content, atom_id=atom_id, link=link, published=published, title=title, updated=updated) self.property = property self.extended_property = extended_property or [] self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} def PropertyEntryFromString(xml_string): return atom.CreateClassFromXMLString(PropertyEntry, xml_string) class PropertyFeed(gdata.GDataFeed, gdata.LinkFinder): """A Google Apps Property feed flavor of an Atom Feed""" _tag = 'feed' _namespace = atom.ATOM_NAMESPACE _children = gdata.GDataFeed._children.copy() _attributes = gdata.GDataFeed._attributes.copy() _children['{%s}entry' % atom.ATOM_NAMESPACE] = ('entry', [PropertyEntry]) def __init__(self, author=None, category=None, contributor=None, generator=None, icon=None, atom_id=None, link=None, logo=None, rights=None, subtitle=None, title=None, updated=None, entry=None, total_results=None, start_index=None, items_per_page=None, extension_elements=None, extension_attributes=None, text=None): gdata.GDataFeed.__init__(self, author=author, category=category, contributor=contributor, generator=generator, icon=icon, atom_id=atom_id, link=link, logo=logo, rights=rights, subtitle=subtitle, title=title, updated=updated, entry=entry, total_results=total_results, start_index=start_index, items_per_page=items_per_page, extension_elements=extension_elements, extension_attributes=extension_attributes, text=text) def PropertyFeedFromString(xml_string): return atom.CreateClassFromXMLString(PropertyFeed, xml_string) gdata-2.0.18/src/gdata/apps/audit/0000750036466300116100000000000012156625015016652 5ustar afshareng00000000000000gdata-2.0.18/src/gdata/apps/audit/__init__.py0000640036466300116100000000000112156622363020756 0ustar afshareng00000000000000 gdata-2.0.18/src/gdata/apps/audit/service.py0000640036466300116100000002244612156622363020700 0ustar afshareng00000000000000# Copyright (C) 2008 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. """Allow Google Apps domain administrators to audit user data. AuditService: Set auditing.""" __author__ = 'jlee@pbu.edu' from base64 import b64encode import gdata.apps import gdata.apps.service import gdata.service class AuditService(gdata.apps.service.PropertyService): """Client for the Google Apps Audit service.""" def _serviceUrl(self, setting_id, domain=None, user=None): if domain is None: domain = self.domain if user is None: return '/a/feeds/compliance/audit/%s/%s' % (setting_id, domain) else: return '/a/feeds/compliance/audit/%s/%s/%s' % (setting_id, domain, user) def updatePGPKey(self, pgpkey): """Updates Public PGP Key Google uses to encrypt audit data Args: pgpkey: string, ASCII text of PGP Public Key to be used Returns: A dict containing the result of the POST operation.""" uri = self._serviceUrl('publickey') b64pgpkey = b64encode(pgpkey) properties = {} properties['publicKey'] = b64pgpkey return self._PostProperties(uri, properties) def createEmailMonitor(self, source_user, destination_user, end_date, begin_date=None, incoming_headers_only=False, outgoing_headers_only=False, drafts=False, drafts_headers_only=False, chats=False, chats_headers_only=False): """Creates a email monitor, forwarding the source_users emails/chats Args: source_user: string, the user whose email will be audited destination_user: string, the user to receive the audited email end_date: string, the date the audit will end in "yyyy-MM-dd HH:mm" format, required begin_date: string, the date the audit will start in "yyyy-MM-dd HH:mm" format, leave blank to use current time incoming_headers_only: boolean, whether to audit only the headers of mail delivered to source user outgoing_headers_only: boolean, whether to audit only the headers of mail sent from the source user drafts: boolean, whether to audit draft messages of the source user drafts_headers_only: boolean, whether to audit only the headers of mail drafts saved by the user chats: boolean, whether to audit archived chats of the source user chats_headers_only: boolean, whether to audit only the headers of archived chats of the source user Returns: A dict containing the result of the POST operation.""" uri = self._serviceUrl('mail/monitor', user=source_user) properties = {} properties['destUserName'] = destination_user if begin_date is not None: properties['beginDate'] = begin_date properties['endDate'] = end_date if incoming_headers_only: properties['incomingEmailMonitorLevel'] = 'HEADER_ONLY' else: properties['incomingEmailMonitorLevel'] = 'FULL_MESSAGE' if outgoing_headers_only: properties['outgoingEmailMonitorLevel'] = 'HEADER_ONLY' else: properties['outgoingEmailMonitorLevel'] = 'FULL_MESSAGE' if drafts: if drafts_headers_only: properties['draftMonitorLevel'] = 'HEADER_ONLY' else: properties['draftMonitorLevel'] = 'FULL_MESSAGE' if chats: if chats_headers_only: properties['chatMonitorLevel'] = 'HEADER_ONLY' else: properties['chatMonitorLevel'] = 'FULL_MESSAGE' return self._PostProperties(uri, properties) def getEmailMonitors(self, user): """"Gets the email monitors for the given user Args: user: string, the user to retrieve email monitors for Returns: list results of the POST operation """ uri = self._serviceUrl('mail/monitor', user=user) return self._GetPropertiesList(uri) def deleteEmailMonitor(self, source_user, destination_user): """Deletes the email monitor for the given user Args: source_user: string, the user who is being monitored destination_user: string, theuser who recieves the monitored emails Returns: Nothing """ uri = self._serviceUrl('mail/monitor', user=source_user+'/'+destination_user) try: return self._DeleteProperties(uri) except gdata.service.RequestError, e: raise AppsForYourDomainException(e.args[0]) def createAccountInformationRequest(self, user): """Creates a request for account auditing details Args: user: string, the user to request account information for Returns: A dict containing the result of the post operation.""" uri = self._serviceUrl('account', user=user) properties = {} #XML Body is left empty try: return self._PostProperties(uri, properties) except gdata.service.RequestError, e: raise AppsForYourDomainException(e.args[0]) def getAccountInformationRequestStatus(self, user, request_id): """Gets the status of an account auditing request Args: user: string, the user whose account auditing details were requested request_id: string, the request_id Returns: A dict containing the result of the get operation.""" uri = self._serviceUrl('account', user=user+'/'+request_id) try: return self._GetProperties(uri) except gdata.service.RequestError, e: raise AppsForYourDomainException(e.args[0]) def getAllAccountInformationRequestsStatus(self): """Gets the status of all account auditing requests for the domain Args: None Returns: list results of the POST operation """ uri = self._serviceUrl('account') return self._GetPropertiesList(uri) def deleteAccountInformationRequest(self, user, request_id): """Deletes the request for account auditing information Args: user: string, the user whose account auditing details were requested request_id: string, the request_id Returns: Nothing """ uri = self._serviceUrl('account', user=user+'/'+request_id) try: return self._DeleteProperties(uri) except gdata.service.RequestError, e: raise AppsForYourDomainException(e.args[0]) def createMailboxExportRequest(self, user, begin_date=None, end_date=None, include_deleted=False, search_query=None, headers_only=False): """Creates a mailbox export request Args: user: string, the user whose mailbox export is being requested begin_date: string, date of earliest emails to export, optional, defaults to date of account creation format is 'yyyy-MM-dd HH:mm' end_date: string, date of latest emails to export, optional, defaults to current date format is 'yyyy-MM-dd HH:mm' include_deleted: boolean, whether to include deleted emails in export, mutually exclusive with search_query search_query: string, gmail style search query, matched emails will be exported, mutually exclusive with include_deleted Returns: A dict containing the result of the post operation.""" uri = self._serviceUrl('mail/export', user=user) properties = {} if begin_date is not None: properties['beginDate'] = begin_date if end_date is not None: properties['endDate'] = end_date if include_deleted is not None: properties['includeDeleted'] = gdata.apps.service._bool2str(include_deleted) if search_query is not None: properties['searchQuery'] = search_query if headers_only is True: properties['packageContent'] = 'HEADER_ONLY' else: properties['packageContent'] = 'FULL_MESSAGE' return self._PostProperties(uri, properties) def getMailboxExportRequestStatus(self, user, request_id): """Gets the status of an mailbox export request Args: user: string, the user whose mailbox were requested request_id: string, the request_id Returns: A dict containing the result of the get operation.""" uri = self._serviceUrl('mail/export', user=user+'/'+request_id) try: return self._GetProperties(uri) except gdata.service.RequestError, e: raise AppsForYourDomainException(e.args[0]) def getAllMailboxExportRequestsStatus(self): """Gets the status of all mailbox export requests for the domain Args: None Returns: list results of the POST operation """ uri = self._serviceUrl('mail/export') return self._GetPropertiesList(uri) def deleteMailboxExportRequest(self, user, request_id): """Deletes the request for mailbox export Args: user: string, the user whose mailbox were requested request_id: string, the request_id Returns: Nothing """ uri = self._serviceUrl('mail/export', user=user+'/'+request_id) try: return self._DeleteProperties(uri) except gdata.service.RequestError, e: raise AppsForYourDomainException(e.args[0]) gdata-2.0.18/src/gdata/apps/client.py0000750036466300116100000001366012156622363017410 0ustar afshareng00000000000000# Copyright 2010 Google Inc. All Rights Reserved. # # 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. """AppsClient adds Client Architecture to Provisioning API.""" __author__ = '' import gdata.apps.data import gdata.client import gdata.service class AppsClient(gdata.client.GDClient): """Client extension for the Google Provisioning API service. Attributes: host: string The hostname for the Provisioning API service. api_version: string The version of the Provisioning API. """ host = 'apps-apis.google.com' api_version = '2.0' auth_service = 'apps' auth_scopes = gdata.gauth.AUTH_SCOPES['apps'] def __init__(self, domain, auth_token=None, **kwargs): """Constructs a new client for the Provisioning API. Args: domain: string Google Apps domain name. auth_token: (optional) gdata.gauth.ClientLoginToken, AuthSubToken, or OAuthToken which authorizes client to make calls to Provisioning API. """ gdata.client.GDClient.__init__(self, auth_token=auth_token, **kwargs) self.domain = domain def _baseURL(self): return '/a/feeds/%s' % self.domain def _userURL(self): return '%s/user/%s' % (self._baseURL(), self.api_version) def _nicknameURL(self): return '%s/nickname/%s' % (self._baseURL(), self.api_version) def RetrieveAllPages(self, feed, desired_class=gdata.data.GDFeed): """Retrieve all pages and add all elements. Args: feed: gdata.data.GDFeed object with linked elements. desired_class: type of feed to be returned. Returns: desired_class: subclass of gdata.data.GDFeed. """ next = feed.GetNextLink() while next is not None: next_feed = self.GetFeed(next.href, desired_class=desired_class) for a_entry in next_feed.entry: feed.entry.append(a_entry) next = next_feed.GetNextLink() return feed def CreateUser(self, user_name, family_name, given_name, password, suspended=False, admin=None, quota_limit=None, password_hash_function=None, agreed_to_terms=None, change_password=None): """Create a user account.""" uri = self._userURL() user_entry = gdata.apps.data.UserEntry() user_entry.login = gdata.apps.data.Login(user_name=user_name, password=password, suspended=suspended, admin=admin, hash_function_name=password_hash_function, agreed_to_terms=agreed_to_terms, change_password=change_password) user_entry.name = gdata.apps.data.Name(family_name=family_name, given_name=given_name) return self.Post(user_entry, uri) def RetrieveUser(self, user_name): """Retrieve a user account. Args: user_name: string user_name to be retrieved. Returns: gdata.apps.data.UserEntry """ uri = '%s/%s' % (self._userURL(), user_name) return self.GetEntry(uri, desired_class=gdata.apps.data.UserEntry) def RetrievePageOfUsers(self, start_username=None): """Retrieve one page of users in this domain. Args: start_username: string user to start from for retrieving a page of users. Returns: gdata.apps.data.UserFeed """ uri = self._userURL() if start_username is not None: uri += '?startUsername=%s' % start_username return self.GetFeed(uri, desired_class=gdata.apps.data.UserFeed) def RetrieveAllUsers(self): """Retrieve all users in this domain. Returns: gdata.apps.data.UserFeed """ ret = self.RetrievePageOfUsers() # pagination return self.RetrieveAllPages(ret, gdata.apps.data.UserFeed) def UpdateUser(self, user_name, user_entry): """Update a user account. Args: user_name: string user_name to be updated. user_entry: gdata.apps.data.UserEntry updated user entry. Returns: gdata.apps.data.UserEntry """ uri = '%s/%s' % (self._userURL(), user_name) return self.Update(entry=user_entry, uri=uri) def DeleteUser(self, user_name): """Delete a user account.""" uri = '%s/%s' % (self._userURL(), user_name) self.Delete(uri) def CreateNickname(self, user_name, nickname): """Create a nickname for a user. Args: user_name: string user whose nickname is being created. nickname: string nickname. Returns: gdata.apps.data.NicknameEntry """ uri = self._nicknameURL() nickname_entry = gdata.apps.data.NicknameEntry() nickname_entry.login = gdata.apps.data.Login(user_name=user_name) nickname_entry.nickname = gdata.apps.data.Nickname(name=nickname) return self.Post(nickname_entry, uri) def RetrieveNickname(self, nickname): """Retrieve a nickname. Args: nickname: string nickname to be retrieved. Returns: gdata.apps.data.NicknameEntry """ uri = '%s/%s' % (self._nicknameURL(), nickname) return self.GetEntry(uri, desired_class=gdata.apps.data.NicknameEntry) def RetrieveNicknames(self, user_name): """Retrieve nicknames of the user. Args: user_name: string user whose nicknames are retrieved. Returns: gdata.apps.data.NicknameFeed """ uri = '%s?username=%s' % (self._nicknameURL(), user_name) ret = self.GetFeed(uri, desired_class=gdata.apps.data.NicknameFeed) # pagination return self.RetrieveAllPages(ret, gdata.apps.data.NicknameFeed) def DeleteNickname(self, nickname): """Delete a nickname.""" uri = '%s/%s' % (self._nicknameURL(), nickname) self.Delete(uri) gdata-2.0.18/src/gdata/apps/multidomain/0000750036466300116100000000000012156625015020066 5ustar afshareng00000000000000gdata-2.0.18/src/gdata/apps/multidomain/data.py0000750036466300116100000003105512156622363021363 0ustar afshareng00000000000000#!/usr/bin/python2.4 # # Copyright 2011 Google Inc. All Rights Reserved. # # 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. """Data model classes for the Multidomain Provisioning API.""" __author__ = 'Claudio Cherubino ' import gdata.apps import gdata.apps.apps_property_entry import gdata.apps_property import gdata.data # This is required to work around a naming conflict between the Google # Spreadsheets API and Python's built-in property function pyproperty = property # The apps:property firstName of a user entry USER_FIRST_NAME = 'firstName' # The apps:property lastName of a user entry USER_LAST_NAME = 'lastName' # The apps:property userEmail of a user entry USER_EMAIL = 'userEmail' # The apps:property password of a user entry USER_PASSWORD = 'password' # The apps:property hashFunction of a user entry USER_HASH_FUNCTION = 'hashFunction' # The apps:property isChangePasswordAtNextLogin of a user entry USER_CHANGE_PASSWORD = 'isChangePasswordAtNextLogin' # The apps:property agreedToTerms of a user entry USER_AGREED_TO_TERMS = 'agreedToTerms' # The apps:property isSuspended of a user entry USER_SUSPENDED = 'isSuspended' # The apps:property isAdmin of a user entry USER_ADMIN = 'isAdmin' # The apps:property ipWhitelisted of a user entry USER_IP_WHITELISTED = 'ipWhitelisted' # The apps:property quotaInGb of a user entry USER_QUOTA = 'quotaInGb' # The apps:property newEmail of a user rename request entry USER_NEW_EMAIL = 'newEmail' # The apps:property aliasEmail of an alias entry ALIAS_EMAIL = 'aliasEmail' class UserEntry(gdata.apps.apps_property_entry.AppsPropertyEntry): """Represents an User in object form.""" def GetFirstName(self): """Get the first name of the User object. Returns: The first name of this User object as a string or None. """ return self._GetProperty(USER_FIRST_NAME) def SetFirstName(self, value): """Set the first name of this User object. Args: value: string The new first name to give this object. """ self._SetProperty(USER_FIRST_NAME, value) first_name = pyproperty(GetFirstName, SetFirstName) def GetLastName(self): """Get the last name of the User object. Returns: The last name of this User object as a string or None. """ return self._GetProperty(USER_LAST_NAME) def SetLastName(self, value): """Set the last name of this User object. Args: value: string The new last name to give this object. """ self._SetProperty(USER_LAST_NAME, value) last_name = pyproperty(GetLastName, SetLastName) def GetEmail(self): """Get the email address of the User object. Returns: The email address of this User object as a string or None. """ return self._GetProperty(USER_EMAIL) def SetEmail(self, value): """Set the email address of this User object. Args: value: string The new email address to give this object. """ self._SetProperty(USER_EMAIL, value) email = pyproperty(GetEmail, SetEmail) def GetPassword(self): """Get the password of the User object. Returns: The password of this User object as a string or None. """ return self._GetProperty(USER_PASSWORD) def SetPassword(self, value): """Set the password of this User object. Args: value: string The new password to give this object. """ self._SetProperty(USER_PASSWORD, value) password = pyproperty(GetPassword, SetPassword) def GetHashFunction(self): """Get the hash function of the User object. Returns: The hash function of this User object as a string or None. """ return self._GetProperty(USER_HASH_FUNCTION) def SetHashFunction(self, value): """Set the hash function of this User object. Args: value: string The new hash function to give this object. """ self._SetProperty(USER_HASH_FUNCTION, value) hash_function = pyproperty(GetHashFunction, SetHashFunction) def GetChangePasswordAtNextLogin(self): """Get the change password at next login flag of the User object. Returns: The change password at next login flag of this User object as a string or None. """ return self._GetProperty(USER_CHANGE_PASSWORD) def SetChangePasswordAtNextLogin(self, value): """Set the change password at next login flag of this User object. Args: value: string The new change password at next login flag to give this object. """ self._SetProperty(USER_CHANGE_PASSWORD, value) change_password_at_next_login = pyproperty(GetChangePasswordAtNextLogin, SetChangePasswordAtNextLogin) def GetAgreedToTerms(self): """Get the agreed to terms flag of the User object. Returns: The agreed to terms flag of this User object as a string or None. """ return self._GetProperty(USER_AGREED_TO_TERMS) agreed_to_terms = pyproperty(GetAgreedToTerms) def GetSuspended(self): """Get the suspended flag of the User object. Returns: The suspended flag of this User object as a string or None. """ return self._GetProperty(USER_SUSPENDED) def SetSuspended(self, value): """Set the suspended flag of this User object. Args: value: string The new suspended flag to give this object. """ self._SetProperty(USER_SUSPENDED, value) suspended = pyproperty(GetSuspended, SetSuspended) def GetIsAdmin(self): """Get the isAdmin flag of the User object. Returns: The isAdmin flag of this User object as a string or None. """ return self._GetProperty(USER_ADMIN) def SetIsAdmin(self, value): """Set the isAdmin flag of this User object. Args: value: string The new isAdmin flag to give this object. """ self._SetProperty(USER_ADMIN, value) is_admin = pyproperty(GetIsAdmin, SetIsAdmin) def GetIpWhitelisted(self): """Get the ipWhitelisted flag of the User object. Returns: The ipWhitelisted flag of this User object as a string or None. """ return self._GetProperty(USER_IP_WHITELISTED) def SetIpWhitelisted(self, value): """Set the ipWhitelisted flag of this User object. Args: value: string The new ipWhitelisted flag to give this object. """ self._SetProperty(USER_IP_WHITELISTED, value) ip_whitelisted = pyproperty(GetIpWhitelisted, SetIpWhitelisted) def GetQuota(self): """Get the quota of the User object. Returns: The quota of this User object as a string or None. """ return self._GetProperty(USER_QUOTA) def SetQuota(self, value): """Set the quota of this User object. Args: value: string The new quota to give this object. """ self._SetProperty(USER_QUOTA, value) quota = pyproperty(GetQuota, GetQuota) def __init__(self, uri=None, email=None, first_name=None, last_name=None, password=None, hash_function=None, change_password=None, agreed_to_terms=None, suspended=None, is_admin=None, ip_whitelisted=None, quota=None, *args, **kwargs): """Constructs a new UserEntry object with the given arguments. Args: uri: string (optional) The uri of of this object for HTTP requests. email: string (optional) The email address of the user. first_name: string (optional) The first name of the user. last_name: string (optional) The last name of the user. password: string (optional) The password of the user. hash_function: string (optional) The name of the function used to hash the password. change_password: Boolean (optional) Whether or not the user must change password at first login. agreed_to_terms: Boolean (optional) Whether or not the user has agreed to the Terms of Service. suspended: Boolean (optional) Whether or not the user is suspended. is_admin: Boolean (optional) Whether or not the user has administrator privileges. ip_whitelisted: Boolean (optional) Whether or not the user's ip is whitelisted. quota: string (optional) The value (in GB) of the user's quota. args: The other parameters to pass to gdata.entry.GDEntry constructor. kwargs: The other parameters to pass to gdata.entry.GDEntry constructor. """ super(UserEntry, self).__init__(*args, **kwargs) if uri: self.uri = uri if email: self.email = email if first_name: self.first_name = first_name if last_name: self.last_name = last_name if password: self.password = password if hash_function: self.hash_function = hash_function if change_password is not None: self.change_password_at_next_login = str(change_password) if agreed_to_terms is not None: self.agreed_to_terms = str(agreed_to_terms) if suspended is not None: self.suspended = str(suspended) if is_admin is not None: self.is_admin = str(is_admin) if ip_whitelisted is not None: self.ip_whitelisted = str(ip_whitelisted) if quota: self.quota = quota class UserFeed(gdata.data.GDFeed): """Represents a feed of UserEntry objects.""" # Override entry so that this feed knows how to type its list of entries. entry = [UserEntry] class UserRenameRequest(gdata.apps.apps_property_entry.AppsPropertyEntry): """Represents an User rename request in object form.""" def GetNewEmail(self): """Get the new email address for the User object. Returns: The new email address for the User object as a string or None. """ return self._GetProperty(USER_NEW_EMAIL) def SetNewEmail(self, value): """Set the new email address for the User object. Args: value: string The new email address to give this object. """ self._SetProperty(USER_NEW_EMAIL, value) new_email = pyproperty(GetNewEmail, SetNewEmail) def __init__(self, new_email=None, *args, **kwargs): """Constructs a new UserRenameRequest object with the given arguments. Args: new_email: string (optional) The new email address for the target user. args: The other parameters to pass to gdata.entry.GDEntry constructor. kwargs: The other parameters to pass to gdata.entry.GDEntry constructor. """ super(UserRenameRequest, self).__init__(*args, **kwargs) if new_email: self.new_email = new_email class AliasEntry(gdata.apps.apps_property_entry.AppsPropertyEntry): """Represents an Alias in object form.""" def GetUserEmail(self): """Get the user email address of the Alias object. Returns: The user email address of this Alias object as a string or None. """ return self._GetProperty(USER_EMAIL) def SetUserEmail(self, value): """Set the user email address of this Alias object. Args: value: string The new user email address to give this object. """ self._SetProperty(USER_EMAIL, value) user_email = pyproperty(GetUserEmail, SetUserEmail) def GetAliasEmail(self): """Get the alias email address of the Alias object. Returns: The alias email address of this Alias object as a string or None. """ return self._GetProperty(ALIAS_EMAIL) def SetAliasEmail(self, value): """Set the alias email address of this Alias object. Args: value: string The new alias email address to give this object. """ self._SetProperty(ALIAS_EMAIL, value) alias_email = pyproperty(GetAliasEmail, SetAliasEmail) def __init__(self, user_email=None, alias_email=None, *args, **kwargs): """Constructs a new AliasEntry object with the given arguments. Args: user_email: string (optional) The user email address for the object. alias_email: string (optional) The alias email address for the object. args: The other parameters to pass to gdata.entry.GDEntry constructor. kwargs: The other parameters to pass to gdata.entry.GDEntry constructor. """ super(AliasEntry, self).__init__(*args, **kwargs) if user_email: self.user_email = user_email if alias_email: self.alias_email = alias_email class AliasFeed(gdata.data.GDFeed): """Represents a feed of AliasEntry objects.""" # Override entry so that this feed knows how to type its list of entries. entry = [AliasEntry] gdata-2.0.18/src/gdata/apps/multidomain/__init__.py0000750036466300116100000000000012156622363022173 0ustar afshareng00000000000000gdata-2.0.18/src/gdata/apps/multidomain/client.py0000750036466300116100000003300612156622363021726 0ustar afshareng00000000000000#!/usr/bin/python2.4 # # Copyright 2011 Google Inc. All Rights Reserved. # # 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. """MultiDomainProvisioningClient simplifies Multidomain Provisioning API calls. MultiDomainProvisioningClient extends gdata.client.GDClient to ease interaction with the Google Multidomain Provisioning API. These interactions include the ability to create, retrieve, update and delete users and aliases in multiple domains. """ __author__ = 'Claudio Cherubino ' import urllib import gdata.apps.multidomain.data import gdata.client # Multidomain URI templates # The strings in this template are eventually replaced with the feed type # (user/alias), API version and Google Apps domain name, respectively. MULTIDOMAIN_URI_TEMPLATE = '/a/feeds/%s/%s/%s' # The strings in this template are eventually replaced with the API version, # Google Apps domain name and old email address, respectively. MULTIDOMAIN_USER_RENAME_URI_TEMPLATE = '/a/feeds/user/userEmail/%s/%s/%s' # The value for user requests MULTIDOMAIN_USER_FEED = 'user' # The value for alias requests MULTIDOMAIN_ALIAS_FEED = 'alias' class MultiDomainProvisioningClient(gdata.client.GDClient): """Client extension for the Google MultiDomain Provisioning API service. Attributes: host: string The hostname for the MultiDomain Provisioning API service. api_version: string The version of the MultiDomain Provisioning API. """ host = 'apps-apis.google.com' api_version = '2.0' auth_service = 'apps' auth_scopes = gdata.gauth.AUTH_SCOPES['apps'] ssl = True def __init__(self, domain, auth_token=None, **kwargs): """Constructs a new client for the MultiDomain Provisioning API. Args: domain: string The Google Apps domain with MultiDomain Provisioning. auth_token: (optional) gdata.gauth.ClientLoginToken, AuthSubToken, or OAuthToken which authorizes this client to edit the email settings. kwargs: The other parameters to pass to the gdata.client.GDClient constructor. """ gdata.client.GDClient.__init__(self, auth_token=auth_token, **kwargs) self.domain = domain def make_multidomain_provisioning_uri( self, feed_type, email=None, params=None): """Creates a resource feed URI for the MultiDomain Provisioning API. Using this client's Google Apps domain, create a feed URI for multidomain provisioning in that domain. If an email address is provided, return a URI for that specific resource. If params are provided, append them as GET params. Args: feed_type: string The type of feed (user/alias) email: string (optional) The email address of multidomain resource for which to make a feed URI. params: dict (optional) key -> value params to append as GET vars to the URI. Example: params={'start': 'my-resource-id'} Returns: A string giving the URI for multidomain provisioning for this client's Google Apps domain. """ uri = MULTIDOMAIN_URI_TEMPLATE % (feed_type, self.api_version, self.domain) if email: uri += '/' + email if params: uri += '?' + urllib.urlencode(params) return uri MakeMultidomainProvisioningUri = make_multidomain_provisioning_uri def make_multidomain_user_provisioning_uri(self, email=None, params=None): """Creates a resource feed URI for the MultiDomain User Provisioning API. Using this client's Google Apps domain, create a feed URI for multidomain user provisioning in that domain. If an email address is provided, return a URI for that specific resource. If params are provided, append them as GET params. Args: email: string (optional) The email address of multidomain user for which to make a feed URI. params: dict (optional) key -> value params to append as GET vars to the URI. Example: params={'start': 'my-resource-id'} Returns: A string giving the URI for multidomain user provisioning for thisis that client's Google Apps domain. """ return self.make_multidomain_provisioning_uri( MULTIDOMAIN_USER_FEED, email, params) MakeMultidomainUserProvisioningUri = make_multidomain_user_provisioning_uri def make_multidomain_alias_provisioning_uri(self, email=None, params=None): """Creates a resource feed URI for the MultiDomain Alias Provisioning API. Using this client's Google Apps domain, create a feed URI for multidomain alias provisioning in that domain. If an email address is provided, return a URI for that specific resource. If params are provided, append them as GET params. Args: email: string (optional) The email address of multidomain alias for which to make a feed URI. params: dict (optional) key -> value params to append as GET vars to the URI. Example: params={'start': 'my-resource-id'} Returns: A string giving the URI for multidomain alias provisioning for this client's Google Apps domain. """ return self.make_multidomain_provisioning_uri( MULTIDOMAIN_ALIAS_FEED, email, params) MakeMultidomainAliasProvisioningUri = make_multidomain_alias_provisioning_uri def retrieve_all_pages(self, uri, desired_class=gdata.data.GDFeed, **kwargs): """Retrieves all pages from uri. Args: uri: The uri where the first page is. desired_class: Type of feed that is retrieved. kwargs: The other parameters to pass to gdata.client.GDClient.GetFeed() Returns: A desired_class feed object. """ feed = self.GetFeed( uri, desired_class=desired_class, **kwargs) next_link = feed.GetNextLink() while next_link is not None: uri = next_link.href temp_feed = self.GetFeed( uri, desired_class=desired_class, **kwargs) feed.entry = feed.entry + temp_feed.entry next_link = temp_feed.GetNextLink() return feed RetrieveAllPages = retrieve_all_pages def retrieve_all_users(self, **kwargs): """Retrieves all users in all domains. Args: kwargs: The other parameters to pass to gdata.client.GDClient.GetFeed() Returns: A gdata.data.GDFeed of the domain users """ uri = self.MakeMultidomainUserProvisioningUri() return self.RetrieveAllPages( uri, desired_class=gdata.apps.multidomain.data.UserFeed, **kwargs) RetrieveAllUsers = retrieve_all_users def retrieve_user(self, email, **kwargs): """Retrieves a single user in the domain. Args: email: string The email address of the user to be retrieved kwargs: The other parameters to pass to gdata.client.GDClient.GetEntry() Returns: A gdata.apps.multidomain.data.UserEntry representing the user """ uri = self.MakeMultidomainUserProvisioningUri(email=email) return self.GetEntry( uri, desired_class=gdata.apps.multidomain.data.UserEntry, **kwargs) RetrieveUser = retrieve_user def create_user(self, email, first_name, last_name, password, is_admin, hash_function=None, suspended=None, change_password=None, ip_whitelisted=None, quota=None, **kwargs): """Creates an user in the domain with the given properties. Args: email: string The email address of the user. first_name: string The first name of the user. last_name: string The last name of the user. password: string The password of the user. is_admin: Boolean Whether or not the user has administrator privileges. hash_function: string (optional) The name of the function used to hash the password. suspended: Boolean (optional) Whether or not the user is suspended. change_password: Boolean (optional) Whether or not the user must change password at first login. ip_whitelisted: Boolean (optional) Whether or not the user's ip is whitelisted. quota: string (optional) The value (in GB) of the user's quota. kwargs: The other parameters to pass to gdata.client.GDClient.post(). Returns: A gdata.apps.multidomain.data.UserEntry of the new user """ new_user = gdata.apps.multidomain.data.UserEntry( email=email, first_name=first_name, last_name=last_name, password=password, is_admin=is_admin, hash_function=hash_function, suspended=suspended, change_password=change_password, ip_whitelisted=ip_whitelisted, quota=quota) return self.post(new_user, self.MakeMultidomainUserProvisioningUri(), **kwargs) CreateUser = create_user def update_user(self, email, user_entry, **kwargs): """Deletes the user with the given email address. Args: email: string The email address of the user to be updated. user_entry: UserEntry The user entry with updated values. kwargs: The other parameters to pass to gdata.client.GDClient.put() Returns: A gdata.apps.multidomain.data.UserEntry representing the user """ return self.update(user_entry, uri=self.MakeMultidomainUserProvisioningUri(email), **kwargs) UpdateUser = update_user def delete_user(self, email, **kwargs): """Deletes the user with the given email address. Args: email: string The email address of the user to delete. kwargs: The other parameters to pass to gdata.client.GDClient.delete() Returns: An HTTP response object. See gdata.client.request(). """ return self.delete(self.MakeMultidomainUserProvisioningUri(email), **kwargs) DeleteUser = delete_user def rename_user(self, old_email, new_email, **kwargs): """Renames an user's account to a different domain. Args: old_email: string The old email address of the user to rename. new_email: string The new email address for the user to be renamed. kwargs: The other parameters to pass to gdata.client.GDClient.put() Returns: A gdata.apps.multidomain.data.UserRenameRequest representing the request. """ rename_uri = MULTIDOMAIN_USER_RENAME_URI_TEMPLATE % (self.api_version, self.domain, old_email) entry = gdata.apps.multidomain.data.UserRenameRequest(new_email) return self.update(entry, uri=rename_uri, **kwargs) RenameUser = rename_user def retrieve_all_aliases(self, **kwargs): """Retrieves all aliases in the domain. Args: kwargs: The other parameters to pass to gdata.client.GDClient.GetFeed() Returns: A gdata.data.GDFeed of the domain aliases """ uri = self.MakeMultidomainAliasProvisioningUri() return self.RetrieveAllPages( uri, desired_class=gdata.apps.multidomain.data.AliasFeed, **kwargs) RetrieveAllAliases = retrieve_all_aliases def retrieve_alias(self, email, **kwargs): """Retrieves a single alias in the domain. Args: email: string The email address of the alias to be retrieved kwargs: The other parameters to pass to gdata.client.GDClient.GetEntry() Returns: A gdata.apps.multidomain.data.AliasEntry representing the alias """ uri = self.MakeMultidomainAliasProvisioningUri(email=email) return self.GetEntry( uri, desired_class=gdata.apps.multidomain.data.AliasEntry, **kwargs) RetrieveAlias = retrieve_alias def retrieve_all_user_aliases(self, user_email, **kwargs): """Retrieves all aliases for a given user in the domain. Args: user_email: string Email address of the user whose aliases are to be retrieved kwargs: The other parameters to pass to gdata.client.GDClient.GetFeed() Returns: A gdata.data.GDFeed of the user aliases """ uri = self.MakeMultidomainAliasProvisioningUri( params = {'userEmail' : user_email}) return self.RetrieveAllPages( uri, desired_class=gdata.apps.multidomain.data.AliasFeed, **kwargs) RetrieveAllUserAliases = retrieve_all_user_aliases def create_alias(self, user_email, alias_email, **kwargs): """Creates an alias in the domain with the given properties. Args: user_email: string The email address of the user. alias_email: string The first name of the user. kwargs: The other parameters to pass to gdata.client.GDClient.post(). Returns: A gdata.apps.multidomain.data.AliasEntry of the new alias """ new_alias = gdata.apps.multidomain.data.AliasEntry( user_email=user_email, alias_email=alias_email) return self.post(new_alias, self.MakeMultidomainAliasProvisioningUri(), **kwargs) CreateAlias = create_alias def delete_alias(self, email, **kwargs): """Deletes the alias with the given email address. Args: email: string The email address of the alias to delete. kwargs: The other parameters to pass to gdata.client.GDClient.delete() Returns: An HTTP response object. See gdata.client.request(). """ return self.delete(self.MakeMultidomainAliasProvisioningUri(email), **kwargs) DeleteAlias = delete_alias gdata-2.0.18/src/gdata/apps/apps_property_entry.py0000640036466300116100000000360112156622363022252 0ustar afshareng00000000000000#!/usr/bin/python2.4 # # Copyright 2011 Google Inc. All Rights Reserved. # # 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. """Generic class for Set/Get properties of GData Provisioning clients.""" __author__ = 'Gunjan Sharma ' import gdata.apps import gdata.apps_property import gdata.data class AppsPropertyEntry(gdata.data.GDEntry): """Represents a generic entry in object form.""" property = [gdata.apps_property.AppsProperty] def _GetProperty(self, name): """Get the apps:property value with the given name. Args: name: string Name of the apps:property value to get. Returns: The apps:property value with the given name, or None if the name was invalid. """ value = None for p in self.property: if p.name == name: value = p.value break return value def _SetProperty(self, name, value): """Set the apps:property value with the given name to the given value. Args: name: string Name of the apps:property value to set. value: string Value to give the apps:property value with the given name. """ found = False for i in range(len(self.property)): if self.property[i].name == name: self.property[i].value = value found = True break if not found: self.property.append( gdata.apps_property.AppsProperty(name=name, value=value)) gdata-2.0.18/src/gdata/apps/groups/0000750036466300116100000000000012156625015017063 5ustar afshareng00000000000000gdata-2.0.18/src/gdata/apps/groups/data.py0000640036466300116100000001566612156622363020370 0ustar afshareng00000000000000#!/usr/bin/python2.4 # # Copyright 2011 Google Inc. All Rights Reserved. # # 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. """Data model classes for the Groups Provisioning API.""" __author__ = 'Shraddha gupta ' import atom.data import gdata.apps import gdata.apps.apps_property_entry import gdata.apps_property import gdata.data # This is required to work around a naming conflict between the Google # Spreadsheets API and Python's built-in property function pyproperty = property # The apps:property groupId of a group entry GROUP_ID = 'groupId' # The apps:property groupName of a group entry GROUP_NAME = 'groupName' # The apps:property description of a group entry DESCRIPTION = 'description' # The apps:property emailPermission of a group entry EMAIL_PERMISSION = 'emailPermission' # The apps:property memberId of a group member entry MEMBER_ID = 'memberId' # The apps:property memberType of a group member entry MEMBER_TYPE = 'memberType' # The apps:property directMember of a group member entry DIRECT_MEMBER = 'directMember' class GroupEntry(gdata.apps.apps_property_entry.AppsPropertyEntry): """Represents a group entry in object form.""" def GetGroupId(self): """Get groupId of the GroupEntry object. Returns: The groupId this GroupEntry object as a string or None. """ return self._GetProperty(GROUP_ID) def SetGroupId(self, value): """Set the groupId of this GroupEntry object. Args: value: string The new groupId to give this object. """ self._SetProperty(GROUP_ID, value) group_id = pyproperty(GetGroupId, SetGroupId) def GetGroupName(self): """Get the groupName of the GroupEntry object. Returns: The groupName of this GroupEntry object as a string or None. """ return self._GetProperty(GROUP_NAME) def SetGroupName(self, value): """Set the groupName of this GroupEntry object. Args: value: string The new groupName to give this object. """ self._SetProperty(GROUP_NAME, value) group_name = pyproperty(GetGroupName, SetGroupName) def GetDescription(self): """Get the description of the GroupEntry object. Returns: The description of this GroupEntry object as a string or None. """ return self._GetProperty(DESCRIPTION) def SetDescription(self, value): """Set the description of this GroupEntry object. Args: value: string The new description to give this object. """ self._SetProperty(DESCRIPTION, value) description = pyproperty(GetDescription, SetDescription) def GetEmailPermission(self): """Get the emailPermission of the GroupEntry object. Returns: The emailPermission of this GroupEntry object as a string or None. """ return self._GetProperty(EMAIL_PERMISSION) def SetEmailPermission(self, value): """Set the emailPermission of this GroupEntry object. Args: value: string The new emailPermission to give this object. """ self._SetProperty(EMAIL_PERMISSION, value) email_permission = pyproperty(GetEmailPermission, SetEmailPermission) def __init__(self, group_id=None, group_name=None, description=None, email_permission=None, *args, **kwargs): """Constructs a new GroupEntry object with the given arguments. Args: group_id: string identifier of the group. group_name: string name of the group. description: string (optional) the group description. email_permisison: string (optional) permission level of the group. """ super(GroupEntry, self).__init__(*args, **kwargs) if group_id: self.group_id = group_id if group_name: self.group_name = group_name if description: self.description = description if email_permission: self.email_permission = email_permission class GroupFeed(gdata.data.GDFeed): """Represents a feed of GroupEntry objects.""" # Override entry so that this feed knows how to type its list of entries. entry = [GroupEntry] class GroupMemberEntry(gdata.apps.apps_property_entry.AppsPropertyEntry): """Represents a group member in object form.""" def GetMemberId(self): """Get the memberId of the GroupMember object. Returns: The memberId of this GroupMember object as a string. """ return self._GetProperty(MEMBER_ID) def SetMemberId(self, value): """Set the memberId of this GroupMember object. Args: value: string The new memberId to give this object. """ self._SetProperty(MEMBER_ID, value) member_id = pyproperty(GetMemberId, SetMemberId) def GetMemberType(self): """Get the memberType(User, Group) of the GroupMember object. Returns: The memberType of this GroupMember object as a string or None. """ return self._GetProperty(MEMBER_TYPE) def SetMemberType(self, value): """Set the memberType of this GroupMember object. Args: value: string The new memberType to give this object. """ self._SetProperty(MEMBER_TYPE, value) member_type = pyproperty(GetMemberType, SetMemberType) def GetDirectMember(self): """Get the directMember of the GroupMember object. Returns: The directMember of this GroupMember object as a bool or None. """ return self._GetProperty(DIRECT_MEMBER) def SetDirectMember(self, value): """Set the memberType of this GroupMember object. Args: value: string The new memberType to give this object. """ self._SetProperty(DIRECT_MEMBER, value) direct_member = pyproperty(GetDirectMember, SetDirectMember) def __init__(self, member_id=None, member_type=None, direct_member=None, *args, **kwargs): """Constructs a new GroupMemberEntry object with the given arguments. Args: member_id: string identifier of group member object. member_type: string (optional) member type of group member object. direct_member: bool (optional) if group member object is direct member. args: The other parameters to pass to gdata.entry.GDEntry constructor. kwargs: The other parameters to pass to gdata.entry.GDEntry constructor. """ super(GroupMemberEntry, self).__init__(*args, **kwargs) if member_id: self.member_id = member_id if member_type: self.member_type = member_type if direct_member: self.direct_member = direct_member class GroupMemberFeed(gdata.data.GDFeed): """Represents a feed of GroupMemberEntry objects.""" # Override entry so that this feed knows how to type its list of entries. entry = [GroupMemberEntry] gdata-2.0.18/src/gdata/apps/groups/__init__.py0000640036466300116100000000000012156622363021166 0ustar afshareng00000000000000gdata-2.0.18/src/gdata/apps/groups/client.py0000640036466300116100000002715412156622363020730 0ustar afshareng00000000000000#!/usr/bin/python2.4 # # Copyright 2011 Google Inc. All Rights Reserved. # # 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. """GroupsClient simplifies Groups Provisioning API calls. GroupsClient extends gdata.client.GDClient to ease interaction with the Group Provisioning API. These interactions include the ability to create, retrieve, update and delete groups. """ __author__ = 'Shraddha gupta ' import urllib import gdata.apps.groups.data import gdata.client # Multidomain URI templates # The strings in this template are eventually replaced with the API version, # and Google Apps domain name respectively. GROUP_URI_TEMPLATE = '/a/feeds/group/%s/%s' GROUP_MEMBER = 'member' class GroupsProvisioningClient(gdata.client.GDClient): """Client extension for the Google Group Provisioning API service. Attributes: host: string The hostname for the Group Provisioning API service. api_version: string The version of the MultiDomain Provisioning API. """ host = 'apps-apis.google.com' api_version = '2.0' auth_service = 'apps' auth_scopes = gdata.gauth.AUTH_SCOPES['apps'] ssl = True def __init__(self, domain, auth_token=None, **kwargs): """Constructs a new client for the Groups Provisioning API. Args: domain: string The Google Apps domain with Group Provisioning. auth_token: (optional) gdata.gauth.ClientLoginToken, AuthSubToken, or OAuthToken which authorizes this client to edit the email settings. kwargs: The other parameters to pass to the gdata.client.GDClient constructor. """ gdata.client.GDClient.__init__(self, auth_token=auth_token, **kwargs) self.domain = domain def make_group_provisioning_uri( self, feed_type=None, group_id=None, member_id=None, params=None): """Creates a resource feed URI for the Groups Provisioning API. Using this client's Google Apps domain, create a feed URI for group provisioning in that domain. If an email address is provided, return a URI for that specific resource. If params are provided, append them as GET params. Args: feed_type: string groupmember for groupmember feed else None group_id: string (optional) The identifier of group for which to make a feed URI. member_id: string (optional) The identifier of group member for which to make a feed URI. params: dict (optional) key -> value params to append as GET vars to the URI. Example: params={'start': 'my-resource-id'} Returns: A string giving the URI for group provisioning for this client's Google Apps domain. """ uri = GROUP_URI_TEMPLATE % (self.api_version, self.domain) if group_id: uri += '/' + group_id if feed_type is GROUP_MEMBER: uri += '/' + feed_type if member_id: uri += '/' + member_id if params: uri += '?' + urllib.urlencode(params) return uri MakeGroupProvisioningUri = make_group_provisioning_uri def make_group_member_uri(self, group_id, member_id=None, params=None): """Creates a resource feed URI for the Group Member Provisioning API.""" return self.make_group_provisioning_uri(GROUP_MEMBER, group_id=group_id, member_id=member_id, params=params) MakeGroupMembersUri = make_group_member_uri def RetrieveAllPages(self, feed, desired_class=gdata.data.GDFeed): """Retrieve all pages and add all elements. Args: feed: gdata.data.GDFeed object with linked elements. desired_class: type of Feed to be returned. Returns: desired_class: subclass of gdata.data.GDFeed. """ next = feed.GetNextLink() while next is not None: next_feed = self.GetFeed(next.href, desired_class=desired_class) for a_entry in next_feed.entry: feed.entry.append(a_entry) next = next_feed.GetNextLink() return feed def retrieve_page_of_groups(self, **kwargs): """Retrieves first page of groups for the given domain. Args: kwargs: The other parameters to pass to gdata.client.GDClient.GetFeed() Returns: A gdata.apps.groups.data.GroupFeed of the groups """ uri = self.MakeGroupProvisioningUri() return self.GetFeed(uri, desired_class=gdata.apps.groups.data.GroupFeed, **kwargs) RetrievePageOfGroups = retrieve_page_of_groups def retrieve_all_groups(self): """Retrieve all groups in this domain. Returns: gdata.apps.groups.data.GroupFeed of the groups """ groups_feed = self.RetrievePageOfGroups() # pagination return self.RetrieveAllPages(groups_feed, gdata.apps.groups.data.GroupFeed) RetrieveAllGroups = retrieve_all_groups def retrieve_group(self, group_id, **kwargs): """Retrieves a single group in the domain. Args: group_id: string groupId of the group to be retrieved kwargs: other parameters to pass to gdata.client.GDClient.GetEntry() Returns: A gdata.apps.groups.data.GroupEntry representing the group """ uri = self.MakeGroupProvisioningUri(group_id=group_id) return self.GetEntry(uri, desired_class=gdata.apps.groups.data.GroupEntry, **kwargs) RetrieveGroup = retrieve_group def retrieve_page_of_member_groups(self, member_id, direct_only=False, **kwargs): """Retrieve one page of groups that belong to the given member_id. Args: member_id: The member's email address (e.g. member@example.com). direct_only: Boolean whether only return groups that this member directly belongs to. Returns: gdata.apps.groups.data.GroupFeed of the groups. """ uri = self.MakeGroupProvisioningUri(params={'member':member_id, 'directOnly':direct_only}) return self.GetFeed(uri, desired_class=gdata.apps.groups.data.GroupFeed, **kwargs) RetrievePageOfMemberGroups = retrieve_page_of_member_groups def retrieve_groups(self, member_id, direct_only=False, **kwargs): """Retrieve all groups that belong to the given member_id. Args: member_id: The member's email address (e.g. member@example.com). direct_only: Boolean whether only return groups that this member directly belongs to. Returns: gdata.apps.groups.data.GroupFeed of the groups """ groups_feed = self.RetrievePageOfMemberGroups(member_id=member_id, direct_only=direct_only) # pagination return self.RetrieveAllPages(groups_feed, gdata.apps.groups.data.GroupFeed) RetrieveGroups = retrieve_groups def create_group(self, group_id, group_name, description=None, email_permission=None, **kwargs): """Creates a group in the domain with the given properties. Args: group_id: string identifier of the group. group_name: string name of the group. description: string (optional) description of the group. email_permission: string (optional) email permission level for the group. kwargs: other parameters to pass to gdata.client.GDClient.post(). Returns: A gdata.apps.groups.data.GroupEntry of the new group """ new_group = gdata.apps.groups.data.GroupEntry(group_id=group_id, group_name=group_name, description=description, email_permission=email_permission) return self.post(new_group, self.MakeGroupProvisioningUri(), **kwargs) CreateGroup = create_group def update_group(self, group_id, group_entry, **kwargs): """Updates the group with the given groupID. Args: group_id: string identifier of the group. group_entry: GroupEntry The group entry with updated values. kwargs: The other parameters to pass to gdata.client.GDClient.put() Returns: A gdata.apps.groups.data.GroupEntry representing the group """ return self.update(group_entry, uri=self.MakeGroupProvisioningUri(group_id=group_id), **kwargs) UpdateGroup = update_group def delete_group(self, group_id, **kwargs): """Deletes the group with the given groupId. Args: group_id: string groupId of the group to delete. kwargs: The other parameters to pass to gdata.client.GDClient.delete() """ self.delete(self.MakeGroupProvisioningUri(group_id=group_id), **kwargs) DeleteGroup = delete_group def retrieve_page_of_members(self, group_id, **kwargs): """Retrieves first page of group members of the group. Args: group_id: string groupId of the group whose members are retrieved kwargs: The other parameters to pass to gdata.client.GDClient.GetFeed() Returns: A gdata.apps.groups.data.GroupMemberFeed of the GroupMember entries """ uri = self.MakeGroupMembersUri(group_id=group_id) return self.GetFeed(uri, desired_class=gdata.apps.groups.data.GroupMemberFeed, **kwargs) RetrievePageOfMembers = retrieve_page_of_members def retrieve_all_members(self, group_id, **kwargs): """Retrieve all members of the group. Returns: gdata.apps.groups.data.GroupMemberFeed """ group_member_feed = self.RetrievePageOfMembers(group_id=group_id) # pagination return self.RetrieveAllPages(group_member_feed, gdata.apps.groups.data.GroupMemberFeed) RetrieveAllMembers = retrieve_all_members def retrieve_group_member(self, group_id, member_id, **kwargs): """Retrieves a group member with the given id from given group. Args: group_id: string groupId of the group whose member is retrieved member_id: string memberId of the group member retrieved kwargs: The other parameters to pass to gdata.client.GDClient.GetEntry() Returns: A gdata.apps.groups.data.GroupEntry representing the group member """ uri = self.MakeGroupMembersUri(group_id=group_id, member_id=member_id) return self.GetEntry(uri, desired_class=gdata.apps.groups.data.GroupMemberEntry, **kwargs) RetrieveGroupMember = retrieve_group_member def add_member_to_group(self, group_id, member_id, member_type=None, direct_member=None, **kwargs): """Adds a member with the given id to the group. Args: group_id: string groupId of the group where member is added member_id: string memberId of the member added member_type: string (optional) type of member(user or group) direct_member: bool (optional) if member is a direct member kwargs: The other parameters to pass to gdata.client.GDClient.post(). Returns: A gdata.apps.groups.data.GroupMemberEntry of the group member """ member = gdata.apps.groups.data.GroupMemberEntry(member_id=member_id, member_type=member_type, direct_member=direct_member) return self.post(member, self.MakeGroupMembersUri(group_id=group_id), **kwargs) AddMemberToGroup = add_member_to_group def remove_member_from_group(self, group_id, member_id, **kwargs): """Remove member from the given group. Args: group_id: string groupId of the group member_id: string memberId of the member to be removed kwargs: The other parameters to pass to gdata.client.GDClient.delete() """ self.delete( self.MakeGroupMembersUri(group_id=group_id, member_id=member_id), **kwargs) RemoveMemberFromGroup = remove_member_from_group gdata-2.0.18/src/gdata/apps/groups/service.py0000640036466300116100000003117412156622363021107 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright (C) 2008 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. """Allow Google Apps domain administrators to manage groups, group members and group owners. GroupsService: Provides methods to manage groups, members and owners. """ __author__ = 'google-apps-apis@googlegroups.com' import urllib import gdata.apps import gdata.apps.service import gdata.service API_VER = '2.0' BASE_URL = '/a/feeds/group/' + API_VER + '/%s' GROUP_MEMBER_URL = BASE_URL + '?member=%s' GROUP_MEMBER_DIRECT_URL = GROUP_MEMBER_URL + '&directOnly=%s' GROUP_ID_URL = BASE_URL + '/%s' MEMBER_URL = BASE_URL + '/%s/member' MEMBER_WITH_SUSPENDED_URL = MEMBER_URL + '?includeSuspendedUsers=%s' MEMBER_ID_URL = MEMBER_URL + '/%s' OWNER_URL = BASE_URL + '/%s/owner' OWNER_WITH_SUSPENDED_URL = OWNER_URL + '?includeSuspendedUsers=%s' OWNER_ID_URL = OWNER_URL + '/%s' PERMISSION_OWNER = 'Owner' PERMISSION_MEMBER = 'Member' PERMISSION_DOMAIN = 'Domain' PERMISSION_ANYONE = 'Anyone' class GroupsService(gdata.apps.service.PropertyService): """Client for the Google Apps Groups service.""" def _ServiceUrl(self, service_type, is_existed, group_id, member_id, owner_email, direct_only=False, domain=None, suspended_users=False): if domain is None: domain = self.domain if service_type == 'group': if group_id != '' and is_existed: return GROUP_ID_URL % (domain, group_id) elif member_id != '': if direct_only: return GROUP_MEMBER_DIRECT_URL % (domain, urllib.quote_plus(member_id), self._Bool2Str(direct_only)) else: return GROUP_MEMBER_URL % (domain, urllib.quote_plus(member_id)) else: return BASE_URL % (domain) if service_type == 'member': if member_id != '' and is_existed: return MEMBER_ID_URL % (domain, group_id, urllib.quote_plus(member_id)) elif suspended_users: return MEMBER_WITH_SUSPENDED_URL % (domain, group_id, self._Bool2Str(suspended_users)) else: return MEMBER_URL % (domain, group_id) if service_type == 'owner': if owner_email != '' and is_existed: return OWNER_ID_URL % (domain, group_id, urllib.quote_plus(owner_email)) elif suspended_users: return OWNER_WITH_SUSPENDED_URL % (domain, group_id, self._Bool2Str(suspended_users)) else: return OWNER_URL % (domain, group_id) def _Bool2Str(self, b): if b is None: return None return str(b is True).lower() def _IsExisted(self, uri): try: self._GetProperties(uri) return True except gdata.apps.service.AppsForYourDomainException, e: if e.error_code == gdata.apps.service.ENTITY_DOES_NOT_EXIST: return False else: raise e def CreateGroup(self, group_id, group_name, description, email_permission): """Create a group. Args: group_id: The ID of the group (e.g. us-sales). group_name: The name of the group. description: A description of the group email_permission: The subscription permission of the group. Returns: A dict containing the result of the create operation. """ uri = self._ServiceUrl('group', False, group_id, '', '') properties = {} properties['groupId'] = group_id properties['groupName'] = group_name properties['description'] = description properties['emailPermission'] = email_permission return self._PostProperties(uri, properties) def UpdateGroup(self, group_id, group_name, description, email_permission): """Update a group's name, description and/or permission. Args: group_id: The ID of the group (e.g. us-sales). group_name: The name of the group. description: A description of the group email_permission: The subscription permission of the group. Returns: A dict containing the result of the update operation. """ uri = self._ServiceUrl('group', True, group_id, '', '') properties = {} properties['groupId'] = group_id properties['groupName'] = group_name properties['description'] = description properties['emailPermission'] = email_permission return self._PutProperties(uri, properties) def RetrieveGroup(self, group_id): """Retrieve a group based on its ID. Args: group_id: The ID of the group (e.g. us-sales). Returns: A dict containing the result of the retrieve operation. """ uri = self._ServiceUrl('group', True, group_id, '', '') return self._GetProperties(uri) def RetrieveAllGroups(self): """Retrieve all groups in the domain. Args: None Returns: A list containing the result of the retrieve operation. """ uri = self._ServiceUrl('group', True, '', '', '') return self._GetPropertiesList(uri) def RetrievePageOfGroups(self, start_group=None): """Retrieve one page of groups in the domain. Args: start_group: The key to continue for pagination through all groups. Returns: A feed object containing the result of the retrieve operation. """ uri = self._ServiceUrl('group', True, '', '', '') if start_group is not None: uri += "?start="+start_group property_feed = self._GetPropertyFeed(uri) return property_feed def RetrieveGroups(self, member_id, direct_only=False): """Retrieve all groups that belong to the given member_id. Args: member_id: The member's email address (e.g. member@example.com). direct_only: Boolean whether only return groups that this member directly belongs to. Returns: A list containing the result of the retrieve operation. """ uri = self._ServiceUrl('group', True, '', member_id, '', direct_only=direct_only) return self._GetPropertiesList(uri) def DeleteGroup(self, group_id): """Delete a group based on its ID. Args: group_id: The ID of the group (e.g. us-sales). Returns: A dict containing the result of the delete operation. """ uri = self._ServiceUrl('group', True, group_id, '', '') return self._DeleteProperties(uri) def AddMemberToGroup(self, member_id, group_id): """Add a member to a group. Args: member_id: The member's email address (e.g. member@example.com). group_id: The ID of the group (e.g. us-sales). Returns: A dict containing the result of the add operation. """ uri = self._ServiceUrl('member', False, group_id, member_id, '') properties = {} properties['memberId'] = member_id return self._PostProperties(uri, properties) def IsMember(self, member_id, group_id): """Check whether the given member already exists in the given group. Args: member_id: The member's email address (e.g. member@example.com). group_id: The ID of the group (e.g. us-sales). Returns: True if the member exists in the group. False otherwise. """ uri = self._ServiceUrl('member', True, group_id, member_id, '') return self._IsExisted(uri) def RetrieveMember(self, member_id, group_id): """Retrieve the given member in the given group. Args: member_id: The member's email address (e.g. member@example.com). group_id: The ID of the group (e.g. us-sales). Returns: A dict containing the result of the retrieve operation. """ uri = self._ServiceUrl('member', True, group_id, member_id, '') return self._GetProperties(uri) def RetrieveAllMembers(self, group_id, suspended_users=False): """Retrieve all members in the given group. Args: group_id: The ID of the group (e.g. us-sales). suspended_users: A boolean; should we include any suspended users in the membership list returned? Returns: A list containing the result of the retrieve operation. """ uri = self._ServiceUrl('member', True, group_id, '', '', suspended_users=suspended_users) return self._GetPropertiesList(uri) def RetrievePageOfMembers(self, group_id, suspended_users=False, start=None): """Retrieve one page of members of a given group. Args: group_id: The ID of the group (e.g. us-sales). suspended_users: A boolean; should we include any suspended users in the membership list returned? start: The key to continue for pagination through all members. Returns: A feed object containing the result of the retrieve operation. """ uri = self._ServiceUrl('member', True, group_id, '', '', suspended_users=suspended_users) if start is not None: if suspended_users: uri += "&start="+start else: uri += "?start="+start property_feed = self._GetPropertyFeed(uri) return property_feed def RemoveMemberFromGroup(self, member_id, group_id): """Remove the given member from the given group. Args: member_id: The member's email address (e.g. member@example.com). group_id: The ID of the group (e.g. us-sales). Returns: A dict containing the result of the remove operation. """ uri = self._ServiceUrl('member', True, group_id, member_id, '') return self._DeleteProperties(uri) def AddOwnerToGroup(self, owner_email, group_id): """Add an owner to a group. Args: owner_email: The email address of a group owner. group_id: The ID of the group (e.g. us-sales). Returns: A dict containing the result of the add operation. """ uri = self._ServiceUrl('owner', False, group_id, '', owner_email) properties = {} properties['email'] = owner_email return self._PostProperties(uri, properties) def IsOwner(self, owner_email, group_id): """Check whether the given member an owner of the given group. Args: owner_email: The email address of a group owner. group_id: The ID of the group (e.g. us-sales). Returns: True if the member is an owner of the given group. False otherwise. """ uri = self._ServiceUrl('owner', True, group_id, '', owner_email) return self._IsExisted(uri) def RetrieveOwner(self, owner_email, group_id): """Retrieve the given owner in the given group. Args: owner_email: The email address of a group owner. group_id: The ID of the group (e.g. us-sales). Returns: A dict containing the result of the retrieve operation. """ uri = self._ServiceUrl('owner', True, group_id, '', owner_email) return self._GetProperties(uri) def RetrieveAllOwners(self, group_id, suspended_users=False): """Retrieve all owners of the given group. Args: group_id: The ID of the group (e.g. us-sales). suspended_users: A boolean; should we include any suspended users in the ownership list returned? Returns: A list containing the result of the retrieve operation. """ uri = self._ServiceUrl('owner', True, group_id, '', '', suspended_users=suspended_users) return self._GetPropertiesList(uri) def RetrievePageOfOwners(self, group_id, suspended_users=False, start=None): """Retrieve one page of owners of the given group. Args: group_id: The ID of the group (e.g. us-sales). suspended_users: A boolean; should we include any suspended users in the ownership list returned? start: The key to continue for pagination through all owners. Returns: A feed object containing the result of the retrieve operation. """ uri = self._ServiceUrl('owner', True, group_id, '', '', suspended_users=suspended_users) if start is not None: if suspended_users: uri += "&start="+start else: uri += "?start="+start property_feed = self._GetPropertyFeed(uri) return property_feed def RemoveOwnerFromGroup(self, owner_email, group_id): """Remove the given owner from the given group. Args: owner_email: The email address of a group owner. group_id: The ID of the group (e.g. us-sales). Returns: A dict containing the result of the remove operation. """ uri = self._ServiceUrl('owner', True, group_id, '', owner_email) return self._DeleteProperties(uri) gdata-2.0.18/src/gdata/apps/adminsettings/0000750036466300116100000000000012156625015020415 5ustar afshareng00000000000000gdata-2.0.18/src/gdata/apps/adminsettings/__init__.py0000640036466300116100000000112412156622363022530 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright (C) 2008 Google # # 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. gdata-2.0.18/src/gdata/apps/adminsettings/service.py0000640036466300116100000003250012156622363022433 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright (C) 2008 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. """Allow Google Apps domain administrators to set domain admin settings. AdminSettingsService: Set admin settings.""" __author__ = 'jlee@pbu.edu' import gdata.apps import gdata.apps.service import gdata.service API_VER='2.0' class AdminSettingsService(gdata.apps.service.PropertyService): """Client for the Google Apps Admin Settings service.""" def _serviceUrl(self, setting_id, domain=None): if domain is None: domain = self.domain return '/a/feeds/domain/%s/%s/%s' % (API_VER, domain, setting_id) def genericGet(self, location): """Generic HTTP Get Wrapper Args: location: relative uri to Get Returns: A dict containing the result of the get operation.""" uri = self._serviceUrl(location) try: return self._GetProperties(uri) except gdata.service.RequestError, e: raise AppsForYourDomainException(e.args[0]) def GetDefaultLanguage(self): """Gets Domain Default Language Args: None Returns: Default Language as a string. All possible values are listed at: http://code.google.com/apis/apps/email_settings/developers_guide_protocol.html#GA_email_language_tags""" result = self.genericGet('general/defaultLanguage') return result['defaultLanguage'] def UpdateDefaultLanguage(self, defaultLanguage): """Updates Domain Default Language Args: defaultLanguage: Domain Language to set possible values are at: http://code.google.com/apis/apps/email_settings/developers_guide_protocol.html#GA_email_language_tags Returns: A dict containing the result of the put operation""" uri = self._serviceUrl('general/defaultLanguage') properties = {'defaultLanguage': defaultLanguage} return self._PutProperties(uri, properties) def GetOrganizationName(self): """Gets Domain Default Language Args: None Returns: Organization Name as a string.""" result = self.genericGet('general/organizationName') return result['organizationName'] def UpdateOrganizationName(self, organizationName): """Updates Organization Name Args: organizationName: Name of organization Returns: A dict containing the result of the put operation""" uri = self._serviceUrl('general/organizationName') properties = {'organizationName': organizationName} return self._PutProperties(uri, properties) def GetMaximumNumberOfUsers(self): """Gets Maximum Number of Users Allowed Args: None Returns: An integer, the maximum number of users""" result = self.genericGet('general/maximumNumberOfUsers') return int(result['maximumNumberOfUsers']) def GetCurrentNumberOfUsers(self): """Gets Current Number of Users Args: None Returns: An integer, the current number of users""" result = self.genericGet('general/currentNumberOfUsers') return int(result['currentNumberOfUsers']) def IsDomainVerified(self): """Is the domain verified Args: None Returns: Boolean, is domain verified""" result = self.genericGet('accountInformation/isVerified') if result['isVerified'] == 'true': return True else: return False def GetSupportPIN(self): """Gets Support PIN Args: None Returns: A string, the Support PIN""" result = self.genericGet('accountInformation/supportPIN') return result['supportPIN'] def GetEdition(self): """Gets Google Apps Domain Edition Args: None Returns: A string, the domain's edition (premier, education, partner)""" result = self.genericGet('accountInformation/edition') return result['edition'] def GetCustomerPIN(self): """Gets Customer PIN Args: None Returns: A string, the customer PIN""" result = self.genericGet('accountInformation/customerPIN') return result['customerPIN'] def GetCreationTime(self): """Gets Domain Creation Time Args: None Returns: A string, the domain's creation time""" result = self.genericGet('accountInformation/creationTime') return result['creationTime'] def GetCountryCode(self): """Gets Domain Country Code Args: None Returns: A string, the domain's country code. Possible values at: http://www.iso.org/iso/country_codes/iso_3166_code_lists/english_country_names_and_code_elements.htm""" result = self.genericGet('accountInformation/countryCode') return result['countryCode'] def GetAdminSecondaryEmail(self): """Gets Domain Admin Secondary Email Address Args: None Returns: A string, the secondary email address for domain admin""" result = self.genericGet('accountInformation/adminSecondaryEmail') return result['adminSecondaryEmail'] def UpdateAdminSecondaryEmail(self, adminSecondaryEmail): """Gets Domain Creation Time Args: adminSecondaryEmail: string, secondary email address of admin Returns: A dict containing the result of the put operation""" uri = self._serviceUrl('accountInformation/adminSecondaryEmail') properties = {'adminSecondaryEmail': adminSecondaryEmail} return self._PutProperties(uri, properties) def GetDomainLogo(self): """Gets Domain Logo This function does not make use of the Google Apps Admin Settings API, it does an HTTP Get of a url specific to the Google Apps domain. It is included for completeness sake. Args: None Returns: binary image file""" import urllib url = 'http://www.google.com/a/cpanel/'+self.domain+'/images/logo.gif' response = urllib.urlopen(url) return response.read() def UpdateDomainLogo(self, logoImage): """Update Domain's Custom Logo Args: logoImage: binary image data Returns: A dict containing the result of the put operation""" from base64 import b64encode uri = self._serviceUrl('appearance/customLogo') properties = {'logoImage': b64encode(logoImage)} return self._PutProperties(uri, properties) def GetCNAMEVerificationStatus(self): """Gets Domain CNAME Verification Status Args: None Returns: A dict {recordName, verified, verifiedMethod}""" return self.genericGet('verification/cname') def UpdateCNAMEVerificationStatus(self, verified): """Updates CNAME Verification Status Args: verified: boolean, True will retry verification process Returns: A dict containing the result of the put operation""" uri = self._serviceUrl('verification/cname') properties = self.GetCNAMEVerificationStatus() properties['verified'] = verified return self._PutProperties(uri, properties) def GetMXVerificationStatus(self): """Gets Domain MX Verification Status Args: None Returns: A dict {verified, verifiedMethod}""" return self.genericGet('verification/mx') def UpdateMXVerificationStatus(self, verified): """Updates MX Verification Status Args: verified: boolean, True will retry verification process Returns: A dict containing the result of the put operation""" uri = self._serviceUrl('verification/mx') properties = self.GetMXVerificationStatus() properties['verified'] = verified return self._PutProperties(uri, properties) def GetSSOSettings(self): """Gets Domain Single Sign-On Settings Args: None Returns: A dict {samlSignonUri, samlLogoutUri, changePasswordUri, enableSSO, ssoWhitelist, useDomainSpecificIssuer}""" return self.genericGet('sso/general') def UpdateSSOSettings(self, enableSSO=None, samlSignonUri=None, samlLogoutUri=None, changePasswordUri=None, ssoWhitelist=None, useDomainSpecificIssuer=None): """Update SSO Settings. Args: enableSSO: boolean, SSO Master on/off switch samlSignonUri: string, SSO Login Page samlLogoutUri: string, SSO Logout Page samlPasswordUri: string, SSO Password Change Page ssoWhitelist: string, Range of IP Addresses which will see SSO useDomainSpecificIssuer: boolean, Include Google Apps Domain in Issuer Returns: A dict containing the result of the update operation. """ uri = self._serviceUrl('sso/general') #Get current settings, replace Nones with '' properties = self.GetSSOSettings() if properties['samlSignonUri'] == None: properties['samlSignonUri'] = '' if properties['samlLogoutUri'] == None: properties['samlLogoutUri'] = '' if properties['changePasswordUri'] == None: properties['changePasswordUri'] = '' if properties['ssoWhitelist'] == None: properties['ssoWhitelist'] = '' #update only the values we were passed if enableSSO != None: properties['enableSSO'] = gdata.apps.service._bool2str(enableSSO) if samlSignonUri != None: properties['samlSignonUri'] = samlSignonUri if samlLogoutUri != None: properties['samlLogoutUri'] = samlLogoutUri if changePasswordUri != None: properties['changePasswordUri'] = changePasswordUri if ssoWhitelist != None: properties['ssoWhitelist'] = ssoWhitelist if useDomainSpecificIssuer != None: properties['useDomainSpecificIssuer'] = gdata.apps.service._bool2str(useDomainSpecificIssuer) return self._PutProperties(uri, properties) def GetSSOKey(self): """Gets Domain Single Sign-On Signing Key Args: None Returns: A dict {modulus, exponent, algorithm, format}""" return self.genericGet('sso/signingkey') def UpdateSSOKey(self, signingKey): """Update SSO Settings. Args: signingKey: string, public key to be uploaded Returns: A dict containing the result of the update operation.""" uri = self._serviceUrl('sso/signingkey') properties = {'signingKey': signingKey} return self._PutProperties(uri, properties) def IsUserMigrationEnabled(self): """Is User Migration Enabled Args: None Returns: boolean, is user migration enabled""" result = self.genericGet('email/migration') if result['enableUserMigration'] == 'true': return True else: return False def UpdateUserMigrationStatus(self, enableUserMigration): """Update User Migration Status Args: enableUserMigration: boolean, user migration enable/disable Returns: A dict containing the result of the update operation.""" uri = self._serviceUrl('email/migration') properties = {'enableUserMigration': enableUserMigration} return self._PutProperties(uri, properties) def GetOutboundGatewaySettings(self): """Get Outbound Gateway Settings Args: None Returns: A dict {smartHost, smtpMode}""" uri = self._serviceUrl('email/gateway') try: return self._GetProperties(uri) except gdata.service.RequestError, e: raise AppsForYourDomainException(e.args[0]) except TypeError: #if no outbound gateway is set, we get a TypeError, #catch it and return nothing... return {'smartHost': None, 'smtpMode': None} def UpdateOutboundGatewaySettings(self, smartHost=None, smtpMode=None): """Update Outbound Gateway Settings Args: smartHost: string, ip address or hostname of outbound gateway smtpMode: string, SMTP or SMTP_TLS Returns: A dict containing the result of the update operation.""" uri = self._serviceUrl('email/gateway') #Get current settings, replace Nones with '' properties = GetOutboundGatewaySettings() if properties['smartHost'] == None: properties['smartHost'] = '' if properties['smtpMode'] == None: properties['smtpMode'] = '' #If we were passed new values for smartHost or smtpMode, update them if smartHost != None: properties['smartHost'] = smartHost if smtpMode != None: properties['smtpMode'] = smtpMode return self._PutProperties(uri, properties) def AddEmailRoute(self, routeDestination, routeRewriteTo, routeEnabled, bounceNotifications, accountHandling): """Adds Domain Email Route Args: routeDestination: string, destination ip address or hostname routeRewriteTo: boolean, rewrite smtp envelop To: routeEnabled: boolean, enable disable email routing bounceNotifications: boolean, send bound notificiations to sender accountHandling: string, which to route, "allAccounts", "provisionedAccounts", "unknownAccounts" Returns: A dict containing the result of the update operation.""" uri = self._serviceUrl('emailrouting') properties = {} properties['routeDestination'] = routeDestination properties['routeRewriteTo'] = gdata.apps.service._bool2str(routeRewriteTo) properties['routeEnabled'] = gdata.apps.service._bool2str(routeEnabled) properties['bounceNotifications'] = gdata.apps.service._bool2str(bounceNotifications) properties['accountHandling'] = accountHandling return self._PostProperties(uri, properties) gdata-2.0.18/src/gdata/apps/service.py0000640036466300116100000005014512156622363017567 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright (C) 2007 SIOS Technology, 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. __author__ = 'tmatsuo@sios.com (Takashi MATSUO)' try: from xml.etree import cElementTree as ElementTree except ImportError: try: import cElementTree as ElementTree except ImportError: try: from xml.etree import ElementTree except ImportError: from elementtree import ElementTree import urllib import gdata import atom.service import gdata.service import gdata.apps import atom API_VER="2.0" HTTP_OK=200 UNKOWN_ERROR=1000 USER_DELETED_RECENTLY=1100 USER_SUSPENDED=1101 DOMAIN_USER_LIMIT_EXCEEDED=1200 DOMAIN_ALIAS_LIMIT_EXCEEDED=1201 DOMAIN_SUSPENDED=1202 DOMAIN_FEATURE_UNAVAILABLE=1203 ENTITY_EXISTS=1300 ENTITY_DOES_NOT_EXIST=1301 ENTITY_NAME_IS_RESERVED=1302 ENTITY_NAME_NOT_VALID=1303 INVALID_GIVEN_NAME=1400 INVALID_FAMILY_NAME=1401 INVALID_PASSWORD=1402 INVALID_USERNAME=1403 INVALID_HASH_FUNCTION_NAME=1404 INVALID_HASH_DIGGEST_LENGTH=1405 INVALID_EMAIL_ADDRESS=1406 INVALID_QUERY_PARAMETER_VALUE=1407 TOO_MANY_RECIPIENTS_ON_EMAIL_LIST=1500 DEFAULT_QUOTA_LIMIT='2048' class Error(Exception): pass class AppsForYourDomainException(Error): def __init__(self, response): Error.__init__(self, response) try: self.element_tree = ElementTree.fromstring(response['body']) self.error_code = int(self.element_tree[0].attrib['errorCode']) self.reason = self.element_tree[0].attrib['reason'] self.invalidInput = self.element_tree[0].attrib['invalidInput'] except: self.error_code = UNKOWN_ERROR class AppsService(gdata.service.GDataService): """Client for the Google Apps Provisioning service.""" def __init__(self, email=None, password=None, domain=None, source=None, server='apps-apis.google.com', additional_headers=None, **kwargs): """Creates a client for the Google Apps Provisioning service. Args: email: string (optional) The user's email address, used for authentication. password: string (optional) The user's password. domain: string (optional) The Google Apps domain name. source: string (optional) The name of the user's application. server: string (optional) The name of the server to which a connection will be opened. Default value: 'apps-apis.google.com'. **kwargs: The other parameters to pass to gdata.service.GDataService constructor. """ gdata.service.GDataService.__init__( self, email=email, password=password, service='apps', source=source, server=server, additional_headers=additional_headers, **kwargs) self.ssl = True self.port = 443 self.domain = domain def _baseURL(self): return "/a/feeds/%s" % self.domain def AddAllElementsFromAllPages(self, link_finder, func): """retrieve all pages and add all elements""" next = link_finder.GetNextLink() while next is not None: next_feed = self.Get(next.href, converter=func) for a_entry in next_feed.entry: link_finder.entry.append(a_entry) next = next_feed.GetNextLink() return link_finder def RetrievePageOfEmailLists(self, start_email_list_name=None, num_retries=gdata.service.DEFAULT_NUM_RETRIES, delay=gdata.service.DEFAULT_DELAY, backoff=gdata.service.DEFAULT_BACKOFF): """Retrieve one page of email list""" uri = "%s/emailList/%s" % (self._baseURL(), API_VER) if start_email_list_name is not None: uri += "?startEmailListName=%s" % start_email_list_name try: return gdata.apps.EmailListFeedFromString(str(self.GetWithRetries( uri, num_retries=num_retries, delay=delay, backoff=backoff))) except gdata.service.RequestError, e: raise AppsForYourDomainException(e.args[0]) def GetGeneratorForAllEmailLists( self, num_retries=gdata.service.DEFAULT_NUM_RETRIES, delay=gdata.service.DEFAULT_DELAY, backoff=gdata.service.DEFAULT_BACKOFF): """Retrieve a generator for all emaillists in this domain.""" first_page = self.RetrievePageOfEmailLists(num_retries=num_retries, delay=delay, backoff=backoff) return self.GetGeneratorFromLinkFinder( first_page, gdata.apps.EmailListRecipientFeedFromString, num_retries=num_retries, delay=delay, backoff=backoff) def RetrieveAllEmailLists(self): """Retrieve all email list of a domain.""" ret = self.RetrievePageOfEmailLists() # pagination return self.AddAllElementsFromAllPages( ret, gdata.apps.EmailListFeedFromString) def RetrieveEmailList(self, list_name): """Retreive a single email list by the list's name.""" uri = "%s/emailList/%s/%s" % ( self._baseURL(), API_VER, list_name) try: return self.Get(uri, converter=gdata.apps.EmailListEntryFromString) except gdata.service.RequestError, e: raise AppsForYourDomainException(e.args[0]) def RetrieveEmailLists(self, recipient): """Retrieve All Email List Subscriptions for an Email Address.""" uri = "%s/emailList/%s?recipient=%s" % ( self._baseURL(), API_VER, recipient) try: ret = gdata.apps.EmailListFeedFromString(str(self.Get(uri))) except gdata.service.RequestError, e: raise AppsForYourDomainException(e.args[0]) # pagination return self.AddAllElementsFromAllPages( ret, gdata.apps.EmailListFeedFromString) def RemoveRecipientFromEmailList(self, recipient, list_name): """Remove recipient from email list.""" uri = "%s/emailList/%s/%s/recipient/%s" % ( self._baseURL(), API_VER, list_name, recipient) try: self.Delete(uri) except gdata.service.RequestError, e: raise AppsForYourDomainException(e.args[0]) def RetrievePageOfRecipients(self, list_name, start_recipient=None, num_retries=gdata.service.DEFAULT_NUM_RETRIES, delay=gdata.service.DEFAULT_DELAY, backoff=gdata.service.DEFAULT_BACKOFF): """Retrieve one page of recipient of an email list. """ uri = "%s/emailList/%s/%s/recipient" % ( self._baseURL(), API_VER, list_name) if start_recipient is not None: uri += "?startRecipient=%s" % start_recipient try: return gdata.apps.EmailListRecipientFeedFromString(str( self.GetWithRetries( uri, num_retries=num_retries, delay=delay, backoff=backoff))) except gdata.service.RequestError, e: raise AppsForYourDomainException(e.args[0]) def GetGeneratorForAllRecipients( self, list_name, num_retries=gdata.service.DEFAULT_NUM_RETRIES, delay=gdata.service.DEFAULT_DELAY, backoff=gdata.service.DEFAULT_BACKOFF): """Retrieve a generator for all recipients of a particular emaillist.""" first_page = self.RetrievePageOfRecipients(list_name, num_retries=num_retries, delay=delay, backoff=backoff) return self.GetGeneratorFromLinkFinder( first_page, gdata.apps.EmailListRecipientFeedFromString, num_retries=num_retries, delay=delay, backoff=backoff) def RetrieveAllRecipients(self, list_name): """Retrieve all recipient of an email list.""" ret = self.RetrievePageOfRecipients(list_name) # pagination return self.AddAllElementsFromAllPages( ret, gdata.apps.EmailListRecipientFeedFromString) def AddRecipientToEmailList(self, recipient, list_name): """Add a recipient to a email list.""" uri = "%s/emailList/%s/%s/recipient" % ( self._baseURL(), API_VER, list_name) recipient_entry = gdata.apps.EmailListRecipientEntry() recipient_entry.who = gdata.apps.Who(email=recipient) try: return gdata.apps.EmailListRecipientEntryFromString( str(self.Post(recipient_entry, uri))) except gdata.service.RequestError, e: raise AppsForYourDomainException(e.args[0]) def DeleteEmailList(self, list_name): """Delete a email list""" uri = "%s/emailList/%s/%s" % (self._baseURL(), API_VER, list_name) try: self.Delete(uri) except gdata.service.RequestError, e: raise AppsForYourDomainException(e.args[0]) def CreateEmailList(self, list_name): """Create a email list. """ uri = "%s/emailList/%s" % (self._baseURL(), API_VER) email_list_entry = gdata.apps.EmailListEntry() email_list_entry.email_list = gdata.apps.EmailList(name=list_name) try: return gdata.apps.EmailListEntryFromString( str(self.Post(email_list_entry, uri))) except gdata.service.RequestError, e: raise AppsForYourDomainException(e.args[0]) def DeleteNickname(self, nickname): """Delete a nickname""" uri = "%s/nickname/%s/%s" % (self._baseURL(), API_VER, nickname) try: self.Delete(uri) except gdata.service.RequestError, e: raise AppsForYourDomainException(e.args[0]) def RetrievePageOfNicknames(self, start_nickname=None, num_retries=gdata.service.DEFAULT_NUM_RETRIES, delay=gdata.service.DEFAULT_DELAY, backoff=gdata.service.DEFAULT_BACKOFF): """Retrieve one page of nicknames in the domain""" uri = "%s/nickname/%s" % (self._baseURL(), API_VER) if start_nickname is not None: uri += "?startNickname=%s" % start_nickname try: return gdata.apps.NicknameFeedFromString(str(self.GetWithRetries( uri, num_retries=num_retries, delay=delay, backoff=backoff))) except gdata.service.RequestError, e: raise AppsForYourDomainException(e.args[0]) def GetGeneratorForAllNicknames( self, num_retries=gdata.service.DEFAULT_NUM_RETRIES, delay=gdata.service.DEFAULT_DELAY, backoff=gdata.service.DEFAULT_BACKOFF): """Retrieve a generator for all nicknames in this domain.""" first_page = self.RetrievePageOfNicknames(num_retries=num_retries, delay=delay, backoff=backoff) return self.GetGeneratorFromLinkFinder( first_page, gdata.apps.NicknameFeedFromString, num_retries=num_retries, delay=delay, backoff=backoff) def RetrieveAllNicknames(self): """Retrieve all nicknames in the domain""" ret = self.RetrievePageOfNicknames() # pagination return self.AddAllElementsFromAllPages( ret, gdata.apps.NicknameFeedFromString) def GetGeneratorForAllNicknamesOfAUser( self, user_name, num_retries=gdata.service.DEFAULT_NUM_RETRIES, delay=gdata.service.DEFAULT_DELAY, backoff=gdata.service.DEFAULT_BACKOFF): """Retrieve a generator for all nicknames of a particular user.""" uri = "%s/nickname/%s?username=%s" % (self._baseURL(), API_VER, user_name) try: first_page = gdata.apps.NicknameFeedFromString(str(self.GetWithRetries( uri, num_retries=num_retries, delay=delay, backoff=backoff))) except gdata.service.RequestError, e: raise AppsForYourDomainException(e.args[0]) return self.GetGeneratorFromLinkFinder( first_page, gdata.apps.NicknameFeedFromString, num_retries=num_retries, delay=delay, backoff=backoff) def RetrieveNicknames(self, user_name): """Retrieve nicknames of the user""" uri = "%s/nickname/%s?username=%s" % (self._baseURL(), API_VER, user_name) try: ret = gdata.apps.NicknameFeedFromString(str(self.Get(uri))) except gdata.service.RequestError, e: raise AppsForYourDomainException(e.args[0]) # pagination return self.AddAllElementsFromAllPages( ret, gdata.apps.NicknameFeedFromString) def RetrieveNickname(self, nickname): """Retrieve a nickname. Args: nickname: string The nickname to retrieve Returns: gdata.apps.NicknameEntry """ uri = "%s/nickname/%s/%s" % (self._baseURL(), API_VER, nickname) try: return gdata.apps.NicknameEntryFromString(str(self.Get(uri))) except gdata.service.RequestError, e: raise AppsForYourDomainException(e.args[0]) def CreateNickname(self, user_name, nickname): """Create a nickname""" uri = "%s/nickname/%s" % (self._baseURL(), API_VER) nickname_entry = gdata.apps.NicknameEntry() nickname_entry.login = gdata.apps.Login(user_name=user_name) nickname_entry.nickname = gdata.apps.Nickname(name=nickname) try: return gdata.apps.NicknameEntryFromString( str(self.Post(nickname_entry, uri))) except gdata.service.RequestError, e: raise AppsForYourDomainException(e.args[0]) def DeleteUser(self, user_name): """Delete a user account""" uri = "%s/user/%s/%s" % (self._baseURL(), API_VER, user_name) try: return self.Delete(uri) except gdata.service.RequestError, e: raise AppsForYourDomainException(e.args[0]) def UpdateUser(self, user_name, user_entry): """Update a user account.""" uri = "%s/user/%s/%s" % (self._baseURL(), API_VER, user_name) try: return gdata.apps.UserEntryFromString(str(self.Put(user_entry, uri))) except gdata.service.RequestError, e: raise AppsForYourDomainException(e.args[0]) def CreateUser(self, user_name, family_name, given_name, password, suspended='false', quota_limit=None, password_hash_function=None, change_password=None): """Create a user account. """ uri = "%s/user/%s" % (self._baseURL(), API_VER) user_entry = gdata.apps.UserEntry() user_entry.login = gdata.apps.Login( user_name=user_name, password=password, suspended=suspended, hash_function_name=password_hash_function, change_password=change_password) user_entry.name = gdata.apps.Name(family_name=family_name, given_name=given_name) if quota_limit is not None: user_entry.quota = gdata.apps.Quota(limit=str(quota_limit)) try: return gdata.apps.UserEntryFromString(str(self.Post(user_entry, uri))) except gdata.service.RequestError, e: raise AppsForYourDomainException(e.args[0]) def SuspendUser(self, user_name): user_entry = self.RetrieveUser(user_name) if user_entry.login.suspended != 'true': user_entry.login.suspended = 'true' user_entry = self.UpdateUser(user_name, user_entry) return user_entry def RestoreUser(self, user_name): user_entry = self.RetrieveUser(user_name) if user_entry.login.suspended != 'false': user_entry.login.suspended = 'false' user_entry = self.UpdateUser(user_name, user_entry) return user_entry def RetrieveUser(self, user_name): """Retrieve an user account. Args: user_name: string The user name to retrieve Returns: gdata.apps.UserEntry """ uri = "%s/user/%s/%s" % (self._baseURL(), API_VER, user_name) try: return gdata.apps.UserEntryFromString(str(self.Get(uri))) except gdata.service.RequestError, e: raise AppsForYourDomainException(e.args[0]) def RetrievePageOfUsers(self, start_username=None, num_retries=gdata.service.DEFAULT_NUM_RETRIES, delay=gdata.service.DEFAULT_DELAY, backoff=gdata.service.DEFAULT_BACKOFF): """Retrieve one page of users in this domain.""" uri = "%s/user/%s" % (self._baseURL(), API_VER) if start_username is not None: uri += "?startUsername=%s" % start_username try: return gdata.apps.UserFeedFromString(str(self.GetWithRetries( uri, num_retries=num_retries, delay=delay, backoff=backoff))) except gdata.service.RequestError, e: raise AppsForYourDomainException(e.args[0]) def GetGeneratorForAllUsers(self, num_retries=gdata.service.DEFAULT_NUM_RETRIES, delay=gdata.service.DEFAULT_DELAY, backoff=gdata.service.DEFAULT_BACKOFF): """Retrieve a generator for all users in this domain.""" first_page = self.RetrievePageOfUsers(num_retries=num_retries, delay=delay, backoff=backoff) return self.GetGeneratorFromLinkFinder( first_page, gdata.apps.UserFeedFromString, num_retries=num_retries, delay=delay, backoff=backoff) def RetrieveAllUsers(self): """Retrieve all users in this domain. OBSOLETE""" ret = self.RetrievePageOfUsers() # pagination return self.AddAllElementsFromAllPages( ret, gdata.apps.UserFeedFromString) class PropertyService(gdata.service.GDataService): """Client for the Google Apps Property service.""" def __init__(self, email=None, password=None, domain=None, source=None, server='apps-apis.google.com', additional_headers=None): gdata.service.GDataService.__init__(self, email=email, password=password, service='apps', source=source, server=server, additional_headers=additional_headers) self.ssl = True self.port = 443 self.domain = domain def AddAllElementsFromAllPages(self, link_finder, func): """retrieve all pages and add all elements""" next = link_finder.GetNextLink() while next is not None: next_feed = self.Get(next.href, converter=func) for a_entry in next_feed.entry: link_finder.entry.append(a_entry) next = next_feed.GetNextLink() return link_finder def _GetPropertyEntry(self, properties): property_entry = gdata.apps.PropertyEntry() property = [] for name, value in properties.iteritems(): if name is not None and value is not None: property.append(gdata.apps.Property(name=name, value=value)) property_entry.property = property return property_entry def _PropertyEntry2Dict(self, property_entry): properties = {} for i, property in enumerate(property_entry.property): properties[property.name] = property.value return properties def _GetPropertyFeed(self, uri): try: return gdata.apps.PropertyFeedFromString(str(self.Get(uri))) except gdata.service.RequestError, e: raise gdata.apps.service.AppsForYourDomainException(e.args[0]) def _GetPropertiesList(self, uri): property_feed = self._GetPropertyFeed(uri) # pagination property_feed = self.AddAllElementsFromAllPages( property_feed, gdata.apps.PropertyFeedFromString) properties_list = [] for property_entry in property_feed.entry: properties_list.append(self._PropertyEntry2Dict(property_entry)) return properties_list def _GetProperties(self, uri): try: return self._PropertyEntry2Dict(gdata.apps.PropertyEntryFromString( str(self.Get(uri)))) except gdata.service.RequestError, e: raise gdata.apps.service.AppsForYourDomainException(e.args[0]) def _PostProperties(self, uri, properties): property_entry = self._GetPropertyEntry(properties) try: return self._PropertyEntry2Dict(gdata.apps.PropertyEntryFromString( str(self.Post(property_entry, uri)))) except gdata.service.RequestError, e: raise gdata.apps.service.AppsForYourDomainException(e.args[0]) def _PutProperties(self, uri, properties): property_entry = self._GetPropertyEntry(properties) try: return self._PropertyEntry2Dict(gdata.apps.PropertyEntryFromString( str(self.Put(property_entry, uri)))) except gdata.service.RequestError, e: raise gdata.apps.service.AppsForYourDomainException(e.args[0]) def _DeleteProperties(self, uri): try: self.Delete(uri) except gdata.service.RequestError, e: raise gdata.apps.service.AppsForYourDomainException(e.args[0]) def _bool2str(b): if b is None: return None return str(b is True).lower() gdata-2.0.18/src/gdata/media/0000750036466300116100000000000012156625015015660 5ustar afshareng00000000000000gdata-2.0.18/src/gdata/media/data.py0000640036466300116100000000733312156622363017155 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright (C) 2009 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. """Contains the data classes of the Yahoo! Media RSS Extension""" __author__ = 'j.s@google.com (Jeff Scudder)' import atom.core MEDIA_TEMPLATE = '{http://search.yahoo.com/mrss//}%s' class MediaCategory(atom.core.XmlElement): """Describes a media category.""" _qname = MEDIA_TEMPLATE % 'category' scheme = 'scheme' label = 'label' class MediaCopyright(atom.core.XmlElement): """Describes a media copyright.""" _qname = MEDIA_TEMPLATE % 'copyright' url = 'url' class MediaCredit(atom.core.XmlElement): """Describes a media credit.""" _qname = MEDIA_TEMPLATE % 'credit' role = 'role' scheme = 'scheme' class MediaDescription(atom.core.XmlElement): """Describes a media description.""" _qname = MEDIA_TEMPLATE % 'description' type = 'type' class MediaHash(atom.core.XmlElement): """Describes a media hash.""" _qname = MEDIA_TEMPLATE % 'hash' algo = 'algo' class MediaKeywords(atom.core.XmlElement): """Describes a media keywords.""" _qname = MEDIA_TEMPLATE % 'keywords' class MediaPlayer(atom.core.XmlElement): """Describes a media player.""" _qname = MEDIA_TEMPLATE % 'player' height = 'height' width = 'width' url = 'url' class MediaRating(atom.core.XmlElement): """Describes a media rating.""" _qname = MEDIA_TEMPLATE % 'rating' scheme = 'scheme' class MediaRestriction(atom.core.XmlElement): """Describes a media restriction.""" _qname = MEDIA_TEMPLATE % 'restriction' relationship = 'relationship' type = 'type' class MediaText(atom.core.XmlElement): """Describes a media text.""" _qname = MEDIA_TEMPLATE % 'text' end = 'end' lang = 'lang' type = 'type' start = 'start' class MediaThumbnail(atom.core.XmlElement): """Describes a media thumbnail.""" _qname = MEDIA_TEMPLATE % 'thumbnail' time = 'time' url = 'url' width = 'width' height = 'height' class MediaTitle(atom.core.XmlElement): """Describes a media title.""" _qname = MEDIA_TEMPLATE % 'title' type = 'type' class MediaContent(atom.core.XmlElement): """Describes a media content.""" _qname = MEDIA_TEMPLATE % 'content' bitrate = 'bitrate' is_default = 'isDefault' medium = 'medium' height = 'height' credit = [MediaCredit] language = 'language' hash = MediaHash width = 'width' player = MediaPlayer url = 'url' file_size = 'fileSize' channels = 'channels' expression = 'expression' text = [MediaText] samplingrate = 'samplingrate' title = MediaTitle category = [MediaCategory] rating = [MediaRating] type = 'type' description = MediaDescription framerate = 'framerate' thumbnail = [MediaThumbnail] duration = 'duration' copyright = MediaCopyright keywords = MediaKeywords restriction = [MediaRestriction] class MediaGroup(atom.core.XmlElement): """Describes a media group.""" _qname = MEDIA_TEMPLATE % 'group' credit = [MediaCredit] content = [MediaContent] copyright = MediaCopyright description = MediaDescription category = [MediaCategory] player = MediaPlayer rating = [MediaRating] hash = MediaHash title = MediaTitle keywords = MediaKeywords restriction = [MediaRestriction] thumbnail = [MediaThumbnail] text = [MediaText] gdata-2.0.18/src/gdata/media/__init__.py0000640036466300116100000002747512156622363020014 0ustar afshareng00000000000000# -*-*- encoding: utf-8 -*-*- # # This is gdata.photos.media, implementing parts of the MediaRSS spec in gdata structures # # $Id: __init__.py 81 2007-10-03 14:41:42Z havard.gulldahl $ # # Copyright 2007 HÃ¥vard Gulldahl # Portions copyright 2007 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. """Essential attributes of photos in Google Photos/Picasa Web Albums are expressed using elements from the `media' namespace, defined in the MediaRSS specification[1]. Due to copyright issues, the elements herein are documented sparingly, please consult with the Google Photos API Reference Guide[2], alternatively the official MediaRSS specification[1] for details. (If there is a version conflict between the two sources, stick to the Google Photos API). [1]: http://search.yahoo.com/mrss (version 1.1.1) [2]: http://code.google.com/apis/picasaweb/reference.html#media_reference Keep in mind that Google Photos only uses a subset of the MediaRSS elements (and some of the attributes are trimmed down, too): media:content media:credit media:description media:group media:keywords media:thumbnail media:title """ __author__ = u'havard@gulldahl.no'# (HÃ¥vard Gulldahl)' #BUG: api chokes on non-ascii chars in __author__ __license__ = 'Apache License v2' import atom import gdata MEDIA_NAMESPACE = 'http://search.yahoo.com/mrss/' YOUTUBE_NAMESPACE = 'http://gdata.youtube.com/schemas/2007' class MediaBaseElement(atom.AtomBase): """Base class for elements in the MEDIA_NAMESPACE. To add new elements, you only need to add the element tag name to self._tag """ _tag = '' _namespace = MEDIA_NAMESPACE _children = atom.AtomBase._children.copy() _attributes = atom.AtomBase._attributes.copy() def __init__(self, name=None, extension_elements=None, extension_attributes=None, text=None): self.name = name self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} class Content(MediaBaseElement): """(attribute container) This element describes the original content, e.g. an image or a video. There may be multiple Content elements in a media:Group. For example, a video may have a element that specifies a JPEG representation of the video, and a element that specifies the URL of the video itself. Attributes: url: non-ambigous reference to online object width: width of the object frame, in pixels height: width of the object frame, in pixels medium: one of `image' or `video', allowing the api user to quickly determine the object's type type: Internet media Type[1] (a.k.a. mime type) of the object -- a more verbose way of determining the media type. To set the type member in the contructor, use the content_type parameter. (optional) fileSize: the size of the object, in bytes [1]: http://en.wikipedia.org/wiki/Internet_media_type """ _tag = 'content' _attributes = atom.AtomBase._attributes.copy() _attributes['url'] = 'url' _attributes['width'] = 'width' _attributes['height'] = 'height' _attributes['medium'] = 'medium' _attributes['type'] = 'type' _attributes['fileSize'] = 'fileSize' def __init__(self, url=None, width=None, height=None, medium=None, content_type=None, fileSize=None, format=None, extension_elements=None, extension_attributes=None, text=None): MediaBaseElement.__init__(self, extension_elements=extension_elements, extension_attributes=extension_attributes, text=text) self.url = url self.width = width self.height = height self.medium = medium self.type = content_type self.fileSize = fileSize def ContentFromString(xml_string): return atom.CreateClassFromXMLString(Content, xml_string) class Credit(MediaBaseElement): """(string) Contains the nickname of the user who created the content, e.g. `Liz Bennet'. This is a user-specified value that should be used when referring to the user by name. Note that none of the attributes from the MediaRSS spec are supported. """ _tag = 'credit' def CreditFromString(xml_string): return atom.CreateClassFromXMLString(Credit, xml_string) class Description(MediaBaseElement): """(string) A description of the media object. Either plain unicode text, or entity-encoded html (look at the `type' attribute). E.g `A set of photographs I took while vacationing in Italy.' For `api' projections, the description is in plain text; for `base' projections, the description is in HTML. Attributes: type: either `text' or `html'. To set the type member in the contructor, use the description_type parameter. """ _tag = 'description' _attributes = atom.AtomBase._attributes.copy() _attributes['type'] = 'type' def __init__(self, description_type=None, extension_elements=None, extension_attributes=None, text=None): MediaBaseElement.__init__(self, extension_elements=extension_elements, extension_attributes=extension_attributes, text=text) self.type = description_type def DescriptionFromString(xml_string): return atom.CreateClassFromXMLString(Description, xml_string) class Keywords(MediaBaseElement): """(string) Lists the tags associated with the entry, e.g `italy, vacation, sunset'. Contains a comma-separated list of tags that have been added to the photo, or all tags that have been added to photos in the album. """ _tag = 'keywords' def KeywordsFromString(xml_string): return atom.CreateClassFromXMLString(Keywords, xml_string) class Thumbnail(MediaBaseElement): """(attributes) Contains the URL of a thumbnail of a photo or album cover. There can be multiple elements for a given ; for example, a given item may have multiple thumbnails at different sizes. Photos generally have two thumbnails at different sizes; albums generally have one cropped thumbnail. If the thumbsize parameter is set to the initial query, this element points to thumbnails of the requested sizes; otherwise the thumbnails are the default thumbnail size. This element must not be confused with the element. Attributes: url: The URL of the thumbnail image. height: The height of the thumbnail image, in pixels. width: The width of the thumbnail image, in pixels. """ _tag = 'thumbnail' _attributes = atom.AtomBase._attributes.copy() _attributes['url'] = 'url' _attributes['width'] = 'width' _attributes['height'] = 'height' def __init__(self, url=None, width=None, height=None, extension_attributes=None, text=None, extension_elements=None): MediaBaseElement.__init__(self, extension_elements=extension_elements, extension_attributes=extension_attributes, text=text) self.url = url self.width = width self.height = height def ThumbnailFromString(xml_string): return atom.CreateClassFromXMLString(Thumbnail, xml_string) class Title(MediaBaseElement): """(string) Contains the title of the entry's media content, in plain text. Attributes: type: Always set to plain. To set the type member in the constructor, use the title_type parameter. """ _tag = 'title' _attributes = atom.AtomBase._attributes.copy() _attributes['type'] = 'type' def __init__(self, title_type=None, extension_attributes=None, text=None, extension_elements=None): MediaBaseElement.__init__(self, extension_elements=extension_elements, extension_attributes=extension_attributes, text=text) self.type = title_type def TitleFromString(xml_string): return atom.CreateClassFromXMLString(Title, xml_string) class Player(MediaBaseElement): """(string) Contains the embeddable player URL for the entry's media content if the media is a video. Attributes: url: Always set to plain """ _tag = 'player' _attributes = atom.AtomBase._attributes.copy() _attributes['url'] = 'url' def __init__(self, player_url=None, extension_attributes=None, extension_elements=None): MediaBaseElement.__init__(self, extension_elements=extension_elements, extension_attributes=extension_attributes) self.url= player_url class Private(atom.AtomBase): """The YouTube Private element""" _tag = 'private' _namespace = YOUTUBE_NAMESPACE class Duration(atom.AtomBase): """The YouTube Duration element""" _tag = 'duration' _namespace = YOUTUBE_NAMESPACE _attributes = atom.AtomBase._attributes.copy() _attributes['seconds'] = 'seconds' class Category(MediaBaseElement): """The mediagroup:category element""" _tag = 'category' _attributes = atom.AtomBase._attributes.copy() _attributes['term'] = 'term' _attributes['scheme'] = 'scheme' _attributes['label'] = 'label' def __init__(self, term=None, scheme=None, label=None, text=None, extension_elements=None, extension_attributes=None): """Constructor for Category Args: term: str scheme: str label: str text: str The text data in the this element extension_elements: list A list of ExtensionElement instances extension_attributes: dict A dictionary of attribute value string pairs """ self.term = term self.scheme = scheme self.label = label self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} class Group(MediaBaseElement): """Container element for all media elements. The element can appear as a child of an album, photo or video entry.""" _tag = 'group' _children = atom.AtomBase._children.copy() _children['{%s}content' % MEDIA_NAMESPACE] = ('content', [Content,]) _children['{%s}credit' % MEDIA_NAMESPACE] = ('credit', Credit) _children['{%s}description' % MEDIA_NAMESPACE] = ('description', Description) _children['{%s}keywords' % MEDIA_NAMESPACE] = ('keywords', Keywords) _children['{%s}thumbnail' % MEDIA_NAMESPACE] = ('thumbnail', [Thumbnail,]) _children['{%s}title' % MEDIA_NAMESPACE] = ('title', Title) _children['{%s}category' % MEDIA_NAMESPACE] = ('category', [Category,]) _children['{%s}duration' % YOUTUBE_NAMESPACE] = ('duration', Duration) _children['{%s}private' % YOUTUBE_NAMESPACE] = ('private', Private) _children['{%s}player' % MEDIA_NAMESPACE] = ('player', Player) def __init__(self, content=None, credit=None, description=None, keywords=None, thumbnail=None, title=None, duration=None, private=None, category=None, player=None, extension_elements=None, extension_attributes=None, text=None): MediaBaseElement.__init__(self, extension_elements=extension_elements, extension_attributes=extension_attributes, text=text) self.content=content self.credit=credit self.description=description self.keywords=keywords self.thumbnail=thumbnail or [] self.title=title self.duration=duration self.private=private self.category=category or [] self.player=player def GroupFromString(xml_string): return atom.CreateClassFromXMLString(Group, xml_string) gdata-2.0.18/src/gdata/notebook/0000750036466300116100000000000012156625015016421 5ustar afshareng00000000000000gdata-2.0.18/src/gdata/notebook/data.py0000640036466300116100000000262212156622363017712 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright (C) 2009 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. """Contains the data classes of the Google Notebook Data API""" __author__ = 'j.s@google.com (Jeff Scudder)' import atom.core import atom.data import gdata.data import gdata.opensearch.data NB_TEMPLATE = '{http://schemas.google.com/notes/2008/}%s' class ComesAfter(atom.core.XmlElement): """Preceding element.""" _qname = NB_TEMPLATE % 'comesAfter' id = 'id' class NoteEntry(gdata.data.GDEntry): """Describes a note entry in the feed of a user's notebook.""" class NotebookFeed(gdata.data.GDFeed): """Describes a notebook feed.""" entry = [NoteEntry] class NotebookListEntry(gdata.data.GDEntry): """Describes a note list entry in the feed of a user's list of public notebooks.""" class NotebookListFeed(gdata.data.GDFeed): """Describes a notebook list feed.""" entry = [NotebookListEntry] gdata-2.0.18/src/gdata/notebook/__init__.py0000640036466300116100000000113012156622363020531 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright (C) 2009 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. gdata-2.0.18/src/gdata/urlfetch.py0000640036466300116100000002216212156622363016776 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright (C) 2008 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. """Provides HTTP functions for gdata.service to use on Google App Engine AppEngineHttpClient: Provides an HTTP request method which uses App Engine's urlfetch API. Set the http_client member of a GDataService object to an instance of an AppEngineHttpClient to allow the gdata library to run on Google App Engine. run_on_appengine: Function which will modify an existing GDataService object to allow it to run on App Engine. It works by creating a new instance of the AppEngineHttpClient and replacing the GDataService object's http_client. HttpRequest: Function that wraps google.appengine.api.urlfetch.Fetch in a common interface which is used by gdata.service.GDataService. In other words, this module can be used as the gdata service request handler so that all HTTP requests will be performed by the hosting Google App Engine server. """ __author__ = 'api.jscudder (Jeff Scudder)' import StringIO import atom.service import atom.http_interface from google.appengine.api import urlfetch def run_on_appengine(gdata_service): """Modifies a GDataService object to allow it to run on App Engine. Args: gdata_service: An instance of AtomService, GDataService, or any of their subclasses which has an http_client member. """ gdata_service.http_client = AppEngineHttpClient() class AppEngineHttpClient(atom.http_interface.GenericHttpClient): def __init__(self, headers=None): self.debug = False self.headers = headers or {} def request(self, operation, url, data=None, headers=None): """Performs an HTTP call to the server, supports GET, POST, PUT, and DELETE. Usage example, perform and HTTP GET on http://www.google.com/: import atom.http client = atom.http.HttpClient() http_response = client.request('GET', 'http://www.google.com/') Args: operation: str The HTTP operation to be performed. This is usually one of 'GET', 'POST', 'PUT', or 'DELETE' data: filestream, list of parts, or other object which can be converted to a string. Should be set to None when performing a GET or DELETE. If data is a file-like object which can be read, this method will read a chunk of 100K bytes at a time and send them. If the data is a list of parts to be sent, each part will be evaluated and sent. url: The full URL to which the request should be sent. Can be a string or atom.url.Url. headers: dict of strings. HTTP headers which should be sent in the request. """ all_headers = self.headers.copy() if headers: all_headers.update(headers) # Construct the full payload. # Assume that data is None or a string. data_str = data if data: if isinstance(data, list): # If data is a list of different objects, convert them all to strings # and join them together. converted_parts = [__ConvertDataPart(x) for x in data] data_str = ''.join(converted_parts) else: data_str = __ConvertDataPart(data) # If the list of headers does not include a Content-Length, attempt to # calculate it based on the data object. if data and 'Content-Length' not in all_headers: all_headers['Content-Length'] = len(data_str) # Set the content type to the default value if none was set. if 'Content-Type' not in all_headers: all_headers['Content-Type'] = 'application/atom+xml' # Lookup the urlfetch operation which corresponds to the desired HTTP verb. if operation == 'GET': method = urlfetch.GET elif operation == 'POST': method = urlfetch.POST elif operation == 'PUT': method = urlfetch.PUT elif operation == 'DELETE': method = urlfetch.DELETE else: method = None return HttpResponse(urlfetch.Fetch(url=str(url), payload=data_str, method=method, headers=all_headers)) def HttpRequest(service, operation, data, uri, extra_headers=None, url_params=None, escape_params=True, content_type='application/atom+xml'): """Performs an HTTP call to the server, supports GET, POST, PUT, and DELETE. This function is deprecated, use AppEngineHttpClient.request instead. To use this module with gdata.service, you can set this module to be the http_request_handler so that HTTP requests use Google App Engine's urlfetch. import gdata.service import gdata.urlfetch gdata.service.http_request_handler = gdata.urlfetch Args: service: atom.AtomService object which contains some of the parameters needed to make the request. The following members are used to construct the HTTP call: server (str), additional_headers (dict), port (int), and ssl (bool). operation: str The HTTP operation to be performed. This is usually one of 'GET', 'POST', 'PUT', or 'DELETE' data: filestream, list of parts, or other object which can be converted to a string. Should be set to None when performing a GET or PUT. If data is a file-like object which can be read, this method will read a chunk of 100K bytes at a time and send them. If the data is a list of parts to be sent, each part will be evaluated and sent. uri: The beginning of the URL to which the request should be sent. Examples: '/', '/base/feeds/snippets', '/m8/feeds/contacts/default/base' extra_headers: dict of strings. HTTP headers which should be sent in the request. These headers are in addition to those stored in service.additional_headers. url_params: dict of strings. Key value pairs to be added to the URL as URL parameters. For example {'foo':'bar', 'test':'param'} will become ?foo=bar&test=param. escape_params: bool default True. If true, the keys and values in url_params will be URL escaped when the form is constructed (Special characters converted to %XX form.) content_type: str The MIME type for the data being sent. Defaults to 'application/atom+xml', this is only used if data is set. """ full_uri = atom.service.BuildUri(uri, url_params, escape_params) (server, port, ssl, partial_uri) = atom.service.ProcessUrl(service, full_uri) # Construct the full URL for the request. if ssl: full_url = 'https://%s%s' % (server, partial_uri) else: full_url = 'http://%s%s' % (server, partial_uri) # Construct the full payload. # Assume that data is None or a string. data_str = data if data: if isinstance(data, list): # If data is a list of different objects, convert them all to strings # and join them together. converted_parts = [__ConvertDataPart(x) for x in data] data_str = ''.join(converted_parts) else: data_str = __ConvertDataPart(data) # Construct the dictionary of HTTP headers. headers = {} if isinstance(service.additional_headers, dict): headers = service.additional_headers.copy() if isinstance(extra_headers, dict): for header, value in extra_headers.iteritems(): headers[header] = value # Add the content type header (we don't need to calculate content length, # since urlfetch.Fetch will calculate for us). if content_type: headers['Content-Type'] = content_type # Lookup the urlfetch operation which corresponds to the desired HTTP verb. if operation == 'GET': method = urlfetch.GET elif operation == 'POST': method = urlfetch.POST elif operation == 'PUT': method = urlfetch.PUT elif operation == 'DELETE': method = urlfetch.DELETE else: method = None return HttpResponse(urlfetch.Fetch(url=full_url, payload=data_str, method=method, headers=headers)) def __ConvertDataPart(data): if not data or isinstance(data, str): return data elif hasattr(data, 'read'): # data is a file like object, so read it completely. return data.read() # The data object was not a file. # Try to convert to a string and send the data. return str(data) class HttpResponse(object): """Translates a urlfetch resoinse to look like an hhtplib resoinse. Used to allow the resoinse from HttpRequest to be usable by gdata.service methods. """ def __init__(self, urlfetch_response): self.body = StringIO.StringIO(urlfetch_response.content) self.headers = urlfetch_response.headers self.status = urlfetch_response.status_code self.reason = '' def read(self, length=None): if not length: return self.body.read() else: return self.body.read(length) def getheader(self, name): if not self.headers.has_key(name): return self.headers[name.lower()] return self.headers[name] gdata-2.0.18/src/gdata/calendar/0000750036466300116100000000000012156625015016352 5ustar afshareng00000000000000gdata-2.0.18/src/gdata/calendar/data.py0000640036466300116100000002317412156622363017650 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright (C) 2009 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. """Contains the data classes of the Google Calendar Data API""" __author__ = 'j.s@google.com (Jeff Scudder)' import atom.core import atom.data import gdata.acl.data import gdata.data import gdata.geo.data import gdata.opensearch.data GCAL_NAMESPACE = 'http://schemas.google.com/gCal/2005' GCAL_TEMPLATE = '{%s}%%s' % GCAL_NAMESPACE WEB_CONTENT_LINK_REL = '%s/%s' % (GCAL_NAMESPACE, 'webContent') class AccessLevelProperty(atom.core.XmlElement): """Describes how much a given user may do with an event or calendar""" _qname = GCAL_TEMPLATE % 'accesslevel' value = 'value' class AllowGSync2Property(atom.core.XmlElement): """Whether the user is permitted to run Google Apps Sync""" _qname = GCAL_TEMPLATE % 'allowGSync2' value = 'value' class AllowGSyncProperty(atom.core.XmlElement): """Whether the user is permitted to run Google Apps Sync""" _qname = GCAL_TEMPLATE % 'allowGSync' value = 'value' class AnyoneCanAddSelfProperty(atom.core.XmlElement): """Whether anyone can add self as attendee""" _qname = GCAL_TEMPLATE % 'anyoneCanAddSelf' value = 'value' class CalendarAclRole(gdata.acl.data.AclRole): """Describes the Calendar roles of an entry in the Calendar access control list""" _qname = gdata.acl.data.GACL_TEMPLATE % 'role' class CalendarCommentEntry(gdata.data.GDEntry): """Describes an entry in a feed of a Calendar event's comments""" class CalendarCommentFeed(gdata.data.GDFeed): """Describes feed of a Calendar event's comments""" entry = [CalendarCommentEntry] class CalendarComments(gdata.data.Comments): """Describes a container of a feed link for Calendar comment entries""" _qname = gdata.data.GD_TEMPLATE % 'comments' class CalendarExtendedProperty(gdata.data.ExtendedProperty): """Defines a value for the realm attribute that is used only in the calendar API""" _qname = gdata.data.GD_TEMPLATE % 'extendedProperty' class CalendarWhere(gdata.data.Where): """Extends the base Where class with Calendar extensions""" _qname = gdata.data.GD_TEMPLATE % 'where' class ColorProperty(atom.core.XmlElement): """Describes the color of a calendar""" _qname = GCAL_TEMPLATE % 'color' value = 'value' class GuestsCanInviteOthersProperty(atom.core.XmlElement): """Whether guests can invite others to the event""" _qname = GCAL_TEMPLATE % 'guestsCanInviteOthers' value = 'value' class GuestsCanModifyProperty(atom.core.XmlElement): """Whether guests can modify event""" _qname = GCAL_TEMPLATE % 'guestsCanModify' value = 'value' class GuestsCanSeeGuestsProperty(atom.core.XmlElement): """Whether guests can see other attendees""" _qname = GCAL_TEMPLATE % 'guestsCanSeeGuests' value = 'value' class HiddenProperty(atom.core.XmlElement): """Describes whether a calendar is hidden""" _qname = GCAL_TEMPLATE % 'hidden' value = 'value' class IcalUIDProperty(atom.core.XmlElement): """Describes the UID in the ical export of the event""" _qname = GCAL_TEMPLATE % 'uid' value = 'value' class OverrideNameProperty(atom.core.XmlElement): """Describes the override name property of a calendar""" _qname = GCAL_TEMPLATE % 'overridename' value = 'value' class PrivateCopyProperty(atom.core.XmlElement): """Indicates whether this is a private copy of the event, changes to which should not be sent to other calendars""" _qname = GCAL_TEMPLATE % 'privateCopy' value = 'value' class QuickAddProperty(atom.core.XmlElement): """Describes whether gd:content is for quick-add processing""" _qname = GCAL_TEMPLATE % 'quickadd' value = 'value' class ResourceProperty(atom.core.XmlElement): """Describes whether gd:who is a resource such as a conference room""" _qname = GCAL_TEMPLATE % 'resource' value = 'value' id = 'id' class EventWho(gdata.data.Who): """Extends the base Who class with Calendar extensions""" _qname = gdata.data.GD_TEMPLATE % 'who' resource = ResourceProperty class SelectedProperty(atom.core.XmlElement): """Describes whether a calendar is selected""" _qname = GCAL_TEMPLATE % 'selected' value = 'value' class SendAclNotificationsProperty(atom.core.XmlElement): """Describes whether to send ACL notifications to grantees""" _qname = GCAL_TEMPLATE % 'sendAclNotifications' value = 'value' class CalendarAclEntry(gdata.acl.data.AclEntry): """Describes an entry in a feed of a Calendar access control list (ACL)""" send_acl_notifications = SendAclNotificationsProperty class CalendarAclFeed(gdata.data.GDFeed): """Describes a Calendar access contorl list (ACL) feed""" entry = [CalendarAclEntry] class SendEventNotificationsProperty(atom.core.XmlElement): """Describes whether to send event notifications to other participants of the event""" _qname = GCAL_TEMPLATE % 'sendEventNotifications' value = 'value' class SequenceNumberProperty(atom.core.XmlElement): """Describes sequence number of an event""" _qname = GCAL_TEMPLATE % 'sequence' value = 'value' class CalendarRecurrenceExceptionEntry(gdata.data.GDEntry): """Describes an entry used by a Calendar recurrence exception entry link""" uid = IcalUIDProperty sequence = SequenceNumberProperty class CalendarRecurrenceException(gdata.data.RecurrenceException): """Describes an exception to a recurring Calendar event""" _qname = gdata.data.GD_TEMPLATE % 'recurrenceException' class SettingsProperty(atom.core.XmlElement): """User preference name-value pair""" _qname = GCAL_TEMPLATE % 'settingsProperty' name = 'name' value = 'value' class SettingsEntry(gdata.data.GDEntry): """Describes a Calendar Settings property entry""" settings_property = SettingsProperty class CalendarSettingsFeed(gdata.data.GDFeed): """Personal settings for Calendar application""" entry = [SettingsEntry] class SuppressReplyNotificationsProperty(atom.core.XmlElement): """Lists notification methods to be suppressed for this reply""" _qname = GCAL_TEMPLATE % 'suppressReplyNotifications' methods = 'methods' class SyncEventProperty(atom.core.XmlElement): """Describes whether this is a sync scenario where the Ical UID and Sequence number are honored during inserts and updates""" _qname = GCAL_TEMPLATE % 'syncEvent' value = 'value' class When(gdata.data.When): """Extends the gd:when element to add reminders""" reminder = [gdata.data.Reminder] class CalendarEventEntry(gdata.data.BatchEntry): """Describes a Calendar event entry""" quick_add = QuickAddProperty send_event_notifications = SendEventNotificationsProperty sync_event = SyncEventProperty anyone_can_add_self = AnyoneCanAddSelfProperty extended_property = [CalendarExtendedProperty] sequence = SequenceNumberProperty guests_can_invite_others = GuestsCanInviteOthersProperty guests_can_modify = GuestsCanModifyProperty guests_can_see_guests = GuestsCanSeeGuestsProperty georss_where = gdata.geo.data.GeoRssWhere private_copy = PrivateCopyProperty suppress_reply_notifications = SuppressReplyNotificationsProperty uid = IcalUIDProperty where = [gdata.data.Where] when = [When] who = [gdata.data.Who] transparency = gdata.data.Transparency comments = gdata.data.Comments event_status = gdata.data.EventStatus visibility = gdata.data.Visibility recurrence = gdata.data.Recurrence recurrence_exception = [gdata.data.RecurrenceException] original_event = gdata.data.OriginalEvent reminder = [gdata.data.Reminder] class TimeZoneProperty(atom.core.XmlElement): """Describes the time zone of a calendar""" _qname = GCAL_TEMPLATE % 'timezone' value = 'value' class TimesCleanedProperty(atom.core.XmlElement): """Describes how many times calendar was cleaned via Manage Calendars""" _qname = GCAL_TEMPLATE % 'timesCleaned' value = 'value' class CalendarEntry(gdata.data.GDEntry): """Describes a Calendar entry in the feed of a user's calendars""" timezone = TimeZoneProperty overridename = OverrideNameProperty hidden = HiddenProperty selected = SelectedProperty times_cleaned = TimesCleanedProperty color = ColorProperty where = [CalendarWhere] accesslevel = AccessLevelProperty class CalendarEventFeed(gdata.data.BatchFeed): """Describes a Calendar event feed""" allow_g_sync2 = AllowGSync2Property timezone = TimeZoneProperty entry = [CalendarEventEntry] times_cleaned = TimesCleanedProperty allow_g_sync = AllowGSyncProperty class CalendarFeed(gdata.data.GDFeed): """Describes a feed of Calendars""" entry = [CalendarEntry] class WebContentGadgetPref(atom.core.XmlElement): """Describes a single web content gadget preference""" _qname = GCAL_TEMPLATE % 'webContentGadgetPref' name = 'name' value = 'value' class WebContent(atom.core.XmlElement): """Describes a "web content" extension""" _qname = GCAL_TEMPLATE % 'webContent' height = 'height' width = 'width' web_content_gadget_pref = [WebContentGadgetPref] url = 'url' display = 'display' class WebContentLink(atom.data.Link): """Describes a "web content" link""" def __init__(self, title=None, href=None, link_type=None, web_content=None): atom.data.Link.__init__(self, rel=WEB_CONTENT_LINK_REL, title=title, href=href, link_type=link_type) web_content = WebContent gdata-2.0.18/src/gdata/calendar/__init__.py0000750036466300116100000011432612156622363020500 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright (C) 2006 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. """Contains extensions to ElementWrapper objects used with Google Calendar.""" __author__ = 'api.vli (Vivian Li), api.rboyd (Ryan Boyd)' try: from xml.etree import cElementTree as ElementTree except ImportError: try: import cElementTree as ElementTree except ImportError: try: from xml.etree import ElementTree except ImportError: from elementtree import ElementTree import atom import gdata # XML namespaces which are often used in Google Calendar entities. GCAL_NAMESPACE = 'http://schemas.google.com/gCal/2005' GCAL_TEMPLATE = '{http://schemas.google.com/gCal/2005}%s' WEB_CONTENT_LINK_REL = '%s/%s' % (GCAL_NAMESPACE, 'webContent') GACL_NAMESPACE = gdata.GACL_NAMESPACE GACL_TEMPLATE = gdata.GACL_TEMPLATE class ValueAttributeContainer(atom.AtomBase): """A parent class for all Calendar classes which have a value attribute. Children include Color, AccessLevel, Hidden """ _children = atom.AtomBase._children.copy() _attributes = atom.AtomBase._attributes.copy() _attributes['value'] = 'value' def __init__(self, value=None, extension_elements=None, extension_attributes=None, text=None): self.value = value self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} class Color(ValueAttributeContainer): """The Google Calendar color element""" _tag = 'color' _namespace = GCAL_NAMESPACE _children = ValueAttributeContainer._children.copy() _attributes = ValueAttributeContainer._attributes.copy() class AccessLevel(ValueAttributeContainer): """The Google Calendar accesslevel element""" _tag = 'accesslevel' _namespace = GCAL_NAMESPACE _children = ValueAttributeContainer._children.copy() _attributes = ValueAttributeContainer._attributes.copy() class Hidden(ValueAttributeContainer): """The Google Calendar hidden element""" _tag = 'hidden' _namespace = GCAL_NAMESPACE _children = ValueAttributeContainer._children.copy() _attributes = ValueAttributeContainer._attributes.copy() class Selected(ValueAttributeContainer): """The Google Calendar selected element""" _tag = 'selected' _namespace = GCAL_NAMESPACE _children = ValueAttributeContainer._children.copy() _attributes = ValueAttributeContainer._attributes.copy() class Timezone(ValueAttributeContainer): """The Google Calendar timezone element""" _tag = 'timezone' _namespace = GCAL_NAMESPACE _children = ValueAttributeContainer._children.copy() _attributes = ValueAttributeContainer._attributes.copy() class Where(atom.AtomBase): """The Google Calendar Where element""" _tag = 'where' _namespace = gdata.GDATA_NAMESPACE _children = atom.AtomBase._children.copy() _attributes = atom.AtomBase._attributes.copy() _attributes['valueString'] = 'value_string' def __init__(self, value_string=None, extension_elements=None, extension_attributes=None, text=None): self.value_string = value_string self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} class CalendarListEntry(gdata.GDataEntry, gdata.LinkFinder): """A Google Calendar meta Entry flavor of an Atom Entry """ _tag = gdata.GDataEntry._tag _namespace = gdata.GDataEntry._namespace _children = gdata.GDataEntry._children.copy() _attributes = gdata.GDataEntry._attributes.copy() _children['{%s}color' % GCAL_NAMESPACE] = ('color', Color) _children['{%s}accesslevel' % GCAL_NAMESPACE] = ('access_level', AccessLevel) _children['{%s}hidden' % GCAL_NAMESPACE] = ('hidden', Hidden) _children['{%s}selected' % GCAL_NAMESPACE] = ('selected', Selected) _children['{%s}timezone' % GCAL_NAMESPACE] = ('timezone', Timezone) _children['{%s}where' % gdata.GDATA_NAMESPACE] = ('where', Where) def __init__(self, author=None, category=None, content=None, atom_id=None, link=None, published=None, title=None, updated=None, color=None, access_level=None, hidden=None, timezone=None, selected=None, where=None, extension_elements=None, extension_attributes=None, text=None): gdata.GDataEntry.__init__(self, author=author, category=category, content=content, atom_id=atom_id, link=link, published=published, title=title, updated=updated, text=None) self.color = color self.access_level = access_level self.hidden = hidden self.selected = selected self.timezone = timezone self.where = where class CalendarListFeed(gdata.GDataFeed, gdata.LinkFinder): """A Google Calendar meta feed flavor of an Atom Feed""" _tag = gdata.GDataFeed._tag _namespace = gdata.GDataFeed._namespace _children = gdata.GDataFeed._children.copy() _attributes = gdata.GDataFeed._attributes.copy() _children['{%s}entry' % atom.ATOM_NAMESPACE] = ('entry', [CalendarListEntry]) class Scope(atom.AtomBase): """The Google ACL scope element""" _tag = 'scope' _namespace = GACL_NAMESPACE _children = atom.AtomBase._children.copy() _attributes = atom.AtomBase._attributes.copy() _attributes['value'] = 'value' _attributes['type'] = 'type' def __init__(self, extension_elements=None, value=None, scope_type=None, extension_attributes=None, text=None): self.value = value self.type = scope_type self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} class Role(ValueAttributeContainer): """The Google Calendar timezone element""" _tag = 'role' _namespace = GACL_NAMESPACE _children = ValueAttributeContainer._children.copy() _attributes = ValueAttributeContainer._attributes.copy() class CalendarAclEntry(gdata.GDataEntry, gdata.LinkFinder): """A Google Calendar ACL Entry flavor of an Atom Entry """ _tag = gdata.GDataEntry._tag _namespace = gdata.GDataEntry._namespace _children = gdata.GDataEntry._children.copy() _attributes = gdata.GDataEntry._attributes.copy() _children['{%s}scope' % GACL_NAMESPACE] = ('scope', Scope) _children['{%s}role' % GACL_NAMESPACE] = ('role', Role) def __init__(self, author=None, category=None, content=None, atom_id=None, link=None, published=None, title=None, updated=None, scope=None, role=None, extension_elements=None, extension_attributes=None, text=None): gdata.GDataEntry.__init__(self, author=author, category=category, content=content, atom_id=atom_id, link=link, published=published, title=title, updated=updated, text=None) self.scope = scope self.role = role class CalendarAclFeed(gdata.GDataFeed, gdata.LinkFinder): """A Google Calendar ACL feed flavor of an Atom Feed""" _tag = gdata.GDataFeed._tag _namespace = gdata.GDataFeed._namespace _children = gdata.GDataFeed._children.copy() _attributes = gdata.GDataFeed._attributes.copy() _children['{%s}entry' % atom.ATOM_NAMESPACE] = ('entry', [CalendarAclEntry]) class CalendarEventCommentEntry(gdata.GDataEntry, gdata.LinkFinder): """A Google Calendar event comments entry flavor of an Atom Entry""" _tag = gdata.GDataEntry._tag _namespace = gdata.GDataEntry._namespace _children = gdata.GDataEntry._children.copy() _attributes = gdata.GDataEntry._attributes.copy() class CalendarEventCommentFeed(gdata.GDataFeed, gdata.LinkFinder): """A Google Calendar event comments feed flavor of an Atom Feed""" _tag = gdata.GDataFeed._tag _namespace = gdata.GDataFeed._namespace _children = gdata.GDataFeed._children.copy() _attributes = gdata.GDataFeed._attributes.copy() _children['{%s}entry' % atom.ATOM_NAMESPACE] = ('entry', [CalendarEventCommentEntry]) class ExtendedProperty(gdata.ExtendedProperty): """A transparent subclass of gdata.ExtendedProperty added to this module for backwards compatibility.""" class Reminder(atom.AtomBase): """The Google Calendar reminder element""" _tag = 'reminder' _namespace = gdata.GDATA_NAMESPACE _children = atom.AtomBase._children.copy() _attributes = atom.AtomBase._attributes.copy() _attributes['absoluteTime'] = 'absolute_time' _attributes['days'] = 'days' _attributes['hours'] = 'hours' _attributes['minutes'] = 'minutes' _attributes['method'] = 'method' def __init__(self, absolute_time=None, days=None, hours=None, minutes=None, method=None, extension_elements=None, extension_attributes=None, text=None): self.absolute_time = absolute_time if days is not None: self.days = str(days) else: self.days = None if hours is not None: self.hours = str(hours) else: self.hours = None if minutes is not None: self.minutes = str(minutes) else: self.minutes = None self.method = method self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} class When(atom.AtomBase): """The Google Calendar When element""" _tag = 'when' _namespace = gdata.GDATA_NAMESPACE _children = atom.AtomBase._children.copy() _attributes = atom.AtomBase._attributes.copy() _children['{%s}reminder' % gdata.GDATA_NAMESPACE] = ('reminder', [Reminder]) _attributes['startTime'] = 'start_time' _attributes['endTime'] = 'end_time' def __init__(self, start_time=None, end_time=None, reminder=None, extension_elements=None, extension_attributes=None, text=None): self.start_time = start_time self.end_time = end_time self.reminder = reminder or [] self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} class Recurrence(atom.AtomBase): """The Google Calendar Recurrence element""" _tag = 'recurrence' _namespace = gdata.GDATA_NAMESPACE _children = atom.AtomBase._children.copy() _attributes = atom.AtomBase._attributes.copy() class UriEnumElement(atom.AtomBase): _children = atom.AtomBase._children.copy() _attributes = atom.AtomBase._attributes.copy() def __init__(self, tag, enum_map, attrib_name='value', extension_elements=None, extension_attributes=None, text=None): self.tag=tag self.enum_map=enum_map self.attrib_name=attrib_name self.value=None self.text=text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} def findKey(self, value): res=[item[0] for item in self.enum_map.items() if item[1] == value] if res is None or len(res) == 0: return None return res[0] def _ConvertElementAttributeToMember(self, attribute, value): # Special logic to use the enum_map to set the value of the object's value member. if attribute == self.attrib_name and value != '': self.value = self.enum_map[value] return # Find the attribute in this class's list of attributes. if self.__class__._attributes.has_key(attribute): # Find the member of this class which corresponds to the XML attribute # (lookup in current_class._attributes) and set this member to the # desired value (using self.__dict__). setattr(self, self.__class__._attributes[attribute], value) else: # The current class doesn't map this attribute, so try to parent class. atom.ExtensionContainer._ConvertElementAttributeToMember(self, attribute, value) def _AddMembersToElementTree(self, tree): # Convert the members of this class which are XML child nodes. # This uses the class's _children dictionary to find the members which # should become XML child nodes. member_node_names = [values[0] for tag, values in self.__class__._children.iteritems()] for member_name in member_node_names: member = getattr(self, member_name) if member is None: pass elif isinstance(member, list): for instance in member: instance._BecomeChildElement(tree) else: member._BecomeChildElement(tree) # Special logic to set the desired XML attribute. key = self.findKey(self.value) if key is not None: tree.attrib[self.attrib_name]=key # Convert the members of this class which are XML attributes. for xml_attribute, member_name in self.__class__._attributes.iteritems(): member = getattr(self, member_name) if member is not None: tree.attrib[xml_attribute] = member # Lastly, call the parent's _AddMembersToElementTree to get any # extension elements. atom.ExtensionContainer._AddMembersToElementTree(self, tree) class AttendeeStatus(UriEnumElement): """The Google Calendar attendeeStatus element""" _tag = 'attendeeStatus' _namespace = gdata.GDATA_NAMESPACE _children = UriEnumElement._children.copy() _attributes = UriEnumElement._attributes.copy() attendee_enum = { 'http://schemas.google.com/g/2005#event.accepted' : 'ACCEPTED', 'http://schemas.google.com/g/2005#event.declined' : 'DECLINED', 'http://schemas.google.com/g/2005#event.invited' : 'INVITED', 'http://schemas.google.com/g/2005#event.tentative' : 'TENTATIVE'} def __init__(self, extension_elements=None, extension_attributes=None, text=None): UriEnumElement.__init__(self, 'attendeeStatus', AttendeeStatus.attendee_enum, extension_elements=extension_elements, extension_attributes=extension_attributes, text=text) class AttendeeType(UriEnumElement): """The Google Calendar attendeeType element""" _tag = 'attendeeType' _namespace = gdata.GDATA_NAMESPACE _children = UriEnumElement._children.copy() _attributes = UriEnumElement._attributes.copy() attendee_type_enum = { 'http://schemas.google.com/g/2005#event.optional' : 'OPTIONAL', 'http://schemas.google.com/g/2005#event.required' : 'REQUIRED' } def __init__(self, extension_elements=None, extension_attributes=None, text=None): UriEnumElement.__init__(self, 'attendeeType', AttendeeType.attendee_type_enum, extension_elements=extension_elements, extension_attributes=extension_attributes,text=text) class Visibility(UriEnumElement): """The Google Calendar Visibility element""" _tag = 'visibility' _namespace = gdata.GDATA_NAMESPACE _children = UriEnumElement._children.copy() _attributes = UriEnumElement._attributes.copy() visibility_enum = { 'http://schemas.google.com/g/2005#event.confidential' : 'CONFIDENTIAL', 'http://schemas.google.com/g/2005#event.default' : 'DEFAULT', 'http://schemas.google.com/g/2005#event.private' : 'PRIVATE', 'http://schemas.google.com/g/2005#event.public' : 'PUBLIC' } def __init__(self, extension_elements=None, extension_attributes=None, text=None): UriEnumElement.__init__(self, 'visibility', Visibility.visibility_enum, extension_elements=extension_elements, extension_attributes=extension_attributes, text=text) class Transparency(UriEnumElement): """The Google Calendar Transparency element""" _tag = 'transparency' _namespace = gdata.GDATA_NAMESPACE _children = UriEnumElement._children.copy() _attributes = UriEnumElement._attributes.copy() transparency_enum = { 'http://schemas.google.com/g/2005#event.opaque' : 'OPAQUE', 'http://schemas.google.com/g/2005#event.transparent' : 'TRANSPARENT' } def __init__(self, extension_elements=None, extension_attributes=None, text=None): UriEnumElement.__init__(self, tag='transparency', enum_map=Transparency.transparency_enum, extension_elements=extension_elements, extension_attributes=extension_attributes, text=text) class Comments(atom.AtomBase): """The Google Calendar comments element""" _tag = 'comments' _namespace = gdata.GDATA_NAMESPACE _children = atom.AtomBase._children.copy() _attributes = atom.AtomBase._attributes.copy() _children['{%s}feedLink' % gdata.GDATA_NAMESPACE] = ('feed_link', gdata.FeedLink) _attributes['rel'] = 'rel' def __init__(self, rel=None, feed_link=None, extension_elements=None, extension_attributes=None, text=None): self.rel = rel self.feed_link = feed_link self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} class EventStatus(UriEnumElement): """The Google Calendar eventStatus element""" _tag = 'eventStatus' _namespace = gdata.GDATA_NAMESPACE _children = UriEnumElement._children.copy() _attributes = UriEnumElement._attributes.copy() status_enum = { 'http://schemas.google.com/g/2005#event.canceled' : 'CANCELED', 'http://schemas.google.com/g/2005#event.confirmed' : 'CONFIRMED', 'http://schemas.google.com/g/2005#event.tentative' : 'TENTATIVE'} def __init__(self, extension_elements=None, extension_attributes=None, text=None): UriEnumElement.__init__(self, tag='eventStatus', enum_map=EventStatus.status_enum, extension_elements=extension_elements, extension_attributes=extension_attributes, text=text) class Who(UriEnumElement): """The Google Calendar Who element""" _tag = 'who' _namespace = gdata.GDATA_NAMESPACE _children = UriEnumElement._children.copy() _attributes = UriEnumElement._attributes.copy() _children['{%s}attendeeStatus' % gdata.GDATA_NAMESPACE] = ( 'attendee_status', AttendeeStatus) _children['{%s}attendeeType' % gdata.GDATA_NAMESPACE] = ('attendee_type', AttendeeType) _attributes['valueString'] = 'name' _attributes['email'] = 'email' relEnum = { 'http://schemas.google.com/g/2005#event.attendee' : 'ATTENDEE', 'http://schemas.google.com/g/2005#event.organizer' : 'ORGANIZER', 'http://schemas.google.com/g/2005#event.performer' : 'PERFORMER', 'http://schemas.google.com/g/2005#event.speaker' : 'SPEAKER', 'http://schemas.google.com/g/2005#message.bcc' : 'BCC', 'http://schemas.google.com/g/2005#message.cc' : 'CC', 'http://schemas.google.com/g/2005#message.from' : 'FROM', 'http://schemas.google.com/g/2005#message.reply-to' : 'REPLY_TO', 'http://schemas.google.com/g/2005#message.to' : 'TO' } def __init__(self, name=None, email=None, attendee_status=None, attendee_type=None, rel=None, extension_elements=None, extension_attributes=None, text=None): UriEnumElement.__init__(self, 'who', Who.relEnum, attrib_name='rel', extension_elements=extension_elements, extension_attributes=extension_attributes, text=text) self.name = name self.email = email self.attendee_status = attendee_status self.attendee_type = attendee_type self.rel = rel class OriginalEvent(atom.AtomBase): """The Google Calendar OriginalEvent element""" _tag = 'originalEvent' _namespace = gdata.GDATA_NAMESPACE _children = atom.AtomBase._children.copy() _attributes = atom.AtomBase._attributes.copy() # TODO: The when tag used to map to a EntryLink, make sure it should really be a When. _children['{%s}when' % gdata.GDATA_NAMESPACE] = ('when', When) _attributes['id'] = 'id' _attributes['href'] = 'href' def __init__(self, id=None, href=None, when=None, extension_elements=None, extension_attributes=None, text=None): self.id = id self.href = href self.when = when self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} def GetCalendarEventEntryClass(): return CalendarEventEntry # This class is not completely defined here, because of a circular reference # in which CalendarEventEntryLink and CalendarEventEntry refer to one another. class CalendarEventEntryLink(gdata.EntryLink): """An entryLink which contains a calendar event entry Within an event's recurranceExceptions, an entry link points to a calendar event entry. This class exists to capture the calendar specific extensions in the entry. """ _tag = 'entryLink' _namespace = gdata.GDATA_NAMESPACE _children = gdata.EntryLink._children.copy() _attributes = gdata.EntryLink._attributes.copy() # The CalendarEventEntryLink should like CalendarEventEntry as a child but # that class hasn't been defined yet, so we will wait until after defining # CalendarEventEntry to list it in _children. class RecurrenceException(atom.AtomBase): """The Google Calendar RecurrenceException element""" _tag = 'recurrenceException' _namespace = gdata.GDATA_NAMESPACE _children = atom.AtomBase._children.copy() _attributes = atom.AtomBase._attributes.copy() _children['{%s}entryLink' % gdata.GDATA_NAMESPACE] = ('entry_link', CalendarEventEntryLink) _children['{%s}originalEvent' % gdata.GDATA_NAMESPACE] = ('original_event', OriginalEvent) _attributes['specialized'] = 'specialized' def __init__(self, specialized=None, entry_link=None, original_event=None, extension_elements=None, extension_attributes=None, text=None): self.specialized = specialized self.entry_link = entry_link self.original_event = original_event self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} class SendEventNotifications(atom.AtomBase): """The Google Calendar sendEventNotifications element""" _tag = 'sendEventNotifications' _namespace = GCAL_NAMESPACE _children = atom.AtomBase._children.copy() _attributes = atom.AtomBase._attributes.copy() _attributes['value'] = 'value' def __init__(self, extension_elements=None, value=None, extension_attributes=None, text=None): self.value = value self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} class QuickAdd(atom.AtomBase): """The Google Calendar quickadd element""" _tag = 'quickadd' _namespace = GCAL_NAMESPACE _children = atom.AtomBase._children.copy() _attributes = atom.AtomBase._attributes.copy() _attributes['value'] = 'value' def __init__(self, extension_elements=None, value=None, extension_attributes=None, text=None): self.value = value self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} def _TransferToElementTree(self, element_tree): if self.value: element_tree.attrib['value'] = self.value element_tree.tag = GCAL_TEMPLATE % 'quickadd' atom.AtomBase._TransferToElementTree(self, element_tree) return element_tree def _TakeAttributeFromElementTree(self, attribute, element_tree): if attribute == 'value': self.value = element_tree.attrib[attribute] del element_tree.attrib[attribute] else: atom.AtomBase._TakeAttributeFromElementTree(self, attribute, element_tree) class SyncEvent(atom.AtomBase): _tag = 'syncEvent' _namespace = GCAL_NAMESPACE _children = atom.AtomBase._children.copy() _attributes = atom.AtomBase._attributes.copy() _attributes['value'] = 'value' def __init__(self, value='false', extension_elements=None, extension_attributes=None, text=None): self.value = value self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} class UID(atom.AtomBase): _tag = 'uid' _namespace = GCAL_NAMESPACE _children = atom.AtomBase._children.copy() _attributes = atom.AtomBase._attributes.copy() _attributes['value'] = 'value' def __init__(self, value=None, extension_elements=None, extension_attributes=None, text=None): self.value = value self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} class Sequence(atom.AtomBase): _tag = 'sequence' _namespace = GCAL_NAMESPACE _children = atom.AtomBase._children.copy() _attributes = atom.AtomBase._attributes.copy() _attributes['value'] = 'value' def __init__(self, value=None, extension_elements=None, extension_attributes=None, text=None): self.value = value self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} class WebContentGadgetPref(atom.AtomBase): _tag = 'webContentGadgetPref' _namespace = GCAL_NAMESPACE _children = atom.AtomBase._children.copy() _attributes = atom.AtomBase._attributes.copy() _attributes['name'] = 'name' _attributes['value'] = 'value' """The Google Calendar Web Content Gadget Preferences element""" def __init__(self, name=None, value=None, extension_elements=None, extension_attributes=None, text=None): self.name = name self.value = value self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} class WebContent(atom.AtomBase): _tag = 'webContent' _namespace = GCAL_NAMESPACE _children = atom.AtomBase._children.copy() _attributes = atom.AtomBase._attributes.copy() _children['{%s}webContentGadgetPref' % GCAL_NAMESPACE] = ('gadget_pref', [WebContentGadgetPref]) _attributes['url'] = 'url' _attributes['width'] = 'width' _attributes['height'] = 'height' def __init__(self, url=None, width=None, height=None, text=None, gadget_pref=None, extension_elements=None, extension_attributes=None): self.url = url self.width = width self.height = height self.text = text self.gadget_pref = gadget_pref or [] self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} class WebContentLink(atom.Link): _tag = 'link' _namespace = atom.ATOM_NAMESPACE _children = atom.Link._children.copy() _attributes = atom.Link._attributes.copy() _children['{%s}webContent' % GCAL_NAMESPACE] = ('web_content', WebContent) def __init__(self, title=None, href=None, link_type=None, web_content=None): atom.Link.__init__(self, rel=WEB_CONTENT_LINK_REL, title=title, href=href, link_type=link_type) self.web_content = web_content class GuestsCanInviteOthers(atom.AtomBase): """Indicates whether event attendees may invite others to the event. This element may only be changed by the organizer of the event. If not included as part of the event entry, this element will default to true during a POST request, and will inherit its previous value during a PUT request. """ _tag = 'guestsCanInviteOthers' _namespace = GCAL_NAMESPACE _attributes = atom.AtomBase._attributes.copy() _attributes['value'] = 'value' def __init__(self, value='true', *args, **kwargs): atom.AtomBase.__init__(self, *args, **kwargs) self.value = value class GuestsCanSeeGuests(atom.AtomBase): """Indicates whether attendees can see other people invited to the event. The organizer always sees all attendees. Guests always see themselves. This property affects what attendees see in the event's guest list via both the Calendar UI and API feeds. This element may only be changed by the organizer of the event. If not included as part of the event entry, this element will default to true during a POST request, and will inherit its previous value during a PUT request. """ _tag = 'guestsCanSeeGuests' _namespace = GCAL_NAMESPACE _attributes = atom.AtomBase._attributes.copy() _attributes['value'] = 'value' def __init__(self, value='true', *args, **kwargs): atom.AtomBase.__init__(self, *args, **kwargs) self.value = value class GuestsCanModify(atom.AtomBase): """Indicates whether event attendees may modify the original event. If yes, changes are visible to organizer and other attendees. Otherwise, any changes made by attendees will be restricted to that attendee's calendar. This element may only be changed by the organizer of the event, and may be set to 'true' only if both gCal:guestsCanInviteOthers and gCal:guestsCanSeeGuests are set to true in the same PUT/POST request. Otherwise, request fails with HTTP error code 400 (Bad Request). If not included as part of the event entry, this element will default to false during a POST request, and will inherit its previous value during a PUT request.""" _tag = 'guestsCanModify' _namespace = GCAL_NAMESPACE _attributes = atom.AtomBase._attributes.copy() _attributes['value'] = 'value' def __init__(self, value='false', *args, **kwargs): atom.AtomBase.__init__(self, *args, **kwargs) self.value = value class CalendarEventEntry(gdata.BatchEntry): """A Google Calendar flavor of an Atom Entry """ _tag = gdata.BatchEntry._tag _namespace = gdata.BatchEntry._namespace _children = gdata.BatchEntry._children.copy() _attributes = gdata.BatchEntry._attributes.copy() # This class also contains WebContentLinks but converting those members # is handled in a special version of _ConvertElementTreeToMember. _children['{%s}where' % gdata.GDATA_NAMESPACE] = ('where', [Where]) _children['{%s}when' % gdata.GDATA_NAMESPACE] = ('when', [When]) _children['{%s}who' % gdata.GDATA_NAMESPACE] = ('who', [Who]) _children['{%s}extendedProperty' % gdata.GDATA_NAMESPACE] = ( 'extended_property', [ExtendedProperty]) _children['{%s}visibility' % gdata.GDATA_NAMESPACE] = ('visibility', Visibility) _children['{%s}transparency' % gdata.GDATA_NAMESPACE] = ('transparency', Transparency) _children['{%s}eventStatus' % gdata.GDATA_NAMESPACE] = ('event_status', EventStatus) _children['{%s}recurrence' % gdata.GDATA_NAMESPACE] = ('recurrence', Recurrence) _children['{%s}recurrenceException' % gdata.GDATA_NAMESPACE] = ( 'recurrence_exception', [RecurrenceException]) _children['{%s}sendEventNotifications' % GCAL_NAMESPACE] = ( 'send_event_notifications', SendEventNotifications) _children['{%s}quickadd' % GCAL_NAMESPACE] = ('quick_add', QuickAdd) _children['{%s}comments' % gdata.GDATA_NAMESPACE] = ('comments', Comments) _children['{%s}originalEvent' % gdata.GDATA_NAMESPACE] = ('original_event', OriginalEvent) _children['{%s}sequence' % GCAL_NAMESPACE] = ('sequence', Sequence) _children['{%s}reminder' % gdata.GDATA_NAMESPACE] = ('reminder', [Reminder]) _children['{%s}syncEvent' % GCAL_NAMESPACE] = ('sync_event', SyncEvent) _children['{%s}uid' % GCAL_NAMESPACE] = ('uid', UID) _children['{%s}guestsCanInviteOthers' % GCAL_NAMESPACE] = ( 'guests_can_invite_others', GuestsCanInviteOthers) _children['{%s}guestsCanModify' % GCAL_NAMESPACE] = ( 'guests_can_modify', GuestsCanModify) _children['{%s}guestsCanSeeGuests' % GCAL_NAMESPACE] = ( 'guests_can_see_guests', GuestsCanSeeGuests) def __init__(self, author=None, category=None, content=None, atom_id=None, link=None, published=None, title=None, updated=None, transparency=None, comments=None, event_status=None, send_event_notifications=None, visibility=None, recurrence=None, recurrence_exception=None, where=None, when=None, who=None, quick_add=None, extended_property=None, original_event=None, batch_operation=None, batch_id=None, batch_status=None, sequence=None, reminder=None, sync_event=None, uid=None, guests_can_invite_others=None, guests_can_modify=None, guests_can_see_guests=None, extension_elements=None, extension_attributes=None, text=None): gdata.BatchEntry.__init__(self, author=author, category=category, content=content, atom_id=atom_id, link=link, published=published, batch_operation=batch_operation, batch_id=batch_id, batch_status=batch_status, title=title, updated=updated) self.transparency = transparency self.comments = comments self.event_status = event_status self.send_event_notifications = send_event_notifications self.visibility = visibility self.recurrence = recurrence self.recurrence_exception = recurrence_exception or [] self.where = where or [] self.when = when or [] self.who = who or [] self.quick_add = quick_add self.extended_property = extended_property or [] self.original_event = original_event self.sequence = sequence self.reminder = reminder or [] self.sync_event = sync_event self.uid = uid self.text = text self.guests_can_invite_others = guests_can_invite_others self.guests_can_modify = guests_can_modify self.guests_can_see_guests = guests_can_see_guests self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} # We needed to add special logic to _ConvertElementTreeToMember because we # want to make links with a rel of WEB_CONTENT_LINK_REL into a # WebContentLink def _ConvertElementTreeToMember(self, child_tree): # Special logic to handle Web Content links if (child_tree.tag == '{%s}link' % atom.ATOM_NAMESPACE and child_tree.attrib['rel'] == WEB_CONTENT_LINK_REL): if self.link is None: self.link = [] self.link.append(atom._CreateClassFromElementTree(WebContentLink, child_tree)) return # Find the element's tag in this class's list of child members if self.__class__._children.has_key(child_tree.tag): member_name = self.__class__._children[child_tree.tag][0] member_class = self.__class__._children[child_tree.tag][1] # If the class member is supposed to contain a list, make sure the # matching member is set to a list, then append the new member # instance to the list. if isinstance(member_class, list): if getattr(self, member_name) is None: setattr(self, member_name, []) getattr(self, member_name).append(atom._CreateClassFromElementTree( member_class[0], child_tree)) else: setattr(self, member_name, atom._CreateClassFromElementTree(member_class, child_tree)) else: atom.ExtensionContainer._ConvertElementTreeToMember(self, child_tree) def GetWebContentLink(self): """Finds the first link with rel set to WEB_CONTENT_REL Returns: A gdata.calendar.WebContentLink or none if none of the links had rel equal to WEB_CONTENT_REL """ for a_link in self.link: if a_link.rel == WEB_CONTENT_LINK_REL: return a_link return None def CalendarEventEntryFromString(xml_string): return atom.CreateClassFromXMLString(CalendarEventEntry, xml_string) def CalendarEventCommentEntryFromString(xml_string): return atom.CreateClassFromXMLString(CalendarEventCommentEntry, xml_string) CalendarEventEntryLink._children = {'{%s}entry' % atom.ATOM_NAMESPACE: ('entry', CalendarEventEntry)} def CalendarEventEntryLinkFromString(xml_string): return atom.CreateClassFromXMLString(CalendarEventEntryLink, xml_string) class CalendarEventFeed(gdata.BatchFeed, gdata.LinkFinder): """A Google Calendar event feed flavor of an Atom Feed""" _tag = gdata.BatchFeed._tag _namespace = gdata.BatchFeed._namespace _children = gdata.BatchFeed._children.copy() _attributes = gdata.BatchFeed._attributes.copy() _children['{%s}entry' % atom.ATOM_NAMESPACE] = ('entry', [CalendarEventEntry]) _children['{%s}timezone' % GCAL_NAMESPACE] = ('timezone', Timezone) def __init__(self, author=None, category=None, contributor=None, generator=None, icon=None, atom_id=None, link=None, logo=None, rights=None, subtitle=None, title=None, updated=None, entry=None, total_results=None, start_index=None, items_per_page=None, interrupted=None, timezone=None, extension_elements=None, extension_attributes=None, text=None): gdata.BatchFeed.__init__(self, author=author, category=category, contributor=contributor, generator=generator, icon=icon, atom_id=atom_id, link=link, logo=logo, rights=rights, subtitle=subtitle, title=title, updated=updated, entry=entry, total_results=total_results, start_index=start_index, items_per_page=items_per_page, interrupted=interrupted, extension_elements=extension_elements, extension_attributes=extension_attributes, text=text) self.timezone = timezone def CalendarListEntryFromString(xml_string): return atom.CreateClassFromXMLString(CalendarListEntry, xml_string) def CalendarAclEntryFromString(xml_string): return atom.CreateClassFromXMLString(CalendarAclEntry, xml_string) def CalendarListFeedFromString(xml_string): return atom.CreateClassFromXMLString(CalendarListFeed, xml_string) def CalendarAclFeedFromString(xml_string): return atom.CreateClassFromXMLString(CalendarAclFeed, xml_string) def CalendarEventFeedFromString(xml_string): return atom.CreateClassFromXMLString(CalendarEventFeed, xml_string) def CalendarEventCommentFeedFromString(xml_string): return atom.CreateClassFromXMLString(CalendarEventCommentFeed, xml_string) gdata-2.0.18/src/gdata/calendar/client.py0000750036466300116100000006062412156622363020220 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright (C) 2011 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. """CalendarClient extends the GDataService to streamline Google Calendar operations. CalendarService: Provides methods to query feeds and manipulate items. Extends GDataService. DictionaryToParamList: Function which converts a dictionary into a list of URL arguments (represented as strings). This is a utility function used in CRUD operations. """ __author__ = 'alainv (Alain Vongsouvanh)' import urllib import gdata.client import gdata.calendar.data import atom.data import atom.http_core import gdata.gauth DEFAULT_BATCH_URL = ('https://www.google.com/calendar/feeds/default/private' '/full/batch') class CalendarClient(gdata.client.GDClient): """Client for the Google Calendar service.""" api_version = '2' auth_service = 'cl' server = "www.google.com" contact_list = "default" auth_scopes = gdata.gauth.AUTH_SCOPES['cl'] def __init__(self, domain=None, auth_token=None, **kwargs): """Constructs a new client for the Calendar API. Args: domain: string The Google Apps domain (if any). kwargs: The other parameters to pass to the gdata.client.GDClient constructor. """ gdata.client.GDClient.__init__(self, auth_token=auth_token, **kwargs) self.domain = domain def get_calendar_feed_uri(self, feed='', projection='full', scheme="https"): """Builds a feed URI. Args: projection: The projection to apply to the feed contents, for example 'full', 'base', 'base/12345', 'full/batch'. Default value: 'full'. scheme: The URL scheme such as 'http' or 'https', None to return a relative URI without hostname. Returns: A feed URI using the given scheme and projection. Example: '/calendar/feeds/default/owncalendars/full'. """ prefix = scheme and '%s://%s' % (scheme, self.server) or '' suffix = feed and '/%s/%s' % (feed, projection) or '' return '%s/calendar/feeds/default%s' % (prefix, suffix) GetCalendarFeedUri = get_calendar_feed_uri def get_calendar_event_feed_uri(self, calendar='default', visibility='private', projection='full', scheme="https"): """Builds a feed URI. Args: projection: The projection to apply to the feed contents, for example 'full', 'base', 'base/12345', 'full/batch'. Default value: 'full'. scheme: The URL scheme such as 'http' or 'https', None to return a relative URI without hostname. Returns: A feed URI using the given scheme and projection. Example: '/calendar/feeds/default/private/full'. """ prefix = scheme and '%s://%s' % (scheme, self.server) or '' return '%s/calendar/feeds/%s/%s/%s' % (prefix, calendar, visibility, projection) GetCalendarEventFeedUri = get_calendar_event_feed_uri def get_calendars_feed(self, uri, desired_class=gdata.calendar.data.CalendarFeed, auth_token=None, **kwargs): """Obtains a calendar feed. Args: uri: The uri of the calendar feed to request. desired_class: class descended from atom.core.XmlElement to which a successful response should be converted. If there is no converter function specified (desired_class=None) then the desired_class will be used in calling the atom.core.parse function. If neither the desired_class nor the converter is specified, an HTTP reponse object will be returned. Defaults to gdata.calendar.data.CalendarFeed. auth_token: An object which sets the Authorization HTTP header in its modify_request method. Recommended classes include gdata.gauth.ClientLoginToken and gdata.gauth.AuthSubToken among others. Represents the current user. Defaults to None and if None, this method will look for a value in the auth_token member of SpreadsheetsClient. """ return self.get_feed(uri, auth_token=auth_token, desired_class=desired_class, **kwargs) GetCalendarsFeed = get_calendars_feed def get_own_calendars_feed(self, desired_class=gdata.calendar.data.CalendarFeed, auth_token=None, **kwargs): """Obtains a feed containing the calendars owned by the current user. Args: desired_class: class descended from atom.core.XmlElement to which a successful response should be converted. If there is no converter function specified (desired_class=None) then the desired_class will be used in calling the atom.core.parse function. If neither the desired_class nor the converter is specified, an HTTP reponse object will be returned. Defaults to gdata.calendar.data.CalendarFeed. auth_token: An object which sets the Authorization HTTP header in its modify_request method. Recommended classes include gdata.gauth.ClientLoginToken and gdata.gauth.AuthSubToken among others. Represents the current user. Defaults to None and if None, this method will look for a value in the auth_token member of SpreadsheetsClient. """ return self.GetCalendarsFeed(uri=self.GetCalendarFeedUri(feed='owncalendars'), desired_class=desired_class, auth_token=auth_token, **kwargs) GetOwnCalendarsFeed = get_own_calendars_feed def get_all_calendars_feed(self, desired_class=gdata.calendar.data.CalendarFeed, auth_token=None, **kwargs): """Obtains a feed containing all the ccalendars the current user has access to. Args: desired_class: class descended from atom.core.XmlElement to which a successful response should be converted. If there is no converter function specified (desired_class=None) then the desired_class will be used in calling the atom.core.parse function. If neither the desired_class nor the converter is specified, an HTTP reponse object will be returned. Defaults to gdata.calendar.data.CalendarFeed. auth_token: An object which sets the Authorization HTTP header in its modify_request method. Recommended classes include gdata.gauth.ClientLoginToken and gdata.gauth.AuthSubToken among others. Represents the current user. Defaults to None and if None, this method will look for a value in the auth_token member of SpreadsheetsClient. """ return self.GetCalendarsFeed(uri=self.GetCalendarFeedUri(feed='allcalendars'), desired_class=desired_class, auth_token=auth_token, **kwargs) GetAllCalendarsFeed = get_all_calendars_feed def get_calendar_entry(self, uri, desired_class=gdata.calendar.data.CalendarEntry, auth_token=None, **kwargs): """Obtains a single calendar entry. Args: uri: The uri of the desired calendar entry. desired_class: class descended from atom.core.XmlElement to which a successful response should be converted. If there is no converter function specified (desired_class=None) then the desired_class will be used in calling the atom.core.parse function. If neither the desired_class nor the converter is specified, an HTTP reponse object will be returned. Defaults to gdata.calendar.data.CalendarEntry. auth_token: An object which sets the Authorization HTTP header in its modify_request method. Recommended classes include gdata.gauth.ClientLoginToken and gdata.gauth.AuthSubToken among others. Represents the current user. Defaults to None and if None, this method will look for a value in the auth_token member of SpreadsheetsClient. """ return self.get_entry(uri, auth_token=auth_token, desired_class=desired_class, **kwargs) GetCalendarEntry = get_calendar_entry def get_calendar_event_feed(self, uri=None, desired_class=gdata.calendar.data.CalendarEventFeed, auth_token=None, **kwargs): """Obtains a feed of events for the desired calendar. Args: uri: The uri of the desired calendar entry. Defaults to https://www.google.com/calendar/feeds/default/private/full. desired_class: class descended from atom.core.XmlElement to which a successful response should be converted. If there is no converter function specified (desired_class=None) then the desired_class will be used in calling the atom.core.parse function. If neither the desired_class nor the converter is specified, an HTTP reponse object will be returned. Defaults to gdata.calendar.data.CalendarEventFeed. auth_token: An object which sets the Authorization HTTP header in its modify_request method. Recommended classes include gdata.gauth.ClientLoginToken and gdata.gauth.AuthSubToken among others. Represents the current user. Defaults to None and if None, this method will look for a value in the auth_token member of SpreadsheetsClient. """ uri = uri or self.GetCalendarEventFeedUri() return self.get_feed(uri, auth_token=auth_token, desired_class=desired_class, **kwargs) GetCalendarEventFeed = get_calendar_event_feed def get_event_entry(self, uri, desired_class=gdata.calendar.data.CalendarEventEntry, auth_token=None, **kwargs): """Obtains a single event entry. Args: uri: The uri of the desired calendar event entry. desired_class: class descended from atom.core.XmlElement to which a successful response should be converted. If there is no converter function specified (desired_class=None) then the desired_class will be used in calling the atom.core.parse function. If neither the desired_class nor the converter is specified, an HTTP reponse object will be returned. Defaults to gdata.calendar.data.CalendarEventEntry. auth_token: An object which sets the Authorization HTTP header in its modify_request method. Recommended classes include gdata.gauth.ClientLoginToken and gdata.gauth.AuthSubToken among others. Represents the current user. Defaults to None and if None, this method will look for a value in the auth_token member of SpreadsheetsClient. """ return self.get_entry(uri, auth_token=auth_token, desired_class=desired_class, **kwargs) GetEventEntry = get_event_entry def get_calendar_acl_feed(self, uri='https://www.google.com/calendar/feeds/default/acl/full', desired_class=gdata.calendar.data.CalendarAclFeed, auth_token=None, **kwargs): """Obtains an Access Control List feed. Args: uri: The uri of the desired Acl feed. Defaults to https://www.google.com/calendar/feeds/default/acl/full. desired_class: class descended from atom.core.XmlElement to which a successful response should be converted. If there is no converter function specified (desired_class=None) then the desired_class will be used in calling the atom.core.parse function. If neither the desired_class nor the converter is specified, an HTTP reponse object will be returned. Defaults to gdata.calendar.data.CalendarAclFeed. auth_token: An object which sets the Authorization HTTP header in its modify_request method. Recommended classes include gdata.gauth.ClientLoginToken and gdata.gauth.AuthSubToken among others. Represents the current user. Defaults to None and if None, this method will look for a value in the auth_token member of SpreadsheetsClient. """ return self.get_feed(uri, auth_token=auth_token, desired_class=desired_class, **kwargs) GetCalendarAclFeed = get_calendar_acl_feed def get_calendar_acl_entry(self, uri, desired_class=gdata.calendar.data.CalendarAclEntry, auth_token=None, **kwargs): """Obtains a single Access Control List entry. Args: uri: The uri of the desired Acl feed. desired_class: class descended from atom.core.XmlElement to which a successful response should be converted. If there is no converter function specified (desired_class=None) then the desired_class will be used in calling the atom.core.parse function. If neither the desired_class nor the converter is specified, an HTTP reponse object will be returned. Defaults to gdata.calendar.data.CalendarAclEntry. auth_token: An object which sets the Authorization HTTP header in its modify_request method. Recommended classes include gdata.gauth.ClientLoginToken and gdata.gauth.AuthSubToken among others. Represents the current user. Defaults to None and if None, this method will look for a value in the auth_token member of SpreadsheetsClient. """ return self.get_entry(uri, auth_token=auth_token, desired_class=desired_class, **kwargs) GetCalendarAclEntry = get_calendar_acl_entry def insert_calendar(self, new_calendar, insert_uri=None, auth_token=None, **kwargs): """Adds an new calendar to Google Calendar. Args: new_calendar: atom.Entry or subclass A new calendar which is to be added to Google Calendar. insert_uri: the URL to post new contacts to the feed url_params: dict (optional) Additional URL parameters to be included in the insertion request. escape_params: boolean (optional) If true, the url_parameters will be escaped before they are included in the request. Returns: On successful insert, an entry containing the contact created On failure, a RequestError is raised of the form: {'status': HTTP status code from server, 'reason': HTTP reason from the server, 'body': HTTP body of the server's response} """ insert_uri = insert_uri or self.GetCalendarFeedUri(feed='owncalendars') return self.Post(new_calendar, insert_uri, auth_token=auth_token, **kwargs) InsertCalendar = insert_calendar def insert_calendar_subscription(self, calendar, insert_uri=None, auth_token=None, **kwargs): """Subscribes the authenticated user to the provided calendar. Args: calendar: The calendar to which the user should be subscribed. url_params: dict (optional) Additional URL parameters to be included in the insertion request. escape_params: boolean (optional) If true, the url_parameters will be escaped before they are included in the request. Returns: On successful insert, an entry containing the subscription created On failure, a RequestError is raised of the form: {'status': HTTP status code from server, 'reason': HTTP reason from the server, 'body': HTTP body of the server's response} """ insert_uri = insert_uri or self.GetCalendarFeedUri(feed='allcalendars') return self.Post(calendar, insert_uri, auth_token=auth_token, **kwargs) InsertCalendarSubscription = insert_calendar_subscription def insert_event(self, new_event, insert_uri=None, auth_token=None, **kwargs): """Adds an new event to Google Calendar. Args: new_event: atom.Entry or subclass A new event which is to be added to Google Calendar. insert_uri: the URL to post new contacts to the feed url_params: dict (optional) Additional URL parameters to be included in the insertion request. escape_params: boolean (optional) If true, the url_parameters will be escaped before they are included in the request. Returns: On successful insert, an entry containing the contact created On failure, a RequestError is raised of the form: {'status': HTTP status code from server, 'reason': HTTP reason from the server, 'body': HTTP body of the server's response} """ insert_uri = insert_uri or self.GetCalendarEventFeedUri() return self.Post(new_event, insert_uri, auth_token=auth_token, **kwargs) InsertEvent = insert_event def insert_acl_entry(self, new_acl_entry, insert_uri = 'https://www.google.com/calendar/feeds/default/acl/full', auth_token=None, **kwargs): """Adds an new Acl entry to Google Calendar. Args: new_acl_event: atom.Entry or subclass A new acl which is to be added to Google Calendar. insert_uri: the URL to post new contacts to the feed url_params: dict (optional) Additional URL parameters to be included in the insertion request. escape_params: boolean (optional) If true, the url_parameters will be escaped before they are included in the request. Returns: On successful insert, an entry containing the contact created On failure, a RequestError is raised of the form: {'status': HTTP status code from server, 'reason': HTTP reason from the server, 'body': HTTP body of the server's response} """ return self.Post(new_acl_entry, insert_uri, auth_token=auth_token, **kwargs) InsertAclEntry = insert_acl_entry def execute_batch(self, batch_feed, url, desired_class=None): """Sends a batch request feed to the server. Args: batch_feed: gdata.contacts.CalendarEventFeed A feed containing batch request entries. Each entry contains the operation to be performed on the data contained in the entry. For example an entry with an operation type of insert will be used as if the individual entry had been inserted. url: str The batch URL to which these operations should be applied. converter: Function (optional) The function used to convert the server's response to an object. Returns: The results of the batch request's execution on the server. If the default converter is used, this is stored in a ContactsFeed. """ return self.Post(batch_feed, url, desired_class=desired_class) ExecuteBatch = execute_batch def update(self, entry, auth_token=None, **kwargs): """Edits the entry on the server by sending the XML for this entry. Performs a PUT and converts the response to a new entry object with a matching class to the entry passed in. Args: entry: auth_token: Returns: A new Entry object of a matching type to the entry which was passed in. """ return gdata.client.GDClient.Update(self, entry, auth_token=auth_token, force=True, **kwargs) Update = update class CalendarEventQuery(gdata.client.Query): """ Create a custom Calendar Query Full specs can be found at: U{Calendar query parameters reference } """ def __init__(self, feed=None, ctz=None, fields=None, futureevents=None, max_attendees=None, orderby=None, recurrence_expansion_start=None, recurrence_expansion_end=None, singleevents=None, showdeleted=None, showhidden=None, sortorder=None, start_min=None, start_max=None, updated_min=None, **kwargs): """ @param max_results: The maximum number of entries to return. If you want to receive all of the contacts, rather than only the default maximum, you can specify a very large number for max-results. @param start-index: The 1-based index of the first result to be retrieved. @param updated-min: The lower bound on entry update dates. @param group: Constrains the results to only the contacts belonging to the group specified. Value of this parameter specifies group ID @param orderby: Sorting criterion. The only supported value is lastmodified. @param showdeleted: Include deleted contacts in the returned contacts feed @pram sortorder: Sorting order direction. Can be either ascending or descending. @param requirealldeleted: Only relevant if showdeleted and updated-min are also provided. It dictates the behavior of the server in case it detects that placeholders of some entries deleted since the point in time specified as updated-min may have been lost. """ gdata.client.Query.__init__(self, **kwargs) self.ctz = ctz self.fields = fields self.futureevents = futureevents self.max_attendees = max_attendees self.orderby = orderby self.recurrence_expansion_start = recurrence_expansion_start self.recurrence_expansion_end = recurrence_expansion_end self.singleevents = singleevents self.showdeleted = showdeleted self.showhidden = showhidden self.sortorder = sortorder self.start_min = start_min self.start_max = start_max self.updated_min = updated_min def modify_request(self, http_request): if self.ctz: gdata.client._add_query_param('ctz', self.ctz, http_request) if self.fields: gdata.client._add_query_param('fields', self.fields, http_request) if self.futureevents: gdata.client._add_query_param('futureevents', self.futureevents, http_request) if self.max_attendees: gdata.client._add_query_param('max-attendees', self.max_attendees, http_request) if self.orderby: gdata.client._add_query_param('orderby', self.orderby, http_request) if self.recurrence_expansion_start: gdata.client._add_query_param('recurrence-expansion-start', self.recurrence_expansion_start, http_request) if self.recurrence_expansion_end: gdata.client._add_query_param('recurrence-expansion-end', self.recurrence_expansion_end, http_request) if self.singleevents: gdata.client._add_query_param('singleevents', self.singleevents, http_request) if self.showdeleted: gdata.client._add_query_param('showdeleted', self.showdeleted, http_request) if self.showhidden: gdata.client._add_query_param('showhidden', self.showhidden, http_request) if self.sortorder: gdata.client._add_query_param('sortorder', self.sortorder, http_request) if self.start_min: gdata.client._add_query_param('start-min', self.start_min, http_request) if self.start_max: gdata.client._add_query_param('start-max', self.start_max, http_request) if self.updated_min: gdata.client._add_query_param('updated-min', self.updated_min, http_request) gdata.client.Query.modify_request(self, http_request) ModifyRequest = modify_request gdata-2.0.18/src/gdata/calendar/service.py0000750036466300116100000005475412156622363020411 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright (C) 2006 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. """CalendarService extends the GDataService to streamline Google Calendar operations. CalendarService: Provides methods to query feeds and manipulate items. Extends GDataService. DictionaryToParamList: Function which converts a dictionary into a list of URL arguments (represented as strings). This is a utility function used in CRUD operations. """ __author__ = 'api.vli (Vivian Li)' import urllib import gdata import atom.service import gdata.service import gdata.calendar import atom DEFAULT_BATCH_URL = ('http://www.google.com/calendar/feeds/default/private' '/full/batch') class Error(Exception): pass class RequestError(Error): pass class CalendarService(gdata.service.GDataService): """Client for the Google Calendar service.""" def __init__(self, email=None, password=None, source=None, server='www.google.com', additional_headers=None, **kwargs): """Creates a client for the Google Calendar service. Args: email: string (optional) The user's email address, used for authentication. password: string (optional) The user's password. source: string (optional) The name of the user's application. server: string (optional) The name of the server to which a connection will be opened. Default value: 'www.google.com'. **kwargs: The other parameters to pass to gdata.service.GDataService constructor. """ gdata.service.GDataService.__init__( self, email=email, password=password, service='cl', source=source, server=server, additional_headers=additional_headers, **kwargs) def GetCalendarEventFeed(self, uri='/calendar/feeds/default/private/full'): return self.Get(uri, converter=gdata.calendar.CalendarEventFeedFromString) def GetCalendarEventEntry(self, uri): return self.Get(uri, converter=gdata.calendar.CalendarEventEntryFromString) def GetCalendarListFeed(self, uri='/calendar/feeds/default/allcalendars/full'): return self.Get(uri, converter=gdata.calendar.CalendarListFeedFromString) def GetAllCalendarsFeed(self, uri='/calendar/feeds/default/allcalendars/full'): return self.Get(uri, converter=gdata.calendar.CalendarListFeedFromString) def GetOwnCalendarsFeed(self, uri='/calendar/feeds/default/owncalendars/full'): return self.Get(uri, converter=gdata.calendar.CalendarListFeedFromString) def GetCalendarListEntry(self, uri): return self.Get(uri, converter=gdata.calendar.CalendarListEntryFromString) def GetCalendarAclFeed(self, uri='/calendar/feeds/default/acl/full'): return self.Get(uri, converter=gdata.calendar.CalendarAclFeedFromString) def GetCalendarAclEntry(self, uri): return self.Get(uri, converter=gdata.calendar.CalendarAclEntryFromString) def GetCalendarEventCommentFeed(self, uri): return self.Get(uri, converter=gdata.calendar.CalendarEventCommentFeedFromString) def GetCalendarEventCommentEntry(self, uri): return self.Get(uri, converter=gdata.calendar.CalendarEventCommentEntryFromString) def Query(self, uri, converter=None): """Performs a query and returns a resulting feed or entry. Args: feed: string The feed which is to be queried Returns: On success, a GDataFeed or Entry depending on which is sent from the server. On failure, a RequestError is raised of the form: {'status': HTTP status code from server, 'reason': HTTP reason from the server, 'body': HTTP body of the server's response} """ if converter: result = self.Get(uri, converter=converter) else: result = self.Get(uri) return result def CalendarQuery(self, query): if isinstance(query, CalendarEventQuery): return self.Query(query.ToUri(), converter=gdata.calendar.CalendarEventFeedFromString) elif isinstance(query, CalendarListQuery): return self.Query(query.ToUri(), converter=gdata.calendar.CalendarListFeedFromString) elif isinstance(query, CalendarEventCommentQuery): return self.Query(query.ToUri(), converter=gdata.calendar.CalendarEventCommentFeedFromString) else: return self.Query(query.ToUri()) def InsertEvent(self, new_event, insert_uri, url_params=None, escape_params=True): """Adds an event to Google Calendar. Args: new_event: atom.Entry or subclass A new event which is to be added to Google Calendar. insert_uri: the URL to post new events to the feed url_params: dict (optional) Additional URL parameters to be included in the insertion request. escape_params: boolean (optional) If true, the url_parameters will be escaped before they are included in the request. Returns: On successful insert, an entry containing the event created On failure, a RequestError is raised of the form: {'status': HTTP status code from server, 'reason': HTTP reason from the server, 'body': HTTP body of the server's response} """ return self.Post(new_event, insert_uri, url_params=url_params, escape_params=escape_params, converter=gdata.calendar.CalendarEventEntryFromString) def InsertCalendarSubscription(self, calendar, url_params=None, escape_params=True): """Subscribes the authenticated user to the provided calendar. Args: calendar: The calendar to which the user should be subscribed. url_params: dict (optional) Additional URL parameters to be included in the insertion request. escape_params: boolean (optional) If true, the url_parameters will be escaped before they are included in the request. Returns: On successful insert, an entry containing the subscription created On failure, a RequestError is raised of the form: {'status': HTTP status code from server, 'reason': HTTP reason from the server, 'body': HTTP body of the server's response} """ insert_uri = '/calendar/feeds/default/allcalendars/full' return self.Post(calendar, insert_uri, url_params=url_params, escape_params=escape_params, converter=gdata.calendar.CalendarListEntryFromString) def InsertCalendar(self, new_calendar, url_params=None, escape_params=True): """Creates a new calendar. Args: new_calendar: The calendar to be created url_params: dict (optional) Additional URL parameters to be included in the insertion request. escape_params: boolean (optional) If true, the url_parameters will be escaped before they are included in the request. Returns: On successful insert, an entry containing the calendar created On failure, a RequestError is raised of the form: {'status': HTTP status code from server, 'reason': HTTP reason from the server, 'body': HTTP body of the server's response} """ insert_uri = '/calendar/feeds/default/owncalendars/full' response = self.Post(new_calendar, insert_uri, url_params=url_params, escape_params=escape_params, converter=gdata.calendar.CalendarListEntryFromString) return response def UpdateCalendar(self, calendar, url_params=None, escape_params=True): """Updates a calendar. Args: calendar: The calendar which should be updated url_params: dict (optional) Additional URL parameters to be included in the insertion request. escape_params: boolean (optional) If true, the url_parameters will be escaped before they are included in the request. Returns: On successful insert, an entry containing the calendar created On failure, a RequestError is raised of the form: {'status': HTTP status code from server, 'reason': HTTP reason from the server, 'body': HTTP body of the server's response} """ update_uri = calendar.GetEditLink().href response = self.Put(data=calendar, uri=update_uri, url_params=url_params, escape_params=escape_params, converter=gdata.calendar.CalendarListEntryFromString) return response def InsertAclEntry(self, new_entry, insert_uri, url_params=None, escape_params=True): """Adds an ACL entry (rule) to Google Calendar. Args: new_entry: atom.Entry or subclass A new ACL entry which is to be added to Google Calendar. insert_uri: the URL to post new entries to the ACL feed url_params: dict (optional) Additional URL parameters to be included in the insertion request. escape_params: boolean (optional) If true, the url_parameters will be escaped before they are included in the request. Returns: On successful insert, an entry containing the ACL entry created On failure, a RequestError is raised of the form: {'status': HTTP status code from server, 'reason': HTTP reason from the server, 'body': HTTP body of the server's response} """ return self.Post(new_entry, insert_uri, url_params=url_params, escape_params=escape_params, converter=gdata.calendar.CalendarAclEntryFromString) def InsertEventComment(self, new_entry, insert_uri, url_params=None, escape_params=True): """Adds an entry to Google Calendar. Args: new_entry: atom.Entry or subclass A new entry which is to be added to Google Calendar. insert_uri: the URL to post new entrys to the feed url_params: dict (optional) Additional URL parameters to be included in the insertion request. escape_params: boolean (optional) If true, the url_parameters will be escaped before they are included in the request. Returns: On successful insert, an entry containing the comment created On failure, a RequestError is raised of the form: {'status': HTTP status code from server, 'reason': HTTP reason from the server, 'body': HTTP body of the server's response} """ return self.Post(new_entry, insert_uri, url_params=url_params, escape_params=escape_params, converter=gdata.calendar.CalendarEventCommentEntryFromString) def _RemoveStandardUrlPrefix(self, url): url_prefix = 'http://%s/' % self.server if url.startswith(url_prefix): return url[len(url_prefix) - 1:] return url def DeleteEvent(self, edit_uri, extra_headers=None, url_params=None, escape_params=True): """Removes an event with the specified ID from Google Calendar. Args: edit_uri: string The edit URL of the entry to be deleted. Example: 'http://www.google.com/calendar/feeds/default/private/full/abx' url_params: dict (optional) Additional URL parameters to be included in the deletion request. escape_params: boolean (optional) If true, the url_parameters will be escaped before they are included in the request. Returns: On successful delete, a httplib.HTTPResponse containing the server's response to the DELETE request. On failure, a RequestError is raised of the form: {'status': HTTP status code from server, 'reason': HTTP reason from the server, 'body': HTTP body of the server's response} """ edit_uri = self._RemoveStandardUrlPrefix(edit_uri) return self.Delete('%s' % edit_uri, url_params=url_params, escape_params=escape_params) def DeleteAclEntry(self, edit_uri, extra_headers=None, url_params=None, escape_params=True): """Removes an ACL entry at the given edit_uri from Google Calendar. Args: edit_uri: string The edit URL of the entry to be deleted. Example: 'http://www.google.com/calendar/feeds/liz%40gmail.com/acl/full/default' url_params: dict (optional) Additional URL parameters to be included in the deletion request. escape_params: boolean (optional) If true, the url_parameters will be escaped before they are included in the request. Returns: On successful delete, a httplib.HTTPResponse containing the server's response to the DELETE request. On failure, a RequestError is raised of the form: {'status': HTTP status code from server, 'reason': HTTP reason from the server, 'body': HTTP body of the server's response} """ edit_uri = self._RemoveStandardUrlPrefix(edit_uri) return self.Delete('%s' % edit_uri, url_params=url_params, escape_params=escape_params) def DeleteCalendarEntry(self, edit_uri, extra_headers=None, url_params=None, escape_params=True): """Removes a calendar entry at the given edit_uri from Google Calendar. Args: edit_uri: string The edit URL of the entry to be deleted. Example: 'http://www.google.com/calendar/feeds/default/allcalendars/abcdef@group.calendar.google.com' url_params: dict (optional) Additional URL parameters to be included in the deletion request. escape_params: boolean (optional) If true, the url_parameters will be escaped before they are included in the request. Returns: On successful delete, True is returned On failure, a RequestError is raised of the form: {'status': HTTP status code from server, 'reason': HTTP reason from the server, 'body': HTTP body of the server's response} """ return self.Delete(edit_uri, url_params=url_params, escape_params=escape_params) def UpdateEvent(self, edit_uri, updated_event, url_params=None, escape_params=True): """Updates an existing event. Args: edit_uri: string The edit link URI for the element being updated updated_event: string, atom.Entry, or subclass containing the Atom Entry which will replace the event which is stored at the edit_url url_params: dict (optional) Additional URL parameters to be included in the update request. escape_params: boolean (optional) If true, the url_parameters will be escaped before they are included in the request. Returns: On successful update, a httplib.HTTPResponse containing the server's response to the PUT request. On failure, a RequestError is raised of the form: {'status': HTTP status code from server, 'reason': HTTP reason from the server, 'body': HTTP body of the server's response} """ edit_uri = self._RemoveStandardUrlPrefix(edit_uri) return self.Put(updated_event, '%s' % edit_uri, url_params=url_params, escape_params=escape_params, converter=gdata.calendar.CalendarEventEntryFromString) def UpdateAclEntry(self, edit_uri, updated_rule, url_params=None, escape_params=True): """Updates an existing ACL rule. Args: edit_uri: string The edit link URI for the element being updated updated_rule: string, atom.Entry, or subclass containing the Atom Entry which will replace the event which is stored at the edit_url url_params: dict (optional) Additional URL parameters to be included in the update request. escape_params: boolean (optional) If true, the url_parameters will be escaped before they are included in the request. Returns: On successful update, a httplib.HTTPResponse containing the server's response to the PUT request. On failure, a RequestError is raised of the form: {'status': HTTP status code from server, 'reason': HTTP reason from the server, 'body': HTTP body of the server's response} """ edit_uri = self._RemoveStandardUrlPrefix(edit_uri) return self.Put(updated_rule, '%s' % edit_uri, url_params=url_params, escape_params=escape_params, converter=gdata.calendar.CalendarAclEntryFromString) def ExecuteBatch(self, batch_feed, url, converter=gdata.calendar.CalendarEventFeedFromString): """Sends a batch request feed to the server. The batch request needs to be sent to the batch URL for a particular calendar. You can find the URL by calling GetBatchLink().href on the CalendarEventFeed. Args: batch_feed: gdata.calendar.CalendarEventFeed A feed containing batch request entries. Each entry contains the operation to be performed on the data contained in the entry. For example an entry with an operation type of insert will be used as if the individual entry had been inserted. url: str The batch URL for the Calendar to which these operations should be applied. converter: Function (optional) The function used to convert the server's response to an object. The default value is CalendarEventFeedFromString. Returns: The results of the batch request's execution on the server. If the default converter is used, this is stored in a CalendarEventFeed. """ return self.Post(batch_feed, url, converter=converter) class CalendarEventQuery(gdata.service.Query): def __init__(self, user='default', visibility='private', projection='full', text_query=None, params=None, categories=None): gdata.service.Query.__init__(self, feed='http://www.google.com/calendar/feeds/%s/%s/%s' % ( urllib.quote(user), urllib.quote(visibility), urllib.quote(projection)), text_query=text_query, params=params, categories=categories) def _GetStartMin(self): if 'start-min' in self.keys(): return self['start-min'] else: return None def _SetStartMin(self, val): self['start-min'] = val start_min = property(_GetStartMin, _SetStartMin, doc="""The start-min query parameter""") def _GetStartMax(self): if 'start-max' in self.keys(): return self['start-max'] else: return None def _SetStartMax(self, val): self['start-max'] = val start_max = property(_GetStartMax, _SetStartMax, doc="""The start-max query parameter""") def _GetOrderBy(self): if 'orderby' in self.keys(): return self['orderby'] else: return None def _SetOrderBy(self, val): if val is not 'lastmodified' and val is not 'starttime': raise Error, "Order By must be either 'lastmodified' or 'starttime'" self['orderby'] = val orderby = property(_GetOrderBy, _SetOrderBy, doc="""The orderby query parameter""") def _GetSortOrder(self): if 'sortorder' in self.keys(): return self['sortorder'] else: return None def _SetSortOrder(self, val): if (val is not 'ascending' and val is not 'descending' and val is not 'a' and val is not 'd' and val is not 'ascend' and val is not 'descend'): raise Error, "Sort order must be either ascending, ascend, " + ( "a or descending, descend, or d") self['sortorder'] = val sortorder = property(_GetSortOrder, _SetSortOrder, doc="""The sortorder query parameter""") def _GetSingleEvents(self): if 'singleevents' in self.keys(): return self['singleevents'] else: return None def _SetSingleEvents(self, val): self['singleevents'] = val singleevents = property(_GetSingleEvents, _SetSingleEvents, doc="""The singleevents query parameter""") def _GetFutureEvents(self): if 'futureevents' in self.keys(): return self['futureevents'] else: return None def _SetFutureEvents(self, val): self['futureevents'] = val futureevents = property(_GetFutureEvents, _SetFutureEvents, doc="""The futureevents query parameter""") def _GetRecurrenceExpansionStart(self): if 'recurrence-expansion-start' in self.keys(): return self['recurrence-expansion-start'] else: return None def _SetRecurrenceExpansionStart(self, val): self['recurrence-expansion-start'] = val recurrence_expansion_start = property(_GetRecurrenceExpansionStart, _SetRecurrenceExpansionStart, doc="""The recurrence-expansion-start query parameter""") def _GetRecurrenceExpansionEnd(self): if 'recurrence-expansion-end' in self.keys(): return self['recurrence-expansion-end'] else: return None def _SetRecurrenceExpansionEnd(self, val): self['recurrence-expansion-end'] = val recurrence_expansion_end = property(_GetRecurrenceExpansionEnd, _SetRecurrenceExpansionEnd, doc="""The recurrence-expansion-end query parameter""") def _SetTimezone(self, val): self['ctz'] = val def _GetTimezone(self): if 'ctz' in self.keys(): return self['ctz'] else: return None ctz = property(_GetTimezone, _SetTimezone, doc="""The ctz query parameter which sets report time on the server.""") class CalendarListQuery(gdata.service.Query): """Queries the Google Calendar meta feed""" def __init__(self, userId=None, text_query=None, params=None, categories=None): if userId is None: userId = 'default' gdata.service.Query.__init__(self, feed='http://www.google.com/calendar/feeds/' +userId, text_query=text_query, params=params, categories=categories) class CalendarEventCommentQuery(gdata.service.Query): """Queries the Google Calendar event comments feed""" def __init__(self, feed=None): gdata.service.Query.__init__(self, feed=feed) gdata-2.0.18/src/gdata/books/0000750036466300116100000000000012156625015015716 5ustar afshareng00000000000000gdata-2.0.18/src/gdata/books/data.py0000640036466300116100000000461112156622363017207 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright (C) 2009 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. """Contains the data classes of the Google Book Search Data API""" __author__ = 'j.s@google.com (Jeff Scudder)' import atom.core import atom.data import gdata.data import gdata.dublincore.data import gdata.opensearch.data GBS_TEMPLATE = '{http://schemas.google.com/books/2008/}%s' class CollectionEntry(gdata.data.GDEntry): """Describes an entry in a feed of collections.""" class CollectionFeed(gdata.data.BatchFeed): """Describes a Book Search collection feed.""" entry = [CollectionEntry] class Embeddability(atom.core.XmlElement): """Describes an embeddability.""" _qname = GBS_TEMPLATE % 'embeddability' value = 'value' class OpenAccess(atom.core.XmlElement): """Describes an open access.""" _qname = GBS_TEMPLATE % 'openAccess' value = 'value' class Review(atom.core.XmlElement): """User-provided review.""" _qname = GBS_TEMPLATE % 'review' lang = 'lang' type = 'type' class Viewability(atom.core.XmlElement): """Describes a viewability.""" _qname = GBS_TEMPLATE % 'viewability' value = 'value' class VolumeEntry(gdata.data.GDEntry): """Describes an entry in a feed of Book Search volumes.""" comments = gdata.data.Comments language = [gdata.dublincore.data.Language] open_access = OpenAccess format = [gdata.dublincore.data.Format] dc_title = [gdata.dublincore.data.Title] viewability = Viewability embeddability = Embeddability creator = [gdata.dublincore.data.Creator] rating = gdata.data.Rating description = [gdata.dublincore.data.Description] publisher = [gdata.dublincore.data.Publisher] date = [gdata.dublincore.data.Date] subject = [gdata.dublincore.data.Subject] identifier = [gdata.dublincore.data.Identifier] review = Review class VolumeFeed(gdata.data.BatchFeed): """Describes a Book Search volume feed.""" entry = [VolumeEntry] gdata-2.0.18/src/gdata/books/__init__.py0000640036466300116100000004414412156622363020042 0ustar afshareng00000000000000#!/usr/bin/python """ Data Models for books.service All classes can be instantiated from an xml string using their FromString class method. Notes: * Book.title displays the first dc:title because the returned XML repeats that datum as atom:title. There is an undocumented gbs:openAccess element that is not parsed. """ __author__ = "James Sams " __copyright__ = "Apache License v2.0" import atom import gdata BOOK_SEARCH_NAMESPACE = 'http://schemas.google.com/books/2008' DC_NAMESPACE = 'http://purl.org/dc/terms' ANNOTATION_REL = "http://schemas.google.com/books/2008/annotation" INFO_REL = "http://schemas.google.com/books/2008/info" LABEL_SCHEME = "http://schemas.google.com/books/2008/labels" PREVIEW_REL = "http://schemas.google.com/books/2008/preview" THUMBNAIL_REL = "http://schemas.google.com/books/2008/thumbnail" FULL_VIEW = "http://schemas.google.com/books/2008#view_all_pages" PARTIAL_VIEW = "http://schemas.google.com/books/2008#view_partial" NO_VIEW = "http://schemas.google.com/books/2008#view_no_pages" UNKNOWN_VIEW = "http://schemas.google.com/books/2008#view_unknown" EMBEDDABLE = "http://schemas.google.com/books/2008#embeddable" NOT_EMBEDDABLE = "http://schemas.google.com/books/2008#not_embeddable" class _AtomFromString(atom.AtomBase): #@classmethod def FromString(cls, s): return atom.CreateClassFromXMLString(cls, s) FromString = classmethod(FromString) class Creator(_AtomFromString): """ The element identifies an author-or more generally, an entity responsible for creating the volume in question. Examples of a creator include a person, an organization, or a service. In the case of anthologies, proceedings, or other edited works, this field may be used to indicate editors or other entities responsible for collecting the volume's contents. This element appears as a child of . If there are multiple authors or contributors to the book, there may be multiple elements in the volume entry (one for each creator or contributor). """ _tag = 'creator' _namespace = DC_NAMESPACE class Date(_AtomFromString): #iso 8601 / W3CDTF profile """ The element indicates the publication date of the specific volume in question. If the book is a reprint, this is the reprint date, not the original publication date. The date is encoded according to the ISO-8601 standard (and more specifically, the W3CDTF profile). The element can appear only as a child of . Usually only the year or the year and the month are given. YYYY-MM-DDThh:mm:ssTZD TZD = -hh:mm or +hh:mm """ _tag = 'date' _namespace = DC_NAMESPACE class Description(_AtomFromString): """ The element includes text that describes a book or book result. In a search result feed, this may be a search result "snippet" that contains the words around the user's search term. For a single volume feed, this element may contain a synopsis of the book. The element can appear only as a child of """ _tag = 'description' _namespace = DC_NAMESPACE class Format(_AtomFromString): """ The element describes the physical properties of the volume. Currently, it indicates the number of pages in the book, but more information may be added to this field in the future. This element can appear only as a child of . """ _tag = 'format' _namespace = DC_NAMESPACE class Identifier(_AtomFromString): """ The element provides an unambiguous reference to a particular book. * Every contains at least one child. * The first identifier is always the unique string Book Search has assigned to the volume (such as s1gVAAAAYAAJ). This is the ID that appears in the book's URL in the Book Search GUI, as well as in the URL of that book's single item feed. * Many books contain additional elements. These provide alternate, external identifiers to the volume. Such identifiers may include the ISBNs, ISSNs, Library of Congress Control Numbers (LCCNs), and OCLC numbers; they are prepended with a corresponding namespace prefix (such as "ISBN:"). * Any can be passed to the Dynamic Links, used to instantiate an Embedded Viewer, or even used to construct static links to Book Search. The element can appear only as a child of . """ _tag = 'identifier' _namespace = DC_NAMESPACE class Publisher(_AtomFromString): """ The element contains the name of the entity responsible for producing and distributing the volume (usually the specific edition of this book). Examples of a publisher include a person, an organization, or a service. This element can appear only as a child of . If there is more than one publisher, multiple elements may appear. """ _tag = 'publisher' _namespace = DC_NAMESPACE class Subject(_AtomFromString): """ The element identifies the topic of the book. Usually this is a Library of Congress Subject Heading (LCSH) or Book Industry Standards and Communications Subject Heading (BISAC). The element can appear only as a child of . There may be multiple elements per entry. """ _tag = 'subject' _namespace = DC_NAMESPACE class Title(_AtomFromString): """ The element contains the title of a book as it was published. If a book has a subtitle, it appears as a second element in the book result's . """ _tag = 'title' _namespace = DC_NAMESPACE class Viewability(_AtomFromString): """ Google Book Search respects the user's local copyright restrictions. As a result, previews or full views of some books are not available in all locations. The element indicates whether a book is fully viewable, can be previewed, or only has "about the book" information. These three "viewability modes" are the same ones returned by the Dynamic Links API. The element can appear only as a child of . The value attribute will take the form of the following URIs to represent the relevant viewing capability: Full View: http://schemas.google.com/books/2008#view_all_pages Limited Preview: http://schemas.google.com/books/2008#view_partial Snippet View/No Preview: http://schemas.google.com/books/2008#view_no_pages Unknown view: http://schemas.google.com/books/2008#view_unknown """ _tag = 'viewability' _namespace = BOOK_SEARCH_NAMESPACE _attributes = atom.AtomBase._attributes.copy() _attributes['value'] = 'value' def __init__(self, value=None, text=None, extension_elements=None, extension_attributes=None): self.value = value _AtomFromString.__init__(self, extension_elements=extension_elements, extension_attributes=extension_attributes, text=text) class Embeddability(_AtomFromString): """ Many of the books found on Google Book Search can be embedded on third-party sites using the Embedded Viewer. The element indicates whether a particular book result is available for embedding. By definition, a book that cannot be previewed on Book Search cannot be embedded on third- party sites. The element can appear only as a child of . The value attribute will take on one of the following URIs: embeddable: http://schemas.google.com/books/2008#embeddable not embeddable: http://schemas.google.com/books/2008#not_embeddable """ _tag = 'embeddability' _namespace = BOOK_SEARCH_NAMESPACE _attributes = atom.AtomBase._attributes.copy() _attributes['value'] = 'value' def __init__(self, value=None, text=None, extension_elements=None, extension_attributes=None): self.value = value _AtomFromString.__init__(self, extension_elements=extension_elements, extension_attributes=extension_attributes, text=text) class Review(_AtomFromString): """ When present, the element contains a user-generated review for a given book. This element currently appears only in the user library and user annotation feeds, as a child of . type: text, html, xhtml xml:lang: id of the language, a guess, (always two letters?) """ _tag = 'review' _namespace = BOOK_SEARCH_NAMESPACE _attributes = atom.AtomBase._attributes.copy() _attributes['type'] = 'type' _attributes['{http://www.w3.org/XML/1998/namespace}lang'] = 'lang' def __init__(self, type=None, lang=None, text=None, extension_elements=None, extension_attributes=None): self.type = type self.lang = lang _AtomFromString.__init__(self, extension_elements=extension_elements, extension_attributes=extension_attributes, text=text) class Rating(_AtomFromString): """All attributes must take an integral string between 1 and 5. The min, max, and average attributes represent 'community' ratings. The value attribute is the user's (of the feed from which the item is fetched, not necessarily the authenticated user) rating of the book. """ _tag = 'rating' _namespace = gdata.GDATA_NAMESPACE _attributes = atom.AtomBase._attributes.copy() _attributes['min'] = 'min' _attributes['max'] = 'max' _attributes['average'] = 'average' _attributes['value'] = 'value' def __init__(self, min=None, max=None, average=None, value=None, text=None, extension_elements=None, extension_attributes=None): self.min = min self.max = max self.average = average self.value = value _AtomFromString.__init__(self, extension_elements=extension_elements, extension_attributes=extension_attributes, text=text) class Book(_AtomFromString, gdata.GDataEntry): """ Represents an from either a search, annotation, library, or single item feed. Note that dc_title attribute is the proper title of the volume, title is an atom element and may not represent the full title. """ _tag = 'entry' _namespace = atom.ATOM_NAMESPACE _children = gdata.GDataEntry._children.copy() for i in (Creator, Identifier, Publisher, Subject,): _children['{%s}%s' % (i._namespace, i._tag)] = (i._tag, [i]) for i in (Date, Description, Format, Viewability, Embeddability, Review, Rating): # Review, Rating maybe only in anno/lib entrys _children['{%s}%s' % (i._namespace, i._tag)] = (i._tag, i) # there is an atom title as well, should we clobber that? del(i) _children['{%s}%s' % (Title._namespace, Title._tag)] = ('dc_title', [Title]) def to_dict(self): """Returns a dictionary of the book's available metadata. If the data cannot be discovered, it is not included as a key in the returned dict. The possible keys are: authors, embeddability, date, description, format, identifiers, publishers, rating, review, subjects, title, and viewability. Notes: * Plural keys will be lists * Singular keys will be strings * Title, despite usually being a list, joins the title and subtitle with a space as a single string. * embeddability and viewability only return the portion of the URI after # * identifiers is a list of tuples, where the first item of each tuple is the type of identifier and the second item is the identifying string. Note that while doing dict() on this tuple may be possible, some items may have multiple of the same identifier and converting to a dict may resulted in collisions/dropped data. * Rating returns only the user's rating. See Rating class for precise definition. """ d = {} if self.GetAnnotationLink(): d['annotation'] = self.GetAnnotationLink().href if self.creator: d['authors'] = [x.text for x in self.creator] if self.embeddability: d['embeddability'] = self.embeddability.value.split('#')[-1] if self.date: d['date'] = self.date.text if self.description: d['description'] = self.description.text if self.format: d['format'] = self.format.text if self.identifier: d['identifiers'] = [('google_id', self.identifier[0].text)] for x in self.identifier[1:]: l = x.text.split(':') # should we lower the case of the ids? d['identifiers'].append((l[0], ':'.join(l[1:]))) if self.GetInfoLink(): d['info'] = self.GetInfoLink().href if self.GetPreviewLink(): d['preview'] = self.GetPreviewLink().href if self.publisher: d['publishers'] = [x.text for x in self.publisher] if self.rating: d['rating'] = self.rating.value if self.review: d['review'] = self.review.text if self.subject: d['subjects'] = [x.text for x in self.subject] if self.GetThumbnailLink(): d['thumbnail'] = self.GetThumbnailLink().href if self.dc_title: d['title'] = ' '.join([x.text for x in self.dc_title]) if self.viewability: d['viewability'] = self.viewability.value.split('#')[-1] return d def __init__(self, creator=None, date=None, description=None, format=None, author=None, identifier=None, publisher=None, subject=None, dc_title=None, viewability=None, embeddability=None, review=None, rating=None, category=None, content=None, contributor=None, atom_id=None, link=None, published=None, rights=None, source=None, summary=None, title=None, control=None, updated=None, text=None, extension_elements=None, extension_attributes=None): self.creator = creator self.date = date self.description = description self.format = format self.identifier = identifier self.publisher = publisher self.subject = subject self.dc_title = dc_title or [] self.viewability = viewability self.embeddability = embeddability self.review = review self.rating = rating gdata.GDataEntry.__init__(self, author=author, category=category, content=content, contributor=contributor, atom_id=atom_id, link=link, published=published, rights=rights, source=source, summary=summary, title=title, control=control, updated=updated, text=text, extension_elements=extension_elements, extension_attributes=extension_attributes) def GetThumbnailLink(self): """Returns the atom.Link object representing the thumbnail URI.""" for i in self.link: if i.rel == THUMBNAIL_REL: return i def GetInfoLink(self): """ Returns the atom.Link object representing the human-readable info URI. """ for i in self.link: if i.rel == INFO_REL: return i def GetPreviewLink(self): """Returns the atom.Link object representing the preview URI.""" for i in self.link: if i.rel == PREVIEW_REL: return i def GetAnnotationLink(self): """ Returns the atom.Link object representing the Annotation URI. Note that the use of www.books in the href of this link seems to make this information useless. Using books.service.ANNOTATION_FEED and BOOK_SERVER to construct your URI seems to work better. """ for i in self.link: if i.rel == ANNOTATION_REL: return i def set_rating(self, value): """Set user's rating. Must be an integral string between 1 nad 5""" assert (value in ('1','2','3','4','5')) if not isinstance(self.rating, Rating): self.rating = Rating() self.rating.value = value def set_review(self, text, type='text', lang='en'): """Set user's review text""" self.review = Review(text=text, type=type, lang=lang) def get_label(self): """Get users label for the item as a string""" for i in self.category: if i.scheme == LABEL_SCHEME: return i.term def set_label(self, term): """Clear pre-existing label for the item and set term as the label.""" self.remove_label() self.category.append(atom.Category(term=term, scheme=LABEL_SCHEME)) def remove_label(self): """Clear the user's label for the item""" ln = len(self.category) for i, j in enumerate(self.category[::-1]): if j.scheme == LABEL_SCHEME: del(self.category[ln-1-i]) def clean_annotations(self): """Clear all annotations from an item. Useful for taking an item from another user's library/annotation feed and adding it to the authenticated user's library without adopting annotations.""" self.remove_label() self.review = None self.rating = None def get_google_id(self): """Get Google's ID of the item.""" return self.id.text.split('/')[-1] class BookFeed(_AtomFromString, gdata.GDataFeed): """Represents a feed of entries from a search.""" _tag = 'feed' _namespace = atom.ATOM_NAMESPACE _children = gdata.GDataFeed._children.copy() _children['{%s}%s' % (Book._namespace, Book._tag)] = (Book._tag, [Book]) if __name__ == '__main__': import doctest doctest.testfile('datamodels.txt') gdata-2.0.18/src/gdata/books/service.py0000640036466300116100000002612312156622363017740 0ustar afshareng00000000000000#!/usr/bin/python """ Extend gdata.service.GDataService to support authenticated CRUD ops on Books API http://code.google.com/apis/books/docs/getting-started.html http://code.google.com/apis/books/docs/gdata/developers_guide_protocol.html TODO: (here and __init__) * search based on label, review, or other annotations (possible?) * edit (specifically, Put requests) seem to fail effect a change Problems With API: * Adding a book with a review to the library adds a note, not a review. This does not get included in the returned item. You see this by looking at My Library through the website. * Editing a review never edits a review (unless it is freshly added, but see above). More generally, * a Put request with changed annotations (label/rating/review) does NOT change the data. Note: Put requests only work on the href from GetEditLink (as per the spec). Do not try to PUT to the annotate or library feeds, this will cause a 400 Invalid URI Bad Request response. Attempting to Post to one of the feeds with the updated annotations does not update them. See the following for (hopefully) a follow up: google.com/support/forum/p/booksearch-apis/thread?tid=27fd7f68de438fc8 * Attempts to workaround the edit problem continue to fail. For example, removing the item, editing the data, readding the item, gives us only our originally added data (annotations). This occurs even if we completely shut python down, refetch the book from the public feed, and re-add it. There is some kind of persistence going on that I cannot change. This is likely due to the annotations being cached in the annotation feed and the inability to edit (see Put, above) * GetAnnotationLink has www.books.... as the server, but hitting www... results in a bad URI error. * Spec indicates there may be multiple labels, but there does not seem to be a way to get the server to accept multiple labels, nor does the web interface have an obvious way to have multiple labels. Multiple labels are never returned. """ __author__ = "James Sams " __copyright__ = "Apache License v2.0" from shlex import split import gdata.service try: import books except ImportError: import gdata.books as books BOOK_SERVER = "books.google.com" GENERAL_FEED = "/books/feeds/volumes" ITEM_FEED = "/books/feeds/volumes/" LIBRARY_FEED = "/books/feeds/users/%s/collections/library/volumes" ANNOTATION_FEED = "/books/feeds/users/%s/volumes" PARTNER_FEED = "/books/feeds/p/%s/volumes" BOOK_SERVICE = "print" ACCOUNT_TYPE = "HOSTED_OR_GOOGLE" class BookService(gdata.service.GDataService): def __init__(self, email=None, password=None, source=None, server=BOOK_SERVER, account_type=ACCOUNT_TYPE, exception_handlers=tuple(), **kwargs): """source should be of form 'ProgramCompany - ProgramName - Version'""" gdata.service.GDataService.__init__(self, email=email, password=password, service=BOOK_SERVICE, source=source, server=server, **kwargs) self.exception_handlers = exception_handlers def search(self, q, start_index="1", max_results="10", min_viewability="none", feed=GENERAL_FEED, converter=books.BookFeed.FromString): """ Query the Public search feed. q is either a search string or a gdata.service.Query instance with a query set. min_viewability must be "none", "partial", or "full". If you change the feed to a single item feed, note that you will probably need to change the converter to be Book.FromString """ if not isinstance(q, gdata.service.Query): q = gdata.service.Query(text_query=q) if feed: q.feed = feed q['start-index'] = start_index q['max-results'] = max_results q['min-viewability'] = min_viewability return self.Get(uri=q.ToUri(),converter=converter) def search_by_keyword(self, q='', feed=GENERAL_FEED, start_index="1", max_results="10", min_viewability="none", **kwargs): """ Query the Public Search Feed by keyword. Non-keyword strings can be set in q. This is quite fragile. Is there a function somewhere in the Google library that will parse a query the same way that Google does? Legal Identifiers are listed below and correspond to their meaning at http://books.google.com/advanced_book_search: all_words exact_phrase at_least_one without_words title author publisher subject isbn lccn oclc seemingly unsupported: publication_date: a sequence of two, two tuples: ((min_month,min_year),(max_month,max_year)) where month is one/two digit month, year is 4 digit, eg: (('1','2000'),('10','2003')). Lower bound is inclusive, upper bound is exclusive """ for k, v in kwargs.items(): if not v: continue k = k.lower() if k == 'all_words': q = "%s %s" % (q, v) elif k == 'exact_phrase': q = '%s "%s"' % (q, v.strip('"')) elif k == 'at_least_one': q = '%s %s' % (q, ' '.join(['OR "%s"' % x for x in split(v)])) elif k == 'without_words': q = '%s %s' % (q, ' '.join(['-"%s"' % x for x in split(v)])) elif k in ('author','title', 'publisher'): q = '%s %s' % (q, ' '.join(['in%s:"%s"'%(k,x) for x in split(v)])) elif k == 'subject': q = '%s %s' % (q, ' '.join(['%s:"%s"' % (k,x) for x in split(v)])) elif k == 'isbn': q = '%s ISBN%s' % (q, v) elif k == 'issn': q = '%s ISSN%s' % (q,v) elif k == 'oclc': q = '%s OCLC%s' % (q,v) else: raise ValueError("Unsupported search keyword") return self.search(q.strip(),start_index=start_index, feed=feed, max_results=max_results, min_viewability=min_viewability) def search_library(self, q, id='me', **kwargs): """Like search, but in a library feed. Default is the authenticated user's feed. Change by setting id.""" if 'feed' in kwargs: raise ValueError("kwarg 'feed' conflicts with library_id") feed = LIBRARY_FEED % id return self.search(q, feed=feed, **kwargs) def search_library_by_keyword(self, id='me', **kwargs): """Hybrid of search_by_keyword and search_library """ if 'feed' in kwargs: raise ValueError("kwarg 'feed' conflicts with library_id") feed = LIBRARY_FEED % id return self.search_by_keyword(feed=feed,**kwargs) def search_annotations(self, q, id='me', **kwargs): """Like search, but in an annotation feed. Default is the authenticated user's feed. Change by setting id.""" if 'feed' in kwargs: raise ValueError("kwarg 'feed' conflicts with library_id") feed = ANNOTATION_FEED % id return self.search(q, feed=feed, **kwargs) def search_annotations_by_keyword(self, id='me', **kwargs): """Hybrid of search_by_keyword and search_annotations """ if 'feed' in kwargs: raise ValueError("kwarg 'feed' conflicts with library_id") feed = ANNOTATION_FEED % id return self.search_by_keyword(feed=feed,**kwargs) def add_item_to_library(self, item): """Add the item, either an XML string or books.Book instance, to the user's library feed""" feed = LIBRARY_FEED % 'me' return self.Post(data=item, uri=feed, converter=books.Book.FromString) def remove_item_from_library(self, item): """ Remove the item, a books.Book instance, from the authenticated user's library feed. Using an item retrieved from a public search will fail. """ return self.Delete(item.GetEditLink().href) def add_annotation(self, item): """ Add the item, either an XML string or books.Book instance, to the user's annotation feed. """ # do not use GetAnnotationLink, results in 400 Bad URI due to www return self.Post(data=item, uri=ANNOTATION_FEED % 'me', converter=books.Book.FromString) def edit_annotation(self, item): """ Send an edited item, a books.Book instance, to the user's annotation feed. Note that whereas extra annotations in add_annotations, minus ratings which are immutable once set, are simply added to the item in the annotation feed, if an annotation has been removed from the item, sending an edit request will remove that annotation. This should not happen with add_annotation. """ return self.Put(data=item, uri=item.GetEditLink().href, converter=books.Book.FromString) def get_by_google_id(self, id): return self.Get(ITEM_FEED + id, converter=books.Book.FromString) def get_library(self, id='me',feed=LIBRARY_FEED, start_index="1", max_results="100", min_viewability="none", converter=books.BookFeed.FromString): """ Return a generator object that will return gbook.Book instances until the search feed no longer returns an item from the GetNextLink method. Thus max_results is not the maximum number of items that will be returned, but rather the number of items per page of searches. This has been set high to reduce the required number of network requests. """ q = gdata.service.Query() q.feed = feed % id q['start-index'] = start_index q['max-results'] = max_results q['min-viewability'] = min_viewability x = self.Get(uri=q.ToUri(), converter=converter) while 1: for entry in x.entry: yield entry else: l = x.GetNextLink() if l: # hope the server preserves our preferences x = self.Get(uri=l.href, converter=converter) else: break def get_annotations(self, id='me', start_index="1", max_results="100", min_viewability="none", converter=books.BookFeed.FromString): """ Like get_library, but for the annotation feed """ return self.get_library(id=id, feed=ANNOTATION_FEED, max_results=max_results, min_viewability = min_viewability, converter=converter) gdata-2.0.18/src/gdata/codesearch/0000750036466300116100000000000012156625015016701 5ustar afshareng00000000000000gdata-2.0.18/src/gdata/codesearch/__init__.py0000640036466300116100000001150612156622363021021 0ustar afshareng00000000000000# -*- coding: utf-8 -*- # # Copyright (c) 2007 Benoit Chesneau # # Permission to use, copy, modify, and distribute this software for any # purpose with or without fee is hereby granted, provided that the above # copyright notice and this permission notice appear in all copies. # # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. """Contains extensions to Atom objects used by Google Codesearch""" __author__ = 'Benoit Chesneau' import atom import gdata CODESEARCH_NAMESPACE='http://schemas.google.com/codesearch/2006' CODESEARCH_TEMPLATE='{http://shema.google.com/codesearch/2006}%s' class Match(atom.AtomBase): """ The Google Codesearch match element """ _tag = 'match' _namespace = CODESEARCH_NAMESPACE _children = atom.AtomBase._children.copy() _attributes = atom.AtomBase._attributes.copy() _attributes['lineNumber'] = 'line_number' _attributes['type'] = 'type' def __init__(self, line_number=None, type=None, extension_elements=None, extension_attributes=None, text=None): self.text = text self.type = type self.line_number = line_number self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} class File(atom.AtomBase): """ The Google Codesearch file element""" _tag = 'file' _namespace = CODESEARCH_NAMESPACE _children = atom.AtomBase._children.copy() _attributes = atom.AtomBase._attributes.copy() _attributes['name'] = 'name' def __init__(self, name=None, extension_elements=None, extension_attributes=None, text=None): self.text = text self.name = name self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} class Package(atom.AtomBase): """ The Google Codesearch package element""" _tag = 'package' _namespace = CODESEARCH_NAMESPACE _children = atom.AtomBase._children.copy() _attributes = atom.AtomBase._attributes.copy() _attributes['name'] = 'name' _attributes['uri'] = 'uri' def __init__(self, name=None, uri=None, extension_elements=None, extension_attributes=None, text=None): self.text = text self.name = name self.uri = uri self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} class CodesearchEntry(gdata.GDataEntry): """ Google codesearch atom entry""" _tag = gdata.GDataEntry._tag _namespace = gdata.GDataEntry._namespace _children = gdata.GDataEntry._children.copy() _attributes = gdata.GDataEntry._attributes.copy() _children['{%s}file' % CODESEARCH_NAMESPACE] = ('file', File) _children['{%s}package' % CODESEARCH_NAMESPACE] = ('package', Package) _children['{%s}match' % CODESEARCH_NAMESPACE] = ('match', [Match]) def __init__(self, author=None, category=None, content=None, atom_id=None, link=None, published=None, title=None, updated=None, match=None, extension_elements=None, extension_attributes=None, text=None): gdata.GDataEntry.__init__(self, author=author, category=category, content=content, atom_id=atom_id, link=link, published=published, title=title, updated=updated, text=None) self.match = match or [] def CodesearchEntryFromString(xml_string): """Converts an XML string into a CodesearchEntry object. Args: xml_string: string The XML describing a Codesearch feed entry. Returns: A CodesearchEntry object corresponding to the given XML. """ return atom.CreateClassFromXMLString(CodesearchEntry, xml_string) class CodesearchFeed(gdata.GDataFeed): """feed containing list of Google codesearch Items""" _tag = gdata.GDataFeed._tag _namespace = gdata.GDataFeed._namespace _children = gdata.GDataFeed._children.copy() _attributes = gdata.GDataFeed._attributes.copy() _children['{%s}entry' % atom.ATOM_NAMESPACE] = ('entry', [CodesearchEntry]) def CodesearchFeedFromString(xml_string): """Converts an XML string into a CodesearchFeed object. Args: xml_string: string The XML describing a Codesearch feed. Returns: A CodeseartchFeed object corresponding to the given XML. """ return atom.CreateClassFromXMLString(CodesearchFeed, xml_string) gdata-2.0.18/src/gdata/codesearch/service.py0000640036466300116100000001121512156622363020717 0ustar afshareng00000000000000# -*- coding: utf-8 -*- # # Copyright (c) 2007 Benoit Chesneau # # Permission to use, copy, modify, and distribute this software for any # purpose with or without fee is hereby granted, provided that the above # copyright notice and this permission notice appear in all copies. # # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. """CodesearchService extends GDataService to streamline Google Codesearch operations""" __author__ = 'Benoit Chesneau' import atom import gdata.service import gdata.codesearch class CodesearchService(gdata.service.GDataService): """Client extension for Google codesearch service""" ssl = True def __init__(self, email=None, password=None, source=None, server='www.google.com', additional_headers=None, **kwargs): """Creates a client for the Google codesearch service. Args: email: string (optional) The user's email address, used for authentication. password: string (optional) The user's password. source: string (optional) The name of the user's application. server: string (optional) The name of the server to which a connection will be opened. Default value: 'www.google.com'. **kwargs: The other parameters to pass to gdata.service.GDataService constructor. """ gdata.service.GDataService.__init__( self, email=email, password=password, service='codesearch', source=source, server=server, additional_headers=additional_headers, **kwargs) def Query(self, uri, converter=gdata.codesearch.CodesearchFeedFromString): """Queries the Codesearch feed and returns the resulting feed of entries. Args: uri: string The full URI to be queried. This can contain query parameters, a hostname, or simply the relative path to a Document List feed. The DocumentQuery object is useful when constructing query parameters. converter: func (optional) A function which will be executed on the retrieved item, generally to render it into a Python object. By default the CodesearchFeedFromString function is used to return a CodesearchFeed object. This is because most feed queries will result in a feed and not a single entry. Returns : A CodesearchFeed objects representing the feed returned by the server """ return self.Get(uri, converter=converter) def GetSnippetsFeed(self, text_query=None): """Retrieve Codesearch feed for a keyword Args: text_query : string (optional) The contents of the q query parameter. This string is URL escaped upon conversion to a URI. Returns: A CodesearchFeed objects representing the feed returned by the server """ query=gdata.codesearch.service.CodesearchQuery(text_query=text_query) feed = self.Query(query.ToUri()) return feed class CodesearchQuery(gdata.service.Query): """Object used to construct the query to the Google Codesearch feed. here only as a shorcut""" def __init__(self, feed='/codesearch/feeds/search', text_query=None, params=None, categories=None): """Constructor for Codesearch Query. Args: feed: string (optional) The path for the feed. (e.g. '/codesearch/feeds/search') text_query: string (optional) The contents of the q query parameter. This string is URL escaped upon conversion to a URI. params: dict (optional) Parameter value string pairs which become URL params when translated to a URI. These parameters are added to the query's items. categories: list (optional) List of category strings which should be included as query categories. See gdata.service.Query for additional documentation. Yelds: A CodesearchQuery object to construct a URI based on Codesearch feed """ gdata.service.Query.__init__(self, feed, text_query, params, categories) gdata-2.0.18/src/gdata/test_config.py0000640036466300116100000004267612156622363017502 0ustar afshareng00000000000000#!/usr/bin/env python # Copyright (C) 2009 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. import sys import unittest import getpass import inspect import atom.mock_http_core import gdata.gauth """Loads configuration for tests which connect to Google servers. Settings used in tests are stored in a ConfigCollection instance in this module called options. If your test needs to get a test related setting, use import gdata.test_config option_value = gdata.test_config.options.get_value('x') The above will check the command line for an '--x' argument, and if not found will either use the default value for 'x' or prompt the user to enter one. Your test can override the value specified by the user by performing: gdata.test_config.options.set_value('x', 'y') If your test uses a new option which you would like to allow the user to specify on the command line or via a prompt, you can use the register_option method as follows: gdata.test_config.options.register( 'option_name', 'Prompt shown to the user', secret=False #As for password. 'This is the description of the option, shown when help is requested.', 'default value, provide only if you do not want the user to be prompted') """ class Option(object): def __init__(self, name, prompt, secret=False, description=None, default=None): self.name = name self.prompt = prompt self.secret = secret self.description = description self.default = default def get(self): value = self.default # Check for a command line parameter. for i in xrange(len(sys.argv)): if sys.argv[i].startswith('--%s=' % self.name): value = sys.argv[i].split('=')[1] elif sys.argv[i] == '--%s' % self.name: value = sys.argv[i + 1] # If the param was not on the command line, ask the user to input the # value. # In order for this to prompt the user, the default value for the option # must be None. if value is None: prompt = '%s: ' % self.prompt if self.secret: value = getpass.getpass(prompt) else: print 'You can specify this on the command line using --%s' % self.name value = raw_input(prompt) return value class ConfigCollection(object): def __init__(self, options=None): self.options = options or {} self.values = {} def register_option(self, option): self.options[option.name] = option def register(self, *args, **kwargs): self.register_option(Option(*args, **kwargs)) def get_value(self, option_name): if option_name in self.values: return self.values[option_name] value = self.options[option_name].get() if value is not None: self.values[option_name] = value return value def set_value(self, option_name, value): self.values[option_name] = value def render_usage(self): message_parts = [] for opt_name, option in self.options.iteritems(): message_parts.append('--%s: %s' % (opt_name, option.description)) return '\n'.join(message_parts) options = ConfigCollection() # Register the default options. options.register( 'username', 'Please enter the email address of your test account', description=('The email address you want to sign in with. ' 'Make sure this is a test account as these tests may edit' ' or delete data.')) options.register( 'password', 'Please enter the password for your test account', secret=True, description='The test account password.') options.register( 'clearcache', 'Delete cached data? (enter true or false)', description=('If set to true, any temporary files which cache test' ' requests and responses will be deleted.'), default='true') options.register( 'savecache', 'Save requests and responses in a temporary file? (enter true or false)', description=('If set to true, requests to the server and responses will' ' be saved in temporary files.'), default='false') options.register( 'runlive', 'Run the live tests which contact the server? (enter true or false)', description=('If set to true, the tests will make real HTTP requests to' ' the servers. This slows down test execution and may' ' modify the users data, be sure to use a test account.'), default='true') options.register( 'host', 'Run the live tests against the given host', description='Examples: docs.google.com, spreadsheets.google.com, etc.', default='') options.register( 'ssl', 'Run the live tests over SSL (enter true or false)', description='If set to true, all tests will be performed over HTTPS (SSL)', default='false') options.register( 'clean', 'Clean ALL data first before and after each test (enter true or false)', description='If set to true, all tests will remove all data (DANGEROUS)', default='false') options.register( 'appsusername', 'Please enter the email address of your test Apps domain account', description=('The email address you want to sign in with. ' 'Make sure this is a test account on your Apps domain as ' 'these tests may edit or delete data.')) options.register( 'appspassword', 'Please enter the password for your test Apps domain account', secret=True, description='The test Apps account password.') # Other options which may be used if needed. BLOG_ID_OPTION = Option( 'blogid', 'Please enter the ID of your test blog', description=('The blog ID for the blog which should have test posts added' ' to it. Example 7682659670455539811')) TEST_IMAGE_LOCATION_OPTION = Option( 'imgpath', 'Please enter the full path to a test image to upload', description=('This test image will be uploaded to a service which' ' accepts a media file, it must be a jpeg.')) SPREADSHEET_ID_OPTION = Option( 'spreadsheetid', 'Please enter the ID of a spreadsheet to use in these tests', description=('The spreadsheet ID for the spreadsheet which should be' ' modified by theses tests.')) APPS_DOMAIN_OPTION = Option( 'appsdomain', 'Please enter your Google Apps domain', description=('The domain the Google Apps is hosted on or leave blank' ' if n/a')) SITES_NAME_OPTION = Option( 'sitename', 'Please enter name of your Google Site', description='The webspace name of the Site found in its URL.') PROJECT_NAME_OPTION = Option( 'project_name', 'Please enter the name of your project hosting project', description=('The name of the project which should have test issues added' ' to it. Example gdata-python-client')) ISSUE_ASSIGNEE_OPTION = Option( 'issue_assignee', 'Enter the email address of the target owner of the updated issue.', description=('The email address of the user a created issue\'s owner will ' ' become. Example testuser2@gmail.com')) GA_TABLE_ID = Option( 'table_id', 'Enter the Table ID of the Google Analytics profile to test', description=('The Table ID of the Google Analytics profile to test.' ' Example ga:1174')) TARGET_USERNAME_OPTION = Option( 'targetusername', 'Please enter the username (without domain) of the user which will be' ' affected by the tests', description=('The username of the user to be tested')) YT_DEVELOPER_KEY_OPTION = Option( 'developerkey', 'Please enter your YouTube developer key', description=('The YouTube developer key for your account')) YT_CLIENT_ID_OPTION = Option( 'clientid', 'Please enter your YouTube client ID', description=('The YouTube client ID for your account')) YT_VIDEO_ID_OPTION= Option( 'videoid', 'Please enter the ID of a YouTube video you uploaded', description=('The video ID of a YouTube video uploaded to your account')) # Functions to inject a cachable HTTP client into a service client. def configure_client(client, case_name, service_name, use_apps_auth=False): """Sets up a mock client which will reuse a saved session. Should be called during setUp of each unit test. Handles authentication to allow the GDClient to make requests which require an auth header. Args: client: a gdata.GDClient whose http_client member should be replaced with a atom.mock_http_core.MockHttpClient so that repeated executions can used cached responses instead of contacting the server. case_name: str The name of the test case class. Examples: 'BloggerTest', 'ContactsTest'. Used to save a session for the ClientLogin auth token request, so the case_name should be reused if and only if the same username, password, and service are being used. service_name: str The service name as used for ClientLogin to identify the Google Data API being accessed. Example: 'blogger', 'wise', etc. use_apps_auth: bool (optional) If set to True, use appsusername and appspassword command-line args instead of username and password respectively. """ # Use a mock HTTP client which will record and replay the HTTP traffic # from these tests. client.http_client = atom.mock_http_core.MockHttpClient() client.http_client.cache_case_name = case_name # Getting the auth token only needs to be done once in the course of test # runs. auth_token_key = '%s_auth_token' % service_name if (auth_token_key not in options.values and options.get_value('runlive') == 'true'): client.http_client.cache_test_name = 'client_login' cache_name = client.http_client.get_cache_file_name() if options.get_value('clearcache') == 'true': client.http_client.delete_session(cache_name) client.http_client.use_cached_session(cache_name) if not use_apps_auth: username = options.get_value('username') password = options.get_value('password') else: username = options.get_value('appsusername') password = options.get_value('appspassword') auth_token = client.client_login(username, password, case_name, service=service_name) options.values[auth_token_key] = gdata.gauth.token_to_blob(auth_token) if client.alt_auth_service is not None: options.values[client.alt_auth_service] = gdata.gauth.token_to_blob( client.alt_auth_token) client.http_client.close_session() # Allow a config auth_token of False to prevent the client's auth header # from being modified. if auth_token_key in options.values: client.auth_token = gdata.gauth.token_from_blob( options.values[auth_token_key]) if client.alt_auth_service is not None: client.alt_auth_token = gdata.gauth.token_from_blob( options.values[client.alt_auth_service]) if options.get_value('host'): client.host = options.get_value('host') def configure_cache(client, test_name): """Loads or begins a cached session to record HTTP traffic. Should be called at the beginning of each test method. Args: client: a gdata.GDClient whose http_client member has been replaced with a atom.mock_http_core.MockHttpClient so that repeated executions can used cached responses instead of contacting the server. test_name: str The name of this test method. Examples: 'TestClass.test_x_works', 'TestClass.test_crud_operations'. This is used to name the recording of the HTTP requests and responses, so it should be unique to each test method in the test case. """ # Auth token is obtained in configure_client which is called as part of # setUp. client.http_client.cache_test_name = test_name cache_name = client.http_client.get_cache_file_name() if options.get_value('clearcache') == 'true': client.http_client.delete_session(cache_name) client.http_client.use_cached_session(cache_name) def close_client(client): """Saves the recoded responses to a temp file if the config file allows. This should be called in the unit test's tearDown method. Checks to see if the 'savecache' option is set to 'true', to make sure we only save sessions to repeat if the user desires. """ if client and options.get_value('savecache') == 'true': # If this was a live request, save the recording. client.http_client.close_session() def configure_service(service, case_name, service_name): """Sets up a mock GDataService v1 client to reuse recorded sessions. Should be called during setUp of each unit test. This is a duplicate of configure_client, modified to handle old v1 service classes. """ service.http_client.v2_http_client = atom.mock_http_core.MockHttpClient() service.http_client.v2_http_client.cache_case_name = case_name # Getting the auth token only needs to be done once in the course of test # runs. auth_token_key = 'service_%s_auth_token' % service_name if (auth_token_key not in options.values and options.get_value('runlive') == 'true'): service.http_client.v2_http_client.cache_test_name = 'client_login' cache_name = service.http_client.v2_http_client.get_cache_file_name() if options.get_value('clearcache') == 'true': service.http_client.v2_http_client.delete_session(cache_name) service.http_client.v2_http_client.use_cached_session(cache_name) service.ClientLogin(options.get_value('username'), options.get_value('password'), service=service_name, source=case_name) options.values[auth_token_key] = service.GetClientLoginToken() service.http_client.v2_http_client.close_session() if auth_token_key in options.values: service.SetClientLoginToken(options.values[auth_token_key]) def configure_service_cache(service, test_name): """Loads or starts a session recording for a v1 Service object. Duplicates the behavior of configure_cache, but the target for this function is a v1 Service object instead of a v2 Client. """ service.http_client.v2_http_client.cache_test_name = test_name cache_name = service.http_client.v2_http_client.get_cache_file_name() if options.get_value('clearcache') == 'true': service.http_client.v2_http_client.delete_session(cache_name) service.http_client.v2_http_client.use_cached_session(cache_name) def close_service(service): if service and options.get_value('savecache') == 'true': # If this was a live request, save the recording. service.http_client.v2_http_client.close_session() def build_suite(classes): """Creates a TestSuite for all unit test classes in the list. Assumes that each of the classes in the list has unit test methods which begin with 'test'. Calls unittest.makeSuite. Returns: A new unittest.TestSuite containing a test suite for all classes. """ suites = [unittest.makeSuite(a_class, 'test') for a_class in classes] return unittest.TestSuite(suites) def check_data_classes(test, classes): import inspect for data_class in classes: test.assert_(data_class.__doc__ is not None, 'The class %s should have a docstring' % data_class) if hasattr(data_class, '_qname'): qname_versions = None if isinstance(data_class._qname, tuple): qname_versions = data_class._qname else: qname_versions = (data_class._qname,) for versioned_qname in qname_versions: test.assert_(isinstance(versioned_qname, str), 'The class %s has a non-string _qname' % data_class) test.assert_(not versioned_qname.endswith('}'), 'The _qname for class %s is only a namespace' % ( data_class)) for attribute_name, value in data_class.__dict__.iteritems(): # Ignore all elements that start with _ (private members) if not attribute_name.startswith('_'): try: if not (isinstance(value, str) or inspect.isfunction(value) or (isinstance(value, list) and issubclass(value[0], atom.core.XmlElement)) or type(value) == property # Allow properties. or inspect.ismethod(value) # Allow methods. or inspect.ismethoddescriptor(value) # Allow method descriptors. # staticmethod et al. or issubclass(value, atom.core.XmlElement)): test.fail( 'XmlElement member should have an attribute, XML class,' ' or list of XML classes as attributes.') except TypeError: test.fail('Element %s in %s was of type %s' % ( attribute_name, data_class._qname, type(value))) def check_clients_with_auth(test, classes): for client_class in classes: test.assert_(hasattr(client_class, 'api_version')) test.assert_(isinstance(client_class.auth_service, (str, unicode, int))) test.assert_(hasattr(client_class, 'auth_service')) test.assert_(isinstance(client_class.auth_service, (str, unicode))) test.assert_(hasattr(client_class, 'auth_scopes')) test.assert_(isinstance(client_class.auth_scopes, (list, tuple))) gdata-2.0.18/src/gdata/gauth.py0000640036466300116100000017762312156622363016307 0ustar afshareng00000000000000#!/usr/bin/env python # # Copyright (C) 2009 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. # This module is used for version 2 of the Google Data APIs. """Provides auth related token classes and functions for Google Data APIs. Token classes represent a user's authorization of this app to access their data. Usually these are not created directly but by a GDClient object. ClientLoginToken AuthSubToken SecureAuthSubToken OAuthHmacToken OAuthRsaToken TwoLeggedOAuthHmacToken TwoLeggedOAuthRsaToken Functions which are often used in application code (as opposed to just within the gdata-python-client library) are the following: generate_auth_sub_url authorize_request_token The following are helper functions which are used to save and load auth token objects in the App Engine datastore. These should only be used if you are using this library within App Engine: ae_load ae_save """ import datetime import time import random import urllib import urlparse import atom.http_core try: import simplejson from simplejson.decoder import JSONDecodeError except ImportError: JSONDecodeError = None try: # Try to import from django, should work on App Engine from django.utils import simplejson except ImportError: # Should work for Python2.6 and higher. import json as simplejson try: from urlparse import parse_qsl except ImportError: from cgi import parse_qsl __author__ = 'j.s@google.com (Jeff Scudder)' PROGRAMMATIC_AUTH_LABEL = 'GoogleLogin auth=' AUTHSUB_AUTH_LABEL = 'AuthSub token=' OAUTH2_AUTH_LABEL = 'Bearer ' # This dict provides the AuthSub and OAuth scopes for all services by service # name. The service name (key) is used in ClientLogin requests. AUTH_SCOPES = { 'cl': ( # Google Calendar API 'https://www.google.com/calendar/feeds/', 'http://www.google.com/calendar/feeds/'), 'gbase': ( # Google Base API 'http://base.google.com/base/feeds/', 'http://www.google.com/base/feeds/'), 'blogger': ( # Blogger API 'http://www.blogger.com/feeds/',), 'codesearch': ( # Google Code Search API 'http://www.google.com/codesearch/feeds/',), 'cp': ( # Contacts API 'https://www.google.com/m8/feeds/', 'http://www.google.com/m8/feeds/'), 'finance': ( # Google Finance API 'http://finance.google.com/finance/feeds/',), 'health': ( # Google Health API 'https://www.google.com/health/feeds/',), 'writely': ( # Documents List API 'https://docs.google.com/feeds/', 'https://spreadsheets.google.com/feeds/', 'https://docs.googleusercontent.com/'), 'lh2': ( # Picasa Web Albums API 'http://picasaweb.google.com/data/',), 'apps': ( # Google Apps Domain Info & Management APIs 'https://apps-apis.google.com/a/feeds/user/', 'https://apps-apis.google.com/a/feeds/policies/', 'https://apps-apis.google.com/a/feeds/alias/', 'https://apps-apis.google.com/a/feeds/groups/', 'https://apps-apis.google.com/a/feeds/compliance/audit/', 'https://apps-apis.google.com/a/feeds/migration/', 'https://apps-apis.google.com/a/feeds/emailsettings/2.0/'), 'weaver': ( # Health H9 Sandbox 'https://www.google.com/h9/feeds/',), 'wise': ( # Spreadsheets Data API 'https://spreadsheets.google.com/feeds/',), 'sitemaps': ( # Google Webmaster Tools API 'https://www.google.com/webmasters/tools/feeds/',), 'youtube': ( # YouTube API 'http://gdata.youtube.com/feeds/api/', 'http://uploads.gdata.youtube.com/feeds/api', 'http://gdata.youtube.com/action/GetUploadToken'), 'books': ( # Google Books API 'http://www.google.com/books/feeds/',), 'analytics': ( # Google Analytics API 'https://www.google.com/analytics/feeds/',), 'jotspot': ( # Google Sites API 'http://sites.google.com/feeds/', 'https://sites.google.com/feeds/'), 'local': ( # Google Maps Data API 'http://maps.google.com/maps/feeds/',), 'code': ( # Project Hosting Data API 'http://code.google.com/feeds/issues',)} class Error(Exception): pass class UnsupportedTokenType(Error): """Raised when token to or from blob is unable to convert the token.""" pass class OAuth2AccessTokenError(Error): """Raised when an OAuth2 error occurs.""" def __init__(self, error_message): self.error_message = error_message class OAuth2RevokeError(Error): """Raised when an OAuth2 token revocation was unsuccessful.""" def __init__(self, http_response, response_body=None): """Sets the HTTP information in the error. Args: http_response: The response from the server, contains error information. response_body: string (optional) specified if the response has already been read from the http_response object. """ body = response_body or http_response.read() self.status = http_response.status self.reason = http_response.reason self.body = body self.headers = atom.http_core.get_headers(http_response) self.error_msg = 'Invalid response %s.' % self.status try: json_from_body = simplejson.loads(body) if isinstance(json_from_body, dict): self.error_msg = json_from_body.get('error', self.error_msg) except (ValueError, JSONDecodeError): pass def __str__(self): return 'OAuth2RevokeError(status=%i, error=%s)' % (self.status, self.error_msg) # ClientLogin functions and classes. def generate_client_login_request_body(email, password, service, source, account_type='HOSTED_OR_GOOGLE', captcha_token=None, captcha_response=None): """Creates the body of the autentication request See http://code.google.com/apis/accounts/AuthForInstalledApps.html#Request for more details. Args: email: str password: str service: str source: str account_type: str (optional) Defaul is 'HOSTED_OR_GOOGLE', other valid values are 'GOOGLE' and 'HOSTED' captcha_token: str (optional) captcha_response: str (optional) Returns: The HTTP body to send in a request for a client login token. """ # Create a POST body containing the user's credentials. request_fields = {'Email': email, 'Passwd': password, 'accountType': account_type, 'service': service, 'source': source} if captcha_token and captcha_response: # Send the captcha token and response as part of the POST body if the # user is responding to a captch challenge. request_fields['logintoken'] = captcha_token request_fields['logincaptcha'] = captcha_response return urllib.urlencode(request_fields) GenerateClientLoginRequestBody = generate_client_login_request_body def get_client_login_token_string(http_body): """Returns the token value for a ClientLoginToken. Reads the token from the server's response to a Client Login request and creates the token value string to use in requests. Args: http_body: str The body of the server's HTTP response to a Client Login request Returns: The token value string for a ClientLoginToken. """ for response_line in http_body.splitlines(): if response_line.startswith('Auth='): # Strip off the leading Auth= and return the Authorization value. return response_line[5:] return None GetClientLoginTokenString = get_client_login_token_string def get_captcha_challenge(http_body, captcha_base_url='http://www.google.com/accounts/'): """Returns the URL and token for a CAPTCHA challenge issued by the server. Args: http_body: str The body of the HTTP response from the server which contains the CAPTCHA challenge. captcha_base_url: str This function returns a full URL for viewing the challenge image which is built from the server's response. This base_url is used as the beginning of the URL because the server only provides the end of the URL. For example the server provides 'Captcha?ctoken=Hi...N' and the URL for the image is 'http://www.google.com/accounts/Captcha?ctoken=Hi...N' Returns: A dictionary containing the information needed to repond to the CAPTCHA challenge, the image URL and the ID token of the challenge. The dictionary is in the form: {'token': string identifying the CAPTCHA image, 'url': string containing the URL of the image} Returns None if there was no CAPTCHA challenge in the response. """ contains_captcha_challenge = False captcha_parameters = {} for response_line in http_body.splitlines(): if response_line.startswith('Error=CaptchaRequired'): contains_captcha_challenge = True elif response_line.startswith('CaptchaToken='): # Strip off the leading CaptchaToken= captcha_parameters['token'] = response_line[13:] elif response_line.startswith('CaptchaUrl='): captcha_parameters['url'] = '%s%s' % (captcha_base_url, response_line[11:]) if contains_captcha_challenge: return captcha_parameters else: return None GetCaptchaChallenge = get_captcha_challenge class ClientLoginToken(object): def __init__(self, token_string): self.token_string = token_string def modify_request(self, http_request): http_request.headers['Authorization'] = '%s%s' % (PROGRAMMATIC_AUTH_LABEL, self.token_string) ModifyRequest = modify_request # AuthSub functions and classes. def _to_uri(str_or_uri): if isinstance(str_or_uri, (str, unicode)): return atom.http_core.Uri.parse_uri(str_or_uri) return str_or_uri def generate_auth_sub_url(next, scopes, secure=False, session=True, request_url=atom.http_core.parse_uri( 'https://www.google.com/accounts/AuthSubRequest'), domain='default', scopes_param_prefix='auth_sub_scopes'): """Constructs a URI for requesting a multiscope AuthSub token. The generated token will contain a URL parameter to pass along the requested scopes to the next URL. When the Google Accounts page redirects the broswser to the 'next' URL, it appends the single use AuthSub token value to the URL as a URL parameter with the key 'token'. However, the information about which scopes were requested is not included by Google Accounts. This method adds the scopes to the next URL before making the request so that the redirect will be sent to a page, and both the token value and the list of scopes for which the token was requested. Args: next: atom.http_core.Uri or string The URL user will be sent to after authorizing this web application to access their data. scopes: list containint strings or atom.http_core.Uri objects. The URLs of the services to be accessed. Could also be a single string or single atom.http_core.Uri for requesting just one scope. secure: boolean (optional) Determines whether or not the issued token is a secure token. session: boolean (optional) Determines whether or not the issued token can be upgraded to a session token. request_url: atom.http_core.Uri or str The beginning of the request URL. This is normally 'http://www.google.com/accounts/AuthSubRequest' or '/accounts/AuthSubRequest' domain: The domain which the account is part of. This is used for Google Apps accounts, the default value is 'default' which means that the requested account is a Google Account (@gmail.com for example) scopes_param_prefix: str (optional) The requested scopes are added as a URL parameter to the next URL so that the page at the 'next' URL can extract the token value and the valid scopes from the URL. The key for the URL parameter defaults to 'auth_sub_scopes' Returns: An atom.http_core.Uri which the user's browser should be directed to in order to authorize this application to access their information. """ if isinstance(next, (str, unicode)): next = atom.http_core.Uri.parse_uri(next) # If the user passed in a string instead of a list for scopes, convert to # a single item tuple. if isinstance(scopes, (str, unicode, atom.http_core.Uri)): scopes = (scopes,) scopes_string = ' '.join([str(scope) for scope in scopes]) next.query[scopes_param_prefix] = scopes_string if isinstance(request_url, (str, unicode)): request_url = atom.http_core.Uri.parse_uri(request_url) request_url.query['next'] = str(next) request_url.query['scope'] = scopes_string if session: request_url.query['session'] = '1' else: request_url.query['session'] = '0' if secure: request_url.query['secure'] = '1' else: request_url.query['secure'] = '0' request_url.query['hd'] = domain return request_url def auth_sub_string_from_url(url, scopes_param_prefix='auth_sub_scopes'): """Finds the token string (and scopes) after the browser is redirected. After the Google Accounts AuthSub pages redirect the user's broswer back to the web application (using the 'next' URL from the request) the web app must extract the token from the current page's URL. The token is provided as a URL parameter named 'token' and if generate_auth_sub_url was used to create the request, the token's valid scopes are included in a URL parameter whose name is specified in scopes_param_prefix. Args: url: atom.url.Url or str representing the current URL. The token value and valid scopes should be included as URL parameters. scopes_param_prefix: str (optional) The URL parameter key which maps to the list of valid scopes for the token. Returns: A tuple containing the token value as a string, and a tuple of scopes (as atom.http_core.Uri objects) which are URL prefixes under which this token grants permission to read and write user data. (token_string, (scope_uri, scope_uri, scope_uri, ...)) If no scopes were included in the URL, the second value in the tuple is None. If there was no token param in the url, the tuple returned is (None, None) """ if isinstance(url, (str, unicode)): url = atom.http_core.Uri.parse_uri(url) if 'token' not in url.query: return (None, None) token = url.query['token'] # TODO: decide whether no scopes should be None or (). scopes = None # Default to None for no scopes. if scopes_param_prefix in url.query: scopes = tuple(url.query[scopes_param_prefix].split(' ')) return (token, scopes) AuthSubStringFromUrl = auth_sub_string_from_url def auth_sub_string_from_body(http_body): """Extracts the AuthSub token from an HTTP body string. Used to find the new session token after making a request to upgrade a single use AuthSub token. Args: http_body: str The repsonse from the server which contains the AuthSub key. For example, this function would find the new session token from the server's response to an upgrade token request. Returns: The raw token value string to use in an AuthSubToken object. """ for response_line in http_body.splitlines(): if response_line.startswith('Token='): # Strip off Token= and return the token value string. return response_line[6:] return None class AuthSubToken(object): def __init__(self, token_string, scopes=None): self.token_string = token_string self.scopes = scopes or [] def modify_request(self, http_request): """Sets Authorization header, allows app to act on the user's behalf.""" http_request.headers['Authorization'] = '%s%s' % (AUTHSUB_AUTH_LABEL, self.token_string) ModifyRequest = modify_request def from_url(str_or_uri): """Creates a new AuthSubToken using information in the URL. Uses auth_sub_string_from_url. Args: str_or_uri: The current page's URL (as a str or atom.http_core.Uri) which should contain a token query parameter since the Google auth server redirected the user's browser to this URL. """ token_and_scopes = auth_sub_string_from_url(str_or_uri) return AuthSubToken(token_and_scopes[0], token_and_scopes[1]) from_url = staticmethod(from_url) FromUrl = from_url def _upgrade_token(self, http_body): """Replaces the token value with a session token from the auth server. Uses the response of a token upgrade request to modify this token. Uses auth_sub_string_from_body. """ self.token_string = auth_sub_string_from_body(http_body) # Functions and classes for Secure-mode AuthSub def build_auth_sub_data(http_request, timestamp, nonce): """Creates the data string which must be RSA-signed in secure requests. For more details see the documenation on secure AuthSub requests: http://code.google.com/apis/accounts/docs/AuthSub.html#signingrequests Args: http_request: The request being made to the server. The Request's URL must be complete before this signature is calculated as any changes to the URL will invalidate the signature. nonce: str Random 64-bit, unsigned number encoded as an ASCII string in decimal format. The nonce/timestamp pair should always be unique to prevent replay attacks. timestamp: Integer representing the time the request is sent. The timestamp should be expressed in number of seconds after January 1, 1970 00:00:00 GMT. """ return '%s %s %s %s' % (http_request.method, str(http_request.uri), str(timestamp), nonce) def generate_signature(data, rsa_key): """Signs the data string for a secure AuthSub request.""" import base64 try: from tlslite.utils import keyfactory except ImportError: try: from gdata.tlslite.utils import keyfactory except ImportError: from tlslite.tlslite.utils import keyfactory private_key = keyfactory.parsePrivateKey(rsa_key) signed = private_key.hashAndSign(data) # Python2.3 and lower does not have the base64.b64encode function. if hasattr(base64, 'b64encode'): return base64.b64encode(signed) else: return base64.encodestring(signed).replace('\n', '') class SecureAuthSubToken(AuthSubToken): def __init__(self, token_string, rsa_private_key, scopes=None): self.token_string = token_string self.scopes = scopes or [] self.rsa_private_key = rsa_private_key def from_url(str_or_uri, rsa_private_key): """Creates a new SecureAuthSubToken using information in the URL. Uses auth_sub_string_from_url. Args: str_or_uri: The current page's URL (as a str or atom.http_core.Uri) which should contain a token query parameter since the Google auth server redirected the user's browser to this URL. rsa_private_key: str the private RSA key cert used to sign all requests made with this token. """ token_and_scopes = auth_sub_string_from_url(str_or_uri) return SecureAuthSubToken(token_and_scopes[0], rsa_private_key, token_and_scopes[1]) from_url = staticmethod(from_url) FromUrl = from_url def modify_request(self, http_request): """Sets the Authorization header and includes a digital signature. Calculates a digital signature using the private RSA key, a timestamp (uses now at the time this method is called) and a random nonce. Args: http_request: The atom.http_core.HttpRequest which contains all of the information needed to send a request to the remote server. The URL and the method of the request must be already set and cannot be changed after this token signs the request, or the signature will not be valid. """ timestamp = str(int(time.time())) nonce = ''.join([str(random.randint(0, 9)) for i in xrange(15)]) data = build_auth_sub_data(http_request, timestamp, nonce) signature = generate_signature(data, self.rsa_private_key) http_request.headers['Authorization'] = ( '%s%s sigalg="rsa-sha1" data="%s" sig="%s"' % (AUTHSUB_AUTH_LABEL, self.token_string, data, signature)) ModifyRequest = modify_request # OAuth functions and classes. RSA_SHA1 = 'RSA-SHA1' HMAC_SHA1 = 'HMAC-SHA1' def build_oauth_base_string(http_request, consumer_key, nonce, signaure_type, timestamp, version, next='oob', token=None, verifier=None): """Generates the base string to be signed in the OAuth request. Args: http_request: The request being made to the server. The Request's URL must be complete before this signature is calculated as any changes to the URL will invalidate the signature. consumer_key: Domain identifying the third-party web application. This is the domain used when registering the application with Google. It identifies who is making the request on behalf of the user. nonce: Random 64-bit, unsigned number encoded as an ASCII string in decimal format. The nonce/timestamp pair should always be unique to prevent replay attacks. signaure_type: either RSA_SHA1 or HMAC_SHA1 timestamp: Integer representing the time the request is sent. The timestamp should be expressed in number of seconds after January 1, 1970 00:00:00 GMT. version: The OAuth version used by the requesting web application. This value must be '1.0' or '1.0a'. If not provided, Google assumes version 1.0 is in use. next: The URL the user should be redirected to after granting access to a Google service(s). It can include url-encoded query parameters. The default value is 'oob'. (This is the oauth_callback.) token: The string for the OAuth request token or OAuth access token. verifier: str Sent as the oauth_verifier and required when upgrading a request token to an access token. """ # First we must build the canonical base string for the request. params = http_request.uri.query.copy() params['oauth_consumer_key'] = consumer_key params['oauth_nonce'] = nonce params['oauth_signature_method'] = signaure_type params['oauth_timestamp'] = str(timestamp) if next is not None: params['oauth_callback'] = str(next) if token is not None: params['oauth_token'] = token if version is not None: params['oauth_version'] = version if verifier is not None: params['oauth_verifier'] = verifier # We need to get the key value pairs in lexigraphically sorted order. sorted_keys = None try: sorted_keys = sorted(params.keys()) # The sorted function is not available in Python2.3 and lower except NameError: sorted_keys = params.keys() sorted_keys.sort() pairs = [] for key in sorted_keys: pairs.append('%s=%s' % (urllib.quote(key, safe='~'), urllib.quote(params[key], safe='~'))) # We want to escape /'s too, so use safe='~' all_parameters = urllib.quote('&'.join(pairs), safe='~') normailzed_host = http_request.uri.host.lower() normalized_scheme = (http_request.uri.scheme or 'http').lower() non_default_port = None if (http_request.uri.port is not None and ((normalized_scheme == 'https' and http_request.uri.port != 443) or (normalized_scheme == 'http' and http_request.uri.port != 80))): non_default_port = http_request.uri.port path = http_request.uri.path or '/' request_path = None if not path.startswith('/'): path = '/%s' % path if non_default_port is not None: # Set the only safe char in url encoding to ~ since we want to escape / # as well. request_path = urllib.quote('%s://%s:%s%s' % ( normalized_scheme, normailzed_host, non_default_port, path), safe='~') else: # Set the only safe char in url encoding to ~ since we want to escape / # as well. request_path = urllib.quote('%s://%s%s' % ( normalized_scheme, normailzed_host, path), safe='~') # TODO: ensure that token escaping logic is correct, not sure if the token # value should be double escaped instead of single. base_string = '&'.join((http_request.method.upper(), request_path, all_parameters)) # Now we have the base string, we can calculate the oauth_signature. return base_string def generate_hmac_signature(http_request, consumer_key, consumer_secret, timestamp, nonce, version, next='oob', token=None, token_secret=None, verifier=None): import hmac import base64 base_string = build_oauth_base_string( http_request, consumer_key, nonce, HMAC_SHA1, timestamp, version, next, token, verifier=verifier) hash_key = None hashed = None if token_secret is not None: hash_key = '%s&%s' % (urllib.quote(consumer_secret, safe='~'), urllib.quote(token_secret, safe='~')) else: hash_key = '%s&' % urllib.quote(consumer_secret, safe='~') try: import hashlib hashed = hmac.new(hash_key, base_string, hashlib.sha1) except ImportError: import sha hashed = hmac.new(hash_key, base_string, sha) # Python2.3 does not have base64.b64encode. if hasattr(base64, 'b64encode'): return base64.b64encode(hashed.digest()) else: return base64.encodestring(hashed.digest()).replace('\n', '') def generate_rsa_signature(http_request, consumer_key, rsa_key, timestamp, nonce, version, next='oob', token=None, token_secret=None, verifier=None): import base64 try: from tlslite.utils import keyfactory except ImportError: try: from gdata.tlslite.utils import keyfactory except ImportError: from tlslite.tlslite.utils import keyfactory base_string = build_oauth_base_string( http_request, consumer_key, nonce, RSA_SHA1, timestamp, version, next, token, verifier=verifier) private_key = keyfactory.parsePrivateKey(rsa_key) # Sign using the key signed = private_key.hashAndSign(base_string) # Python2.3 does not have base64.b64encode. if hasattr(base64, 'b64encode'): return base64.b64encode(signed) else: return base64.encodestring(signed).replace('\n', '') def generate_auth_header(consumer_key, timestamp, nonce, signature_type, signature, version='1.0', next=None, token=None, verifier=None): """Builds the Authorization header to be sent in the request. Args: consumer_key: Identifies the application making the request (str). timestamp: nonce: signature_type: One of either HMAC_SHA1 or RSA_SHA1 signature: The HMAC or RSA signature for the request as a base64 encoded string. version: The version of the OAuth protocol that this request is using. Default is '1.0' next: The URL of the page that the user's browser should be sent to after they authorize the token. (Optional) token: str The OAuth token value to be used in the oauth_token parameter of the header. verifier: str The OAuth verifier which must be included when you are upgrading a request token to an access token. """ params = { 'oauth_consumer_key': consumer_key, 'oauth_version': version, 'oauth_nonce': nonce, 'oauth_timestamp': str(timestamp), 'oauth_signature_method': signature_type, 'oauth_signature': signature} if next is not None: params['oauth_callback'] = str(next) if token is not None: params['oauth_token'] = token if verifier is not None: params['oauth_verifier'] = verifier pairs = [ '%s="%s"' % ( k, urllib.quote(v, safe='~')) for k, v in params.iteritems()] return 'OAuth %s' % (', '.join(pairs)) REQUEST_TOKEN_URL = 'https://www.google.com/accounts/OAuthGetRequestToken' ACCESS_TOKEN_URL = 'https://www.google.com/accounts/OAuthGetAccessToken' def generate_request_for_request_token( consumer_key, signature_type, scopes, rsa_key=None, consumer_secret=None, auth_server_url=REQUEST_TOKEN_URL, next='oob', version='1.0'): """Creates request to be sent to auth server to get an OAuth request token. Args: consumer_key: signature_type: either RSA_SHA1 or HMAC_SHA1. The rsa_key must be provided if the signature type is RSA but if the signature method is HMAC, the consumer_secret must be used. scopes: List of URL prefixes for the data which we want to access. For example, to request access to the user's Blogger and Google Calendar data, we would request ['http://www.blogger.com/feeds/', 'https://www.google.com/calendar/feeds/', 'http://www.google.com/calendar/feeds/'] rsa_key: Only used if the signature method is RSA_SHA1. consumer_secret: Only used if the signature method is HMAC_SHA1. auth_server_url: The URL to which the token request should be directed. Defaults to 'https://www.google.com/accounts/OAuthGetRequestToken'. next: The URL of the page that the user's browser should be sent to after they authorize the token. (Optional) version: The OAuth version used by the requesting web application. Defaults to '1.0a' Returns: An atom.http_core.HttpRequest object with the URL, Authorization header and body filled in. """ request = atom.http_core.HttpRequest(auth_server_url, 'POST') # Add the requested auth scopes to the Auth request URL. if scopes: request.uri.query['scope'] = ' '.join(scopes) timestamp = str(int(time.time())) nonce = ''.join([str(random.randint(0, 9)) for i in xrange(15)]) signature = None if signature_type == HMAC_SHA1: signature = generate_hmac_signature( request, consumer_key, consumer_secret, timestamp, nonce, version, next=next) elif signature_type == RSA_SHA1: signature = generate_rsa_signature( request, consumer_key, rsa_key, timestamp, nonce, version, next=next) else: return None request.headers['Authorization'] = generate_auth_header( consumer_key, timestamp, nonce, signature_type, signature, version, next) request.headers['Content-Length'] = '0' return request def generate_request_for_access_token( request_token, auth_server_url=ACCESS_TOKEN_URL): """Creates a request to ask the OAuth server for an access token. Requires a request token which the user has authorized. See the documentation on OAuth with Google Data for more details: http://code.google.com/apis/accounts/docs/OAuth.html#AccessToken Args: request_token: An OAuthHmacToken or OAuthRsaToken which the user has approved using their browser. auth_server_url: (optional) The URL at which the OAuth access token is requested. Defaults to https://www.google.com/accounts/OAuthGetAccessToken Returns: A new HttpRequest object which can be sent to the OAuth server to request an OAuth Access Token. """ http_request = atom.http_core.HttpRequest(auth_server_url, 'POST') http_request.headers['Content-Length'] = '0' return request_token.modify_request(http_request) def oauth_token_info_from_body(http_body): """Exracts an OAuth request token from the server's response. Returns: A tuple of strings containing the OAuth token and token secret. If neither of these are present in the body, returns (None, None) """ token = None token_secret = None for pair in http_body.split('&'): if pair.startswith('oauth_token='): token = urllib.unquote(pair[len('oauth_token='):]) if pair.startswith('oauth_token_secret='): token_secret = urllib.unquote(pair[len('oauth_token_secret='):]) return (token, token_secret) def hmac_token_from_body(http_body, consumer_key, consumer_secret, auth_state): token_value, token_secret = oauth_token_info_from_body(http_body) token = OAuthHmacToken(consumer_key, consumer_secret, token_value, token_secret, auth_state) return token def rsa_token_from_body(http_body, consumer_key, rsa_private_key, auth_state): token_value, token_secret = oauth_token_info_from_body(http_body) token = OAuthRsaToken(consumer_key, rsa_private_key, token_value, token_secret, auth_state) return token DEFAULT_DOMAIN = 'default' OAUTH_AUTHORIZE_URL = 'https://www.google.com/accounts/OAuthAuthorizeToken' def generate_oauth_authorization_url( token, next=None, hd=DEFAULT_DOMAIN, hl=None, btmpl=None, auth_server=OAUTH_AUTHORIZE_URL): """Creates a URL for the page where the request token can be authorized. Args: token: str The request token from the OAuth server. next: str (optional) URL the user should be redirected to after granting access to a Google service(s). It can include url-encoded query parameters. hd: str (optional) Identifies a particular hosted domain account to be accessed (for example, 'mycollege.edu'). Uses 'default' to specify a regular Google account ('username@gmail.com'). hl: str (optional) An ISO 639 country code identifying what language the approval page should be translated in (for example, 'hl=en' for English). The default is the user's selected language. btmpl: str (optional) Forces a mobile version of the approval page. The only accepted value is 'mobile'. auth_server: str (optional) The start of the token authorization web page. Defaults to 'https://www.google.com/accounts/OAuthAuthorizeToken' Returns: An atom.http_core.Uri pointing to the token authorization page where the user may allow or deny this app to access their Google data. """ uri = atom.http_core.Uri.parse_uri(auth_server) uri.query['oauth_token'] = token uri.query['hd'] = hd if next is not None: uri.query['oauth_callback'] = str(next) if hl is not None: uri.query['hl'] = hl if btmpl is not None: uri.query['btmpl'] = btmpl return uri def oauth_token_info_from_url(url): """Exracts an OAuth access token from the redirected page's URL. Returns: A tuple of strings containing the OAuth token and the OAuth verifier which need to sent when upgrading a request token to an access token. """ if isinstance(url, (str, unicode)): url = atom.http_core.Uri.parse_uri(url) token = None verifier = None if 'oauth_token' in url.query: token = urllib.unquote(url.query['oauth_token']) if 'oauth_verifier' in url.query: verifier = urllib.unquote(url.query['oauth_verifier']) return (token, verifier) def authorize_request_token(request_token, url): """Adds information to request token to allow it to become an access token. Modifies the request_token object passed in by setting and unsetting the necessary fields to allow this token to form a valid upgrade request. Args: request_token: The OAuth request token which has been authorized by the user. In order for this token to be upgraded to an access token, certain fields must be extracted from the URL and added to the token so that they can be passed in an upgrade-token request. url: The URL of the current page which the user's browser was redirected to after they authorized access for the app. This function extracts information from the URL which is needed to upgraded the token from a request token to an access token. Returns: The same token object which was passed in. """ token, verifier = oauth_token_info_from_url(url) request_token.token = token request_token.verifier = verifier request_token.auth_state = AUTHORIZED_REQUEST_TOKEN return request_token AuthorizeRequestToken = authorize_request_token def upgrade_to_access_token(request_token, server_response_body): """Extracts access token information from response to an upgrade request. Once the server has responded with the new token info for the OAuth access token, this method modifies the request_token to set and unset necessary fields to create valid OAuth authorization headers for requests. Args: request_token: An OAuth token which this function modifies to allow it to be used as an access token. server_response_body: str The server's response to an OAuthAuthorizeToken request. This should contain the new token and token_secret which are used to generate the signature and parameters of the Authorization header in subsequent requests to Google Data APIs. Returns: The same token object which was passed in. """ token, token_secret = oauth_token_info_from_body(server_response_body) request_token.token = token request_token.token_secret = token_secret request_token.auth_state = ACCESS_TOKEN request_token.next = None request_token.verifier = None return request_token UpgradeToAccessToken = upgrade_to_access_token REQUEST_TOKEN = 1 AUTHORIZED_REQUEST_TOKEN = 2 ACCESS_TOKEN = 3 class OAuthHmacToken(object): SIGNATURE_METHOD = HMAC_SHA1 def __init__(self, consumer_key, consumer_secret, token, token_secret, auth_state, next=None, verifier=None): self.consumer_key = consumer_key self.consumer_secret = consumer_secret self.token = token self.token_secret = token_secret self.auth_state = auth_state self.next = next self.verifier = verifier # Used to convert request token to access token. def generate_authorization_url( self, google_apps_domain=DEFAULT_DOMAIN, language=None, btmpl=None, auth_server=OAUTH_AUTHORIZE_URL): """Creates the URL at which the user can authorize this app to access. Args: google_apps_domain: str (optional) If the user should be signing in using an account under a known Google Apps domain, provide the domain name ('example.com') here. If not provided, 'default' will be used, and the user will be prompted to select an account if they are signed in with a Google Account and Google Apps accounts. language: str (optional) An ISO 639 country code identifying what language the approval page should be translated in (for example, 'en' for English). The default is the user's selected language. btmpl: str (optional) Forces a mobile version of the approval page. The only accepted value is 'mobile'. auth_server: str (optional) The start of the token authorization web page. Defaults to 'https://www.google.com/accounts/OAuthAuthorizeToken' """ return generate_oauth_authorization_url( self.token, hd=google_apps_domain, hl=language, btmpl=btmpl, auth_server=auth_server) GenerateAuthorizationUrl = generate_authorization_url def modify_request(self, http_request): """Sets the Authorization header in the HTTP request using the token. Calculates an HMAC signature using the information in the token to indicate that the request came from this application and that this application has permission to access a particular user's data. Returns: The same HTTP request object which was passed in. """ timestamp = str(int(time.time())) nonce = ''.join([str(random.randint(0, 9)) for i in xrange(15)]) signature = generate_hmac_signature( http_request, self.consumer_key, self.consumer_secret, timestamp, nonce, version='1.0', next=self.next, token=self.token, token_secret=self.token_secret, verifier=self.verifier) http_request.headers['Authorization'] = generate_auth_header( self.consumer_key, timestamp, nonce, HMAC_SHA1, signature, version='1.0', next=self.next, token=self.token, verifier=self.verifier) return http_request ModifyRequest = modify_request class OAuthRsaToken(OAuthHmacToken): SIGNATURE_METHOD = RSA_SHA1 def __init__(self, consumer_key, rsa_private_key, token, token_secret, auth_state, next=None, verifier=None): self.consumer_key = consumer_key self.rsa_private_key = rsa_private_key self.token = token self.token_secret = token_secret self.auth_state = auth_state self.next = next self.verifier = verifier # Used to convert request token to access token. def modify_request(self, http_request): """Sets the Authorization header in the HTTP request using the token. Calculates an RSA signature using the information in the token to indicate that the request came from this application and that this application has permission to access a particular user's data. Returns: The same HTTP request object which was passed in. """ timestamp = str(int(time.time())) nonce = ''.join([str(random.randint(0, 9)) for i in xrange(15)]) signature = generate_rsa_signature( http_request, self.consumer_key, self.rsa_private_key, timestamp, nonce, version='1.0', next=self.next, token=self.token, token_secret=self.token_secret, verifier=self.verifier) http_request.headers['Authorization'] = generate_auth_header( self.consumer_key, timestamp, nonce, RSA_SHA1, signature, version='1.0', next=self.next, token=self.token, verifier=self.verifier) return http_request ModifyRequest = modify_request class TwoLeggedOAuthHmacToken(OAuthHmacToken): def __init__(self, consumer_key, consumer_secret, requestor_id): self.requestor_id = requestor_id OAuthHmacToken.__init__( self, consumer_key, consumer_secret, None, None, ACCESS_TOKEN, next=None, verifier=None) def modify_request(self, http_request): """Sets the Authorization header in the HTTP request using the token. Calculates an HMAC signature using the information in the token to indicate that the request came from this application and that this application has permission to access a particular user's data using 2LO. Returns: The same HTTP request object which was passed in. """ http_request.uri.query['xoauth_requestor_id'] = self.requestor_id return OAuthHmacToken.modify_request(self, http_request) ModifyRequest = modify_request class TwoLeggedOAuthRsaToken(OAuthRsaToken): def __init__(self, consumer_key, rsa_private_key, requestor_id): self.requestor_id = requestor_id OAuthRsaToken.__init__( self, consumer_key, rsa_private_key, None, None, ACCESS_TOKEN, next=None, verifier=None) def modify_request(self, http_request): """Sets the Authorization header in the HTTP request using the token. Calculates an RSA signature using the information in the token to indicate that the request came from this application and that this application has permission to access a particular user's data using 2LO. Returns: The same HTTP request object which was passed in. """ http_request.uri.query['xoauth_requestor_id'] = self.requestor_id return OAuthRsaToken.modify_request(self, http_request) ModifyRequest = modify_request class OAuth2Token(object): """Token object for OAuth 2.0 as described on . Token can be applied to a gdata.client.GDClient object using the authorize() method, which then signs each request from that object with the OAuth 2.0 access token. This class supports 3 flows of OAuth 2.0: Client-side web flow: call generate_authorize_url with `response_type='token'' and the registered `redirect_uri'. Server-side web flow: call generate_authorize_url with the registered `redirect_url'. Native applications flow: call generate_authorize_url as it is. You will have to ask the user to go to the generated url and pass in the authorization code to your application. """ def __init__(self, client_id, client_secret, scope, user_agent, auth_uri='https://accounts.google.com/o/oauth2/auth', token_uri='https://accounts.google.com/o/oauth2/token', access_token=None, refresh_token=None, revoke_uri='https://accounts.google.com/o/oauth2/revoke'): """Create an instance of OAuth2Token Args: client_id: string, client identifier. client_secret: string client secret. scope: string, scope of the credentials being requested. user_agent: string, HTTP User-Agent to provide for this application. auth_uri: string, URI for authorization endpoint. For convenience defaults to Google's endpoints but any OAuth 2.0 provider can be used. token_uri: string, URI for token endpoint. For convenience defaults to Google's endpoints but any OAuth 2.0 provider can be used. revoke_uri: string, URI for revoke endpoint. For convenience defaults to Google's endpoints but any OAuth 2.0 provider can be used. access_token: string, access token. refresh_token: string, refresh token. """ self.client_id = client_id self.client_secret = client_secret self.scope = scope self.user_agent = user_agent self.auth_uri = auth_uri self.token_uri = token_uri self.revoke_uri = revoke_uri self.access_token = access_token self.refresh_token = refresh_token # True if the credentials have been revoked or expired and can't be # refreshed. self._invalid = False @property def invalid(self): """True if the credentials are invalid, such as being revoked.""" return getattr(self, '_invalid', False) def _refresh(self, request): """Refresh the access_token using the refresh_token. Args: request: The atom.http_core.HttpRequest which contains all of the information needed to send a request to the remote server. """ body = urllib.urlencode({ 'grant_type': 'refresh_token', 'client_id': self.client_id, 'client_secret': self.client_secret, 'refresh_token' : self.refresh_token }) headers = { 'user-agent': self.user_agent, } http_request = atom.http_core.HttpRequest( uri=self.token_uri, method='POST', headers=headers) http_request.add_body_part( body, mime_type='application/x-www-form-urlencoded') response = request(http_request) body = response.read() if response.status == 200: self._extract_tokens(body) else: self._invalid = True return response def _extract_tokens(self, body): d = simplejson.loads(body) self.access_token = d['access_token'] self.refresh_token = d.get('refresh_token', self.refresh_token) if 'expires_in' in d: self.token_expiry = datetime.timedelta( seconds = int(d['expires_in'])) + datetime.datetime.now() else: self.token_expiry = None def generate_authorize_url(self, redirect_uri='urn:ietf:wg:oauth:2.0:oob', response_type='code', access_type='offline', approval_prompt='auto', **kwargs): """Returns a URI to redirect to the provider. Args: redirect_uri: Either the string 'urn:ietf:wg:oauth:2.0:oob' for a non-web-based application, or a URI that handles the callback from the authorization server. response_type: Either the string 'code' for server-side or native application, or the string 'token' for client-side application. access_type: Either the string 'offline' to request a refresh token or 'online'. approval_prompt: Either the string 'auto' to let the OAuth mechanism determine if the approval page is needed or 'force' if the approval prompt is desired in all cases. If redirect_uri is 'urn:ietf:wg:oauth:2.0:oob' then pass in the generated verification code to get_access_token, otherwise pass in the query parameters received at the callback uri to get_access_token. If the response_type is 'token', no need to call get_access_token as the API will return it within the query parameters received at the callback: oauth2_token.access_token = YOUR_ACCESS_TOKEN """ self.redirect_uri = redirect_uri query = { 'response_type': response_type, 'client_id': self.client_id, 'redirect_uri': redirect_uri, 'scope': self.scope, 'approval_prompt': approval_prompt, 'access_type': access_type } query.update(kwargs) parts = list(urlparse.urlparse(self.auth_uri)) query.update(dict(parse_qsl(parts[4]))) # 4 is the index of the query part parts[4] = urllib.urlencode(query) return urlparse.urlunparse(parts) def get_access_token(self, code): """Exhanges a code for an access token. Args: code: string or dict, either the code as a string, or a dictionary of the query parameters to the redirect_uri, which contains the code. """ if not (isinstance(code, str) or isinstance(code, unicode)): code = code['code'] body = urllib.urlencode({ 'grant_type': 'authorization_code', 'client_id': self.client_id, 'client_secret': self.client_secret, 'code': code, 'redirect_uri': self.redirect_uri, 'scope': self.scope }) headers = { 'user-agent': self.user_agent, } http_client = atom.http_core.HttpClient() http_request = atom.http_core.HttpRequest(uri=self.token_uri, method='POST', headers=headers) http_request.add_body_part(data=body, mime_type='application/x-www-form-urlencoded') response = http_client.request(http_request) body = response.read() if response.status == 200: self._extract_tokens(body) return self else: error_msg = 'Invalid response %s.' % response.status try: d = simplejson.loads(body) if 'error' in d: error_msg = d['error'] except: pass raise OAuth2AccessTokenError(error_msg) def authorize(self, client): """Authorize a gdata.client.GDClient instance with these credentials. Args: client: An instance of gdata.client.GDClient or something that acts like it. Returns: A modified instance of client that was passed in. Example: >>> c = gdata.client.GDClient(source='user-agent') >>> c = token.authorize(c) """ client.auth_token = self request_orig = client.http_client.request def new_request(http_request): response = request_orig(http_request) if response.status == 401: refresh_response = self._refresh(request_orig) if self._invalid: return refresh_response else: self.modify_request(http_request) return request_orig(http_request) else: return response client.http_client.request = new_request return client def revoke(self, revoke_uri=None, refresh_token=None): """Revokes access via a refresh token. Args: revoke_uri: string, URI for revoke endpoint. If not provided, or if None is provided, the revoke_uri attribute on the object is used. refresh_token: string, refresh token. If not provided, or if None is provided, the refresh_token attribute on the object is used. Raises: UnsupportedTokenType if the token is not one of the supported token classes listed above. Example: >>> token.revoke() """ base_revoke_uri = revoke_uri or self.revoke_uri refresh_token = refresh_token or self.refresh_token revoke_uri = atom.http_core.ParseUri(base_revoke_uri) revoke_uri.query['token'] = refresh_token http_client = atom.http_core.HttpClient() http_request = atom.http_core.HttpRequest(uri=revoke_uri, method='GET') response = http_client.request(http_request) if response.status != 200: raise OAuth2RevokeError(response) else: self._invalid = True def modify_request(self, http_request): """Sets the Authorization header in the HTTP request using the token. Returns: The same HTTP request object which was passed in. """ http_request.headers['Authorization'] = '%s%s' % (OAUTH2_AUTH_LABEL, self.access_token) return http_request ModifyRequest = modify_request def _make_credentials_property(name): """Helper method which generates properties. Used to access and set values on credentials property as if they were native attributes on the current object. Args: name: A string corresponding to the attribute being accessed on the credentials attribute of the object which will own the property. Returns: An instance of `property` which is a proxy for the `name` attribute on the credentials attribute of the object. """ def get_credentials_value(self): return getattr(self.credentials, name) def set_credentials_value(self, value): setattr(self.credentials, name, value) return property(get_credentials_value, set_credentials_value) class OAuth2TokenFromCredentials(OAuth2Token): """Special subclass to be used in conjunction with google-api-python-client. These libraries are built for different purposes. This one is used for APIs that use the GData Protocol: https://developers.google.com/gdata/docs/2.0/reference while google-api-python-client is for APIs which are discovery-based: https://developers.google.com/discovery/v1/getting_started#background Developers using Google APIs may want to use both simultaneously, and this class provides a way for them to use OAuth 2.0 credentials obtained for use with google-api-python-client as credentials in gdata-python-client and to ensure all token/credential refresh impacts both this object and the google-api-python-client credentials object. In addition, any manual patching of this object or the credentials object will be immediately reflected since attributes such as `client_id`, `access_token`, etc. are directly proxied between instances of this class and the credentials objects that they own. """ def __init__(self, credentials): """Constructor for OAuth2TokenFromCredentials object. The constructor for the superclass is not called because the actual values are retrieved from the credentials object directly via `property` instances which act as proxies. Args: credentials: An instance of oauth2client.client.Credentials or some subclass. """ self.credentials = credentials client_id = _make_credentials_property('client_id') client_secret = _make_credentials_property('client_secret') user_agent = _make_credentials_property('user_agent') # `scope` is not included as an attribute because the Credentials object in # google-api-python-client does not handle the initial auth flow. token_uri = _make_credentials_property('token_uri') access_token = _make_credentials_property('access_token') refresh_token = _make_credentials_property('refresh_token') # Again, since Credentials don't handle the initial auth flow, `auth_uri` and # `redirect_uri` are not included as attributes. In addition, revoke has not # been implemented for Credentials hence `revoke_uri` is not included either. token_expiry = _make_credentials_property('token_expiry') _invalid = _make_credentials_property('invalid') # Disable methods not supported by Credentials. def generate_authorize_url(self, *args, **kwargs): raise NotImplementedError def get_access_token(self, *args, **kwargs): raise NotImplementedError def revoke(self, *args, **kwargs): raise NotImplementedError def _extract_tokens(self, *args, **kwargs): raise NotImplementedError def _refresh(self, unused_request): """Refresh the access_token using the Credentials object. Args: unused_request: The atom.http_core.HttpRequest which contains all of the information needed to send a request to the remote server. This won't be used since we are refreshing the Credentials object directly and need to use the supported HTTP mechanisms for the google-api-python-client library. """ # Import httplib2 here since we don't want it as an unnecessary dependency # for users who don't integrate with google-api-python-client. import httplib2 self.credentials._refresh(httplib2.Http().request) def _join_token_parts(*args): """"Escapes and combines all strings passed in. Used to convert a token object's members into a string instead of using pickle. Note: A None value will be converted to an empty string. Returns: A string in the form 1x|member1|member2|member3... """ return '|'.join([urllib.quote_plus(a or '') for a in args]) def _split_token_parts(blob): """Extracts and unescapes fields from the provided binary string. Reverses the packing performed by _join_token_parts. Used to extract the members of a token object. Note: An empty string from the blob will be interpreted as None. Args: blob: str A string of the form 1x|member1|member2|member3 as created by _join_token_parts Returns: A list of unescaped strings. """ return [urllib.unquote_plus(part) or None for part in blob.split('|')] def token_to_blob(token): """Serializes the token data as a string for storage in a datastore. Supported token classes: ClientLoginToken, AuthSubToken, SecureAuthSubToken, OAuthRsaToken, and OAuthHmacToken, TwoLeggedOAuthRsaToken, TwoLeggedOAuthHmacToken and OAuth2Token. Args: token: A token object which must be of one of the supported token classes. Raises: UnsupportedTokenType if the token is not one of the supported token classes listed above. Returns: A string represenging this token. The string can be converted back into an equivalent token object using token_from_blob. Note that any members which are set to '' will be set to None when the token is deserialized by token_from_blob. """ if isinstance(token, ClientLoginToken): return _join_token_parts('1c', token.token_string) # Check for secure auth sub type first since it is a subclass of # AuthSubToken. elif isinstance(token, SecureAuthSubToken): return _join_token_parts('1s', token.token_string, token.rsa_private_key, *token.scopes) elif isinstance(token, AuthSubToken): return _join_token_parts('1a', token.token_string, *token.scopes) elif isinstance(token, TwoLeggedOAuthRsaToken): return _join_token_parts( '1rtl', token.consumer_key, token.rsa_private_key, token.requestor_id) elif isinstance(token, TwoLeggedOAuthHmacToken): return _join_token_parts( '1htl', token.consumer_key, token.consumer_secret, token.requestor_id) # Check RSA OAuth token first since the OAuthRsaToken is a subclass of # OAuthHmacToken. elif isinstance(token, OAuthRsaToken): return _join_token_parts( '1r', token.consumer_key, token.rsa_private_key, token.token, token.token_secret, str(token.auth_state), token.next, token.verifier) elif isinstance(token, OAuthHmacToken): return _join_token_parts( '1h', token.consumer_key, token.consumer_secret, token.token, token.token_secret, str(token.auth_state), token.next, token.verifier) elif isinstance(token, OAuth2Token): return _join_token_parts( '2o', token.client_id, token.client_secret, token.scope, token.user_agent, token.auth_uri, token.token_uri, token.access_token, token.refresh_token) else: raise UnsupportedTokenType( 'Unable to serialize token of type %s' % type(token)) TokenToBlob = token_to_blob def token_from_blob(blob): """Deserializes a token string from the datastore back into a token object. Supported token classes: ClientLoginToken, AuthSubToken, SecureAuthSubToken, OAuthRsaToken, and OAuthHmacToken, TwoLeggedOAuthRsaToken, TwoLeggedOAuthHmacToken and OAuth2Token. Args: blob: string created by token_to_blob. Raises: UnsupportedTokenType if the token is not one of the supported token classes listed above. Returns: A new token object with members set to the values serialized in the blob string. Note that any members which were set to '' in the original token will now be None. """ parts = _split_token_parts(blob) if parts[0] == '1c': return ClientLoginToken(parts[1]) elif parts[0] == '1a': return AuthSubToken(parts[1], parts[2:]) elif parts[0] == '1s': return SecureAuthSubToken(parts[1], parts[2], parts[3:]) elif parts[0] == '1rtl': return TwoLeggedOAuthRsaToken(parts[1], parts[2], parts[3]) elif parts[0] == '1htl': return TwoLeggedOAuthHmacToken(parts[1], parts[2], parts[3]) elif parts[0] == '1r': auth_state = int(parts[5]) return OAuthRsaToken(parts[1], parts[2], parts[3], parts[4], auth_state, parts[6], parts[7]) elif parts[0] == '1h': auth_state = int(parts[5]) return OAuthHmacToken(parts[1], parts[2], parts[3], parts[4], auth_state, parts[6], parts[7]) elif parts[0] == '2o': return OAuth2Token(parts[1], parts[2], parts[3], parts[4], parts[5], parts[6], parts[7], parts[8]) else: raise UnsupportedTokenType( 'Unable to deserialize token with type marker of %s' % parts[0]) TokenFromBlob = token_from_blob def dump_tokens(tokens): return ','.join([token_to_blob(t) for t in tokens]) def load_tokens(blob): return [token_from_blob(s) for s in blob.split(',')] def find_scopes_for_services(service_names=None): """Creates a combined list of scope URLs for the desired services. This method searches the AUTH_SCOPES dictionary. Args: service_names: list of strings (optional) Each name must be a key in the AUTH_SCOPES dictionary. If no list is provided (None) then the resulting list will contain all scope URLs in the AUTH_SCOPES dict. Returns: A list of URL strings which are the scopes needed to access these services when requesting a token using AuthSub or OAuth. """ result_scopes = [] if service_names is None: for service_name, scopes in AUTH_SCOPES.iteritems(): result_scopes.extend(scopes) else: for service_name in service_names: result_scopes.extend(AUTH_SCOPES[service_name]) return result_scopes FindScopesForServices = find_scopes_for_services def ae_save(token, token_key): """Stores an auth token in the App Engine datastore. This is a convenience method for using the library with App Engine. Recommended usage is to associate the auth token with the current_user. If a user is signed in to the app using the App Engine users API, you can use gdata.gauth.ae_save(some_token, users.get_current_user().user_id()) If you are not using the Users API you are free to choose whatever string you would like for a token_string. Args: token: an auth token object. Must be one of ClientLoginToken, AuthSubToken, SecureAuthSubToken, OAuthRsaToken, or OAuthHmacToken (see token_to_blob). token_key: str A unique identified to be used when you want to retrieve the token. If the user is signed in to App Engine using the users API, I recommend using the user ID for the token_key: users.get_current_user().user_id() """ import gdata.alt.app_engine key_name = ''.join(('gd_auth_token', token_key)) return gdata.alt.app_engine.set_token(key_name, token_to_blob(token)) AeSave = ae_save def ae_load(token_key): """Retrieves a token object from the App Engine datastore. This is a convenience method for using the library with App Engine. See also ae_save. Args: token_key: str The unique key associated with the desired token when it was saved using ae_save. Returns: A token object if there was a token associated with the token_key or None if the key could not be found. """ import gdata.alt.app_engine key_name = ''.join(('gd_auth_token', token_key)) token_string = gdata.alt.app_engine.get_token(key_name) if token_string is not None: return token_from_blob(token_string) else: return None AeLoad = ae_load def ae_delete(token_key): """Removes the token object from the App Engine datastore.""" import gdata.alt.app_engine key_name = ''.join(('gd_auth_token', token_key)) gdata.alt.app_engine.delete_token(key_name) AeDelete = ae_delete gdata-2.0.18/src/gdata/webmastertools/0000750036466300116100000000000012156625015017653 5ustar afshareng00000000000000gdata-2.0.18/src/gdata/webmastertools/data.py0000640036466300116100000001260012156622363021141 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright (C) 2009 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. """Contains the data classes of the Google Webmaster Tools Data API""" __author__ = 'j.s@google.com (Jeff Scudder)' import atom.core import atom.data import gdata.data import gdata.opensearch.data WT_TEMPLATE = '{http://schemas.google.com/webmaster/tools/2007/}%s' class CrawlIssueCrawlType(atom.core.XmlElement): """Type of crawl of the crawl issue""" _qname = WT_TEMPLATE % 'crawl-type' class CrawlIssueDateDetected(atom.core.XmlElement): """Detection date for the issue""" _qname = WT_TEMPLATE % 'date-detected' class CrawlIssueDetail(atom.core.XmlElement): """Detail of the crawl issue""" _qname = WT_TEMPLATE % 'detail' class CrawlIssueIssueType(atom.core.XmlElement): """Type of crawl issue""" _qname = WT_TEMPLATE % 'issue-type' class CrawlIssueLinkedFromUrl(atom.core.XmlElement): """Source URL that links to the issue URL""" _qname = WT_TEMPLATE % 'linked-from' class CrawlIssueUrl(atom.core.XmlElement): """URL affected by the crawl issue""" _qname = WT_TEMPLATE % 'url' class CrawlIssueEntry(gdata.data.GDEntry): """Describes a crawl issue entry""" date_detected = CrawlIssueDateDetected url = CrawlIssueUrl detail = CrawlIssueDetail issue_type = CrawlIssueIssueType crawl_type = CrawlIssueCrawlType linked_from = [CrawlIssueLinkedFromUrl] class CrawlIssuesFeed(gdata.data.GDFeed): """Feed of crawl issues for a particular site""" entry = [CrawlIssueEntry] class Indexed(atom.core.XmlElement): """Describes the indexing status of a site""" _qname = WT_TEMPLATE % 'indexed' class Keyword(atom.core.XmlElement): """A keyword in a site or in a link to a site""" _qname = WT_TEMPLATE % 'keyword' source = 'source' class KeywordEntry(gdata.data.GDEntry): """Describes a keyword entry""" class KeywordsFeed(gdata.data.GDFeed): """Feed of keywords for a particular site""" entry = [KeywordEntry] keyword = [Keyword] class LastCrawled(atom.core.XmlElement): """Describes the last crawled date of a site""" _qname = WT_TEMPLATE % 'last-crawled' class MessageBody(atom.core.XmlElement): """Message body""" _qname = WT_TEMPLATE % 'body' class MessageDate(atom.core.XmlElement): """Message date""" _qname = WT_TEMPLATE % 'date' class MessageLanguage(atom.core.XmlElement): """Message language""" _qname = WT_TEMPLATE % 'language' class MessageRead(atom.core.XmlElement): """Indicates if the message has already been read""" _qname = WT_TEMPLATE % 'read' class MessageSubject(atom.core.XmlElement): """Message subject""" _qname = WT_TEMPLATE % 'subject' class SiteId(atom.core.XmlElement): """Site URL""" _qname = WT_TEMPLATE % 'id' class MessageEntry(gdata.data.GDEntry): """Describes a message entry""" wt_id = SiteId subject = MessageSubject date = MessageDate body = MessageBody language = MessageLanguage read = MessageRead class MessagesFeed(gdata.data.GDFeed): """Describes a messages feed""" entry = [MessageEntry] class SitemapEntry(gdata.data.GDEntry): """Describes a sitemap entry""" indexed = Indexed wt_id = SiteId class SitemapMobileMarkupLanguage(atom.core.XmlElement): """Describes a markup language for URLs in this sitemap""" _qname = WT_TEMPLATE % 'sitemap-mobile-markup-language' class SitemapMobile(atom.core.XmlElement): """Lists acceptable mobile markup languages for URLs in this sitemap""" _qname = WT_TEMPLATE % 'sitemap-mobile' sitemap_mobile_markup_language = [SitemapMobileMarkupLanguage] class SitemapNewsPublicationLabel(atom.core.XmlElement): """Specifies the publication label for this sitemap""" _qname = WT_TEMPLATE % 'sitemap-news-publication-label' class SitemapNews(atom.core.XmlElement): """Lists publication labels for this sitemap""" _qname = WT_TEMPLATE % 'sitemap-news' sitemap_news_publication_label = [SitemapNewsPublicationLabel] class SitemapType(atom.core.XmlElement): """Indicates the type of sitemap. Not used for News or Mobile Sitemaps""" _qname = WT_TEMPLATE % 'sitemap-type' class SitemapUrlCount(atom.core.XmlElement): """Indicates the number of URLs contained in the sitemap""" _qname = WT_TEMPLATE % 'sitemap-url-count' class SitemapsFeed(gdata.data.GDFeed): """Describes a sitemaps feed""" entry = [SitemapEntry] class VerificationMethod(atom.core.XmlElement): """Describes a verification method that may be used for a site""" _qname = WT_TEMPLATE % 'verification-method' in_use = 'in-use' type = 'type' class Verified(atom.core.XmlElement): """Describes the verification status of a site""" _qname = WT_TEMPLATE % 'verified' class SiteEntry(gdata.data.GDEntry): """Describes a site entry""" indexed = Indexed wt_id = SiteId verified = Verified last_crawled = LastCrawled verification_method = [VerificationMethod] class SitesFeed(gdata.data.GDFeed): """Describes a sites feed""" entry = [SiteEntry] gdata-2.0.18/src/gdata/webmastertools/__init__.py0000640036466300116100000004275412156622363022004 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright (C) 2008 Yu-Jie Lin # # 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. """Contains extensions to Atom objects used with Google Webmaster Tools.""" __author__ = 'livibetter (Yu-Jie Lin)' try: from xml.etree import cElementTree as ElementTree except ImportError: try: import cElementTree as ElementTree except ImportError: try: from xml.etree import ElementTree except ImportError: from elementtree import ElementTree import atom import gdata # XML namespaces which are often used in Google Webmaster Tools entities. GWEBMASTERTOOLS_NAMESPACE = 'http://schemas.google.com/webmasters/tools/2007' GWEBMASTERTOOLS_TEMPLATE = '{http://schemas.google.com/webmasters/tools/2007}%s' class Indexed(atom.AtomBase): _tag = 'indexed' _namespace = GWEBMASTERTOOLS_NAMESPACE def IndexedFromString(xml_string): return atom.CreateClassFromXMLString(Indexed, xml_string) class Crawled(atom.Date): _tag = 'crawled' _namespace = GWEBMASTERTOOLS_NAMESPACE def CrawledFromString(xml_string): return atom.CreateClassFromXMLString(Crawled, xml_string) class GeoLocation(atom.AtomBase): _tag = 'geolocation' _namespace = GWEBMASTERTOOLS_NAMESPACE def GeoLocationFromString(xml_string): return atom.CreateClassFromXMLString(GeoLocation, xml_string) class PreferredDomain(atom.AtomBase): _tag = 'preferred-domain' _namespace = GWEBMASTERTOOLS_NAMESPACE def PreferredDomainFromString(xml_string): return atom.CreateClassFromXMLString(PreferredDomain, xml_string) class CrawlRate(atom.AtomBase): _tag = 'crawl-rate' _namespace = GWEBMASTERTOOLS_NAMESPACE def CrawlRateFromString(xml_string): return atom.CreateClassFromXMLString(CrawlRate, xml_string) class EnhancedImageSearch(atom.AtomBase): _tag = 'enhanced-image-search' _namespace = GWEBMASTERTOOLS_NAMESPACE def EnhancedImageSearchFromString(xml_string): return atom.CreateClassFromXMLString(EnhancedImageSearch, xml_string) class Verified(atom.AtomBase): _tag = 'verified' _namespace = GWEBMASTERTOOLS_NAMESPACE def VerifiedFromString(xml_string): return atom.CreateClassFromXMLString(Verified, xml_string) class VerificationMethodMeta(atom.AtomBase): _tag = 'meta' _namespace = atom.ATOM_NAMESPACE _children = atom.AtomBase._children.copy() _attributes = atom.AtomBase._attributes.copy() _attributes['name'] = 'name' _attributes['content'] = 'content' def __init__(self, text=None, name=None, content=None, extension_elements=None, extension_attributes=None): self.text = text self.name = name self.content = content self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} def VerificationMethodMetaFromString(xml_string): return atom.CreateClassFromXMLString(VerificationMethodMeta, xml_string) class VerificationMethod(atom.AtomBase): _tag = 'verification-method' _namespace = GWEBMASTERTOOLS_NAMESPACE _children = atom.Text._children.copy() _attributes = atom.Text._attributes.copy() _children['{%s}meta' % atom.ATOM_NAMESPACE] = ( 'meta', VerificationMethodMeta) _attributes['in-use'] = 'in_use' _attributes['type'] = 'type' def __init__(self, text=None, in_use=None, meta=None, type=None, extension_elements=None, extension_attributes=None): self.text = text self.in_use = in_use self.meta = meta self.type = type self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} def VerificationMethodFromString(xml_string): return atom.CreateClassFromXMLString(VerificationMethod, xml_string) class MarkupLanguage(atom.AtomBase): _tag = 'markup-language' _namespace = GWEBMASTERTOOLS_NAMESPACE def MarkupLanguageFromString(xml_string): return atom.CreateClassFromXMLString(MarkupLanguage, xml_string) class SitemapMobile(atom.AtomBase): _tag = 'sitemap-mobile' _namespace = GWEBMASTERTOOLS_NAMESPACE _children = atom.AtomBase._children.copy() _attributes = atom.AtomBase._attributes.copy() _children['{%s}markup-language' % GWEBMASTERTOOLS_NAMESPACE] = ( 'markup_language', [MarkupLanguage]) def __init__(self, markup_language=None, extension_elements=None, extension_attributes=None, text=None): self.markup_language = markup_language or [] self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} def SitemapMobileFromString(xml_string): return atom.CreateClassFromXMLString(SitemapMobile, xml_string) class SitemapMobileMarkupLanguage(atom.AtomBase): _tag = 'sitemap-mobile-markup-language' _namespace = GWEBMASTERTOOLS_NAMESPACE def SitemapMobileMarkupLanguageFromString(xml_string): return atom.CreateClassFromXMLString(SitemapMobileMarkupLanguage, xml_string) class PublicationLabel(atom.AtomBase): _tag = 'publication-label' _namespace = GWEBMASTERTOOLS_NAMESPACE def PublicationLabelFromString(xml_string): return atom.CreateClassFromXMLString(PublicationLabel, xml_string) class SitemapNews(atom.AtomBase): _tag = 'sitemap-news' _namespace = GWEBMASTERTOOLS_NAMESPACE _children = atom.AtomBase._children.copy() _attributes = atom.AtomBase._attributes.copy() _children['{%s}publication-label' % GWEBMASTERTOOLS_NAMESPACE] = ( 'publication_label', [PublicationLabel]) def __init__(self, publication_label=None, extension_elements=None, extension_attributes=None, text=None): self.publication_label = publication_label or [] self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} def SitemapNewsFromString(xml_string): return atom.CreateClassFromXMLString(SitemapNews, xml_string) class SitemapNewsPublicationLabel(atom.AtomBase): _tag = 'sitemap-news-publication-label' _namespace = GWEBMASTERTOOLS_NAMESPACE def SitemapNewsPublicationLabelFromString(xml_string): return atom.CreateClassFromXMLString(SitemapNewsPublicationLabel, xml_string) class SitemapLastDownloaded(atom.Date): _tag = 'sitemap-last-downloaded' _namespace = GWEBMASTERTOOLS_NAMESPACE def SitemapLastDownloadedFromString(xml_string): return atom.CreateClassFromXMLString(SitemapLastDownloaded, xml_string) class SitemapType(atom.AtomBase): _tag = 'sitemap-type' _namespace = GWEBMASTERTOOLS_NAMESPACE def SitemapTypeFromString(xml_string): return atom.CreateClassFromXMLString(SitemapType, xml_string) class SitemapStatus(atom.AtomBase): _tag = 'sitemap-status' _namespace = GWEBMASTERTOOLS_NAMESPACE def SitemapStatusFromString(xml_string): return atom.CreateClassFromXMLString(SitemapStatus, xml_string) class SitemapUrlCount(atom.AtomBase): _tag = 'sitemap-url-count' _namespace = GWEBMASTERTOOLS_NAMESPACE def SitemapUrlCountFromString(xml_string): return atom.CreateClassFromXMLString(SitemapUrlCount, xml_string) class LinkFinder(atom.LinkFinder): """An "interface" providing methods to find link elements SitesEntry elements often contain multiple links which differ in the rel attribute or content type. Often, developers are interested in a specific type of link so this class provides methods to find specific classes of links. This class is used as a mixin in SitesEntry. """ def GetSelfLink(self): """Find the first link with rel set to 'self' Returns: An atom.Link or none if none of the links had rel equal to 'self' """ for a_link in self.link: if a_link.rel == 'self': return a_link return None def GetEditLink(self): for a_link in self.link: if a_link.rel == 'edit': return a_link return None def GetPostLink(self): """Get a link containing the POST target URL. The POST target URL is used to insert new entries. Returns: A link object with a rel matching the POST type. """ for a_link in self.link: if a_link.rel == 'http://schemas.google.com/g/2005#post': return a_link return None def GetFeedLink(self): for a_link in self.link: if a_link.rel == 'http://schemas.google.com/g/2005#feed': return a_link return None class SitesEntry(atom.Entry, LinkFinder): """A Google Webmaster Tools meta Entry flavor of an Atom Entry """ _tag = atom.Entry._tag _namespace = atom.Entry._namespace _children = atom.Entry._children.copy() _attributes = atom.Entry._attributes.copy() _children['{%s}entryLink' % gdata.GDATA_NAMESPACE] = ( 'entry_link', [gdata.EntryLink]) _children['{%s}indexed' % GWEBMASTERTOOLS_NAMESPACE] = ('indexed', Indexed) _children['{%s}crawled' % GWEBMASTERTOOLS_NAMESPACE] = ( 'crawled', Crawled) _children['{%s}geolocation' % GWEBMASTERTOOLS_NAMESPACE] = ( 'geolocation', GeoLocation) _children['{%s}preferred-domain' % GWEBMASTERTOOLS_NAMESPACE] = ( 'preferred_domain', PreferredDomain) _children['{%s}crawl-rate' % GWEBMASTERTOOLS_NAMESPACE] = ( 'crawl_rate', CrawlRate) _children['{%s}enhanced-image-search' % GWEBMASTERTOOLS_NAMESPACE] = ( 'enhanced_image_search', EnhancedImageSearch) _children['{%s}verified' % GWEBMASTERTOOLS_NAMESPACE] = ( 'verified', Verified) _children['{%s}verification-method' % GWEBMASTERTOOLS_NAMESPACE] = ( 'verification_method', [VerificationMethod]) def __GetId(self): return self.__id # This method was created to strip the unwanted whitespace from the id's # text node. def __SetId(self, id): self.__id = id if id is not None and id.text is not None: self.__id.text = id.text.strip() id = property(__GetId, __SetId) def __init__(self, category=None, content=None, atom_id=None, link=None, title=None, updated=None, entry_link=None, indexed=None, crawled=None, geolocation=None, preferred_domain=None, crawl_rate=None, enhanced_image_search=None, verified=None, verification_method=None, extension_elements=None, extension_attributes=None, text=None): atom.Entry.__init__(self, category=category, content=content, atom_id=atom_id, link=link, title=title, updated=updated, text=text) self.entry_link = entry_link or [] self.indexed = indexed self.crawled = crawled self.geolocation = geolocation self.preferred_domain = preferred_domain self.crawl_rate = crawl_rate self.enhanced_image_search = enhanced_image_search self.verified = verified self.verification_method = verification_method or [] def SitesEntryFromString(xml_string): return atom.CreateClassFromXMLString(SitesEntry, xml_string) class SitesFeed(atom.Feed, LinkFinder): """A Google Webmaster Tools meta Sites feed flavor of an Atom Feed""" _tag = atom.Feed._tag _namespace = atom.Feed._namespace _children = atom.Feed._children.copy() _attributes = atom.Feed._attributes.copy() _children['{%s}startIndex' % gdata.OPENSEARCH_NAMESPACE] = ( 'start_index', gdata.StartIndex) _children['{%s}entry' % atom.ATOM_NAMESPACE] = ('entry', [SitesEntry]) del _children['{%s}generator' % atom.ATOM_NAMESPACE] del _children['{%s}author' % atom.ATOM_NAMESPACE] del _children['{%s}contributor' % atom.ATOM_NAMESPACE] del _children['{%s}logo' % atom.ATOM_NAMESPACE] del _children['{%s}icon' % atom.ATOM_NAMESPACE] del _children['{%s}rights' % atom.ATOM_NAMESPACE] del _children['{%s}subtitle' % atom.ATOM_NAMESPACE] def __GetId(self): return self.__id def __SetId(self, id): self.__id = id if id is not None and id.text is not None: self.__id.text = id.text.strip() id = property(__GetId, __SetId) def __init__(self, start_index=None, atom_id=None, title=None, entry=None, category=None, link=None, updated=None, extension_elements=None, extension_attributes=None, text=None): """Constructor for Source Args: category: list (optional) A list of Category instances id: Id (optional) The entry's Id element link: list (optional) A list of Link instances title: Title (optional) the entry's title element updated: Updated (optional) the entry's updated element entry: list (optional) A list of the Entry instances contained in the feed. text: String (optional) The text contents of the element. This is the contents of the Entry's XML text node. (Example: This is the text) extension_elements: list (optional) A list of ExtensionElement instances which are children of this element. extension_attributes: dict (optional) A dictionary of strings which are the values for additional XML attributes of this element. """ self.start_index = start_index self.category = category or [] self.id = atom_id self.link = link or [] self.title = title self.updated = updated self.entry = entry or [] self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} def SitesFeedFromString(xml_string): return atom.CreateClassFromXMLString(SitesFeed, xml_string) class SitemapsEntry(atom.Entry, LinkFinder): """A Google Webmaster Tools meta Sitemaps Entry flavor of an Atom Entry """ _tag = atom.Entry._tag _namespace = atom.Entry._namespace _children = atom.Entry._children.copy() _attributes = atom.Entry._attributes.copy() _children['{%s}sitemap-type' % GWEBMASTERTOOLS_NAMESPACE] = ( 'sitemap_type', SitemapType) _children['{%s}sitemap-status' % GWEBMASTERTOOLS_NAMESPACE] = ( 'sitemap_status', SitemapStatus) _children['{%s}sitemap-last-downloaded' % GWEBMASTERTOOLS_NAMESPACE] = ( 'sitemap_last_downloaded', SitemapLastDownloaded) _children['{%s}sitemap-url-count' % GWEBMASTERTOOLS_NAMESPACE] = ( 'sitemap_url_count', SitemapUrlCount) _children['{%s}sitemap-mobile-markup-language' % GWEBMASTERTOOLS_NAMESPACE] \ = ('sitemap_mobile_markup_language', SitemapMobileMarkupLanguage) _children['{%s}sitemap-news-publication-label' % GWEBMASTERTOOLS_NAMESPACE] \ = ('sitemap_news_publication_label', SitemapNewsPublicationLabel) def __GetId(self): return self.__id # This method was created to strip the unwanted whitespace from the id's # text node. def __SetId(self, id): self.__id = id if id is not None and id.text is not None: self.__id.text = id.text.strip() id = property(__GetId, __SetId) def __init__(self, category=None, content=None, atom_id=None, link=None, title=None, updated=None, sitemap_type=None, sitemap_status=None, sitemap_last_downloaded=None, sitemap_url_count=None, sitemap_mobile_markup_language=None, sitemap_news_publication_label=None, extension_elements=None, extension_attributes=None, text=None): atom.Entry.__init__(self, category=category, content=content, atom_id=atom_id, link=link, title=title, updated=updated, text=text) self.sitemap_type = sitemap_type self.sitemap_status = sitemap_status self.sitemap_last_downloaded = sitemap_last_downloaded self.sitemap_url_count = sitemap_url_count self.sitemap_mobile_markup_language = sitemap_mobile_markup_language self.sitemap_news_publication_label = sitemap_news_publication_label def SitemapsEntryFromString(xml_string): return atom.CreateClassFromXMLString(SitemapsEntry, xml_string) class SitemapsFeed(atom.Feed, LinkFinder): """A Google Webmaster Tools meta Sitemaps feed flavor of an Atom Feed""" _tag = atom.Feed._tag _namespace = atom.Feed._namespace _children = atom.Feed._children.copy() _attributes = atom.Feed._attributes.copy() _children['{%s}entry' % atom.ATOM_NAMESPACE] = ('entry', [SitemapsEntry]) _children['{%s}sitemap-mobile' % GWEBMASTERTOOLS_NAMESPACE] = ( 'sitemap_mobile', SitemapMobile) _children['{%s}sitemap-news' % GWEBMASTERTOOLS_NAMESPACE] = ( 'sitemap_news', SitemapNews) del _children['{%s}generator' % atom.ATOM_NAMESPACE] del _children['{%s}author' % atom.ATOM_NAMESPACE] del _children['{%s}contributor' % atom.ATOM_NAMESPACE] del _children['{%s}logo' % atom.ATOM_NAMESPACE] del _children['{%s}icon' % atom.ATOM_NAMESPACE] del _children['{%s}rights' % atom.ATOM_NAMESPACE] del _children['{%s}subtitle' % atom.ATOM_NAMESPACE] def __GetId(self): return self.__id def __SetId(self, id): self.__id = id if id is not None and id.text is not None: self.__id.text = id.text.strip() id = property(__GetId, __SetId) def __init__(self, category=None, content=None, atom_id=None, link=None, title=None, updated=None, entry=None, sitemap_mobile=None, sitemap_news=None, extension_elements=None, extension_attributes=None, text=None): self.category = category or [] self.id = atom_id self.link = link or [] self.title = title self.updated = updated self.entry = entry or [] self.text = text self.sitemap_mobile = sitemap_mobile self.sitemap_news = sitemap_news self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} def SitemapsFeedFromString(xml_string): return atom.CreateClassFromXMLString(SitemapsFeed, xml_string) gdata-2.0.18/src/gdata/webmastertools/service.py0000640036466300116100000005323612156622363021702 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright (C) 2008 Yu-Jie Lin # # 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. """GWebmasterToolsService extends the GDataService to streamline Google Webmaster Tools operations. GWebmasterToolsService: Provides methods to query feeds and manipulate items. Extends GDataService. """ __author__ = 'livibetter (Yu-Jie Lin)' import urllib import gdata import atom.service import gdata.service import gdata.webmastertools as webmastertools import atom FEED_BASE = 'https://www.google.com/webmasters/tools/feeds/' SITES_FEED = FEED_BASE + 'sites/' SITE_TEMPLATE = SITES_FEED + '%s' SITEMAPS_FEED_TEMPLATE = FEED_BASE + '%(site_id)s/sitemaps/' SITEMAP_TEMPLATE = SITEMAPS_FEED_TEMPLATE + '%(sitemap_id)s' class Error(Exception): pass class RequestError(Error): pass class GWebmasterToolsService(gdata.service.GDataService): """Client for the Google Webmaster Tools service.""" def __init__(self, email=None, password=None, source=None, server='www.google.com', **kwargs): """Creates a client for the Google Webmaster Tools service. Args: email: string (optional) The user's email address, used for authentication. password: string (optional) The user's password. source: string (optional) The name of the user's application. server: string (optional) The name of the server to which a connection will be opened. Default value: 'www.google.com'. **kwargs: The other parameters to pass to gdata.service.GDataService constructor. """ gdata.service.GDataService.__init__( self, email=email, password=password, service='sitemaps', source=source, server=server, **kwargs) def GetSitesFeed(self, uri=SITES_FEED, converter=webmastertools.SitesFeedFromString): """Gets sites feed. Args: uri: str (optional) URI to retrieve sites feed. converter: func (optional) Function which is executed on the server's response before it is returned. Usually this is a function like SitesFeedFromString which will parse the response and turn it into an object. Returns: If converter is defined, the results of running converter on the server's response. Otherwise, it will be a SitesFeed object. """ return self.Get(uri, converter=converter) def AddSite(self, site_uri, uri=SITES_FEED, url_params=None, escape_params=True, converter=None): """Adds a site to Google Webmaster Tools. Args: site_uri: str URI of which site to add. uri: str (optional) URI to add a site. url_params: dict (optional) Additional URL parameters to be included in the insertion request. escape_params: boolean (optional) If true, the url_parameters will be escaped before they are included in the request. converter: func (optional) Function which is executed on the server's response before it is returned. Usually this is a function like SitesEntryFromString which will parse the response and turn it into an object. Returns: If converter is defined, the results of running converter on the server's response. Otherwise, it will be a SitesEntry object. """ site_entry = webmastertools.SitesEntry() site_entry.content = atom.Content(src=site_uri) response = self.Post(site_entry, uri, url_params=url_params, escape_params=escape_params, converter=converter) if not converter and isinstance(response, atom.Entry): return webmastertools.SitesEntryFromString(response.ToString()) return response def DeleteSite(self, site_uri, uri=SITE_TEMPLATE, url_params=None, escape_params=True): """Removes a site from Google Webmaster Tools. Args: site_uri: str URI of which site to remove. uri: str (optional) A URI template to send DELETE request. Default SITE_TEMPLATE. url_params: dict (optional) Additional URL parameters to be included in the insertion request. escape_params: boolean (optional) If true, the url_parameters will be escaped before they are included in the request. Returns: True if the delete succeeded. """ return self.Delete( uri % urllib.quote_plus(site_uri), url_params=url_params, escape_params=escape_params) def VerifySite(self, site_uri, verification_method, uri=SITE_TEMPLATE, url_params=None, escape_params=True, converter=None): """Requests a verification of a site. Args: site_uri: str URI of which site to add sitemap for. verification_method: str The method to verify a site. Valid values are 'htmlpage', and 'metatag'. uri: str (optional) URI template to update a site. Default SITE_TEMPLATE. url_params: dict (optional) Additional URL parameters to be included in the insertion request. escape_params: boolean (optional) If true, the url_parameters will be escaped before they are included in the request. converter: func (optional) Function which is executed on the server's response before it is returned. Usually this is a function like SitemapsEntryFromString which will parse the response and turn it into an object. Returns: If converter is defined, the results of running converter on the server's response. Otherwise, it will be a SitesEntry object. """ site_entry = webmastertools.SitesEntry( atom_id=atom.Id(text=site_uri), category=atom.Category( scheme='http://schemas.google.com/g/2005#kind', term='http://schemas.google.com/webmasters/tools/2007#sites-info'), verification_method=webmastertools.VerificationMethod( type=verification_method, in_use='true') ) response = self.Put( site_entry, uri % urllib.quote_plus(site_uri), url_params=url_params, escape_params=escape_params, converter=converter) if not converter and isinstance(response, atom.Entry): return webmastertools.SitesEntryFromString(response.ToString()) return response def UpdateGeoLocation(self, site_uri, geolocation, uri=SITE_TEMPLATE, url_params=None, escape_params=True, converter=None): """Updates geolocation setting of a site. Args: site_uri: str URI of which site to add sitemap for. geolocation: str The geographic location. Valid values are listed in http://en.wikipedia.org/wiki/ISO_3166-1_alpha-2 uri: str (optional) URI template to update a site. Default SITE_TEMPLATE. url_params: dict (optional) Additional URL parameters to be included in the insertion request. escape_params: boolean (optional) If true, the url_parameters will be escaped before they are included in the request. converter: func (optional) Function which is executed on the server's response before it is returned. Usually this is a function like SitemapsEntryFromString which will parse the response and turn it into an object. Returns: If converter is defined, the results of running converter on the server's response. Otherwise, it will be a SitesEntry object. """ site_entry = webmastertools.SitesEntry( atom_id=atom.Id(text=site_uri), category=atom.Category( scheme='http://schemas.google.com/g/2005#kind', term='http://schemas.google.com/webmasters/tools/2007#sites-info'), geolocation=webmastertools.GeoLocation(text=geolocation) ) response = self.Put( site_entry, uri % urllib.quote_plus(site_uri), url_params=url_params, escape_params=escape_params, converter=converter) if not converter and isinstance(response, atom.Entry): return webmastertools.SitesEntryFromString(response.ToString()) return response def UpdateCrawlRate(self, site_uri, crawl_rate, uri=SITE_TEMPLATE, url_params=None, escape_params=True, converter=None): """Updates crawl rate setting of a site. Args: site_uri: str URI of which site to add sitemap for. crawl_rate: str The crawl rate for a site. Valid values are 'slower', 'normal', and 'faster'. uri: str (optional) URI template to update a site. Default SITE_TEMPLATE. url_params: dict (optional) Additional URL parameters to be included in the insertion request. escape_params: boolean (optional) If true, the url_parameters will be escaped before they are included in the request. converter: func (optional) Function which is executed on the server's response before it is returned. Usually this is a function like SitemapsEntryFromString which will parse the response and turn it into an object. Returns: If converter is defined, the results of running converter on the server's response. Otherwise, it will be a SitesEntry object. """ site_entry = webmastertools.SitesEntry( atom_id=atom.Id(text=site_uri), category=atom.Category( scheme='http://schemas.google.com/g/2005#kind', term='http://schemas.google.com/webmasters/tools/2007#sites-info'), crawl_rate=webmastertools.CrawlRate(text=crawl_rate) ) response = self.Put( site_entry, uri % urllib.quote_plus(site_uri), url_params=url_params, escape_params=escape_params, converter=converter) if not converter and isinstance(response, atom.Entry): return webmastertools.SitesEntryFromString(response.ToString()) return response def UpdatePreferredDomain(self, site_uri, preferred_domain, uri=SITE_TEMPLATE, url_params=None, escape_params=True, converter=None): """Updates preferred domain setting of a site. Note that if using 'preferwww', will also need www.example.com in account to take effect. Args: site_uri: str URI of which site to add sitemap for. preferred_domain: str The preferred domain for a site. Valid values are 'none', 'preferwww', and 'prefernowww'. uri: str (optional) URI template to update a site. Default SITE_TEMPLATE. url_params: dict (optional) Additional URL parameters to be included in the insertion request. escape_params: boolean (optional) If true, the url_parameters will be escaped before they are included in the request. converter: func (optional) Function which is executed on the server's response before it is returned. Usually this is a function like SitemapsEntryFromString which will parse the response and turn it into an object. Returns: If converter is defined, the results of running converter on the server's response. Otherwise, it will be a SitesEntry object. """ site_entry = webmastertools.SitesEntry( atom_id=atom.Id(text=site_uri), category=atom.Category( scheme='http://schemas.google.com/g/2005#kind', term='http://schemas.google.com/webmasters/tools/2007#sites-info'), preferred_domain=webmastertools.PreferredDomain(text=preferred_domain) ) response = self.Put( site_entry, uri % urllib.quote_plus(site_uri), url_params=url_params, escape_params=escape_params, converter=converter) if not converter and isinstance(response, atom.Entry): return webmastertools.SitesEntryFromString(response.ToString()) return response def UpdateEnhancedImageSearch(self, site_uri, enhanced_image_search, uri=SITE_TEMPLATE, url_params=None, escape_params=True, converter=None): """Updates enhanced image search setting of a site. Args: site_uri: str URI of which site to add sitemap for. enhanced_image_search: str The enhanced image search setting for a site. Valid values are 'true', and 'false'. uri: str (optional) URI template to update a site. Default SITE_TEMPLATE. url_params: dict (optional) Additional URL parameters to be included in the insertion request. escape_params: boolean (optional) If true, the url_parameters will be escaped before they are included in the request. converter: func (optional) Function which is executed on the server's response before it is returned. Usually this is a function like SitemapsEntryFromString which will parse the response and turn it into an object. Returns: If converter is defined, the results of running converter on the server's response. Otherwise, it will be a SitesEntry object. """ site_entry = webmastertools.SitesEntry( atom_id=atom.Id(text=site_uri), category=atom.Category( scheme='http://schemas.google.com/g/2005#kind', term='http://schemas.google.com/webmasters/tools/2007#sites-info'), enhanced_image_search=webmastertools.EnhancedImageSearch( text=enhanced_image_search) ) response = self.Put( site_entry, uri % urllib.quote_plus(site_uri), url_params=url_params, escape_params=escape_params, converter=converter) if not converter and isinstance(response, atom.Entry): return webmastertools.SitesEntryFromString(response.ToString()) return response def GetSitemapsFeed(self, site_uri, uri=SITEMAPS_FEED_TEMPLATE, converter=webmastertools.SitemapsFeedFromString): """Gets sitemaps feed of a site. Args: site_uri: str (optional) URI of which site to retrieve its sitemaps feed. uri: str (optional) URI to retrieve sites feed. converter: func (optional) Function which is executed on the server's response before it is returned. Usually this is a function like SitemapsFeedFromString which will parse the response and turn it into an object. Returns: If converter is defined, the results of running converter on the server's response. Otherwise, it will be a SitemapsFeed object. """ return self.Get(uri % {'site_id': urllib.quote_plus(site_uri)}, converter=converter) def AddSitemap(self, site_uri, sitemap_uri, sitemap_type='WEB', uri=SITEMAPS_FEED_TEMPLATE, url_params=None, escape_params=True, converter=None): """Adds a regular sitemap to a site. Args: site_uri: str URI of which site to add sitemap for. sitemap_uri: str URI of sitemap to add to a site. sitemap_type: str Type of added sitemap. Valid types: WEB, VIDEO, or CODE. uri: str (optional) URI template to add a sitemap. Default SITEMAP_FEED_TEMPLATE. url_params: dict (optional) Additional URL parameters to be included in the insertion request. escape_params: boolean (optional) If true, the url_parameters will be escaped before they are included in the request. converter: func (optional) Function which is executed on the server's response before it is returned. Usually this is a function like SitemapsEntryFromString which will parse the response and turn it into an object. Returns: If converter is defined, the results of running converter on the server's response. Otherwise, it will be a SitemapsEntry object. """ sitemap_entry = webmastertools.SitemapsEntry( atom_id=atom.Id(text=sitemap_uri), category=atom.Category( scheme='http://schemas.google.com/g/2005#kind', term='http://schemas.google.com/webmasters/tools/2007#sitemap-regular'), sitemap_type=webmastertools.SitemapType(text=sitemap_type)) response = self.Post( sitemap_entry, uri % {'site_id': urllib.quote_plus(site_uri)}, url_params=url_params, escape_params=escape_params, converter=converter) if not converter and isinstance(response, atom.Entry): return webmastertools.SitemapsEntryFromString(response.ToString()) return response def AddMobileSitemap(self, site_uri, sitemap_uri, sitemap_mobile_markup_language='XHTML', uri=SITEMAPS_FEED_TEMPLATE, url_params=None, escape_params=True, converter=None): """Adds a mobile sitemap to a site. Args: site_uri: str URI of which site to add sitemap for. sitemap_uri: str URI of sitemap to add to a site. sitemap_mobile_markup_language: str Format of added sitemap. Valid types: XHTML, WML, or cHTML. uri: str (optional) URI template to add a sitemap. Default SITEMAP_FEED_TEMPLATE. url_params: dict (optional) Additional URL parameters to be included in the insertion request. escape_params: boolean (optional) If true, the url_parameters will be escaped before they are included in the request. converter: func (optional) Function which is executed on the server's response before it is returned. Usually this is a function like SitemapsEntryFromString which will parse the response and turn it into an object. Returns: If converter is defined, the results of running converter on the server's response. Otherwise, it will be a SitemapsEntry object. """ # FIXME sitemap_entry = webmastertools.SitemapsEntry( atom_id=atom.Id(text=sitemap_uri), category=atom.Category( scheme='http://schemas.google.com/g/2005#kind', term='http://schemas.google.com/webmasters/tools/2007#sitemap-mobile'), sitemap_mobile_markup_language=\ webmastertools.SitemapMobileMarkupLanguage( text=sitemap_mobile_markup_language)) print sitemap_entry response = self.Post( sitemap_entry, uri % {'site_id': urllib.quote_plus(site_uri)}, url_params=url_params, escape_params=escape_params, converter=converter) if not converter and isinstance(response, atom.Entry): return webmastertools.SitemapsEntryFromString(response.ToString()) return response def AddNewsSitemap(self, site_uri, sitemap_uri, sitemap_news_publication_label, uri=SITEMAPS_FEED_TEMPLATE, url_params=None, escape_params=True, converter=None): """Adds a news sitemap to a site. Args: site_uri: str URI of which site to add sitemap for. sitemap_uri: str URI of sitemap to add to a site. sitemap_news_publication_label: str, list of str Publication Labels for sitemap. uri: str (optional) URI template to add a sitemap. Default SITEMAP_FEED_TEMPLATE. url_params: dict (optional) Additional URL parameters to be included in the insertion request. escape_params: boolean (optional) If true, the url_parameters will be escaped before they are included in the request. converter: func (optional) Function which is executed on the server's response before it is returned. Usually this is a function like SitemapsEntryFromString which will parse the response and turn it into an object. Returns: If converter is defined, the results of running converter on the server's response. Otherwise, it will be a SitemapsEntry object. """ sitemap_entry = webmastertools.SitemapsEntry( atom_id=atom.Id(text=sitemap_uri), category=atom.Category( scheme='http://schemas.google.com/g/2005#kind', term='http://schemas.google.com/webmasters/tools/2007#sitemap-news'), sitemap_news_publication_label=[], ) if isinstance(sitemap_news_publication_label, str): sitemap_news_publication_label = [sitemap_news_publication_label] for label in sitemap_news_publication_label: sitemap_entry.sitemap_news_publication_label.append( webmastertools.SitemapNewsPublicationLabel(text=label)) print sitemap_entry response = self.Post( sitemap_entry, uri % {'site_id': urllib.quote_plus(site_uri)}, url_params=url_params, escape_params=escape_params, converter=converter) if not converter and isinstance(response, atom.Entry): return webmastertools.SitemapsEntryFromString(response.ToString()) return response def DeleteSitemap(self, site_uri, sitemap_uri, uri=SITEMAP_TEMPLATE, url_params=None, escape_params=True): """Removes a sitemap from a site. Args: site_uri: str URI of which site to remove a sitemap from. sitemap_uri: str URI of sitemap to remove from a site. uri: str (optional) A URI template to send DELETE request. Default SITEMAP_TEMPLATE. url_params: dict (optional) Additional URL parameters to be included in the insertion request. escape_params: boolean (optional) If true, the url_parameters will be escaped before they are included in the request. Returns: True if the delete succeeded. """ return self.Delete( uri % {'site_id': urllib.quote_plus(site_uri), 'sitemap_id': urllib.quote_plus(sitemap_uri)}, url_params=url_params, escape_params=escape_params) gdata-2.0.18/src/gdata/calendar_resource/0000750036466300116100000000000012156625015020261 5ustar afshareng00000000000000gdata-2.0.18/src/gdata/calendar_resource/data.py0000640036466300116100000001435312156622363021556 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright 2009 Google Inc. All Rights Reserved. # # 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. """Data model for parsing and generating XML for the Calendar Resource API.""" __author__ = 'Vic Fryzel ' import atom.core import atom.data import gdata.apps import gdata.apps_property import gdata.data # This is required to work around a naming conflict between the Google # Spreadsheets API and Python's built-in property function pyproperty = property # The apps:property name of the resourceId property RESOURCE_ID_NAME = 'resourceId' # The apps:property name of the resourceCommonName property RESOURCE_COMMON_NAME_NAME = 'resourceCommonName' # The apps:property name of the resourceDescription property RESOURCE_DESCRIPTION_NAME = 'resourceDescription' # The apps:property name of the resourceType property RESOURCE_TYPE_NAME = 'resourceType' # The apps:property name of the resourceEmail property RESOURCE_EMAIL_NAME = 'resourceEmail' class CalendarResourceEntry(gdata.data.GDEntry): """Represents a Calendar Resource entry in object form.""" property = [gdata.apps_property.AppsProperty] def _GetProperty(self, name): """Get the apps:property value with the given name. Args: name: string Name of the apps:property value to get. Returns: The apps:property value with the given name, or None if the name was invalid. """ for p in self.property: if p.name == name: return p.value return None def _SetProperty(self, name, value): """Set the apps:property value with the given name to the given value. Args: name: string Name of the apps:property value to set. value: string Value to give the apps:property value with the given name. """ for i in range(len(self.property)): if self.property[i].name == name: self.property[i].value = value return self.property.append(gdata.apps_property.AppsProperty(name=name, value=value)) def GetResourceId(self): """Get the resource ID of this Calendar Resource object. Returns: The resource ID of this Calendar Resource object as a string or None. """ return self._GetProperty(RESOURCE_ID_NAME) def SetResourceId(self, value): """Set the resource ID of this Calendar Resource object. Args: value: string The new resource ID value to give this object. """ self._SetProperty(RESOURCE_ID_NAME, value) resource_id = pyproperty(GetResourceId, SetResourceId) def GetResourceCommonName(self): """Get the common name of this Calendar Resource object. Returns: The common name of this Calendar Resource object as a string or None. """ return self._GetProperty(RESOURCE_COMMON_NAME_NAME) def SetResourceCommonName(self, value): """Set the common name of this Calendar Resource object. Args: value: string The new common name value to give this object. """ self._SetProperty(RESOURCE_COMMON_NAME_NAME, value) resource_common_name = pyproperty( GetResourceCommonName, SetResourceCommonName) def GetResourceDescription(self): """Get the description of this Calendar Resource object. Returns: The description of this Calendar Resource object as a string or None. """ return self._GetProperty(RESOURCE_DESCRIPTION_NAME) def SetResourceDescription(self, value): """Set the description of this Calendar Resource object. Args: value: string The new description value to give this object. """ self._SetProperty(RESOURCE_DESCRIPTION_NAME, value) resource_description = pyproperty( GetResourceDescription, SetResourceDescription) def GetResourceType(self): """Get the type of this Calendar Resource object. Returns: The type of this Calendar Resource object as a string or None. """ return self._GetProperty(RESOURCE_TYPE_NAME) def SetResourceType(self, value): """Set the type value of this Calendar Resource object. Args: value: string The new type value to give this object. """ self._SetProperty(RESOURCE_TYPE_NAME, value) resource_type = pyproperty(GetResourceType, SetResourceType) def GetResourceEmail(self): """Get the email of this Calendar Resource object. Returns: The email of this Calendar Resource object as a string or None. """ return self._GetProperty(RESOURCE_EMAIL_NAME) resource_email = pyproperty(GetResourceEmail) def __init__(self, resource_id=None, resource_common_name=None, resource_description=None, resource_type=None, *args, **kwargs): """Constructs a new CalendarResourceEntry object with the given arguments. Args: resource_id: string (optional) The resource ID to give this new object. resource_common_name: string (optional) The common name to give this new object. resource_description: string (optional) The description to give this new object. resource_type: string (optional) The type to give this new object. args: The other parameters to pass to gdata.entry.GDEntry constructor. kwargs: The other parameters to pass to gdata.entry.GDEntry constructor. """ super(CalendarResourceEntry, self).__init__(*args, **kwargs) if resource_id: self.resource_id = resource_id if resource_common_name: self.resource_common_name = resource_common_name if resource_description: self.resource_description = resource_description if resource_type: self.resource_type = resource_type class CalendarResourceFeed(gdata.data.GDFeed): """Represents a feed of CalendarResourceEntry objects.""" # Override entry so that this feed knows how to type its list of entries. entry = [CalendarResourceEntry] gdata-2.0.18/src/gdata/calendar_resource/__init__.py0000640036466300116100000000000112156622363022365 0ustar afshareng00000000000000 gdata-2.0.18/src/gdata/calendar_resource/client.py0000640036466300116100000001605212156622363022121 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright 2009 Google Inc. All Rights Reserved. # # 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. """CalendarResourceClient simplifies Calendar Resources API calls. CalendarResourceClient extends gdata.client.GDClient to ease interaction with the Google Apps Calendar Resources API. These interactions include the ability to create, retrieve, update, and delete calendar resources in a Google Apps domain. """ __author__ = 'Vic Fryzel ' import gdata.calendar_resource.data import gdata.client import urllib # Feed URI template. This must end with a / # The strings in this template are eventually replaced with the API version # and Google Apps domain name, respectively. RESOURCE_FEED_TEMPLATE = '/a/feeds/calendar/resource/%s/%s/' class CalendarResourceClient(gdata.client.GDClient): """Client extension for the Google Calendar Resource API service. Attributes: host: string The hostname for the Calendar Resouce API service. api_version: string The version of the Calendar Resource API. """ host = 'apps-apis.google.com' api_version = '2.0' auth_service = 'apps' auth_scopes = gdata.gauth.AUTH_SCOPES['apps'] ssl = True def __init__(self, domain, auth_token=None, **kwargs): """Constructs a new client for the Calendar Resource API. Args: domain: string The Google Apps domain with Calendar Resources. auth_token: (optional) gdata.gauth.ClientLoginToken, AuthSubToken, or OAuthToken which authorizes this client to edit the calendar resource data. kwargs: The other parameters to pass to the gdata.client.GDClient constructor. """ gdata.client.GDClient.__init__(self, auth_token=auth_token, **kwargs) self.domain = domain def make_resource_feed_uri(self, resource_id=None, params=None): """Creates a resource feed URI for the Calendar Resource API. Using this client's Google Apps domain, create a feed URI for calendar resources in that domain. If a resource_id is provided, return a URI for that specific resource. If params are provided, append them as GET params. Args: resource_id: string (optional) The ID of the calendar resource for which to make a feed URI. params: dict (optional) key -> value params to append as GET vars to the URI. Example: params={'start': 'my-resource-id'} Returns: A string giving the URI for calendar resources for this client's Google Apps domain. """ uri = RESOURCE_FEED_TEMPLATE % (self.api_version, self.domain) if resource_id: uri += resource_id if params: uri += '?' + urllib.urlencode(params) return uri MakeResourceFeedUri = make_resource_feed_uri def get_resource_feed(self, uri=None, **kwargs): """Fetches a ResourceFeed of calendar resources at the given URI. Args: uri: string The URI of the feed to pull. kwargs: The other parameters to pass to gdata.client.GDClient.get_feed(). Returns: A ResourceFeed object representing the feed at the given URI. """ if uri is None: uri = self.MakeResourceFeedUri() return self.get_feed( uri, desired_class=gdata.calendar_resource.data.CalendarResourceFeed, **kwargs) GetResourceFeed = get_resource_feed def get_resource(self, uri=None, resource_id=None, **kwargs): """Fetches a single calendar resource by resource ID. Args: uri: string The base URI of the feed from which to fetch the resource. resource_id: string The string ID of the Resource to fetch. kwargs: The other parameters to pass to gdata.client.GDClient.get_entry(). Returns: A Resource object representing the calendar resource with the given base URI and resource ID. """ if uri is None: uri = self.MakeResourceFeedUri(resource_id) return self.get_entry( uri, desired_class=gdata.calendar_resource.data.CalendarResourceEntry, **kwargs) GetResource = get_resource def create_resource(self, resource_id, resource_common_name=None, resource_description=None, resource_type=None, **kwargs): """Creates a calendar resource with the given properties. Args: resource_id: string The resource ID of the calendar resource. resource_common_name: string (optional) The common name of the resource. resource_description: string (optional) The description of the resource. resource_type: string (optional) The type of the resource. kwargs: The other parameters to pass to gdata.client.GDClient.post(). Returns: gdata.calendar_resource.data.CalendarResourceEntry of the new resource. """ new_resource = gdata.calendar_resource.data.CalendarResourceEntry( resource_id=resource_id, resource_common_name=resource_common_name, resource_description=resource_description, resource_type=resource_type) return self.post(new_resource, self.MakeResourceFeedUri(), **kwargs) CreateResource = create_resource def update_resource(self, resource_id, resource_common_name=None, resource_description=None, resource_type=None, **kwargs): """Updates the calendar resource with the given resource ID. Args: resource_id: string The resource ID of the calendar resource to update. resource_common_name: string (optional) The common name to give the resource. resource_description: string (optional) The description to give the resource. resource_type: string (optional) The type to give the resource. kwargs: The other parameters to pass to gdata.client.GDClient.update(). Returns: gdata.calendar_resource.data.CalendarResourceEntry of the updated resource. """ new_resource = gdata.calendar_resource.data.CalendarResourceEntry( resource_id=resource_id, resource_common_name=resource_common_name, resource_description=resource_description, resource_type=resource_type) return self.update(new_resource, uri=self.MakeResourceFeedUri(resource_id), **kwargs) UpdateResource = update_resource def delete_resource(self, resource_id, **kwargs): """Deletes the calendar resource with the given resource ID. Args: resource_id: string The resource ID of the calendar resource to delete. kwargs: The other parameters to pass to gdata.client.GDClient.delete() Returns: An HTTP response object. See gdata.client.request(). """ return self.delete(self.MakeResourceFeedUri(resource_id), **kwargs) DeleteResource = delete_resource gdata-2.0.18/src/gdata/acl/0000750036466300116100000000000012156625015015340 5ustar afshareng00000000000000gdata-2.0.18/src/gdata/acl/data.py0000640036466300116100000000354512156622363016636 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright (C) 2009 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. """Contains the data classes of the Google Access Control List (ACL) Extension""" __author__ = 'j.s@google.com (Jeff Scudder)' import atom.core import atom.data import gdata.data import gdata.opensearch.data GACL_TEMPLATE = '{http://schemas.google.com/acl/2007}%s' class AclRole(atom.core.XmlElement): """Describes the role of an entry in an access control list.""" _qname = GACL_TEMPLATE % 'role' value = 'value' class AclAdditionalRole(atom.core.XmlElement): """Describes an additionalRole element.""" _qname = GACL_TEMPLATE % 'additionalRole' value = 'value' class AclScope(atom.core.XmlElement): """Describes the scope of an entry in an access control list.""" _qname = GACL_TEMPLATE % 'scope' type = 'type' value = 'value' class AclWithKey(atom.core.XmlElement): """Describes a key that can be used to access a document.""" _qname = GACL_TEMPLATE % 'withKey' key = 'key' role = AclRole additional_role = AclAdditionalRole class AclEntry(gdata.data.GDEntry): """Describes an entry in a feed of an access control list (ACL).""" scope = AclScope role = AclRole with_key = AclWithKey additional_role = AclAdditionalRole class AclFeed(gdata.data.GDFeed): """Describes a feed of an access control list (ACL).""" entry = [AclEntry] gdata-2.0.18/src/gdata/acl/__init__.py0000640036466300116100000000113012156622363017450 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright (C) 2009 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. gdata-2.0.18/src/gdata/contentforshopping/0000750036466300116100000000000012156625015020532 5ustar afshareng00000000000000gdata-2.0.18/src/gdata/contentforshopping/data.py0000640036466300116100000011732412156622363022031 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright (C) 2010-2011 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. """GData definitions for Content API for Shopping""" __author__ = 'afshar (Ali Afshar), dhermes (Daniel Hermes)' import atom.core import atom.data import atom.http_core import gdata.data ATOM_NAMESPACE = 'http://www.w3.org/2005/Atom' GD_NAMESPACE = 'http://schemas.google.com/g/2005' GD_NAMESPACE_TEMPLATE = '{http://schemas.google.com/g/2005}%s' SC_NAMESPACE_TEMPLATE = ('{http://schemas.google.com/' 'structuredcontent/2009}%s') SCP_NAMESPACE_TEMPLATE = ('{http://schemas.google.com/' 'structuredcontent/2009/products}%s') # Content API for Shopping, general (sc) attributes class ProductId(atom.core.XmlElement): """sc:id element It is required that all inserted products are provided with a unique alphanumeric ID, in this element. """ _qname = SC_NAMESPACE_TEMPLATE % 'id' class ImageLink(atom.core.XmlElement): """sc:image_link element This is the URL of an associated image for a product. Please use full size images (400x400 pixels or larger), not thumbnails. """ _qname = SC_NAMESPACE_TEMPLATE % 'image_link' class AdditionalImageLink(atom.core.XmlElement): """sc:additional_image_link element The URLs of any additional images for the product. This tag may be repeated. """ _qname = SC_NAMESPACE_TEMPLATE % 'additional_image_link' class Channel(atom.core.XmlElement): """ sc:channel element The channel for the product. Supported values are: 'online', 'local' """ _qname = SC_NAMESPACE_TEMPLATE % 'channel' class ContentLanguage(atom.core.XmlElement): """ sc:content_language element Language used in the item content for the product """ _qname = SC_NAMESPACE_TEMPLATE % 'content_language' class TargetCountry(atom.core.XmlElement): """ sc:target_country element The target country of the product """ _qname = SC_NAMESPACE_TEMPLATE % 'target_country' class ExpirationDate(atom.core.XmlElement): """sc:expiration_date This is the date when the product listing will expire. If omitted, this will default to 30 days after the product was created. """ _qname = SC_NAMESPACE_TEMPLATE % 'expiration_date' class Adult(atom.core.XmlElement): """sc:adult element Indicates whether the content is targeted towards adults, with possible values of "true" or "false". Defaults to "false". """ _qname = SC_NAMESPACE_TEMPLATE % 'adult' class Attribute(atom.core.XmlElement): """sc:attribute element Generic attribute used for generic projection and to define custom elements. """ _qname = SC_NAMESPACE_TEMPLATE % 'attribute' name = 'name' type = 'type' unit = 'unit' class Group(atom.core.XmlElement): """sc:group element Generic group used for generic projection and to define custom elements groups. """ _qname = SC_NAMESPACE_TEMPLATE % 'group' name = 'name' attribute = [Attribute] # Destination Attributes (to be used with app:control element) class RequiredDestination(atom.core.XmlElement): """sc:required_destination element This element defines the required destination for a product, namely "ProductSearch", "ProductAds" or "CommerceSearch". It should be added to the app:control element (ProductEntry's "control" attribute) to specify where the product should appear in search APIs. By default, when omitted, the api attempts to upload to as many destinations as possible. """ _qname = SC_NAMESPACE_TEMPLATE % 'required_destination' dest = 'dest' class ValidateDestination(atom.core.XmlElement): """sc:validate_destination element This element defines the validate destination for a product, namely "ProductSearch", "ProductAds" or "CommerceSearch". It should be added to the app:control element (ProductEntry's "control" attribute) to specify which the destinations you would like error info for. """ _qname = SC_NAMESPACE_TEMPLATE % 'validate_destination' dest = 'dest' class ExcludedDestination(atom.core.XmlElement): """sc:excluded_destination element This element defines the required destination for a product, namely "ProductSearch", "ProductAds" or "CommerceSearch". It should be added to the app:control element (ProductEntry's "control" attribute) to specify where the product should not appear in search APIs. """ _qname = SC_NAMESPACE_TEMPLATE % 'excluded_destination' dest = 'dest' class Status(atom.core.XmlElement): """sc:status element This element defines the status of an element in a particular destination. It has a destination and an actual status such as enlisted, review, or disapproved. """ _qname = SC_NAMESPACE_TEMPLATE % 'status' dest = 'dest' status = 'status' # Warning Attributes (to be used with app:control element) class Code(atom.core.XmlElement): """sc:code element The warning code. Currently validation/missing_recommended is the only code used. """ _qname = SC_NAMESPACE_TEMPLATE % 'code' class Domain(atom.core.XmlElement): """sc:domain element The scope of the warning. A comma-separated list of destinations, for example: ProductSearch. """ _qname = SC_NAMESPACE_TEMPLATE % 'domain' class Location(atom.core.XmlElement): """sc:location element The name of the product element that has raised the warning. This may be any valid product element. """ _qname = SC_NAMESPACE_TEMPLATE % 'location' class Message(atom.core.XmlElement): """sc:message element A plain text description of the warning. """ _qname = SC_NAMESPACE_TEMPLATE % 'message' class WarningElement(atom.core.XmlElement): """sc:warning element Container element for an individual warning. """ _qname = SC_NAMESPACE_TEMPLATE % 'warning' code = Code domain = Domain location = Location message = Message class Warnings(atom.core.XmlElement): """sc:warnings element Container element for the list of warnings. """ _qname = SC_NAMESPACE_TEMPLATE % 'warnings' warnings = [WarningElement] class Datapoint(atom.core.XmlElement): """sc:datapoint element Datapoint representing click data for an item on a given day. """ _qname = SC_NAMESPACE_TEMPLATE % 'datapoint' clicks = 'clicks' date = 'date' paid_clicks = 'paid_clicks' class Performance(atom.core.XmlElement): """sc:performance element Container element for daily click data. """ _qname = SC_NAMESPACE_TEMPLATE % 'performance' datapoint = [Datapoint] class ProductControl(atom.data.Control): """ app:control element overridden to provide additional elements in the sc namespace. """ _qname = atom.data.Control._qname[1] required_destination = [RequiredDestination] validate_destination = [ValidateDestination] excluded_destination = [ExcludedDestination] status = [Status] warnings = Warnings # Content API for Shopping, product (scp) attributes class Author(atom.core.XmlElement): """ scp:author element Defines the author of the information, recommended for books. """ _qname = SCP_NAMESPACE_TEMPLATE % 'author' class Availability(atom.core.XmlElement): """ scp:availability element The retailer's suggested label for product availability. Supported values include: 'in stock', 'out of stock', 'limited availability', 'available for order', and 'preorder'. """ _qname = SCP_NAMESPACE_TEMPLATE % 'availability' class Brand(atom.core.XmlElement): """ scp:brand element The brand of the product """ _qname = SCP_NAMESPACE_TEMPLATE % 'brand' class Color(atom.core.XmlElement): """scp:color element The color of the product. """ _qname = SCP_NAMESPACE_TEMPLATE % 'color' class Condition(atom.core.XmlElement): """scp:condition element The condition of the product, one of "new", "used", "refurbished" """ _qname = SCP_NAMESPACE_TEMPLATE % 'condition' class Edition(atom.core.XmlElement): """scp:edition element The edition of the product. Recommended for products with multiple editions such as collectors' editions etc, such as books. """ _qname = SCP_NAMESPACE_TEMPLATE % 'edition' class Feature(atom.core.XmlElement): """scp:feature element A product feature. A product may have multiple features, each being text, for example a smartphone may have features: "wifi", "gps" etc. """ _qname = SCP_NAMESPACE_TEMPLATE % 'feature' class FeaturedProduct(atom.core.XmlElement): """scp:featured_product element Used to indicate that this item is a special, featured product; Supported values are: "true", "false". """ _qname = SCP_NAMESPACE_TEMPLATE % 'featured_product' class Gender(atom.core.XmlElement): """scp:gender element The gender for the item. Supported values are: 'unisex', 'female', 'male' Note: This tag is required if the google product type is part of Apparel & Accessories. """ _qname = SCP_NAMESPACE_TEMPLATE % 'gender' class Genre(atom.core.XmlElement): """scp:genre element Describes the genre of a product, eg "comedy". Strongly recommended for media. """ _qname = SCP_NAMESPACE_TEMPLATE % 'genre' class GoogleProductCategory(atom.core.XmlElement): """scp:google_product_category element The product's google category. The value must be one of the categories listed in the Product type taxonomy, which can be found at http://www.google.com/support/merchants/bin/answer.py?answer=160081. Note that & and > characters must be encoded as & and > """ _qname = SCP_NAMESPACE_TEMPLATE % 'google_product_category' class Gtin(atom.core.XmlElement): """scp:gtin element GTIN of the product (isbn/upc/ean) """ _qname = SCP_NAMESPACE_TEMPLATE % 'gtin' class ItemGroupID(atom.core.XmlElement): """scp:item_group_id element The identifier for products with variants. This id is used to link items which have different values for the fields: 'color', 'material', 'pattern', 'size' but are the same item, for example a shirt with different sizes. Note: This tag is required for all product variants. """ _qname = SCP_NAMESPACE_TEMPLATE % 'item_group_id' class Manufacturer(atom.core.XmlElement): """scp:manufacturer element Manufacturer of the product. """ _qname = SCP_NAMESPACE_TEMPLATE % 'manufacturer' class Material(atom.core.XmlElement): """scp:material element The material the product is made of. """ _qname = SCP_NAMESPACE_TEMPLATE % 'material' class Mpn(atom.core.XmlElement): """scp:mpn element Manufacturer's Part Number. A unique code determined by the manufacturer for the product. """ _qname = SCP_NAMESPACE_TEMPLATE % 'mpn' class Pattern(atom.core.XmlElement): """scp:pattern element The pattern of the product. (e.g. polka dots) """ _qname = SCP_NAMESPACE_TEMPLATE % 'pattern' class Price(atom.core.XmlElement): """scp:price element The price of the product. The unit attribute must be set, and should represent the currency. Note: Required Element """ _qname = SCP_NAMESPACE_TEMPLATE % 'price' unit = 'unit' class ProductType(atom.core.XmlElement): """scp:product_type element Describes the type of product. A taxonomy of available product types is listed at http://www.google.com/basepages/producttype/taxonomy.txt and the entire line in the taxonomy should be included, for example "Electronics > Video > Projectors". """ _qname = SCP_NAMESPACE_TEMPLATE % 'product_type' class Quantity(atom.core.XmlElement): """scp:quantity element The number of items available. A value of 0 indicates items that are currently out of stock. """ _qname = SCP_NAMESPACE_TEMPLATE % 'quantity' class ShippingPrice(atom.core.XmlElement): """scp:shipping_price element Fixed shipping price, represented as a number. Specify the currency as the "unit" attribute". This element should be placed inside the scp:shipping element. """ _qname = SCP_NAMESPACE_TEMPLATE % 'shipping_price' unit = 'unit' class ShippingCountry(atom.core.XmlElement): """scp:shipping_country element The two-letter ISO 3166 country code for the country to which an item will ship. This element should be placed inside the scp:shipping element. """ _qname = SCP_NAMESPACE_TEMPLATE % 'shipping_country' class ShippingRegion(atom.core.XmlElement): """scp:shipping_region element The geographic region to which a shipping rate applies, e.g., in the US, the two-letter state abbreviation, ZIP code, or ZIP code range using * wildcard. This element should be placed inside the scp:shipping element. """ _qname = SCP_NAMESPACE_TEMPLATE % 'shipping_region' class ShippingService(atom.core.XmlElement): """scp:shipping_service element A free-form description of the service class or delivery speed. This element should be placed inside the scp:shipping element. """ _qname = SCP_NAMESPACE_TEMPLATE % 'shipping_service' class Shipping(atom.core.XmlElement): """scp:shipping element Container for the shipping rules as provided by the shipping_country, shipping_price, shipping_region and shipping_service tags. """ _qname = SCP_NAMESPACE_TEMPLATE % 'shipping' shipping_price = ShippingPrice shipping_country = ShippingCountry shipping_service = ShippingService shipping_region = ShippingRegion class ShippingWeight(atom.core.XmlElement): """scp:shipping_weight element The shipping weight of a product. Requires a value and a unit using the unit attribute. Valid units include lb, pound, oz, ounce, g, gram, kg, kilogram. """ _qname = SCP_NAMESPACE_TEMPLATE % 'shipping_weight' unit = 'unit' class Size(atom.core.XmlElement): """scp:size element Available sizes of an item. Appropriate values include: "small", "medium", "large", etc. The product enttry may contain multiple sizes, to indicate the available sizes. """ _qname = SCP_NAMESPACE_TEMPLATE % 'size' class TaxRate(atom.core.XmlElement): """scp:tax_rate element The tax rate as a percent of the item price, i.e., number, as a percentage. This element should be placed inside the scp:tax (Tax class) element. """ _qname = SCP_NAMESPACE_TEMPLATE % 'tax_rate' class TaxCountry(atom.core.XmlElement): """scp:tax_country element The country an item is taxed in (as a two-letter ISO 3166 country code). This element should be placed inside the scp:tax (Tax class) element. """ _qname = SCP_NAMESPACE_TEMPLATE % 'tax_country' class TaxRegion(atom.core.XmlElement): """scp:tax_region element The geographic region that a tax rate applies to, e.g., in the US, the two-letter state abbreviation, ZIP code, or ZIP code range using * wildcard. This element should be placed inside the scp:tax (Tax class) element. """ _qname = SCP_NAMESPACE_TEMPLATE % 'tax_region' class TaxShip(atom.core.XmlElement): """scp:tax_ship element Whether tax is charged on shipping for this product. The default value is "false". This element should be placed inside the scp:tax (Tax class) element. """ _qname = SCP_NAMESPACE_TEMPLATE % 'tax_ship' class Tax(atom.core.XmlElement): """scp:tax element Container for the tax rules for this product. Containing the tax_rate, tax_country, tax_region, and tax_ship elements """ _qname = SCP_NAMESPACE_TEMPLATE % 'tax' tax_rate = TaxRate tax_country = TaxCountry tax_region = TaxRegion tax_ship = TaxShip class Year(atom.core.XmlElement): """scp:year element The year the product was produced. Expects four digits """ _qname = SCP_NAMESPACE_TEMPLATE % 'year' class IdentifierExists(atom.core.XmlElement): """scp:identifier_exists element Specify as true if the item has no manufacturer part number or any other industry standard product identifier. """ _qname = SCP_NAMESPACE_TEMPLATE % 'identifier_exists' class UnitPricingMeasure(atom.core.XmlElement): """scp:unit_pricing_measure element The dimension by which the item is sold. """ _qname = SCP_NAMESPACE_TEMPLATE % 'unit_pricing_measure' unit = 'unit' class UnitPricingBaseMeasure(atom.core.XmlElement): """scp:unit_pricing_base_measure element Your preference of the denominator of the unit price. """ _qname = SCP_NAMESPACE_TEMPLATE % 'unit_pricing_base_measure' unit = 'unit' class EnergyEfficiencyClass(atom.core.XmlElement): """scp:energy_efficiency_class element The item's energy efficiency class. """ _qname = SCP_NAMESPACE_TEMPLATE % 'energy_efficiency_class' class Multipack(atom.core.XmlElement): """scp:ultipack element The number of products in a merchant-defined custom multipack """ _qname = SCP_NAMESPACE_TEMPLATE % 'multipack' class ProductEntry(gdata.data.BatchEntry): """Product entry containing product information The elements of this entry that are used are made up of five different namespaces. They are: atom: - Atom app: - Atom Publishing Protocol gd: - Google Data API sc: - Content API for Shopping, general attributes scp: - Content API for Shopping, product attributes Only the sc and scp namespace elements are defined here, but additional useful elements are defined in superclasses. The following attributes are encoded as XML elements in the Atomn (atom:) namespace: title, link, entry, id, category, content, author, created updated. Among these, the title, content and link tags are part of the required Content for Shopping API so we document them here. .. attribute:: title The title of the product. This should be a :class:`atom.data.Title` element, for example:: entry = ProductEntry() entry.title = atom.data.Title(u'32GB MP3 Player') .. attribute:: content The description of the item. This should be a :class:`atom.data.Content` element, for example:: entry = ProductEntry() entry.content = atom.data.Content('My item description') .. attribute:: link A link to a page where the item can be purchased. This should be a :class:`atom.data.Link` element, for example:: link = atom.data.Link(rel='alternate', type='text/html', href='http://www.somehost.com/123456jsh9') entry = ProductEntry() entry.link.append(link) .. attribute:: additional_image_link A list of additional links to images of the product. Each link should be an :class:`AdditionalImageLink` element, for example:: entry = ProductEntry() entry.additional_image_link.append( AdditionalImageLink('http://myshop/cdplayer.jpg')) .. attribute:: author The author of the product. This should be a :class:`Author` element, for example:: entry = ProductEntry() entry.author = atom.data.Author(u'Isaac Asimov') .. attribute:: attribute List of generic attributes. This should be a list of :class:`Attribute` elements, for example:: attribute = Attribute('foo') attribute.name = 'bar' attribute.type = 'baz' attribute.unit = 'kg' entry = ProductEntry() entry.attributes.append(attribute) .. attribute:: availability The avilability of a product. This should be an :class:`Availability` instance, for example:: entry = ProductEntry() entry.availability = Availability('in stock') .. attribute:: brand The brand of a product. This should be a :class:`Brand` element, for example:: entry = ProductEntry() entry.brand = Brand(u'Sony') .. attribute:: channel The channel for the product. Supported values are: 'online', 'local' This should be a :class:`Channel` element, for example:: entry = ProductEntry() entry.channel = Channel('online') .. attribute:: color The color of a product. This should be a :class:`Color` element, for example:: entry = ProductEntry() entry.color = Color(u'purple') .. attribute:: condition The condition of a product. This should be a :class:`Condition` element, for example:: entry = ProductEntry() entry.condition = Condition(u'new') .. attribute:: content_language The language for the product. This should be a :class:`ContentLanguage` element, for example:: entry = ProductEntry() entry.content_language = ContentLanguage('EN') .. attribute:: control Overrides :class:`atom.data.Control` to provide additional elements required_destination and excluded_destination in the sc namespace This should be a :class:`ProductControl` element. .. attribute:: edition The edition of the product. This should be a :class:`Edition` element, for example:: entry = ProductEntry() entry.edition = Edition('1') .. attribute:: expiration_date The expiration date of this product listing. This should be a :class:`ExpirationDate` element, for example:: entry = ProductEntry() entry.expiration_date = ExpirationDate('2011-22-03') .. attribute:: feature A list of features for this product. Each feature should be a :class:`Feature` element, for example:: entry = ProductEntry() entry.feature.append(Feature(u'wifi')) entry.feature.append(Feature(u'gps')) .. attribute:: featured_product Whether the product is featured. This should be a :class:`FeaturedProduct` element, for example:: entry = ProductEntry() entry.featured_product = FeaturedProduct('true') .. attribute:: gender The gender for the item. Supported values are: 'unisex', 'female', 'male' This should be a :class:`Gender` element, for example:: entry = ProductEntry() entry.gender = Gender('female') .. attribute:: genre The genre of the product. This should be a :class:`Genre` element, for example:: entry = ProductEntry() entry.genre = Genre(u'comedy') .. attribute:: google_product_category The product's google category. Value must come from taxonomy listed at http://www.google.com/support/merchants/bin/answer.py?answer=160081 This should be a :class:`GoogleProductCategory` element, for example:: entry = ProductEntry() entry.google_product_category = GoogleProductCategory( 'Animals > Live Animals') .. attribute:: gtin The gtin for this product. This should be a :class:`Gtin` element, for example:: entry = ProductEntry() entry.gtin = Gtin('A888998877997') .. attribute:: image_link A link to the product image. This link should be an :class:`ImageLink` element, for example:: entry = ProductEntry() entry.image_link = ImageLink('http://myshop/cdplayer.jpg') .. attribute:: item_group_id The identifier for products with variants. This id is used to link items which have different values for the fields: 'color', 'material', 'pattern', 'size' but are the same item, for example a shirt with different sizes. This should be a :class:`ItemGroupID` element, for example:: entry = ProductEntry() entry.item_group_id = ItemGroupID('R1726122') .. attribute:: manufacturer The manufacturer of the product. This should be a :class:`Manufacturer` element, for example:: entry = ProductEntry() entry.manufacturer = Manufacturer('Sony') .. attribute:: material The material the product is made of. This should be a :class:`Material` element, for example:: entry = ProductEntry() entry.material = Material('cotton') .. attribute:: mpn The manufacturer's part number for this product. This should be a :class:`Mpn` element, for example:: entry = ProductEntry() entry.mpn = Mpn('cd700199US') .. attribute:: pattern The pattern of the product. This should be a :class:`Pattern` element, for example:: entry = ProductEntry() entry.pattern = Pattern('polka dots') .. attribute:: performance The performance of the product. This should be a :class:`Performance` element. .. attribute:: price The price for this product. This should be a :class:`Price` element, including a unit argument to indicate the currency, for example:: entry = ProductEntry() entry.price = Price('20.00', unit='USD') .. attribute:: product_id A link to the product image. This link should be an :class:`ProductId` element, for example:: entry = ProductEntry() entry.product_id = ProductId('ABC1234') .. attribute:: product_type The type of product. This should be a :class:`ProductType` element, for example:: entry = ProductEntry() entry.product_type = ProductType("Electronics > Video > Projectors") .. attribute:: quantity The quantity of product available in stock. This should be a :class:`Quantity` element, for example:: entry = ProductEntry() entry.quantity = Quantity('100') .. attribute:: shipping The shipping rules for the product. This should be a :class:`Shipping` with the necessary rules embedded as elements, for example:: shipping = Shipping() shipping.shipping_price = ShippingPrice('10.00', unit='USD') entry = ProductEntry() entry.shipping.append(shipping) .. attribute:: shipping_weight The shipping weight for this product. This should be a :class:`ShippingWeight` element, including a unit parameter for the unit of weight, for example:: entry = ProductEntry() entry.shipping_weight = ShippingWeight('10.45', unit='kg') .. attribute:: size A list of the available sizes for this product. Each item in this list should be a :class:`Size` element, for example:: entry = ProductEntry() entry.size.append(Size('Small')) entry.size.append(Size('Medium')) entry.size.append(Size('Large')) .. attribute:: target_country The target country for the product. This should be a :class:`TargetCountry` element, for example:: entry = ProductEntry() entry.target_country = TargetCountry('US') .. attribute:: tax The tax rules for this product. This should be a :class:`Tax` element, with the tax rule elements embedded within, for example:: tax = Tax() tax.tax_rate = TaxRate('17.5') entry = ProductEntry() entry.tax.append(tax) .. attribute:: year The year the product was created. This should be a :class:`Year` element, for example:: entry = ProductEntry() entry.year = Year('2001') """ additional_image_link = [AdditionalImageLink] author = Author attribute = [Attribute] availability = Availability brand = Brand channel = Channel color = Color condition = Condition content_language = ContentLanguage control = ProductControl edition = Edition expiration_date = ExpirationDate feature = [Feature] featured_product = FeaturedProduct gender = Gender genre = Genre google_product_category = GoogleProductCategory group = [Group] gtin = Gtin image_link = ImageLink item_group_id = ItemGroupID manufacturer = Manufacturer material = Material mpn = Mpn pattern = Pattern performance = Performance price = Price product_id = ProductId product_type = ProductType quantity = Quantity shipping = [Shipping] shipping_weight = ShippingWeight size = [Size] target_country = TargetCountry tax = [Tax] year = Year identifier_exists = IdentifierExists unit_pricing_measure = UnitPricingMeasure unit_pricing_base_measure = UnitPricingBaseMeasure energy_efficiency_class = EnergyEfficiencyClass multipack = Multipack def get_batch_errors(self): """Attempts to parse errors from atom:content element. If the atom:content element is type application/vnd.google.gdata.error+xml, then it will contain a gd:errors block. Returns: If the type of the content element is not 'application/vnd.google.gdata.error+xml', or 0 or more than 1 gd:errors elements are found within the block, then None is returned. Other wise, the gd:errors element parsed as a ContentForShoppingErrors object is returned. """ if self.content.type == 'application/vnd.google.gdata.error+xml': errors_elements = self.content.get_elements(tag='errors', namespace=GD_NAMESPACE) if len(errors_elements) == 1: errors_block = errors_elements[0] return atom.core.parse(errors_block.to_string(), ContentForShoppingErrors) return None GetBatchErrors = get_batch_errors class ErrorDomain(atom.core.XmlElement): """gd:domain element The scope of the error. If the error is global (e.g. missing title) then sc value is returned. Otherwise a comma-separated list of destinations is returned. This element should be placed inside the gd:error (ContentForShoppingError) element. """ _qname = GD_NAMESPACE_TEMPLATE % 'domain' class ErrorCode(atom.core.XmlElement): """gd:code element A code to categorize the errors. This element should be placed inside the gd:error (ContentForShoppingError) element. """ _qname = GD_NAMESPACE_TEMPLATE % 'code' class ErrorLocation(atom.core.XmlElement): """gd:location element The name of the attribute that failed validation. This element should be placed inside the gd:error (ContentForShoppingError) element. """ _qname = GD_NAMESPACE_TEMPLATE % 'location' type = 'type' class InternalReason(atom.core.XmlElement): """gd:internalReason element A more detailed message to explain the cause of the error. This element should be placed inside the gd:error (ContentForShoppingError) element. """ _qname = GD_NAMESPACE_TEMPLATE % 'internalReason' class ContentForShoppingError(atom.core.XmlElement): """gd:error element This element should be placed inside the gd:errors (ContentForShoppingErrors) element. """ _qname = GD_NAMESPACE_TEMPLATE % 'error' domain = ErrorDomain code = ErrorCode location = ErrorLocation internal_reason = InternalReason @property def id(self): """Id for error element. The namespace for the id element is different in batch requests than in individual requests, so we need to consider either case. """ for element in self.GetElements(): if element.tag == 'id': if element.namespace in (GD_NAMESPACE, ATOM_NAMESPACE): return element.text return None class ContentForShoppingErrors(atom.core.XmlElement): """The gd:errors element.""" _qname = GD_NAMESPACE_TEMPLATE % 'errors' errors = [ContentForShoppingError] # opensearch needs overriding for wrong version # see http://code.google.com/p/gdata-python-client/issues/detail?id=483 class TotalResults(gdata.data.TotalResults): _qname = gdata.data.TotalResults._qname[1] class ItemsPerPage(gdata.data.ItemsPerPage): _qname = gdata.data.ItemsPerPage._qname[1] class StartIndex(gdata.data.StartIndex): _qname = gdata.data.StartIndex._qname[1] class ProductFeed(gdata.data.BatchFeed): """Represents a feed of a merchant's products.""" entry = [ProductEntry] total_results = TotalResults items_per_page = ItemsPerPage start_index = StartIndex def get_start_token(self): """Attempts to parse start-token from rel="next" link. A products feed may contain a rel="next" atom:link and the href contained in the link may have a start-token query parameter. Returns: If there is no rel="next" link or the rel="next" link doesn't contain the start-token query parameter, None is returned. Otherwise, the string value of the start-token query parameter is returned. """ next_link = self.get_next_link() if next_link is not None: uri = atom.http_core.parse_uri(next_link.href) if 'start-token' in uri.query: return uri.query['start-token'] return None GetStartToken = get_start_token class Edited(atom.core.XmlElement): """sc:edited element """ _qname = SC_NAMESPACE_TEMPLATE % 'edited' class AttributeLanguage(atom.core.XmlElement): """sc:attribute_language element """ _qname = SC_NAMESPACE_TEMPLATE % 'attribute_language' class FeedFileName(atom.core.XmlElement): """sc:feed_file_name element """ _qname = SC_NAMESPACE_TEMPLATE % 'feed_file_name' class FeedType(atom.core.XmlElement): """sc:feed_type element """ _qname = SC_NAMESPACE_TEMPLATE % 'feed_type' class UseQuotedFields(atom.core.XmlElement): """sc:use_quoted_fields element """ _qname = SC_NAMESPACE_TEMPLATE % 'use_quoted_fields' class FileFormat(atom.core.XmlElement): """sc:file_format element """ _qname = SC_NAMESPACE_TEMPLATE % 'file_format' use_quoted_fields = UseQuotedFields format = 'format' class ProcessingStatus(atom.core.XmlElement): """sc:processing_status element """ _qname = SC_NAMESPACE_TEMPLATE % 'processing_status' class DatafeedEntry(gdata.data.GDEntry): """An entry for a Datafeed.""" content_language = ContentLanguage target_country = TargetCountry feed_file_name = FeedFileName file_format = FileFormat attribute_language = AttributeLanguage processing_status = ProcessingStatus edited = Edited feed_type = FeedType class DatafeedFeed(gdata.data.GDFeed): """A datafeed feed.""" entry = [DatafeedEntry] total_results = TotalResults items_per_page = ItemsPerPage start_index = StartIndex class AdultContent(atom.core.XmlElement): """sc:adult_content element """ _qname = SC_NAMESPACE_TEMPLATE % 'adult_content' class InternalId(atom.core.XmlElement): """sc:internal_id element """ _qname = SC_NAMESPACE_TEMPLATE % 'internal_id' class ReviewsUrl(atom.core.XmlElement): """sc:reviews_url element """ _qname = SC_NAMESPACE_TEMPLATE % 'reviews_url' class AdwordsAccount(atom.core.XmlElement): """sc:adwords_account element""" _qname = SC_NAMESPACE_TEMPLATE % 'adwords_account' status = 'status' class AdwordsAccounts(atom.core.XmlElement): """sc:adwords_accounts element Container element for adwords accounts settings. """ _qname = SC_NAMESPACE_TEMPLATE % 'adwords_accounts' adwords_account = [AdwordsAccount] class ClientAccount(gdata.data.GDEntry): """A multiclient account entry.""" adult_content = AdultContent internal_id = InternalId reviews_url = ReviewsUrl adwords_accounts = AdwordsAccounts class ClientAccountFeed(gdata.data.GDFeed): """A multiclient account feed.""" entry = [ClientAccount] total_results = TotalResults items_per_page = ItemsPerPage start_index = StartIndex # Users Feed Classes class Admin(atom.core.XmlElement): """sc:admin element""" _qname = SC_NAMESPACE_TEMPLATE % 'admin' class Permission(atom.core.XmlElement): """sc:permission element""" _qname = SC_NAMESPACE_TEMPLATE % 'permission' scope = 'scope' class UsersEntry(gdata.data.GDEntry): """A User Management Feed entry.""" admin = Admin permission = [Permission] class UsersFeed(gdata.data.GDFeed): """A User Management Feed.""" entry = [UsersEntry] total_results = TotalResults items_per_page = ItemsPerPage start_index = StartIndex # Data Quality Feed Classes class ExampleItemLink(atom.core.XmlElement): """sc:link element""" _qname = SC_NAMESPACE_TEMPLATE % 'link' class ExampleItemTitle(atom.core.XmlElement): """sc:title element""" _qname = SC_NAMESPACE_TEMPLATE % 'title' class ItemId(atom.core.XmlElement): """sc:item_id element""" _qname = SC_NAMESPACE_TEMPLATE % 'item_id' class SubmittedValue(atom.core.XmlElement): """sc:submitted_value element""" _qname = SC_NAMESPACE_TEMPLATE % 'submitted_value' class ValueOnLandingPage(atom.core.XmlElement): """sc:value_on_landing_page element""" _qname = SC_NAMESPACE_TEMPLATE % 'value_on_landing_page' class ExampleItem(atom.core.XmlElement): """sc:example_item element""" _qname = SC_NAMESPACE_TEMPLATE % 'example_item' item_id = ItemId link = ExampleItemLink title = ExampleItemTitle submitted_value = SubmittedValue value_on_landing_page = ValueOnLandingPage class Issue(atom.core.XmlElement): """sc:issue element""" _qname = SC_NAMESPACE_TEMPLATE % 'issue' id = 'id' last_checked = 'last_checked' num_items = 'num_items' offending_term = 'offending_term' example_item = [ExampleItem] class IssueGroup(atom.core.XmlElement): """sc:issue_group element""" _qname = SC_NAMESPACE_TEMPLATE % 'issue_group' issue = [Issue] country = 'country' id = 'id' class IssueGroups(atom.core.XmlElement): """sc:issue_groups element""" _qname = SC_NAMESPACE_TEMPLATE % 'issue_groups' issue_group = [IssueGroup] class DataQualityEntry(gdata.data.GDEntry): """A Data Quality Feed entry.""" issue_groups = IssueGroups class DataQualityFeed(gdata.data.GDFeed): """A Data Quality Feed.""" entry = [DataQualityEntry] total_results = TotalResults items_per_page = ItemsPerPage start_index = StartIndex # Local Products Feed Classes class SalePrice(atom.core.XmlElement): """scp:sale_price element The sale price of the product. The unit attribute must be set, and should represent the currency. """ _qname = SCP_NAMESPACE_TEMPLATE % 'sale_price' unit = 'unit' class SalePriceEffectiveDate(atom.core.XmlElement): """scp:sale_price_effective_date element The effective date of the sale price of the product. """ _qname = SCP_NAMESPACE_TEMPLATE % 'sale_price_effective_date' class InventoryEntry(gdata.data.BatchEntry): """Product entry containing local product information. The elements of this entry that are used are made up of five different namespaces. They are: atom: - Atom app: - Atom Publishing Protocol gd: - Google Data API sc: - Content API for Shopping, general attributes scp: - Content API for Shopping, product attributes Only the sc and scp namespace elements are defined here, but additional useful elements are defined in superclasses. To remove the value of an attribute of a local product, set the value of the attribute to the empty string. For example: entry.availability = Availability('') The following attributes are encoded as XML elements in the Atom (atom:) namespace: title, link, entry, id, category, content, author, created updated. Among these, the title, content and link tags are part of the required Content for Shopping API so we document them here. .. attribute:: availability The avilability of a local product. This should be an :class:`Availability` instance, for example:: entry = InventoryEntry() entry.availability = Availability('in stock') .. attribute:: price The price for this local product. This should be a :class:`Price` element, including a unit argument to indicate the currency, for example:: entry = InventoryEntry() entry.price = Price('20.00', unit='USD') .. attribute:: quantity The quantity of local product available in stock. This should be a :class:`Quantity` element, for example:: entry = InventoryEntry() entry.quantity = Quantity('100') .. attribute:: sale_price The sale price for this local product. This should be a :class:`SalePrice` element, including a unit argument to indicate the currency, for example:: entry = InventoryEntry() entry.sale_price = SalePrice('20.00', unit='USD') .. attribute:: sale_price_effective_date The effective data of the sale price for this local product. This should be a :class:`SalePriceEffectiveDate` element, for example:: entry = InventoryEntry() entry.sale_price_effective_date = SalePriceEffectiveDate( '2012-01-09 2012-01-13') """ availability = Availability price = Price quantity = Quantity sale_price = SalePrice sale_price_effective_date = SalePriceEffectiveDate class InventoryFeed(gdata.data.BatchFeed): """Represents a feed of a merchant's local products.""" entry = [InventoryEntry] total_results = TotalResults items_per_page = ItemsPerPage start_index = StartIndex gdata-2.0.18/src/gdata/contentforshopping/__init__.py0000640036466300116100000000131512156622363022647 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright (C) 2010-2011 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. """Support for the Content API for Shopping See: http://code.google.com/apis/shopping/content/index.html """ gdata-2.0.18/src/gdata/contentforshopping/client.py0000640036466300116100000007621412156622363022400 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright (C) 2010-2011 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. """Extend the gdata client for the Content API for Shopping.""" __author__ = 'afshar (Ali Afshar), dhermes (Daniel Hermes)' import urllib import atom.data import gdata.client from gdata.contentforshopping.data import ClientAccount from gdata.contentforshopping.data import ClientAccountFeed from gdata.contentforshopping.data import DatafeedEntry from gdata.contentforshopping.data import DatafeedFeed from gdata.contentforshopping.data import DataQualityEntry from gdata.contentforshopping.data import DataQualityFeed from gdata.contentforshopping.data import InventoryFeed from gdata.contentforshopping.data import ProductEntry from gdata.contentforshopping.data import ProductFeed from gdata.contentforshopping.data import UsersEntry from gdata.contentforshopping.data import UsersFeed CFS_VERSION = 'v1' CFS_HOST = 'content.googleapis.com' CFS_URI = 'https://%s/content' % CFS_HOST CFS_PROJECTION = 'schema' class ContentForShoppingClient(gdata.client.GDClient): """Client for Content for Shopping API. :param account_id: Merchant account ID. This value will be used by default for all requests, but may be overridden on a request-by-request basis. :param api_version: The version of the API to target. Default value: 'v1'. :param **kwargs: Pass all addtional keywords to the GDClient constructor. """ api_version = '1.0' def __init__(self, account_id=None, api_version=CFS_VERSION, cfs_uri=CFS_URI, **kwargs): self.cfs_account_id = account_id self.cfs_api_version = api_version self.cfs_uri = cfs_uri gdata.client.GDClient.__init__(self, **kwargs) def _create_uri(self, account_id, resource, path=(), use_projection=True, dry_run=False, warnings=False, max_results=None, start_token=None, start_index=None, performance_start=None, performance_end=None): """Create a request uri from the given arguments. If arguments are None, use the default client attributes. """ account_id = account_id or self.cfs_account_id if account_id is None: raise ValueError('No Account ID set. ' 'Either set for the client, or per request') segments = [self.cfs_uri, self.cfs_api_version, account_id, resource] if use_projection: segments.append(CFS_PROJECTION) segments.extend(urllib.quote(value) for value in path) result = '/'.join(segments) request_params = [] if dry_run: request_params.append('dry-run') if warnings: request_params.append('warnings') if max_results is not None: request_params.append('max-results=%s' % max_results) if start_token is not None: request_params.append('start-token=%s' % start_token) if start_index is not None: request_params.append('start-index=%s' % start_index) if performance_start is not None: request_params.append('performance.start=%s' % performance_start) if performance_end is not None: request_params.append('performance.end=%s' % performance_end) request_params = '&'.join(request_params) if request_params: result = '%s?%s' % (result, request_params) return result def _create_product_id(self, id, country, language, channel='online'): return '%s:%s:%s:%s' % (channel, language, country, id) def _create_batch_feed(self, entries, operation, feed=None, feed_class=ProductFeed): if feed is None: feed = feed_class() for entry in entries: entry.batch_operation = gdata.data.BatchOperation(type=operation) feed.entry.append(entry) return feed # Operations on a single product def get_product(self, id, country, language, account_id=None, auth_token=None): """Get a product by id, country and language. :param id: The product ID :param country: The country (target_country) :param language: The language (content_language) :param account_id: The Merchant Center Account ID. If ommitted the default Account ID will be used for this client :param auth_token: An object which sets the Authorization HTTP header in its modify_request method. """ pid = self._create_product_id(id, country, language) uri = self._create_uri(account_id, 'items/products', path=[pid]) return self.get_entry(uri, desired_class=ProductEntry, auth_token=auth_token) GetProduct = get_product def insert_product(self, product, account_id=None, auth_token=None, dry_run=False, warnings=False): """Create a new product, by posting the product entry feed. :param product: A :class:`gdata.contentforshopping.data.ProductEntry` with the required product data. :param account_id: The Merchant Center Account ID. If ommitted the default Account ID will be used for this client :param auth_token: An object which sets the Authorization HTTP header in its modify_request method. :param dry_run: Flag to run all requests that modify persistent data in dry-run mode. False by default. :param warnings: Flag to include warnings in response. False by default. """ uri = self._create_uri(account_id, 'items/products', dry_run=dry_run, warnings=warnings) return self.post(product, uri=uri, auth_token=auth_token) InsertProduct = insert_product def update_product(self, product, account_id=None, auth_token=None, dry_run=False, warnings=False): """Update a product, by putting the product entry feed. :param product: A :class:`gdata.contentforshopping.data.ProductEntry` with the required product data. :param account_id: The Merchant Center Account ID. If ommitted the default Account ID will be used for this client :param auth_token: An object which sets the Authorization HTTP header in its modify_request method. :param dry_run: Flag to run all requests that modify persistent data in dry-run mode. False by default. :param warnings: Flag to include warnings in response. False by default. """ pid = self._create_product_id(product.product_id.text, product.target_country.text, product.content_language.text) uri = self._create_uri(account_id, 'items/products', path=[pid], dry_run=dry_run, warnings=warnings) return self.update(product, uri=uri, auth_token=auth_token) UpdateProduct = update_product def delete_product(self, product, account_id=None, auth_token=None, dry_run=False, warnings=False): """Delete a product :param product: A :class:`gdata.contentforshopping.data.ProductEntry` with the required product data. :param account_id: The Merchant Center Account ID. If ommitted the default Account ID will be used for this client :param auth_token: An object which sets the Authorization HTTP header in its modify_request method. :param dry_run: Flag to run all requests that modify persistent data in dry-run mode. False by default. :param warnings: Flag to include warnings in response. False by default. """ pid = self._create_product_id(product.product_id.text, product.target_country.text, product.content_language.text) uri = self._create_uri(account_id, 'items/products', path=[pid], dry_run=dry_run, warnings=warnings) return self.delete(uri, auth_token=auth_token) DeleteProduct = delete_product # Operations on multiple products def get_products(self, max_results=None, start_token=None, start_index=None, performance_start=None, performance_end=None, account_id=None, auth_token=None): """Get a feed of products for the account. :param max_results: The maximum number of results to return (default 25, maximum 250). :param start_token: The start token of the feed provided by the API. :param start_index: The starting index of the feed to return (default 1, maximum 10000) :param performance_start: The start date (inclusive) of click data returned. Should be represented as YYYY-MM-DD; not appended if left as None. :param performance_end: The end date (inclusive) of click data returned. Should be represented as YYYY-MM-DD; not appended if left as None. :param account_id: The Merchant Center Account ID. If ommitted the default Account ID will be used for this client :param auth_token: An object which sets the Authorization HTTP header in its modify_request method. """ uri = self._create_uri(account_id, 'items/products', max_results=max_results, start_token=start_token, start_index=start_index, performance_start=performance_start, performance_end=performance_end) return self.get_feed(uri, auth_token=auth_token, desired_class=ProductFeed) GetProducts = get_products def batch(self, feed, account_id=None, auth_token=None, dry_run=False, warnings=False): """Send a batch request. :param feed: The feed of batch entries to send. :param account_id: The Merchant Center Account ID. If ommitted the default Account ID will be used for this client :param auth_token: An object which sets the Authorization HTTP header in its modify_request method. :param dry_run: Flag to run all requests that modify persistent data in dry-run mode. False by default. :param warnings: Flag to include warnings in response. False by default. """ uri = self._create_uri(account_id, 'items/products', path=['batch'], dry_run=dry_run, warnings=warnings) return self.post(feed, uri=uri, auth_token=auth_token, desired_class=ProductFeed) Batch = batch def insert_products(self, products, account_id=None, auth_token=None, dry_run=False, warnings=False): """Insert the products using a batch request :param products: A list of product entries :param account_id: The Merchant Center Account ID. If ommitted the default Account ID will be used for this client :param auth_token: An object which sets the Authorization HTTP header in its modify_request method. :param dry_run: Flag to run all requests that modify persistent data in dry-run mode. False by default. :param warnings: Flag to include warnings in response. False by default. """ feed = self._create_batch_feed(products, 'insert') return self.batch(feed, account_id=account_id, auth_token=auth_token, dry_run=dry_run, warnings=warnings) InsertProducts = insert_products def update_products(self, products, account_id=None, auth_token=None, dry_run=False, warnings=False): """Update the products using a batch request :param products: A list of product entries :param account_id: The Merchant Center Account ID. If ommitted the default Account ID will be used for this client :param auth_token: An object which sets the Authorization HTTP header in its modify_request method. :param dry_run: Flag to run all requests that modify persistent data in dry-run mode. False by default. :param warnings: Flag to include warnings in response. False by default. .. note:: Entries must have the atom:id element set. """ feed = self._create_batch_feed(products, 'update') return self.batch(feed, account_id=account_id, auth_token=auth_token, dry_run=dry_run, warnings=warnings) UpdateProducts = update_products def delete_products(self, products, account_id=None, auth_token=None, dry_run=False, warnings=False): """Delete the products using a batch request. :param products: A list of product entries :param account_id: The Merchant Center Account ID. If ommitted the default Account ID will be used for this client :param auth_token: An object which sets the Authorization HTTP header in its modify_request method. :param dry_run: Flag to run all requests that modify persistent data in dry-run mode. False by default. :param warnings: Flag to include warnings in response. False by default. .. note:: Entries must have the atom:id element set. """ feed = self._create_batch_feed(products, 'delete') return self.batch(feed, account_id=account_id, auth_token=auth_token, dry_run=dry_run, warnings=warnings) DeleteProducts = delete_products # Operations on datafeeds def get_datafeeds(self, account_id=None): """Get the feed of datafeeds. :param account_id: The Sub-Account ID. If ommitted the default Account ID will be used for this client. """ uri = self._create_uri(account_id, 'datafeeds/products', use_projection=False) return self.get_feed(uri, desired_class=DatafeedFeed) GetDatafeeds = get_datafeeds # Operations on a single datafeed def get_datafeed(self, feed_id, account_id=None, auth_token=None): """Get the feed of a single datafeed. :param feed_id: The ID of the desired datafeed. :param account_id: The Sub-Account ID. If ommitted the default Account ID will be used for this client. :param auth_token: An object which sets the Authorization HTTP header in its modify_request method. """ uri = self._create_uri(account_id, 'datafeeds/products', path=[feed_id], use_projection=False) return self.get_feed(uri, auth_token=auth_token, desired_class=DatafeedEntry) GetDatafeed = get_datafeed def insert_datafeed(self, entry, account_id=None, auth_token=None, dry_run=False, warnings=False): """Insert a datafeed. :param entry: XML Content of post request required for registering a datafeed. :param account_id: The Sub-Account ID. If ommitted the default Account ID will be used for this client. :param auth_token: An object which sets the Authorization HTTP header in its modify_request method. :param dry_run: Flag to run all requests that modify persistent data in dry-run mode. False by default. :param warnings: Flag to include warnings in response. False by default. """ uri = self._create_uri(account_id, 'datafeeds/products', use_projection=False, dry_run=dry_run, warnings=warnings) return self.post(entry, uri=uri, auth_token=auth_token) InsertDatafeed = insert_datafeed def update_datafeed(self, entry, feed_id, account_id=None, auth_token=None, dry_run=False, warnings=False): """Update the feed of a single datafeed. :param entry: XML Content of put request required for updating a datafeed. :param feed_id: The ID of the desired datafeed. :param account_id: The Sub-Account ID. If ommitted the default Account ID will be used for this client. :param auth_token: An object which sets the Authorization HTTP header in its modify_request method. :param dry_run: Flag to run all requests that modify persistent data in dry-run mode. False by default. :param warnings: Flag to include warnings in response. False by default. """ uri = self._create_uri(account_id, 'datafeeds/products', path=[feed_id], use_projection=False, dry_run=dry_run, warnings=warnings) return self.update(entry, auth_token=auth_token, uri=uri) UpdateDatafeed = update_datafeed def delete_datafeed(self, feed_id, account_id=None, auth_token=None): """Delete a single datafeed. :param feed_id: The ID of the desired datafeed. :param account_id: The Sub-Account ID. If ommitted the default Account ID will be used for this client. :param auth_token: An object which sets the Authorization HTTP header in its modify_request method. """ uri = self._create_uri(account_id, 'datafeeds/products', path=[feed_id], use_projection=False) return self.delete(uri, auth_token=auth_token) DeleteDatafeed = delete_datafeed # Operations on client accounts def get_client_accounts(self, max_results=None, start_index=None, account_id=None, auth_token=None): """Get the feed of managed accounts :param max_results: The maximum number of results to return (default 25, maximum 250). :param start_index: The starting index of the feed to return (default 1, maximum 10000) :param account_id: The Merchant Center Account ID. If ommitted the default Account ID will be used for this client :param auth_token: An object which sets the Authorization HTTP header in its modify_request method. """ uri = self._create_uri(account_id, 'managedaccounts', max_results=max_results, start_index=start_index, use_projection=False) return self.get_feed(uri, desired_class=ClientAccountFeed, auth_token=auth_token) GetClientAccounts = get_client_accounts def get_client_account(self, client_account_id, account_id=None, auth_token=None): """Get a managed account. :param client_account_id: The Account ID of the subaccount being retrieved. :param account_id: The Merchant Center Account ID. If ommitted the default Account ID will be used for this client :param auth_token: An object which sets the Authorization HTTP header in its modify_request method. """ uri = self._create_uri(account_id, 'managedaccounts', path=[client_account_id], use_projection=False) return self.get_entry(uri, desired_class=ClientAccount, auth_token=auth_token) GetClientAccount = get_client_account def insert_client_account(self, entry, account_id=None, auth_token=None, dry_run=False, warnings=False): """Insert a client account entry :param entry: An entry of type ClientAccount :param account_id: The Merchant Center Account ID. If ommitted the default Account ID will be used for this client :param auth_token: An object which sets the Authorization HTTP header in its modify_request method. :param dry_run: Flag to run all requests that modify persistent data in dry-run mode. False by default. :param warnings: Flag to include warnings in response. False by default. """ uri = self._create_uri(account_id, 'managedaccounts', use_projection=False, dry_run=dry_run, warnings=warnings) return self.post(entry, uri=uri, auth_token=auth_token) InsertClientAccount = insert_client_account def update_client_account(self, entry, client_account_id, account_id=None, auth_token=None, dry_run=False, warnings=False): """Update a client account :param entry: An entry of type ClientAccount to update to :param client_account_id: The client account ID :param account_id: The Merchant Center Account ID. If ommitted the default Account ID will be used for this client :param auth_token: An object which sets the Authorization HTTP header in its modify_request method. :param dry_run: Flag to run all requests that modify persistent data in dry-run mode. False by default. :param warnings: Flag to include warnings in response. False by default. """ uri = self._create_uri(account_id, 'managedaccounts', path=[client_account_id], use_projection=False, dry_run=dry_run, warnings=warnings) return self.update(entry, uri=uri, auth_token=auth_token) UpdateClientAccount = update_client_account def delete_client_account(self, client_account_id, account_id=None, auth_token=None, dry_run=False, warnings=False): """Delete a client account :param client_account_id: The client account ID :param account_id: The Merchant Center Account ID. If ommitted the default Account ID will be used for this client :param auth_token: An object which sets the Authorization HTTP header in its modify_request method. :param dry_run: Flag to run all requests that modify persistent data in dry-run mode. False by default. :param warnings: Flag to include warnings in response. False by default. """ uri = self._create_uri(account_id, 'managedaccounts', path=[client_account_id], use_projection=False, dry_run=dry_run, warnings=warnings) return self.delete(uri, auth_token=auth_token) DeleteClientAccount = delete_client_account def get_users_feed(self, account_id=None, auth_token=None): """Get the users feed for an account. :param account_id: The Merchant Center Account ID. If ommitted the default Account ID will be used for this client :param auth_token: An object which sets the Authorization HTTP header in its modify_request method. """ uri = self._create_uri(account_id, 'users', use_projection=False) return self.get_feed(uri, auth_token=auth_token, desired_class=UsersFeed) GetUsersFeed = get_users_feed def get_users_entry(self, user_email, account_id=None, auth_token=None): """Get a users feed entry for an account. :param user_email: Email of the user entry to be retrieved. :param account_id: The Merchant Center Account ID. If ommitted the default Account ID will be used for this client :param auth_token: An object which sets the Authorization HTTP header in its modify_request method. """ uri = self._create_uri( account_id, 'users', path=[user_email], use_projection=False) return self.get_entry(uri, auth_token=auth_token, desired_class=UsersEntry) GetUsersEntry = get_users_entry def insert_users_entry(self, entry, account_id=None, auth_token=None): """Insert a users feed entry for an account. :param entry: A :class:`gdata.contentforshopping.data.UsersEntry` with the required user data. :param account_id: The Merchant Center Account ID. If ommitted the default Account ID will be used for this client :param auth_token: An object which sets the Authorization HTTP header in its modify_request method. """ uri = self._create_uri(account_id, 'users', use_projection=False) return self.post(entry, uri=uri, auth_token=auth_token) InsertUsersEntry = insert_users_entry def update_users_entry(self, entry, account_id=None, auth_token=None): """Update a users feed entry for an account. :param entry: A :class:`gdata.contentforshopping.data.UsersEntry` with the required user data. :param account_id: The Merchant Center Account ID. If ommitted the default Account ID will be used for this client :param auth_token: An object which sets the Authorization HTTP header in its modify_request method. """ # Could also use entry.find_edit_link() but that is inconsistent # with the rest of the module user_email = entry.title.text uri = self._create_uri( account_id, 'users', path=[user_email], use_projection=False) return self.update(entry, uri=uri, auth_token=auth_token) UpdateUsersEntry = update_users_entry def delete_users_entry(self, entry, account_id=None, auth_token=None): """Delete a users feed entry for an account. :param entry: A :class:`gdata.contentforshopping.data.UsersEntry` with the required user data. :param account_id: The Merchant Center Account ID. If ommitted the default Account ID will be used for this client :param auth_token: An object which sets the Authorization HTTP header in its modify_request method. """ # Could also use entry.find_edit_link() but that is inconsistent # with the rest of the module user_email = entry.title.text uri = self._create_uri( account_id, 'users', path=[user_email], use_projection=False) return self.delete(uri, auth_token=auth_token) DeleteUsersEntry = delete_users_entry def get_data_quality_feed(self, account_id=None, auth_token=None, max_results=None, start_index=None): """Get the data quality feed for an account. :param max_results: The maximum number of results to return (default 25, max 100). :param start_index: The starting index of the feed to return. :param account_id: The Merchant Center Account ID. If ommitted the default Account ID will be used for this client :param auth_token: An object which sets the Authorization HTTP header in its modify_request method. """ uri = self._create_uri(account_id, 'dataquality', use_projection=False, max_results=max_results, start_index=start_index) return self.get_feed(uri, auth_token=auth_token, desired_class=DataQualityFeed) GetDataQualityFeed = get_data_quality_feed def get_data_quality_entry(self, secondary_account_id=None, account_id=None, auth_token=None): """Get the data quality feed entry for an account. :param secondary_account_id: The Account ID of the secondary account. If ommitted the value of account_id is used. :param account_id: The Merchant Center Account ID. If ommitted the default Account ID will be used for this client :param auth_token: An object which sets the Authorization HTTP header in its modify_request method. """ if secondary_account_id is None: secondary_account_id = account_id or self.cfs_account_id uri = self._create_uri(account_id, 'dataquality', path=[secondary_account_id], use_projection=False) return self.get_entry(uri, auth_token=auth_token, desired_class=DataQualityEntry) GetDataQualityEntry = get_data_quality_entry def update_inventory_entry(self, product, id, country, language, store_code, account_id=None, auth_token=None): """Make a local product update, by putting the inventory entry. :param product: A :class:`gdata.contentforshopping.data.InventoryEntry` with the required product data. :param id: The product ID :param country: The country (target_country) :param language: The language (content_language) :param store_code: The code for the store where this local product will be updated. :param account_id: The Merchant Center Account ID. If ommitted the default Account ID will be used for this client :param auth_token: An object which sets the Authorization HTTP header in its modify_request method. """ pid = self._create_product_id(id, country, language, channel='local') uri = self._create_uri(account_id, 'inventory', path=[store_code, 'items', pid], use_projection=False) return self.update(product, uri=uri, auth_token=auth_token) UpdateInventoryEntry = update_inventory_entry def add_local_id(self, product, id, country, language, store_code, account_id=None): """Add an atom id to a local product with a local store specific URI. :param product: A :class:`gdata.contentforshopping.data.InventoryEntry` with the required product data. :param id: The product ID :param country: The country (target_country) :param language: The language (content_language) :param store_code: The code for the store where this local product will be updated. :param account_id: The Merchant Center Account ID. If ommitted the default Account ID will be used for this client """ pid = self._create_product_id(id, country, language, channel='local') uri = self._create_uri(account_id, 'inventory', path=[store_code, 'items', pid], use_projection=False) product.id = atom.data.Id(uri) return product AddLocalId = add_local_id def update_inventory_feed(self, products, account_id=None, auth_token=None): """Update a batch of local products, by putting the product entry feed. :param products: A list containing entries of :class:`gdata.contentforshopping.data.InventoryEntry` with the required product data :param account_id: The Merchant Center Account ID. If ommitted the default Account ID will be used for this client :param auth_token: An object which sets the Authorization HTTP header in its modify_request method. .. note:: Entries must have the atom:id element set. You can use add_local_id to set this attribute using the store_code, product id, country and language. """ feed = self._create_batch_feed(products, 'update', feed_class=InventoryFeed) uri = self._create_uri(account_id, 'inventory', path=['batch'], use_projection=False) return self.post(feed, uri=uri, auth_token=auth_token) UpdateInventoryFeed = update_inventory_feed gdata-2.0.18/src/gdata/projecthosting/0000750036466300116100000000000012156625015017643 5ustar afshareng00000000000000gdata-2.0.18/src/gdata/projecthosting/data.py0000750036466300116100000000635512156622363021145 0ustar afshareng00000000000000#!/usr/bin/env python # # Copyright 2009 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. # This module is used for version 2 of the Google Data APIs. """Provides classes and constants for XML in the Google Project Hosting API. Canonical documentation for the raw XML which these classes represent can be found here: http://code.google.com/p/support/wiki/IssueTrackerAPI """ __author__ = 'jlapenna@google.com (Joe LaPenna)' import atom.core import gdata.data ISSUES_TEMPLATE = '{http://schemas.google.com/projecthosting/issues/2009}%s' ISSUES_FULL_FEED = '/feeds/issues/p/%s/issues/full' COMMENTS_FULL_FEED = '/feeds/issues/p/%s/issues/%s/comments/full' class Uri(atom.core.XmlElement): """The issues:uri element.""" _qname = ISSUES_TEMPLATE % 'uri' class Username(atom.core.XmlElement): """The issues:username element.""" _qname = ISSUES_TEMPLATE % 'username' class Cc(atom.core.XmlElement): """The issues:cc element.""" _qname = ISSUES_TEMPLATE % 'cc' uri = Uri username = Username class Label(atom.core.XmlElement): """The issues:label element.""" _qname = ISSUES_TEMPLATE % 'label' class Owner(atom.core.XmlElement): """The issues:owner element.""" _qname = ISSUES_TEMPLATE % 'owner' uri = Uri username = Username class Stars(atom.core.XmlElement): """The issues:stars element.""" _qname = ISSUES_TEMPLATE % 'stars' class State(atom.core.XmlElement): """The issues:state element.""" _qname = ISSUES_TEMPLATE % 'state' class Status(atom.core.XmlElement): """The issues:status element.""" _qname = ISSUES_TEMPLATE % 'status' class Summary(atom.core.XmlElement): """The issues:summary element.""" _qname = ISSUES_TEMPLATE % 'summary' class OwnerUpdate(atom.core.XmlElement): """The issues:ownerUpdate element.""" _qname = ISSUES_TEMPLATE % 'ownerUpdate' class CcUpdate(atom.core.XmlElement): """The issues:ccUpdate element.""" _qname = ISSUES_TEMPLATE % 'ccUpdate' class Updates(atom.core.XmlElement): """The issues:updates element.""" _qname = ISSUES_TEMPLATE % 'updates' summary = Summary status = Status ownerUpdate = OwnerUpdate label = [Label] ccUpdate = [CcUpdate] class IssueEntry(gdata.data.GDEntry): """Represents the information of one issue.""" _qname = atom.data.ATOM_TEMPLATE % 'entry' owner = Owner cc = [Cc] label = [Label] stars = Stars state = State status = Status class IssuesFeed(gdata.data.GDFeed): """An Atom feed listing a project's issues.""" entry = [IssueEntry] class CommentEntry(gdata.data.GDEntry): """An entry detailing one comment on an issue.""" _qname = atom.data.ATOM_TEMPLATE % 'entry' updates = Updates class CommentsFeed(gdata.data.GDFeed): """An Atom feed listing a project's issue's comments.""" entry = [CommentEntry] gdata-2.0.18/src/gdata/projecthosting/__init__.py0000750036466300116100000000000112156622363021751 0ustar afshareng00000000000000 gdata-2.0.18/src/gdata/projecthosting/client.py0000750036466300116100000001572312156622363021511 0ustar afshareng00000000000000#!/usr/bin/env python # # Copyright 2009 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. import atom.data import gdata.client import gdata.gauth import gdata.projecthosting.data class ProjectHostingClient(gdata.client.GDClient): """Client to interact with the Project Hosting GData API.""" api_version = '1.0' auth_service = 'code' auth_scopes = gdata.gauth.AUTH_SCOPES['code'] host = 'code.google.com' ssl = True def get_issues(self, project_name, desired_class=gdata.projecthosting.data.IssuesFeed, **kwargs): """Get a feed of issues for a particular project. Args: project_name str The name of the project. query Query Set returned issues parameters. Returns: data.IssuesFeed """ return self.get_feed(gdata.projecthosting.data.ISSUES_FULL_FEED % project_name, desired_class=desired_class, **kwargs) def add_issue(self, project_name, title, content, author, status=None, owner=None, labels=None, ccs=None, **kwargs): """Create a new issue for the project. Args: project_name str The name of the project. title str The title of the new issue. content str The summary of the new issue. author str The authenticated user's username. status str The status of the new issue, Accepted, etc. owner str The username of new issue's owner. labels [str] Labels to associate with the new issue. ccs [str] usernames to Cc on the new issue. Returns: data.IssueEntry """ new_entry = gdata.projecthosting.data.IssueEntry( title=atom.data.Title(text=title), content=atom.data.Content(text=content), author=[atom.data.Author(name=atom.data.Name(text=author))]) if status: new_entry.status = gdata.projecthosting.data.Status(text=status) if owner: owner = [gdata.projecthosting.data.Owner( username=gdata.projecthosting.data.Username(text=owner))] if labels: new_entry.label = [gdata.projecthosting.data.Label(text=label) for label in labels] if ccs: new_entry.cc = [ gdata.projecthosting.data.Cc( username=gdata.projecthosting.data.Username(text=cc)) for cc in ccs] return self.post( new_entry, gdata.projecthosting.data.ISSUES_FULL_FEED % project_name, **kwargs) def update_issue(self, project_name, issue_id, author, comment=None, summary=None, status=None, owner=None, labels=None, ccs=None, **kwargs): """Update or comment on one issue for the project. Args: project_name str The name of the issue's project. issue_id str The issue number needing updated. author str The authenticated user's username. comment str A comment to append to the issue summary str Rewrite the summary of the issue. status str A new status for the issue. owner str The username of the new owner. labels [str] Labels to set on the issue (prepend issue with - to remove a label). ccs [str] Ccs to set on th enew issue (prepend cc with - to remove a cc). Returns: data.CommentEntry """ updates = gdata.projecthosting.data.Updates() if summary: updates.summary = gdata.projecthosting.data.Summary(text=summary) if status: updates.status = gdata.projecthosting.data.Status(text=status) if owner: updates.ownerUpdate = gdata.projecthosting.data.OwnerUpdate(text=owner) if labels: updates.label = [gdata.projecthosting.data.Label(text=label) for label in labels] if ccs: updates.ccUpdate = [gdata.projecthosting.data.CcUpdate(text=cc) for cc in ccs] update_entry = gdata.projecthosting.data.CommentEntry( content=atom.data.Content(text=comment), author=[atom.data.Author(name=atom.data.Name(text=author))], updates=updates) return self.post( update_entry, gdata.projecthosting.data.COMMENTS_FULL_FEED % (project_name, issue_id), **kwargs) def get_comments(self, project_name, issue_id, desired_class=gdata.projecthosting.data.CommentsFeed, **kwargs): """Get a feed of all updates to an issue. Args: project_name str The name of the issue's project. issue_id str The issue number needing updated. Returns: data.CommentsFeed """ return self.get_feed( gdata.projecthosting.data.COMMENTS_FULL_FEED % (project_name, issue_id), desired_class=desired_class, **kwargs) def update(self, entry, auth_token=None, force=False, **kwargs): """Unsupported GData update method. Use update_*() instead. """ raise NotImplementedError( 'GData Update operation unsupported, try update_*') def delete(self, entry_or_uri, auth_token=None, force=False, **kwargs): """Unsupported GData delete method. Use update_issue(status='Closed') instead. """ raise NotImplementedError( 'GData Delete API unsupported, try closing the issue instead.') class Query(gdata.client.Query): def __init__(self, issue_id=None, label=None, canned_query=None, owner=None, status=None, **kwargs): """Constructs a Google Data Query to filter feed contents serverside. Args: issue_id: int or str The issue to return based on the issue id. label: str A label returned issues must have. canned_query: str Return issues based on a canned query identifier owner: str Return issues based on the owner of the issue. For Gmail users, this will be the part of the email preceding the '@' sign. status: str Return issues based on the status of the issue. """ super(Query, self).__init__(**kwargs) self.label = label self.issue_id = issue_id self.canned_query = canned_query self.owner = owner self.status = status def modify_request(self, http_request): if self.issue_id: gdata.client._add_query_param('id', self.issue_id, http_request) if self.label: gdata.client._add_query_param('label', self.label, http_request) if self.canned_query: gdata.client._add_query_param('can', self.canned_query, http_request) if self.owner: gdata.client._add_query_param('owner', self.owner, http_request) if self.status: gdata.client._add_query_param('status', self.status, http_request) super(Query, self).modify_request(http_request) ModifyRequest = modify_request gdata-2.0.18/src/gdata/tlslite/0000750036466300116100000000000012156625015016261 5ustar afshareng00000000000000gdata-2.0.18/src/gdata/tlslite/api.py0000750036466300116100000000562512156622363017422 0ustar afshareng00000000000000"""Import this module for easy access to TLS Lite objects. The TLS Lite API consists of classes, functions, and variables spread throughout this package. Instead of importing them individually with:: from tlslite.TLSConnection import TLSConnection from tlslite.HandshakeSettings import HandshakeSettings from tlslite.errors import * . . It's easier to do:: from tlslite.api import * This imports all the important objects (TLSConnection, Checker, HandshakeSettings, etc.) into the global namespace. In particular, it imports:: from constants import AlertLevel, AlertDescription, Fault from errors import * from Checker import Checker from HandshakeSettings import HandshakeSettings from Session import Session from SessionCache import SessionCache from SharedKeyDB import SharedKeyDB from TLSConnection import TLSConnection from VerifierDB import VerifierDB from X509 import X509 from X509CertChain import X509CertChain from integration.HTTPTLSConnection import HTTPTLSConnection from integration.POP3_TLS import POP3_TLS from integration.IMAP4_TLS import IMAP4_TLS from integration.SMTP_TLS import SMTP_TLS from integration.XMLRPCTransport import XMLRPCTransport from integration.TLSSocketServerMixIn import TLSSocketServerMixIn from integration.TLSAsyncDispatcherMixIn import TLSAsyncDispatcherMixIn from integration.TLSTwistedProtocolWrapper import TLSTwistedProtocolWrapper from utils.cryptomath import cryptlibpyLoaded, m2cryptoLoaded, gmpyLoaded, pycryptoLoaded, prngName from utils.keyfactory import generateRSAKey, parsePEMKey, parseXMLKey, parseAsPublicKey, parsePrivateKey """ from constants import AlertLevel, AlertDescription, Fault from errors import * from Checker import Checker from HandshakeSettings import HandshakeSettings from Session import Session from SessionCache import SessionCache from SharedKeyDB import SharedKeyDB from TLSConnection import TLSConnection from VerifierDB import VerifierDB from X509 import X509 from X509CertChain import X509CertChain from integration.HTTPTLSConnection import HTTPTLSConnection from integration.TLSSocketServerMixIn import TLSSocketServerMixIn from integration.TLSAsyncDispatcherMixIn import TLSAsyncDispatcherMixIn from integration.POP3_TLS import POP3_TLS from integration.IMAP4_TLS import IMAP4_TLS from integration.SMTP_TLS import SMTP_TLS from integration.XMLRPCTransport import XMLRPCTransport try: import twisted del(twisted) from integration.TLSTwistedProtocolWrapper import TLSTwistedProtocolWrapper except ImportError: pass from utils.cryptomath import cryptlibpyLoaded, m2cryptoLoaded, gmpyLoaded, \ pycryptoLoaded, prngName from utils.keyfactory import generateRSAKey, parsePEMKey, parseXMLKey, \ parseAsPublicKey, parsePrivateKey gdata-2.0.18/src/gdata/tlslite/mathtls.py0000750036466300116100000002657712156622363020336 0ustar afshareng00000000000000"""Miscellaneous helper functions.""" from utils.compat import * from utils.cryptomath import * import hmac import md5 import sha #1024, 1536, 2048, 3072, 4096, 6144, and 8192 bit groups] goodGroupParameters = [(2,0xEEAF0AB9ADB38DD69C33F80AFA8FC5E86072618775FF3C0B9EA2314C9C256576D674DF7496EA81D3383B4813D692C6E0E0D5D8E250B98BE48E495C1D6089DAD15DC7D7B46154D6B6CE8EF4AD69B15D4982559B297BCF1885C529F566660E57EC68EDBC3C05726CC02FD4CBF4976EAA9AFD5138FE8376435B9FC61D2FC0EB06E3),\ (2,0x9DEF3CAFB939277AB1F12A8617A47BBBDBA51DF499AC4C80BEEEA9614B19CC4D5F4F5F556E27CBDE51C6A94BE4607A291558903BA0D0F84380B655BB9A22E8DCDF028A7CEC67F0D08134B1C8B97989149B609E0BE3BAB63D47548381DBC5B1FC764E3F4B53DD9DA1158BFD3E2B9C8CF56EDF019539349627DB2FD53D24B7C48665772E437D6C7F8CE442734AF7CCB7AE837C264AE3A9BEB87F8A2FE9B8B5292E5A021FFF5E91479E8CE7A28C2442C6F315180F93499A234DCF76E3FED135F9BB),\ (2,0xAC6BDB41324A9A9BF166DE5E1389582FAF72B6651987EE07FC3192943DB56050A37329CBB4A099ED8193E0757767A13DD52312AB4B03310DCD7F48A9DA04FD50E8083969EDB767B0CF6095179A163AB3661A05FBD5FAAAE82918A9962F0B93B855F97993EC975EEAA80D740ADBF4FF747359D041D5C33EA71D281E446B14773BCA97B43A23FB801676BD207A436C6481F1D2B9078717461A5B9D32E688F87748544523B524B0D57D5EA77A2775D2ECFA032CFBDBF52FB3786160279004E57AE6AF874E7303CE53299CCC041C7BC308D82A5698F3A8D0C38271AE35F8E9DBFBB694B5C803D89F7AE435DE236D525F54759B65E372FCD68EF20FA7111F9E4AFF73),\ (2,0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A93AD2CAFFFFFFFFFFFFFFFF),\ (5,0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934063199FFFFFFFFFFFFFFFF),\ (5,0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C93402849236C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BDF8FF9406AD9E530EE5DB382F413001AEB06A53ED9027D831179727B0865A8918DA3EDBEBCF9B14ED44CE6CBACED4BB1BDB7F1447E6CC254B332051512BD7AF426FB8F401378CD2BF5983CA01C64B92ECF032EA15D1721D03F482D7CE6E74FEF6D55E702F46980C82B5A84031900B1C9E59E7C97FBEC7E8F323A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AACC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE32806A1D58BB7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55CDA56C9EC2EF29632387FE8D76E3C0468043E8F663F4860EE12BF2D5B0B7474D6E694F91E6DCC4024FFFFFFFFFFFFFFFF),\ (5,0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C93402849236C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BDF8FF9406AD9E530EE5DB382F413001AEB06A53ED9027D831179727B0865A8918DA3EDBEBCF9B14ED44CE6CBACED4BB1BDB7F1447E6CC254B332051512BD7AF426FB8F401378CD2BF5983CA01C64B92ECF032EA15D1721D03F482D7CE6E74FEF6D55E702F46980C82B5A84031900B1C9E59E7C97FBEC7E8F323A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AACC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE32806A1D58BB7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55CDA56C9EC2EF29632387FE8D76E3C0468043E8F663F4860EE12BF2D5B0B7474D6E694F91E6DBE115974A3926F12FEE5E438777CB6A932DF8CD8BEC4D073B931BA3BC832B68D9DD300741FA7BF8AFC47ED2576F6936BA424663AAB639C5AE4F5683423B4742BF1C978238F16CBE39D652DE3FDB8BEFC848AD922222E04A4037C0713EB57A81A23F0C73473FC646CEA306B4BCBC8862F8385DDFA9D4B7FA2C087E879683303ED5BDD3A062B3CF5B3A278A66D2A13F83F44F82DDF310EE074AB6A364597E899A0255DC164F31CC50846851DF9AB48195DED7EA1B1D510BD7EE74D73FAF36BC31ECFA268359046F4EB879F924009438B481C6CD7889A002ED5EE382BC9190DA6FC026E479558E4475677E9AA9E3050E2765694DFC81F56E880B96E7160C980DD98EDD3DFFFFFFFFFFFFFFFFF)] def P_hash(hashModule, secret, seed, length): bytes = createByteArrayZeros(length) secret = bytesToString(secret) seed = bytesToString(seed) A = seed index = 0 while 1: A = hmac.HMAC(secret, A, hashModule).digest() output = hmac.HMAC(secret, A+seed, hashModule).digest() for c in output: if index >= length: return bytes bytes[index] = ord(c) index += 1 return bytes def PRF(secret, label, seed, length): #Split the secret into left and right halves S1 = secret[ : int(math.ceil(len(secret)/2.0))] S2 = secret[ int(math.floor(len(secret)/2.0)) : ] #Run the left half through P_MD5 and the right half through P_SHA1 p_md5 = P_hash(md5, S1, concatArrays(stringToBytes(label), seed), length) p_sha1 = P_hash(sha, S2, concatArrays(stringToBytes(label), seed), length) #XOR the output values and return the result for x in range(length): p_md5[x] ^= p_sha1[x] return p_md5 def PRF_SSL(secret, seed, length): secretStr = bytesToString(secret) seedStr = bytesToString(seed) bytes = createByteArrayZeros(length) index = 0 for x in range(26): A = chr(ord('A')+x) * (x+1) # 'A', 'BB', 'CCC', etc.. input = secretStr + sha.sha(A + secretStr + seedStr).digest() output = md5.md5(input).digest() for c in output: if index >= length: return bytes bytes[index] = ord(c) index += 1 return bytes def makeX(salt, username, password): if len(username)>=256: raise ValueError("username too long") if len(salt)>=256: raise ValueError("salt too long") return stringToNumber(sha.sha(salt + sha.sha(username + ":" + password)\ .digest()).digest()) #This function is used by VerifierDB.makeVerifier def makeVerifier(username, password, bits): bitsIndex = {1024:0, 1536:1, 2048:2, 3072:3, 4096:4, 6144:5, 8192:6}[bits] g,N = goodGroupParameters[bitsIndex] salt = bytesToString(getRandomBytes(16)) x = makeX(salt, username, password) verifier = powMod(g, x, N) return N, g, salt, verifier def PAD(n, x): nLength = len(numberToString(n)) s = numberToString(x) if len(s) < nLength: s = ("\0" * (nLength-len(s))) + s return s def makeU(N, A, B): return stringToNumber(sha.sha(PAD(N, A) + PAD(N, B)).digest()) def makeK(N, g): return stringToNumber(sha.sha(numberToString(N) + PAD(N, g)).digest()) """ MAC_SSL Modified from Python HMAC by Trevor """ class MAC_SSL: """MAC_SSL class. This supports the API for Cryptographic Hash Functions (PEP 247). """ def __init__(self, key, msg = None, digestmod = None): """Create a new MAC_SSL object. key: key for the keyed hash object. msg: Initial input for the hash, if provided. digestmod: A module supporting PEP 247. Defaults to the md5 module. """ if digestmod is None: import md5 digestmod = md5 if key == None: #TREVNEW - for faster copying return #TREVNEW self.digestmod = digestmod self.outer = digestmod.new() self.inner = digestmod.new() self.digest_size = digestmod.digest_size ipad = "\x36" * 40 opad = "\x5C" * 40 self.inner.update(key) self.inner.update(ipad) self.outer.update(key) self.outer.update(opad) if msg is not None: self.update(msg) def update(self, msg): """Update this hashing object with the string msg. """ self.inner.update(msg) def copy(self): """Return a separate copy of this hashing object. An update to this copy won't affect the original object. """ other = MAC_SSL(None) #TREVNEW - for faster copying other.digest_size = self.digest_size #TREVNEW other.digestmod = self.digestmod other.inner = self.inner.copy() other.outer = self.outer.copy() return other def digest(self): """Return the hash value of this hashing object. This returns a string containing 8-bit data. The object is not altered in any way by this function; you can continue updating the object after calling this function. """ h = self.outer.copy() h.update(self.inner.digest()) return h.digest() def hexdigest(self): """Like digest(), but returns a string of hexadecimal digits instead. """ return "".join([hex(ord(x))[2:].zfill(2) for x in tuple(self.digest())]) gdata-2.0.18/src/gdata/tlslite/SharedKeyDB.py0000750036466300116100000000357212156622363020735 0ustar afshareng00000000000000"""Class for storing shared keys.""" from utils.cryptomath import * from utils.compat import * from mathtls import * from Session import Session from BaseDB import BaseDB class SharedKeyDB(BaseDB): """This class represent an in-memory or on-disk database of shared keys. A SharedKeyDB can be passed to a server handshake function to authenticate a client based on one of the shared keys. This class is thread-safe. """ def __init__(self, filename=None): """Create a new SharedKeyDB. @type filename: str @param filename: Filename for an on-disk database, or None for an in-memory database. If the filename already exists, follow this with a call to open(). To create a new on-disk database, follow this with a call to create(). """ BaseDB.__init__(self, filename, "shared key") def _getItem(self, username, valueStr): session = Session() session._createSharedKey(username, valueStr) return session def __setitem__(self, username, sharedKey): """Add a shared key to the database. @type username: str @param username: The username to associate the shared key with. Must be less than or equal to 16 characters in length, and must not already be in the database. @type sharedKey: str @param sharedKey: The shared key to add. Must be less than 48 characters in length. """ BaseDB.__setitem__(self, username, sharedKey) def _setItem(self, username, value): if len(username)>16: raise ValueError("username too long") if len(value)>=48: raise ValueError("shared key too long") return value def _checkItem(self, value, username, param): newSession = self._getItem(username, param) return value.masterSecret == newSession.masterSecretgdata-2.0.18/src/gdata/tlslite/VerifierDB.py0000750036466300116100000000604012156622363020622 0ustar afshareng00000000000000"""Class for storing SRP password verifiers.""" from utils.cryptomath import * from utils.compat import * import mathtls from BaseDB import BaseDB class VerifierDB(BaseDB): """This class represent an in-memory or on-disk database of SRP password verifiers. A VerifierDB can be passed to a server handshake to authenticate a client based on one of the verifiers. This class is thread-safe. """ def __init__(self, filename=None): """Create a new VerifierDB instance. @type filename: str @param filename: Filename for an on-disk database, or None for an in-memory database. If the filename already exists, follow this with a call to open(). To create a new on-disk database, follow this with a call to create(). """ BaseDB.__init__(self, filename, "verifier") def _getItem(self, username, valueStr): (N, g, salt, verifier) = valueStr.split(" ") N = base64ToNumber(N) g = base64ToNumber(g) salt = base64ToString(salt) verifier = base64ToNumber(verifier) return (N, g, salt, verifier) def __setitem__(self, username, verifierEntry): """Add a verifier entry to the database. @type username: str @param username: The username to associate the verifier with. Must be less than 256 characters in length. Must not already be in the database. @type verifierEntry: tuple @param verifierEntry: The verifier entry to add. Use L{tlslite.VerifierDB.VerifierDB.makeVerifier} to create a verifier entry. """ BaseDB.__setitem__(self, username, verifierEntry) def _setItem(self, username, value): if len(username)>=256: raise ValueError("username too long") N, g, salt, verifier = value N = numberToBase64(N) g = numberToBase64(g) salt = stringToBase64(salt) verifier = numberToBase64(verifier) valueStr = " ".join( (N, g, salt, verifier) ) return valueStr def _checkItem(self, value, username, param): (N, g, salt, verifier) = value x = mathtls.makeX(salt, username, param) v = powMod(g, x, N) return (verifier == v) def makeVerifier(username, password, bits): """Create a verifier entry which can be stored in a VerifierDB. @type username: str @param username: The username for this verifier. Must be less than 256 characters in length. @type password: str @param password: The password for this verifier. @type bits: int @param bits: This values specifies which SRP group parameters to use. It must be one of (1024, 1536, 2048, 3072, 4096, 6144, 8192). Larger values are more secure but slower. 2048 is a good compromise between safety and speed. @rtype: tuple @return: A tuple which may be stored in a VerifierDB. """ return mathtls.makeVerifier(username, password, bits) makeVerifier = staticmethod(makeVerifier)gdata-2.0.18/src/gdata/tlslite/integration/0000750036466300116100000000000012156625015020604 5ustar afshareng00000000000000gdata-2.0.18/src/gdata/tlslite/integration/HTTPTLSConnection.py0000750036466300116100000001501412156622363024347 0ustar afshareng00000000000000"""TLS Lite + httplib.""" import socket import httplib from gdata.tlslite.TLSConnection import TLSConnection from gdata.tlslite.integration.ClientHelper import ClientHelper class HTTPBaseTLSConnection(httplib.HTTPConnection): """This abstract class provides a framework for adding TLS support to httplib.""" default_port = 443 def __init__(self, host, port=None, strict=None): if strict == None: #Python 2.2 doesn't support strict httplib.HTTPConnection.__init__(self, host, port) else: httplib.HTTPConnection.__init__(self, host, port, strict) def connect(self): sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) if hasattr(sock, 'settimeout'): sock.settimeout(10) sock.connect((self.host, self.port)) #Use a TLSConnection to emulate a socket self.sock = TLSConnection(sock) #When httplib closes this, close the socket self.sock.closeSocket = True self._handshake(self.sock) def _handshake(self, tlsConnection): """Called to perform some sort of handshake. This method must be overridden in a subclass to do some type of handshake. This method will be called after the socket has been connected but before any data has been sent. If this method does not raise an exception, the TLS connection will be considered valid. This method may (or may not) be called every time an HTTP request is performed, depending on whether the underlying HTTP connection is persistent. @type tlsConnection: L{tlslite.TLSConnection.TLSConnection} @param tlsConnection: The connection to perform the handshake on. """ raise NotImplementedError() class HTTPTLSConnection(HTTPBaseTLSConnection, ClientHelper): """This class extends L{HTTPBaseTLSConnection} to support the common types of handshaking.""" def __init__(self, host, port=None, username=None, password=None, sharedKey=None, certChain=None, privateKey=None, cryptoID=None, protocol=None, x509Fingerprint=None, x509TrustList=None, x509CommonName=None, settings = None): """Create a new HTTPTLSConnection. For client authentication, use one of these argument combinations: - username, password (SRP) - username, sharedKey (shared-key) - certChain, privateKey (certificate) For server authentication, you can either rely on the implicit mutual authentication performed by SRP or shared-keys, or you can do certificate-based server authentication with one of these argument combinations: - cryptoID[, protocol] (requires cryptoIDlib) - x509Fingerprint - x509TrustList[, x509CommonName] (requires cryptlib_py) Certificate-based server authentication is compatible with SRP or certificate-based client authentication. It is not compatible with shared-keys. The constructor does not perform the TLS handshake itself, but simply stores these arguments for later. The handshake is performed only when this class needs to connect with the server. Thus you should be prepared to handle TLS-specific exceptions when calling methods inherited from L{httplib.HTTPConnection} such as request(), connect(), and send(). See the client handshake functions in L{tlslite.TLSConnection.TLSConnection} for details on which exceptions might be raised. @type host: str @param host: Server to connect to. @type port: int @param port: Port to connect to. @type username: str @param username: SRP or shared-key username. Requires the 'password' or 'sharedKey' argument. @type password: str @param password: SRP password for mutual authentication. Requires the 'username' argument. @type sharedKey: str @param sharedKey: Shared key for mutual authentication. Requires the 'username' argument. @type certChain: L{tlslite.X509CertChain.X509CertChain} or L{cryptoIDlib.CertChain.CertChain} @param certChain: Certificate chain for client authentication. Requires the 'privateKey' argument. Excludes the SRP or shared-key related arguments. @type privateKey: L{tlslite.utils.RSAKey.RSAKey} @param privateKey: Private key for client authentication. Requires the 'certChain' argument. Excludes the SRP or shared-key related arguments. @type cryptoID: str @param cryptoID: cryptoID for server authentication. Mutually exclusive with the 'x509...' arguments. @type protocol: str @param protocol: cryptoID protocol URI for server authentication. Requires the 'cryptoID' argument. @type x509Fingerprint: str @param x509Fingerprint: Hex-encoded X.509 fingerprint for server authentication. Mutually exclusive with the 'cryptoID' and 'x509TrustList' arguments. @type x509TrustList: list of L{tlslite.X509.X509} @param x509TrustList: A list of trusted root certificates. The other party must present a certificate chain which extends to one of these root certificates. The cryptlib_py module must be installed to use this parameter. Mutually exclusive with the 'cryptoID' and 'x509Fingerprint' arguments. @type x509CommonName: str @param x509CommonName: The end-entity certificate's 'CN' field must match this value. For a web server, this is typically a server name such as 'www.amazon.com'. Mutually exclusive with the 'cryptoID' and 'x509Fingerprint' arguments. Requires the 'x509TrustList' argument. @type settings: L{tlslite.HandshakeSettings.HandshakeSettings} @param settings: Various settings which can be used to control the ciphersuites, certificate types, and SSL/TLS versions offered by the client. """ HTTPBaseTLSConnection.__init__(self, host, port) ClientHelper.__init__(self, username, password, sharedKey, certChain, privateKey, cryptoID, protocol, x509Fingerprint, x509TrustList, x509CommonName, settings) def _handshake(self, tlsConnection): ClientHelper._handshake(self, tlsConnection) gdata-2.0.18/src/gdata/tlslite/integration/ClientHelper.py0000750036466300116100000001555512156622363023555 0ustar afshareng00000000000000""" A helper class for using TLS Lite with stdlib clients (httplib, xmlrpclib, imaplib, poplib). """ from gdata.tlslite.Checker import Checker class ClientHelper: """This is a helper class used to integrate TLS Lite with various TLS clients (e.g. poplib, smtplib, httplib, etc.)""" def __init__(self, username=None, password=None, sharedKey=None, certChain=None, privateKey=None, cryptoID=None, protocol=None, x509Fingerprint=None, x509TrustList=None, x509CommonName=None, settings = None): """ For client authentication, use one of these argument combinations: - username, password (SRP) - username, sharedKey (shared-key) - certChain, privateKey (certificate) For server authentication, you can either rely on the implicit mutual authentication performed by SRP or shared-keys, or you can do certificate-based server authentication with one of these argument combinations: - cryptoID[, protocol] (requires cryptoIDlib) - x509Fingerprint - x509TrustList[, x509CommonName] (requires cryptlib_py) Certificate-based server authentication is compatible with SRP or certificate-based client authentication. It is not compatible with shared-keys. The constructor does not perform the TLS handshake itself, but simply stores these arguments for later. The handshake is performed only when this class needs to connect with the server. Then you should be prepared to handle TLS-specific exceptions. See the client handshake functions in L{tlslite.TLSConnection.TLSConnection} for details on which exceptions might be raised. @type username: str @param username: SRP or shared-key username. Requires the 'password' or 'sharedKey' argument. @type password: str @param password: SRP password for mutual authentication. Requires the 'username' argument. @type sharedKey: str @param sharedKey: Shared key for mutual authentication. Requires the 'username' argument. @type certChain: L{tlslite.X509CertChain.X509CertChain} or L{cryptoIDlib.CertChain.CertChain} @param certChain: Certificate chain for client authentication. Requires the 'privateKey' argument. Excludes the SRP or shared-key related arguments. @type privateKey: L{tlslite.utils.RSAKey.RSAKey} @param privateKey: Private key for client authentication. Requires the 'certChain' argument. Excludes the SRP or shared-key related arguments. @type cryptoID: str @param cryptoID: cryptoID for server authentication. Mutually exclusive with the 'x509...' arguments. @type protocol: str @param protocol: cryptoID protocol URI for server authentication. Requires the 'cryptoID' argument. @type x509Fingerprint: str @param x509Fingerprint: Hex-encoded X.509 fingerprint for server authentication. Mutually exclusive with the 'cryptoID' and 'x509TrustList' arguments. @type x509TrustList: list of L{tlslite.X509.X509} @param x509TrustList: A list of trusted root certificates. The other party must present a certificate chain which extends to one of these root certificates. The cryptlib_py module must be installed to use this parameter. Mutually exclusive with the 'cryptoID' and 'x509Fingerprint' arguments. @type x509CommonName: str @param x509CommonName: The end-entity certificate's 'CN' field must match this value. For a web server, this is typically a server name such as 'www.amazon.com'. Mutually exclusive with the 'cryptoID' and 'x509Fingerprint' arguments. Requires the 'x509TrustList' argument. @type settings: L{tlslite.HandshakeSettings.HandshakeSettings} @param settings: Various settings which can be used to control the ciphersuites, certificate types, and SSL/TLS versions offered by the client. """ self.username = None self.password = None self.sharedKey = None self.certChain = None self.privateKey = None self.checker = None #SRP Authentication if username and password and not \ (sharedKey or certChain or privateKey): self.username = username self.password = password #Shared Key Authentication elif username and sharedKey and not \ (password or certChain or privateKey): self.username = username self.sharedKey = sharedKey #Certificate Chain Authentication elif certChain and privateKey and not \ (username or password or sharedKey): self.certChain = certChain self.privateKey = privateKey #No Authentication elif not password and not username and not \ sharedKey and not certChain and not privateKey: pass else: raise ValueError("Bad parameters") #Authenticate the server based on its cryptoID or fingerprint if sharedKey and (cryptoID or protocol or x509Fingerprint): raise ValueError("Can't use shared keys with other forms of"\ "authentication") self.checker = Checker(cryptoID, protocol, x509Fingerprint, x509TrustList, x509CommonName) self.settings = settings self.tlsSession = None def _handshake(self, tlsConnection): if self.username and self.password: tlsConnection.handshakeClientSRP(username=self.username, password=self.password, checker=self.checker, settings=self.settings, session=self.tlsSession) elif self.username and self.sharedKey: tlsConnection.handshakeClientSharedKey(username=self.username, sharedKey=self.sharedKey, settings=self.settings) else: tlsConnection.handshakeClientCert(certChain=self.certChain, privateKey=self.privateKey, checker=self.checker, settings=self.settings, session=self.tlsSession) self.tlsSession = tlsConnection.session gdata-2.0.18/src/gdata/tlslite/integration/TLSSocketServerMixIn.py0000750036466300116100000000423312156622363025135 0ustar afshareng00000000000000"""TLS Lite + SocketServer.""" from gdata.tlslite.TLSConnection import TLSConnection class TLSSocketServerMixIn: """ This class can be mixed in with any L{SocketServer.TCPServer} to add TLS support. To use this class, define a new class that inherits from it and some L{SocketServer.TCPServer} (with the mix-in first). Then implement the handshake() method, doing some sort of server handshake on the connection argument. If the handshake method returns True, the RequestHandler will be triggered. Below is a complete example of a threaded HTTPS server:: from SocketServer import * from BaseHTTPServer import * from SimpleHTTPServer import * from tlslite.api import * s = open("./serverX509Cert.pem").read() x509 = X509() x509.parse(s) certChain = X509CertChain([x509]) s = open("./serverX509Key.pem").read() privateKey = parsePEMKey(s, private=True) sessionCache = SessionCache() class MyHTTPServer(ThreadingMixIn, TLSSocketServerMixIn, HTTPServer): def handshake(self, tlsConnection): try: tlsConnection.handshakeServer(certChain=certChain, privateKey=privateKey, sessionCache=sessionCache) tlsConnection.ignoreAbruptClose = True return True except TLSError, error: print "Handshake failure:", str(error) return False httpd = MyHTTPServer(('localhost', 443), SimpleHTTPRequestHandler) httpd.serve_forever() """ def finish_request(self, sock, client_address): tlsConnection = TLSConnection(sock) if self.handshake(tlsConnection) == True: self.RequestHandlerClass(tlsConnection, client_address, self) tlsConnection.close() #Implement this method to do some form of handshaking. Return True #if the handshake finishes properly and the request is authorized. def handshake(self, tlsConnection): raise NotImplementedError() gdata-2.0.18/src/gdata/tlslite/integration/POP3_TLS.py0000750036466300116100000001253212156622363022432 0ustar afshareng00000000000000"""TLS Lite + poplib.""" import socket from poplib import POP3 from gdata.tlslite.TLSConnection import TLSConnection from gdata.tlslite.integration.ClientHelper import ClientHelper # POP TLS PORT POP3_TLS_PORT = 995 class POP3_TLS(POP3, ClientHelper): """This class extends L{poplib.POP3} with TLS support.""" def __init__(self, host, port = POP3_TLS_PORT, username=None, password=None, sharedKey=None, certChain=None, privateKey=None, cryptoID=None, protocol=None, x509Fingerprint=None, x509TrustList=None, x509CommonName=None, settings=None): """Create a new POP3_TLS. For client authentication, use one of these argument combinations: - username, password (SRP) - username, sharedKey (shared-key) - certChain, privateKey (certificate) For server authentication, you can either rely on the implicit mutual authentication performed by SRP or shared-keys, or you can do certificate-based server authentication with one of these argument combinations: - cryptoID[, protocol] (requires cryptoIDlib) - x509Fingerprint - x509TrustList[, x509CommonName] (requires cryptlib_py) Certificate-based server authentication is compatible with SRP or certificate-based client authentication. It is not compatible with shared-keys. The caller should be prepared to handle TLS-specific exceptions. See the client handshake functions in L{tlslite.TLSConnection.TLSConnection} for details on which exceptions might be raised. @type host: str @param host: Server to connect to. @type port: int @param port: Port to connect to. @type username: str @param username: SRP or shared-key username. Requires the 'password' or 'sharedKey' argument. @type password: str @param password: SRP password for mutual authentication. Requires the 'username' argument. @type sharedKey: str @param sharedKey: Shared key for mutual authentication. Requires the 'username' argument. @type certChain: L{tlslite.X509CertChain.X509CertChain} or L{cryptoIDlib.CertChain.CertChain} @param certChain: Certificate chain for client authentication. Requires the 'privateKey' argument. Excludes the SRP or shared-key related arguments. @type privateKey: L{tlslite.utils.RSAKey.RSAKey} @param privateKey: Private key for client authentication. Requires the 'certChain' argument. Excludes the SRP or shared-key related arguments. @type cryptoID: str @param cryptoID: cryptoID for server authentication. Mutually exclusive with the 'x509...' arguments. @type protocol: str @param protocol: cryptoID protocol URI for server authentication. Requires the 'cryptoID' argument. @type x509Fingerprint: str @param x509Fingerprint: Hex-encoded X.509 fingerprint for server authentication. Mutually exclusive with the 'cryptoID' and 'x509TrustList' arguments. @type x509TrustList: list of L{tlslite.X509.X509} @param x509TrustList: A list of trusted root certificates. The other party must present a certificate chain which extends to one of these root certificates. The cryptlib_py module must be installed to use this parameter. Mutually exclusive with the 'cryptoID' and 'x509Fingerprint' arguments. @type x509CommonName: str @param x509CommonName: The end-entity certificate's 'CN' field must match this value. For a web server, this is typically a server name such as 'www.amazon.com'. Mutually exclusive with the 'cryptoID' and 'x509Fingerprint' arguments. Requires the 'x509TrustList' argument. @type settings: L{tlslite.HandshakeSettings.HandshakeSettings} @param settings: Various settings which can be used to control the ciphersuites, certificate types, and SSL/TLS versions offered by the client. """ self.host = host self.port = port msg = "getaddrinfo returns an empty list" self.sock = None for res in socket.getaddrinfo(self.host, self.port, 0, socket.SOCK_STREAM): af, socktype, proto, canonname, sa = res try: self.sock = socket.socket(af, socktype, proto) self.sock.connect(sa) except socket.error, msg: if self.sock: self.sock.close() self.sock = None continue break if not self.sock: raise socket.error, msg ### New code below (all else copied from poplib) ClientHelper.__init__(self, username, password, sharedKey, certChain, privateKey, cryptoID, protocol, x509Fingerprint, x509TrustList, x509CommonName, settings) self.sock = TLSConnection(self.sock) self.sock.closeSocket = True ClientHelper._handshake(self, self.sock) ### self.file = self.sock.makefile('rb') self._debugging = 0 self.welcome = self._getresp() gdata-2.0.18/src/gdata/tlslite/integration/__init__.py0000750036466300116100000000071212156622363022723 0ustar afshareng00000000000000"""Classes for integrating TLS Lite with other packages.""" __all__ = ["AsyncStateMachine", "HTTPTLSConnection", "POP3_TLS", "IMAP4_TLS", "SMTP_TLS", "XMLRPCTransport", "TLSSocketServerMixIn", "TLSAsyncDispatcherMixIn", "TLSTwistedProtocolWrapper"] try: import twisted del twisted except ImportError: del __all__[__all__.index("TLSTwistedProtocolWrapper")] gdata-2.0.18/src/gdata/tlslite/integration/XMLRPCTransport.py0000750036466300116100000001326412156622363024114 0ustar afshareng00000000000000"""TLS Lite + xmlrpclib.""" import xmlrpclib import httplib from gdata.tlslite.integration.HTTPTLSConnection import HTTPTLSConnection from gdata.tlslite.integration.ClientHelper import ClientHelper class XMLRPCTransport(xmlrpclib.Transport, ClientHelper): """Handles an HTTPS transaction to an XML-RPC server.""" def __init__(self, username=None, password=None, sharedKey=None, certChain=None, privateKey=None, cryptoID=None, protocol=None, x509Fingerprint=None, x509TrustList=None, x509CommonName=None, settings=None): """Create a new XMLRPCTransport. An instance of this class can be passed to L{xmlrpclib.ServerProxy} to use TLS with XML-RPC calls:: from tlslite.api import XMLRPCTransport from xmlrpclib import ServerProxy transport = XMLRPCTransport(user="alice", password="abra123") server = ServerProxy("https://localhost", transport) For client authentication, use one of these argument combinations: - username, password (SRP) - username, sharedKey (shared-key) - certChain, privateKey (certificate) For server authentication, you can either rely on the implicit mutual authentication performed by SRP or shared-keys, or you can do certificate-based server authentication with one of these argument combinations: - cryptoID[, protocol] (requires cryptoIDlib) - x509Fingerprint - x509TrustList[, x509CommonName] (requires cryptlib_py) Certificate-based server authentication is compatible with SRP or certificate-based client authentication. It is not compatible with shared-keys. The constructor does not perform the TLS handshake itself, but simply stores these arguments for later. The handshake is performed only when this class needs to connect with the server. Thus you should be prepared to handle TLS-specific exceptions when calling methods of L{xmlrpclib.ServerProxy}. See the client handshake functions in L{tlslite.TLSConnection.TLSConnection} for details on which exceptions might be raised. @type username: str @param username: SRP or shared-key username. Requires the 'password' or 'sharedKey' argument. @type password: str @param password: SRP password for mutual authentication. Requires the 'username' argument. @type sharedKey: str @param sharedKey: Shared key for mutual authentication. Requires the 'username' argument. @type certChain: L{tlslite.X509CertChain.X509CertChain} or L{cryptoIDlib.CertChain.CertChain} @param certChain: Certificate chain for client authentication. Requires the 'privateKey' argument. Excludes the SRP or shared-key related arguments. @type privateKey: L{tlslite.utils.RSAKey.RSAKey} @param privateKey: Private key for client authentication. Requires the 'certChain' argument. Excludes the SRP or shared-key related arguments. @type cryptoID: str @param cryptoID: cryptoID for server authentication. Mutually exclusive with the 'x509...' arguments. @type protocol: str @param protocol: cryptoID protocol URI for server authentication. Requires the 'cryptoID' argument. @type x509Fingerprint: str @param x509Fingerprint: Hex-encoded X.509 fingerprint for server authentication. Mutually exclusive with the 'cryptoID' and 'x509TrustList' arguments. @type x509TrustList: list of L{tlslite.X509.X509} @param x509TrustList: A list of trusted root certificates. The other party must present a certificate chain which extends to one of these root certificates. The cryptlib_py module must be installed to use this parameter. Mutually exclusive with the 'cryptoID' and 'x509Fingerprint' arguments. @type x509CommonName: str @param x509CommonName: The end-entity certificate's 'CN' field must match this value. For a web server, this is typically a server name such as 'www.amazon.com'. Mutually exclusive with the 'cryptoID' and 'x509Fingerprint' arguments. Requires the 'x509TrustList' argument. @type settings: L{tlslite.HandshakeSettings.HandshakeSettings} @param settings: Various settings which can be used to control the ciphersuites, certificate types, and SSL/TLS versions offered by the client. """ ClientHelper.__init__(self, username, password, sharedKey, certChain, privateKey, cryptoID, protocol, x509Fingerprint, x509TrustList, x509CommonName, settings) def make_connection(self, host): # create a HTTPS connection object from a host descriptor host, extra_headers, x509 = self.get_host_info(host) http = HTTPTLSConnection(host, None, self.username, self.password, self.sharedKey, self.certChain, self.privateKey, self.checker.cryptoID, self.checker.protocol, self.checker.x509Fingerprint, self.checker.x509TrustList, self.checker.x509CommonName, self.settings) http2 = httplib.HTTP() http2._setup(http) return http2 gdata-2.0.18/src/gdata/tlslite/integration/SMTP_TLS.py0000750036466300116100000001120312156622363022466 0ustar afshareng00000000000000"""TLS Lite + smtplib.""" from smtplib import SMTP from gdata.tlslite.TLSConnection import TLSConnection from gdata.tlslite.integration.ClientHelper import ClientHelper class SMTP_TLS(SMTP): """This class extends L{smtplib.SMTP} with TLS support.""" def starttls(self, username=None, password=None, sharedKey=None, certChain=None, privateKey=None, cryptoID=None, protocol=None, x509Fingerprint=None, x509TrustList=None, x509CommonName=None, settings=None): """Puts the connection to the SMTP server into TLS mode. If the server supports TLS, this will encrypt the rest of the SMTP session. For client authentication, use one of these argument combinations: - username, password (SRP) - username, sharedKey (shared-key) - certChain, privateKey (certificate) For server authentication, you can either rely on the implicit mutual authentication performed by SRP or shared-keys, or you can do certificate-based server authentication with one of these argument combinations: - cryptoID[, protocol] (requires cryptoIDlib) - x509Fingerprint - x509TrustList[, x509CommonName] (requires cryptlib_py) Certificate-based server authentication is compatible with SRP or certificate-based client authentication. It is not compatible with shared-keys. The caller should be prepared to handle TLS-specific exceptions. See the client handshake functions in L{tlslite.TLSConnection.TLSConnection} for details on which exceptions might be raised. @type username: str @param username: SRP or shared-key username. Requires the 'password' or 'sharedKey' argument. @type password: str @param password: SRP password for mutual authentication. Requires the 'username' argument. @type sharedKey: str @param sharedKey: Shared key for mutual authentication. Requires the 'username' argument. @type certChain: L{tlslite.X509CertChain.X509CertChain} or L{cryptoIDlib.CertChain.CertChain} @param certChain: Certificate chain for client authentication. Requires the 'privateKey' argument. Excludes the SRP or shared-key related arguments. @type privateKey: L{tlslite.utils.RSAKey.RSAKey} @param privateKey: Private key for client authentication. Requires the 'certChain' argument. Excludes the SRP or shared-key related arguments. @type cryptoID: str @param cryptoID: cryptoID for server authentication. Mutually exclusive with the 'x509...' arguments. @type protocol: str @param protocol: cryptoID protocol URI for server authentication. Requires the 'cryptoID' argument. @type x509Fingerprint: str @param x509Fingerprint: Hex-encoded X.509 fingerprint for server authentication. Mutually exclusive with the 'cryptoID' and 'x509TrustList' arguments. @type x509TrustList: list of L{tlslite.X509.X509} @param x509TrustList: A list of trusted root certificates. The other party must present a certificate chain which extends to one of these root certificates. The cryptlib_py module must be installed to use this parameter. Mutually exclusive with the 'cryptoID' and 'x509Fingerprint' arguments. @type x509CommonName: str @param x509CommonName: The end-entity certificate's 'CN' field must match this value. For a web server, this is typically a server name such as 'www.amazon.com'. Mutually exclusive with the 'cryptoID' and 'x509Fingerprint' arguments. Requires the 'x509TrustList' argument. @type settings: L{tlslite.HandshakeSettings.HandshakeSettings} @param settings: Various settings which can be used to control the ciphersuites, certificate types, and SSL/TLS versions offered by the client. """ (resp, reply) = self.docmd("STARTTLS") if resp == 220: helper = ClientHelper( username, password, sharedKey, certChain, privateKey, cryptoID, protocol, x509Fingerprint, x509TrustList, x509CommonName, settings) conn = TLSConnection(self.sock) conn.closeSocket = True helper._handshake(conn) self.sock = conn self.file = conn.makefile('rb') return (resp, reply) gdata-2.0.18/src/gdata/tlslite/integration/IntegrationHelper.py0000750036466300116100000000345712156622363024620 0ustar afshareng00000000000000 class IntegrationHelper: def __init__(self, username=None, password=None, sharedKey=None, certChain=None, privateKey=None, cryptoID=None, protocol=None, x509Fingerprint=None, x509TrustList=None, x509CommonName=None, settings = None): self.username = None self.password = None self.sharedKey = None self.certChain = None self.privateKey = None self.checker = None #SRP Authentication if username and password and not \ (sharedKey or certChain or privateKey): self.username = username self.password = password #Shared Key Authentication elif username and sharedKey and not \ (password or certChain or privateKey): self.username = username self.sharedKey = sharedKey #Certificate Chain Authentication elif certChain and privateKey and not \ (username or password or sharedKey): self.certChain = certChain self.privateKey = privateKey #No Authentication elif not password and not username and not \ sharedKey and not certChain and not privateKey: pass else: raise ValueError("Bad parameters") #Authenticate the server based on its cryptoID or fingerprint if sharedKey and (cryptoID or protocol or x509Fingerprint): raise ValueError("Can't use shared keys with other forms of"\ "authentication") self.checker = Checker(cryptoID, protocol, x509Fingerprint, x509TrustList, x509CommonName) self.settings = settingsgdata-2.0.18/src/gdata/tlslite/integration/AsyncStateMachine.py0000750036466300116100000001603612156622363024535 0ustar afshareng00000000000000""" A state machine for using TLS Lite with asynchronous I/O. """ class AsyncStateMachine: """ This is an abstract class that's used to integrate TLS Lite with asyncore and Twisted. This class signals wantsReadsEvent() and wantsWriteEvent(). When the underlying socket has become readable or writeable, the event should be passed to this class by calling inReadEvent() or inWriteEvent(). This class will then try to read or write through the socket, and will update its state appropriately. This class will forward higher-level events to its subclass. For example, when a complete TLS record has been received, outReadEvent() will be called with the decrypted data. """ def __init__(self): self._clear() def _clear(self): #These store the various asynchronous operations (i.e. #generators). Only one of them, at most, is ever active at a #time. self.handshaker = None self.closer = None self.reader = None self.writer = None #This stores the result from the last call to the #currently active operation. If 0 it indicates that the #operation wants to read, if 1 it indicates that the #operation wants to write. If None, there is no active #operation. self.result = None def _checkAssert(self, maxActive=1): #This checks that only one operation, at most, is #active, and that self.result is set appropriately. activeOps = 0 if self.handshaker: activeOps += 1 if self.closer: activeOps += 1 if self.reader: activeOps += 1 if self.writer: activeOps += 1 if self.result == None: if activeOps != 0: raise AssertionError() elif self.result in (0,1): if activeOps != 1: raise AssertionError() else: raise AssertionError() if activeOps > maxActive: raise AssertionError() def wantsReadEvent(self): """If the state machine wants to read. If an operation is active, this returns whether or not the operation wants to read from the socket. If an operation is not active, this returns None. @rtype: bool or None @return: If the state machine wants to read. """ if self.result != None: return self.result == 0 return None def wantsWriteEvent(self): """If the state machine wants to write. If an operation is active, this returns whether or not the operation wants to write to the socket. If an operation is not active, this returns None. @rtype: bool or None @return: If the state machine wants to write. """ if self.result != None: return self.result == 1 return None def outConnectEvent(self): """Called when a handshake operation completes. May be overridden in subclass. """ pass def outCloseEvent(self): """Called when a close operation completes. May be overridden in subclass. """ pass def outReadEvent(self, readBuffer): """Called when a read operation completes. May be overridden in subclass.""" pass def outWriteEvent(self): """Called when a write operation completes. May be overridden in subclass.""" pass def inReadEvent(self): """Tell the state machine it can read from the socket.""" try: self._checkAssert() if self.handshaker: self._doHandshakeOp() elif self.closer: self._doCloseOp() elif self.reader: self._doReadOp() elif self.writer: self._doWriteOp() else: self.reader = self.tlsConnection.readAsync(16384) self._doReadOp() except: self._clear() raise def inWriteEvent(self): """Tell the state machine it can write to the socket.""" try: self._checkAssert() if self.handshaker: self._doHandshakeOp() elif self.closer: self._doCloseOp() elif self.reader: self._doReadOp() elif self.writer: self._doWriteOp() else: self.outWriteEvent() except: self._clear() raise def _doHandshakeOp(self): try: self.result = self.handshaker.next() except StopIteration: self.handshaker = None self.result = None self.outConnectEvent() def _doCloseOp(self): try: self.result = self.closer.next() except StopIteration: self.closer = None self.result = None self.outCloseEvent() def _doReadOp(self): self.result = self.reader.next() if not self.result in (0,1): readBuffer = self.result self.reader = None self.result = None self.outReadEvent(readBuffer) def _doWriteOp(self): try: self.result = self.writer.next() except StopIteration: self.writer = None self.result = None def setHandshakeOp(self, handshaker): """Start a handshake operation. @type handshaker: generator @param handshaker: A generator created by using one of the asynchronous handshake functions (i.e. handshakeServerAsync, or handshakeClientxxx(..., async=True). """ try: self._checkAssert(0) self.handshaker = handshaker self._doHandshakeOp() except: self._clear() raise def setServerHandshakeOp(self, **args): """Start a handshake operation. The arguments passed to this function will be forwarded to L{tlslite.TLSConnection.TLSConnection.handshakeServerAsync}. """ handshaker = self.tlsConnection.handshakeServerAsync(**args) self.setHandshakeOp(handshaker) def setCloseOp(self): """Start a close operation. """ try: self._checkAssert(0) self.closer = self.tlsConnection.closeAsync() self._doCloseOp() except: self._clear() raise def setWriteOp(self, writeBuffer): """Start a write operation. @type writeBuffer: str @param writeBuffer: The string to transmit. """ try: self._checkAssert(0) self.writer = self.tlsConnection.writeAsync(writeBuffer) self._doWriteOp() except: self._clear() raise gdata-2.0.18/src/gdata/tlslite/integration/TLSTwistedProtocolWrapper.py0000750036466300116100000001557112156622363026266 0ustar afshareng00000000000000"""TLS Lite + Twisted.""" from twisted.protocols.policies import ProtocolWrapper, WrappingFactory from twisted.python.failure import Failure from AsyncStateMachine import AsyncStateMachine from gdata.tlslite.TLSConnection import TLSConnection from gdata.tlslite.errors import * import socket import errno #The TLSConnection is created around a "fake socket" that #plugs it into the underlying Twisted transport class _FakeSocket: def __init__(self, wrapper): self.wrapper = wrapper self.data = "" def send(self, data): ProtocolWrapper.write(self.wrapper, data) return len(data) def recv(self, numBytes): if self.data == "": raise socket.error, (errno.EWOULDBLOCK, "") returnData = self.data[:numBytes] self.data = self.data[numBytes:] return returnData class TLSTwistedProtocolWrapper(ProtocolWrapper, AsyncStateMachine): """This class can wrap Twisted protocols to add TLS support. Below is a complete example of using TLS Lite with a Twisted echo server. There are two server implementations below. Echo is the original protocol, which is oblivious to TLS. Echo1 subclasses Echo and negotiates TLS when the client connects. Echo2 subclasses Echo and negotiates TLS when the client sends "STARTTLS":: from twisted.internet.protocol import Protocol, Factory from twisted.internet import reactor from twisted.protocols.policies import WrappingFactory from twisted.protocols.basic import LineReceiver from twisted.python import log from twisted.python.failure import Failure import sys from tlslite.api import * s = open("./serverX509Cert.pem").read() x509 = X509() x509.parse(s) certChain = X509CertChain([x509]) s = open("./serverX509Key.pem").read() privateKey = parsePEMKey(s, private=True) verifierDB = VerifierDB("verifierDB") verifierDB.open() class Echo(LineReceiver): def connectionMade(self): self.transport.write("Welcome to the echo server!\\r\\n") def lineReceived(self, line): self.transport.write(line + "\\r\\n") class Echo1(Echo): def connectionMade(self): if not self.transport.tlsStarted: self.transport.setServerHandshakeOp(certChain=certChain, privateKey=privateKey, verifierDB=verifierDB) else: Echo.connectionMade(self) def connectionLost(self, reason): pass #Handle any TLS exceptions here class Echo2(Echo): def lineReceived(self, data): if data == "STARTTLS": self.transport.setServerHandshakeOp(certChain=certChain, privateKey=privateKey, verifierDB=verifierDB) else: Echo.lineReceived(self, data) def connectionLost(self, reason): pass #Handle any TLS exceptions here factory = Factory() factory.protocol = Echo1 #factory.protocol = Echo2 wrappingFactory = WrappingFactory(factory) wrappingFactory.protocol = TLSTwistedProtocolWrapper log.startLogging(sys.stdout) reactor.listenTCP(1079, wrappingFactory) reactor.run() This class works as follows: Data comes in and is given to the AsyncStateMachine for handling. AsyncStateMachine will forward events to this class, and we'll pass them on to the ProtocolHandler, which will proxy them to the wrapped protocol. The wrapped protocol may then call back into this class, and these calls will be proxied into the AsyncStateMachine. The call graph looks like this: - self.dataReceived - AsyncStateMachine.inReadEvent - self.out(Connect|Close|Read)Event - ProtocolWrapper.(connectionMade|loseConnection|dataReceived) - self.(loseConnection|write|writeSequence) - AsyncStateMachine.(setCloseOp|setWriteOp) """ #WARNING: IF YOU COPY-AND-PASTE THE ABOVE CODE, BE SURE TO REMOVE #THE EXTRA ESCAPING AROUND "\\r\\n" def __init__(self, factory, wrappedProtocol): ProtocolWrapper.__init__(self, factory, wrappedProtocol) AsyncStateMachine.__init__(self) self.fakeSocket = _FakeSocket(self) self.tlsConnection = TLSConnection(self.fakeSocket) self.tlsStarted = False self.connectionLostCalled = False def connectionMade(self): try: ProtocolWrapper.connectionMade(self) except TLSError, e: self.connectionLost(Failure(e)) ProtocolWrapper.loseConnection(self) def dataReceived(self, data): try: if not self.tlsStarted: ProtocolWrapper.dataReceived(self, data) else: self.fakeSocket.data += data while self.fakeSocket.data: AsyncStateMachine.inReadEvent(self) except TLSError, e: self.connectionLost(Failure(e)) ProtocolWrapper.loseConnection(self) def connectionLost(self, reason): if not self.connectionLostCalled: ProtocolWrapper.connectionLost(self, reason) self.connectionLostCalled = True def outConnectEvent(self): ProtocolWrapper.connectionMade(self) def outCloseEvent(self): ProtocolWrapper.loseConnection(self) def outReadEvent(self, data): if data == "": ProtocolWrapper.loseConnection(self) else: ProtocolWrapper.dataReceived(self, data) def setServerHandshakeOp(self, **args): self.tlsStarted = True AsyncStateMachine.setServerHandshakeOp(self, **args) def loseConnection(self): if not self.tlsStarted: ProtocolWrapper.loseConnection(self) else: AsyncStateMachine.setCloseOp(self) def write(self, data): if not self.tlsStarted: ProtocolWrapper.write(self, data) else: #Because of the FakeSocket, write operations are guaranteed to #terminate immediately. AsyncStateMachine.setWriteOp(self, data) def writeSequence(self, seq): if not self.tlsStarted: ProtocolWrapper.writeSequence(self, seq) else: #Because of the FakeSocket, write operations are guaranteed to #terminate immediately. AsyncStateMachine.setWriteOp(self, "".join(seq)) gdata-2.0.18/src/gdata/tlslite/integration/IMAP4_TLS.py0000750036466300116100000001203112156622363022515 0ustar afshareng00000000000000"""TLS Lite + imaplib.""" import socket from imaplib import IMAP4 from gdata.tlslite.TLSConnection import TLSConnection from gdata.tlslite.integration.ClientHelper import ClientHelper # IMAP TLS PORT IMAP4_TLS_PORT = 993 class IMAP4_TLS(IMAP4, ClientHelper): """This class extends L{imaplib.IMAP4} with TLS support.""" def __init__(self, host = '', port = IMAP4_TLS_PORT, username=None, password=None, sharedKey=None, certChain=None, privateKey=None, cryptoID=None, protocol=None, x509Fingerprint=None, x509TrustList=None, x509CommonName=None, settings=None): """Create a new IMAP4_TLS. For client authentication, use one of these argument combinations: - username, password (SRP) - username, sharedKey (shared-key) - certChain, privateKey (certificate) For server authentication, you can either rely on the implicit mutual authentication performed by SRP or shared-keys, or you can do certificate-based server authentication with one of these argument combinations: - cryptoID[, protocol] (requires cryptoIDlib) - x509Fingerprint - x509TrustList[, x509CommonName] (requires cryptlib_py) Certificate-based server authentication is compatible with SRP or certificate-based client authentication. It is not compatible with shared-keys. The caller should be prepared to handle TLS-specific exceptions. See the client handshake functions in L{tlslite.TLSConnection.TLSConnection} for details on which exceptions might be raised. @type host: str @param host: Server to connect to. @type port: int @param port: Port to connect to. @type username: str @param username: SRP or shared-key username. Requires the 'password' or 'sharedKey' argument. @type password: str @param password: SRP password for mutual authentication. Requires the 'username' argument. @type sharedKey: str @param sharedKey: Shared key for mutual authentication. Requires the 'username' argument. @type certChain: L{tlslite.X509CertChain.X509CertChain} or L{cryptoIDlib.CertChain.CertChain} @param certChain: Certificate chain for client authentication. Requires the 'privateKey' argument. Excludes the SRP or shared-key related arguments. @type privateKey: L{tlslite.utils.RSAKey.RSAKey} @param privateKey: Private key for client authentication. Requires the 'certChain' argument. Excludes the SRP or shared-key related arguments. @type cryptoID: str @param cryptoID: cryptoID for server authentication. Mutually exclusive with the 'x509...' arguments. @type protocol: str @param protocol: cryptoID protocol URI for server authentication. Requires the 'cryptoID' argument. @type x509Fingerprint: str @param x509Fingerprint: Hex-encoded X.509 fingerprint for server authentication. Mutually exclusive with the 'cryptoID' and 'x509TrustList' arguments. @type x509TrustList: list of L{tlslite.X509.X509} @param x509TrustList: A list of trusted root certificates. The other party must present a certificate chain which extends to one of these root certificates. The cryptlib_py module must be installed to use this parameter. Mutually exclusive with the 'cryptoID' and 'x509Fingerprint' arguments. @type x509CommonName: str @param x509CommonName: The end-entity certificate's 'CN' field must match this value. For a web server, this is typically a server name such as 'www.amazon.com'. Mutually exclusive with the 'cryptoID' and 'x509Fingerprint' arguments. Requires the 'x509TrustList' argument. @type settings: L{tlslite.HandshakeSettings.HandshakeSettings} @param settings: Various settings which can be used to control the ciphersuites, certificate types, and SSL/TLS versions offered by the client. """ ClientHelper.__init__(self, username, password, sharedKey, certChain, privateKey, cryptoID, protocol, x509Fingerprint, x509TrustList, x509CommonName, settings) IMAP4.__init__(self, host, port) def open(self, host = '', port = IMAP4_TLS_PORT): """Setup connection to remote server on "host:port". This connection will be used by the routines: read, readline, send, shutdown. """ self.host = host self.port = port self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.sock.connect((host, port)) self.sock = TLSConnection(self.sock) self.sock.closeSocket = True ClientHelper._handshake(self, self.sock) self.file = self.sock.makefile('rb') gdata-2.0.18/src/gdata/tlslite/integration/TLSAsyncDispatcherMixIn.py0000750036466300116100000001141612156622363025603 0ustar afshareng00000000000000"""TLS Lite + asyncore.""" import asyncore from gdata.tlslite.TLSConnection import TLSConnection from AsyncStateMachine import AsyncStateMachine class TLSAsyncDispatcherMixIn(AsyncStateMachine): """This class can be "mixed in" with an L{asyncore.dispatcher} to add TLS support. This class essentially sits between the dispatcher and the select loop, intercepting events and only calling the dispatcher when applicable. In the case of handle_read(), a read operation will be activated, and when it completes, the bytes will be placed in a buffer where the dispatcher can retrieve them by calling recv(), and the dispatcher's handle_read() will be called. In the case of handle_write(), the dispatcher's handle_write() will be called, and when it calls send(), a write operation will be activated. To use this class, you must combine it with an asyncore.dispatcher, and pass in a handshake operation with setServerHandshakeOp(). Below is an example of using this class with medusa. This class is mixed in with http_channel to create http_tls_channel. Note: 1. the mix-in is listed first in the inheritance list 2. the input buffer size must be at least 16K, otherwise the dispatcher might not read all the bytes from the TLS layer, leaving some bytes in limbo. 3. IE seems to have a problem receiving a whole HTTP response in a single TLS record, so HTML pages containing '\\r\\n\\r\\n' won't be displayed on IE. Add the following text into 'start_medusa.py', in the 'HTTP Server' section:: from tlslite.api import * s = open("./serverX509Cert.pem").read() x509 = X509() x509.parse(s) certChain = X509CertChain([x509]) s = open("./serverX509Key.pem").read() privateKey = parsePEMKey(s, private=True) class http_tls_channel(TLSAsyncDispatcherMixIn, http_server.http_channel): ac_in_buffer_size = 16384 def __init__ (self, server, conn, addr): http_server.http_channel.__init__(self, server, conn, addr) TLSAsyncDispatcherMixIn.__init__(self, conn) self.tlsConnection.ignoreAbruptClose = True self.setServerHandshakeOp(certChain=certChain, privateKey=privateKey) hs.channel_class = http_tls_channel If the TLS layer raises an exception, the exception will be caught in asyncore.dispatcher, which will call close() on this class. The TLS layer always closes the TLS connection before raising an exception, so the close operation will complete right away, causing asyncore.dispatcher.close() to be called, which closes the socket and removes this instance from the asyncore loop. """ def __init__(self, sock=None): AsyncStateMachine.__init__(self) if sock: self.tlsConnection = TLSConnection(sock) #Calculate the sibling I'm being mixed in with. #This is necessary since we override functions #like readable(), handle_read(), etc., but we #also want to call the sibling's versions. for cl in self.__class__.__bases__: if cl != TLSAsyncDispatcherMixIn and cl != AsyncStateMachine: self.siblingClass = cl break else: raise AssertionError() def readable(self): result = self.wantsReadEvent() if result != None: return result return self.siblingClass.readable(self) def writable(self): result = self.wantsWriteEvent() if result != None: return result return self.siblingClass.writable(self) def handle_read(self): self.inReadEvent() def handle_write(self): self.inWriteEvent() def outConnectEvent(self): self.siblingClass.handle_connect(self) def outCloseEvent(self): asyncore.dispatcher.close(self) def outReadEvent(self, readBuffer): self.readBuffer = readBuffer self.siblingClass.handle_read(self) def outWriteEvent(self): self.siblingClass.handle_write(self) def recv(self, bufferSize=16384): if bufferSize < 16384 or self.readBuffer == None: raise AssertionError() returnValue = self.readBuffer self.readBuffer = None return returnValue def send(self, writeBuffer): self.setWriteOp(writeBuffer) return len(writeBuffer) def close(self): if hasattr(self, "tlsConnection"): self.setCloseOp() else: asyncore.dispatcher.close(self) gdata-2.0.18/src/gdata/tlslite/__init__.py0000750036466300116100000000215112156622363020377 0ustar afshareng00000000000000""" TLS Lite is a free python library that implements SSL v3, TLS v1, and TLS v1.1. TLS Lite supports non-traditional authentication methods such as SRP, shared keys, and cryptoIDs, in addition to X.509 certificates. TLS Lite is pure python, however it can access OpenSSL, cryptlib, pycrypto, and GMPY for faster crypto operations. TLS Lite integrates with httplib, xmlrpclib, poplib, imaplib, smtplib, SocketServer, asyncore, and Twisted. To use, do:: from tlslite.api import * Then use the L{tlslite.TLSConnection.TLSConnection} class with a socket, or use one of the integration classes in L{tlslite.integration}. @version: 0.3.8 """ __version__ = "0.3.8" __all__ = ["api", "BaseDB", "Checker", "constants", "errors", "FileObject", "HandshakeSettings", "mathtls", "messages", "Session", "SessionCache", "SharedKeyDB", "TLSConnection", "TLSRecordLayer", "VerifierDB", "X509", "X509CertChain", "integration", "utils"] gdata-2.0.18/src/gdata/tlslite/constants.py0000750036466300116100000001646412156622363020670 0ustar afshareng00000000000000"""Constants used in various places.""" class CertificateType: x509 = 0 openpgp = 1 cryptoID = 2 class HandshakeType: hello_request = 0 client_hello = 1 server_hello = 2 certificate = 11 server_key_exchange = 12 certificate_request = 13 server_hello_done = 14 certificate_verify = 15 client_key_exchange = 16 finished = 20 class ContentType: change_cipher_spec = 20 alert = 21 handshake = 22 application_data = 23 all = (20,21,22,23) class AlertLevel: warning = 1 fatal = 2 class AlertDescription: """ @cvar bad_record_mac: A TLS record failed to decrypt properly. If this occurs during a shared-key or SRP handshake it most likely indicates a bad password. It may also indicate an implementation error, or some tampering with the data in transit. This alert will be signalled by the server if the SRP password is bad. It may also be signalled by the server if the SRP username is unknown to the server, but it doesn't wish to reveal that fact. This alert will be signalled by the client if the shared-key username is bad. @cvar handshake_failure: A problem occurred while handshaking. This typically indicates a lack of common ciphersuites between client and server, or some other disagreement (about SRP parameters or key sizes, for example). @cvar protocol_version: The other party's SSL/TLS version was unacceptable. This indicates that the client and server couldn't agree on which version of SSL or TLS to use. @cvar user_canceled: The handshake is being cancelled for some reason. """ close_notify = 0 unexpected_message = 10 bad_record_mac = 20 decryption_failed = 21 record_overflow = 22 decompression_failure = 30 handshake_failure = 40 no_certificate = 41 #SSLv3 bad_certificate = 42 unsupported_certificate = 43 certificate_revoked = 44 certificate_expired = 45 certificate_unknown = 46 illegal_parameter = 47 unknown_ca = 48 access_denied = 49 decode_error = 50 decrypt_error = 51 export_restriction = 60 protocol_version = 70 insufficient_security = 71 internal_error = 80 user_canceled = 90 no_renegotiation = 100 unknown_srp_username = 120 missing_srp_username = 121 untrusted_srp_parameters = 122 class CipherSuite: TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA = 0x0050 TLS_SRP_SHA_WITH_AES_128_CBC_SHA = 0x0053 TLS_SRP_SHA_WITH_AES_256_CBC_SHA = 0x0056 TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA = 0x0051 TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA = 0x0054 TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA = 0x0057 TLS_RSA_WITH_3DES_EDE_CBC_SHA = 0x000A TLS_RSA_WITH_AES_128_CBC_SHA = 0x002F TLS_RSA_WITH_AES_256_CBC_SHA = 0x0035 TLS_RSA_WITH_RC4_128_SHA = 0x0005 srpSuites = [] srpSuites.append(TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA) srpSuites.append(TLS_SRP_SHA_WITH_AES_128_CBC_SHA) srpSuites.append(TLS_SRP_SHA_WITH_AES_256_CBC_SHA) def getSrpSuites(ciphers): suites = [] for cipher in ciphers: if cipher == "aes128": suites.append(CipherSuite.TLS_SRP_SHA_WITH_AES_128_CBC_SHA) elif cipher == "aes256": suites.append(CipherSuite.TLS_SRP_SHA_WITH_AES_256_CBC_SHA) elif cipher == "3des": suites.append(CipherSuite.TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA) return suites getSrpSuites = staticmethod(getSrpSuites) srpRsaSuites = [] srpRsaSuites.append(TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA) srpRsaSuites.append(TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA) srpRsaSuites.append(TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA) def getSrpRsaSuites(ciphers): suites = [] for cipher in ciphers: if cipher == "aes128": suites.append(CipherSuite.TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA) elif cipher == "aes256": suites.append(CipherSuite.TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA) elif cipher == "3des": suites.append(CipherSuite.TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA) return suites getSrpRsaSuites = staticmethod(getSrpRsaSuites) rsaSuites = [] rsaSuites.append(TLS_RSA_WITH_3DES_EDE_CBC_SHA) rsaSuites.append(TLS_RSA_WITH_AES_128_CBC_SHA) rsaSuites.append(TLS_RSA_WITH_AES_256_CBC_SHA) rsaSuites.append(TLS_RSA_WITH_RC4_128_SHA) def getRsaSuites(ciphers): suites = [] for cipher in ciphers: if cipher == "aes128": suites.append(CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA) elif cipher == "aes256": suites.append(CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA) elif cipher == "rc4": suites.append(CipherSuite.TLS_RSA_WITH_RC4_128_SHA) elif cipher == "3des": suites.append(CipherSuite.TLS_RSA_WITH_3DES_EDE_CBC_SHA) return suites getRsaSuites = staticmethod(getRsaSuites) tripleDESSuites = [] tripleDESSuites.append(TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA) tripleDESSuites.append(TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA) tripleDESSuites.append(TLS_RSA_WITH_3DES_EDE_CBC_SHA) aes128Suites = [] aes128Suites.append(TLS_SRP_SHA_WITH_AES_128_CBC_SHA) aes128Suites.append(TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA) aes128Suites.append(TLS_RSA_WITH_AES_128_CBC_SHA) aes256Suites = [] aes256Suites.append(TLS_SRP_SHA_WITH_AES_256_CBC_SHA) aes256Suites.append(TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA) aes256Suites.append(TLS_RSA_WITH_AES_256_CBC_SHA) rc4Suites = [] rc4Suites.append(TLS_RSA_WITH_RC4_128_SHA) class Fault: badUsername = 101 badPassword = 102 badA = 103 clientSrpFaults = range(101,104) badVerifyMessage = 601 clientCertFaults = range(601,602) badPremasterPadding = 501 shortPremasterSecret = 502 clientNoAuthFaults = range(501,503) badIdentifier = 401 badSharedKey = 402 clientSharedKeyFaults = range(401,403) badB = 201 serverFaults = range(201,202) badFinished = 300 badMAC = 301 badPadding = 302 genericFaults = range(300,303) faultAlerts = {\ badUsername: (AlertDescription.unknown_srp_username, \ AlertDescription.bad_record_mac),\ badPassword: (AlertDescription.bad_record_mac,),\ badA: (AlertDescription.illegal_parameter,),\ badIdentifier: (AlertDescription.handshake_failure,),\ badSharedKey: (AlertDescription.bad_record_mac,),\ badPremasterPadding: (AlertDescription.bad_record_mac,),\ shortPremasterSecret: (AlertDescription.bad_record_mac,),\ badVerifyMessage: (AlertDescription.decrypt_error,),\ badFinished: (AlertDescription.decrypt_error,),\ badMAC: (AlertDescription.bad_record_mac,),\ badPadding: (AlertDescription.bad_record_mac,) } faultNames = {\ badUsername: "bad username",\ badPassword: "bad password",\ badA: "bad A",\ badIdentifier: "bad identifier",\ badSharedKey: "bad sharedkey",\ badPremasterPadding: "bad premaster padding",\ shortPremasterSecret: "short premaster secret",\ badVerifyMessage: "bad verify message",\ badFinished: "bad finished message",\ badMAC: "bad MAC",\ badPadding: "bad padding" } gdata-2.0.18/src/gdata/tlslite/HandshakeSettings.py0000750036466300116100000001433412156622363022255 0ustar afshareng00000000000000"""Class for setting handshake parameters.""" from constants import CertificateType from utils import cryptomath from utils import cipherfactory class HandshakeSettings: """This class encapsulates various parameters that can be used with a TLS handshake. @sort: minKeySize, maxKeySize, cipherNames, certificateTypes, minVersion, maxVersion @type minKeySize: int @ivar minKeySize: The minimum bit length for asymmetric keys. If the other party tries to use SRP, RSA, or Diffie-Hellman parameters smaller than this length, an alert will be signalled. The default is 1023. @type maxKeySize: int @ivar maxKeySize: The maximum bit length for asymmetric keys. If the other party tries to use SRP, RSA, or Diffie-Hellman parameters larger than this length, an alert will be signalled. The default is 8193. @type cipherNames: list @ivar cipherNames: The allowed ciphers, in order of preference. The allowed values in this list are 'aes256', 'aes128', '3des', and 'rc4'. If these settings are used with a client handshake, they determine the order of the ciphersuites offered in the ClientHello message. If these settings are used with a server handshake, the server will choose whichever ciphersuite matches the earliest entry in this list. NOTE: If '3des' is used in this list, but TLS Lite can't find an add-on library that supports 3DES, then '3des' will be silently removed. The default value is ['aes256', 'aes128', '3des', 'rc4']. @type certificateTypes: list @ivar certificateTypes: The allowed certificate types, in order of preference. The allowed values in this list are 'x509' and 'cryptoID'. This list is only used with a client handshake. The client will advertise to the server which certificate types are supported, and will check that the server uses one of the appropriate types. NOTE: If 'cryptoID' is used in this list, but cryptoIDlib is not installed, then 'cryptoID' will be silently removed. @type minVersion: tuple @ivar minVersion: The minimum allowed SSL/TLS version. This variable can be set to (3,0) for SSL 3.0, (3,1) for TLS 1.0, or (3,2) for TLS 1.1. If the other party wishes to use a lower version, a protocol_version alert will be signalled. The default is (3,0). @type maxVersion: tuple @ivar maxVersion: The maximum allowed SSL/TLS version. This variable can be set to (3,0) for SSL 3.0, (3,1) for TLS 1.0, or (3,2) for TLS 1.1. If the other party wishes to use a higher version, a protocol_version alert will be signalled. The default is (3,2). (WARNING: Some servers may (improperly) reject clients which offer support for TLS 1.1. In this case, try lowering maxVersion to (3,1)). """ def __init__(self): self.minKeySize = 1023 self.maxKeySize = 8193 self.cipherNames = ["aes256", "aes128", "3des", "rc4"] self.cipherImplementations = ["cryptlib", "openssl", "pycrypto", "python"] self.certificateTypes = ["x509", "cryptoID"] self.minVersion = (3,0) self.maxVersion = (3,2) #Filters out options that are not supported def _filter(self): other = HandshakeSettings() other.minKeySize = self.minKeySize other.maxKeySize = self.maxKeySize other.cipherNames = self.cipherNames other.cipherImplementations = self.cipherImplementations other.certificateTypes = self.certificateTypes other.minVersion = self.minVersion other.maxVersion = self.maxVersion if not cipherfactory.tripleDESPresent: other.cipherNames = [e for e in self.cipherNames if e != "3des"] if len(other.cipherNames)==0: raise ValueError("No supported ciphers") try: import cryptoIDlib except ImportError: other.certificateTypes = [e for e in self.certificateTypes \ if e != "cryptoID"] if len(other.certificateTypes)==0: raise ValueError("No supported certificate types") if not cryptomath.cryptlibpyLoaded: other.cipherImplementations = [e for e in \ self.cipherImplementations if e != "cryptlib"] if not cryptomath.m2cryptoLoaded: other.cipherImplementations = [e for e in \ other.cipherImplementations if e != "openssl"] if not cryptomath.pycryptoLoaded: other.cipherImplementations = [e for e in \ other.cipherImplementations if e != "pycrypto"] if len(other.cipherImplementations)==0: raise ValueError("No supported cipher implementations") if other.minKeySize<512: raise ValueError("minKeySize too small") if other.minKeySize>16384: raise ValueError("minKeySize too large") if other.maxKeySize<512: raise ValueError("maxKeySize too small") if other.maxKeySize>16384: raise ValueError("maxKeySize too large") for s in other.cipherNames: if s not in ("aes256", "aes128", "rc4", "3des"): raise ValueError("Unknown cipher name: '%s'" % s) for s in other.cipherImplementations: if s not in ("cryptlib", "openssl", "python", "pycrypto"): raise ValueError("Unknown cipher implementation: '%s'" % s) for s in other.certificateTypes: if s not in ("x509", "cryptoID"): raise ValueError("Unknown certificate type: '%s'" % s) if other.minVersion > other.maxVersion: raise ValueError("Versions set incorrectly") if not other.minVersion in ((3,0), (3,1), (3,2)): raise ValueError("minVersion set incorrectly") if not other.maxVersion in ((3,0), (3,1), (3,2)): raise ValueError("maxVersion set incorrectly") return other def _getCertificateTypes(self): l = [] for ct in self.certificateTypes: if ct == "x509": l.append(CertificateType.x509) elif ct == "cryptoID": l.append(CertificateType.cryptoID) else: raise AssertionError() return l gdata-2.0.18/src/gdata/tlslite/messages.py0000750036466300116100000004377512156622363020470 0ustar afshareng00000000000000"""Classes representing TLS messages.""" from utils.compat import * from utils.cryptomath import * from errors import * from utils.codec import * from constants import * from X509 import X509 from X509CertChain import X509CertChain import sha import md5 class RecordHeader3: def __init__(self): self.type = 0 self.version = (0,0) self.length = 0 self.ssl2 = False def create(self, version, type, length): self.type = type self.version = version self.length = length return self def write(self): w = Writer(5) w.add(self.type, 1) w.add(self.version[0], 1) w.add(self.version[1], 1) w.add(self.length, 2) return w.bytes def parse(self, p): self.type = p.get(1) self.version = (p.get(1), p.get(1)) self.length = p.get(2) self.ssl2 = False return self class RecordHeader2: def __init__(self): self.type = 0 self.version = (0,0) self.length = 0 self.ssl2 = True def parse(self, p): if p.get(1)!=128: raise SyntaxError() self.type = ContentType.handshake self.version = (2,0) #We don't support 2-byte-length-headers; could be a problem self.length = p.get(1) return self class Msg: def preWrite(self, trial): if trial: w = Writer() else: length = self.write(True) w = Writer(length) return w def postWrite(self, w, trial): if trial: return w.index else: return w.bytes class Alert(Msg): def __init__(self): self.contentType = ContentType.alert self.level = 0 self.description = 0 def create(self, description, level=AlertLevel.fatal): self.level = level self.description = description return self def parse(self, p): p.setLengthCheck(2) self.level = p.get(1) self.description = p.get(1) p.stopLengthCheck() return self def write(self): w = Writer(2) w.add(self.level, 1) w.add(self.description, 1) return w.bytes class HandshakeMsg(Msg): def preWrite(self, handshakeType, trial): if trial: w = Writer() w.add(handshakeType, 1) w.add(0, 3) else: length = self.write(True) w = Writer(length) w.add(handshakeType, 1) w.add(length-4, 3) return w class ClientHello(HandshakeMsg): def __init__(self, ssl2=False): self.contentType = ContentType.handshake self.ssl2 = ssl2 self.client_version = (0,0) self.random = createByteArrayZeros(32) self.session_id = createByteArraySequence([]) self.cipher_suites = [] # a list of 16-bit values self.certificate_types = [CertificateType.x509] self.compression_methods = [] # a list of 8-bit values self.srp_username = None # a string def create(self, version, random, session_id, cipher_suites, certificate_types=None, srp_username=None): self.client_version = version self.random = random self.session_id = session_id self.cipher_suites = cipher_suites self.certificate_types = certificate_types self.compression_methods = [0] self.srp_username = srp_username return self def parse(self, p): if self.ssl2: self.client_version = (p.get(1), p.get(1)) cipherSpecsLength = p.get(2) sessionIDLength = p.get(2) randomLength = p.get(2) self.cipher_suites = p.getFixList(3, int(cipherSpecsLength/3)) self.session_id = p.getFixBytes(sessionIDLength) self.random = p.getFixBytes(randomLength) if len(self.random) < 32: zeroBytes = 32-len(self.random) self.random = createByteArrayZeros(zeroBytes) + self.random self.compression_methods = [0]#Fake this value #We're not doing a stopLengthCheck() for SSLv2, oh well.. else: p.startLengthCheck(3) self.client_version = (p.get(1), p.get(1)) self.random = p.getFixBytes(32) self.session_id = p.getVarBytes(1) self.cipher_suites = p.getVarList(2, 2) self.compression_methods = p.getVarList(1, 1) if not p.atLengthCheck(): totalExtLength = p.get(2) soFar = 0 while soFar != totalExtLength: extType = p.get(2) extLength = p.get(2) if extType == 6: self.srp_username = bytesToString(p.getVarBytes(1)) elif extType == 7: self.certificate_types = p.getVarList(1, 1) else: p.getFixBytes(extLength) soFar += 4 + extLength p.stopLengthCheck() return self def write(self, trial=False): w = HandshakeMsg.preWrite(self, HandshakeType.client_hello, trial) w.add(self.client_version[0], 1) w.add(self.client_version[1], 1) w.addFixSeq(self.random, 1) w.addVarSeq(self.session_id, 1, 1) w.addVarSeq(self.cipher_suites, 2, 2) w.addVarSeq(self.compression_methods, 1, 1) extLength = 0 if self.certificate_types and self.certificate_types != \ [CertificateType.x509]: extLength += 5 + len(self.certificate_types) if self.srp_username: extLength += 5 + len(self.srp_username) if extLength > 0: w.add(extLength, 2) if self.certificate_types and self.certificate_types != \ [CertificateType.x509]: w.add(7, 2) w.add(len(self.certificate_types)+1, 2) w.addVarSeq(self.certificate_types, 1, 1) if self.srp_username: w.add(6, 2) w.add(len(self.srp_username)+1, 2) w.addVarSeq(stringToBytes(self.srp_username), 1, 1) return HandshakeMsg.postWrite(self, w, trial) class ServerHello(HandshakeMsg): def __init__(self): self.contentType = ContentType.handshake self.server_version = (0,0) self.random = createByteArrayZeros(32) self.session_id = createByteArraySequence([]) self.cipher_suite = 0 self.certificate_type = CertificateType.x509 self.compression_method = 0 def create(self, version, random, session_id, cipher_suite, certificate_type): self.server_version = version self.random = random self.session_id = session_id self.cipher_suite = cipher_suite self.certificate_type = certificate_type self.compression_method = 0 return self def parse(self, p): p.startLengthCheck(3) self.server_version = (p.get(1), p.get(1)) self.random = p.getFixBytes(32) self.session_id = p.getVarBytes(1) self.cipher_suite = p.get(2) self.compression_method = p.get(1) if not p.atLengthCheck(): totalExtLength = p.get(2) soFar = 0 while soFar != totalExtLength: extType = p.get(2) extLength = p.get(2) if extType == 7: self.certificate_type = p.get(1) else: p.getFixBytes(extLength) soFar += 4 + extLength p.stopLengthCheck() return self def write(self, trial=False): w = HandshakeMsg.preWrite(self, HandshakeType.server_hello, trial) w.add(self.server_version[0], 1) w.add(self.server_version[1], 1) w.addFixSeq(self.random, 1) w.addVarSeq(self.session_id, 1, 1) w.add(self.cipher_suite, 2) w.add(self.compression_method, 1) extLength = 0 if self.certificate_type and self.certificate_type != \ CertificateType.x509: extLength += 5 if extLength != 0: w.add(extLength, 2) if self.certificate_type and self.certificate_type != \ CertificateType.x509: w.add(7, 2) w.add(1, 2) w.add(self.certificate_type, 1) return HandshakeMsg.postWrite(self, w, trial) class Certificate(HandshakeMsg): def __init__(self, certificateType): self.certificateType = certificateType self.contentType = ContentType.handshake self.certChain = None def create(self, certChain): self.certChain = certChain return self def parse(self, p): p.startLengthCheck(3) if self.certificateType == CertificateType.x509: chainLength = p.get(3) index = 0 certificate_list = [] while index != chainLength: certBytes = p.getVarBytes(3) x509 = X509() x509.parseBinary(certBytes) certificate_list.append(x509) index += len(certBytes)+3 if certificate_list: self.certChain = X509CertChain(certificate_list) elif self.certificateType == CertificateType.cryptoID: s = bytesToString(p.getVarBytes(2)) if s: try: import cryptoIDlib.CertChain except ImportError: raise SyntaxError(\ "cryptoID cert chain received, cryptoIDlib not present") self.certChain = cryptoIDlib.CertChain.CertChain().parse(s) else: raise AssertionError() p.stopLengthCheck() return self def write(self, trial=False): w = HandshakeMsg.preWrite(self, HandshakeType.certificate, trial) if self.certificateType == CertificateType.x509: chainLength = 0 if self.certChain: certificate_list = self.certChain.x509List else: certificate_list = [] #determine length for cert in certificate_list: bytes = cert.writeBytes() chainLength += len(bytes)+3 #add bytes w.add(chainLength, 3) for cert in certificate_list: bytes = cert.writeBytes() w.addVarSeq(bytes, 1, 3) elif self.certificateType == CertificateType.cryptoID: if self.certChain: bytes = stringToBytes(self.certChain.write()) else: bytes = createByteArraySequence([]) w.addVarSeq(bytes, 1, 2) else: raise AssertionError() return HandshakeMsg.postWrite(self, w, trial) class CertificateRequest(HandshakeMsg): def __init__(self): self.contentType = ContentType.handshake self.certificate_types = [] #treat as opaque bytes for now self.certificate_authorities = createByteArraySequence([]) def create(self, certificate_types, certificate_authorities): self.certificate_types = certificate_types self.certificate_authorities = certificate_authorities return self def parse(self, p): p.startLengthCheck(3) self.certificate_types = p.getVarList(1, 1) self.certificate_authorities = p.getVarBytes(2) p.stopLengthCheck() return self def write(self, trial=False): w = HandshakeMsg.preWrite(self, HandshakeType.certificate_request, trial) w.addVarSeq(self.certificate_types, 1, 1) w.addVarSeq(self.certificate_authorities, 1, 2) return HandshakeMsg.postWrite(self, w, trial) class ServerKeyExchange(HandshakeMsg): def __init__(self, cipherSuite): self.cipherSuite = cipherSuite self.contentType = ContentType.handshake self.srp_N = 0L self.srp_g = 0L self.srp_s = createByteArraySequence([]) self.srp_B = 0L self.signature = createByteArraySequence([]) def createSRP(self, srp_N, srp_g, srp_s, srp_B): self.srp_N = srp_N self.srp_g = srp_g self.srp_s = srp_s self.srp_B = srp_B return self def parse(self, p): p.startLengthCheck(3) self.srp_N = bytesToNumber(p.getVarBytes(2)) self.srp_g = bytesToNumber(p.getVarBytes(2)) self.srp_s = p.getVarBytes(1) self.srp_B = bytesToNumber(p.getVarBytes(2)) if self.cipherSuite in CipherSuite.srpRsaSuites: self.signature = p.getVarBytes(2) p.stopLengthCheck() return self def write(self, trial=False): w = HandshakeMsg.preWrite(self, HandshakeType.server_key_exchange, trial) w.addVarSeq(numberToBytes(self.srp_N), 1, 2) w.addVarSeq(numberToBytes(self.srp_g), 1, 2) w.addVarSeq(self.srp_s, 1, 1) w.addVarSeq(numberToBytes(self.srp_B), 1, 2) if self.cipherSuite in CipherSuite.srpRsaSuites: w.addVarSeq(self.signature, 1, 2) return HandshakeMsg.postWrite(self, w, trial) def hash(self, clientRandom, serverRandom): oldCipherSuite = self.cipherSuite self.cipherSuite = None try: bytes = clientRandom + serverRandom + self.write()[4:] s = bytesToString(bytes) return stringToBytes(md5.md5(s).digest() + sha.sha(s).digest()) finally: self.cipherSuite = oldCipherSuite class ServerHelloDone(HandshakeMsg): def __init__(self): self.contentType = ContentType.handshake def create(self): return self def parse(self, p): p.startLengthCheck(3) p.stopLengthCheck() return self def write(self, trial=False): w = HandshakeMsg.preWrite(self, HandshakeType.server_hello_done, trial) return HandshakeMsg.postWrite(self, w, trial) class ClientKeyExchange(HandshakeMsg): def __init__(self, cipherSuite, version=None): self.cipherSuite = cipherSuite self.version = version self.contentType = ContentType.handshake self.srp_A = 0 self.encryptedPreMasterSecret = createByteArraySequence([]) def createSRP(self, srp_A): self.srp_A = srp_A return self def createRSA(self, encryptedPreMasterSecret): self.encryptedPreMasterSecret = encryptedPreMasterSecret return self def parse(self, p): p.startLengthCheck(3) if self.cipherSuite in CipherSuite.srpSuites + \ CipherSuite.srpRsaSuites: self.srp_A = bytesToNumber(p.getVarBytes(2)) elif self.cipherSuite in CipherSuite.rsaSuites: if self.version in ((3,1), (3,2)): self.encryptedPreMasterSecret = p.getVarBytes(2) elif self.version == (3,0): self.encryptedPreMasterSecret = \ p.getFixBytes(len(p.bytes)-p.index) else: raise AssertionError() else: raise AssertionError() p.stopLengthCheck() return self def write(self, trial=False): w = HandshakeMsg.preWrite(self, HandshakeType.client_key_exchange, trial) if self.cipherSuite in CipherSuite.srpSuites + \ CipherSuite.srpRsaSuites: w.addVarSeq(numberToBytes(self.srp_A), 1, 2) elif self.cipherSuite in CipherSuite.rsaSuites: if self.version in ((3,1), (3,2)): w.addVarSeq(self.encryptedPreMasterSecret, 1, 2) elif self.version == (3,0): w.addFixSeq(self.encryptedPreMasterSecret, 1) else: raise AssertionError() else: raise AssertionError() return HandshakeMsg.postWrite(self, w, trial) class CertificateVerify(HandshakeMsg): def __init__(self): self.contentType = ContentType.handshake self.signature = createByteArraySequence([]) def create(self, signature): self.signature = signature return self def parse(self, p): p.startLengthCheck(3) self.signature = p.getVarBytes(2) p.stopLengthCheck() return self def write(self, trial=False): w = HandshakeMsg.preWrite(self, HandshakeType.certificate_verify, trial) w.addVarSeq(self.signature, 1, 2) return HandshakeMsg.postWrite(self, w, trial) class ChangeCipherSpec(Msg): def __init__(self): self.contentType = ContentType.change_cipher_spec self.type = 1 def create(self): self.type = 1 return self def parse(self, p): p.setLengthCheck(1) self.type = p.get(1) p.stopLengthCheck() return self def write(self, trial=False): w = Msg.preWrite(self, trial) w.add(self.type,1) return Msg.postWrite(self, w, trial) class Finished(HandshakeMsg): def __init__(self, version): self.contentType = ContentType.handshake self.version = version self.verify_data = createByteArraySequence([]) def create(self, verify_data): self.verify_data = verify_data return self def parse(self, p): p.startLengthCheck(3) if self.version == (3,0): self.verify_data = p.getFixBytes(36) elif self.version in ((3,1), (3,2)): self.verify_data = p.getFixBytes(12) else: raise AssertionError() p.stopLengthCheck() return self def write(self, trial=False): w = HandshakeMsg.preWrite(self, HandshakeType.finished, trial) w.addFixSeq(self.verify_data, 1) return HandshakeMsg.postWrite(self, w, trial) class ApplicationData(Msg): def __init__(self): self.contentType = ContentType.application_data self.bytes = createByteArraySequence([]) def create(self, bytes): self.bytes = bytes return self def parse(self, p): self.bytes = p.bytes return self def write(self): return self.bytesgdata-2.0.18/src/gdata/tlslite/FileObject.py0000750036466300116100000001522712156622363020656 0ustar afshareng00000000000000"""Class returned by TLSConnection.makefile().""" class FileObject: """This class provides a file object interface to a L{tlslite.TLSConnection.TLSConnection}. Call makefile() on a TLSConnection to create a FileObject instance. This class was copied, with minor modifications, from the _fileobject class in socket.py. Note that fileno() is not implemented.""" default_bufsize = 16384 #TREV: changed from 8192 def __init__(self, sock, mode='rb', bufsize=-1): self._sock = sock self.mode = mode # Not actually used in this version if bufsize < 0: bufsize = self.default_bufsize self.bufsize = bufsize self.softspace = False if bufsize == 0: self._rbufsize = 1 elif bufsize == 1: self._rbufsize = self.default_bufsize else: self._rbufsize = bufsize self._wbufsize = bufsize self._rbuf = "" # A string self._wbuf = [] # A list of strings def _getclosed(self): return self._sock is not None closed = property(_getclosed, doc="True if the file is closed") def close(self): try: if self._sock: for result in self._sock._decrefAsync(): #TREV pass finally: self._sock = None def __del__(self): try: self.close() except: # close() may fail if __init__ didn't complete pass def flush(self): if self._wbuf: buffer = "".join(self._wbuf) self._wbuf = [] self._sock.sendall(buffer) #def fileno(self): # raise NotImplementedError() #TREV def write(self, data): data = str(data) # XXX Should really reject non-string non-buffers if not data: return self._wbuf.append(data) if (self._wbufsize == 0 or self._wbufsize == 1 and '\n' in data or self._get_wbuf_len() >= self._wbufsize): self.flush() def writelines(self, list): # XXX We could do better here for very long lists # XXX Should really reject non-string non-buffers self._wbuf.extend(filter(None, map(str, list))) if (self._wbufsize <= 1 or self._get_wbuf_len() >= self._wbufsize): self.flush() def _get_wbuf_len(self): buf_len = 0 for x in self._wbuf: buf_len += len(x) return buf_len def read(self, size=-1): data = self._rbuf if size < 0: # Read until EOF buffers = [] if data: buffers.append(data) self._rbuf = "" if self._rbufsize <= 1: recv_size = self.default_bufsize else: recv_size = self._rbufsize while True: data = self._sock.recv(recv_size) if not data: break buffers.append(data) return "".join(buffers) else: # Read until size bytes or EOF seen, whichever comes first buf_len = len(data) if buf_len >= size: self._rbuf = data[size:] return data[:size] buffers = [] if data: buffers.append(data) self._rbuf = "" while True: left = size - buf_len recv_size = max(self._rbufsize, left) data = self._sock.recv(recv_size) if not data: break buffers.append(data) n = len(data) if n >= left: self._rbuf = data[left:] buffers[-1] = data[:left] break buf_len += n return "".join(buffers) def readline(self, size=-1): data = self._rbuf if size < 0: # Read until \n or EOF, whichever comes first if self._rbufsize <= 1: # Speed up unbuffered case assert data == "" buffers = [] recv = self._sock.recv while data != "\n": data = recv(1) if not data: break buffers.append(data) return "".join(buffers) nl = data.find('\n') if nl >= 0: nl += 1 self._rbuf = data[nl:] return data[:nl] buffers = [] if data: buffers.append(data) self._rbuf = "" while True: data = self._sock.recv(self._rbufsize) if not data: break buffers.append(data) nl = data.find('\n') if nl >= 0: nl += 1 self._rbuf = data[nl:] buffers[-1] = data[:nl] break return "".join(buffers) else: # Read until size bytes or \n or EOF seen, whichever comes first nl = data.find('\n', 0, size) if nl >= 0: nl += 1 self._rbuf = data[nl:] return data[:nl] buf_len = len(data) if buf_len >= size: self._rbuf = data[size:] return data[:size] buffers = [] if data: buffers.append(data) self._rbuf = "" while True: data = self._sock.recv(self._rbufsize) if not data: break buffers.append(data) left = size - buf_len nl = data.find('\n', 0, left) if nl >= 0: nl += 1 self._rbuf = data[nl:] buffers[-1] = data[:nl] break n = len(data) if n >= left: self._rbuf = data[left:] buffers[-1] = data[:left] break buf_len += n return "".join(buffers) def readlines(self, sizehint=0): total = 0 list = [] while True: line = self.readline() if not line: break list.append(line) total += len(line) if sizehint and total >= sizehint: break return list # Iterator protocols def __iter__(self): return self def next(self): line = self.readline() if not line: raise StopIteration return line gdata-2.0.18/src/gdata/tlslite/X509.py0000750036466300116100000001027712156622363017315 0ustar afshareng00000000000000"""Class representing an X.509 certificate.""" from utils.ASN1Parser import ASN1Parser from utils.cryptomath import * from utils.keyfactory import _createPublicRSAKey class X509: """This class represents an X.509 certificate. @type bytes: L{array.array} of unsigned bytes @ivar bytes: The DER-encoded ASN.1 certificate @type publicKey: L{tlslite.utils.RSAKey.RSAKey} @ivar publicKey: The subject public key from the certificate. """ def __init__(self): self.bytes = createByteArraySequence([]) self.publicKey = None def parse(self, s): """Parse a PEM-encoded X.509 certificate. @type s: str @param s: A PEM-encoded X.509 certificate (i.e. a base64-encoded certificate wrapped with "-----BEGIN CERTIFICATE-----" and "-----END CERTIFICATE-----" tags). """ start = s.find("-----BEGIN CERTIFICATE-----") end = s.find("-----END CERTIFICATE-----") if start == -1: raise SyntaxError("Missing PEM prefix") if end == -1: raise SyntaxError("Missing PEM postfix") s = s[start+len("-----BEGIN CERTIFICATE-----") : end] bytes = base64ToBytes(s) self.parseBinary(bytes) return self def parseBinary(self, bytes): """Parse a DER-encoded X.509 certificate. @type bytes: str or L{array.array} of unsigned bytes @param bytes: A DER-encoded X.509 certificate. """ if isinstance(bytes, type("")): bytes = stringToBytes(bytes) self.bytes = bytes p = ASN1Parser(bytes) #Get the tbsCertificate tbsCertificateP = p.getChild(0) #Is the optional version field present? #This determines which index the key is at. if tbsCertificateP.value[0]==0xA0: subjectPublicKeyInfoIndex = 6 else: subjectPublicKeyInfoIndex = 5 #Get the subjectPublicKeyInfo subjectPublicKeyInfoP = tbsCertificateP.getChild(\ subjectPublicKeyInfoIndex) #Get the algorithm algorithmP = subjectPublicKeyInfoP.getChild(0) rsaOID = algorithmP.value if list(rsaOID) != [6, 9, 42, 134, 72, 134, 247, 13, 1, 1, 1, 5, 0]: raise SyntaxError("Unrecognized AlgorithmIdentifier") #Get the subjectPublicKey subjectPublicKeyP = subjectPublicKeyInfoP.getChild(1) #Adjust for BIT STRING encapsulation if (subjectPublicKeyP.value[0] !=0): raise SyntaxError() subjectPublicKeyP = ASN1Parser(subjectPublicKeyP.value[1:]) #Get the modulus and exponent modulusP = subjectPublicKeyP.getChild(0) publicExponentP = subjectPublicKeyP.getChild(1) #Decode them into numbers n = bytesToNumber(modulusP.value) e = bytesToNumber(publicExponentP.value) #Create a public key instance self.publicKey = _createPublicRSAKey(n, e) def getFingerprint(self): """Get the hex-encoded fingerprint of this certificate. @rtype: str @return: A hex-encoded fingerprint. """ return sha.sha(self.bytes).hexdigest() def getCommonName(self): """Get the Subject's Common Name from the certificate. The cryptlib_py module must be installed in order to use this function. @rtype: str or None @return: The CN component of the certificate's subject DN, if present. """ import cryptlib_py import array c = cryptlib_py.cryptImportCert(self.bytes, cryptlib_py.CRYPT_UNUSED) name = cryptlib_py.CRYPT_CERTINFO_COMMONNAME try: try: length = cryptlib_py.cryptGetAttributeString(c, name, None) returnVal = array.array('B', [0] * length) cryptlib_py.cryptGetAttributeString(c, name, returnVal) returnVal = returnVal.tostring() except cryptlib_py.CryptException, e: if e[0] == cryptlib_py.CRYPT_ERROR_NOTFOUND: returnVal = None return returnVal finally: cryptlib_py.cryptDestroyCert(c) def writeBytes(self): return self.bytes gdata-2.0.18/src/gdata/tlslite/TLSConnection.py0000750036466300116100000021131312156622363021324 0ustar afshareng00000000000000""" MAIN CLASS FOR TLS LITE (START HERE!). """ from __future__ import generators import socket from utils.compat import formatExceptionTrace from TLSRecordLayer import TLSRecordLayer from Session import Session from constants import * from utils.cryptomath import getRandomBytes from errors import * from messages import * from mathtls import * from HandshakeSettings import HandshakeSettings class TLSConnection(TLSRecordLayer): """ This class wraps a socket and provides TLS handshaking and data transfer. To use this class, create a new instance, passing a connected socket into the constructor. Then call some handshake function. If the handshake completes without raising an exception, then a TLS connection has been negotiated. You can transfer data over this connection as if it were a socket. This class provides both synchronous and asynchronous versions of its key functions. The synchronous versions should be used when writing single-or multi-threaded code using blocking sockets. The asynchronous versions should be used when performing asynchronous, event-based I/O with non-blocking sockets. Asynchronous I/O is a complicated subject; typically, you should not use the asynchronous functions directly, but should use some framework like asyncore or Twisted which TLS Lite integrates with (see L{tlslite.integration.TLSAsyncDispatcherMixIn.TLSAsyncDispatcherMixIn} or L{tlslite.integration.TLSTwistedProtocolWrapper.TLSTwistedProtocolWrapper}). """ def __init__(self, sock): """Create a new TLSConnection instance. @param sock: The socket data will be transmitted on. The socket should already be connected. It may be in blocking or non-blocking mode. @type sock: L{socket.socket} """ TLSRecordLayer.__init__(self, sock) def handshakeClientSRP(self, username, password, session=None, settings=None, checker=None, async=False): """Perform an SRP handshake in the role of client. This function performs a TLS/SRP handshake. SRP mutually authenticates both parties to each other using only a username and password. This function may also perform a combined SRP and server-certificate handshake, if the server chooses to authenticate itself with a certificate chain in addition to doing SRP. TLS/SRP is non-standard. Most TLS implementations don't support it. See U{http://www.ietf.org/html.charters/tls-charter.html} or U{http://trevp.net/tlssrp/} for the latest information on TLS/SRP. Like any handshake function, this can be called on a closed TLS connection, or on a TLS connection that is already open. If called on an open connection it performs a re-handshake. If the function completes without raising an exception, the TLS connection will be open and available for data transfer. If an exception is raised, the connection will have been automatically closed (if it was ever open). @type username: str @param username: The SRP username. @type password: str @param password: The SRP password. @type session: L{tlslite.Session.Session} @param session: A TLS session to attempt to resume. This session must be an SRP session performed with the same username and password as were passed in. If the resumption does not succeed, a full SRP handshake will be performed. @type settings: L{tlslite.HandshakeSettings.HandshakeSettings} @param settings: Various settings which can be used to control the ciphersuites, certificate types, and SSL/TLS versions offered by the client. @type checker: L{tlslite.Checker.Checker} @param checker: A Checker instance. This instance will be invoked to examine the other party's authentication credentials, if the handshake completes succesfully. @type async: bool @param async: If False, this function will block until the handshake is completed. If True, this function will return a generator. Successive invocations of the generator will return 0 if it is waiting to read from the socket, 1 if it is waiting to write to the socket, or will raise StopIteration if the handshake operation is completed. @rtype: None or an iterable @return: If 'async' is True, a generator object will be returned. @raise socket.error: If a socket error occurs. @raise tlslite.errors.TLSAbruptCloseError: If the socket is closed without a preceding alert. @raise tlslite.errors.TLSAlert: If a TLS alert is signalled. @raise tlslite.errors.TLSAuthenticationError: If the checker doesn't like the other party's authentication credentials. """ handshaker = self._handshakeClientAsync(srpParams=(username, password), session=session, settings=settings, checker=checker) if async: return handshaker for result in handshaker: pass def handshakeClientCert(self, certChain=None, privateKey=None, session=None, settings=None, checker=None, async=False): """Perform a certificate-based handshake in the role of client. This function performs an SSL or TLS handshake. The server will authenticate itself using an X.509 or cryptoID certificate chain. If the handshake succeeds, the server's certificate chain will be stored in the session's serverCertChain attribute. Unless a checker object is passed in, this function does no validation or checking of the server's certificate chain. If the server requests client authentication, the client will send the passed-in certificate chain, and use the passed-in private key to authenticate itself. If no certificate chain and private key were passed in, the client will attempt to proceed without client authentication. The server may or may not allow this. Like any handshake function, this can be called on a closed TLS connection, or on a TLS connection that is already open. If called on an open connection it performs a re-handshake. If the function completes without raising an exception, the TLS connection will be open and available for data transfer. If an exception is raised, the connection will have been automatically closed (if it was ever open). @type certChain: L{tlslite.X509CertChain.X509CertChain} or L{cryptoIDlib.CertChain.CertChain} @param certChain: The certificate chain to be used if the server requests client authentication. @type privateKey: L{tlslite.utils.RSAKey.RSAKey} @param privateKey: The private key to be used if the server requests client authentication. @type session: L{tlslite.Session.Session} @param session: A TLS session to attempt to resume. If the resumption does not succeed, a full handshake will be performed. @type settings: L{tlslite.HandshakeSettings.HandshakeSettings} @param settings: Various settings which can be used to control the ciphersuites, certificate types, and SSL/TLS versions offered by the client. @type checker: L{tlslite.Checker.Checker} @param checker: A Checker instance. This instance will be invoked to examine the other party's authentication credentials, if the handshake completes succesfully. @type async: bool @param async: If False, this function will block until the handshake is completed. If True, this function will return a generator. Successive invocations of the generator will return 0 if it is waiting to read from the socket, 1 if it is waiting to write to the socket, or will raise StopIteration if the handshake operation is completed. @rtype: None or an iterable @return: If 'async' is True, a generator object will be returned. @raise socket.error: If a socket error occurs. @raise tlslite.errors.TLSAbruptCloseError: If the socket is closed without a preceding alert. @raise tlslite.errors.TLSAlert: If a TLS alert is signalled. @raise tlslite.errors.TLSAuthenticationError: If the checker doesn't like the other party's authentication credentials. """ handshaker = self._handshakeClientAsync(certParams=(certChain, privateKey), session=session, settings=settings, checker=checker) if async: return handshaker for result in handshaker: pass def handshakeClientUnknown(self, srpCallback=None, certCallback=None, session=None, settings=None, checker=None, async=False): """Perform a to-be-determined type of handshake in the role of client. This function performs an SSL or TLS handshake. If the server requests client certificate authentication, the certCallback will be invoked and should return a (certChain, privateKey) pair. If the callback returns None, the library will attempt to proceed without client authentication. The server may or may not allow this. If the server requests SRP authentication, the srpCallback will be invoked and should return a (username, password) pair. If the callback returns None, the local implementation will signal a user_canceled error alert. After the handshake completes, the client can inspect the connection's session attribute to determine what type of authentication was performed. Like any handshake function, this can be called on a closed TLS connection, or on a TLS connection that is already open. If called on an open connection it performs a re-handshake. If the function completes without raising an exception, the TLS connection will be open and available for data transfer. If an exception is raised, the connection will have been automatically closed (if it was ever open). @type srpCallback: callable @param srpCallback: The callback to be used if the server requests SRP authentication. If None, the client will not offer support for SRP ciphersuites. @type certCallback: callable @param certCallback: The callback to be used if the server requests client certificate authentication. @type session: L{tlslite.Session.Session} @param session: A TLS session to attempt to resume. If the resumption does not succeed, a full handshake will be performed. @type settings: L{tlslite.HandshakeSettings.HandshakeSettings} @param settings: Various settings which can be used to control the ciphersuites, certificate types, and SSL/TLS versions offered by the client. @type checker: L{tlslite.Checker.Checker} @param checker: A Checker instance. This instance will be invoked to examine the other party's authentication credentials, if the handshake completes succesfully. @type async: bool @param async: If False, this function will block until the handshake is completed. If True, this function will return a generator. Successive invocations of the generator will return 0 if it is waiting to read from the socket, 1 if it is waiting to write to the socket, or will raise StopIteration if the handshake operation is completed. @rtype: None or an iterable @return: If 'async' is True, a generator object will be returned. @raise socket.error: If a socket error occurs. @raise tlslite.errors.TLSAbruptCloseError: If the socket is closed without a preceding alert. @raise tlslite.errors.TLSAlert: If a TLS alert is signalled. @raise tlslite.errors.TLSAuthenticationError: If the checker doesn't like the other party's authentication credentials. """ handshaker = self._handshakeClientAsync(unknownParams=(srpCallback, certCallback), session=session, settings=settings, checker=checker) if async: return handshaker for result in handshaker: pass def handshakeClientSharedKey(self, username, sharedKey, settings=None, checker=None, async=False): """Perform a shared-key handshake in the role of client. This function performs a shared-key handshake. Using shared symmetric keys of high entropy (128 bits or greater) mutually authenticates both parties to each other. TLS with shared-keys is non-standard. Most TLS implementations don't support it. See U{http://www.ietf.org/html.charters/tls-charter.html} for the latest information on TLS with shared-keys. If the shared-keys Internet-Draft changes or is superceded, TLS Lite will track those changes, so the shared-key support in later versions of TLS Lite may become incompatible with this version. Like any handshake function, this can be called on a closed TLS connection, or on a TLS connection that is already open. If called on an open connection it performs a re-handshake. If the function completes without raising an exception, the TLS connection will be open and available for data transfer. If an exception is raised, the connection will have been automatically closed (if it was ever open). @type username: str @param username: The shared-key username. @type sharedKey: str @param sharedKey: The shared key. @type settings: L{tlslite.HandshakeSettings.HandshakeSettings} @param settings: Various settings which can be used to control the ciphersuites, certificate types, and SSL/TLS versions offered by the client. @type checker: L{tlslite.Checker.Checker} @param checker: A Checker instance. This instance will be invoked to examine the other party's authentication credentials, if the handshake completes succesfully. @type async: bool @param async: If False, this function will block until the handshake is completed. If True, this function will return a generator. Successive invocations of the generator will return 0 if it is waiting to read from the socket, 1 if it is waiting to write to the socket, or will raise StopIteration if the handshake operation is completed. @rtype: None or an iterable @return: If 'async' is True, a generator object will be returned. @raise socket.error: If a socket error occurs. @raise tlslite.errors.TLSAbruptCloseError: If the socket is closed without a preceding alert. @raise tlslite.errors.TLSAlert: If a TLS alert is signalled. @raise tlslite.errors.TLSAuthenticationError: If the checker doesn't like the other party's authentication credentials. """ handshaker = self._handshakeClientAsync(sharedKeyParams=(username, sharedKey), settings=settings, checker=checker) if async: return handshaker for result in handshaker: pass def _handshakeClientAsync(self, srpParams=(), certParams=(), unknownParams=(), sharedKeyParams=(), session=None, settings=None, checker=None, recursive=False): handshaker = self._handshakeClientAsyncHelper(srpParams=srpParams, certParams=certParams, unknownParams=unknownParams, sharedKeyParams=sharedKeyParams, session=session, settings=settings, recursive=recursive) for result in self._handshakeWrapperAsync(handshaker, checker): yield result def _handshakeClientAsyncHelper(self, srpParams, certParams, unknownParams, sharedKeyParams, session, settings, recursive): if not recursive: self._handshakeStart(client=True) #Unpack parameters srpUsername = None # srpParams password = None # srpParams clientCertChain = None # certParams privateKey = None # certParams srpCallback = None # unknownParams certCallback = None # unknownParams #session # sharedKeyParams (or session) #settings # settings if srpParams: srpUsername, password = srpParams elif certParams: clientCertChain, privateKey = certParams elif unknownParams: srpCallback, certCallback = unknownParams elif sharedKeyParams: session = Session()._createSharedKey(*sharedKeyParams) if not settings: settings = HandshakeSettings() settings = settings._filter() #Validate parameters if srpUsername and not password: raise ValueError("Caller passed a username but no password") if password and not srpUsername: raise ValueError("Caller passed a password but no username") if clientCertChain and not privateKey: raise ValueError("Caller passed a certChain but no privateKey") if privateKey and not clientCertChain: raise ValueError("Caller passed a privateKey but no certChain") if clientCertChain: foundType = False try: import cryptoIDlib.CertChain if isinstance(clientCertChain, cryptoIDlib.CertChain.CertChain): if "cryptoID" not in settings.certificateTypes: raise ValueError("Client certificate doesn't "\ "match Handshake Settings") settings.certificateTypes = ["cryptoID"] foundType = True except ImportError: pass if not foundType and isinstance(clientCertChain, X509CertChain): if "x509" not in settings.certificateTypes: raise ValueError("Client certificate doesn't match "\ "Handshake Settings") settings.certificateTypes = ["x509"] foundType = True if not foundType: raise ValueError("Unrecognized certificate type") if session: if not session.valid(): session = None #ignore non-resumable sessions... elif session.resumable and \ (session.srpUsername != srpUsername): raise ValueError("Session username doesn't match") #Add Faults to parameters if srpUsername and self.fault == Fault.badUsername: srpUsername += "GARBAGE" if password and self.fault == Fault.badPassword: password += "GARBAGE" if sharedKeyParams: identifier = sharedKeyParams[0] sharedKey = sharedKeyParams[1] if self.fault == Fault.badIdentifier: identifier += "GARBAGE" session = Session()._createSharedKey(identifier, sharedKey) elif self.fault == Fault.badSharedKey: sharedKey += "GARBAGE" session = Session()._createSharedKey(identifier, sharedKey) #Initialize locals serverCertChain = None cipherSuite = 0 certificateType = CertificateType.x509 premasterSecret = None #Get client nonce clientRandom = getRandomBytes(32) #Initialize acceptable ciphersuites cipherSuites = [] if srpParams: cipherSuites += CipherSuite.getSrpRsaSuites(settings.cipherNames) cipherSuites += CipherSuite.getSrpSuites(settings.cipherNames) elif certParams: cipherSuites += CipherSuite.getRsaSuites(settings.cipherNames) elif unknownParams: if srpCallback: cipherSuites += \ CipherSuite.getSrpRsaSuites(settings.cipherNames) cipherSuites += \ CipherSuite.getSrpSuites(settings.cipherNames) cipherSuites += CipherSuite.getRsaSuites(settings.cipherNames) elif sharedKeyParams: cipherSuites += CipherSuite.getRsaSuites(settings.cipherNames) else: cipherSuites += CipherSuite.getRsaSuites(settings.cipherNames) #Initialize acceptable certificate types certificateTypes = settings._getCertificateTypes() #Tentatively set the version to the client's minimum version. #We'll use this for the ClientHello, and if an error occurs #parsing the Server Hello, we'll use this version for the response self.version = settings.maxVersion #Either send ClientHello (with a resumable session)... if session: #If it's a resumable (i.e. not a shared-key session), then its #ciphersuite must be one of the acceptable ciphersuites if (not sharedKeyParams) and \ session.cipherSuite not in cipherSuites: raise ValueError("Session's cipher suite not consistent "\ "with parameters") else: clientHello = ClientHello() clientHello.create(settings.maxVersion, clientRandom, session.sessionID, cipherSuites, certificateTypes, session.srpUsername) #Or send ClientHello (without) else: clientHello = ClientHello() clientHello.create(settings.maxVersion, clientRandom, createByteArraySequence([]), cipherSuites, certificateTypes, srpUsername) for result in self._sendMsg(clientHello): yield result #Get ServerHello (or missing_srp_username) for result in self._getMsg((ContentType.handshake, ContentType.alert), HandshakeType.server_hello): if result in (0,1): yield result else: break msg = result if isinstance(msg, ServerHello): serverHello = msg elif isinstance(msg, Alert): alert = msg #If it's not a missing_srp_username, re-raise if alert.description != AlertDescription.missing_srp_username: self._shutdown(False) raise TLSRemoteAlert(alert) #If we're not in SRP callback mode, we won't have offered SRP #without a username, so we shouldn't get this alert if not srpCallback: for result in self._sendError(\ AlertDescription.unexpected_message): yield result srpParams = srpCallback() #If the callback returns None, cancel the handshake if srpParams == None: for result in self._sendError(AlertDescription.user_canceled): yield result #Recursively perform handshake for result in self._handshakeClientAsyncHelper(srpParams, None, None, None, None, settings, True): yield result return #Get the server version. Do this before anything else, so any #error alerts will use the server's version self.version = serverHello.server_version #Future responses from server must use this version self._versionCheck = True #Check ServerHello if serverHello.server_version < settings.minVersion: for result in self._sendError(\ AlertDescription.protocol_version, "Too old version: %s" % str(serverHello.server_version)): yield result if serverHello.server_version > settings.maxVersion: for result in self._sendError(\ AlertDescription.protocol_version, "Too new version: %s" % str(serverHello.server_version)): yield result if serverHello.cipher_suite not in cipherSuites: for result in self._sendError(\ AlertDescription.illegal_parameter, "Server responded with incorrect ciphersuite"): yield result if serverHello.certificate_type not in certificateTypes: for result in self._sendError(\ AlertDescription.illegal_parameter, "Server responded with incorrect certificate type"): yield result if serverHello.compression_method != 0: for result in self._sendError(\ AlertDescription.illegal_parameter, "Server responded with incorrect compression method"): yield result #Get the server nonce serverRandom = serverHello.random #If the server agrees to resume if session and session.sessionID and \ serverHello.session_id == session.sessionID: #If a shared-key, we're flexible about suites; otherwise the #server-chosen suite has to match the session's suite if sharedKeyParams: session.cipherSuite = serverHello.cipher_suite elif serverHello.cipher_suite != session.cipherSuite: for result in self._sendError(\ AlertDescription.illegal_parameter,\ "Server's ciphersuite doesn't match session"): yield result #Set the session for this connection self.session = session #Calculate pending connection states self._calcPendingStates(clientRandom, serverRandom, settings.cipherImplementations) #Exchange ChangeCipherSpec and Finished messages for result in self._getFinished(): yield result for result in self._sendFinished(): yield result #Mark the connection as open self._handshakeDone(resumed=True) #If server DOES NOT agree to resume else: if sharedKeyParams: for result in self._sendError(\ AlertDescription.user_canceled, "Was expecting a shared-key resumption"): yield result #We've already validated these cipherSuite = serverHello.cipher_suite certificateType = serverHello.certificate_type #If the server chose an SRP suite... if cipherSuite in CipherSuite.srpSuites: #Get ServerKeyExchange, ServerHelloDone for result in self._getMsg(ContentType.handshake, HandshakeType.server_key_exchange, cipherSuite): if result in (0,1): yield result else: break serverKeyExchange = result for result in self._getMsg(ContentType.handshake, HandshakeType.server_hello_done): if result in (0,1): yield result else: break serverHelloDone = result #If the server chose an SRP+RSA suite... elif cipherSuite in CipherSuite.srpRsaSuites: #Get Certificate, ServerKeyExchange, ServerHelloDone for result in self._getMsg(ContentType.handshake, HandshakeType.certificate, certificateType): if result in (0,1): yield result else: break serverCertificate = result for result in self._getMsg(ContentType.handshake, HandshakeType.server_key_exchange, cipherSuite): if result in (0,1): yield result else: break serverKeyExchange = result for result in self._getMsg(ContentType.handshake, HandshakeType.server_hello_done): if result in (0,1): yield result else: break serverHelloDone = result #If the server chose an RSA suite... elif cipherSuite in CipherSuite.rsaSuites: #Get Certificate[, CertificateRequest], ServerHelloDone for result in self._getMsg(ContentType.handshake, HandshakeType.certificate, certificateType): if result in (0,1): yield result else: break serverCertificate = result for result in self._getMsg(ContentType.handshake, (HandshakeType.server_hello_done, HandshakeType.certificate_request)): if result in (0,1): yield result else: break msg = result certificateRequest = None if isinstance(msg, CertificateRequest): certificateRequest = msg for result in self._getMsg(ContentType.handshake, HandshakeType.server_hello_done): if result in (0,1): yield result else: break serverHelloDone = result elif isinstance(msg, ServerHelloDone): serverHelloDone = msg else: raise AssertionError() #Calculate SRP premaster secret, if server chose an SRP or #SRP+RSA suite if cipherSuite in CipherSuite.srpSuites + \ CipherSuite.srpRsaSuites: #Get and check the server's group parameters and B value N = serverKeyExchange.srp_N g = serverKeyExchange.srp_g s = serverKeyExchange.srp_s B = serverKeyExchange.srp_B if (g,N) not in goodGroupParameters: for result in self._sendError(\ AlertDescription.untrusted_srp_parameters, "Unknown group parameters"): yield result if numBits(N) < settings.minKeySize: for result in self._sendError(\ AlertDescription.untrusted_srp_parameters, "N value is too small: %d" % numBits(N)): yield result if numBits(N) > settings.maxKeySize: for result in self._sendError(\ AlertDescription.untrusted_srp_parameters, "N value is too large: %d" % numBits(N)): yield result if B % N == 0: for result in self._sendError(\ AlertDescription.illegal_parameter, "Suspicious B value"): yield result #Check the server's signature, if server chose an #SRP+RSA suite if cipherSuite in CipherSuite.srpRsaSuites: #Hash ServerKeyExchange/ServerSRPParams hashBytes = serverKeyExchange.hash(clientRandom, serverRandom) #Extract signature bytes from ServerKeyExchange sigBytes = serverKeyExchange.signature if len(sigBytes) == 0: for result in self._sendError(\ AlertDescription.illegal_parameter, "Server sent an SRP ServerKeyExchange "\ "message without a signature"): yield result #Get server's public key from the Certificate message for result in self._getKeyFromChain(serverCertificate, settings): if result in (0,1): yield result else: break publicKey, serverCertChain = result #Verify signature if not publicKey.verify(sigBytes, hashBytes): for result in self._sendError(\ AlertDescription.decrypt_error, "Signature failed to verify"): yield result #Calculate client's ephemeral DH values (a, A) a = bytesToNumber(getRandomBytes(32)) A = powMod(g, a, N) #Calculate client's static DH values (x, v) x = makeX(bytesToString(s), srpUsername, password) v = powMod(g, x, N) #Calculate u u = makeU(N, A, B) #Calculate premaster secret k = makeK(N, g) S = powMod((B - (k*v)) % N, a+(u*x), N) if self.fault == Fault.badA: A = N S = 0 premasterSecret = numberToBytes(S) #Send ClientKeyExchange for result in self._sendMsg(\ ClientKeyExchange(cipherSuite).createSRP(A)): yield result #Calculate RSA premaster secret, if server chose an RSA suite elif cipherSuite in CipherSuite.rsaSuites: #Handle the presence of a CertificateRequest if certificateRequest: if unknownParams and certCallback: certParamsNew = certCallback() if certParamsNew: clientCertChain, privateKey = certParamsNew #Get server's public key from the Certificate message for result in self._getKeyFromChain(serverCertificate, settings): if result in (0,1): yield result else: break publicKey, serverCertChain = result #Calculate premaster secret premasterSecret = getRandomBytes(48) premasterSecret[0] = settings.maxVersion[0] premasterSecret[1] = settings.maxVersion[1] if self.fault == Fault.badPremasterPadding: premasterSecret[0] = 5 if self.fault == Fault.shortPremasterSecret: premasterSecret = premasterSecret[:-1] #Encrypt premaster secret to server's public key encryptedPreMasterSecret = publicKey.encrypt(premasterSecret) #If client authentication was requested, send Certificate #message, either with certificates or empty if certificateRequest: clientCertificate = Certificate(certificateType) if clientCertChain: #Check to make sure we have the same type of #certificates the server requested wrongType = False if certificateType == CertificateType.x509: if not isinstance(clientCertChain, X509CertChain): wrongType = True elif certificateType == CertificateType.cryptoID: if not isinstance(clientCertChain, cryptoIDlib.CertChain.CertChain): wrongType = True if wrongType: for result in self._sendError(\ AlertDescription.handshake_failure, "Client certificate is of wrong type"): yield result clientCertificate.create(clientCertChain) for result in self._sendMsg(clientCertificate): yield result else: #The server didn't request client auth, so we #zeroize these so the clientCertChain won't be #stored in the session. privateKey = None clientCertChain = None #Send ClientKeyExchange clientKeyExchange = ClientKeyExchange(cipherSuite, self.version) clientKeyExchange.createRSA(encryptedPreMasterSecret) for result in self._sendMsg(clientKeyExchange): yield result #If client authentication was requested and we have a #private key, send CertificateVerify if certificateRequest and privateKey: if self.version == (3,0): #Create a temporary session object, just for the #purpose of creating the CertificateVerify session = Session() session._calcMasterSecret(self.version, premasterSecret, clientRandom, serverRandom) verifyBytes = self._calcSSLHandshakeHash(\ session.masterSecret, "") elif self.version in ((3,1), (3,2)): verifyBytes = stringToBytes(\ self._handshake_md5.digest() + \ self._handshake_sha.digest()) if self.fault == Fault.badVerifyMessage: verifyBytes[0] = ((verifyBytes[0]+1) % 256) signedBytes = privateKey.sign(verifyBytes) certificateVerify = CertificateVerify() certificateVerify.create(signedBytes) for result in self._sendMsg(certificateVerify): yield result #Create the session object self.session = Session() self.session._calcMasterSecret(self.version, premasterSecret, clientRandom, serverRandom) self.session.sessionID = serverHello.session_id self.session.cipherSuite = cipherSuite self.session.srpUsername = srpUsername self.session.clientCertChain = clientCertChain self.session.serverCertChain = serverCertChain #Calculate pending connection states self._calcPendingStates(clientRandom, serverRandom, settings.cipherImplementations) #Exchange ChangeCipherSpec and Finished messages for result in self._sendFinished(): yield result for result in self._getFinished(): yield result #Mark the connection as open self.session._setResumable(True) self._handshakeDone(resumed=False) def handshakeServer(self, sharedKeyDB=None, verifierDB=None, certChain=None, privateKey=None, reqCert=False, sessionCache=None, settings=None, checker=None): """Perform a handshake in the role of server. This function performs an SSL or TLS handshake. Depending on the arguments and the behavior of the client, this function can perform a shared-key, SRP, or certificate-based handshake. It can also perform a combined SRP and server-certificate handshake. Like any handshake function, this can be called on a closed TLS connection, or on a TLS connection that is already open. If called on an open connection it performs a re-handshake. This function does not send a Hello Request message before performing the handshake, so if re-handshaking is required, the server must signal the client to begin the re-handshake through some other means. If the function completes without raising an exception, the TLS connection will be open and available for data transfer. If an exception is raised, the connection will have been automatically closed (if it was ever open). @type sharedKeyDB: L{tlslite.SharedKeyDB.SharedKeyDB} @param sharedKeyDB: A database of shared symmetric keys associated with usernames. If the client performs a shared-key handshake, the session's sharedKeyUsername attribute will be set. @type verifierDB: L{tlslite.VerifierDB.VerifierDB} @param verifierDB: A database of SRP password verifiers associated with usernames. If the client performs an SRP handshake, the session's srpUsername attribute will be set. @type certChain: L{tlslite.X509CertChain.X509CertChain} or L{cryptoIDlib.CertChain.CertChain} @param certChain: The certificate chain to be used if the client requests server certificate authentication. @type privateKey: L{tlslite.utils.RSAKey.RSAKey} @param privateKey: The private key to be used if the client requests server certificate authentication. @type reqCert: bool @param reqCert: Whether to request client certificate authentication. This only applies if the client chooses server certificate authentication; if the client chooses SRP or shared-key authentication, this will be ignored. If the client performs a client certificate authentication, the sessions's clientCertChain attribute will be set. @type sessionCache: L{tlslite.SessionCache.SessionCache} @param sessionCache: An in-memory cache of resumable sessions. The client can resume sessions from this cache. Alternatively, if the client performs a full handshake, a new session will be added to the cache. @type settings: L{tlslite.HandshakeSettings.HandshakeSettings} @param settings: Various settings which can be used to control the ciphersuites and SSL/TLS version chosen by the server. @type checker: L{tlslite.Checker.Checker} @param checker: A Checker instance. This instance will be invoked to examine the other party's authentication credentials, if the handshake completes succesfully. @raise socket.error: If a socket error occurs. @raise tlslite.errors.TLSAbruptCloseError: If the socket is closed without a preceding alert. @raise tlslite.errors.TLSAlert: If a TLS alert is signalled. @raise tlslite.errors.TLSAuthenticationError: If the checker doesn't like the other party's authentication credentials. """ for result in self.handshakeServerAsync(sharedKeyDB, verifierDB, certChain, privateKey, reqCert, sessionCache, settings, checker): pass def handshakeServerAsync(self, sharedKeyDB=None, verifierDB=None, certChain=None, privateKey=None, reqCert=False, sessionCache=None, settings=None, checker=None): """Start a server handshake operation on the TLS connection. This function returns a generator which behaves similarly to handshakeServer(). Successive invocations of the generator will return 0 if it is waiting to read from the socket, 1 if it is waiting to write to the socket, or it will raise StopIteration if the handshake operation is complete. @rtype: iterable @return: A generator; see above for details. """ handshaker = self._handshakeServerAsyncHelper(\ sharedKeyDB=sharedKeyDB, verifierDB=verifierDB, certChain=certChain, privateKey=privateKey, reqCert=reqCert, sessionCache=sessionCache, settings=settings) for result in self._handshakeWrapperAsync(handshaker, checker): yield result def _handshakeServerAsyncHelper(self, sharedKeyDB, verifierDB, certChain, privateKey, reqCert, sessionCache, settings): self._handshakeStart(client=False) if (not sharedKeyDB) and (not verifierDB) and (not certChain): raise ValueError("Caller passed no authentication credentials") if certChain and not privateKey: raise ValueError("Caller passed a certChain but no privateKey") if privateKey and not certChain: raise ValueError("Caller passed a privateKey but no certChain") if not settings: settings = HandshakeSettings() settings = settings._filter() #Initialize acceptable cipher suites cipherSuites = [] if verifierDB: if certChain: cipherSuites += \ CipherSuite.getSrpRsaSuites(settings.cipherNames) cipherSuites += CipherSuite.getSrpSuites(settings.cipherNames) if sharedKeyDB or certChain: cipherSuites += CipherSuite.getRsaSuites(settings.cipherNames) #Initialize acceptable certificate type certificateType = None if certChain: try: import cryptoIDlib.CertChain if isinstance(certChain, cryptoIDlib.CertChain.CertChain): certificateType = CertificateType.cryptoID except ImportError: pass if isinstance(certChain, X509CertChain): certificateType = CertificateType.x509 if certificateType == None: raise ValueError("Unrecognized certificate type") #Initialize locals clientCertChain = None serverCertChain = None #We may set certChain to this later postFinishedError = None #Tentatively set version to most-desirable version, so if an error #occurs parsing the ClientHello, this is what we'll use for the #error alert self.version = settings.maxVersion #Get ClientHello for result in self._getMsg(ContentType.handshake, HandshakeType.client_hello): if result in (0,1): yield result else: break clientHello = result #If client's version is too low, reject it if clientHello.client_version < settings.minVersion: self.version = settings.minVersion for result in self._sendError(\ AlertDescription.protocol_version, "Too old version: %s" % str(clientHello.client_version)): yield result #If client's version is too high, propose my highest version elif clientHello.client_version > settings.maxVersion: self.version = settings.maxVersion else: #Set the version to the client's version self.version = clientHello.client_version #Get the client nonce; create server nonce clientRandom = clientHello.random serverRandom = getRandomBytes(32) #Calculate the first cipher suite intersection. #This is the 'privileged' ciphersuite. We'll use it if we're #doing a shared-key resumption or a new negotiation. In fact, #the only time we won't use it is if we're resuming a non-sharedkey #session, in which case we use the ciphersuite from the session. # #Given the current ciphersuite ordering, this means we prefer SRP #over non-SRP. for cipherSuite in cipherSuites: if cipherSuite in clientHello.cipher_suites: break else: for result in self._sendError(\ AlertDescription.handshake_failure): yield result #If resumption was requested... if clientHello.session_id and (sharedKeyDB or sessionCache): session = None #Check in the sharedKeys container if sharedKeyDB and len(clientHello.session_id)==16: try: #Trim off zero padding, if any for x in range(16): if clientHello.session_id[x]==0: break self.allegedSharedKeyUsername = bytesToString(\ clientHello.session_id[:x]) session = sharedKeyDB[self.allegedSharedKeyUsername] if not session.sharedKey: raise AssertionError() #use privileged ciphersuite session.cipherSuite = cipherSuite except KeyError: pass #Then check in the session cache if sessionCache and not session: try: session = sessionCache[bytesToString(\ clientHello.session_id)] if session.sharedKey: raise AssertionError() if not session.resumable: raise AssertionError() #Check for consistency with ClientHello if session.cipherSuite not in cipherSuites: for result in self._sendError(\ AlertDescription.handshake_failure): yield result if session.cipherSuite not in clientHello.cipher_suites: for result in self._sendError(\ AlertDescription.handshake_failure): yield result if clientHello.srp_username: if clientHello.srp_username != session.srpUsername: for result in self._sendError(\ AlertDescription.handshake_failure): yield result except KeyError: pass #If a session is found.. if session: #Set the session self.session = session #Send ServerHello serverHello = ServerHello() serverHello.create(self.version, serverRandom, session.sessionID, session.cipherSuite, certificateType) for result in self._sendMsg(serverHello): yield result #From here on, the client's messages must have the right version self._versionCheck = True #Calculate pending connection states self._calcPendingStates(clientRandom, serverRandom, settings.cipherImplementations) #Exchange ChangeCipherSpec and Finished messages for result in self._sendFinished(): yield result for result in self._getFinished(): yield result #Mark the connection as open self._handshakeDone(resumed=True) return #If not a resumption... #TRICKY: we might have chosen an RSA suite that was only deemed #acceptable because of the shared-key resumption. If the shared- #key resumption failed, because the identifier wasn't recognized, #we might fall through to here, where we have an RSA suite #chosen, but no certificate. if cipherSuite in CipherSuite.rsaSuites and not certChain: for result in self._sendError(\ AlertDescription.handshake_failure): yield result #If an RSA suite is chosen, check for certificate type intersection #(We do this check down here because if the mismatch occurs but the # client is using a shared-key session, it's okay) if cipherSuite in CipherSuite.rsaSuites + \ CipherSuite.srpRsaSuites: if certificateType not in clientHello.certificate_types: for result in self._sendError(\ AlertDescription.handshake_failure, "the client doesn't support my certificate type"): yield result #Move certChain -> serverCertChain, now that we're using it serverCertChain = certChain #Create sessionID if sessionCache: sessionID = getRandomBytes(32) else: sessionID = createByteArraySequence([]) #If we've selected an SRP suite, exchange keys and calculate #premaster secret: if cipherSuite in CipherSuite.srpSuites + CipherSuite.srpRsaSuites: #If there's no SRP username... if not clientHello.srp_username: #Ask the client to re-send ClientHello with one for result in self._sendMsg(Alert().create(\ AlertDescription.missing_srp_username, AlertLevel.warning)): yield result #Get ClientHello for result in self._getMsg(ContentType.handshake, HandshakeType.client_hello): if result in (0,1): yield result else: break clientHello = result #Check ClientHello #If client's version is too low, reject it (COPIED CODE; BAD!) if clientHello.client_version < settings.minVersion: self.version = settings.minVersion for result in self._sendError(\ AlertDescription.protocol_version, "Too old version: %s" % str(clientHello.client_version)): yield result #If client's version is too high, propose my highest version elif clientHello.client_version > settings.maxVersion: self.version = settings.maxVersion else: #Set the version to the client's version self.version = clientHello.client_version #Recalculate the privileged cipher suite, making sure to #pick an SRP suite cipherSuites = [c for c in cipherSuites if c in \ CipherSuite.srpSuites + \ CipherSuite.srpRsaSuites] for cipherSuite in cipherSuites: if cipherSuite in clientHello.cipher_suites: break else: for result in self._sendError(\ AlertDescription.handshake_failure): yield result #Get the client nonce; create server nonce clientRandom = clientHello.random serverRandom = getRandomBytes(32) #The username better be there, this time if not clientHello.srp_username: for result in self._sendError(\ AlertDescription.illegal_parameter, "Client resent a hello, but without the SRP"\ " username"): yield result #Get username self.allegedSrpUsername = clientHello.srp_username #Get parameters from username try: entry = verifierDB[self.allegedSrpUsername] except KeyError: for result in self._sendError(\ AlertDescription.unknown_srp_username): yield result (N, g, s, v) = entry #Calculate server's ephemeral DH values (b, B) b = bytesToNumber(getRandomBytes(32)) k = makeK(N, g) B = (powMod(g, b, N) + (k*v)) % N #Create ServerKeyExchange, signing it if necessary serverKeyExchange = ServerKeyExchange(cipherSuite) serverKeyExchange.createSRP(N, g, stringToBytes(s), B) if cipherSuite in CipherSuite.srpRsaSuites: hashBytes = serverKeyExchange.hash(clientRandom, serverRandom) serverKeyExchange.signature = privateKey.sign(hashBytes) #Send ServerHello[, Certificate], ServerKeyExchange, #ServerHelloDone msgs = [] serverHello = ServerHello() serverHello.create(self.version, serverRandom, sessionID, cipherSuite, certificateType) msgs.append(serverHello) if cipherSuite in CipherSuite.srpRsaSuites: certificateMsg = Certificate(certificateType) certificateMsg.create(serverCertChain) msgs.append(certificateMsg) msgs.append(serverKeyExchange) msgs.append(ServerHelloDone()) for result in self._sendMsgs(msgs): yield result #From here on, the client's messages must have the right version self._versionCheck = True #Get and check ClientKeyExchange for result in self._getMsg(ContentType.handshake, HandshakeType.client_key_exchange, cipherSuite): if result in (0,1): yield result else: break clientKeyExchange = result A = clientKeyExchange.srp_A if A % N == 0: postFinishedError = (AlertDescription.illegal_parameter, "Suspicious A value") #Calculate u u = makeU(N, A, B) #Calculate premaster secret S = powMod((A * powMod(v,u,N)) % N, b, N) premasterSecret = numberToBytes(S) #If we've selected an RSA suite, exchange keys and calculate #premaster secret: elif cipherSuite in CipherSuite.rsaSuites: #Send ServerHello, Certificate[, CertificateRequest], #ServerHelloDone msgs = [] msgs.append(ServerHello().create(self.version, serverRandom, sessionID, cipherSuite, certificateType)) msgs.append(Certificate(certificateType).create(serverCertChain)) if reqCert: msgs.append(CertificateRequest()) msgs.append(ServerHelloDone()) for result in self._sendMsgs(msgs): yield result #From here on, the client's messages must have the right version self._versionCheck = True #Get [Certificate,] (if was requested) if reqCert: if self.version == (3,0): for result in self._getMsg((ContentType.handshake, ContentType.alert), HandshakeType.certificate, certificateType): if result in (0,1): yield result else: break msg = result if isinstance(msg, Alert): #If it's not a no_certificate alert, re-raise alert = msg if alert.description != \ AlertDescription.no_certificate: self._shutdown(False) raise TLSRemoteAlert(alert) elif isinstance(msg, Certificate): clientCertificate = msg if clientCertificate.certChain and \ clientCertificate.certChain.getNumCerts()!=0: clientCertChain = clientCertificate.certChain else: raise AssertionError() elif self.version in ((3,1), (3,2)): for result in self._getMsg(ContentType.handshake, HandshakeType.certificate, certificateType): if result in (0,1): yield result else: break clientCertificate = result if clientCertificate.certChain and \ clientCertificate.certChain.getNumCerts()!=0: clientCertChain = clientCertificate.certChain else: raise AssertionError() #Get ClientKeyExchange for result in self._getMsg(ContentType.handshake, HandshakeType.client_key_exchange, cipherSuite): if result in (0,1): yield result else: break clientKeyExchange = result #Decrypt ClientKeyExchange premasterSecret = privateKey.decrypt(\ clientKeyExchange.encryptedPreMasterSecret) randomPreMasterSecret = getRandomBytes(48) versionCheck = (premasterSecret[0], premasterSecret[1]) if not premasterSecret: premasterSecret = randomPreMasterSecret elif len(premasterSecret)!=48: premasterSecret = randomPreMasterSecret elif versionCheck != clientHello.client_version: if versionCheck != self.version: #Tolerate buggy IE clients premasterSecret = randomPreMasterSecret #Get and check CertificateVerify, if relevant if clientCertChain: if self.version == (3,0): #Create a temporary session object, just for the purpose #of checking the CertificateVerify session = Session() session._calcMasterSecret(self.version, premasterSecret, clientRandom, serverRandom) verifyBytes = self._calcSSLHandshakeHash(\ session.masterSecret, "") elif self.version in ((3,1), (3,2)): verifyBytes = stringToBytes(self._handshake_md5.digest() +\ self._handshake_sha.digest()) for result in self._getMsg(ContentType.handshake, HandshakeType.certificate_verify): if result in (0,1): yield result else: break certificateVerify = result publicKey = clientCertChain.getEndEntityPublicKey() if len(publicKey) < settings.minKeySize: postFinishedError = (AlertDescription.handshake_failure, "Client's public key too small: %d" % len(publicKey)) if len(publicKey) > settings.maxKeySize: postFinishedError = (AlertDescription.handshake_failure, "Client's public key too large: %d" % len(publicKey)) if not publicKey.verify(certificateVerify.signature, verifyBytes): postFinishedError = (AlertDescription.decrypt_error, "Signature failed to verify") #Create the session object self.session = Session() self.session._calcMasterSecret(self.version, premasterSecret, clientRandom, serverRandom) self.session.sessionID = sessionID self.session.cipherSuite = cipherSuite self.session.srpUsername = self.allegedSrpUsername self.session.clientCertChain = clientCertChain self.session.serverCertChain = serverCertChain #Calculate pending connection states self._calcPendingStates(clientRandom, serverRandom, settings.cipherImplementations) #Exchange ChangeCipherSpec and Finished messages for result in self._getFinished(): yield result #If we were holding a post-finished error until receiving the client #finished message, send it now. We delay the call until this point #because calling sendError() throws an exception, and our caller might #shut down the socket upon receiving the exception. If he did, and the #client was still sending its ChangeCipherSpec or Finished messages, it #would cause a socket error on the client side. This is a lot of #consideration to show to misbehaving clients, but this would also #cause problems with fault-testing. if postFinishedError: for result in self._sendError(*postFinishedError): yield result for result in self._sendFinished(): yield result #Add the session object to the session cache if sessionCache and sessionID: sessionCache[bytesToString(sessionID)] = self.session #Mark the connection as open self.session._setResumable(True) self._handshakeDone(resumed=False) def _handshakeWrapperAsync(self, handshaker, checker): if not self.fault: try: for result in handshaker: yield result if checker: try: checker(self) except TLSAuthenticationError: alert = Alert().create(AlertDescription.close_notify, AlertLevel.fatal) for result in self._sendMsg(alert): yield result raise except: self._shutdown(False) raise else: try: for result in handshaker: yield result if checker: try: checker(self) except TLSAuthenticationError: alert = Alert().create(AlertDescription.close_notify, AlertLevel.fatal) for result in self._sendMsg(alert): yield result raise except socket.error, e: raise TLSFaultError("socket error!") except TLSAbruptCloseError, e: raise TLSFaultError("abrupt close error!") except TLSAlert, alert: if alert.description not in Fault.faultAlerts[self.fault]: raise TLSFaultError(str(alert)) else: pass except: self._shutdown(False) raise else: raise TLSFaultError("No error!") def _getKeyFromChain(self, certificate, settings): #Get and check cert chain from the Certificate message certChain = certificate.certChain if not certChain or certChain.getNumCerts() == 0: for result in self._sendError(AlertDescription.illegal_parameter, "Other party sent a Certificate message without "\ "certificates"): yield result #Get and check public key from the cert chain publicKey = certChain.getEndEntityPublicKey() if len(publicKey) < settings.minKeySize: for result in self._sendError(AlertDescription.handshake_failure, "Other party's public key too small: %d" % len(publicKey)): yield result if len(publicKey) > settings.maxKeySize: for result in self._sendError(AlertDescription.handshake_failure, "Other party's public key too large: %d" % len(publicKey)): yield result yield publicKey, certChain gdata-2.0.18/src/gdata/tlslite/SessionCache.py0000750036466300116100000000661612156622363021221 0ustar afshareng00000000000000"""Class for caching TLS sessions.""" import thread import time class SessionCache: """This class is used by the server to cache TLS sessions. Caching sessions allows the client to use TLS session resumption and avoid the expense of a full handshake. To use this class, simply pass a SessionCache instance into the server handshake function. This class is thread-safe. """ #References to these instances #are also held by the caller, who may change the 'resumable' #flag, so the SessionCache must return the same instances #it was passed in. def __init__(self, maxEntries=10000, maxAge=14400): """Create a new SessionCache. @type maxEntries: int @param maxEntries: The maximum size of the cache. When this limit is reached, the oldest sessions will be deleted as necessary to make room for new ones. The default is 10000. @type maxAge: int @param maxAge: The number of seconds before a session expires from the cache. The default is 14400 (i.e. 4 hours).""" self.lock = thread.allocate_lock() # Maps sessionIDs to sessions self.entriesDict = {} #Circular list of (sessionID, timestamp) pairs self.entriesList = [(None,None)] * maxEntries self.firstIndex = 0 self.lastIndex = 0 self.maxAge = maxAge def __getitem__(self, sessionID): self.lock.acquire() try: self._purge() #Delete old items, so we're assured of a new one session = self.entriesDict[sessionID] #When we add sessions they're resumable, but it's possible #for the session to be invalidated later on (if a fatal alert #is returned), so we have to check for resumability before #returning the session. if session.valid(): return session else: raise KeyError() finally: self.lock.release() def __setitem__(self, sessionID, session): self.lock.acquire() try: #Add the new element self.entriesDict[sessionID] = session self.entriesList[self.lastIndex] = (sessionID, time.time()) self.lastIndex = (self.lastIndex+1) % len(self.entriesList) #If the cache is full, we delete the oldest element to make an #empty space if self.lastIndex == self.firstIndex: del(self.entriesDict[self.entriesList[self.firstIndex][0]]) self.firstIndex = (self.firstIndex+1) % len(self.entriesList) finally: self.lock.release() #Delete expired items def _purge(self): currentTime = time.time() #Search through the circular list, deleting expired elements until #we reach a non-expired element. Since elements in list are #ordered in time, we can break once we reach the first non-expired #element index = self.firstIndex while index != self.lastIndex: if currentTime - self.entriesList[index][1] > self.maxAge: del(self.entriesDict[self.entriesList[index][0]]) index = (index+1) % len(self.entriesList) else: break self.firstIndex = index def _test(): import doctest, SessionCache return doctest.testmod(SessionCache) if __name__ == "__main__": _test() gdata-2.0.18/src/gdata/tlslite/Session.py0000750036466300116100000001117512156622363020271 0ustar afshareng00000000000000"""Class representing a TLS session.""" from utils.compat import * from mathtls import * from constants import * class Session: """ This class represents a TLS session. TLS distinguishes between connections and sessions. A new handshake creates both a connection and a session. Data is transmitted over the connection. The session contains a more permanent record of the handshake. The session can be inspected to determine handshake results. The session can also be used to create a new connection through "session resumption". If the client and server both support this, they can create a new connection based on an old session without the overhead of a full handshake. The session for a L{tlslite.TLSConnection.TLSConnection} can be retrieved from the connection's 'session' attribute. @type srpUsername: str @ivar srpUsername: The client's SRP username (or None). @type sharedKeyUsername: str @ivar sharedKeyUsername: The client's shared-key username (or None). @type clientCertChain: L{tlslite.X509CertChain.X509CertChain} or L{cryptoIDlib.CertChain.CertChain} @ivar clientCertChain: The client's certificate chain (or None). @type serverCertChain: L{tlslite.X509CertChain.X509CertChain} or L{cryptoIDlib.CertChain.CertChain} @ivar serverCertChain: The server's certificate chain (or None). """ def __init__(self): self.masterSecret = createByteArraySequence([]) self.sessionID = createByteArraySequence([]) self.cipherSuite = 0 self.srpUsername = None self.sharedKeyUsername = None self.clientCertChain = None self.serverCertChain = None self.resumable = False self.sharedKey = False def _clone(self): other = Session() other.masterSecret = self.masterSecret other.sessionID = self.sessionID other.cipherSuite = self.cipherSuite other.srpUsername = self.srpUsername other.sharedKeyUsername = self.sharedKeyUsername other.clientCertChain = self.clientCertChain other.serverCertChain = self.serverCertChain other.resumable = self.resumable other.sharedKey = self.sharedKey return other def _calcMasterSecret(self, version, premasterSecret, clientRandom, serverRandom): if version == (3,0): self.masterSecret = PRF_SSL(premasterSecret, concatArrays(clientRandom, serverRandom), 48) elif version in ((3,1), (3,2)): self.masterSecret = PRF(premasterSecret, "master secret", concatArrays(clientRandom, serverRandom), 48) else: raise AssertionError() def valid(self): """If this session can be used for session resumption. @rtype: bool @return: If this session can be used for session resumption. """ return self.resumable or self.sharedKey def _setResumable(self, boolean): #Only let it be set if this isn't a shared key if not self.sharedKey: #Only let it be set to True if the sessionID is non-null if (not boolean) or (boolean and self.sessionID): self.resumable = boolean def getCipherName(self): """Get the name of the cipher used with this connection. @rtype: str @return: The name of the cipher used with this connection. Either 'aes128', 'aes256', 'rc4', or '3des'. """ if self.cipherSuite in CipherSuite.aes128Suites: return "aes128" elif self.cipherSuite in CipherSuite.aes256Suites: return "aes256" elif self.cipherSuite in CipherSuite.rc4Suites: return "rc4" elif self.cipherSuite in CipherSuite.tripleDESSuites: return "3des" else: return None def _createSharedKey(self, sharedKeyUsername, sharedKey): if len(sharedKeyUsername)>16: raise ValueError() if len(sharedKey)>47: raise ValueError() self.sharedKeyUsername = sharedKeyUsername self.sessionID = createByteArrayZeros(16) for x in range(len(sharedKeyUsername)): self.sessionID[x] = ord(sharedKeyUsername[x]) premasterSecret = createByteArrayZeros(48) sharedKey = chr(len(sharedKey)) + sharedKey for x in range(48): premasterSecret[x] = ord(sharedKey[x % len(sharedKey)]) self.masterSecret = PRF(premasterSecret, "shared secret", createByteArraySequence([]), 48) self.sharedKey = True return self gdata-2.0.18/src/gdata/tlslite/utils/0000750036466300116100000000000012156625015017421 5ustar afshareng00000000000000gdata-2.0.18/src/gdata/tlslite/utils/jython_compat.py0000750036466300116100000001222612156622363022662 0ustar afshareng00000000000000"""Miscellaneous functions to mask Python/Jython differences.""" import os import sha if os.name != "java": BaseException = Exception from sets import Set import array import math def createByteArraySequence(seq): return array.array('B', seq) def createByteArrayZeros(howMany): return array.array('B', [0] * howMany) def concatArrays(a1, a2): return a1+a2 def bytesToString(bytes): return bytes.tostring() def stringToBytes(s): bytes = createByteArrayZeros(0) bytes.fromstring(s) return bytes def numBits(n): if n==0: return 0 return int(math.floor(math.log(n, 2))+1) class CertChainBase: pass class SelfTestBase: pass class ReportFuncBase: pass #Helper functions for working with sets (from Python 2.3) def iterSet(set): return iter(set) def getListFromSet(set): return list(set) #Factory function for getting a SHA1 object def getSHA1(s): return sha.sha(s) import sys import traceback def formatExceptionTrace(e): newStr = "".join(traceback.format_exception(sys.exc_type, sys.exc_value, sys.exc_traceback)) return newStr else: #Jython 2.1 is missing lots of python 2.3 stuff, #which we have to emulate here: import java import jarray BaseException = java.lang.Exception def createByteArraySequence(seq): if isinstance(seq, type("")): #If it's a string, convert seq = [ord(c) for c in seq] return jarray.array(seq, 'h') #use short instead of bytes, cause bytes are signed def createByteArrayZeros(howMany): return jarray.zeros(howMany, 'h') #use short instead of bytes, cause bytes are signed def concatArrays(a1, a2): l = list(a1)+list(a2) return createByteArraySequence(l) #WAY TOO SLOW - MUST BE REPLACED------------ def bytesToString(bytes): return "".join([chr(b) for b in bytes]) def stringToBytes(s): bytes = createByteArrayZeros(len(s)) for count, c in enumerate(s): bytes[count] = ord(c) return bytes #WAY TOO SLOW - MUST BE REPLACED------------ def numBits(n): if n==0: return 0 n= 1L * n; #convert to long, if it isn't already return n.__tojava__(java.math.BigInteger).bitLength() #This properly creates static methods for Jython class staticmethod: def __init__(self, anycallable): self.__call__ = anycallable #Properties are not supported for Jython class property: def __init__(self, anycallable): pass #True and False have to be specially defined False = 0 True = 1 class StopIteration(Exception): pass def enumerate(collection): return zip(range(len(collection)), collection) class Set: def __init__(self, seq=None): self.values = {} if seq: for e in seq: self.values[e] = None def add(self, e): self.values[e] = None def discard(self, e): if e in self.values.keys(): del(self.values[e]) def union(self, s): ret = Set() for e in self.values.keys(): ret.values[e] = None for e in s.values.keys(): ret.values[e] = None return ret def issubset(self, other): for e in self.values.keys(): if e not in other.values.keys(): return False return True def __nonzero__( self): return len(self.values.keys()) def __contains__(self, e): return e in self.values.keys() def iterSet(set): return set.values.keys() def getListFromSet(set): return set.values.keys() """ class JCE_SHA1: def __init__(self, s=None): self.md = java.security.MessageDigest.getInstance("SHA1") if s: self.update(s) def update(self, s): self.md.update(s) def copy(self): sha1 = JCE_SHA1() sha1.md = self.md.clone() return sha1 def digest(self): digest = self.md.digest() bytes = jarray.zeros(20, 'h') for count in xrange(20): x = digest[count] if x < 0: x += 256 bytes[count] = x return bytes """ #Factory function for getting a SHA1 object #The JCE_SHA1 class is way too slow... #the sha.sha object we use instead is broken in the jython 2.1 #release, and needs to be patched def getSHA1(s): #return JCE_SHA1(s) return sha.sha(s) #Adjust the string to an array of bytes def stringToJavaByteArray(s): bytes = jarray.zeros(len(s), 'b') for count, c in enumerate(s): x = ord(c) if x >= 128: x -= 256 bytes[count] = x return bytes import sys import traceback def formatExceptionTrace(e): newStr = "".join(traceback.format_exception(sys.exc_type, sys.exc_value, sys.exc_traceback)) return newStr gdata-2.0.18/src/gdata/tlslite/utils/rijndael.py0000750036466300116100000002611512156622363021576 0ustar afshareng00000000000000""" A pure python (slow) implementation of rijndael with a decent interface To include - from rijndael import rijndael To do a key setup - r = rijndael(key, block_size = 16) key must be a string of length 16, 24, or 32 blocksize must be 16, 24, or 32. Default is 16 To use - ciphertext = r.encrypt(plaintext) plaintext = r.decrypt(ciphertext) If any strings are of the wrong length a ValueError is thrown """ # ported from the Java reference code by Bram Cohen, bram@gawth.com, April 2001 # this code is public domain, unless someone makes # an intellectual property claim against the reference # code, in which case it can be made public domain by # deleting all the comments and renaming all the variables import copy import string #----------------------- #TREV - ADDED BECAUSE THERE'S WARNINGS ABOUT INT OVERFLOW BEHAVIOR CHANGING IN #2.4..... import os if os.name != "java": import exceptions if hasattr(exceptions, "FutureWarning"): import warnings warnings.filterwarnings("ignore", category=FutureWarning, append=1) #----------------------- shifts = [[[0, 0], [1, 3], [2, 2], [3, 1]], [[0, 0], [1, 5], [2, 4], [3, 3]], [[0, 0], [1, 7], [3, 5], [4, 4]]] # [keysize][block_size] num_rounds = {16: {16: 10, 24: 12, 32: 14}, 24: {16: 12, 24: 12, 32: 14}, 32: {16: 14, 24: 14, 32: 14}} A = [[1, 1, 1, 1, 1, 0, 0, 0], [0, 1, 1, 1, 1, 1, 0, 0], [0, 0, 1, 1, 1, 1, 1, 0], [0, 0, 0, 1, 1, 1, 1, 1], [1, 0, 0, 0, 1, 1, 1, 1], [1, 1, 0, 0, 0, 1, 1, 1], [1, 1, 1, 0, 0, 0, 1, 1], [1, 1, 1, 1, 0, 0, 0, 1]] # produce log and alog tables, needed for multiplying in the # field GF(2^m) (generator = 3) alog = [1] for i in xrange(255): j = (alog[-1] << 1) ^ alog[-1] if j & 0x100 != 0: j ^= 0x11B alog.append(j) log = [0] * 256 for i in xrange(1, 255): log[alog[i]] = i # multiply two elements of GF(2^m) def mul(a, b): if a == 0 or b == 0: return 0 return alog[(log[a & 0xFF] + log[b & 0xFF]) % 255] # substitution box based on F^{-1}(x) box = [[0] * 8 for i in xrange(256)] box[1][7] = 1 for i in xrange(2, 256): j = alog[255 - log[i]] for t in xrange(8): box[i][t] = (j >> (7 - t)) & 0x01 B = [0, 1, 1, 0, 0, 0, 1, 1] # affine transform: box[i] <- B + A*box[i] cox = [[0] * 8 for i in xrange(256)] for i in xrange(256): for t in xrange(8): cox[i][t] = B[t] for j in xrange(8): cox[i][t] ^= A[t][j] * box[i][j] # S-boxes and inverse S-boxes S = [0] * 256 Si = [0] * 256 for i in xrange(256): S[i] = cox[i][0] << 7 for t in xrange(1, 8): S[i] ^= cox[i][t] << (7-t) Si[S[i] & 0xFF] = i # T-boxes G = [[2, 1, 1, 3], [3, 2, 1, 1], [1, 3, 2, 1], [1, 1, 3, 2]] AA = [[0] * 8 for i in xrange(4)] for i in xrange(4): for j in xrange(4): AA[i][j] = G[i][j] AA[i][i+4] = 1 for i in xrange(4): pivot = AA[i][i] if pivot == 0: t = i + 1 while AA[t][i] == 0 and t < 4: t += 1 assert t != 4, 'G matrix must be invertible' for j in xrange(8): AA[i][j], AA[t][j] = AA[t][j], AA[i][j] pivot = AA[i][i] for j in xrange(8): if AA[i][j] != 0: AA[i][j] = alog[(255 + log[AA[i][j] & 0xFF] - log[pivot & 0xFF]) % 255] for t in xrange(4): if i != t: for j in xrange(i+1, 8): AA[t][j] ^= mul(AA[i][j], AA[t][i]) AA[t][i] = 0 iG = [[0] * 4 for i in xrange(4)] for i in xrange(4): for j in xrange(4): iG[i][j] = AA[i][j + 4] def mul4(a, bs): if a == 0: return 0 r = 0 for b in bs: r <<= 8 if b != 0: r = r | mul(a, b) return r T1 = [] T2 = [] T3 = [] T4 = [] T5 = [] T6 = [] T7 = [] T8 = [] U1 = [] U2 = [] U3 = [] U4 = [] for t in xrange(256): s = S[t] T1.append(mul4(s, G[0])) T2.append(mul4(s, G[1])) T3.append(mul4(s, G[2])) T4.append(mul4(s, G[3])) s = Si[t] T5.append(mul4(s, iG[0])) T6.append(mul4(s, iG[1])) T7.append(mul4(s, iG[2])) T8.append(mul4(s, iG[3])) U1.append(mul4(t, iG[0])) U2.append(mul4(t, iG[1])) U3.append(mul4(t, iG[2])) U4.append(mul4(t, iG[3])) # round constants rcon = [1] r = 1 for t in xrange(1, 30): r = mul(2, r) rcon.append(r) del A del AA del pivot del B del G del box del log del alog del i del j del r del s del t del mul del mul4 del cox del iG class rijndael: def __init__(self, key, block_size = 16): if block_size != 16 and block_size != 24 and block_size != 32: raise ValueError('Invalid block size: ' + str(block_size)) if len(key) != 16 and len(key) != 24 and len(key) != 32: raise ValueError('Invalid key size: ' + str(len(key))) self.block_size = block_size ROUNDS = num_rounds[len(key)][block_size] BC = block_size / 4 # encryption round keys Ke = [[0] * BC for i in xrange(ROUNDS + 1)] # decryption round keys Kd = [[0] * BC for i in xrange(ROUNDS + 1)] ROUND_KEY_COUNT = (ROUNDS + 1) * BC KC = len(key) / 4 # copy user material bytes into temporary ints tk = [] for i in xrange(0, KC): tk.append((ord(key[i * 4]) << 24) | (ord(key[i * 4 + 1]) << 16) | (ord(key[i * 4 + 2]) << 8) | ord(key[i * 4 + 3])) # copy values into round key arrays t = 0 j = 0 while j < KC and t < ROUND_KEY_COUNT: Ke[t / BC][t % BC] = tk[j] Kd[ROUNDS - (t / BC)][t % BC] = tk[j] j += 1 t += 1 tt = 0 rconpointer = 0 while t < ROUND_KEY_COUNT: # extrapolate using phi (the round key evolution function) tt = tk[KC - 1] tk[0] ^= (S[(tt >> 16) & 0xFF] & 0xFF) << 24 ^ \ (S[(tt >> 8) & 0xFF] & 0xFF) << 16 ^ \ (S[ tt & 0xFF] & 0xFF) << 8 ^ \ (S[(tt >> 24) & 0xFF] & 0xFF) ^ \ (rcon[rconpointer] & 0xFF) << 24 rconpointer += 1 if KC != 8: for i in xrange(1, KC): tk[i] ^= tk[i-1] else: for i in xrange(1, KC / 2): tk[i] ^= tk[i-1] tt = tk[KC / 2 - 1] tk[KC / 2] ^= (S[ tt & 0xFF] & 0xFF) ^ \ (S[(tt >> 8) & 0xFF] & 0xFF) << 8 ^ \ (S[(tt >> 16) & 0xFF] & 0xFF) << 16 ^ \ (S[(tt >> 24) & 0xFF] & 0xFF) << 24 for i in xrange(KC / 2 + 1, KC): tk[i] ^= tk[i-1] # copy values into round key arrays j = 0 while j < KC and t < ROUND_KEY_COUNT: Ke[t / BC][t % BC] = tk[j] Kd[ROUNDS - (t / BC)][t % BC] = tk[j] j += 1 t += 1 # inverse MixColumn where needed for r in xrange(1, ROUNDS): for j in xrange(BC): tt = Kd[r][j] Kd[r][j] = U1[(tt >> 24) & 0xFF] ^ \ U2[(tt >> 16) & 0xFF] ^ \ U3[(tt >> 8) & 0xFF] ^ \ U4[ tt & 0xFF] self.Ke = Ke self.Kd = Kd def encrypt(self, plaintext): if len(plaintext) != self.block_size: raise ValueError('wrong block length, expected ' + str(self.block_size) + ' got ' + str(len(plaintext))) Ke = self.Ke BC = self.block_size / 4 ROUNDS = len(Ke) - 1 if BC == 4: SC = 0 elif BC == 6: SC = 1 else: SC = 2 s1 = shifts[SC][1][0] s2 = shifts[SC][2][0] s3 = shifts[SC][3][0] a = [0] * BC # temporary work array t = [] # plaintext to ints + key for i in xrange(BC): t.append((ord(plaintext[i * 4 ]) << 24 | ord(plaintext[i * 4 + 1]) << 16 | ord(plaintext[i * 4 + 2]) << 8 | ord(plaintext[i * 4 + 3]) ) ^ Ke[0][i]) # apply round transforms for r in xrange(1, ROUNDS): for i in xrange(BC): a[i] = (T1[(t[ i ] >> 24) & 0xFF] ^ T2[(t[(i + s1) % BC] >> 16) & 0xFF] ^ T3[(t[(i + s2) % BC] >> 8) & 0xFF] ^ T4[ t[(i + s3) % BC] & 0xFF] ) ^ Ke[r][i] t = copy.copy(a) # last round is special result = [] for i in xrange(BC): tt = Ke[ROUNDS][i] result.append((S[(t[ i ] >> 24) & 0xFF] ^ (tt >> 24)) & 0xFF) result.append((S[(t[(i + s1) % BC] >> 16) & 0xFF] ^ (tt >> 16)) & 0xFF) result.append((S[(t[(i + s2) % BC] >> 8) & 0xFF] ^ (tt >> 8)) & 0xFF) result.append((S[ t[(i + s3) % BC] & 0xFF] ^ tt ) & 0xFF) return string.join(map(chr, result), '') def decrypt(self, ciphertext): if len(ciphertext) != self.block_size: raise ValueError('wrong block length, expected ' + str(self.block_size) + ' got ' + str(len(plaintext))) Kd = self.Kd BC = self.block_size / 4 ROUNDS = len(Kd) - 1 if BC == 4: SC = 0 elif BC == 6: SC = 1 else: SC = 2 s1 = shifts[SC][1][1] s2 = shifts[SC][2][1] s3 = shifts[SC][3][1] a = [0] * BC # temporary work array t = [0] * BC # ciphertext to ints + key for i in xrange(BC): t[i] = (ord(ciphertext[i * 4 ]) << 24 | ord(ciphertext[i * 4 + 1]) << 16 | ord(ciphertext[i * 4 + 2]) << 8 | ord(ciphertext[i * 4 + 3]) ) ^ Kd[0][i] # apply round transforms for r in xrange(1, ROUNDS): for i in xrange(BC): a[i] = (T5[(t[ i ] >> 24) & 0xFF] ^ T6[(t[(i + s1) % BC] >> 16) & 0xFF] ^ T7[(t[(i + s2) % BC] >> 8) & 0xFF] ^ T8[ t[(i + s3) % BC] & 0xFF] ) ^ Kd[r][i] t = copy.copy(a) # last round is special result = [] for i in xrange(BC): tt = Kd[ROUNDS][i] result.append((Si[(t[ i ] >> 24) & 0xFF] ^ (tt >> 24)) & 0xFF) result.append((Si[(t[(i + s1) % BC] >> 16) & 0xFF] ^ (tt >> 16)) & 0xFF) result.append((Si[(t[(i + s2) % BC] >> 8) & 0xFF] ^ (tt >> 8)) & 0xFF) result.append((Si[ t[(i + s3) % BC] & 0xFF] ^ tt ) & 0xFF) return string.join(map(chr, result), '') def encrypt(key, block): return rijndael(key, len(block)).encrypt(block) def decrypt(key, block): return rijndael(key, len(block)).decrypt(block) def test(): def t(kl, bl): b = 'b' * bl r = rijndael('a' * kl, bl) assert r.decrypt(r.encrypt(b)) == b t(16, 16) t(16, 24) t(16, 32) t(24, 16) t(24, 24) t(24, 32) t(32, 16) t(32, 24) t(32, 32) gdata-2.0.18/src/gdata/tlslite/utils/cryptomath.py0000750036466300116100000002660612156622363022205 0ustar afshareng00000000000000"""cryptomath module This module has basic math/crypto code.""" import os import sys import math import base64 import binascii if sys.version_info[:2] <= (2, 4): from sha import sha as sha1 else: from hashlib import sha1 from compat import * # ************************************************************************** # Load Optional Modules # ************************************************************************** # Try to load M2Crypto/OpenSSL try: from M2Crypto import m2 m2cryptoLoaded = True except ImportError: m2cryptoLoaded = False # Try to load cryptlib try: import cryptlib_py try: cryptlib_py.cryptInit() except cryptlib_py.CryptException, e: #If tlslite and cryptoIDlib are both present, #they might each try to re-initialize this, #so we're tolerant of that. if e[0] != cryptlib_py.CRYPT_ERROR_INITED: raise cryptlibpyLoaded = True except ImportError: cryptlibpyLoaded = False #Try to load GMPY try: import gmpy gmpyLoaded = True except ImportError: gmpyLoaded = False #Try to load pycrypto try: import Crypto.Cipher.AES pycryptoLoaded = True except ImportError: pycryptoLoaded = False # ************************************************************************** # PRNG Functions # ************************************************************************** # Get os.urandom PRNG try: os.urandom(1) def getRandomBytes(howMany): return stringToBytes(os.urandom(howMany)) prngName = "os.urandom" except: # Else get cryptlib PRNG if cryptlibpyLoaded: def getRandomBytes(howMany): randomKey = cryptlib_py.cryptCreateContext(cryptlib_py.CRYPT_UNUSED, cryptlib_py.CRYPT_ALGO_AES) cryptlib_py.cryptSetAttribute(randomKey, cryptlib_py.CRYPT_CTXINFO_MODE, cryptlib_py.CRYPT_MODE_OFB) cryptlib_py.cryptGenerateKey(randomKey) bytes = createByteArrayZeros(howMany) cryptlib_py.cryptEncrypt(randomKey, bytes) return bytes prngName = "cryptlib" else: #Else get UNIX /dev/urandom PRNG try: devRandomFile = open("/dev/urandom", "rb") def getRandomBytes(howMany): return stringToBytes(devRandomFile.read(howMany)) prngName = "/dev/urandom" except IOError: #Else get Win32 CryptoAPI PRNG try: import win32prng def getRandomBytes(howMany): s = win32prng.getRandomBytes(howMany) if len(s) != howMany: raise AssertionError() return stringToBytes(s) prngName ="CryptoAPI" except ImportError: #Else no PRNG :-( def getRandomBytes(howMany): raise NotImplementedError("No Random Number Generator "\ "available.") prngName = "None" # ************************************************************************** # Converter Functions # ************************************************************************** def bytesToNumber(bytes): total = 0L multiplier = 1L for count in range(len(bytes)-1, -1, -1): byte = bytes[count] total += multiplier * byte multiplier *= 256 return total def numberToBytes(n): howManyBytes = numBytes(n) bytes = createByteArrayZeros(howManyBytes) for count in range(howManyBytes-1, -1, -1): bytes[count] = int(n % 256) n >>= 8 return bytes def bytesToBase64(bytes): s = bytesToString(bytes) return stringToBase64(s) def base64ToBytes(s): s = base64ToString(s) return stringToBytes(s) def numberToBase64(n): bytes = numberToBytes(n) return bytesToBase64(bytes) def base64ToNumber(s): bytes = base64ToBytes(s) return bytesToNumber(bytes) def stringToNumber(s): bytes = stringToBytes(s) return bytesToNumber(bytes) def numberToString(s): bytes = numberToBytes(s) return bytesToString(bytes) def base64ToString(s): try: return base64.decodestring(s) except binascii.Error, e: raise SyntaxError(e) except binascii.Incomplete, e: raise SyntaxError(e) def stringToBase64(s): return base64.encodestring(s).replace("\n", "") def mpiToNumber(mpi): #mpi is an openssl-format bignum string if (ord(mpi[4]) & 0x80) !=0: #Make sure this is a positive number raise AssertionError() bytes = stringToBytes(mpi[4:]) return bytesToNumber(bytes) def numberToMPI(n): bytes = numberToBytes(n) ext = 0 #If the high-order bit is going to be set, #add an extra byte of zeros if (numBits(n) & 0x7)==0: ext = 1 length = numBytes(n) + ext bytes = concatArrays(createByteArrayZeros(4+ext), bytes) bytes[0] = (length >> 24) & 0xFF bytes[1] = (length >> 16) & 0xFF bytes[2] = (length >> 8) & 0xFF bytes[3] = length & 0xFF return bytesToString(bytes) # ************************************************************************** # Misc. Utility Functions # ************************************************************************** def numBytes(n): if n==0: return 0 bits = numBits(n) return int(math.ceil(bits / 8.0)) def hashAndBase64(s): return stringToBase64(sha1(s).digest()) def getBase64Nonce(numChars=22): #defaults to an 132 bit nonce bytes = getRandomBytes(numChars) bytesStr = "".join([chr(b) for b in bytes]) return stringToBase64(bytesStr)[:numChars] # ************************************************************************** # Big Number Math # ************************************************************************** def getRandomNumber(low, high): if low >= high: raise AssertionError() howManyBits = numBits(high) howManyBytes = numBytes(high) lastBits = howManyBits % 8 while 1: bytes = getRandomBytes(howManyBytes) if lastBits: bytes[0] = bytes[0] % (1 << lastBits) n = bytesToNumber(bytes) if n >= low and n < high: return n def gcd(a,b): a, b = max(a,b), min(a,b) while b: a, b = b, a % b return a def lcm(a, b): #This will break when python division changes, but we can't use // cause #of Jython return (a * b) / gcd(a, b) #Returns inverse of a mod b, zero if none #Uses Extended Euclidean Algorithm def invMod(a, b): c, d = a, b uc, ud = 1, 0 while c != 0: #This will break when python division changes, but we can't use // #cause of Jython q = d / c c, d = d-(q*c), c uc, ud = ud - (q * uc), uc if d == 1: return ud % b return 0 if gmpyLoaded: def powMod(base, power, modulus): base = gmpy.mpz(base) power = gmpy.mpz(power) modulus = gmpy.mpz(modulus) result = pow(base, power, modulus) return long(result) else: #Copied from Bryan G. Olson's post to comp.lang.python #Does left-to-right instead of pow()'s right-to-left, #thus about 30% faster than the python built-in with small bases def powMod(base, power, modulus): nBitScan = 5 """ Return base**power mod modulus, using multi bit scanning with nBitScan bits at a time.""" #TREV - Added support for negative exponents negativeResult = False if (power < 0): power *= -1 negativeResult = True exp2 = 2**nBitScan mask = exp2 - 1 # Break power into a list of digits of nBitScan bits. # The list is recursive so easy to read in reverse direction. nibbles = None while power: nibbles = int(power & mask), nibbles power = power >> nBitScan # Make a table of powers of base up to 2**nBitScan - 1 lowPowers = [1] for i in xrange(1, exp2): lowPowers.append((lowPowers[i-1] * base) % modulus) # To exponentiate by the first nibble, look it up in the table nib, nibbles = nibbles prod = lowPowers[nib] # For the rest, square nBitScan times, then multiply by # base^nibble while nibbles: nib, nibbles = nibbles for i in xrange(nBitScan): prod = (prod * prod) % modulus if nib: prod = (prod * lowPowers[nib]) % modulus #TREV - Added support for negative exponents if negativeResult: prodInv = invMod(prod, modulus) #Check to make sure the inverse is correct if (prod * prodInv) % modulus != 1: raise AssertionError() return prodInv return prod #Pre-calculate a sieve of the ~100 primes < 1000: def makeSieve(n): sieve = range(n) for count in range(2, int(math.sqrt(n))): if sieve[count] == 0: continue x = sieve[count] * 2 while x < len(sieve): sieve[x] = 0 x += sieve[count] sieve = [x for x in sieve[2:] if x] return sieve sieve = makeSieve(1000) def isPrime(n, iterations=5, display=False): #Trial division with sieve for x in sieve: if x >= n: return True if n % x == 0: return False #Passed trial division, proceed to Rabin-Miller #Rabin-Miller implemented per Ferguson & Schneier #Compute s, t for Rabin-Miller if display: print "*", s, t = n-1, 0 while s % 2 == 0: s, t = s/2, t+1 #Repeat Rabin-Miller x times a = 2 #Use 2 as a base for first iteration speedup, per HAC for count in range(iterations): v = powMod(a, s, n) if v==1: continue i = 0 while v != n-1: if i == t-1: return False else: v, i = powMod(v, 2, n), i+1 a = getRandomNumber(2, n) return True def getRandomPrime(bits, display=False): if bits < 10: raise AssertionError() #The 1.5 ensures the 2 MSBs are set #Thus, when used for p,q in RSA, n will have its MSB set # #Since 30 is lcm(2,3,5), we'll set our test numbers to #29 % 30 and keep them there low = (2L ** (bits-1)) * 3/2 high = 2L ** bits - 30 p = getRandomNumber(low, high) p += 29 - (p % 30) while 1: if display: print ".", p += 30 if p >= high: p = getRandomNumber(low, high) p += 29 - (p % 30) if isPrime(p, display=display): return p #Unused at the moment... def getRandomSafePrime(bits, display=False): if bits < 10: raise AssertionError() #The 1.5 ensures the 2 MSBs are set #Thus, when used for p,q in RSA, n will have its MSB set # #Since 30 is lcm(2,3,5), we'll set our test numbers to #29 % 30 and keep them there low = (2 ** (bits-2)) * 3/2 high = (2 ** (bits-1)) - 30 q = getRandomNumber(low, high) q += 29 - (q % 30) while 1: if display: print ".", q += 30 if (q >= high): q = getRandomNumber(low, high) q += 29 - (q % 30) #Ideas from Tom Wu's SRP code #Do trial division on p and q before Rabin-Miller if isPrime(q, 0, display=display): p = (2 * q) + 1 if isPrime(p, display=display): if isPrime(q, display=display): return p gdata-2.0.18/src/gdata/tlslite/utils/dateFuncs.py0000750036466300116100000000420512156622363021716 0ustar afshareng00000000000000 import os #Functions for manipulating datetime objects #CCYY-MM-DDThh:mm:ssZ def parseDateClass(s): year, month, day = s.split("-") day, tail = day[:2], day[2:] hour, minute, second = tail[1:].split(":") second = second[:2] year, month, day = int(year), int(month), int(day) hour, minute, second = int(hour), int(minute), int(second) return createDateClass(year, month, day, hour, minute, second) if os.name != "java": from datetime import datetime, timedelta #Helper functions for working with a date/time class def createDateClass(year, month, day, hour, minute, second): return datetime(year, month, day, hour, minute, second) def printDateClass(d): #Split off fractional seconds, append 'Z' return d.isoformat().split(".")[0]+"Z" def getNow(): return datetime.utcnow() def getHoursFromNow(hours): return datetime.utcnow() + timedelta(hours=hours) def getMinutesFromNow(minutes): return datetime.utcnow() + timedelta(minutes=minutes) def isDateClassExpired(d): return d < datetime.utcnow() def isDateClassBefore(d1, d2): return d1 < d2 else: #Jython 2.1 is missing lots of python 2.3 stuff, #which we have to emulate here: import java import jarray def createDateClass(year, month, day, hour, minute, second): c = java.util.Calendar.getInstance() c.setTimeZone(java.util.TimeZone.getTimeZone("UTC")) c.set(year, month-1, day, hour, minute, second) return c def printDateClass(d): return "%04d-%02d-%02dT%02d:%02d:%02dZ" % \ (d.get(d.YEAR), d.get(d.MONTH)+1, d.get(d.DATE), \ d.get(d.HOUR_OF_DAY), d.get(d.MINUTE), d.get(d.SECOND)) def getNow(): c = java.util.Calendar.getInstance() c.setTimeZone(java.util.TimeZone.getTimeZone("UTC")) c.get(c.HOUR) #force refresh? return c def getHoursFromNow(hours): d = getNow() d.add(d.HOUR, hours) return d def isDateClassExpired(d): n = getNow() return d.before(n) def isDateClassBefore(d1, d2): return d1.before(d2) gdata-2.0.18/src/gdata/tlslite/utils/Cryptlib_TripleDES.py0000750036466300116100000000260012156622363023442 0ustar afshareng00000000000000"""Cryptlib 3DES implementation.""" from cryptomath import * from TripleDES import * if cryptlibpyLoaded: def new(key, mode, IV): return Cryptlib_TripleDES(key, mode, IV) class Cryptlib_TripleDES(TripleDES): def __init__(self, key, mode, IV): TripleDES.__init__(self, key, mode, IV, "cryptlib") self.context = cryptlib_py.cryptCreateContext(cryptlib_py.CRYPT_UNUSED, cryptlib_py.CRYPT_ALGO_3DES) cryptlib_py.cryptSetAttribute(self.context, cryptlib_py.CRYPT_CTXINFO_MODE, cryptlib_py.CRYPT_MODE_CBC) cryptlib_py.cryptSetAttribute(self.context, cryptlib_py.CRYPT_CTXINFO_KEYSIZE, len(key)) cryptlib_py.cryptSetAttributeString(self.context, cryptlib_py.CRYPT_CTXINFO_KEY, key) cryptlib_py.cryptSetAttributeString(self.context, cryptlib_py.CRYPT_CTXINFO_IV, IV) def __del__(self): cryptlib_py.cryptDestroyContext(self.context) def encrypt(self, plaintext): TripleDES.encrypt(self, plaintext) bytes = stringToBytes(plaintext) cryptlib_py.cryptEncrypt(self.context, bytes) return bytesToString(bytes) def decrypt(self, ciphertext): TripleDES.decrypt(self, ciphertext) bytes = stringToBytes(ciphertext) cryptlib_py.cryptDecrypt(self.context, bytes) return bytesToString(bytes)gdata-2.0.18/src/gdata/tlslite/utils/win32prng.c0000750036466300116100000000225612156622363021431 0ustar afshareng00000000000000 #include "Python.h" #define _WIN32_WINNT 0x0400 /* Needed for CryptoAPI on some systems */ #include static PyObject* getRandomBytes(PyObject *self, PyObject *args) { int howMany; HCRYPTPROV hCryptProv; unsigned char* bytes = NULL; PyObject* returnVal = NULL; /* Read Arguments */ if (!PyArg_ParseTuple(args, "i", &howMany)) return(NULL); /* Get Context */ if(CryptAcquireContext( &hCryptProv, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT) == 0) return Py_BuildValue("s#", NULL, 0); /* Allocate bytes */ bytes = malloc(howMany); /* Get random data */ if(CryptGenRandom( hCryptProv, howMany, bytes) == 0) returnVal = Py_BuildValue("s#", NULL, 0); else returnVal = Py_BuildValue("s#", bytes, howMany); free(bytes); CryptReleaseContext(hCryptProv, 0); return returnVal; } /* List of functions exported by this module */ static struct PyMethodDef win32prng_functions[] = { {"getRandomBytes", (PyCFunction)getRandomBytes, METH_VARARGS}, {NULL, NULL} /* Sentinel */ }; /* Initialize this module. */ DL_EXPORT(void) initwin32prng(void) { Py_InitModule("win32prng", win32prng_functions); } gdata-2.0.18/src/gdata/tlslite/utils/ASN1Parser.py0000750036466300116100000000170012156622363021656 0ustar afshareng00000000000000"""Class for parsing ASN.1""" from compat import * from codec import * #Takes a byte array which has a DER TLV field at its head class ASN1Parser: def __init__(self, bytes): p = Parser(bytes) p.get(1) #skip Type #Get Length self.length = self._getASN1Length(p) #Get Value self.value = p.getFixBytes(self.length) #Assuming this is a sequence... def getChild(self, which): p = Parser(self.value) for x in range(which+1): markIndex = p.index p.get(1) #skip Type length = self._getASN1Length(p) p.getFixBytes(length) return ASN1Parser(p.bytes[markIndex : p.index]) #Decode the ASN.1 DER length field def _getASN1Length(self, p): firstLength = p.get(1) if firstLength<=127: return firstLength else: lengthLength = firstLength & 0x7F return p.get(lengthLength) gdata-2.0.18/src/gdata/tlslite/utils/xmltools.py0000750036466300116100000001632412156622363021670 0ustar afshareng00000000000000"""Helper functions for XML. This module has misc. helper functions for working with XML DOM nodes.""" from compat import * import os import re if os.name == "java": # Only for Jython from javax.xml.parsers import * import java builder = DocumentBuilderFactory.newInstance().newDocumentBuilder() def parseDocument(s): stream = java.io.ByteArrayInputStream(java.lang.String(s).getBytes()) return builder.parse(stream) else: from xml.dom import minidom from xml.sax import saxutils def parseDocument(s): return minidom.parseString(s) def parseAndStripWhitespace(s): try: element = parseDocument(s).documentElement except BaseException, e: raise SyntaxError(str(e)) stripWhitespace(element) return element #Goes through a DOM tree and removes whitespace besides child elements, #as long as this whitespace is correctly tab-ified def stripWhitespace(element, tab=0): element.normalize() lastSpacer = "\n" + ("\t"*tab) spacer = lastSpacer + "\t" #Zero children aren't allowed (i.e. ) #This makes writing output simpler, and matches Canonical XML if element.childNodes.length==0: #DON'T DO len(element.childNodes) - doesn't work in Jython raise SyntaxError("Empty XML elements not allowed") #If there's a single child, it must be text context if element.childNodes.length==1: if element.firstChild.nodeType == element.firstChild.TEXT_NODE: #If it's an empty element, remove if element.firstChild.data == lastSpacer: element.removeChild(element.firstChild) return #If not text content, give an error elif element.firstChild.nodeType == element.firstChild.ELEMENT_NODE: raise SyntaxError("Bad whitespace under '%s'" % element.tagName) else: raise SyntaxError("Unexpected node type in XML document") #Otherwise there's multiple child element child = element.firstChild while child: if child.nodeType == child.ELEMENT_NODE: stripWhitespace(child, tab+1) child = child.nextSibling elif child.nodeType == child.TEXT_NODE: if child == element.lastChild: if child.data != lastSpacer: raise SyntaxError("Bad whitespace under '%s'" % element.tagName) elif child.data != spacer: raise SyntaxError("Bad whitespace under '%s'" % element.tagName) next = child.nextSibling element.removeChild(child) child = next else: raise SyntaxError("Unexpected node type in XML document") def checkName(element, name): if element.nodeType != element.ELEMENT_NODE: raise SyntaxError("Missing element: '%s'" % name) if name == None: return if element.tagName != name: raise SyntaxError("Wrong element name: should be '%s', is '%s'" % (name, element.tagName)) def getChild(element, index, name=None): if element.nodeType != element.ELEMENT_NODE: raise SyntaxError("Wrong node type in getChild()") child = element.childNodes.item(index) if child == None: raise SyntaxError("Missing child: '%s'" % name) checkName(child, name) return child def getChildIter(element, index): class ChildIter: def __init__(self, element, index): self.element = element self.index = index def next(self): if self.index < len(self.element.childNodes): retVal = self.element.childNodes.item(self.index) self.index += 1 else: retVal = None return retVal def checkEnd(self): if self.index != len(self.element.childNodes): raise SyntaxError("Too many elements under: '%s'" % self.element.tagName) return ChildIter(element, index) def getChildOrNone(element, index): if element.nodeType != element.ELEMENT_NODE: raise SyntaxError("Wrong node type in getChild()") child = element.childNodes.item(index) return child def getLastChild(element, index, name=None): if element.nodeType != element.ELEMENT_NODE: raise SyntaxError("Wrong node type in getLastChild()") child = element.childNodes.item(index) if child == None: raise SyntaxError("Missing child: '%s'" % name) if child != element.lastChild: raise SyntaxError("Too many elements under: '%s'" % element.tagName) checkName(child, name) return child #Regular expressions for syntax-checking attribute and element content nsRegEx = "http://trevp.net/cryptoID\Z" cryptoIDRegEx = "([a-km-z3-9]{5}\.){3}[a-km-z3-9]{5}\Z" urlRegEx = "http(s)?://.{1,100}\Z" sha1Base64RegEx = "[A-Za-z0-9+/]{27}=\Z" base64RegEx = "[A-Za-z0-9+/]+={0,4}\Z" certsListRegEx = "(0)?(1)?(2)?(3)?(4)?(5)?(6)?(7)?(8)?(9)?\Z" keyRegEx = "[A-Z]\Z" keysListRegEx = "(A)?(B)?(C)?(D)?(E)?(F)?(G)?(H)?(I)?(J)?(K)?(L)?(M)?(N)?(O)?(P)?(Q)?(R)?(S)?(T)?(U)?(V)?(W)?(X)?(Y)?(Z)?\Z" dateTimeRegEx = "\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\dZ\Z" shortStringRegEx = ".{1,100}\Z" exprRegEx = "[a-zA-Z0-9 ,()]{1,200}\Z" notAfterDeltaRegEx = "0|([1-9][0-9]{0,8})\Z" #A number from 0 to (1 billion)-1 booleanRegEx = "(true)|(false)" def getReqAttribute(element, attrName, regEx=""): if element.nodeType != element.ELEMENT_NODE: raise SyntaxError("Wrong node type in getReqAttribute()") value = element.getAttribute(attrName) if not value: raise SyntaxError("Missing Attribute: " + attrName) if not re.match(regEx, value): raise SyntaxError("Bad Attribute Value for '%s': '%s' " % (attrName, value)) element.removeAttribute(attrName) return str(value) #de-unicode it; this is needed for bsddb, for example def getAttribute(element, attrName, regEx=""): if element.nodeType != element.ELEMENT_NODE: raise SyntaxError("Wrong node type in getAttribute()") value = element.getAttribute(attrName) if value: if not re.match(regEx, value): raise SyntaxError("Bad Attribute Value for '%s': '%s' " % (attrName, value)) element.removeAttribute(attrName) return str(value) #de-unicode it; this is needed for bsddb, for example def checkNoMoreAttributes(element): if element.nodeType != element.ELEMENT_NODE: raise SyntaxError("Wrong node type in checkNoMoreAttributes()") if element.attributes.length!=0: raise SyntaxError("Extra attributes on '%s'" % element.tagName) def getText(element, regEx=""): textNode = element.firstChild if textNode == None: raise SyntaxError("Empty element '%s'" % element.tagName) if textNode.nodeType != textNode.TEXT_NODE: raise SyntaxError("Non-text node: '%s'" % element.tagName) if not re.match(regEx, textNode.data): raise SyntaxError("Bad Text Value for '%s': '%s' " % (element.tagName, textNode.data)) return str(textNode.data) #de-unicode it; this is needed for bsddb, for example #Function for adding tabs to a string def indent(s, steps, ch="\t"): tabs = ch*steps if s[-1] != "\n": s = tabs + s.replace("\n", "\n"+tabs) else: s = tabs + s.replace("\n", "\n"+tabs) s = s[ : -len(tabs)] return s def escape(s): return saxutils.escape(s) gdata-2.0.18/src/gdata/tlslite/utils/__init__.py0000750036466300116100000000143412156622363021542 0ustar afshareng00000000000000"""Toolkit for crypto and other stuff.""" __all__ = ["AES", "ASN1Parser", "cipherfactory", "codec", "Cryptlib_AES", "Cryptlib_RC4", "Cryptlib_TripleDES", "cryptomath: cryptomath module", "dateFuncs", "hmac", "JCE_RSAKey", "compat", "keyfactory", "OpenSSL_AES", "OpenSSL_RC4", "OpenSSL_RSAKey", "OpenSSL_TripleDES", "PyCrypto_AES", "PyCrypto_RC4", "PyCrypto_RSAKey", "PyCrypto_TripleDES", "Python_AES", "Python_RC4", "Python_RSAKey", "RC4", "rijndael", "RSAKey", "TripleDES", "xmltools"] gdata-2.0.18/src/gdata/tlslite/utils/Cryptlib_AES.py0000750036466300116100000000252412156622363022264 0ustar afshareng00000000000000"""Cryptlib AES implementation.""" from cryptomath import * from AES import * if cryptlibpyLoaded: def new(key, mode, IV): return Cryptlib_AES(key, mode, IV) class Cryptlib_AES(AES): def __init__(self, key, mode, IV): AES.__init__(self, key, mode, IV, "cryptlib") self.context = cryptlib_py.cryptCreateContext(cryptlib_py.CRYPT_UNUSED, cryptlib_py.CRYPT_ALGO_AES) cryptlib_py.cryptSetAttribute(self.context, cryptlib_py.CRYPT_CTXINFO_MODE, cryptlib_py.CRYPT_MODE_CBC) cryptlib_py.cryptSetAttribute(self.context, cryptlib_py.CRYPT_CTXINFO_KEYSIZE, len(key)) cryptlib_py.cryptSetAttributeString(self.context, cryptlib_py.CRYPT_CTXINFO_KEY, key) cryptlib_py.cryptSetAttributeString(self.context, cryptlib_py.CRYPT_CTXINFO_IV, IV) def __del__(self): cryptlib_py.cryptDestroyContext(self.context) def encrypt(self, plaintext): AES.encrypt(self, plaintext) bytes = stringToBytes(plaintext) cryptlib_py.cryptEncrypt(self.context, bytes) return bytesToString(bytes) def decrypt(self, ciphertext): AES.decrypt(self, ciphertext) bytes = stringToBytes(ciphertext) cryptlib_py.cryptDecrypt(self.context, bytes) return bytesToString(bytes) gdata-2.0.18/src/gdata/tlslite/utils/Cryptlib_RC4.py0000750036466300116100000000164212156622363022244 0ustar afshareng00000000000000"""Cryptlib RC4 implementation.""" from cryptomath import * from RC4 import RC4 if cryptlibpyLoaded: def new(key): return Cryptlib_RC4(key) class Cryptlib_RC4(RC4): def __init__(self, key): RC4.__init__(self, key, "cryptlib") self.context = cryptlib_py.cryptCreateContext(cryptlib_py.CRYPT_UNUSED, cryptlib_py.CRYPT_ALGO_RC4) cryptlib_py.cryptSetAttribute(self.context, cryptlib_py.CRYPT_CTXINFO_KEYSIZE, len(key)) cryptlib_py.cryptSetAttributeString(self.context, cryptlib_py.CRYPT_CTXINFO_KEY, key) def __del__(self): cryptlib_py.cryptDestroyContext(self.context) def encrypt(self, plaintext): bytes = stringToBytes(plaintext) cryptlib_py.cryptEncrypt(self.context, bytes) return bytesToString(bytes) def decrypt(self, ciphertext): return self.encrypt(ciphertext)gdata-2.0.18/src/gdata/tlslite/utils/cipherfactory.py0000750036466300116100000000615112156622363022646 0ustar afshareng00000000000000"""Factory functions for symmetric cryptography.""" import os import Python_AES import Python_RC4 import cryptomath tripleDESPresent = False if cryptomath.m2cryptoLoaded: import OpenSSL_AES import OpenSSL_RC4 import OpenSSL_TripleDES tripleDESPresent = True if cryptomath.cryptlibpyLoaded: import Cryptlib_AES import Cryptlib_RC4 import Cryptlib_TripleDES tripleDESPresent = True if cryptomath.pycryptoLoaded: import PyCrypto_AES import PyCrypto_RC4 import PyCrypto_TripleDES tripleDESPresent = True # ************************************************************************** # Factory Functions for AES # ************************************************************************** def createAES(key, IV, implList=None): """Create a new AES object. @type key: str @param key: A 16, 24, or 32 byte string. @type IV: str @param IV: A 16 byte string @rtype: L{tlslite.utils.AES} @return: An AES object. """ if implList == None: implList = ["cryptlib", "openssl", "pycrypto", "python"] for impl in implList: if impl == "cryptlib" and cryptomath.cryptlibpyLoaded: return Cryptlib_AES.new(key, 2, IV) elif impl == "openssl" and cryptomath.m2cryptoLoaded: return OpenSSL_AES.new(key, 2, IV) elif impl == "pycrypto" and cryptomath.pycryptoLoaded: return PyCrypto_AES.new(key, 2, IV) elif impl == "python": return Python_AES.new(key, 2, IV) raise NotImplementedError() def createRC4(key, IV, implList=None): """Create a new RC4 object. @type key: str @param key: A 16 to 32 byte string. @type IV: object @param IV: Ignored, whatever it is. @rtype: L{tlslite.utils.RC4} @return: An RC4 object. """ if implList == None: implList = ["cryptlib", "openssl", "pycrypto", "python"] if len(IV) != 0: raise AssertionError() for impl in implList: if impl == "cryptlib" and cryptomath.cryptlibpyLoaded: return Cryptlib_RC4.new(key) elif impl == "openssl" and cryptomath.m2cryptoLoaded: return OpenSSL_RC4.new(key) elif impl == "pycrypto" and cryptomath.pycryptoLoaded: return PyCrypto_RC4.new(key) elif impl == "python": return Python_RC4.new(key) raise NotImplementedError() #Create a new TripleDES instance def createTripleDES(key, IV, implList=None): """Create a new 3DES object. @type key: str @param key: A 24 byte string. @type IV: str @param IV: An 8 byte string @rtype: L{tlslite.utils.TripleDES} @return: A 3DES object. """ if implList == None: implList = ["cryptlib", "openssl", "pycrypto"] for impl in implList: if impl == "cryptlib" and cryptomath.cryptlibpyLoaded: return Cryptlib_TripleDES.new(key, 2, IV) elif impl == "openssl" and cryptomath.m2cryptoLoaded: return OpenSSL_TripleDES.new(key, 2, IV) elif impl == "pycrypto" and cryptomath.pycryptoLoaded: return PyCrypto_TripleDES.new(key, 2, IV) raise NotImplementedError()gdata-2.0.18/src/gdata/tlslite/utils/PyCrypto_RC4.py0000750036466300116100000000102112156622363022234 0ustar afshareng00000000000000"""PyCrypto RC4 implementation.""" from cryptomath import * from RC4 import * if pycryptoLoaded: import Crypto.Cipher.ARC4 def new(key): return PyCrypto_RC4(key) class PyCrypto_RC4(RC4): def __init__(self, key): RC4.__init__(self, key, "pycrypto") self.context = Crypto.Cipher.ARC4.new(key) def encrypt(self, plaintext): return self.context.encrypt(plaintext) def decrypt(self, ciphertext): return self.context.decrypt(ciphertext)gdata-2.0.18/src/gdata/tlslite/utils/keyfactory.py0000750036466300116100000002112712156622363022164 0ustar afshareng00000000000000"""Factory functions for asymmetric cryptography. @sort: generateRSAKey, parseXMLKey, parsePEMKey, parseAsPublicKey, parseAsPrivateKey """ from compat import * from RSAKey import RSAKey from Python_RSAKey import Python_RSAKey import cryptomath if cryptomath.m2cryptoLoaded: from OpenSSL_RSAKey import OpenSSL_RSAKey if cryptomath.pycryptoLoaded: from PyCrypto_RSAKey import PyCrypto_RSAKey # ************************************************************************** # Factory Functions for RSA Keys # ************************************************************************** def generateRSAKey(bits, implementations=["openssl", "python"]): """Generate an RSA key with the specified bit length. @type bits: int @param bits: Desired bit length of the new key's modulus. @rtype: L{tlslite.utils.RSAKey.RSAKey} @return: A new RSA private key. """ for implementation in implementations: if implementation == "openssl" and cryptomath.m2cryptoLoaded: return OpenSSL_RSAKey.generate(bits) elif implementation == "python": return Python_RSAKey.generate(bits) raise ValueError("No acceptable implementations") def parseXMLKey(s, private=False, public=False, implementations=["python"]): """Parse an XML-format key. The XML format used here is specific to tlslite and cryptoIDlib. The format can store the public component of a key, or the public and private components. For example:: 4a5yzB8oGNlHo866CAspAC47M4Fvx58zwK8pou... Aw== 4a5yzB8oGNlHo866CAspAC47M4Fvx58zwK8pou... Aw== JZ0TIgUxWXmL8KJ0VqyG1V0J3ern9pqIoB0xmy...

    5PreIj6z6ldIGL1V4+1C36dQFHNCQHJvW52GXc... /E/wDit8YXPCxx126zTq2ilQ3IcW54NJYyNjiZ... mKc+wX8inDowEH45Qp4slRo1YveBgExKPROu6... qDVKtBz9lk0shL5PR3ickXDgkwS576zbl2ztB... j6E8EA7dNsTImaXexAmLA1DoeArsYeFAInr... @type s: str @param s: A string containing an XML public or private key. @type private: bool @param private: If True, a L{SyntaxError} will be raised if the private key component is not present. @type public: bool @param public: If True, the private key component (if present) will be discarded, so this function will always return a public key. @rtype: L{tlslite.utils.RSAKey.RSAKey} @return: An RSA key. @raise SyntaxError: If the key is not properly formatted. """ for implementation in implementations: if implementation == "python": key = Python_RSAKey.parseXML(s) break else: raise ValueError("No acceptable implementations") return _parseKeyHelper(key, private, public) #Parse as an OpenSSL or Python key def parsePEMKey(s, private=False, public=False, passwordCallback=None, implementations=["openssl", "python"]): """Parse a PEM-format key. The PEM format is used by OpenSSL and other tools. The format is typically used to store both the public and private components of a key. For example:: -----BEGIN RSA PRIVATE KEY----- MIICXQIBAAKBgQDYscuoMzsGmW0pAYsmyHltxB2TdwHS0dImfjCMfaSDkfLdZY5+ dOWORVns9etWnr194mSGA1F0Pls/VJW8+cX9+3vtJV8zSdANPYUoQf0TP7VlJxkH dSRkUbEoz5bAAs/+970uos7n7iXQIni+3erUTdYEk2iWnMBjTljfgbK/dQIDAQAB AoGAJHoJZk75aKr7DSQNYIHuruOMdv5ZeDuJvKERWxTrVJqE32/xBKh42/IgqRrc esBN9ZregRCd7YtxoL+EVUNWaJNVx2mNmezEznrc9zhcYUrgeaVdFO2yBF1889zO gCOVwrO8uDgeyj6IKa25H6c1N13ih/o7ZzEgWbGG+ylU1yECQQDv4ZSJ4EjSh/Fl aHdz3wbBa/HKGTjC8iRy476Cyg2Fm8MZUe9Yy3udOrb5ZnS2MTpIXt5AF3h2TfYV VoFXIorjAkEA50FcJmzT8sNMrPaV8vn+9W2Lu4U7C+K/O2g1iXMaZms5PC5zV5aV CKXZWUX1fq2RaOzlbQrpgiolhXpeh8FjxwJBAOFHzSQfSsTNfttp3KUpU0LbiVvv i+spVSnA0O4rq79KpVNmK44Mq67hsW1P11QzrzTAQ6GVaUBRv0YS061td1kCQHnP wtN2tboFR6lABkJDjxoGRvlSt4SOPr7zKGgrWjeiuTZLHXSAnCY+/hr5L9Q3ZwXG 6x6iBdgLjVIe4BZQNtcCQQDXGv/gWinCNTN3MPWfTW/RGzuMYVmyBFais0/VrgdH h1dLpztmpQqfyH/zrBXQ9qL/zR4ojS6XYneO/U18WpEe -----END RSA PRIVATE KEY----- To generate a key like this with OpenSSL, run:: openssl genrsa 2048 > key.pem This format also supports password-encrypted private keys. TLS Lite can only handle password-encrypted private keys when OpenSSL and M2Crypto are installed. In this case, passwordCallback will be invoked to query the user for the password. @type s: str @param s: A string containing a PEM-encoded public or private key. @type private: bool @param private: If True, a L{SyntaxError} will be raised if the private key component is not present. @type public: bool @param public: If True, the private key component (if present) will be discarded, so this function will always return a public key. @type passwordCallback: callable @param passwordCallback: This function will be called, with no arguments, if the PEM-encoded private key is password-encrypted. The callback should return the password string. If the password is incorrect, SyntaxError will be raised. If no callback is passed and the key is password-encrypted, a prompt will be displayed at the console. @rtype: L{tlslite.utils.RSAKey.RSAKey} @return: An RSA key. @raise SyntaxError: If the key is not properly formatted. """ for implementation in implementations: if implementation == "openssl" and cryptomath.m2cryptoLoaded: key = OpenSSL_RSAKey.parse(s, passwordCallback) break elif implementation == "python": key = Python_RSAKey.parsePEM(s) break else: raise ValueError("No acceptable implementations") return _parseKeyHelper(key, private, public) def _parseKeyHelper(key, private, public): if private: if not key.hasPrivateKey(): raise SyntaxError("Not a private key!") if public: return _createPublicKey(key) if private: if hasattr(key, "d"): return _createPrivateKey(key) else: return key return key def parseAsPublicKey(s): """Parse an XML or PEM-formatted public key. @type s: str @param s: A string containing an XML or PEM-encoded public or private key. @rtype: L{tlslite.utils.RSAKey.RSAKey} @return: An RSA public key. @raise SyntaxError: If the key is not properly formatted. """ try: return parsePEMKey(s, public=True) except: return parseXMLKey(s, public=True) def parsePrivateKey(s): """Parse an XML or PEM-formatted private key. @type s: str @param s: A string containing an XML or PEM-encoded private key. @rtype: L{tlslite.utils.RSAKey.RSAKey} @return: An RSA private key. @raise SyntaxError: If the key is not properly formatted. """ try: return parsePEMKey(s, private=True) except: return parseXMLKey(s, private=True) def _createPublicKey(key): """ Create a new public key. Discard any private component, and return the most efficient key possible. """ if not isinstance(key, RSAKey): raise AssertionError() return _createPublicRSAKey(key.n, key.e) def _createPrivateKey(key): """ Create a new private key. Return the most efficient key possible. """ if not isinstance(key, RSAKey): raise AssertionError() if not key.hasPrivateKey(): raise AssertionError() return _createPrivateRSAKey(key.n, key.e, key.d, key.p, key.q, key.dP, key.dQ, key.qInv) def _createPublicRSAKey(n, e, implementations = ["openssl", "pycrypto", "python"]): for implementation in implementations: if implementation == "openssl" and cryptomath.m2cryptoLoaded: return OpenSSL_RSAKey(n, e) elif implementation == "pycrypto" and cryptomath.pycryptoLoaded: return PyCrypto_RSAKey(n, e) elif implementation == "python": return Python_RSAKey(n, e) raise ValueError("No acceptable implementations") def _createPrivateRSAKey(n, e, d, p, q, dP, dQ, qInv, implementations = ["pycrypto", "python"]): for implementation in implementations: if implementation == "pycrypto" and cryptomath.pycryptoLoaded: return PyCrypto_RSAKey(n, e, d, p, q, dP, dQ, qInv) elif implementation == "python": return Python_RSAKey(n, e, d, p, q, dP, dQ, qInv) raise ValueError("No acceptable implementations") gdata-2.0.18/src/gdata/tlslite/utils/OpenSSL_RSAKey.py0000750036466300116100000001162612156622363022450 0ustar afshareng00000000000000"""OpenSSL/M2Crypto RSA implementation.""" from cryptomath import * from RSAKey import * from Python_RSAKey import Python_RSAKey #copied from M2Crypto.util.py, so when we load the local copy of m2 #we can still use it def password_callback(v, prompt1='Enter private key passphrase:', prompt2='Verify passphrase:'): from getpass import getpass while 1: try: p1=getpass(prompt1) if v: p2=getpass(prompt2) if p1==p2: break else: break except KeyboardInterrupt: return None return p1 if m2cryptoLoaded: class OpenSSL_RSAKey(RSAKey): def __init__(self, n=0, e=0): self.rsa = None self._hasPrivateKey = False if (n and not e) or (e and not n): raise AssertionError() if n and e: self.rsa = m2.rsa_new() m2.rsa_set_n(self.rsa, numberToMPI(n)) m2.rsa_set_e(self.rsa, numberToMPI(e)) def __del__(self): if self.rsa: m2.rsa_free(self.rsa) def __getattr__(self, name): if name == 'e': if not self.rsa: return 0 return mpiToNumber(m2.rsa_get_e(self.rsa)) elif name == 'n': if not self.rsa: return 0 return mpiToNumber(m2.rsa_get_n(self.rsa)) else: raise AttributeError def hasPrivateKey(self): return self._hasPrivateKey def hash(self): return Python_RSAKey(self.n, self.e).hash() def _rawPrivateKeyOp(self, m): s = numberToString(m) byteLength = numBytes(self.n) if len(s)== byteLength: pass elif len(s) == byteLength-1: s = '\0' + s else: raise AssertionError() c = stringToNumber(m2.rsa_private_encrypt(self.rsa, s, m2.no_padding)) return c def _rawPublicKeyOp(self, c): s = numberToString(c) byteLength = numBytes(self.n) if len(s)== byteLength: pass elif len(s) == byteLength-1: s = '\0' + s else: raise AssertionError() m = stringToNumber(m2.rsa_public_decrypt(self.rsa, s, m2.no_padding)) return m def acceptsPassword(self): return True def write(self, password=None): bio = m2.bio_new(m2.bio_s_mem()) if self._hasPrivateKey: if password: def f(v): return password m2.rsa_write_key(self.rsa, bio, m2.des_ede_cbc(), f) else: def f(): pass m2.rsa_write_key_no_cipher(self.rsa, bio, f) else: if password: raise AssertionError() m2.rsa_write_pub_key(self.rsa, bio) s = m2.bio_read(bio, m2.bio_ctrl_pending(bio)) m2.bio_free(bio) return s def writeXMLPublicKey(self, indent=''): return Python_RSAKey(self.n, self.e).write(indent) def generate(bits): key = OpenSSL_RSAKey() def f():pass key.rsa = m2.rsa_generate_key(bits, 3, f) key._hasPrivateKey = True return key generate = staticmethod(generate) def parse(s, passwordCallback=None): if s.startswith("-----BEGIN "): if passwordCallback==None: callback = password_callback else: def f(v, prompt1=None, prompt2=None): return passwordCallback() callback = f bio = m2.bio_new(m2.bio_s_mem()) try: m2.bio_write(bio, s) key = OpenSSL_RSAKey() if s.startswith("-----BEGIN RSA PRIVATE KEY-----"): def f():pass key.rsa = m2.rsa_read_key(bio, callback) if key.rsa == None: raise SyntaxError() key._hasPrivateKey = True elif s.startswith("-----BEGIN PUBLIC KEY-----"): key.rsa = m2.rsa_read_pub_key(bio) if key.rsa == None: raise SyntaxError() key._hasPrivateKey = False else: raise SyntaxError() return key finally: m2.bio_free(bio) else: raise SyntaxError() parse = staticmethod(parse) gdata-2.0.18/src/gdata/tlslite/utils/Python_RC4.py0000750036466300116100000000176612156622363021744 0ustar afshareng00000000000000"""Pure-Python RC4 implementation.""" from RC4 import RC4 from cryptomath import * def new(key): return Python_RC4(key) class Python_RC4(RC4): def __init__(self, key): RC4.__init__(self, key, "python") keyBytes = stringToBytes(key) S = [i for i in range(256)] j = 0 for i in range(256): j = (j + S[i] + keyBytes[i % len(keyBytes)]) % 256 S[i], S[j] = S[j], S[i] self.S = S self.i = 0 self.j = 0 def encrypt(self, plaintext): plaintextBytes = stringToBytes(plaintext) S = self.S i = self.i j = self.j for x in range(len(plaintextBytes)): i = (i + 1) % 256 j = (j + S[i]) % 256 S[i], S[j] = S[j], S[i] t = (S[i] + S[j]) % 256 plaintextBytes[x] ^= S[t] self.i = i self.j = j return bytesToString(plaintextBytes) def decrypt(self, ciphertext): return self.encrypt(ciphertext) gdata-2.0.18/src/gdata/tlslite/utils/hmac.py0000750036466300116100000000632612156622363020720 0ustar afshareng00000000000000"""HMAC (Keyed-Hashing for Message Authentication) Python module. Implements the HMAC algorithm as described by RFC 2104. (This file is modified from the standard library version to do faster copying) """ def _strxor(s1, s2): """Utility method. XOR the two strings s1 and s2 (must have same length). """ return "".join(map(lambda x, y: chr(ord(x) ^ ord(y)), s1, s2)) # The size of the digests returned by HMAC depends on the underlying # hashing module used. digest_size = None class HMAC: """RFC2104 HMAC class. This supports the API for Cryptographic Hash Functions (PEP 247). """ def __init__(self, key, msg = None, digestmod = None): """Create a new HMAC object. key: key for the keyed hash object. msg: Initial input for the hash, if provided. digestmod: A module supporting PEP 247. Defaults to the md5 module. """ if digestmod is None: import md5 digestmod = md5 if key == None: #TREVNEW - for faster copying return #TREVNEW self.digestmod = digestmod self.outer = digestmod.new() self.inner = digestmod.new() self.digest_size = digestmod.digest_size blocksize = 64 ipad = "\x36" * blocksize opad = "\x5C" * blocksize if len(key) > blocksize: key = digestmod.new(key).digest() key = key + chr(0) * (blocksize - len(key)) self.outer.update(_strxor(key, opad)) self.inner.update(_strxor(key, ipad)) if msg is not None: self.update(msg) ## def clear(self): ## raise NotImplementedError, "clear() method not available in HMAC." def update(self, msg): """Update this hashing object with the string msg. """ self.inner.update(msg) def copy(self): """Return a separate copy of this hashing object. An update to this copy won't affect the original object. """ other = HMAC(None) #TREVNEW - for faster copying other.digest_size = self.digest_size #TREVNEW other.digestmod = self.digestmod other.inner = self.inner.copy() other.outer = self.outer.copy() return other def digest(self): """Return the hash value of this hashing object. This returns a string containing 8-bit data. The object is not altered in any way by this function; you can continue updating the object after calling this function. """ h = self.outer.copy() h.update(self.inner.digest()) return h.digest() def hexdigest(self): """Like digest(), but returns a string of hexadecimal digits instead. """ return "".join([hex(ord(x))[2:].zfill(2) for x in tuple(self.digest())]) def new(key, msg = None, digestmod = None): """Create a new hashing object and return it. key: The starting key for the hash. msg: if available, will immediately be hashed into the object's starting state. You can now feed arbitrary strings into the object using its update() method, and can ask for the hash value at any time by calling its digest() method. """ return HMAC(key, msg, digestmod) gdata-2.0.18/src/gdata/tlslite/utils/entropy.c0000750036466300116100000001127512156622363021301 0ustar afshareng00000000000000 #include "Python.h" #ifdef MS_WINDOWS /* The following #define is not needed on VC6 with the Platform SDK, and it may not be needed on VC7, I'm not sure. I don't think it hurts anything.*/ #define _WIN32_WINNT 0x0400 #include typedef BOOL (WINAPI *CRYPTACQUIRECONTEXTA)(HCRYPTPROV *phProv,\ LPCSTR pszContainer, LPCSTR pszProvider, DWORD dwProvType,\ DWORD dwFlags ); typedef BOOL (WINAPI *CRYPTGENRANDOM)(HCRYPTPROV hProv, DWORD dwLen,\ BYTE *pbBuffer ); typedef BOOL (WINAPI *CRYPTRELEASECONTEXT)(HCRYPTPROV hProv,\ DWORD dwFlags); static PyObject* entropy(PyObject *self, PyObject *args) { int howMany = 0; HINSTANCE hAdvAPI32 = NULL; CRYPTACQUIRECONTEXTA pCryptAcquireContextA = NULL; CRYPTGENRANDOM pCryptGenRandom = NULL; CRYPTRELEASECONTEXT pCryptReleaseContext = NULL; HCRYPTPROV hCryptProv = 0; unsigned char* bytes = NULL; PyObject* returnVal = NULL; /* Read arguments */ if (!PyArg_ParseTuple(args, "i", &howMany)) return(NULL); /* Obtain handle to the DLL containing CryptoAPI This should not fail */ if( (hAdvAPI32 = GetModuleHandle("advapi32.dll")) == NULL) { PyErr_Format(PyExc_SystemError, "Advapi32.dll not found"); return NULL; } /* Obtain pointers to the CryptoAPI functions This will fail on some early version of Win95 */ pCryptAcquireContextA = (CRYPTACQUIRECONTEXTA)GetProcAddress(hAdvAPI32,\ "CryptAcquireContextA"); pCryptGenRandom = (CRYPTGENRANDOM)GetProcAddress(hAdvAPI32,\ "CryptGenRandom"); pCryptReleaseContext = (CRYPTRELEASECONTEXT) GetProcAddress(hAdvAPI32,\ "CryptReleaseContext"); if (pCryptAcquireContextA == NULL || pCryptGenRandom == NULL || pCryptReleaseContext == NULL) { PyErr_Format(PyExc_NotImplementedError, "CryptoAPI not available on this version of Windows"); return NULL; } /* Allocate bytes */ if ((bytes = (unsigned char*)PyMem_Malloc(howMany)) == NULL) return PyErr_NoMemory(); /* Acquire context */ if(!pCryptAcquireContextA(&hCryptProv, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) { PyErr_Format(PyExc_SystemError, "CryptAcquireContext failed, error %d", GetLastError()); PyMem_Free(bytes); return NULL; } /* Get random data */ if(!pCryptGenRandom(hCryptProv, howMany, bytes)) { PyErr_Format(PyExc_SystemError, "CryptGenRandom failed, error %d", GetLastError()); PyMem_Free(bytes); CryptReleaseContext(hCryptProv, 0); return NULL; } /* Build return value */ returnVal = Py_BuildValue("s#", bytes, howMany); PyMem_Free(bytes); /* Release context */ if (!pCryptReleaseContext(hCryptProv, 0)) { PyErr_Format(PyExc_SystemError, "CryptReleaseContext failed, error %d", GetLastError()); return NULL; } return returnVal; } #elif defined(HAVE_UNISTD_H) && defined(HAVE_FCNTL_H) #include #include static PyObject* entropy(PyObject *self, PyObject *args) { int howMany; int fd; unsigned char* bytes = NULL; PyObject* returnVal = NULL; /* Read arguments */ if (!PyArg_ParseTuple(args, "i", &howMany)) return(NULL); /* Allocate bytes */ if ((bytes = (unsigned char*)PyMem_Malloc(howMany)) == NULL) return PyErr_NoMemory(); /* Open device */ if ((fd = open("/dev/urandom", O_RDONLY, 0)) == -1) { PyErr_Format(PyExc_NotImplementedError, "No entropy source found"); PyMem_Free(bytes); return NULL; } /* Get random data */ if (read(fd, bytes, howMany) < howMany) { PyErr_Format(PyExc_SystemError, "Reading from /dev/urandom failed"); PyMem_Free(bytes); close(fd); return NULL; } /* Build return value */ returnVal = Py_BuildValue("s#", bytes, howMany); PyMem_Free(bytes); /* Close device */ close(fd); return returnVal; } #else static PyObject* entropy(PyObject *self, PyObject *args) { PyErr_Format(PyExc_NotImplementedError, "Function not supported"); return NULL; } #endif /* List of functions exported by this module */ static struct PyMethodDef entropy_functions[] = { {"entropy", (PyCFunction)entropy, METH_VARARGS, "Return a string of random bytes produced by a platform-specific\nentropy source."}, {NULL, NULL} /* Sentinel */ }; /* Initialize this module. */ PyMODINIT_FUNC initentropy(void) { Py_InitModule("entropy", entropy_functions); }gdata-2.0.18/src/gdata/tlslite/utils/OpenSSL_TripleDES.py0000750036466300116100000000320212156622363023134 0ustar afshareng00000000000000"""OpenSSL/M2Crypto 3DES implementation.""" from cryptomath import * from TripleDES import * if m2cryptoLoaded: def new(key, mode, IV): return OpenSSL_TripleDES(key, mode, IV) class OpenSSL_TripleDES(TripleDES): def __init__(self, key, mode, IV): TripleDES.__init__(self, key, mode, IV, "openssl") self.key = key self.IV = IV def _createContext(self, encrypt): context = m2.cipher_ctx_new() cipherType = m2.des_ede3_cbc() m2.cipher_init(context, cipherType, self.key, self.IV, encrypt) return context def encrypt(self, plaintext): TripleDES.encrypt(self, plaintext) context = self._createContext(1) ciphertext = m2.cipher_update(context, plaintext) m2.cipher_ctx_free(context) self.IV = ciphertext[-self.block_size:] return ciphertext def decrypt(self, ciphertext): TripleDES.decrypt(self, ciphertext) context = self._createContext(0) #I think M2Crypto has a bug - it fails to decrypt and return the last block passed in. #To work around this, we append sixteen zeros to the string, below: plaintext = m2.cipher_update(context, ciphertext+('\0'*16)) #If this bug is ever fixed, then plaintext will end up having a garbage #plaintext block on the end. That's okay - the below code will ignore it. plaintext = plaintext[:len(ciphertext)] m2.cipher_ctx_free(context) self.IV = ciphertext[-self.block_size:] return plaintextgdata-2.0.18/src/gdata/tlslite/utils/Python_AES.py0000750036466300116100000000411112156622363021747 0ustar afshareng00000000000000"""Pure-Python AES implementation.""" from cryptomath import * from AES import * from rijndael import rijndael def new(key, mode, IV): return Python_AES(key, mode, IV) class Python_AES(AES): def __init__(self, key, mode, IV): AES.__init__(self, key, mode, IV, "python") self.rijndael = rijndael(key, 16) self.IV = IV def encrypt(self, plaintext): AES.encrypt(self, plaintext) plaintextBytes = stringToBytes(plaintext) chainBytes = stringToBytes(self.IV) #CBC Mode: For each block... for x in range(len(plaintextBytes)/16): #XOR with the chaining block blockBytes = plaintextBytes[x*16 : (x*16)+16] for y in range(16): blockBytes[y] ^= chainBytes[y] blockString = bytesToString(blockBytes) #Encrypt it encryptedBytes = stringToBytes(self.rijndael.encrypt(blockString)) #Overwrite the input with the output for y in range(16): plaintextBytes[(x*16)+y] = encryptedBytes[y] #Set the next chaining block chainBytes = encryptedBytes self.IV = bytesToString(chainBytes) return bytesToString(plaintextBytes) def decrypt(self, ciphertext): AES.decrypt(self, ciphertext) ciphertextBytes = stringToBytes(ciphertext) chainBytes = stringToBytes(self.IV) #CBC Mode: For each block... for x in range(len(ciphertextBytes)/16): #Decrypt it blockBytes = ciphertextBytes[x*16 : (x*16)+16] blockString = bytesToString(blockBytes) decryptedBytes = stringToBytes(self.rijndael.decrypt(blockString)) #XOR with the chaining block and overwrite the input with output for y in range(16): decryptedBytes[y] ^= chainBytes[y] ciphertextBytes[(x*16)+y] = decryptedBytes[y] #Set the next chaining block chainBytes = blockBytes self.IV = bytesToString(chainBytes) return bytesToString(ciphertextBytes) gdata-2.0.18/src/gdata/tlslite/utils/Python_RSAKey.py0000750036466300116100000001703312156622363022444 0ustar afshareng00000000000000"""Pure-Python RSA implementation.""" from cryptomath import * import xmltools from ASN1Parser import ASN1Parser from RSAKey import * class Python_RSAKey(RSAKey): def __init__(self, n=0, e=0, d=0, p=0, q=0, dP=0, dQ=0, qInv=0): if (n and not e) or (e and not n): raise AssertionError() self.n = n self.e = e self.d = d self.p = p self.q = q self.dP = dP self.dQ = dQ self.qInv = qInv self.blinder = 0 self.unblinder = 0 def hasPrivateKey(self): return self.d != 0 def hash(self): s = self.writeXMLPublicKey('\t\t') return hashAndBase64(s.strip()) def _rawPrivateKeyOp(self, m): #Create blinding values, on the first pass: if not self.blinder: self.unblinder = getRandomNumber(2, self.n) self.blinder = powMod(invMod(self.unblinder, self.n), self.e, self.n) #Blind the input m = (m * self.blinder) % self.n #Perform the RSA operation c = self._rawPrivateKeyOpHelper(m) #Unblind the output c = (c * self.unblinder) % self.n #Update blinding values self.blinder = (self.blinder * self.blinder) % self.n self.unblinder = (self.unblinder * self.unblinder) % self.n #Return the output return c def _rawPrivateKeyOpHelper(self, m): #Non-CRT version #c = powMod(m, self.d, self.n) #CRT version (~3x faster) s1 = powMod(m, self.dP, self.p) s2 = powMod(m, self.dQ, self.q) h = ((s1 - s2) * self.qInv) % self.p c = s2 + self.q * h return c def _rawPublicKeyOp(self, c): m = powMod(c, self.e, self.n) return m def acceptsPassword(self): return False def write(self, indent=''): if self.d: s = indent+'\n' else: s = indent+'\n' s += indent+'\t%s\n' % numberToBase64(self.n) s += indent+'\t%s\n' % numberToBase64(self.e) if self.d: s += indent+'\t%s\n' % numberToBase64(self.d) s += indent+'\t

    %s

    \n' % numberToBase64(self.p) s += indent+'\t%s\n' % numberToBase64(self.q) s += indent+'\t%s\n' % numberToBase64(self.dP) s += indent+'\t%s\n' % numberToBase64(self.dQ) s += indent+'\t%s\n' % numberToBase64(self.qInv) s += indent+'
    ' else: s += indent+'
    ' #Only add \n if part of a larger structure if indent != '': s += '\n' return s def writeXMLPublicKey(self, indent=''): return Python_RSAKey(self.n, self.e).write(indent) def generate(bits): key = Python_RSAKey() p = getRandomPrime(bits/2, False) q = getRandomPrime(bits/2, False) t = lcm(p-1, q-1) key.n = p * q key.e = 3L #Needed to be long, for Java key.d = invMod(key.e, t) key.p = p key.q = q key.dP = key.d % (p-1) key.dQ = key.d % (q-1) key.qInv = invMod(q, p) return key generate = staticmethod(generate) def parsePEM(s, passwordCallback=None): """Parse a string containing a or , or PEM-encoded key.""" start = s.find("-----BEGIN PRIVATE KEY-----") if start != -1: end = s.find("-----END PRIVATE KEY-----") if end == -1: raise SyntaxError("Missing PEM Postfix") s = s[start+len("-----BEGIN PRIVATE KEY -----") : end] bytes = base64ToBytes(s) return Python_RSAKey._parsePKCS8(bytes) else: start = s.find("-----BEGIN RSA PRIVATE KEY-----") if start != -1: end = s.find("-----END RSA PRIVATE KEY-----") if end == -1: raise SyntaxError("Missing PEM Postfix") s = s[start+len("-----BEGIN RSA PRIVATE KEY -----") : end] bytes = base64ToBytes(s) return Python_RSAKey._parseSSLeay(bytes) raise SyntaxError("Missing PEM Prefix") parsePEM = staticmethod(parsePEM) def parseXML(s): element = xmltools.parseAndStripWhitespace(s) return Python_RSAKey._parseXML(element) parseXML = staticmethod(parseXML) def _parsePKCS8(bytes): p = ASN1Parser(bytes) version = p.getChild(0).value[0] if version != 0: raise SyntaxError("Unrecognized PKCS8 version") rsaOID = p.getChild(1).value if list(rsaOID) != [6, 9, 42, 134, 72, 134, 247, 13, 1, 1, 1, 5, 0]: raise SyntaxError("Unrecognized AlgorithmIdentifier") #Get the privateKey privateKeyP = p.getChild(2) #Adjust for OCTET STRING encapsulation privateKeyP = ASN1Parser(privateKeyP.value) return Python_RSAKey._parseASN1PrivateKey(privateKeyP) _parsePKCS8 = staticmethod(_parsePKCS8) def _parseSSLeay(bytes): privateKeyP = ASN1Parser(bytes) return Python_RSAKey._parseASN1PrivateKey(privateKeyP) _parseSSLeay = staticmethod(_parseSSLeay) def _parseASN1PrivateKey(privateKeyP): version = privateKeyP.getChild(0).value[0] if version != 0: raise SyntaxError("Unrecognized RSAPrivateKey version") n = bytesToNumber(privateKeyP.getChild(1).value) e = bytesToNumber(privateKeyP.getChild(2).value) d = bytesToNumber(privateKeyP.getChild(3).value) p = bytesToNumber(privateKeyP.getChild(4).value) q = bytesToNumber(privateKeyP.getChild(5).value) dP = bytesToNumber(privateKeyP.getChild(6).value) dQ = bytesToNumber(privateKeyP.getChild(7).value) qInv = bytesToNumber(privateKeyP.getChild(8).value) return Python_RSAKey(n, e, d, p, q, dP, dQ, qInv) _parseASN1PrivateKey = staticmethod(_parseASN1PrivateKey) def _parseXML(element): try: xmltools.checkName(element, "privateKey") except SyntaxError: xmltools.checkName(element, "publicKey") #Parse attributes xmltools.getReqAttribute(element, "xmlns", "http://trevp.net/rsa\Z") xmltools.checkNoMoreAttributes(element) #Parse public values ( and ) n = base64ToNumber(xmltools.getText(xmltools.getChild(element, 0, "n"), xmltools.base64RegEx)) e = base64ToNumber(xmltools.getText(xmltools.getChild(element, 1, "e"), xmltools.base64RegEx)) d = 0 p = 0 q = 0 dP = 0 dQ = 0 qInv = 0 #Parse private values, if present if element.childNodes.length>=3: d = base64ToNumber(xmltools.getText(xmltools.getChild(element, 2, "d"), xmltools.base64RegEx)) p = base64ToNumber(xmltools.getText(xmltools.getChild(element, 3, "p"), xmltools.base64RegEx)) q = base64ToNumber(xmltools.getText(xmltools.getChild(element, 4, "q"), xmltools.base64RegEx)) dP = base64ToNumber(xmltools.getText(xmltools.getChild(element, 5, "dP"), xmltools.base64RegEx)) dQ = base64ToNumber(xmltools.getText(xmltools.getChild(element, 6, "dQ"), xmltools.base64RegEx)) qInv = base64ToNumber(xmltools.getText(xmltools.getLastChild(element, 7, "qInv"), xmltools.base64RegEx)) return Python_RSAKey(n, e, d, p, q, dP, dQ, qInv) _parseXML = staticmethod(_parseXML) gdata-2.0.18/src/gdata/tlslite/utils/codec.py0000750036466300116100000000532312156622363021061 0ustar afshareng00000000000000"""Classes for reading/writing binary data (such as TLS records).""" from compat import * class Writer: def __init__(self, length=0): #If length is zero, then this is just a "trial run" to determine length self.index = 0 self.bytes = createByteArrayZeros(length) def add(self, x, length): if self.bytes: newIndex = self.index+length-1 while newIndex >= self.index: self.bytes[newIndex] = x & 0xFF x >>= 8 newIndex -= 1 self.index += length def addFixSeq(self, seq, length): if self.bytes: for e in seq: self.add(e, length) else: self.index += len(seq)*length def addVarSeq(self, seq, length, lengthLength): if self.bytes: self.add(len(seq)*length, lengthLength) for e in seq: self.add(e, length) else: self.index += lengthLength + (len(seq)*length) class Parser: def __init__(self, bytes): self.bytes = bytes self.index = 0 def get(self, length): if self.index + length > len(self.bytes): raise SyntaxError() x = 0 for count in range(length): x <<= 8 x |= self.bytes[self.index] self.index += 1 return x def getFixBytes(self, lengthBytes): bytes = self.bytes[self.index : self.index+lengthBytes] self.index += lengthBytes return bytes def getVarBytes(self, lengthLength): lengthBytes = self.get(lengthLength) return self.getFixBytes(lengthBytes) def getFixList(self, length, lengthList): l = [0] * lengthList for x in range(lengthList): l[x] = self.get(length) return l def getVarList(self, length, lengthLength): lengthList = self.get(lengthLength) if lengthList % length != 0: raise SyntaxError() lengthList = int(lengthList/length) l = [0] * lengthList for x in range(lengthList): l[x] = self.get(length) return l def startLengthCheck(self, lengthLength): self.lengthCheck = self.get(lengthLength) self.indexCheck = self.index def setLengthCheck(self, length): self.lengthCheck = length self.indexCheck = self.index def stopLengthCheck(self): if (self.index - self.indexCheck) != self.lengthCheck: raise SyntaxError() def atLengthCheck(self): if (self.index - self.indexCheck) < self.lengthCheck: return False elif (self.index - self.indexCheck) == self.lengthCheck: return True else: raise SyntaxError()gdata-2.0.18/src/gdata/tlslite/utils/compat.py0000750036466300116100000000773412156622363021277 0ustar afshareng00000000000000"""Miscellaneous functions to mask Python version differences.""" import sys import os if sys.version_info < (2,2): raise AssertionError("Python 2.2 or later required") if sys.version_info < (2,3): def enumerate(collection): return zip(range(len(collection)), collection) class Set: def __init__(self, seq=None): self.values = {} if seq: for e in seq: self.values[e] = None def add(self, e): self.values[e] = None def discard(self, e): if e in self.values.keys(): del(self.values[e]) def union(self, s): ret = Set() for e in self.values.keys(): ret.values[e] = None for e in s.values.keys(): ret.values[e] = None return ret def issubset(self, other): for e in self.values.keys(): if e not in other.values.keys(): return False return True def __nonzero__( self): return len(self.values.keys()) def __contains__(self, e): return e in self.values.keys() def __iter__(self): return iter(set.values.keys()) if os.name != "java": import array def createByteArraySequence(seq): return array.array('B', seq) def createByteArrayZeros(howMany): return array.array('B', [0] * howMany) def concatArrays(a1, a2): return a1+a2 def bytesToString(bytes): return bytes.tostring() def stringToBytes(s): bytes = createByteArrayZeros(0) bytes.fromstring(s) return bytes import math def numBits(n): if n==0: return 0 s = "%x" % n return ((len(s)-1)*4) + \ {'0':0, '1':1, '2':2, '3':2, '4':3, '5':3, '6':3, '7':3, '8':4, '9':4, 'a':4, 'b':4, 'c':4, 'd':4, 'e':4, 'f':4, }[s[0]] return int(math.floor(math.log(n, 2))+1) BaseException = Exception import sys import traceback def formatExceptionTrace(e): newStr = "".join(traceback.format_exception(sys.exc_type, sys.exc_value, sys.exc_traceback)) return newStr else: #Jython 2.1 is missing lots of python 2.3 stuff, #which we have to emulate here: #NOTE: JYTHON SUPPORT NO LONGER WORKS, DUE TO USE OF GENERATORS. #THIS CODE IS LEFT IN SO THAT ONE JYTHON UPDATES TO 2.2, IT HAS A #CHANCE OF WORKING AGAIN. import java import jarray def createByteArraySequence(seq): if isinstance(seq, type("")): #If it's a string, convert seq = [ord(c) for c in seq] return jarray.array(seq, 'h') #use short instead of bytes, cause bytes are signed def createByteArrayZeros(howMany): return jarray.zeros(howMany, 'h') #use short instead of bytes, cause bytes are signed def concatArrays(a1, a2): l = list(a1)+list(a2) return createByteArraySequence(l) #WAY TOO SLOW - MUST BE REPLACED------------ def bytesToString(bytes): return "".join([chr(b) for b in bytes]) def stringToBytes(s): bytes = createByteArrayZeros(len(s)) for count, c in enumerate(s): bytes[count] = ord(c) return bytes #WAY TOO SLOW - MUST BE REPLACED------------ def numBits(n): if n==0: return 0 n= 1L * n; #convert to long, if it isn't already return n.__tojava__(java.math.BigInteger).bitLength() #Adjust the string to an array of bytes def stringToJavaByteArray(s): bytes = jarray.zeros(len(s), 'b') for count, c in enumerate(s): x = ord(c) if x >= 128: x -= 256 bytes[count] = x return bytes BaseException = java.lang.Exception import sys import traceback def formatExceptionTrace(e): newStr = "".join(traceback.format_exception(sys.exc_type, sys.exc_value, sys.exc_traceback)) return newStrgdata-2.0.18/src/gdata/tlslite/utils/PyCrypto_TripleDES.py0000750036466300116100000000114212156622363023443 0ustar afshareng00000000000000"""PyCrypto 3DES implementation.""" from cryptomath import * from TripleDES import * if pycryptoLoaded: import Crypto.Cipher.DES3 def new(key, mode, IV): return PyCrypto_TripleDES(key, mode, IV) class PyCrypto_TripleDES(TripleDES): def __init__(self, key, mode, IV): TripleDES.__init__(self, key, mode, IV, "pycrypto") self.context = Crypto.Cipher.DES3.new(key, mode, IV) def encrypt(self, plaintext): return self.context.encrypt(plaintext) def decrypt(self, ciphertext): return self.context.decrypt(ciphertext)gdata-2.0.18/src/gdata/tlslite/utils/RC4.py0000750036466300116100000000071212156622363020371 0ustar afshareng00000000000000"""Abstract class for RC4.""" from compat import * #For False class RC4: def __init__(self, keyBytes, implementation): if len(keyBytes) < 16 or len(keyBytes) > 256: raise ValueError() self.isBlockCipher = False self.name = "rc4" self.implementation = implementation def encrypt(self, plaintext): raise NotImplementedError() def decrypt(self, ciphertext): raise NotImplementedError()gdata-2.0.18/src/gdata/tlslite/utils/OpenSSL_AES.py0000750036466300116100000000343612156622363021762 0ustar afshareng00000000000000"""OpenSSL/M2Crypto AES implementation.""" from cryptomath import * from AES import * if m2cryptoLoaded: def new(key, mode, IV): return OpenSSL_AES(key, mode, IV) class OpenSSL_AES(AES): def __init__(self, key, mode, IV): AES.__init__(self, key, mode, IV, "openssl") self.key = key self.IV = IV def _createContext(self, encrypt): context = m2.cipher_ctx_new() if len(self.key)==16: cipherType = m2.aes_128_cbc() if len(self.key)==24: cipherType = m2.aes_192_cbc() if len(self.key)==32: cipherType = m2.aes_256_cbc() m2.cipher_init(context, cipherType, self.key, self.IV, encrypt) return context def encrypt(self, plaintext): AES.encrypt(self, plaintext) context = self._createContext(1) ciphertext = m2.cipher_update(context, plaintext) m2.cipher_ctx_free(context) self.IV = ciphertext[-self.block_size:] return ciphertext def decrypt(self, ciphertext): AES.decrypt(self, ciphertext) context = self._createContext(0) #I think M2Crypto has a bug - it fails to decrypt and return the last block passed in. #To work around this, we append sixteen zeros to the string, below: plaintext = m2.cipher_update(context, ciphertext+('\0'*16)) #If this bug is ever fixed, then plaintext will end up having a garbage #plaintext block on the end. That's okay - the below code will discard it. plaintext = plaintext[:len(ciphertext)] m2.cipher_ctx_free(context) self.IV = ciphertext[-self.block_size:] return plaintext gdata-2.0.18/src/gdata/tlslite/utils/PyCrypto_RSAKey.py0000750036466300116100000000342612156622363022755 0ustar afshareng00000000000000"""PyCrypto RSA implementation.""" from cryptomath import * from RSAKey import * from Python_RSAKey import Python_RSAKey if pycryptoLoaded: from Crypto.PublicKey import RSA class PyCrypto_RSAKey(RSAKey): def __init__(self, n=0, e=0, d=0, p=0, q=0, dP=0, dQ=0, qInv=0): if not d: self.rsa = RSA.construct( (n, e) ) else: self.rsa = RSA.construct( (n, e, d, p, q) ) def __getattr__(self, name): return getattr(self.rsa, name) def hasPrivateKey(self): return self.rsa.has_private() def hash(self): return Python_RSAKey(self.n, self.e).hash() def _rawPrivateKeyOp(self, m): s = numberToString(m) byteLength = numBytes(self.n) if len(s)== byteLength: pass elif len(s) == byteLength-1: s = '\0' + s else: raise AssertionError() c = stringToNumber(self.rsa.decrypt((s,))) return c def _rawPublicKeyOp(self, c): s = numberToString(c) byteLength = numBytes(self.n) if len(s)== byteLength: pass elif len(s) == byteLength-1: s = '\0' + s else: raise AssertionError() m = stringToNumber(self.rsa.encrypt(s, None)[0]) return m def writeXMLPublicKey(self, indent=''): return Python_RSAKey(self.n, self.e).write(indent) def generate(bits): key = PyCrypto_RSAKey() def f(numBytes): return bytesToString(getRandomBytes(numBytes)) key.rsa = RSA.generate(bits, f) return key generate = staticmethod(generate) gdata-2.0.18/src/gdata/tlslite/utils/OpenSSL_RC4.py0000750036466300116100000000111312156622363021730 0ustar afshareng00000000000000"""OpenSSL/M2Crypto RC4 implementation.""" from cryptomath import * from RC4 import RC4 if m2cryptoLoaded: def new(key): return OpenSSL_RC4(key) class OpenSSL_RC4(RC4): def __init__(self, key): RC4.__init__(self, key, "openssl") self.rc4 = m2.rc4_new() m2.rc4_set_key(self.rc4, key) def __del__(self): m2.rc4_free(self.rc4) def encrypt(self, plaintext): return m2.rc4_update(self.rc4, plaintext) def decrypt(self, ciphertext): return self.encrypt(ciphertext) gdata-2.0.18/src/gdata/tlslite/utils/TripleDES.py0000750036466300116100000000140012156622363021567 0ustar afshareng00000000000000"""Abstract class for 3DES.""" from compat import * #For True class TripleDES: def __init__(self, key, mode, IV, implementation): if len(key) != 24: raise ValueError() if mode != 2: raise ValueError() if len(IV) != 8: raise ValueError() self.isBlockCipher = True self.block_size = 8 self.implementation = implementation self.name = "3des" #CBC-Mode encryption, returns ciphertext #WARNING: *MAY* modify the input as well def encrypt(self, plaintext): assert(len(plaintext) % 8 == 0) #CBC-Mode decryption, returns plaintext #WARNING: *MAY* modify the input as well def decrypt(self, ciphertext): assert(len(ciphertext) % 8 == 0) gdata-2.0.18/src/gdata/tlslite/utils/RSAKey.py0000750036466300116100000002057112156622363021104 0ustar afshareng00000000000000"""Abstract class for RSA.""" from cryptomath import * class RSAKey: """This is an abstract base class for RSA keys. Particular implementations of RSA keys, such as L{OpenSSL_RSAKey.OpenSSL_RSAKey}, L{Python_RSAKey.Python_RSAKey}, and L{PyCrypto_RSAKey.PyCrypto_RSAKey}, inherit from this. To create or parse an RSA key, don't use one of these classes directly. Instead, use the factory functions in L{tlslite.utils.keyfactory}. """ def __init__(self, n=0, e=0): """Create a new RSA key. If n and e are passed in, the new key will be initialized. @type n: int @param n: RSA modulus. @type e: int @param e: RSA public exponent. """ raise NotImplementedError() def __len__(self): """Return the length of this key in bits. @rtype: int """ return numBits(self.n) def hasPrivateKey(self): """Return whether or not this key has a private component. @rtype: bool """ raise NotImplementedError() def hash(self): """Return the cryptoID value corresponding to this key. @rtype: str """ raise NotImplementedError() def getSigningAlgorithm(self): """Return the cryptoID sigAlgo value corresponding to this key. @rtype: str """ return "pkcs1-sha1" def hashAndSign(self, bytes): """Hash and sign the passed-in bytes. This requires the key to have a private component. It performs a PKCS1-SHA1 signature on the passed-in data. @type bytes: str or L{array.array} of unsigned bytes @param bytes: The value which will be hashed and signed. @rtype: L{array.array} of unsigned bytes. @return: A PKCS1-SHA1 signature on the passed-in data. """ if not isinstance(bytes, type("")): bytes = bytesToString(bytes) hashBytes = stringToBytes(sha1(bytes).digest()) prefixedHashBytes = self._addPKCS1SHA1Prefix(hashBytes) sigBytes = self.sign(prefixedHashBytes) return sigBytes def hashAndVerify(self, sigBytes, bytes): """Hash and verify the passed-in bytes with the signature. This verifies a PKCS1-SHA1 signature on the passed-in data. @type sigBytes: L{array.array} of unsigned bytes @param sigBytes: A PKCS1-SHA1 signature. @type bytes: str or L{array.array} of unsigned bytes @param bytes: The value which will be hashed and verified. @rtype: bool @return: Whether the signature matches the passed-in data. """ if not isinstance(bytes, type("")): bytes = bytesToString(bytes) hashBytes = stringToBytes(sha1(bytes).digest()) prefixedHashBytes = self._addPKCS1SHA1Prefix(hashBytes) return self.verify(sigBytes, prefixedHashBytes) def sign(self, bytes): """Sign the passed-in bytes. This requires the key to have a private component. It performs a PKCS1 signature on the passed-in data. @type bytes: L{array.array} of unsigned bytes @param bytes: The value which will be signed. @rtype: L{array.array} of unsigned bytes. @return: A PKCS1 signature on the passed-in data. """ if not self.hasPrivateKey(): raise AssertionError() paddedBytes = self._addPKCS1Padding(bytes, 1) m = bytesToNumber(paddedBytes) if m >= self.n: raise ValueError() c = self._rawPrivateKeyOp(m) sigBytes = numberToBytes(c) return sigBytes def verify(self, sigBytes, bytes): """Verify the passed-in bytes with the signature. This verifies a PKCS1 signature on the passed-in data. @type sigBytes: L{array.array} of unsigned bytes @param sigBytes: A PKCS1 signature. @type bytes: L{array.array} of unsigned bytes @param bytes: The value which will be verified. @rtype: bool @return: Whether the signature matches the passed-in data. """ paddedBytes = self._addPKCS1Padding(bytes, 1) c = bytesToNumber(sigBytes) if c >= self.n: return False m = self._rawPublicKeyOp(c) checkBytes = numberToBytes(m) return checkBytes == paddedBytes def encrypt(self, bytes): """Encrypt the passed-in bytes. This performs PKCS1 encryption of the passed-in data. @type bytes: L{array.array} of unsigned bytes @param bytes: The value which will be encrypted. @rtype: L{array.array} of unsigned bytes. @return: A PKCS1 encryption of the passed-in data. """ paddedBytes = self._addPKCS1Padding(bytes, 2) m = bytesToNumber(paddedBytes) if m >= self.n: raise ValueError() c = self._rawPublicKeyOp(m) encBytes = numberToBytes(c) return encBytes def decrypt(self, encBytes): """Decrypt the passed-in bytes. This requires the key to have a private component. It performs PKCS1 decryption of the passed-in data. @type encBytes: L{array.array} of unsigned bytes @param encBytes: The value which will be decrypted. @rtype: L{array.array} of unsigned bytes or None. @return: A PKCS1 decryption of the passed-in data or None if the data is not properly formatted. """ if not self.hasPrivateKey(): raise AssertionError() c = bytesToNumber(encBytes) if c >= self.n: return None m = self._rawPrivateKeyOp(c) decBytes = numberToBytes(m) if (len(decBytes) != numBytes(self.n)-1): #Check first byte return None if decBytes[0] != 2: #Check second byte return None for x in range(len(decBytes)-1): #Scan through for zero separator if decBytes[x]== 0: break else: return None return decBytes[x+1:] #Return everything after the separator def _rawPrivateKeyOp(self, m): raise NotImplementedError() def _rawPublicKeyOp(self, c): raise NotImplementedError() def acceptsPassword(self): """Return True if the write() method accepts a password for use in encrypting the private key. @rtype: bool """ raise NotImplementedError() def write(self, password=None): """Return a string containing the key. @rtype: str @return: A string describing the key, in whichever format (PEM or XML) is native to the implementation. """ raise NotImplementedError() def writeXMLPublicKey(self, indent=''): """Return a string containing the key. @rtype: str @return: A string describing the public key, in XML format. """ return Python_RSAKey(self.n, self.e).write(indent) def generate(bits): """Generate a new key with the specified bit length. @rtype: L{tlslite.utils.RSAKey.RSAKey} """ raise NotImplementedError() generate = staticmethod(generate) # ************************************************************************** # Helper Functions for RSA Keys # ************************************************************************** def _addPKCS1SHA1Prefix(self, bytes): prefixBytes = createByteArraySequence(\ [48,33,48,9,6,5,43,14,3,2,26,5,0,4,20]) prefixedBytes = prefixBytes + bytes return prefixedBytes def _addPKCS1Padding(self, bytes, blockType): padLength = (numBytes(self.n) - (len(bytes)+3)) if blockType == 1: #Signature padding pad = [0xFF] * padLength elif blockType == 2: #Encryption padding pad = createByteArraySequence([]) while len(pad) < padLength: padBytes = getRandomBytes(padLength * 2) pad = [b for b in padBytes if b != 0] pad = pad[:padLength] else: raise AssertionError() #NOTE: To be proper, we should add [0,blockType]. However, #the zero is lost when the returned padding is converted #to a number, so we don't even bother with it. Also, #adding it would cause a misalignment in verify() padding = createByteArraySequence([blockType] + pad + [0]) paddedBytes = padding + bytes return paddedBytes gdata-2.0.18/src/gdata/tlslite/utils/AES.py0000750036466300116100000000167612156622363020423 0ustar afshareng00000000000000"""Abstract class for AES.""" class AES: def __init__(self, key, mode, IV, implementation): if len(key) not in (16, 24, 32): raise AssertionError() if mode != 2: raise AssertionError() if len(IV) != 16: raise AssertionError() self.isBlockCipher = True self.block_size = 16 self.implementation = implementation if len(key)==16: self.name = "aes128" elif len(key)==24: self.name = "aes192" elif len(key)==32: self.name = "aes256" else: raise AssertionError() #CBC-Mode encryption, returns ciphertext #WARNING: *MAY* modify the input as well def encrypt(self, plaintext): assert(len(plaintext) % 16 == 0) #CBC-Mode decryption, returns plaintext #WARNING: *MAY* modify the input as well def decrypt(self, ciphertext): assert(len(ciphertext) % 16 == 0)gdata-2.0.18/src/gdata/tlslite/utils/PyCrypto_AES.py0000750036466300116100000000110112156622363022253 0ustar afshareng00000000000000"""PyCrypto AES implementation.""" from cryptomath import * from AES import * if pycryptoLoaded: import Crypto.Cipher.AES def new(key, mode, IV): return PyCrypto_AES(key, mode, IV) class PyCrypto_AES(AES): def __init__(self, key, mode, IV): AES.__init__(self, key, mode, IV, "pycrypto") self.context = Crypto.Cipher.AES.new(key, mode, IV) def encrypt(self, plaintext): return self.context.encrypt(plaintext) def decrypt(self, ciphertext): return self.context.decrypt(ciphertext)gdata-2.0.18/src/gdata/tlslite/BaseDB.py0000750036466300116100000000666412156622363017735 0ustar afshareng00000000000000"""Base class for SharedKeyDB and VerifierDB.""" import anydbm import thread class BaseDB: def __init__(self, filename, type): self.type = type self.filename = filename if self.filename: self.db = None else: self.db = {} self.lock = thread.allocate_lock() def create(self): """Create a new on-disk database. @raise anydbm.error: If there's a problem creating the database. """ if self.filename: self.db = anydbm.open(self.filename, "n") #raises anydbm.error self.db["--Reserved--type"] = self.type self.db.sync() else: self.db = {} def open(self): """Open a pre-existing on-disk database. @raise anydbm.error: If there's a problem opening the database. @raise ValueError: If the database is not of the right type. """ if not self.filename: raise ValueError("Can only open on-disk databases") self.db = anydbm.open(self.filename, "w") #raises anydbm.error try: if self.db["--Reserved--type"] != self.type: raise ValueError("Not a %s database" % self.type) except KeyError: raise ValueError("Not a recognized database") def __getitem__(self, username): if self.db == None: raise AssertionError("DB not open") self.lock.acquire() try: valueStr = self.db[username] finally: self.lock.release() return self._getItem(username, valueStr) def __setitem__(self, username, value): if self.db == None: raise AssertionError("DB not open") valueStr = self._setItem(username, value) self.lock.acquire() try: self.db[username] = valueStr if self.filename: self.db.sync() finally: self.lock.release() def __delitem__(self, username): if self.db == None: raise AssertionError("DB not open") self.lock.acquire() try: del(self.db[username]) if self.filename: self.db.sync() finally: self.lock.release() def __contains__(self, username): """Check if the database contains the specified username. @type username: str @param username: The username to check for. @rtype: bool @return: True if the database contains the username, False otherwise. """ if self.db == None: raise AssertionError("DB not open") self.lock.acquire() try: return self.db.has_key(username) finally: self.lock.release() def check(self, username, param): value = self.__getitem__(username) return self._checkItem(value, username, param) def keys(self): """Return a list of usernames in the database. @rtype: list @return: The usernames in the database. """ if self.db == None: raise AssertionError("DB not open") self.lock.acquire() try: usernames = self.db.keys() finally: self.lock.release() usernames = [u for u in usernames if not u.startswith("--Reserved--")] return usernamesgdata-2.0.18/src/gdata/tlslite/X509CertChain.py0000750036466300116100000001531512156622363021074 0ustar afshareng00000000000000"""Class representing an X.509 certificate chain.""" from utils import cryptomath class X509CertChain: """This class represents a chain of X.509 certificates. @type x509List: list @ivar x509List: A list of L{tlslite.X509.X509} instances, starting with the end-entity certificate and with every subsequent certificate certifying the previous. """ def __init__(self, x509List=None): """Create a new X509CertChain. @type x509List: list @param x509List: A list of L{tlslite.X509.X509} instances, starting with the end-entity certificate and with every subsequent certificate certifying the previous. """ if x509List: self.x509List = x509List else: self.x509List = [] def getNumCerts(self): """Get the number of certificates in this chain. @rtype: int """ return len(self.x509List) def getEndEntityPublicKey(self): """Get the public key from the end-entity certificate. @rtype: L{tlslite.utils.RSAKey.RSAKey} """ if self.getNumCerts() == 0: raise AssertionError() return self.x509List[0].publicKey def getFingerprint(self): """Get the hex-encoded fingerprint of the end-entity certificate. @rtype: str @return: A hex-encoded fingerprint. """ if self.getNumCerts() == 0: raise AssertionError() return self.x509List[0].getFingerprint() def getCommonName(self): """Get the Subject's Common Name from the end-entity certificate. The cryptlib_py module must be installed in order to use this function. @rtype: str or None @return: The CN component of the certificate's subject DN, if present. """ if self.getNumCerts() == 0: raise AssertionError() return self.x509List[0].getCommonName() def validate(self, x509TrustList): """Check the validity of the certificate chain. This checks that every certificate in the chain validates with the subsequent one, until some certificate validates with (or is identical to) one of the passed-in root certificates. The cryptlib_py module must be installed in order to use this function. @type x509TrustList: list of L{tlslite.X509.X509} @param x509TrustList: A list of trusted root certificates. The certificate chain must extend to one of these certificates to be considered valid. """ import cryptlib_py c1 = None c2 = None lastC = None rootC = None try: rootFingerprints = [c.getFingerprint() for c in x509TrustList] #Check that every certificate in the chain validates with the #next one for cert1, cert2 in zip(self.x509List, self.x509List[1:]): #If we come upon a root certificate, we're done. if cert1.getFingerprint() in rootFingerprints: return True c1 = cryptlib_py.cryptImportCert(cert1.writeBytes(), cryptlib_py.CRYPT_UNUSED) c2 = cryptlib_py.cryptImportCert(cert2.writeBytes(), cryptlib_py.CRYPT_UNUSED) try: cryptlib_py.cryptCheckCert(c1, c2) except: return False cryptlib_py.cryptDestroyCert(c1) c1 = None cryptlib_py.cryptDestroyCert(c2) c2 = None #If the last certificate is one of the root certificates, we're #done. if self.x509List[-1].getFingerprint() in rootFingerprints: return True #Otherwise, find a root certificate that the last certificate #chains to, and validate them. lastC = cryptlib_py.cryptImportCert(self.x509List[-1].writeBytes(), cryptlib_py.CRYPT_UNUSED) for rootCert in x509TrustList: rootC = cryptlib_py.cryptImportCert(rootCert.writeBytes(), cryptlib_py.CRYPT_UNUSED) if self._checkChaining(lastC, rootC): try: cryptlib_py.cryptCheckCert(lastC, rootC) return True except: return False return False finally: if not (c1 is None): cryptlib_py.cryptDestroyCert(c1) if not (c2 is None): cryptlib_py.cryptDestroyCert(c2) if not (lastC is None): cryptlib_py.cryptDestroyCert(lastC) if not (rootC is None): cryptlib_py.cryptDestroyCert(rootC) def _checkChaining(self, lastC, rootC): import cryptlib_py import array def compareNames(name): try: length = cryptlib_py.cryptGetAttributeString(lastC, name, None) lastName = array.array('B', [0] * length) cryptlib_py.cryptGetAttributeString(lastC, name, lastName) lastName = lastName.tostring() except cryptlib_py.CryptException, e: if e[0] == cryptlib_py.CRYPT_ERROR_NOTFOUND: lastName = None try: length = cryptlib_py.cryptGetAttributeString(rootC, name, None) rootName = array.array('B', [0] * length) cryptlib_py.cryptGetAttributeString(rootC, name, rootName) rootName = rootName.tostring() except cryptlib_py.CryptException, e: if e[0] == cryptlib_py.CRYPT_ERROR_NOTFOUND: rootName = None return lastName == rootName cryptlib_py.cryptSetAttribute(lastC, cryptlib_py.CRYPT_CERTINFO_ISSUERNAME, cryptlib_py.CRYPT_UNUSED) if not compareNames(cryptlib_py.CRYPT_CERTINFO_COUNTRYNAME): return False if not compareNames(cryptlib_py.CRYPT_CERTINFO_LOCALITYNAME): return False if not compareNames(cryptlib_py.CRYPT_CERTINFO_ORGANIZATIONNAME): return False if not compareNames(cryptlib_py.CRYPT_CERTINFO_ORGANIZATIONALUNITNAME): return False if not compareNames(cryptlib_py.CRYPT_CERTINFO_COMMONNAME): return False return Truegdata-2.0.18/src/gdata/tlslite/TLSRecordLayer.py0000750036466300116100000012576412156622363021456 0ustar afshareng00000000000000"""Helper class for TLSConnection.""" from __future__ import generators from utils.compat import * from utils.cryptomath import * from utils.cipherfactory import createAES, createRC4, createTripleDES from utils.codec import * from errors import * from messages import * from mathtls import * from constants import * from utils.cryptomath import getRandomBytes from utils import hmac from FileObject import FileObject import sha import md5 import socket import errno import traceback class _ConnectionState: def __init__(self): self.macContext = None self.encContext = None self.seqnum = 0 def getSeqNumStr(self): w = Writer(8) w.add(self.seqnum, 8) seqnumStr = bytesToString(w.bytes) self.seqnum += 1 return seqnumStr class TLSRecordLayer: """ This class handles data transmission for a TLS connection. Its only subclass is L{tlslite.TLSConnection.TLSConnection}. We've separated the code in this class from TLSConnection to make things more readable. @type sock: socket.socket @ivar sock: The underlying socket object. @type session: L{tlslite.Session.Session} @ivar session: The session corresponding to this connection. Due to TLS session resumption, multiple connections can correspond to the same underlying session. @type version: tuple @ivar version: The TLS version being used for this connection. (3,0) means SSL 3.0, and (3,1) means TLS 1.0. @type closed: bool @ivar closed: If this connection is closed. @type resumed: bool @ivar resumed: If this connection is based on a resumed session. @type allegedSharedKeyUsername: str or None @ivar allegedSharedKeyUsername: This is set to the shared-key username asserted by the client, whether the handshake succeeded or not. If the handshake fails, this can be inspected to determine if a guessing attack is in progress against a particular user account. @type allegedSrpUsername: str or None @ivar allegedSrpUsername: This is set to the SRP username asserted by the client, whether the handshake succeeded or not. If the handshake fails, this can be inspected to determine if a guessing attack is in progress against a particular user account. @type closeSocket: bool @ivar closeSocket: If the socket should be closed when the connection is closed (writable). If you set this to True, TLS Lite will assume the responsibility of closing the socket when the TLS Connection is shutdown (either through an error or through the user calling close()). The default is False. @type ignoreAbruptClose: bool @ivar ignoreAbruptClose: If an abrupt close of the socket should raise an error (writable). If you set this to True, TLS Lite will not raise a L{tlslite.errors.TLSAbruptCloseError} exception if the underlying socket is unexpectedly closed. Such an unexpected closure could be caused by an attacker. However, it also occurs with some incorrect TLS implementations. You should set this to True only if you're not worried about an attacker truncating the connection, and only if necessary to avoid spurious errors. The default is False. @sort: __init__, read, readAsync, write, writeAsync, close, closeAsync, getCipherImplementation, getCipherName """ def __init__(self, sock): self.sock = sock #My session object (Session instance; read-only) self.session = None #Am I a client or server? self._client = None #Buffers for processing messages self._handshakeBuffer = [] self._readBuffer = "" #Handshake digests self._handshake_md5 = md5.md5() self._handshake_sha = sha.sha() #TLS Protocol Version self.version = (0,0) #read-only self._versionCheck = False #Once we choose a version, this is True #Current and Pending connection states self._writeState = _ConnectionState() self._readState = _ConnectionState() self._pendingWriteState = _ConnectionState() self._pendingReadState = _ConnectionState() #Is the connection open? self.closed = True #read-only self._refCount = 0 #Used to trigger closure #Is this a resumed (or shared-key) session? self.resumed = False #read-only #What username did the client claim in his handshake? self.allegedSharedKeyUsername = None self.allegedSrpUsername = None #On a call to close(), do we close the socket? (writeable) self.closeSocket = False #If the socket is abruptly closed, do we ignore it #and pretend the connection was shut down properly? (writeable) self.ignoreAbruptClose = False #Fault we will induce, for testing purposes self.fault = None #********************************************************* # Public Functions START #********************************************************* def read(self, max=None, min=1): """Read some data from the TLS connection. This function will block until at least 'min' bytes are available (or the connection is closed). If an exception is raised, the connection will have been automatically closed. @type max: int @param max: The maximum number of bytes to return. @type min: int @param min: The minimum number of bytes to return @rtype: str @return: A string of no more than 'max' bytes, and no fewer than 'min' (unless the connection has been closed, in which case fewer than 'min' bytes may be returned). @raise socket.error: If a socket error occurs. @raise tlslite.errors.TLSAbruptCloseError: If the socket is closed without a preceding alert. @raise tlslite.errors.TLSAlert: If a TLS alert is signalled. """ for result in self.readAsync(max, min): pass return result def readAsync(self, max=None, min=1): """Start a read operation on the TLS connection. This function returns a generator which behaves similarly to read(). Successive invocations of the generator will return 0 if it is waiting to read from the socket, 1 if it is waiting to write to the socket, or a string if the read operation has completed. @rtype: iterable @return: A generator; see above for details. """ try: while len(self._readBuffer)= len(s): break if endIndex > len(s): endIndex = len(s) block = stringToBytes(s[startIndex : endIndex]) applicationData = ApplicationData().create(block) for result in self._sendMsg(applicationData, skipEmptyFrag): yield result skipEmptyFrag = True #only send an empy fragment on 1st message index += 1 except: self._shutdown(False) raise def close(self): """Close the TLS connection. This function will block until it has exchanged close_notify alerts with the other party. After doing so, it will shut down the TLS connection. Further attempts to read through this connection will return "". Further attempts to write through this connection will raise ValueError. If makefile() has been called on this connection, the connection will be not be closed until the connection object and all file objects have been closed. Even if an exception is raised, the connection will have been closed. @raise socket.error: If a socket error occurs. @raise tlslite.errors.TLSAbruptCloseError: If the socket is closed without a preceding alert. @raise tlslite.errors.TLSAlert: If a TLS alert is signalled. """ if not self.closed: for result in self._decrefAsync(): pass def closeAsync(self): """Start a close operation on the TLS connection. This function returns a generator which behaves similarly to close(). Successive invocations of the generator will return 0 if it is waiting to read from the socket, 1 if it is waiting to write to the socket, or will raise StopIteration if the close operation has completed. @rtype: iterable @return: A generator; see above for details. """ if not self.closed: for result in self._decrefAsync(): yield result def _decrefAsync(self): self._refCount -= 1 if self._refCount == 0 and not self.closed: try: for result in self._sendMsg(Alert().create(\ AlertDescription.close_notify, AlertLevel.warning)): yield result alert = None while not alert: for result in self._getMsg((ContentType.alert, \ ContentType.application_data)): if result in (0,1): yield result if result.contentType == ContentType.alert: alert = result if alert.description == AlertDescription.close_notify: self._shutdown(True) else: raise TLSRemoteAlert(alert) except (socket.error, TLSAbruptCloseError): #If the other side closes the socket, that's okay self._shutdown(True) except: self._shutdown(False) raise def getCipherName(self): """Get the name of the cipher used with this connection. @rtype: str @return: The name of the cipher used with this connection. Either 'aes128', 'aes256', 'rc4', or '3des'. """ if not self._writeState.encContext: return None return self._writeState.encContext.name def getCipherImplementation(self): """Get the name of the cipher implementation used with this connection. @rtype: str @return: The name of the cipher implementation used with this connection. Either 'python', 'cryptlib', 'openssl', or 'pycrypto'. """ if not self._writeState.encContext: return None return self._writeState.encContext.implementation #Emulate a socket, somewhat - def send(self, s): """Send data to the TLS connection (socket emulation). @raise socket.error: If a socket error occurs. """ self.write(s) return len(s) def sendall(self, s): """Send data to the TLS connection (socket emulation). @raise socket.error: If a socket error occurs. """ self.write(s) def recv(self, bufsize): """Get some data from the TLS connection (socket emulation). @raise socket.error: If a socket error occurs. @raise tlslite.errors.TLSAbruptCloseError: If the socket is closed without a preceding alert. @raise tlslite.errors.TLSAlert: If a TLS alert is signalled. """ return self.read(bufsize) def makefile(self, mode='r', bufsize=-1): """Create a file object for the TLS connection (socket emulation). @rtype: L{tlslite.FileObject.FileObject} """ self._refCount += 1 return FileObject(self, mode, bufsize) def getsockname(self): """Return the socket's own address (socket emulation).""" return self.sock.getsockname() def getpeername(self): """Return the remote address to which the socket is connected (socket emulation).""" return self.sock.getpeername() def settimeout(self, value): """Set a timeout on blocking socket operations (socket emulation).""" return self.sock.settimeout(value) def gettimeout(self): """Return the timeout associated with socket operations (socket emulation).""" return self.sock.gettimeout() def setsockopt(self, level, optname, value): """Set the value of the given socket option (socket emulation).""" return self.sock.setsockopt(level, optname, value) #********************************************************* # Public Functions END #********************************************************* def _shutdown(self, resumable): self._writeState = _ConnectionState() self._readState = _ConnectionState() #Don't do this: self._readBuffer = "" self.version = (0,0) self._versionCheck = False self.closed = True if self.closeSocket: self.sock.close() #Even if resumable is False, we'll never toggle this on if not resumable and self.session: self.session.resumable = False def _sendError(self, alertDescription, errorStr=None): alert = Alert().create(alertDescription, AlertLevel.fatal) for result in self._sendMsg(alert): yield result self._shutdown(False) raise TLSLocalAlert(alert, errorStr) def _sendMsgs(self, msgs): skipEmptyFrag = False for msg in msgs: for result in self._sendMsg(msg, skipEmptyFrag): yield result skipEmptyFrag = True def _sendMsg(self, msg, skipEmptyFrag=False): bytes = msg.write() contentType = msg.contentType #Whenever we're connected and asked to send a message, #we first send an empty Application Data message. This prevents #an attacker from launching a chosen-plaintext attack based on #knowing the next IV. if not self.closed and not skipEmptyFrag and self.version == (3,1): if self._writeState.encContext: if self._writeState.encContext.isBlockCipher: for result in self._sendMsg(ApplicationData(), skipEmptyFrag=True): yield result #Update handshake hashes if contentType == ContentType.handshake: bytesStr = bytesToString(bytes) self._handshake_md5.update(bytesStr) self._handshake_sha.update(bytesStr) #Calculate MAC if self._writeState.macContext: seqnumStr = self._writeState.getSeqNumStr() bytesStr = bytesToString(bytes) mac = self._writeState.macContext.copy() mac.update(seqnumStr) mac.update(chr(contentType)) if self.version == (3,0): mac.update( chr( int(len(bytes)/256) ) ) mac.update( chr( int(len(bytes)%256) ) ) elif self.version in ((3,1), (3,2)): mac.update(chr(self.version[0])) mac.update(chr(self.version[1])) mac.update( chr( int(len(bytes)/256) ) ) mac.update( chr( int(len(bytes)%256) ) ) else: raise AssertionError() mac.update(bytesStr) macString = mac.digest() macBytes = stringToBytes(macString) if self.fault == Fault.badMAC: macBytes[0] = (macBytes[0]+1) % 256 #Encrypt for Block or Stream Cipher if self._writeState.encContext: #Add padding and encrypt (for Block Cipher): if self._writeState.encContext.isBlockCipher: #Add TLS 1.1 fixed block if self.version == (3,2): bytes = self.fixedIVBlock + bytes #Add padding: bytes = bytes + (macBytes + paddingBytes) currentLength = len(bytes) + len(macBytes) + 1 blockLength = self._writeState.encContext.block_size paddingLength = blockLength-(currentLength % blockLength) paddingBytes = createByteArraySequence([paddingLength] * \ (paddingLength+1)) if self.fault == Fault.badPadding: paddingBytes[0] = (paddingBytes[0]+1) % 256 endBytes = concatArrays(macBytes, paddingBytes) bytes = concatArrays(bytes, endBytes) #Encrypt plaintext = stringToBytes(bytes) ciphertext = self._writeState.encContext.encrypt(plaintext) bytes = stringToBytes(ciphertext) #Encrypt (for Stream Cipher) else: bytes = concatArrays(bytes, macBytes) plaintext = bytesToString(bytes) ciphertext = self._writeState.encContext.encrypt(plaintext) bytes = stringToBytes(ciphertext) #Add record header and send r = RecordHeader3().create(self.version, contentType, len(bytes)) s = bytesToString(concatArrays(r.write(), bytes)) while 1: try: bytesSent = self.sock.send(s) #Might raise socket.error except socket.error, why: if why[0] == errno.EWOULDBLOCK: yield 1 continue else: raise if bytesSent == len(s): return s = s[bytesSent:] yield 1 def _getMsg(self, expectedType, secondaryType=None, constructorType=None): try: if not isinstance(expectedType, tuple): expectedType = (expectedType,) #Spin in a loop, until we've got a non-empty record of a type we #expect. The loop will be repeated if: # - we receive a renegotiation attempt; we send no_renegotiation, # then try again # - we receive an empty application-data fragment; we try again while 1: for result in self._getNextRecord(): if result in (0,1): yield result recordHeader, p = result #If this is an empty application-data fragment, try again if recordHeader.type == ContentType.application_data: if p.index == len(p.bytes): continue #If we received an unexpected record type... if recordHeader.type not in expectedType: #If we received an alert... if recordHeader.type == ContentType.alert: alert = Alert().parse(p) #We either received a fatal error, a warning, or a #close_notify. In any case, we're going to close the #connection. In the latter two cases we respond with #a close_notify, but ignore any socket errors, since #the other side might have already closed the socket. if alert.level == AlertLevel.warning or \ alert.description == AlertDescription.close_notify: #If the sendMsg() call fails because the socket has #already been closed, we will be forgiving and not #report the error nor invalidate the "resumability" #of the session. try: alertMsg = Alert() alertMsg.create(AlertDescription.close_notify, AlertLevel.warning) for result in self._sendMsg(alertMsg): yield result except socket.error: pass if alert.description == \ AlertDescription.close_notify: self._shutdown(True) elif alert.level == AlertLevel.warning: self._shutdown(False) else: #Fatal alert: self._shutdown(False) #Raise the alert as an exception raise TLSRemoteAlert(alert) #If we received a renegotiation attempt... if recordHeader.type == ContentType.handshake: subType = p.get(1) reneg = False if self._client: if subType == HandshakeType.hello_request: reneg = True else: if subType == HandshakeType.client_hello: reneg = True #Send no_renegotiation, then try again if reneg: alertMsg = Alert() alertMsg.create(AlertDescription.no_renegotiation, AlertLevel.warning) for result in self._sendMsg(alertMsg): yield result continue #Otherwise: this is an unexpected record, but neither an #alert nor renegotiation for result in self._sendError(\ AlertDescription.unexpected_message, "received type=%d" % recordHeader.type): yield result break #Parse based on content_type if recordHeader.type == ContentType.change_cipher_spec: yield ChangeCipherSpec().parse(p) elif recordHeader.type == ContentType.alert: yield Alert().parse(p) elif recordHeader.type == ContentType.application_data: yield ApplicationData().parse(p) elif recordHeader.type == ContentType.handshake: #Convert secondaryType to tuple, if it isn't already if not isinstance(secondaryType, tuple): secondaryType = (secondaryType,) #If it's a handshake message, check handshake header if recordHeader.ssl2: subType = p.get(1) if subType != HandshakeType.client_hello: for result in self._sendError(\ AlertDescription.unexpected_message, "Can only handle SSLv2 ClientHello messages"): yield result if HandshakeType.client_hello not in secondaryType: for result in self._sendError(\ AlertDescription.unexpected_message): yield result subType = HandshakeType.client_hello else: subType = p.get(1) if subType not in secondaryType: for result in self._sendError(\ AlertDescription.unexpected_message, "Expecting %s, got %s" % (str(secondaryType), subType)): yield result #Update handshake hashes sToHash = bytesToString(p.bytes) self._handshake_md5.update(sToHash) self._handshake_sha.update(sToHash) #Parse based on handshake type if subType == HandshakeType.client_hello: yield ClientHello(recordHeader.ssl2).parse(p) elif subType == HandshakeType.server_hello: yield ServerHello().parse(p) elif subType == HandshakeType.certificate: yield Certificate(constructorType).parse(p) elif subType == HandshakeType.certificate_request: yield CertificateRequest().parse(p) elif subType == HandshakeType.certificate_verify: yield CertificateVerify().parse(p) elif subType == HandshakeType.server_key_exchange: yield ServerKeyExchange(constructorType).parse(p) elif subType == HandshakeType.server_hello_done: yield ServerHelloDone().parse(p) elif subType == HandshakeType.client_key_exchange: yield ClientKeyExchange(constructorType, \ self.version).parse(p) elif subType == HandshakeType.finished: yield Finished(self.version).parse(p) else: raise AssertionError() #If an exception was raised by a Parser or Message instance: except SyntaxError, e: for result in self._sendError(AlertDescription.decode_error, formatExceptionTrace(e)): yield result #Returns next record or next handshake message def _getNextRecord(self): #If there's a handshake message waiting, return it if self._handshakeBuffer: recordHeader, bytes = self._handshakeBuffer[0] self._handshakeBuffer = self._handshakeBuffer[1:] yield (recordHeader, Parser(bytes)) return #Otherwise... #Read the next record header bytes = createByteArraySequence([]) recordHeaderLength = 1 ssl2 = False while 1: try: s = self.sock.recv(recordHeaderLength-len(bytes)) except socket.error, why: if why[0] == errno.EWOULDBLOCK: yield 0 continue else: raise #If the connection was abruptly closed, raise an error if len(s)==0: raise TLSAbruptCloseError() bytes += stringToBytes(s) if len(bytes)==1: if bytes[0] in ContentType.all: ssl2 = False recordHeaderLength = 5 elif bytes[0] == 128: ssl2 = True recordHeaderLength = 2 else: raise SyntaxError() if len(bytes) == recordHeaderLength: break #Parse the record header if ssl2: r = RecordHeader2().parse(Parser(bytes)) else: r = RecordHeader3().parse(Parser(bytes)) #Check the record header fields if r.length > 18432: for result in self._sendError(AlertDescription.record_overflow): yield result #Read the record contents bytes = createByteArraySequence([]) while 1: try: s = self.sock.recv(r.length - len(bytes)) except socket.error, why: if why[0] == errno.EWOULDBLOCK: yield 0 continue else: raise #If the connection is closed, raise a socket error if len(s)==0: raise TLSAbruptCloseError() bytes += stringToBytes(s) if len(bytes) == r.length: break #Check the record header fields (2) #We do this after reading the contents from the socket, so that #if there's an error, we at least don't leave extra bytes in the #socket.. # # THIS CHECK HAS NO SECURITY RELEVANCE (?), BUT COULD HURT INTEROP. # SO WE LEAVE IT OUT FOR NOW. # #if self._versionCheck and r.version != self.version: # for result in self._sendError(AlertDescription.protocol_version, # "Version in header field: %s, should be %s" % (str(r.version), # str(self.version))): # yield result #Decrypt the record for result in self._decryptRecord(r.type, bytes): if result in (0,1): yield result else: break bytes = result p = Parser(bytes) #If it doesn't contain handshake messages, we can just return it if r.type != ContentType.handshake: yield (r, p) #If it's an SSLv2 ClientHello, we can return it as well elif r.ssl2: yield (r, p) else: #Otherwise, we loop through and add the handshake messages to the #handshake buffer while 1: if p.index == len(bytes): #If we're at the end if not self._handshakeBuffer: for result in self._sendError(\ AlertDescription.decode_error, \ "Received empty handshake record"): yield result break #There needs to be at least 4 bytes to get a header if p.index+4 > len(bytes): for result in self._sendError(\ AlertDescription.decode_error, "A record has a partial handshake message (1)"): yield result p.get(1) # skip handshake type msgLength = p.get(3) if p.index+msgLength > len(bytes): for result in self._sendError(\ AlertDescription.decode_error, "A record has a partial handshake message (2)"): yield result handshakePair = (r, bytes[p.index-4 : p.index+msgLength]) self._handshakeBuffer.append(handshakePair) p.index += msgLength #We've moved at least one handshake message into the #handshakeBuffer, return the first one recordHeader, bytes = self._handshakeBuffer[0] self._handshakeBuffer = self._handshakeBuffer[1:] yield (recordHeader, Parser(bytes)) def _decryptRecord(self, recordType, bytes): if self._readState.encContext: #Decrypt if it's a block cipher if self._readState.encContext.isBlockCipher: blockLength = self._readState.encContext.block_size if len(bytes) % blockLength != 0: for result in self._sendError(\ AlertDescription.decryption_failed, "Encrypted data not a multiple of blocksize"): yield result ciphertext = bytesToString(bytes) plaintext = self._readState.encContext.decrypt(ciphertext) if self.version == (3,2): #For TLS 1.1, remove explicit IV plaintext = plaintext[self._readState.encContext.block_size : ] bytes = stringToBytes(plaintext) #Check padding paddingGood = True paddingLength = bytes[-1] if (paddingLength+1) > len(bytes): paddingGood=False totalPaddingLength = 0 else: if self.version == (3,0): totalPaddingLength = paddingLength+1 elif self.version in ((3,1), (3,2)): totalPaddingLength = paddingLength+1 paddingBytes = bytes[-totalPaddingLength:-1] for byte in paddingBytes: if byte != paddingLength: paddingGood = False totalPaddingLength = 0 else: raise AssertionError() #Decrypt if it's a stream cipher else: paddingGood = True ciphertext = bytesToString(bytes) plaintext = self._readState.encContext.decrypt(ciphertext) bytes = stringToBytes(plaintext) totalPaddingLength = 0 #Check MAC macGood = True macLength = self._readState.macContext.digest_size endLength = macLength + totalPaddingLength if endLength > len(bytes): macGood = False else: #Read MAC startIndex = len(bytes) - endLength endIndex = startIndex + macLength checkBytes = bytes[startIndex : endIndex] #Calculate MAC seqnumStr = self._readState.getSeqNumStr() bytes = bytes[:-endLength] bytesStr = bytesToString(bytes) mac = self._readState.macContext.copy() mac.update(seqnumStr) mac.update(chr(recordType)) if self.version == (3,0): mac.update( chr( int(len(bytes)/256) ) ) mac.update( chr( int(len(bytes)%256) ) ) elif self.version in ((3,1), (3,2)): mac.update(chr(self.version[0])) mac.update(chr(self.version[1])) mac.update( chr( int(len(bytes)/256) ) ) mac.update( chr( int(len(bytes)%256) ) ) else: raise AssertionError() mac.update(bytesStr) macString = mac.digest() macBytes = stringToBytes(macString) #Compare MACs if macBytes != checkBytes: macGood = False if not (paddingGood and macGood): for result in self._sendError(AlertDescription.bad_record_mac, "MAC failure (or padding failure)"): yield result yield bytes def _handshakeStart(self, client): self._client = client self._handshake_md5 = md5.md5() self._handshake_sha = sha.sha() self._handshakeBuffer = [] self.allegedSharedKeyUsername = None self.allegedSrpUsername = None self._refCount = 1 def _handshakeDone(self, resumed): self.resumed = resumed self.closed = False def _calcPendingStates(self, clientRandom, serverRandom, implementations): if self.session.cipherSuite in CipherSuite.aes128Suites: macLength = 20 keyLength = 16 ivLength = 16 createCipherFunc = createAES elif self.session.cipherSuite in CipherSuite.aes256Suites: macLength = 20 keyLength = 32 ivLength = 16 createCipherFunc = createAES elif self.session.cipherSuite in CipherSuite.rc4Suites: macLength = 20 keyLength = 16 ivLength = 0 createCipherFunc = createRC4 elif self.session.cipherSuite in CipherSuite.tripleDESSuites: macLength = 20 keyLength = 24 ivLength = 8 createCipherFunc = createTripleDES else: raise AssertionError() if self.version == (3,0): createMACFunc = MAC_SSL elif self.version in ((3,1), (3,2)): createMACFunc = hmac.HMAC outputLength = (macLength*2) + (keyLength*2) + (ivLength*2) #Calculate Keying Material from Master Secret if self.version == (3,0): keyBlock = PRF_SSL(self.session.masterSecret, concatArrays(serverRandom, clientRandom), outputLength) elif self.version in ((3,1), (3,2)): keyBlock = PRF(self.session.masterSecret, "key expansion", concatArrays(serverRandom,clientRandom), outputLength) else: raise AssertionError() #Slice up Keying Material clientPendingState = _ConnectionState() serverPendingState = _ConnectionState() p = Parser(keyBlock) clientMACBlock = bytesToString(p.getFixBytes(macLength)) serverMACBlock = bytesToString(p.getFixBytes(macLength)) clientKeyBlock = bytesToString(p.getFixBytes(keyLength)) serverKeyBlock = bytesToString(p.getFixBytes(keyLength)) clientIVBlock = bytesToString(p.getFixBytes(ivLength)) serverIVBlock = bytesToString(p.getFixBytes(ivLength)) clientPendingState.macContext = createMACFunc(clientMACBlock, digestmod=sha) serverPendingState.macContext = createMACFunc(serverMACBlock, digestmod=sha) clientPendingState.encContext = createCipherFunc(clientKeyBlock, clientIVBlock, implementations) serverPendingState.encContext = createCipherFunc(serverKeyBlock, serverIVBlock, implementations) #Assign new connection states to pending states if self._client: self._pendingWriteState = clientPendingState self._pendingReadState = serverPendingState else: self._pendingWriteState = serverPendingState self._pendingReadState = clientPendingState if self.version == (3,2) and ivLength: #Choose fixedIVBlock for TLS 1.1 (this is encrypted with the CBC #residue to create the IV for each sent block) self.fixedIVBlock = getRandomBytes(ivLength) def _changeWriteState(self): self._writeState = self._pendingWriteState self._pendingWriteState = _ConnectionState() def _changeReadState(self): self._readState = self._pendingReadState self._pendingReadState = _ConnectionState() def _sendFinished(self): #Send ChangeCipherSpec for result in self._sendMsg(ChangeCipherSpec()): yield result #Switch to pending write state self._changeWriteState() #Calculate verification data verifyData = self._calcFinished(True) if self.fault == Fault.badFinished: verifyData[0] = (verifyData[0]+1)%256 #Send Finished message under new state finished = Finished(self.version).create(verifyData) for result in self._sendMsg(finished): yield result def _getFinished(self): #Get and check ChangeCipherSpec for result in self._getMsg(ContentType.change_cipher_spec): if result in (0,1): yield result changeCipherSpec = result if changeCipherSpec.type != 1: for result in self._sendError(AlertDescription.illegal_parameter, "ChangeCipherSpec type incorrect"): yield result #Switch to pending read state self._changeReadState() #Calculate verification data verifyData = self._calcFinished(False) #Get and check Finished message under new state for result in self._getMsg(ContentType.handshake, HandshakeType.finished): if result in (0,1): yield result finished = result if finished.verify_data != verifyData: for result in self._sendError(AlertDescription.decrypt_error, "Finished message is incorrect"): yield result def _calcFinished(self, send=True): if self.version == (3,0): if (self._client and send) or (not self._client and not send): senderStr = "\x43\x4C\x4E\x54" else: senderStr = "\x53\x52\x56\x52" verifyData = self._calcSSLHandshakeHash(self.session.masterSecret, senderStr) return verifyData elif self.version in ((3,1), (3,2)): if (self._client and send) or (not self._client and not send): label = "client finished" else: label = "server finished" handshakeHashes = stringToBytes(self._handshake_md5.digest() + \ self._handshake_sha.digest()) verifyData = PRF(self.session.masterSecret, label, handshakeHashes, 12) return verifyData else: raise AssertionError() #Used for Finished messages and CertificateVerify messages in SSL v3 def _calcSSLHandshakeHash(self, masterSecret, label): masterSecretStr = bytesToString(masterSecret) imac_md5 = self._handshake_md5.copy() imac_sha = self._handshake_sha.copy() imac_md5.update(label + masterSecretStr + '\x36'*48) imac_sha.update(label + masterSecretStr + '\x36'*40) md5Str = md5.md5(masterSecretStr + ('\x5c'*48) + \ imac_md5.digest()).digest() shaStr = sha.sha(masterSecretStr + ('\x5c'*40) + \ imac_sha.digest()).digest() return stringToBytes(md5Str + shaStr) gdata-2.0.18/src/gdata/tlslite/errors.py0000750036466300116100000001324312156622363020160 0ustar afshareng00000000000000"""Exception classes. @sort: TLSError, TLSAbruptCloseError, TLSAlert, TLSLocalAlert, TLSRemoteAlert, TLSAuthenticationError, TLSNoAuthenticationError, TLSAuthenticationTypeError, TLSFingerprintError, TLSAuthorizationError, TLSValidationError, TLSFaultError """ from constants import AlertDescription, AlertLevel class TLSError(Exception): """Base class for all TLS Lite exceptions.""" pass class TLSAbruptCloseError(TLSError): """The socket was closed without a proper TLS shutdown. The TLS specification mandates that an alert of some sort must be sent before the underlying socket is closed. If the socket is closed without this, it could signify that an attacker is trying to truncate the connection. It could also signify a misbehaving TLS implementation, or a random network failure. """ pass class TLSAlert(TLSError): """A TLS alert has been signalled.""" pass _descriptionStr = {\ AlertDescription.close_notify: "close_notify",\ AlertDescription.unexpected_message: "unexpected_message",\ AlertDescription.bad_record_mac: "bad_record_mac",\ AlertDescription.decryption_failed: "decryption_failed",\ AlertDescription.record_overflow: "record_overflow",\ AlertDescription.decompression_failure: "decompression_failure",\ AlertDescription.handshake_failure: "handshake_failure",\ AlertDescription.no_certificate: "no certificate",\ AlertDescription.bad_certificate: "bad_certificate",\ AlertDescription.unsupported_certificate: "unsupported_certificate",\ AlertDescription.certificate_revoked: "certificate_revoked",\ AlertDescription.certificate_expired: "certificate_expired",\ AlertDescription.certificate_unknown: "certificate_unknown",\ AlertDescription.illegal_parameter: "illegal_parameter",\ AlertDescription.unknown_ca: "unknown_ca",\ AlertDescription.access_denied: "access_denied",\ AlertDescription.decode_error: "decode_error",\ AlertDescription.decrypt_error: "decrypt_error",\ AlertDescription.export_restriction: "export_restriction",\ AlertDescription.protocol_version: "protocol_version",\ AlertDescription.insufficient_security: "insufficient_security",\ AlertDescription.internal_error: "internal_error",\ AlertDescription.user_canceled: "user_canceled",\ AlertDescription.no_renegotiation: "no_renegotiation",\ AlertDescription.unknown_srp_username: "unknown_srp_username",\ AlertDescription.missing_srp_username: "missing_srp_username"} class TLSLocalAlert(TLSAlert): """A TLS alert has been signalled by the local implementation. @type description: int @ivar description: Set to one of the constants in L{tlslite.constants.AlertDescription} @type level: int @ivar level: Set to one of the constants in L{tlslite.constants.AlertLevel} @type message: str @ivar message: Description of what went wrong. """ def __init__(self, alert, message=None): self.description = alert.description self.level = alert.level self.message = message def __str__(self): alertStr = TLSAlert._descriptionStr.get(self.description) if alertStr == None: alertStr = str(self.description) if self.message: return alertStr + ": " + self.message else: return alertStr class TLSRemoteAlert(TLSAlert): """A TLS alert has been signalled by the remote implementation. @type description: int @ivar description: Set to one of the constants in L{tlslite.constants.AlertDescription} @type level: int @ivar level: Set to one of the constants in L{tlslite.constants.AlertLevel} """ def __init__(self, alert): self.description = alert.description self.level = alert.level def __str__(self): alertStr = TLSAlert._descriptionStr.get(self.description) if alertStr == None: alertStr = str(self.description) return alertStr class TLSAuthenticationError(TLSError): """The handshake succeeded, but the other party's authentication was inadequate. This exception will only be raised when a L{tlslite.Checker.Checker} has been passed to a handshake function. The Checker will be invoked once the handshake completes, and if the Checker objects to how the other party authenticated, a subclass of this exception will be raised. """ pass class TLSNoAuthenticationError(TLSAuthenticationError): """The Checker was expecting the other party to authenticate with a certificate chain, but this did not occur.""" pass class TLSAuthenticationTypeError(TLSAuthenticationError): """The Checker was expecting the other party to authenticate with a different type of certificate chain.""" pass class TLSFingerprintError(TLSAuthenticationError): """The Checker was expecting the other party to authenticate with a certificate chain that matches a different fingerprint.""" pass class TLSAuthorizationError(TLSAuthenticationError): """The Checker was expecting the other party to authenticate with a certificate chain that has a different authorization.""" pass class TLSValidationError(TLSAuthenticationError): """The Checker has determined that the other party's certificate chain is invalid.""" pass class TLSFaultError(TLSError): """The other party responded incorrectly to an induced fault. This exception will only occur during fault testing, when a TLSConnection's fault variable is set to induce some sort of faulty behavior, and the other party doesn't respond appropriately. """ pass gdata-2.0.18/src/gdata/tlslite/Checker.py0000750036466300116100000001423512156622363020212 0ustar afshareng00000000000000"""Class for post-handshake certificate checking.""" from utils.cryptomath import hashAndBase64 from X509 import X509 from X509CertChain import X509CertChain from errors import * class Checker: """This class is passed to a handshake function to check the other party's certificate chain. If a handshake function completes successfully, but the Checker judges the other party's certificate chain to be missing or inadequate, a subclass of L{tlslite.errors.TLSAuthenticationError} will be raised. Currently, the Checker can check either an X.509 or a cryptoID chain (for the latter, cryptoIDlib must be installed). """ def __init__(self, cryptoID=None, protocol=None, x509Fingerprint=None, x509TrustList=None, x509CommonName=None, checkResumedSession=False): """Create a new Checker instance. You must pass in one of these argument combinations: - cryptoID[, protocol] (requires cryptoIDlib) - x509Fingerprint - x509TrustList[, x509CommonName] (requires cryptlib_py) @type cryptoID: str @param cryptoID: A cryptoID which the other party's certificate chain must match. The cryptoIDlib module must be installed. Mutually exclusive with all of the 'x509...' arguments. @type protocol: str @param protocol: A cryptoID protocol URI which the other party's certificate chain must match. Requires the 'cryptoID' argument. @type x509Fingerprint: str @param x509Fingerprint: A hex-encoded X.509 end-entity fingerprint which the other party's end-entity certificate must match. Mutually exclusive with the 'cryptoID' and 'x509TrustList' arguments. @type x509TrustList: list of L{tlslite.X509.X509} @param x509TrustList: A list of trusted root certificates. The other party must present a certificate chain which extends to one of these root certificates. The cryptlib_py module must be installed. Mutually exclusive with the 'cryptoID' and 'x509Fingerprint' arguments. @type x509CommonName: str @param x509CommonName: The end-entity certificate's 'CN' field must match this value. For a web server, this is typically a server name such as 'www.amazon.com'. Mutually exclusive with the 'cryptoID' and 'x509Fingerprint' arguments. Requires the 'x509TrustList' argument. @type checkResumedSession: bool @param checkResumedSession: If resumed sessions should be checked. This defaults to False, on the theory that if the session was checked once, we don't need to bother re-checking it. """ if cryptoID and (x509Fingerprint or x509TrustList): raise ValueError() if x509Fingerprint and x509TrustList: raise ValueError() if x509CommonName and not x509TrustList: raise ValueError() if protocol and not cryptoID: raise ValueError() if cryptoID: import cryptoIDlib #So we raise an error here if x509TrustList: import cryptlib_py #So we raise an error here self.cryptoID = cryptoID self.protocol = protocol self.x509Fingerprint = x509Fingerprint self.x509TrustList = x509TrustList self.x509CommonName = x509CommonName self.checkResumedSession = checkResumedSession def __call__(self, connection): """Check a TLSConnection. When a Checker is passed to a handshake function, this will be called at the end of the function. @type connection: L{tlslite.TLSConnection.TLSConnection} @param connection: The TLSConnection to examine. @raise tlslite.errors.TLSAuthenticationError: If the other party's certificate chain is missing or bad. """ if not self.checkResumedSession and connection.resumed: return if self.cryptoID or self.x509Fingerprint or self.x509TrustList: if connection._client: chain = connection.session.serverCertChain else: chain = connection.session.clientCertChain if self.x509Fingerprint or self.x509TrustList: if isinstance(chain, X509CertChain): if self.x509Fingerprint: if chain.getFingerprint() != self.x509Fingerprint: raise TLSFingerprintError(\ "X.509 fingerprint mismatch: %s, %s" % \ (chain.getFingerprint(), self.x509Fingerprint)) else: #self.x509TrustList if not chain.validate(self.x509TrustList): raise TLSValidationError("X.509 validation failure") if self.x509CommonName and \ (chain.getCommonName() != self.x509CommonName): raise TLSAuthorizationError(\ "X.509 Common Name mismatch: %s, %s" % \ (chain.getCommonName(), self.x509CommonName)) elif chain: raise TLSAuthenticationTypeError() else: raise TLSNoAuthenticationError() elif self.cryptoID: import cryptoIDlib.CertChain if isinstance(chain, cryptoIDlib.CertChain.CertChain): if chain.cryptoID != self.cryptoID: raise TLSFingerprintError(\ "cryptoID mismatch: %s, %s" % \ (chain.cryptoID, self.cryptoID)) if self.protocol: if not chain.checkProtocol(self.protocol): raise TLSAuthorizationError(\ "cryptoID protocol mismatch") if not chain.validate(): raise TLSValidationError("cryptoID validation failure") elif chain: raise TLSAuthenticationTypeError() else: raise TLSNoAuthenticationError() gdata-2.0.18/src/gdata/photos/0000750036466300116100000000000012156625015016115 5ustar afshareng00000000000000gdata-2.0.18/src/gdata/photos/__init__.py0000640036466300116100000011424412156622363020240 0ustar afshareng00000000000000# -*-*- encoding: utf-8 -*-*- # # This is the base file for the PicasaWeb python client. # It is used for lower level operations. # # $Id: __init__.py 148 2007-10-28 15:09:19Z havard.gulldahl $ # # Copyright 2007 HÃ¥vard Gulldahl # Portions (C) 2006 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. """This module provides a pythonic, gdata-centric interface to Google Photos (a.k.a. Picasa Web Services. It is modelled after the gdata/* interfaces from the gdata-python-client project[1] by Google. You'll find the user-friendly api in photos.service. Please see the documentation or live help() system for available methods. [1]: http://gdata-python-client.googlecode.com/ """ __author__ = u'havard@gulldahl.no'# (HÃ¥vard Gulldahl)' #BUG: pydoc chokes on non-ascii chars in __author__ __license__ = 'Apache License v2' __version__ = '$Revision: 164 $'[11:-2] import re try: from xml.etree import cElementTree as ElementTree except ImportError: try: import cElementTree as ElementTree except ImportError: try: from xml.etree import ElementTree except ImportError: from elementtree import ElementTree import atom import gdata # importing google photo submodules import gdata.media as Media, gdata.exif as Exif, gdata.geo as Geo # XML namespaces which are often used in Google Photo elements PHOTOS_NAMESPACE = 'http://schemas.google.com/photos/2007' MEDIA_NAMESPACE = 'http://search.yahoo.com/mrss/' EXIF_NAMESPACE = 'http://schemas.google.com/photos/exif/2007' OPENSEARCH_NAMESPACE = 'http://a9.com/-/spec/opensearchrss/1.0/' GEO_NAMESPACE = 'http://www.w3.org/2003/01/geo/wgs84_pos#' GML_NAMESPACE = 'http://www.opengis.net/gml' GEORSS_NAMESPACE = 'http://www.georss.org/georss' PHEED_NAMESPACE = 'http://www.pheed.com/pheed/' BATCH_NAMESPACE = 'http://schemas.google.com/gdata/batch' class PhotosBaseElement(atom.AtomBase): """Base class for elements in the PHOTO_NAMESPACE. To add new elements, you only need to add the element tag name to self._tag """ _tag = '' _namespace = PHOTOS_NAMESPACE _children = atom.AtomBase._children.copy() _attributes = atom.AtomBase._attributes.copy() def __init__(self, name=None, extension_elements=None, extension_attributes=None, text=None): self.name = name self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} #def __str__(self): #return str(self.text) #def __unicode__(self): #return unicode(self.text) def __int__(self): return int(self.text) def bool(self): return self.text == 'true' class GPhotosBaseFeed(gdata.GDataFeed, gdata.LinkFinder): "Base class for all Feeds in gdata.photos" _tag = 'feed' _namespace = atom.ATOM_NAMESPACE _attributes = gdata.GDataFeed._attributes.copy() _children = gdata.GDataFeed._children.copy() # We deal with Entry elements ourselves del _children['{%s}entry' % atom.ATOM_NAMESPACE] def __init__(self, author=None, category=None, contributor=None, generator=None, icon=None, atom_id=None, link=None, logo=None, rights=None, subtitle=None, title=None, updated=None, entry=None, total_results=None, start_index=None, items_per_page=None, extension_elements=None, extension_attributes=None, text=None): gdata.GDataFeed.__init__(self, author=author, category=category, contributor=contributor, generator=generator, icon=icon, atom_id=atom_id, link=link, logo=logo, rights=rights, subtitle=subtitle, title=title, updated=updated, entry=entry, total_results=total_results, start_index=start_index, items_per_page=items_per_page, extension_elements=extension_elements, extension_attributes=extension_attributes, text=text) def kind(self): "(string) Returns the kind" try: return self.category[0].term.split('#')[1] except IndexError: return None def _feedUri(self, kind): "Convenience method to return a uri to a feed of a special kind" assert(kind in ('album', 'tag', 'photo', 'comment', 'user')) here_href = self.GetSelfLink().href if 'kind=%s' % kind in here_href: return here_href if not 'kind=' in here_href: sep = '?' if '?' in here_href: sep = '&' return here_href + "%skind=%s" % (sep, kind) rx = re.match('.*(kind=)(album|tag|photo|comment)', here_href) return here_href[:rx.end(1)] + kind + here_href[rx.end(2):] def _ConvertElementTreeToMember(self, child_tree): """Re-implementing the method from AtomBase, since we deal with Entry elements specially""" category = child_tree.find('{%s}category' % atom.ATOM_NAMESPACE) if category is None: return atom.AtomBase._ConvertElementTreeToMember(self, child_tree) namespace, kind = category.get('term').split('#') if namespace != PHOTOS_NAMESPACE: return atom.AtomBase._ConvertElementTreeToMember(self, child_tree) ## TODO: is it safe to use getattr on gdata.photos? entry_class = getattr(gdata.photos, '%sEntry' % kind.title()) if not hasattr(self, 'entry') or self.entry is None: self.entry = [] self.entry.append(atom._CreateClassFromElementTree( entry_class, child_tree)) class GPhotosBaseEntry(gdata.GDataEntry, gdata.LinkFinder): "Base class for all Entry elements in gdata.photos" _tag = 'entry' _kind = '' _namespace = atom.ATOM_NAMESPACE _children = gdata.GDataEntry._children.copy() _attributes = gdata.GDataEntry._attributes.copy() def __init__(self, author=None, category=None, content=None, atom_id=None, link=None, published=None, title=None, updated=None, extended_property=None, extension_elements=None, extension_attributes=None, text=None): gdata.GDataEntry.__init__(self, author=author, category=category, content=content, atom_id=atom_id, link=link, published=published, title=title, updated=updated, text=text, extension_elements=extension_elements, extension_attributes=extension_attributes) self.category.append( atom.Category(scheme='http://schemas.google.com/g/2005#kind', term = 'http://schemas.google.com/photos/2007#%s' % self._kind)) def kind(self): "(string) Returns the kind" try: return self.category[0].term.split('#')[1] except IndexError: return None def _feedUri(self, kind): "Convenience method to get the uri to this entry's feed of the some kind" try: href = self.GetFeedLink().href except AttributeError: return None sep = '?' if '?' in href: sep = '&' return '%s%skind=%s' % (href, sep, kind) class PhotosBaseEntry(GPhotosBaseEntry): pass class PhotosBaseFeed(GPhotosBaseFeed): pass class GPhotosBaseData(object): pass class Access(PhotosBaseElement): """The Google Photo `Access' element. The album's access level. Valid values are `public' or `private'. In documentation, access level is also referred to as `visibility.'""" _tag = 'access' def AccessFromString(xml_string): return atom.CreateClassFromXMLString(Access, xml_string) class Albumid(PhotosBaseElement): "The Google Photo `Albumid' element" _tag = 'albumid' def AlbumidFromString(xml_string): return atom.CreateClassFromXMLString(Albumid, xml_string) class BytesUsed(PhotosBaseElement): "The Google Photo `BytesUsed' element" _tag = 'bytesUsed' def BytesUsedFromString(xml_string): return atom.CreateClassFromXMLString(BytesUsed, xml_string) class Client(PhotosBaseElement): "The Google Photo `Client' element" _tag = 'client' def ClientFromString(xml_string): return atom.CreateClassFromXMLString(Client, xml_string) class Checksum(PhotosBaseElement): "The Google Photo `Checksum' element" _tag = 'checksum' def ChecksumFromString(xml_string): return atom.CreateClassFromXMLString(Checksum, xml_string) class CommentCount(PhotosBaseElement): "The Google Photo `CommentCount' element" _tag = 'commentCount' def CommentCountFromString(xml_string): return atom.CreateClassFromXMLString(CommentCount, xml_string) class CommentingEnabled(PhotosBaseElement): "The Google Photo `CommentingEnabled' element" _tag = 'commentingEnabled' def CommentingEnabledFromString(xml_string): return atom.CreateClassFromXMLString(CommentingEnabled, xml_string) class Height(PhotosBaseElement): "The Google Photo `Height' element" _tag = 'height' def HeightFromString(xml_string): return atom.CreateClassFromXMLString(Height, xml_string) class Id(PhotosBaseElement): "The Google Photo `Id' element" _tag = 'id' def IdFromString(xml_string): return atom.CreateClassFromXMLString(Id, xml_string) class Location(PhotosBaseElement): "The Google Photo `Location' element" _tag = 'location' def LocationFromString(xml_string): return atom.CreateClassFromXMLString(Location, xml_string) class MaxPhotosPerAlbum(PhotosBaseElement): "The Google Photo `MaxPhotosPerAlbum' element" _tag = 'maxPhotosPerAlbum' def MaxPhotosPerAlbumFromString(xml_string): return atom.CreateClassFromXMLString(MaxPhotosPerAlbum, xml_string) class Name(PhotosBaseElement): "The Google Photo `Name' element" _tag = 'name' def NameFromString(xml_string): return atom.CreateClassFromXMLString(Name, xml_string) class Nickname(PhotosBaseElement): "The Google Photo `Nickname' element" _tag = 'nickname' def NicknameFromString(xml_string): return atom.CreateClassFromXMLString(Nickname, xml_string) class Numphotos(PhotosBaseElement): "The Google Photo `Numphotos' element" _tag = 'numphotos' def NumphotosFromString(xml_string): return atom.CreateClassFromXMLString(Numphotos, xml_string) class Numphotosremaining(PhotosBaseElement): "The Google Photo `Numphotosremaining' element" _tag = 'numphotosremaining' def NumphotosremainingFromString(xml_string): return atom.CreateClassFromXMLString(Numphotosremaining, xml_string) class Position(PhotosBaseElement): "The Google Photo `Position' element" _tag = 'position' def PositionFromString(xml_string): return atom.CreateClassFromXMLString(Position, xml_string) class Photoid(PhotosBaseElement): "The Google Photo `Photoid' element" _tag = 'photoid' def PhotoidFromString(xml_string): return atom.CreateClassFromXMLString(Photoid, xml_string) class Quotacurrent(PhotosBaseElement): "The Google Photo `Quotacurrent' element" _tag = 'quotacurrent' def QuotacurrentFromString(xml_string): return atom.CreateClassFromXMLString(Quotacurrent, xml_string) class Quotalimit(PhotosBaseElement): "The Google Photo `Quotalimit' element" _tag = 'quotalimit' def QuotalimitFromString(xml_string): return atom.CreateClassFromXMLString(Quotalimit, xml_string) class Rotation(PhotosBaseElement): "The Google Photo `Rotation' element" _tag = 'rotation' def RotationFromString(xml_string): return atom.CreateClassFromXMLString(Rotation, xml_string) class Size(PhotosBaseElement): "The Google Photo `Size' element" _tag = 'size' def SizeFromString(xml_string): return atom.CreateClassFromXMLString(Size, xml_string) class Snippet(PhotosBaseElement): """The Google Photo `snippet' element. When searching, the snippet element will contain a string with the word you're looking for, highlighted in html markup E.g. when your query is `hafjell', this element may contain: `... here at Hafjell.' You'll find this element in searches -- that is, feeds that combine the `kind=photo' and `q=yoursearch' parameters in the request. See also gphoto:truncated and gphoto:snippettype. """ _tag = 'snippet' def SnippetFromString(xml_string): return atom.CreateClassFromXMLString(Snippet, xml_string) class Snippettype(PhotosBaseElement): """The Google Photo `Snippettype' element When searching, this element will tell you the type of element that matches. You'll find this element in searches -- that is, feeds that combine the `kind=photo' and `q=yoursearch' parameters in the request. See also gphoto:snippet and gphoto:truncated. Possible values and their interpretation: o ALBUM_TITLE - The album title matches o PHOTO_TAGS - The match is a tag/keyword o PHOTO_DESCRIPTION - The match is in the photo's description If you discover a value not listed here, please submit a patch to update this docstring. """ _tag = 'snippettype' def SnippettypeFromString(xml_string): return atom.CreateClassFromXMLString(Snippettype, xml_string) class Thumbnail(PhotosBaseElement): """The Google Photo `Thumbnail' element Used to display user's photo thumbnail (hackergotchi). (Not to be confused with the element, which gives you small versions of the photo object.)""" _tag = 'thumbnail' def ThumbnailFromString(xml_string): return atom.CreateClassFromXMLString(Thumbnail, xml_string) class Timestamp(PhotosBaseElement): """The Google Photo `Timestamp' element Represented as the number of milliseconds since January 1st, 1970. Take a look at the convenience methods .isoformat() and .datetime(): photo_epoch = Time.text # 1180294337000 photo_isostring = Time.isoformat() # '2007-05-27T19:32:17.000Z' Alternatively: photo_datetime = Time.datetime() # (requires python >= 2.3) """ _tag = 'timestamp' def isoformat(self): """(string) Return the timestamp as a ISO 8601 formatted string, e.g. '2007-05-27T19:32:17.000Z' """ import time epoch = float(self.text)/1000 return time.strftime('%Y-%m-%dT%H:%M:%S.000Z', time.gmtime(epoch)) def datetime(self): """(datetime.datetime) Return the timestamp as a datetime.datetime object Requires python 2.3 """ import datetime epoch = float(self.text)/1000 return datetime.datetime.fromtimestamp(epoch) def TimestampFromString(xml_string): return atom.CreateClassFromXMLString(Timestamp, xml_string) class Truncated(PhotosBaseElement): """The Google Photo `Truncated' element You'll find this element in searches -- that is, feeds that combine the `kind=photo' and `q=yoursearch' parameters in the request. See also gphoto:snippet and gphoto:snippettype. Possible values and their interpretation: 0 -- unknown """ _tag = 'Truncated' def TruncatedFromString(xml_string): return atom.CreateClassFromXMLString(Truncated, xml_string) class User(PhotosBaseElement): "The Google Photo `User' element" _tag = 'user' def UserFromString(xml_string): return atom.CreateClassFromXMLString(User, xml_string) class Version(PhotosBaseElement): "The Google Photo `Version' element" _tag = 'version' def VersionFromString(xml_string): return atom.CreateClassFromXMLString(Version, xml_string) class Width(PhotosBaseElement): "The Google Photo `Width' element" _tag = 'width' def WidthFromString(xml_string): return atom.CreateClassFromXMLString(Width, xml_string) class Weight(PhotosBaseElement): """The Google Photo `Weight' element. The weight of the tag is the number of times the tag appears in the collection of tags currently being viewed. The default weight is 1, in which case this tags is omitted.""" _tag = 'weight' def WeightFromString(xml_string): return atom.CreateClassFromXMLString(Weight, xml_string) class CommentAuthor(atom.Author): """The Atom `Author' element in CommentEntry entries is augmented to contain elements from the PHOTOS_NAMESPACE http://groups.google.com/group/Google-Picasa-Data-API/msg/819b0025b5ff5e38 """ _children = atom.Author._children.copy() _children['{%s}user' % PHOTOS_NAMESPACE] = ('user', User) _children['{%s}nickname' % PHOTOS_NAMESPACE] = ('nickname', Nickname) _children['{%s}thumbnail' % PHOTOS_NAMESPACE] = ('thumbnail', Thumbnail) def CommentAuthorFromString(xml_string): return atom.CreateClassFromXMLString(CommentAuthor, xml_string) ########################## ################################ class AlbumData(object): _children = {} _children['{%s}id' % PHOTOS_NAMESPACE] = ('gphoto_id', Id) _children['{%s}name' % PHOTOS_NAMESPACE] = ('name', Name) _children['{%s}location' % PHOTOS_NAMESPACE] = ('location', Location) _children['{%s}access' % PHOTOS_NAMESPACE] = ('access', Access) _children['{%s}bytesUsed' % PHOTOS_NAMESPACE] = ('bytesUsed', BytesUsed) _children['{%s}timestamp' % PHOTOS_NAMESPACE] = ('timestamp', Timestamp) _children['{%s}numphotos' % PHOTOS_NAMESPACE] = ('numphotos', Numphotos) _children['{%s}numphotosremaining' % PHOTOS_NAMESPACE] = \ ('numphotosremaining', Numphotosremaining) _children['{%s}user' % PHOTOS_NAMESPACE] = ('user', User) _children['{%s}nickname' % PHOTOS_NAMESPACE] = ('nickname', Nickname) _children['{%s}commentingEnabled' % PHOTOS_NAMESPACE] = \ ('commentingEnabled', CommentingEnabled) _children['{%s}commentCount' % PHOTOS_NAMESPACE] = \ ('commentCount', CommentCount) ## NOTE: storing media:group as self.media, to create a self-explaining api gphoto_id = None name = None location = None access = None bytesUsed = None timestamp = None numphotos = None numphotosremaining = None user = None nickname = None commentingEnabled = None commentCount = None class AlbumEntry(GPhotosBaseEntry, AlbumData): """All metadata for a Google Photos Album Take a look at AlbumData for metadata accessible as attributes to this object. Notes: To avoid name clashes, and to create a more sensible api, some objects have names that differ from the original elements: o media:group -> self.media, o geo:where -> self.geo, o photo:id -> self.gphoto_id """ _kind = 'album' _children = GPhotosBaseEntry._children.copy() _children.update(AlbumData._children.copy()) # child tags only for Album entries, not feeds _children['{%s}where' % GEORSS_NAMESPACE] = ('geo', Geo.Where) _children['{%s}group' % MEDIA_NAMESPACE] = ('media', Media.Group) media = Media.Group() geo = Geo.Where() def __init__(self, author=None, category=None, content=None, atom_id=None, link=None, published=None, title=None, updated=None, #GPHOTO NAMESPACE: gphoto_id=None, name=None, location=None, access=None, timestamp=None, numphotos=None, user=None, nickname=None, commentingEnabled=None, commentCount=None, thumbnail=None, # MEDIA NAMESPACE: media=None, # GEORSS NAMESPACE: geo=None, extended_property=None, extension_elements=None, extension_attributes=None, text=None): GPhotosBaseEntry.__init__(self, author=author, category=category, content=content, atom_id=atom_id, link=link, published=published, title=title, updated=updated, text=text, extension_elements=extension_elements, extension_attributes=extension_attributes) ## NOTE: storing photo:id as self.gphoto_id, to avoid name clash with atom:id self.gphoto_id = gphoto_id self.name = name self.location = location self.access = access self.timestamp = timestamp self.numphotos = numphotos self.user = user self.nickname = nickname self.commentingEnabled = commentingEnabled self.commentCount = commentCount self.thumbnail = thumbnail self.extended_property = extended_property or [] self.text = text ## NOTE: storing media:group as self.media, and geo:where as geo, ## to create a self-explaining api self.media = media or Media.Group() self.geo = geo or Geo.Where() def GetAlbumId(self): "Return the id of this album" return self.GetFeedLink().href.split('/')[-1] def GetPhotosUri(self): "(string) Return the uri to this albums feed of the PhotoEntry kind" return self._feedUri('photo') def GetCommentsUri(self): "(string) Return the uri to this albums feed of the CommentEntry kind" return self._feedUri('comment') def GetTagsUri(self): "(string) Return the uri to this albums feed of the TagEntry kind" return self._feedUri('tag') def AlbumEntryFromString(xml_string): return atom.CreateClassFromXMLString(AlbumEntry, xml_string) class AlbumFeed(GPhotosBaseFeed, AlbumData): """All metadata for a Google Photos Album, including its sub-elements This feed represents an album as the container for other objects. A Album feed contains entries of PhotoEntry, CommentEntry or TagEntry, depending on the `kind' parameter in the original query. Take a look at AlbumData for accessible attributes. """ _children = GPhotosBaseFeed._children.copy() _children.update(AlbumData._children.copy()) def GetPhotosUri(self): "(string) Return the uri to the same feed, but of the PhotoEntry kind" return self._feedUri('photo') def GetTagsUri(self): "(string) Return the uri to the same feed, but of the TagEntry kind" return self._feedUri('tag') def GetCommentsUri(self): "(string) Return the uri to the same feed, but of the CommentEntry kind" return self._feedUri('comment') def AlbumFeedFromString(xml_string): return atom.CreateClassFromXMLString(AlbumFeed, xml_string) class PhotoData(object): _children = {} ## NOTE: storing photo:id as self.gphoto_id, to avoid name clash with atom:id _children['{%s}id' % PHOTOS_NAMESPACE] = ('gphoto_id', Id) _children['{%s}albumid' % PHOTOS_NAMESPACE] = ('albumid', Albumid) _children['{%s}checksum' % PHOTOS_NAMESPACE] = ('checksum', Checksum) _children['{%s}client' % PHOTOS_NAMESPACE] = ('client', Client) _children['{%s}height' % PHOTOS_NAMESPACE] = ('height', Height) _children['{%s}position' % PHOTOS_NAMESPACE] = ('position', Position) _children['{%s}rotation' % PHOTOS_NAMESPACE] = ('rotation', Rotation) _children['{%s}size' % PHOTOS_NAMESPACE] = ('size', Size) _children['{%s}timestamp' % PHOTOS_NAMESPACE] = ('timestamp', Timestamp) _children['{%s}version' % PHOTOS_NAMESPACE] = ('version', Version) _children['{%s}width' % PHOTOS_NAMESPACE] = ('width', Width) _children['{%s}commentingEnabled' % PHOTOS_NAMESPACE] = \ ('commentingEnabled', CommentingEnabled) _children['{%s}commentCount' % PHOTOS_NAMESPACE] = \ ('commentCount', CommentCount) ## NOTE: storing media:group as self.media, exif:tags as self.exif, and ## geo:where as self.geo, to create a self-explaining api _children['{%s}tags' % EXIF_NAMESPACE] = ('exif', Exif.Tags) _children['{%s}where' % GEORSS_NAMESPACE] = ('geo', Geo.Where) _children['{%s}group' % MEDIA_NAMESPACE] = ('media', Media.Group) # These elements show up in search feeds _children['{%s}snippet' % PHOTOS_NAMESPACE] = ('snippet', Snippet) _children['{%s}snippettype' % PHOTOS_NAMESPACE] = ('snippettype', Snippettype) _children['{%s}truncated' % PHOTOS_NAMESPACE] = ('truncated', Truncated) gphoto_id = None albumid = None checksum = None client = None height = None position = None rotation = None size = None timestamp = None version = None width = None commentingEnabled = None commentCount = None snippet=None snippettype=None truncated=None media = Media.Group() geo = Geo.Where() tags = Exif.Tags() class PhotoEntry(GPhotosBaseEntry, PhotoData): """All metadata for a Google Photos Photo Take a look at PhotoData for metadata accessible as attributes to this object. Notes: To avoid name clashes, and to create a more sensible api, some objects have names that differ from the original elements: o media:group -> self.media, o exif:tags -> self.exif, o geo:where -> self.geo, o photo:id -> self.gphoto_id """ _kind = 'photo' _children = GPhotosBaseEntry._children.copy() _children.update(PhotoData._children.copy()) def __init__(self, author=None, category=None, content=None, atom_id=None, link=None, published=None, title=None, updated=None, text=None, # GPHOTO NAMESPACE: gphoto_id=None, albumid=None, checksum=None, client=None, height=None, position=None, rotation=None, size=None, timestamp=None, version=None, width=None, commentCount=None, commentingEnabled=None, # MEDIARSS NAMESPACE: media=None, # EXIF_NAMESPACE: exif=None, # GEORSS NAMESPACE: geo=None, extension_elements=None, extension_attributes=None): GPhotosBaseEntry.__init__(self, author=author, category=category, content=content, atom_id=atom_id, link=link, published=published, title=title, updated=updated, text=text, extension_elements=extension_elements, extension_attributes=extension_attributes) ## NOTE: storing photo:id as self.gphoto_id, to avoid name clash with atom:id self.gphoto_id = gphoto_id self.albumid = albumid self.checksum = checksum self.client = client self.height = height self.position = position self.rotation = rotation self.size = size self.timestamp = timestamp self.version = version self.width = width self.commentingEnabled = commentingEnabled self.commentCount = commentCount ## NOTE: storing media:group as self.media, to create a self-explaining api self.media = media or Media.Group() self.exif = exif or Exif.Tags() self.geo = geo or Geo.Where() def GetPostLink(self): "Return the uri to this photo's `POST' link (use it for updates of the object)" return self.GetFeedLink() def GetCommentsUri(self): "Return the uri to this photo's feed of CommentEntry comments" return self._feedUri('comment') def GetTagsUri(self): "Return the uri to this photo's feed of TagEntry tags" return self._feedUri('tag') def GetAlbumUri(self): """Return the uri to the AlbumEntry containing this photo""" href = self.GetSelfLink().href return href[:href.find('/photoid')] def PhotoEntryFromString(xml_string): return atom.CreateClassFromXMLString(PhotoEntry, xml_string) class PhotoFeed(GPhotosBaseFeed, PhotoData): """All metadata for a Google Photos Photo, including its sub-elements This feed represents a photo as the container for other objects. A Photo feed contains entries of CommentEntry or TagEntry, depending on the `kind' parameter in the original query. Take a look at PhotoData for metadata accessible as attributes to this object. """ _children = GPhotosBaseFeed._children.copy() _children.update(PhotoData._children.copy()) def GetTagsUri(self): "(string) Return the uri to the same feed, but of the TagEntry kind" return self._feedUri('tag') def GetCommentsUri(self): "(string) Return the uri to the same feed, but of the CommentEntry kind" return self._feedUri('comment') def PhotoFeedFromString(xml_string): return atom.CreateClassFromXMLString(PhotoFeed, xml_string) class TagData(GPhotosBaseData): _children = {} _children['{%s}weight' % PHOTOS_NAMESPACE] = ('weight', Weight) weight=None class TagEntry(GPhotosBaseEntry, TagData): """All metadata for a Google Photos Tag The actual tag is stored in the .title.text attribute """ _kind = 'tag' _children = GPhotosBaseEntry._children.copy() _children.update(TagData._children.copy()) def __init__(self, author=None, category=None, content=None, atom_id=None, link=None, published=None, title=None, updated=None, # GPHOTO NAMESPACE: weight=None, extended_property=None, extension_elements=None, extension_attributes=None, text=None): GPhotosBaseEntry.__init__(self, author=author, category=category, content=content, atom_id=atom_id, link=link, published=published, title=title, updated=updated, text=text, extension_elements=extension_elements, extension_attributes=extension_attributes) self.weight = weight def GetAlbumUri(self): """Return the uri to the AlbumEntry containing this tag""" href = self.GetSelfLink().href pos = href.find('/photoid') if pos == -1: return None return href[:pos] def GetPhotoUri(self): """Return the uri to the PhotoEntry containing this tag""" href = self.GetSelfLink().href pos = href.find('/tag') if pos == -1: return None return href[:pos] def TagEntryFromString(xml_string): return atom.CreateClassFromXMLString(TagEntry, xml_string) class TagFeed(GPhotosBaseFeed, TagData): """All metadata for a Google Photos Tag, including its sub-elements""" _children = GPhotosBaseFeed._children.copy() _children.update(TagData._children.copy()) def TagFeedFromString(xml_string): return atom.CreateClassFromXMLString(TagFeed, xml_string) class CommentData(GPhotosBaseData): _children = {} ## NOTE: storing photo:id as self.gphoto_id, to avoid name clash with atom:id _children['{%s}id' % PHOTOS_NAMESPACE] = ('gphoto_id', Id) _children['{%s}albumid' % PHOTOS_NAMESPACE] = ('albumid', Albumid) _children['{%s}photoid' % PHOTOS_NAMESPACE] = ('photoid', Photoid) _children['{%s}author' % atom.ATOM_NAMESPACE] = ('author', [CommentAuthor,]) gphoto_id=None albumid=None photoid=None author=None class CommentEntry(GPhotosBaseEntry, CommentData): """All metadata for a Google Photos Comment The comment is stored in the .content.text attribute, with a content type in .content.type. """ _kind = 'comment' _children = GPhotosBaseEntry._children.copy() _children.update(CommentData._children.copy()) def __init__(self, author=None, category=None, content=None, atom_id=None, link=None, published=None, title=None, updated=None, # GPHOTO NAMESPACE: gphoto_id=None, albumid=None, photoid=None, extended_property=None, extension_elements=None, extension_attributes=None, text=None): GPhotosBaseEntry.__init__(self, author=author, category=category, content=content, atom_id=atom_id, link=link, published=published, title=title, updated=updated, extension_elements=extension_elements, extension_attributes=extension_attributes, text=text) self.gphoto_id = gphoto_id self.albumid = albumid self.photoid = photoid def GetCommentId(self): """Return the globally unique id of this comment""" return self.GetSelfLink().href.split('/')[-1] def GetAlbumUri(self): """Return the uri to the AlbumEntry containing this comment""" href = self.GetSelfLink().href return href[:href.find('/photoid')] def GetPhotoUri(self): """Return the uri to the PhotoEntry containing this comment""" href = self.GetSelfLink().href return href[:href.find('/commentid')] def CommentEntryFromString(xml_string): return atom.CreateClassFromXMLString(CommentEntry, xml_string) class CommentFeed(GPhotosBaseFeed, CommentData): """All metadata for a Google Photos Comment, including its sub-elements""" _children = GPhotosBaseFeed._children.copy() _children.update(CommentData._children.copy()) def CommentFeedFromString(xml_string): return atom.CreateClassFromXMLString(CommentFeed, xml_string) class UserData(GPhotosBaseData): _children = {} _children['{%s}maxPhotosPerAlbum' % PHOTOS_NAMESPACE] = ('maxPhotosPerAlbum', MaxPhotosPerAlbum) _children['{%s}nickname' % PHOTOS_NAMESPACE] = ('nickname', Nickname) _children['{%s}quotalimit' % PHOTOS_NAMESPACE] = ('quotalimit', Quotalimit) _children['{%s}quotacurrent' % PHOTOS_NAMESPACE] = ('quotacurrent', Quotacurrent) _children['{%s}thumbnail' % PHOTOS_NAMESPACE] = ('thumbnail', Thumbnail) _children['{%s}user' % PHOTOS_NAMESPACE] = ('user', User) _children['{%s}id' % PHOTOS_NAMESPACE] = ('gphoto_id', Id) maxPhotosPerAlbum=None nickname=None quotalimit=None quotacurrent=None thumbnail=None user=None gphoto_id=None class UserEntry(GPhotosBaseEntry, UserData): """All metadata for a Google Photos User This entry represents an album owner and all appropriate metadata. Take a look at at the attributes of the UserData for metadata available. """ _children = GPhotosBaseEntry._children.copy() _children.update(UserData._children.copy()) _kind = 'user' def __init__(self, author=None, category=None, content=None, atom_id=None, link=None, published=None, title=None, updated=None, # GPHOTO NAMESPACE: gphoto_id=None, maxPhotosPerAlbum=None, nickname=None, quotalimit=None, quotacurrent=None, thumbnail=None, user=None, extended_property=None, extension_elements=None, extension_attributes=None, text=None): GPhotosBaseEntry.__init__(self, author=author, category=category, content=content, atom_id=atom_id, link=link, published=published, title=title, updated=updated, extension_elements=extension_elements, extension_attributes=extension_attributes, text=text) self.gphoto_id=gphoto_id self.maxPhotosPerAlbum=maxPhotosPerAlbum self.nickname=nickname self.quotalimit=quotalimit self.quotacurrent=quotacurrent self.thumbnail=thumbnail self.user=user def GetAlbumsUri(self): "(string) Return the uri to this user's feed of the AlbumEntry kind" return self._feedUri('album') def GetPhotosUri(self): "(string) Return the uri to this user's feed of the PhotoEntry kind" return self._feedUri('photo') def GetCommentsUri(self): "(string) Return the uri to this user's feed of the CommentEntry kind" return self._feedUri('comment') def GetTagsUri(self): "(string) Return the uri to this user's feed of the TagEntry kind" return self._feedUri('tag') def UserEntryFromString(xml_string): return atom.CreateClassFromXMLString(UserEntry, xml_string) class UserFeed(GPhotosBaseFeed, UserData): """Feed for a User in the google photos api. This feed represents a user as the container for other objects. A User feed contains entries of AlbumEntry, PhotoEntry, CommentEntry, UserEntry or TagEntry, depending on the `kind' parameter in the original query. The user feed itself also contains all of the metadata available as part of a UserData object.""" _children = GPhotosBaseFeed._children.copy() _children.update(UserData._children.copy()) def GetAlbumsUri(self): """Get the uri to this feed, but with entries of the AlbumEntry kind.""" return self._feedUri('album') def GetTagsUri(self): """Get the uri to this feed, but with entries of the TagEntry kind.""" return self._feedUri('tag') def GetPhotosUri(self): """Get the uri to this feed, but with entries of the PhotosEntry kind.""" return self._feedUri('photo') def GetCommentsUri(self): """Get the uri to this feed, but with entries of the CommentsEntry kind.""" return self._feedUri('comment') def UserFeedFromString(xml_string): return atom.CreateClassFromXMLString(UserFeed, xml_string) def AnyFeedFromString(xml_string): """Creates an instance of the appropriate feed class from the xml string contents. Args: xml_string: str A string which contains valid XML. The root element of the XML string should match the tag and namespace of the desired class. Returns: An instance of the target class with members assigned according to the contents of the XML - or a basic gdata.GDataFeed instance if it is impossible to determine the appropriate class (look for extra elements in GDataFeed's .FindExtensions() and extension_elements[] ). """ tree = ElementTree.fromstring(xml_string) category = tree.find('{%s}category' % atom.ATOM_NAMESPACE) if category is None: # TODO: is this the best way to handle this? return atom._CreateClassFromElementTree(GPhotosBaseFeed, tree) namespace, kind = category.get('term').split('#') if namespace != PHOTOS_NAMESPACE: # TODO: is this the best way to handle this? return atom._CreateClassFromElementTree(GPhotosBaseFeed, tree) ## TODO: is getattr safe this way? feed_class = getattr(gdata.photos, '%sFeed' % kind.title()) return atom._CreateClassFromElementTree(feed_class, tree) def AnyEntryFromString(xml_string): """Creates an instance of the appropriate entry class from the xml string contents. Args: xml_string: str A string which contains valid XML. The root element of the XML string should match the tag and namespace of the desired class. Returns: An instance of the target class with members assigned according to the contents of the XML - or a basic gdata.GDataEndry instance if it is impossible to determine the appropriate class (look for extra elements in GDataEntry's .FindExtensions() and extension_elements[] ). """ tree = ElementTree.fromstring(xml_string) category = tree.find('{%s}category' % atom.ATOM_NAMESPACE) if category is None: # TODO: is this the best way to handle this? return atom._CreateClassFromElementTree(GPhotosBaseEntry, tree) namespace, kind = category.get('term').split('#') if namespace != PHOTOS_NAMESPACE: # TODO: is this the best way to handle this? return atom._CreateClassFromElementTree(GPhotosBaseEntry, tree) ## TODO: is getattr safe this way? feed_class = getattr(gdata.photos, '%sEntry' % kind.title()) return atom._CreateClassFromElementTree(feed_class, tree) gdata-2.0.18/src/gdata/photos/service.py0000750036466300116100000005746212156622363020153 0ustar afshareng00000000000000#!/usr/bin/env python # -*-*- encoding: utf-8 -*-*- # # This is the service file for the Google Photo python client. # It is used for higher level operations. # # $Id: service.py 144 2007-10-25 21:03:34Z havard.gulldahl $ # # Copyright 2007 HÃ¥vard Gulldahl # # 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. """Google PhotoService provides a human-friendly interface to Google Photo (a.k.a Picasa Web) services[1]. It extends gdata.service.GDataService and as such hides all the nasty details about authenticating, parsing and communicating with Google Photos. [1]: http://code.google.com/apis/picasaweb/gdata.html Example: import gdata.photos, gdata.photos.service pws = gdata.photos.service.PhotosService() pws.ClientLogin(username, password) #Get all albums albums = pws.GetUserFeed().entry # Get all photos in second album photos = pws.GetFeed(albums[1].GetPhotosUri()).entry # Get all tags for photos in second album and print them tags = pws.GetFeed(albums[1].GetTagsUri()).entry print [ tag.summary.text for tag in tags ] # Get all comments for the first photos in list and print them comments = pws.GetCommentFeed(photos[0].GetCommentsUri()).entry print [ c.summary.text for c in comments ] # Get a photo to work with photo = photos[0] # Update metadata # Attributes from the namespace photo.summary.text = u'A nice view from my veranda' photo.title.text = u'Verandaview.jpg' # Attributes from the namespace photo.media.keywords.text = u'Home, Long-exposure, Sunset' # Comma-separated # Adding attributes to media object # Rotate 90 degrees clockwise photo.rotation = gdata.photos.Rotation(text='90') # Submit modified photo object photo = pws.UpdatePhotoMetadata(photo) # Make sure you only modify the newly returned object, else you'll get # versioning errors. See Optimistic-concurrency # Add comment to a picture comment = pws.InsertComment(photo, u'I wish the water always was this warm') # Remove comment because it was silly print "*blush*" pws.Delete(comment.GetEditLink().href) """ __author__ = u'havard@gulldahl.no'# (HÃ¥vard Gulldahl)' #BUG: pydoc chokes on non-ascii chars in __author__ __license__ = 'Apache License v2' __version__ = '$Revision: 176 $'[11:-2] import sys, os.path, StringIO import time import gdata.service import gdata import atom.service import atom import gdata.photos SUPPORTED_UPLOAD_TYPES = ('bmp', 'jpeg', 'jpg', 'gif', 'png') UNKOWN_ERROR=1000 GPHOTOS_BAD_REQUEST=400 GPHOTOS_CONFLICT=409 GPHOTOS_INTERNAL_SERVER_ERROR=500 GPHOTOS_INVALID_ARGUMENT=601 GPHOTOS_INVALID_CONTENT_TYPE=602 GPHOTOS_NOT_AN_IMAGE=603 GPHOTOS_INVALID_KIND=604 class GooglePhotosException(Exception): def __init__(self, response): self.error_code = response['status'] self.reason = response['reason'].strip() if '' in str(response['body']): #general html message, discard it response['body'] = "" self.body = response['body'].strip() self.message = "(%(status)s) %(body)s -- %(reason)s" % response #return explicit error codes error_map = { '(12) Not an image':GPHOTOS_NOT_AN_IMAGE, 'kind: That is not one of the acceptable values': GPHOTOS_INVALID_KIND, } for msg, code in error_map.iteritems(): if self.body == msg: self.error_code = code break self.args = [self.error_code, self.reason, self.body] class PhotosService(gdata.service.GDataService): ssl = True userUri = '/data/feed/api/user/%s' def __init__(self, email=None, password=None, source=None, server='picasaweb.google.com', additional_headers=None, **kwargs): """Creates a client for the Google Photos service. Args: email: string (optional) The user's email address, used for authentication. password: string (optional) The user's password. source: string (optional) The name of the user's application. server: string (optional) The name of the server to which a connection will be opened. Default value: 'picasaweb.google.com'. **kwargs: The other parameters to pass to gdata.service.GDataService constructor. """ self.email = email self.client = source gdata.service.GDataService.__init__( self, email=email, password=password, service='lh2', source=source, server=server, additional_headers=additional_headers, **kwargs) def GetFeed(self, uri, limit=None, start_index=None): """Get a feed. The results are ordered by the values of their `updated' elements, with the most recently updated entry appearing first in the feed. Arguments: uri: the uri to fetch limit (optional): the maximum number of entries to return. Defaults to what the server returns. Returns: one of gdata.photos.AlbumFeed, gdata.photos.UserFeed, gdata.photos.PhotoFeed, gdata.photos.CommentFeed, gdata.photos.TagFeed, depending on the results of the query. Raises: GooglePhotosException See: http://code.google.com/apis/picasaweb/gdata.html#Get_Album_Feed_Manual """ if limit is not None: uri += '&max-results=%s' % limit if start_index is not None: uri += '&start-index=%s' % start_index try: return self.Get(uri, converter=gdata.photos.AnyFeedFromString) except gdata.service.RequestError, e: raise GooglePhotosException(e.args[0]) def GetEntry(self, uri, limit=None, start_index=None): """Get an Entry. Arguments: uri: the uri to the entry limit (optional): the maximum number of entries to return. Defaults to what the server returns. Returns: one of gdata.photos.AlbumEntry, gdata.photos.UserEntry, gdata.photos.PhotoEntry, gdata.photos.CommentEntry, gdata.photos.TagEntry, depending on the results of the query. Raises: GooglePhotosException """ if limit is not None: uri += '&max-results=%s' % limit if start_index is not None: uri += '&start-index=%s' % start_index try: return self.Get(uri, converter=gdata.photos.AnyEntryFromString) except gdata.service.RequestError, e: raise GooglePhotosException(e.args[0]) def GetUserFeed(self, kind='album', user='default', limit=None): """Get user-based feed, containing albums, photos, comments or tags; defaults to albums. The entries are ordered by the values of their `updated' elements, with the most recently updated entry appearing first in the feed. Arguments: kind: the kind of entries to get, either `album', `photo', `comment' or `tag', or a python list of these. Defaults to `album'. user (optional): whose albums we're querying. Defaults to current user. limit (optional): the maximum number of entries to return. Defaults to everything the server returns. Returns: gdata.photos.UserFeed, containing appropriate Entry elements See: http://code.google.com/apis/picasaweb/gdata.html#Get_Album_Feed_Manual http://googledataapis.blogspot.com/2007/07/picasa-web-albums-adds-new-api-features.html """ if isinstance(kind, (list, tuple) ): kind = ",".join(kind) uri = '/data/feed/api/user/%s?kind=%s' % (user, kind) return self.GetFeed(uri, limit=limit) def GetTaggedPhotos(self, tag, user='default', limit=None): """Get all photos belonging to a specific user, tagged by the given keyword Arguments: tag: The tag you're looking for, e.g. `dog' user (optional): Whose images/videos you want to search, defaults to current user limit (optional): the maximum number of entries to return. Defaults to everything the server returns. Returns: gdata.photos.UserFeed containing PhotoEntry elements """ # Lower-casing because of # http://code.google.com/p/gdata-issues/issues/detail?id=194 uri = '/data/feed/api/user/%s?kind=photo&tag=%s' % (user, tag.lower()) return self.GetFeed(uri, limit) def SearchUserPhotos(self, query, user='default', limit=100): """Search through all photos for a specific user and return a feed. This will look for matches in file names and image tags (a.k.a. keywords) Arguments: query: The string you're looking for, e.g. `vacation' user (optional): The username of whose photos you want to search, defaults to current user. limit (optional): Don't return more than `limit' hits, defaults to 100 Only public photos are searched, unless you are authenticated and searching through your own photos. Returns: gdata.photos.UserFeed with PhotoEntry elements """ uri = '/data/feed/api/user/%s?kind=photo&q=%s' % (user, query) return self.GetFeed(uri, limit=limit) def SearchCommunityPhotos(self, query, limit=100): """Search through all public photos and return a feed. This will look for matches in file names and image tags (a.k.a. keywords) Arguments: query: The string you're looking for, e.g. `vacation' limit (optional): Don't return more than `limit' hits, defaults to 100 Returns: gdata.GDataFeed with PhotoEntry elements """ uri='/data/feed/api/all?q=%s' % query return self.GetFeed(uri, limit=limit) def GetContacts(self, user='default', limit=None): """Retrieve a feed that contains a list of your contacts Arguments: user: Username of the user whose contacts you want Returns gdata.photos.UserFeed, with UserEntry entries See: http://groups.google.com/group/Google-Picasa-Data-API/msg/819b0025b5ff5e38 """ uri = '/data/feed/api/user/%s/contacts?kind=user' % user return self.GetFeed(uri, limit=limit) def SearchContactsPhotos(self, user='default', search=None, limit=None): """Search over your contacts' photos and return a feed Arguments: user: Username of the user whose contacts you want search (optional): What to search for (photo title, description and keywords) Returns gdata.photos.UserFeed, with PhotoEntry elements See: http://groups.google.com/group/Google-Picasa-Data-API/msg/819b0025b5ff5e38 """ uri = '/data/feed/api/user/%s/contacts?kind=photo&q=%s' % (user, search) return self.GetFeed(uri, limit=limit) def InsertAlbum(self, title, summary, location=None, access='public', commenting_enabled='true', timestamp=None): """Add an album. Needs authentication, see self.ClientLogin() Arguments: title: Album title summary: Album summary / description access (optional): `private' or `public'. Public albums are searchable by everyone on the internet. Defaults to `public' commenting_enabled (optional): `true' or `false'. Defaults to `true'. timestamp (optional): A date and time for the album, in milliseconds since Unix epoch[1] UTC. Defaults to now. Returns: The newly created gdata.photos.AlbumEntry See: http://code.google.com/apis/picasaweb/gdata.html#Add_Album_Manual_Installed [1]: http://en.wikipedia.org/wiki/Unix_epoch """ album = gdata.photos.AlbumEntry() album.title = atom.Title(text=title, title_type='text') album.summary = atom.Summary(text=summary, summary_type='text') if location is not None: album.location = gdata.photos.Location(text=location) album.access = gdata.photos.Access(text=access) if commenting_enabled in ('true', 'false'): album.commentingEnabled = gdata.photos.CommentingEnabled(text=commenting_enabled) if timestamp is None: timestamp = '%i' % int(time.time() * 1000) album.timestamp = gdata.photos.Timestamp(text=timestamp) try: return self.Post(album, uri=self.userUri % self.email, converter=gdata.photos.AlbumEntryFromString) except gdata.service.RequestError, e: raise GooglePhotosException(e.args[0]) def InsertPhoto(self, album_or_uri, photo, filename_or_handle, content_type='image/jpeg'): """Add a PhotoEntry Needs authentication, see self.ClientLogin() Arguments: album_or_uri: AlbumFeed or uri of the album where the photo should go photo: PhotoEntry to add filename_or_handle: A file-like object or file name where the image/video will be read from content_type (optional): Internet media type (a.k.a. mime type) of media object. Currently Google Photos supports these types: o image/bmp o image/gif o image/jpeg o image/png Images will be converted to jpeg on upload. Defaults to `image/jpeg' """ try: assert(isinstance(photo, gdata.photos.PhotoEntry)) except AssertionError: raise GooglePhotosException({'status':GPHOTOS_INVALID_ARGUMENT, 'body':'`photo` must be a gdata.photos.PhotoEntry instance', 'reason':'Found %s, not PhotoEntry' % type(photo) }) try: majtype, mintype = content_type.split('/') assert(mintype in SUPPORTED_UPLOAD_TYPES) except (ValueError, AssertionError): raise GooglePhotosException({'status':GPHOTOS_INVALID_CONTENT_TYPE, 'body':'This is not a valid content type: %s' % content_type, 'reason':'Accepted content types: %s' % \ ['image/'+t for t in SUPPORTED_UPLOAD_TYPES] }) if isinstance(filename_or_handle, (str, unicode)) and \ os.path.exists(filename_or_handle): # it's a file name mediasource = gdata.MediaSource() mediasource.setFile(filename_or_handle, content_type) elif hasattr(filename_or_handle, 'read'):# it's a file-like resource if hasattr(filename_or_handle, 'seek'): filename_or_handle.seek(0) # rewind pointer to the start of the file # gdata.MediaSource needs the content length, so read the whole image file_handle = StringIO.StringIO(filename_or_handle.read()) name = 'image' if hasattr(filename_or_handle, 'name'): name = filename_or_handle.name mediasource = gdata.MediaSource(file_handle, content_type, content_length=file_handle.len, file_name=name) else: #filename_or_handle is not valid raise GooglePhotosException({'status':GPHOTOS_INVALID_ARGUMENT, 'body':'`filename_or_handle` must be a path name or a file-like object', 'reason':'Found %s, not path name or object with a .read() method' % \ filename_or_handle }) if isinstance(album_or_uri, (str, unicode)): # it's a uri feed_uri = album_or_uri elif hasattr(album_or_uri, 'GetFeedLink'): # it's a AlbumFeed object feed_uri = album_or_uri.GetFeedLink().href try: return self.Post(photo, uri=feed_uri, media_source=mediasource, converter=gdata.photos.PhotoEntryFromString) except gdata.service.RequestError, e: raise GooglePhotosException(e.args[0]) def InsertPhotoSimple(self, album_or_uri, title, summary, filename_or_handle, content_type='image/jpeg', keywords=None): """Add a photo without constructing a PhotoEntry. Needs authentication, see self.ClientLogin() Arguments: album_or_uri: AlbumFeed or uri of the album where the photo should go title: Photo title summary: Photo summary / description filename_or_handle: A file-like object or file name where the image/video will be read from content_type (optional): Internet media type (a.k.a. mime type) of media object. Currently Google Photos supports these types: o image/bmp o image/gif o image/jpeg o image/png Images will be converted to jpeg on upload. Defaults to `image/jpeg' keywords (optional): a 1) comma separated string or 2) a python list() of keywords (a.k.a. tags) to add to the image. E.g. 1) `dog, vacation, happy' 2) ['dog', 'happy', 'vacation'] Returns: The newly created gdata.photos.PhotoEntry or GooglePhotosException on errors See: http://code.google.com/apis/picasaweb/gdata.html#Add_Album_Manual_Installed [1]: http://en.wikipedia.org/wiki/Unix_epoch """ metadata = gdata.photos.PhotoEntry() metadata.title=atom.Title(text=title) metadata.summary = atom.Summary(text=summary, summary_type='text') if keywords is not None: if isinstance(keywords, list): keywords = ','.join(keywords) metadata.media.keywords = gdata.media.Keywords(text=keywords) return self.InsertPhoto(album_or_uri, metadata, filename_or_handle, content_type) def UpdatePhotoMetadata(self, photo): """Update a photo's metadata. Needs authentication, see self.ClientLogin() You can update any or all of the following metadata properties: * * <media:description> * <gphoto:checksum> * <gphoto:client> * <gphoto:rotation> * <gphoto:timestamp> * <gphoto:commentingEnabled> Arguments: photo: a gdata.photos.PhotoEntry object with updated elements Returns: The modified gdata.photos.PhotoEntry Example: p = GetFeed(uri).entry[0] p.title.text = u'My new text' p.commentingEnabled.text = 'false' p = UpdatePhotoMetadata(p) It is important that you don't keep the old object around, once it has been updated. See http://code.google.com/apis/gdata/reference.html#Optimistic-concurrency """ try: return self.Put(data=photo, uri=photo.GetEditLink().href, converter=gdata.photos.PhotoEntryFromString) except gdata.service.RequestError, e: raise GooglePhotosException(e.args[0]) def UpdatePhotoBlob(self, photo_or_uri, filename_or_handle, content_type = 'image/jpeg'): """Update a photo's binary data. Needs authentication, see self.ClientLogin() Arguments: photo_or_uri: a gdata.photos.PhotoEntry that will be updated, or a `edit-media' uri pointing to it filename_or_handle: A file-like object or file name where the image/video will be read from content_type (optional): Internet media type (a.k.a. mime type) of media object. Currently Google Photos supports these types: o image/bmp o image/gif o image/jpeg o image/png Images will be converted to jpeg on upload. Defaults to `image/jpeg' Returns: The modified gdata.photos.PhotoEntry Example: p = GetFeed(PhotoUri) p = UpdatePhotoBlob(p, '/tmp/newPic.jpg') It is important that you don't keep the old object around, once it has been updated. See http://code.google.com/apis/gdata/reference.html#Optimistic-concurrency """ try: majtype, mintype = content_type.split('/') assert(mintype in SUPPORTED_UPLOAD_TYPES) except (ValueError, AssertionError): raise GooglePhotosException({'status':GPHOTOS_INVALID_CONTENT_TYPE, 'body':'This is not a valid content type: %s' % content_type, 'reason':'Accepted content types: %s' % \ ['image/'+t for t in SUPPORTED_UPLOAD_TYPES] }) if isinstance(filename_or_handle, (str, unicode)) and \ os.path.exists(filename_or_handle): # it's a file name photoblob = gdata.MediaSource() photoblob.setFile(filename_or_handle, content_type) elif hasattr(filename_or_handle, 'read'):# it's a file-like resource if hasattr(filename_or_handle, 'seek'): filename_or_handle.seek(0) # rewind pointer to the start of the file # gdata.MediaSource needs the content length, so read the whole image file_handle = StringIO.StringIO(filename_or_handle.read()) name = 'image' if hasattr(filename_or_handle, 'name'): name = filename_or_handle.name mediasource = gdata.MediaSource(file_handle, content_type, content_length=file_handle.len, file_name=name) else: #filename_or_handle is not valid raise GooglePhotosException({'status':GPHOTOS_INVALID_ARGUMENT, 'body':'`filename_or_handle` must be a path name or a file-like object', 'reason':'Found %s, not path name or an object with .read() method' % \ type(filename_or_handle) }) if isinstance(photo_or_uri, (str, unicode)): entry_uri = photo_or_uri # it's a uri elif hasattr(photo_or_uri, 'GetEditMediaLink'): entry_uri = photo_or_uri.GetEditMediaLink().href try: return self.Put(photoblob, entry_uri, converter=gdata.photos.PhotoEntryFromString) except gdata.service.RequestError, e: raise GooglePhotosException(e.args[0]) def InsertTag(self, photo_or_uri, tag): """Add a tag (a.k.a. keyword) to a photo. Needs authentication, see self.ClientLogin() Arguments: photo_or_uri: a gdata.photos.PhotoEntry that will be tagged, or a `post' uri pointing to it (string) tag: The tag/keyword Returns: The new gdata.photos.TagEntry Example: p = GetFeed(PhotoUri) tag = InsertTag(p, 'Beautiful sunsets') """ tag = gdata.photos.TagEntry(title=atom.Title(text=tag)) if isinstance(photo_or_uri, (str, unicode)): post_uri = photo_or_uri # it's a uri elif hasattr(photo_or_uri, 'GetEditMediaLink'): post_uri = photo_or_uri.GetPostLink().href try: return self.Post(data=tag, uri=post_uri, converter=gdata.photos.TagEntryFromString) except gdata.service.RequestError, e: raise GooglePhotosException(e.args[0]) def InsertComment(self, photo_or_uri, comment): """Add a comment to a photo. Needs authentication, see self.ClientLogin() Arguments: photo_or_uri: a gdata.photos.PhotoEntry that is about to be commented , or a `post' uri pointing to it (string) comment: The actual comment Returns: The new gdata.photos.CommentEntry Example: p = GetFeed(PhotoUri) tag = InsertComment(p, 'OOOH! I would have loved to be there. Who's that in the back?') """ comment = gdata.photos.CommentEntry(content=atom.Content(text=comment)) if isinstance(photo_or_uri, (str, unicode)): post_uri = photo_or_uri # it's a uri elif hasattr(photo_or_uri, 'GetEditMediaLink'): post_uri = photo_or_uri.GetPostLink().href try: return self.Post(data=comment, uri=post_uri, converter=gdata.photos.CommentEntryFromString) except gdata.service.RequestError, e: raise GooglePhotosException(e.args[0]) def Delete(self, object_or_uri, *args, **kwargs): """Delete an object. Re-implementing the GDataService.Delete method, to add some convenience. Arguments: object_or_uri: Any object that has a GetEditLink() method that returns a link, or a uri to that object. Returns: ? or GooglePhotosException on errors """ try: uri = object_or_uri.GetEditLink().href except AttributeError: uri = object_or_uri try: return gdata.service.GDataService.Delete(self, uri, *args, **kwargs) except gdata.service.RequestError, e: raise GooglePhotosException(e.args[0]) def GetSmallestThumbnail(media_thumbnail_list): """Helper function to get the smallest thumbnail of a list of gdata.media.Thumbnail. Returns gdata.media.Thumbnail """ r = {} for thumb in media_thumbnail_list: r[int(thumb.width)*int(thumb.height)] = thumb keys = r.keys() keys.sort() return r[keys[0]] def ConvertAtomTimestampToEpoch(timestamp): """Helper function to convert a timestamp string, for instance from atom:updated or atom:published, to milliseconds since Unix epoch (a.k.a. POSIX time). `2007-07-22T00:45:10.000Z' -> """ return time.mktime(time.strptime(timestamp, '%Y-%m-%dT%H:%M:%S.000Z')) ## TODO: Timezone aware ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������gdata-2.0.18/src/gdata/health/����������������������������������������������������������������������0000750�0364663�0011610�00000000000�12156625015�016046� 5����������������������������������������������������������������������������������������������������ustar �afshar��������������������������eng�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������gdata-2.0.18/src/gdata/health/__init__.py�����������������������������������������������������������0000640�0364663�0011610�00000015662�12156622363�020175� 0����������������������������������������������������������������������������������������������������ustar �afshar��������������������������eng�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/python # # Copyright 2009 Google Inc. All Rights Reserved. # # 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. """Contains extensions to Atom objects used with Google Health.""" __author__ = 'api.eric@google.com (Eric Bidelman)' import atom import gdata CCR_NAMESPACE = 'urn:astm-org:CCR' METADATA_NAMESPACE = 'http://schemas.google.com/health/metadata' class Ccr(atom.AtomBase): """Represents a Google Health <ContinuityOfCareRecord>.""" _tag = 'ContinuityOfCareRecord' _namespace = CCR_NAMESPACE _children = atom.AtomBase._children.copy() def __init__(self, extension_elements=None, extension_attributes=None, text=None): atom.AtomBase.__init__(self, extension_elements=extension_elements, extension_attributes=extension_attributes, text=text) def GetAlerts(self): """Helper for extracting Alert/Allergy data from the CCR. Returns: A list of ExtensionElements (one for each allergy found) or None if no allergies where found in this CCR. """ try: body = self.FindExtensions('Body')[0] return body.FindChildren('Alerts')[0].FindChildren('Alert') except: return None def GetAllergies(self): """Alias for GetAlerts().""" return self.GetAlerts() def GetProblems(self): """Helper for extracting Problem/Condition data from the CCR. Returns: A list of ExtensionElements (one for each problem found) or None if no problems where found in this CCR. """ try: body = self.FindExtensions('Body')[0] return body.FindChildren('Problems')[0].FindChildren('Problem') except: return None def GetConditions(self): """Alias for GetProblems().""" return self.GetProblems() def GetProcedures(self): """Helper for extracting Procedure data from the CCR. Returns: A list of ExtensionElements (one for each procedure found) or None if no procedures where found in this CCR. """ try: body = self.FindExtensions('Body')[0] return body.FindChildren('Procedures')[0].FindChildren('Procedure') except: return None def GetImmunizations(self): """Helper for extracting Immunization data from the CCR. Returns: A list of ExtensionElements (one for each immunization found) or None if no immunizations where found in this CCR. """ try: body = self.FindExtensions('Body')[0] return body.FindChildren('Immunizations')[0].FindChildren('Immunization') except: return None def GetMedications(self): """Helper for extracting Medication data from the CCR. Returns: A list of ExtensionElements (one for each medication found) or None if no medications where found in this CCR. """ try: body = self.FindExtensions('Body')[0] return body.FindChildren('Medications')[0].FindChildren('Medication') except: return None def GetResults(self): """Helper for extracting Results/Labresults data from the CCR. Returns: A list of ExtensionElements (one for each result found) or None if no results where found in this CCR. """ try: body = self.FindExtensions('Body')[0] return body.FindChildren('Results')[0].FindChildren('Result') except: return None class ProfileEntry(gdata.GDataEntry): """The Google Health version of an Atom Entry.""" _tag = gdata.GDataEntry._tag _namespace = atom.ATOM_NAMESPACE _children = gdata.GDataEntry._children.copy() _attributes = gdata.GDataEntry._attributes.copy() _children['{%s}ContinuityOfCareRecord' % CCR_NAMESPACE] = ('ccr', Ccr) def __init__(self, ccr=None, author=None, category=None, content=None, atom_id=None, link=None, published=None, title=None, updated=None, text=None, extension_elements=None, extension_attributes=None): self.ccr = ccr gdata.GDataEntry.__init__( self, author=author, category=category, content=content, atom_id=atom_id, link=link, published=published, title=title, updated=updated, extension_elements=extension_elements, extension_attributes=extension_attributes, text=text) class ProfileFeed(gdata.GDataFeed): """A feed containing a list of Google Health profile entries.""" _tag = gdata.GDataFeed._tag _namespace = atom.ATOM_NAMESPACE _children = gdata.GDataFeed._children.copy() _attributes = gdata.GDataFeed._attributes.copy() _children['{%s}entry' % atom.ATOM_NAMESPACE] = ('entry', [ProfileEntry]) class ProfileListEntry(gdata.GDataEntry): """The Atom Entry in the Google Health profile list feed.""" _tag = gdata.GDataEntry._tag _namespace = atom.ATOM_NAMESPACE _children = gdata.GDataEntry._children.copy() _attributes = gdata.GDataEntry._attributes.copy() def GetProfileId(self): return self.content.text def GetProfileName(self): return self.title.text class ProfileListFeed(gdata.GDataFeed): """A feed containing a list of Google Health profile list entries.""" _tag = gdata.GDataFeed._tag _namespace = atom.ATOM_NAMESPACE _children = gdata.GDataFeed._children.copy() _attributes = gdata.GDataFeed._attributes.copy() _children['{%s}entry' % atom.ATOM_NAMESPACE] = ('entry', [ProfileListEntry]) def ProfileEntryFromString(xml_string): """Converts an XML string into a ProfileEntry object. Args: xml_string: string The XML describing a Health profile feed entry. Returns: A ProfileEntry object corresponding to the given XML. """ return atom.CreateClassFromXMLString(ProfileEntry, xml_string) def ProfileListEntryFromString(xml_string): """Converts an XML string into a ProfileListEntry object. Args: xml_string: string The XML describing a Health profile list feed entry. Returns: A ProfileListEntry object corresponding to the given XML. """ return atom.CreateClassFromXMLString(ProfileListEntry, xml_string) def ProfileFeedFromString(xml_string): """Converts an XML string into a ProfileFeed object. Args: xml_string: string The XML describing a ProfileFeed feed. Returns: A ProfileFeed object corresponding to the given XML. """ return atom.CreateClassFromXMLString(ProfileFeed, xml_string) def ProfileListFeedFromString(xml_string): """Converts an XML string into a ProfileListFeed object. Args: xml_string: string The XML describing a ProfileListFeed feed. Returns: A ProfileListFeed object corresponding to the given XML. """ return atom.CreateClassFromXMLString(ProfileListFeed, xml_string) ������������������������������������������������������������������������������gdata-2.0.18/src/gdata/health/service.py������������������������������������������������������������0000640�0364663�0011610�00000023427�12156622363�020074� 0����������������������������������������������������������������������������������������������������ustar �afshar��������������������������eng�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/python # # Copyright 2009 Google Inc. All Rights Reserved. # # 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. """HealthService extends GDataService to streamline Google Health API access. HealthService: Provides methods to interact with the profile, profile list, and register/notices feeds. Extends GDataService. HealthProfileQuery: Queries the Google Health Profile feed. HealthProfileListQuery: Queries the Google Health Profile list feed. """ __author__ = 'api.eric@google.com (Eric Bidelman)' import atom import gdata.health import gdata.service class HealthService(gdata.service.GDataService): """Client extension for the Google Health service Document List feed.""" def __init__(self, email=None, password=None, source=None, use_h9_sandbox=False, server='www.google.com', additional_headers=None, **kwargs): """Creates a client for the Google Health service. Args: email: string (optional) The user's email address, used for authentication. password: string (optional) The user's password. source: string (optional) The name of the user's application. use_h9_sandbox: boolean (optional) True to issue requests against the /h9 developer's sandbox. server: string (optional) The name of the server to which a connection will be opened. additional_headers: dictionary (optional) Any additional headers which should be included with CRUD operations. kwargs: The other parameters to pass to gdata.service.GDataService constructor. """ service = use_h9_sandbox and 'weaver' or 'health' gdata.service.GDataService.__init__( self, email=email, password=password, service=service, source=source, server=server, additional_headers=additional_headers, **kwargs) self.ssl = True self.use_h9_sandbox = use_h9_sandbox def __get_service(self): return self.use_h9_sandbox and 'h9' or 'health' def GetProfileFeed(self, query=None, profile_id=None): """Fetches the users Google Health profile feed. Args: query: HealthProfileQuery or string (optional) A query to use on the profile feed. If None, a HealthProfileQuery is constructed. profile_id: string (optional) The profile id to query the profile feed with when using ClientLogin. Note: this parameter is ignored if query is set. Returns: A gdata.health.ProfileFeed object containing the user's Health profile. """ if query is None: projection = profile_id and 'ui' or 'default' uri = HealthProfileQuery( service=self.__get_service(), projection=projection, profile_id=profile_id).ToUri() elif isinstance(query, HealthProfileQuery): uri = query.ToUri() else: uri = query return self.GetFeed(uri, converter=gdata.health.ProfileFeedFromString) def GetProfileListFeed(self, query=None): """Fetches the users Google Health profile feed. Args: query: HealthProfileListQuery or string (optional) A query to use on the profile list feed. If None, a HealthProfileListQuery is constructed to /health/feeds/profile/list or /h9/feeds/profile/list. Returns: A gdata.health.ProfileListFeed object containing the user's list of profiles. """ if not query: uri = HealthProfileListQuery(service=self.__get_service()).ToUri() elif isinstance(query, HealthProfileListQuery): uri = query.ToUri() else: uri = query return self.GetFeed(uri, converter=gdata.health.ProfileListFeedFromString) def SendNotice(self, subject, body=None, content_type='html', ccr=None, profile_id=None): """Sends (posts) a notice to the user's Google Health profile. Args: subject: A string representing the message's subject line. body: string (optional) The message body. content_type: string (optional) The content type of the notice message body. This parameter is only honored when a message body is specified. ccr: string (optional) The CCR XML document to reconcile into the user's profile. profile_id: string (optional) The profile id to work with when using ClientLogin. Note: this parameter is ignored if query is set. Returns: A gdata.health.ProfileEntry object of the posted entry. """ if body: content = atom.Content(content_type=content_type, text=body) else: content = body entry = gdata.GDataEntry( title=atom.Title(text=subject), content=content, extension_elements=[atom.ExtensionElementFromString(ccr)]) projection = profile_id and 'ui' or 'default' query = HealthRegisterQuery(service=self.__get_service(), projection=projection, profile_id=profile_id) return self.Post(entry, query.ToUri(), converter=gdata.health.ProfileEntryFromString) class HealthProfileQuery(gdata.service.Query): """Object used to construct a URI to query the Google Health profile feed.""" def __init__(self, service='health', feed='feeds/profile', projection='default', profile_id=None, text_query=None, params=None, categories=None): """Constructor for Health profile feed query. Args: service: string (optional) The service to query. Either 'health' or 'h9'. feed: string (optional) The path for the feed. The default value is 'feeds/profile'. projection: string (optional) The visibility of the data. Possible values are 'default' for AuthSub and 'ui' for ClientLogin. If this value is set to 'ui', the profile_id parameter should also be set. profile_id: string (optional) The profile id to query. This should only be used when using ClientLogin. text_query: str (optional) The contents of the q query parameter. The contents of the text_query are URL escaped upon conversion to a URI. Note: this parameter can only be used on the register feed using ClientLogin. params: dict (optional) Parameter value string pairs which become URL params when translated to a URI. These parameters are added to the query's items. categories: list (optional) List of category strings which should be included as query categories. See gdata.service.Query for additional documentation. """ self.service = service self.profile_id = profile_id self.projection = projection gdata.service.Query.__init__(self, feed=feed, text_query=text_query, params=params, categories=categories) def ToUri(self): """Generates a URI from the query parameters set in the object. Returns: A string containing the URI used to retrieve entries from the Health profile feed. """ old_feed = self.feed self.feed = '/'.join([self.service, old_feed, self.projection]) if self.profile_id: self.feed += '/' + self.profile_id self.feed = '/%s' % (self.feed,) new_feed = gdata.service.Query.ToUri(self) self.feed = old_feed return new_feed class HealthProfileListQuery(gdata.service.Query): """Object used to construct a URI to query a Health profile list feed.""" def __init__(self, service='health', feed='feeds/profile/list'): """Constructor for Health profile list feed query. Args: service: string (optional) The service to query. Either 'health' or 'h9'. feed: string (optional) The path for the feed. The default value is 'feeds/profile/list'. """ gdata.service.Query.__init__(self, feed) self.service = service def ToUri(self): """Generates a URI from the query parameters set in the object. Returns: A string containing the URI used to retrieve entries from the profile list feed. """ return '/%s' % ('/'.join([self.service, self.feed]),) class HealthRegisterQuery(gdata.service.Query): """Object used to construct a URI to query a Health register/notice feed.""" def __init__(self, service='health', feed='feeds/register', projection='default', profile_id=None): """Constructor for Health profile list feed query. Args: service: string (optional) The service to query. Either 'health' or 'h9'. feed: string (optional) The path for the feed. The default value is 'feeds/register'. projection: string (optional) The visibility of the data. Possible values are 'default' for AuthSub and 'ui' for ClientLogin. If this value is set to 'ui', the profile_id parameter should also be set. profile_id: string (optional) The profile id to query. This should only be used when using ClientLogin. """ gdata.service.Query.__init__(self, feed) self.service = service self.projection = projection self.profile_id = profile_id def ToUri(self): """Generates a URI from the query parameters set in the object. Returns: A string containing the URI needed to interact with the register feed. """ old_feed = self.feed self.feed = '/'.join([self.service, old_feed, self.projection]) new_feed = gdata.service.Query.ToUri(self) self.feed = old_feed if self.profile_id: new_feed += '/' + self.profile_id return '/%s' % (new_feed,) �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������gdata-2.0.18/src/gdata/finance/���������������������������������������������������������������������0000750�0364663�0011610�00000000000�12156625015�016204� 5����������������������������������������������������������������������������������������������������ustar �afshar��������������������������eng�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������gdata-2.0.18/src/gdata/finance/data.py��������������������������������������������������������������0000640�0364663�0011610�00000007502�12156622363�017477� 0����������������������������������������������������������������������������������������������������ustar �afshar��������������������������eng�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/python # # Copyright (C) 2009 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. """Contains the data classes of the Google Finance Portfolio Data API""" __author__ = 'j.s@google.com (Jeff Scudder)' import atom.core import atom.data import gdata.data import gdata.opensearch.data GF_TEMPLATE = '{http://schemas.google.com/finance/2007/}%s' class Commission(atom.core.XmlElement): """Commission for the transaction""" _qname = GF_TEMPLATE % 'commission' money = [gdata.data.Money] class CostBasis(atom.core.XmlElement): """Cost basis for the portfolio or position""" _qname = GF_TEMPLATE % 'costBasis' money = [gdata.data.Money] class DaysGain(atom.core.XmlElement): """Today's gain for the portfolio or position""" _qname = GF_TEMPLATE % 'daysGain' money = [gdata.data.Money] class Gain(atom.core.XmlElement): """Total gain for the portfolio or position""" _qname = GF_TEMPLATE % 'gain' money = [gdata.data.Money] class MarketValue(atom.core.XmlElement): """Market value for the portfolio or position""" _qname = GF_TEMPLATE % 'marketValue' money = [gdata.data.Money] class PortfolioData(atom.core.XmlElement): """Data for the portfolio""" _qname = GF_TEMPLATE % 'portfolioData' return_overall = 'returnOverall' currency_code = 'currencyCode' return3y = 'return3y' return4w = 'return4w' market_value = MarketValue return_y_t_d = 'returnYTD' cost_basis = CostBasis gain_percentage = 'gainPercentage' days_gain = DaysGain return3m = 'return3m' return5y = 'return5y' return1w = 'return1w' gain = Gain return1y = 'return1y' class PortfolioEntry(gdata.data.GDEntry): """Describes an entry in a feed of Finance portfolios""" portfolio_data = PortfolioData class PortfolioFeed(gdata.data.GDFeed): """Describes a Finance portfolio feed""" entry = [PortfolioEntry] class PositionData(atom.core.XmlElement): """Data for the position""" _qname = GF_TEMPLATE % 'positionData' return_y_t_d = 'returnYTD' return5y = 'return5y' return_overall = 'returnOverall' cost_basis = CostBasis return3y = 'return3y' return1y = 'return1y' return4w = 'return4w' shares = 'shares' days_gain = DaysGain gain_percentage = 'gainPercentage' market_value = MarketValue gain = Gain return3m = 'return3m' return1w = 'return1w' class Price(atom.core.XmlElement): """Price of the transaction""" _qname = GF_TEMPLATE % 'price' money = [gdata.data.Money] class Symbol(atom.core.XmlElement): """Stock symbol for the company""" _qname = GF_TEMPLATE % 'symbol' symbol = 'symbol' exchange = 'exchange' full_name = 'fullName' class PositionEntry(gdata.data.GDEntry): """Describes an entry in a feed of Finance positions""" symbol = Symbol position_data = PositionData class PositionFeed(gdata.data.GDFeed): """Describes a Finance position feed""" entry = [PositionEntry] class TransactionData(atom.core.XmlElement): """Data for the transction""" _qname = GF_TEMPLATE % 'transactionData' shares = 'shares' notes = 'notes' date = 'date' type = 'type' commission = Commission price = Price class TransactionEntry(gdata.data.GDEntry): """Describes an entry in a feed of Finance transactions""" transaction_data = TransactionData class TransactionFeed(gdata.data.GDFeed): """Describes a Finance transaction feed""" entry = [TransactionEntry] ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������gdata-2.0.18/src/gdata/finance/__init__.py����������������������������������������������������������0000640�0364663�0011610�00000036045�12156622363�020331� 0����������������������������������������������������������������������������������������������������ustar �afshar��������������������������eng�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/python # # Copyright (C) 2009 Tan Swee Heng # # 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. """Contains extensions to Atom objects used with Google Finance.""" __author__ = 'thesweeheng@gmail.com' import atom import gdata GD_NAMESPACE = 'http://schemas.google.com/g/2005' GF_NAMESPACE = 'http://schemas.google.com/finance/2007' class Money(atom.AtomBase): """The <gd:money> element.""" _tag = 'money' _namespace = GD_NAMESPACE _attributes = atom.AtomBase._attributes.copy() _attributes['amount'] = 'amount' _attributes['currencyCode'] = 'currency_code' def __init__(self, amount=None, currency_code=None, **kwargs): self.amount = amount self.currency_code = currency_code atom.AtomBase.__init__(self, **kwargs) def __str__(self): return "%s %s" % (self.amount, self.currency_code) def MoneyFromString(xml_string): return atom.CreateClassFromXMLString(Money, xml_string) class _Monies(atom.AtomBase): """An element containing multiple <gd:money> in multiple currencies.""" _namespace = GF_NAMESPACE _children = atom.AtomBase._children.copy() _children['{%s}money' % GD_NAMESPACE] = ('money', [Money]) def __init__(self, money=None, **kwargs): self.money = money or [] atom.AtomBase.__init__(self, **kwargs) def __str__(self): return " / ".join(["%s" % i for i in self.money]) class CostBasis(_Monies): """The <gf:costBasis> element.""" _tag = 'costBasis' def CostBasisFromString(xml_string): return atom.CreateClassFromXMLString(CostBasis, xml_string) class DaysGain(_Monies): """The <gf:daysGain> element.""" _tag = 'daysGain' def DaysGainFromString(xml_string): return atom.CreateClassFromXMLString(DaysGain, xml_string) class Gain(_Monies): """The <gf:gain> element.""" _tag = 'gain' def GainFromString(xml_string): return atom.CreateClassFromXMLString(Gain, xml_string) class MarketValue(_Monies): """The <gf:marketValue> element.""" _tag = 'gain' _tag = 'marketValue' def MarketValueFromString(xml_string): return atom.CreateClassFromXMLString(MarketValue, xml_string) class Commission(_Monies): """The <gf:commission> element.""" _tag = 'commission' def CommissionFromString(xml_string): return atom.CreateClassFromXMLString(Commission, xml_string) class Price(_Monies): """The <gf:price> element.""" _tag = 'price' def PriceFromString(xml_string): return atom.CreateClassFromXMLString(Price, xml_string) class Symbol(atom.AtomBase): """The <gf:symbol> element.""" _tag = 'symbol' _namespace = GF_NAMESPACE _attributes = atom.AtomBase._attributes.copy() _attributes['fullName'] = 'full_name' _attributes['exchange'] = 'exchange' _attributes['symbol'] = 'symbol' def __init__(self, full_name=None, exchange=None, symbol=None, **kwargs): self.full_name = full_name self.exchange = exchange self.symbol = symbol atom.AtomBase.__init__(self, **kwargs) def __str__(self): return "%s:%s (%s)" % (self.exchange, self.symbol, self.full_name) def SymbolFromString(xml_string): return atom.CreateClassFromXMLString(Symbol, xml_string) class TransactionData(atom.AtomBase): """The <gf:transactionData> element.""" _tag = 'transactionData' _namespace = GF_NAMESPACE _attributes = atom.AtomBase._attributes.copy() _attributes['type'] = 'type' _attributes['date'] = 'date' _attributes['shares'] = 'shares' _attributes['notes'] = 'notes' _children = atom.AtomBase._children.copy() _children['{%s}commission' % GF_NAMESPACE] = ('commission', Commission) _children['{%s}price' % GF_NAMESPACE] = ('price', Price) def __init__(self, type=None, date=None, shares=None, notes=None, commission=None, price=None, **kwargs): self.type = type self.date = date self.shares = shares self.notes = notes self.commission = commission self.price = price atom.AtomBase.__init__(self, **kwargs) def TransactionDataFromString(xml_string): return atom.CreateClassFromXMLString(TransactionData, xml_string) class TransactionEntry(gdata.GDataEntry): """An entry of the transaction feed. A TransactionEntry contains TransactionData such as the transaction type (Buy, Sell, Sell Short, or Buy to Cover), the number of units, the date, the price, any commission, and any notes. """ _tag = 'entry' _namespace = atom.ATOM_NAMESPACE _children = gdata.GDataEntry._children.copy() _children['{%s}transactionData' % GF_NAMESPACE] = ( 'transaction_data', TransactionData) def __init__(self, transaction_data=None, **kwargs): self.transaction_data = transaction_data gdata.GDataEntry.__init__(self, **kwargs) def transaction_id(self): return self.id.text.split("/")[-1] transaction_id = property(transaction_id, doc='The transaction ID.') def TransactionEntryFromString(xml_string): return atom.CreateClassFromXMLString(TransactionEntry, xml_string) class TransactionFeed(gdata.GDataFeed): """A feed that lists all of the transactions that have been recorded for a particular position. A transaction is a collection of information about an instance of buying or selling a particular security. The TransactionFeed lists all of the transactions that have been recorded for a particular position as a list of TransactionEntries. """ _tag = 'feed' _namespace = atom.ATOM_NAMESPACE _children = gdata.GDataFeed._children.copy() _children['{%s}entry' % atom.ATOM_NAMESPACE] = ('entry', [TransactionEntry]) def TransactionFeedFromString(xml_string): return atom.CreateClassFromXMLString(TransactionFeed, xml_string) class TransactionFeedLink(atom.AtomBase): """Link to TransactionFeed embedded in PositionEntry. If a PositionFeed is queried with transactions='true', TransactionFeeds are inlined in the returned PositionEntries. These TransactionFeeds are accessible via TransactionFeedLink's feed attribute. """ _tag = 'feedLink' _namespace = GD_NAMESPACE _attributes = atom.AtomBase._attributes.copy() _attributes['href'] = 'href' _children = atom.AtomBase._children.copy() _children['{%s}feed' % atom.ATOM_NAMESPACE] = ( 'feed', TransactionFeed) def __init__(self, href=None, feed=None, **kwargs): self.href = href self.feed = feed atom.AtomBase.__init__(self, **kwargs) class PositionData(atom.AtomBase): """The <gf:positionData> element.""" _tag = 'positionData' _namespace = GF_NAMESPACE _attributes = atom.AtomBase._attributes.copy() _attributes['gainPercentage'] = 'gain_percentage' _attributes['return1w'] = 'return1w' _attributes['return4w'] = 'return4w' _attributes['return3m'] = 'return3m' _attributes['returnYTD'] = 'returnYTD' _attributes['return1y'] = 'return1y' _attributes['return3y'] = 'return3y' _attributes['return5y'] = 'return5y' _attributes['returnOverall'] = 'return_overall' _attributes['shares'] = 'shares' _children = atom.AtomBase._children.copy() _children['{%s}costBasis' % GF_NAMESPACE] = ('cost_basis', CostBasis) _children['{%s}daysGain' % GF_NAMESPACE] = ('days_gain', DaysGain) _children['{%s}gain' % GF_NAMESPACE] = ('gain', Gain) _children['{%s}marketValue' % GF_NAMESPACE] = ('market_value', MarketValue) def __init__(self, gain_percentage=None, return1w=None, return4w=None, return3m=None, returnYTD=None, return1y=None, return3y=None, return5y=None, return_overall=None, shares=None, cost_basis=None, days_gain=None, gain=None, market_value=None, **kwargs): self.gain_percentage = gain_percentage self.return1w = return1w self.return4w = return4w self.return3m = return3m self.returnYTD = returnYTD self.return1y = return1y self.return3y = return3y self.return5y = return5y self.return_overall = return_overall self.shares = shares self.cost_basis = cost_basis self.days_gain = days_gain self.gain = gain self.market_value = market_value atom.AtomBase.__init__(self, **kwargs) def PositionDataFromString(xml_string): return atom.CreateClassFromXMLString(PositionData, xml_string) class PositionEntry(gdata.GDataEntry): """An entry of the position feed. A PositionEntry contains the ticker exchange and Symbol for a stock, mutual fund, or other security, along with PositionData such as the number of units of that security that the user holds, and performance statistics. """ _tag = 'entry' _namespace = atom.ATOM_NAMESPACE _children = gdata.GDataEntry._children.copy() _children['{%s}positionData' % GF_NAMESPACE] = ( 'position_data', PositionData) _children['{%s}symbol' % GF_NAMESPACE] = ('symbol', Symbol) _children['{%s}feedLink' % GD_NAMESPACE] = ( 'feed_link', TransactionFeedLink) def __init__(self, position_data=None, symbol=None, feed_link=None, **kwargs): self.position_data = position_data self.symbol = symbol self.feed_link = feed_link gdata.GDataEntry.__init__(self, **kwargs) def position_title(self): return self.title.text position_title = property(position_title, doc='The position title as a string (i.e. position.title.text).') def ticker_id(self): return self.id.text.split("/")[-1] ticker_id = property(ticker_id, doc='The position TICKER ID.') def transactions(self): if self.feed_link.feed: return self.feed_link.feed.entry else: return None transactions = property(transactions, doc=""" Inlined TransactionEntries are returned if PositionFeed is queried with transactions='true'.""") def PositionEntryFromString(xml_string): return atom.CreateClassFromXMLString(PositionEntry, xml_string) class PositionFeed(gdata.GDataFeed): """A feed that lists all of the positions in a particular portfolio. A position is a collection of information about a security that the user holds. The PositionFeed lists all of the positions in a particular portfolio as a list of PositionEntries. """ _tag = 'feed' _namespace = atom.ATOM_NAMESPACE _children = gdata.GDataFeed._children.copy() _children['{%s}entry' % atom.ATOM_NAMESPACE] = ('entry', [PositionEntry]) def PositionFeedFromString(xml_string): return atom.CreateClassFromXMLString(PositionFeed, xml_string) class PositionFeedLink(atom.AtomBase): """Link to PositionFeed embedded in PortfolioEntry. If a PortfolioFeed is queried with positions='true', the PositionFeeds are inlined in the returned PortfolioEntries. These PositionFeeds are accessible via PositionFeedLink's feed attribute. """ _tag = 'feedLink' _namespace = GD_NAMESPACE _attributes = atom.AtomBase._attributes.copy() _attributes['href'] = 'href' _children = atom.AtomBase._children.copy() _children['{%s}feed' % atom.ATOM_NAMESPACE] = ( 'feed', PositionFeed) def __init__(self, href=None, feed=None, **kwargs): self.href = href self.feed = feed atom.AtomBase.__init__(self, **kwargs) class PortfolioData(atom.AtomBase): """The <gf:portfolioData> element.""" _tag = 'portfolioData' _namespace = GF_NAMESPACE _attributes = atom.AtomBase._attributes.copy() _attributes['currencyCode'] = 'currency_code' _attributes['gainPercentage'] = 'gain_percentage' _attributes['return1w'] = 'return1w' _attributes['return4w'] = 'return4w' _attributes['return3m'] = 'return3m' _attributes['returnYTD'] = 'returnYTD' _attributes['return1y'] = 'return1y' _attributes['return3y'] = 'return3y' _attributes['return5y'] = 'return5y' _attributes['returnOverall'] = 'return_overall' _children = atom.AtomBase._children.copy() _children['{%s}costBasis' % GF_NAMESPACE] = ('cost_basis', CostBasis) _children['{%s}daysGain' % GF_NAMESPACE] = ('days_gain', DaysGain) _children['{%s}gain' % GF_NAMESPACE] = ('gain', Gain) _children['{%s}marketValue' % GF_NAMESPACE] = ('market_value', MarketValue) def __init__(self, currency_code=None, gain_percentage=None, return1w=None, return4w=None, return3m=None, returnYTD=None, return1y=None, return3y=None, return5y=None, return_overall=None, cost_basis=None, days_gain=None, gain=None, market_value=None, **kwargs): self.currency_code = currency_code self.gain_percentage = gain_percentage self.return1w = return1w self.return4w = return4w self.return3m = return3m self.returnYTD = returnYTD self.return1y = return1y self.return3y = return3y self.return5y = return5y self.return_overall = return_overall self.cost_basis = cost_basis self.days_gain = days_gain self.gain = gain self.market_value = market_value atom.AtomBase.__init__(self, **kwargs) def PortfolioDataFromString(xml_string): return atom.CreateClassFromXMLString(PortfolioData, xml_string) class PortfolioEntry(gdata.GDataEntry): """An entry of the PortfolioFeed. A PortfolioEntry contains the portfolio's title along with PortfolioData such as currency, total market value, and overall performance statistics. """ _tag = 'entry' _namespace = atom.ATOM_NAMESPACE _children = gdata.GDataEntry._children.copy() _children['{%s}portfolioData' % GF_NAMESPACE] = ( 'portfolio_data', PortfolioData) _children['{%s}feedLink' % GD_NAMESPACE] = ( 'feed_link', PositionFeedLink) def __init__(self, portfolio_data=None, feed_link=None, **kwargs): self.portfolio_data = portfolio_data self.feed_link = feed_link gdata.GDataEntry.__init__(self, **kwargs) def portfolio_title(self): return self.title.text def set_portfolio_title(self, portfolio_title): self.title = atom.Title(text=portfolio_title, title_type='text') portfolio_title = property(portfolio_title, set_portfolio_title, doc='The portfolio title as a string (i.e. portfolio.title.text).') def portfolio_id(self): return self.id.text.split("/")[-1] portfolio_id = property(portfolio_id, doc='The portfolio ID. Do not confuse with portfolio.id.') def positions(self): if self.feed_link.feed: return self.feed_link.feed.entry else: return None positions = property(positions, doc=""" Inlined PositionEntries are returned if PortfolioFeed was queried with positions='true'.""") def PortfolioEntryFromString(xml_string): return atom.CreateClassFromXMLString(PortfolioEntry, xml_string) class PortfolioFeed(gdata.GDataFeed): """A feed that lists all of the user's portfolios. A portfolio is a collection of positions that the user holds in various securities, plus metadata. The PortfolioFeed lists all of the user's portfolios as a list of PortfolioEntries. """ _tag = 'feed' _namespace = atom.ATOM_NAMESPACE _children = gdata.GDataFeed._children.copy() _children['{%s}entry' % atom.ATOM_NAMESPACE] = ('entry', [PortfolioEntry]) def PortfolioFeedFromString(xml_string): return atom.CreateClassFromXMLString(PortfolioFeed, xml_string) �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������gdata-2.0.18/src/gdata/finance/service.py�����������������������������������������������������������0000640�0364663�0011610�00000021416�12156622363�020226� 0����������������������������������������������������������������������������������������������������ustar �afshar��������������������������eng�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/python # # Copyright (C) 2009 Tan Swee Heng # # 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. """Classes to interact with the Google Finance server.""" __author__ = 'thesweeheng@gmail.com' import gdata.service import gdata.finance import atom class PortfolioQuery(gdata.service.Query): """A query object for the list of a user's portfolios.""" def returns(self): return self.get('returns', False) def set_returns(self, value): if value is 'true' or value is True: self['returns'] = 'true' returns = property(returns, set_returns, doc="The returns query parameter") def positions(self): return self.get('positions', False) def set_positions(self, value): if value is 'true' or value is True: self['positions'] = 'true' positions = property(positions, set_positions, doc="The positions query parameter") class PositionQuery(gdata.service.Query): """A query object for the list of a user's positions in a portfolio.""" def returns(self): return self.get('returns', False) def set_returns(self, value): if value is 'true' or value is True: self['returns'] = 'true' returns = property(returns, set_returns, doc="The returns query parameter") def transactions(self): return self.get('transactions', False) def set_transactions(self, value): if value is 'true' or value is True: self['transactions'] = 'true' transactions = property(transactions, set_transactions, doc="The transactions query parameter") class FinanceService(gdata.service.GDataService): def __init__(self, email=None, password=None, source=None, server='finance.google.com', **kwargs): """Creates a client for the Finance service. Args: email: string (optional) The user's email address, used for authentication. password: string (optional) The user's password. source: string (optional) The name of the user's application. server: string (optional) The name of the server to which a connection will be opened. Default value: 'finance.google.com'. **kwargs: The other parameters to pass to gdata.service.GDataService constructor. """ gdata.service.GDataService.__init__(self, email=email, password=password, service='finance', server=server, **kwargs) def GetPortfolioFeed(self, query=None): uri = '/finance/feeds/default/portfolios' if query: uri = PortfolioQuery(feed=uri, params=query).ToUri() return self.Get(uri, converter=gdata.finance.PortfolioFeedFromString) def GetPositionFeed(self, portfolio_entry=None, portfolio_id=None, query=None): """ Args: portfolio_entry: PortfolioEntry (optional; see Notes) portfolio_id: string (optional; see Notes) This may be obtained from a PortfolioEntry's portfolio_id attribute. query: PortfolioQuery (optional) Notes: Either a PortfolioEntry OR a portfolio ID must be provided. """ if portfolio_entry: uri = portfolio_entry.GetSelfLink().href + '/positions' elif portfolio_id: uri = '/finance/feeds/default/portfolios/%s/positions' % portfolio_id if query: uri = PositionQuery(feed=uri, params=query).ToUri() return self.Get(uri, converter=gdata.finance.PositionFeedFromString) def GetTransactionFeed(self, position_entry=None, portfolio_id=None, ticker_id=None): """ Args: position_entry: PositionEntry (optional; see Notes) portfolio_id: string (optional; see Notes) This may be obtained from a PortfolioEntry's portfolio_id attribute. ticker_id: string (optional; see Notes) This may be obtained from a PositionEntry's ticker_id attribute. Alternatively it can be constructed using the security's exchange and symbol, e.g. 'NASDAQ:GOOG' Notes: Either a PositionEntry OR (a portfolio ID AND ticker ID) must be provided. """ if position_entry: uri = position_entry.GetSelfLink().href + '/transactions' elif portfolio_id and ticker_id: uri = '/finance/feeds/default/portfolios/%s/positions/%s/transactions' \ % (portfolio_id, ticker_id) return self.Get(uri, converter=gdata.finance.TransactionFeedFromString) def GetPortfolio(self, portfolio_id=None, query=None): uri = '/finance/feeds/default/portfolios/%s' % portfolio_id if query: uri = PortfolioQuery(feed=uri, params=query).ToUri() return self.Get(uri, converter=gdata.finance.PortfolioEntryFromString) def AddPortfolio(self, portfolio_entry=None): uri = '/finance/feeds/default/portfolios' return self.Post(portfolio_entry, uri, converter=gdata.finance.PortfolioEntryFromString) def UpdatePortfolio(self, portfolio_entry=None): uri = portfolio_entry.GetEditLink().href return self.Put(portfolio_entry, uri, converter=gdata.finance.PortfolioEntryFromString) def DeletePortfolio(self, portfolio_entry=None): uri = portfolio_entry.GetEditLink().href return self.Delete(uri) def GetPosition(self, portfolio_id=None, ticker_id=None, query=None): uri = '/finance/feeds/default/portfolios/%s/positions/%s' \ % (portfolio_id, ticker_id) if query: uri = PositionQuery(feed=uri, params=query).ToUri() return self.Get(uri, converter=gdata.finance.PositionEntryFromString) def DeletePosition(self, position_entry=None, portfolio_id=None, ticker_id=None, transaction_feed=None): """A position is deleted by deleting all its transactions. Args: position_entry: PositionEntry (optional; see Notes) portfolio_id: string (optional; see Notes) This may be obtained from a PortfolioEntry's portfolio_id attribute. ticker_id: string (optional; see Notes) This may be obtained from a PositionEntry's ticker_id attribute. Alternatively it can be constructed using the security's exchange and symbol, e.g. 'NASDAQ:GOOG' transaction_feed: TransactionFeed (optional; see Notes) Notes: Either a PositionEntry OR (a portfolio ID AND ticker ID) OR a TransactionFeed must be provided. """ if transaction_feed: feed = transaction_feed else: if position_entry: feed = self.GetTransactionFeed(position_entry=position_entry) elif portfolio_id and ticker_id: feed = self.GetTransactionFeed( portfolio_id=portfolio_id, ticker_id=ticker_id) for txn in feed.entry: self.DeleteTransaction(txn) return True def GetTransaction(self, portfolio_id=None, ticker_id=None, transaction_id=None): uri = '/finance/feeds/default/portfolios/%s/positions/%s/transactions/%s' \ % (portfolio_id, ticker_id, transaction_id) return self.Get(uri, converter=gdata.finance.TransactionEntryFromString) def AddTransaction(self, transaction_entry=None, transaction_feed = None, position_entry=None, portfolio_id=None, ticker_id=None): """ Args: transaction_entry: TransactionEntry (required) transaction_feed: TransactionFeed (optional; see Notes) position_entry: PositionEntry (optional; see Notes) portfolio_id: string (optional; see Notes) This may be obtained from a PortfolioEntry's portfolio_id attribute. ticker_id: string (optional; see Notes) This may be obtained from a PositionEntry's ticker_id attribute. Alternatively it can be constructed using the security's exchange and symbol, e.g. 'NASDAQ:GOOG' Notes: Either a TransactionFeed OR a PositionEntry OR (a portfolio ID AND ticker ID) must be provided. """ if transaction_feed: uri = transaction_feed.GetPostLink().href elif position_entry: uri = position_entry.GetSelfLink().href + '/transactions' elif portfolio_id and ticker_id: uri = '/finance/feeds/default/portfolios/%s/positions/%s/transactions' \ % (portfolio_id, ticker_id) return self.Post(transaction_entry, uri, converter=gdata.finance.TransactionEntryFromString) def UpdateTransaction(self, transaction_entry=None): uri = transaction_entry.GetEditLink().href return self.Put(transaction_entry, uri, converter=gdata.finance.TransactionEntryFromString) def DeleteTransaction(self, transaction_entry=None): uri = transaction_entry.GetEditLink().href return self.Delete(uri) ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������gdata-2.0.18/src/gdata/geo/�������������������������������������������������������������������������0000750�0364663�0011610�00000000000�12156625015�015353� 5����������������������������������������������������������������������������������������������������ustar �afshar��������������������������eng�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������gdata-2.0.18/src/gdata/geo/data.py������������������������������������������������������������������0000640�0364663�0011610�00000004521�12156622363�016644� 0����������������������������������������������������������������������������������������������������ustar �afshar��������������������������eng�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/python # # Copyright (C) 2009 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. """Contains the data classes of the Geography Extension""" __author__ = 'j.s@google.com (Jeff Scudder)' import atom.core GEORSS_TEMPLATE = '{http://www.georss.org/georss/}%s' GML_TEMPLATE = '{http://www.opengis.net/gml/}%s' GEO_TEMPLATE = '{http://www.w3.org/2003/01/geo/wgs84_pos#/}%s' class GeoLat(atom.core.XmlElement): """Describes a W3C latitude.""" _qname = GEO_TEMPLATE % 'lat' class GeoLong(atom.core.XmlElement): """Describes a W3C longitude.""" _qname = GEO_TEMPLATE % 'long' class GeoRssBox(atom.core.XmlElement): """Describes a geographical region.""" _qname = GEORSS_TEMPLATE % 'box' class GeoRssPoint(atom.core.XmlElement): """Describes a geographical location.""" _qname = GEORSS_TEMPLATE % 'point' class GmlLowerCorner(atom.core.XmlElement): """Describes a lower corner of a region.""" _qname = GML_TEMPLATE % 'lowerCorner' class GmlPos(atom.core.XmlElement): """Describes a latitude and longitude.""" _qname = GML_TEMPLATE % 'pos' class GmlPoint(atom.core.XmlElement): """Describes a particular geographical point.""" _qname = GML_TEMPLATE % 'Point' pos = GmlPos class GmlUpperCorner(atom.core.XmlElement): """Describes an upper corner of a region.""" _qname = GML_TEMPLATE % 'upperCorner' class GmlEnvelope(atom.core.XmlElement): """Describes a Gml geographical region.""" _qname = GML_TEMPLATE % 'Envelope' lower_corner = GmlLowerCorner upper_corner = GmlUpperCorner class GeoRssWhere(atom.core.XmlElement): """Describes a geographical location or region.""" _qname = GEORSS_TEMPLATE % 'where' Point = GmlPoint Envelope = GmlEnvelope class W3CPoint(atom.core.XmlElement): """Describes a W3C geographical location.""" _qname = GEO_TEMPLATE % 'Point' long = GeoLong lat = GeoLat �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������gdata-2.0.18/src/gdata/geo/__init__.py��������������������������������������������������������������0000640�0364663�0011610�00000013566�12156622363�017503� 0����������������������������������������������������������������������������������������������������ustar �afshar��������������������������eng�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# -*-*- encoding: utf-8 -*-*- # # This is gdata.photos.geo, implementing geological positioning in gdata structures # # $Id: __init__.py 81 2007-10-03 14:41:42Z havard.gulldahl $ # # Copyright 2007 HÃ¥vard Gulldahl # Portions copyright 2007 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. """Picasa Web Albums uses the georss and gml namespaces for elements defined in the GeoRSS and Geography Markup Language specifications. Specifically, Picasa Web Albums uses the following elements: georss:where gml:Point gml:pos http://code.google.com/apis/picasaweb/reference.html#georss_reference Picasa Web Albums also accepts geographic-location data in two other formats: W3C format and plain-GeoRSS (without GML) format. """ # #Over the wire, the Picasa Web Albums only accepts and sends the #elements mentioned above, but this module will let you seamlessly convert #between the different formats (TODO 2007-10-18 hg) __author__ = u'havard@gulldahl.no'# (HÃ¥vard Gulldahl)' #BUG: api chokes on non-ascii chars in __author__ __license__ = 'Apache License v2' import atom import gdata GEO_NAMESPACE = 'http://www.w3.org/2003/01/geo/wgs84_pos#' GML_NAMESPACE = 'http://www.opengis.net/gml' GEORSS_NAMESPACE = 'http://www.georss.org/georss' class GeoBaseElement(atom.AtomBase): """Base class for elements. To add new elements, you only need to add the element tag name to self._tag and the namespace to self._namespace """ _tag = '' _namespace = GML_NAMESPACE _children = atom.AtomBase._children.copy() _attributes = atom.AtomBase._attributes.copy() def __init__(self, name=None, extension_elements=None, extension_attributes=None, text=None): self.name = name self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} class Pos(GeoBaseElement): """(string) Specifies a latitude and longitude, separated by a space, e.g. `35.669998 139.770004'""" _tag = 'pos' def PosFromString(xml_string): return atom.CreateClassFromXMLString(Pos, xml_string) class Point(GeoBaseElement): """(container) Specifies a particular geographical point, by means of a <gml:pos> element.""" _tag = 'Point' _children = atom.AtomBase._children.copy() _children['{%s}pos' % GML_NAMESPACE] = ('pos', Pos) def __init__(self, pos=None, extension_elements=None, extension_attributes=None, text=None): GeoBaseElement.__init__(self, extension_elements=extension_elements, extension_attributes=extension_attributes, text=text) if pos is None: pos = Pos() self.pos=pos def PointFromString(xml_string): return atom.CreateClassFromXMLString(Point, xml_string) class Where(GeoBaseElement): """(container) Specifies a geographical location or region. A container element, containing a single <gml:Point> element. (Not to be confused with <gd:where>.) Note that the (only) child attribute, .Point, is title-cased. This reflects the names of elements in the xml stream (principle of least surprise). As a convenience, you can get a tuple of (lat, lon) with Where.location(), and set the same data with Where.setLocation( (lat, lon) ). Similarly, there are methods to set and get only latitude and longitude. """ _tag = 'where' _namespace = GEORSS_NAMESPACE _children = atom.AtomBase._children.copy() _children['{%s}Point' % GML_NAMESPACE] = ('Point', Point) def __init__(self, point=None, extension_elements=None, extension_attributes=None, text=None): GeoBaseElement.__init__(self, extension_elements=extension_elements, extension_attributes=extension_attributes, text=text) if point is None: point = Point() self.Point=point def location(self): "(float, float) Return Where.Point.pos.text as a (lat,lon) tuple" try: return tuple([float(z) for z in self.Point.pos.text.split(' ')]) except AttributeError: return tuple() def set_location(self, latlon): """(bool) Set Where.Point.pos.text from a (lat,lon) tuple. Arguments: lat (float): The latitude in degrees, from -90.0 to 90.0 lon (float): The longitude in degrees, from -180.0 to 180.0 Returns True on success. """ assert(isinstance(latlon[0], float)) assert(isinstance(latlon[1], float)) try: self.Point.pos.text = "%s %s" % (latlon[0], latlon[1]) return True except AttributeError: return False def latitude(self): "(float) Get the latitude value of the geo-tag. See also .location()" lat, lon = self.location() return lat def longitude(self): "(float) Get the longtitude value of the geo-tag. See also .location()" lat, lon = self.location() return lon longtitude = longitude def set_latitude(self, lat): """(bool) Set the latitude value of the geo-tag. Args: lat (float): The new latitude value See also .set_location() """ _lat, lon = self.location() return self.set_location(lat, lon) def set_longitude(self, lon): """(bool) Set the longtitude value of the geo-tag. Args: lat (float): The new latitude value See also .set_location() """ lat, _lon = self.location() return self.set_location(lat, lon) set_longtitude = set_longitude def WhereFromString(xml_string): return atom.CreateClassFromXMLString(Where, xml_string) ������������������������������������������������������������������������������������������������������������������������������������������gdata-2.0.18/src/gdata/docs/������������������������������������������������������������������������0000750�0364663�0011610�00000000000�12156625015�015531� 5����������������������������������������������������������������������������������������������������ustar �afshar��������������������������eng�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������gdata-2.0.18/src/gdata/docs/data.py�����������������������������������������������������������������0000750�0364663�0011610�00000044410�12156622363�017025� 0����������������������������������������������������������������������������������������������������ustar �afshar��������������������������eng�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/python # # Copyright 2011 Google Inc. All Rights Reserved. # # 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. """Data model classes for representing elements of the Documents List API.""" __author__ = 'vicfryzel@google.com (Vic Fryzel)' import atom.core import atom.data import gdata.acl.data import gdata.data DOCUMENTS_NS = 'http://schemas.google.com/docs/2007' LABELS_NS = 'http://schemas.google.com/g/2005/labels' DOCUMENTS_TEMPLATE = '{http://schemas.google.com/docs/2007}%s' ACL_FEEDLINK_REL = 'http://schemas.google.com/acl/2007#accessControlList' RESUMABLE_CREATE_MEDIA_LINK_REL = 'http://schemas.google.com/g/2005#resumable-create-media' RESUMABLE_EDIT_MEDIA_LINK_REL = 'http://schemas.google.com/g/2005#resumable-edit-media' REVISION_FEEDLINK_REL = DOCUMENTS_NS + '/revisions' PARENT_LINK_REL = DOCUMENTS_NS + '#parent' PUBLISH_LINK_REL = DOCUMENTS_NS + '#publish' DATA_KIND_SCHEME = 'http://schemas.google.com/g/2005#kind' LABELS_SCHEME = LABELS_NS DOCUMENT_LABEL = 'document' SPREADSHEET_LABEL = 'spreadsheet' DRAWING_LABEL = 'drawing' PRESENTATION_LABEL = 'presentation' FILE_LABEL = 'file' PDF_LABEL = 'pdf' FORM_LABEL = 'form' ITEM_LABEL = 'item' COLLECTION_LABEL = 'folder' STARRED_LABEL = 'starred' VIEWED_LABEL = 'viewed' HIDDEN_LABEL = 'hidden' TRASHED_LABEL = 'trashed' MINE_LABEL = 'mine' PRIVATE_LABEL = 'private' SHAREDWITHDOMAIN_LABEL = 'shared-with-domain' RESTRICTEDDOWNLOAD_LABEL = 'restricted-download' class ResourceId(atom.core.XmlElement): """The DocList gd:resourceId element.""" _qname = gdata.data.GDATA_TEMPLATE % 'resourceId' class LastModifiedBy(atom.data.Person): """The DocList gd:lastModifiedBy element.""" _qname = gdata.data.GDATA_TEMPLATE % 'lastModifiedBy' class LastViewed(atom.data.Person): """The DocList gd:lastViewed element.""" _qname = gdata.data.GDATA_TEMPLATE % 'lastViewed' class WritersCanInvite(atom.core.XmlElement): """The DocList docs:writersCanInvite element.""" _qname = DOCUMENTS_TEMPLATE % 'writersCanInvite' value = 'value' class Deleted(atom.core.XmlElement): """The DocList gd:deleted element.""" _qname = gdata.data.GDATA_TEMPLATE % 'deleted' class QuotaBytesUsed(atom.core.XmlElement): """The DocList gd:quotaBytesUsed element.""" _qname = gdata.data.GDATA_TEMPLATE % 'quotaBytesUsed' class Publish(atom.core.XmlElement): """The DocList docs:publish element.""" _qname = DOCUMENTS_TEMPLATE % 'publish' value = 'value' class PublishAuto(atom.core.XmlElement): """The DocList docs:publishAuto element.""" _qname = DOCUMENTS_TEMPLATE % 'publishAuto' value = 'value' class PublishOutsideDomain(atom.core.XmlElement): """The DocList docs:publishOutsideDomain element.""" _qname = DOCUMENTS_TEMPLATE % 'publishOutsideDomain' value = 'value' class Filename(atom.core.XmlElement): """The DocList docs:filename element.""" _qname = DOCUMENTS_TEMPLATE % 'filename' class SuggestedFilename(atom.core.XmlElement): """The DocList docs:suggestedFilename element.""" _qname = DOCUMENTS_TEMPLATE % 'suggestedFilename' class Description(atom.core.XmlElement): """The DocList docs:description element.""" _qname = DOCUMENTS_TEMPLATE % 'description' class CategoryFinder(object): """Mixin to provide category finding functionality. Analogous to atom.data.LinkFinder, but a simpler API, specialized for DocList categories. """ def add_category(self, scheme, term, label): """Add a category for a scheme, term and label. Args: scheme: The scheme for the category. term: The term for the category. label: The label for the category Returns: The newly created atom.data.Category. """ category = atom.data.Category(scheme=scheme, term=term, label=label) self.category.append(category) return category AddCategory = add_category def get_categories(self, scheme): """Fetch the category elements for a scheme. Args: scheme: The scheme to fetch the elements for. Returns: Generator of atom.data.Category elements. """ for category in self.category: if category.scheme == scheme: yield category GetCategories = get_categories def remove_categories(self, scheme): """Remove category elements for a scheme. Args: scheme: The scheme of category to remove. """ for category in list(self.get_categories(scheme)): self.category.remove(category) RemoveCategories = remove_categories def get_first_category(self, scheme): """Fetch the first category element for a scheme. Args: scheme: The scheme of category to return. Returns: atom.data.Category if found or None. """ try: return self.get_categories(scheme).next() except StopIteration, e: # The entry doesn't have the category return None GetFirstCategory = get_first_category def set_resource_type(self, label): """Set the document type for an entry, by building the appropriate atom.data.Category Args: label: str The value for the category entry. If None is passed the category is removed and not set. Returns: An atom.data.Category or None if label is None. """ self.remove_categories(DATA_KIND_SCHEME) if label is not None: return self.add_category(scheme=DATA_KIND_SCHEME, term='%s#%s' % (DOCUMENTS_NS, label), label=label) else: return None SetResourceType = set_resource_type def get_resource_type(self): """Extracts the type of document this Resource is. This method returns the type of document the Resource represents. Possible values are document, presentation, drawing, spreadsheet, file, folder, form, item, or pdf. 'folder' is a possible return value of this method because, for legacy support, we have not yet renamed the folder keyword to collection in the API itself. Returns: String representing the type of document. """ category = self.get_first_category(DATA_KIND_SCHEME) if category is not None: return category.label else: return None GetResourceType = get_resource_type def get_labels(self): """Extracts the labels for this Resource. This method returns the labels as a set, for example: 'hidden', 'starred', 'viewed'. Returns: Set of string labels. """ return set(category.label for category in self.get_categories(LABELS_SCHEME)) GetLabels = get_labels def has_label(self, label): """Whether this Resource has a label. Args: label: The str label to test for Returns: Boolean value indicating presence of label. """ return label in self.get_labels() HasLabel = has_label def add_label(self, label): """Add a label, if it is not present. Args: label: The str label to set """ if not self.has_label(label): self.add_category(scheme=LABELS_SCHEME, term='%s#%s' % (LABELS_NS, label), label=label) AddLabel = add_label def remove_label(self, label): """Remove a label, if it is present. Args: label: The str label to remove """ for category in self.get_categories(LABELS_SCHEME): if category.label == label: self.category.remove(category) RemoveLabel = remove_label def is_starred(self): """Whether this Resource is starred. Returns: Boolean value indicating that the resource is starred. """ return self.has_label(STARRED_LABEL) IsStarred = is_starred def is_hidden(self): """Whether this Resource is hidden. Returns: Boolean value indicating that the resource is hidden. """ return self.has_label(HIDDEN_LABEL) IsHidden = is_hidden def is_viewed(self): """Whether this Resource is viewed. Returns: Boolean value indicating that the resource is viewed. """ return self.has_label(VIEWED_LABEL) IsViewed = is_viewed def is_trashed(self): """Whether this resource is trashed. Returns: Boolean value indicating that the resource is trashed. """ return self.has_label(TRASHED_LABEL) IsTrashed = is_trashed def is_mine(self): """Whether this resource is marked as mine. Returns: Boolean value indicating that the resource is marked as mine. """ return self.has_label(MINE_LABEL) IsMine = is_mine def is_private(self): """Whether this resource is private. Returns: Boolean value indicating that the resource is private. """ return self.has_label(PRIVATE_LABEL) IsPrivate = is_private def is_shared_with_domain(self): """Whether this resource is shared with the domain. Returns: Boolean value indicating that the resource is shared with the domain. """ return self.has_label(SHAREDWITHDOMAIN_LABEL) IsSharedWithDomain = is_shared_with_domain def is_restricted_download(self): """Whether this resource is restricted download. Returns: Boolean value indicating whether the resource is restricted download. """ return self.has_label(RESTRICTEDDOWNLOAD_LABEL) IsRestrictedDownload = is_restricted_download class AclEntry(gdata.acl.data.AclEntry, gdata.data.BatchEntry): """Resource ACL entry.""" @staticmethod def get_instance(role=None, scope_type=None, scope_value=None, key=False): entry = AclEntry() if role is not None: if isinstance(role, basestring): role = gdata.acl.data.AclRole(value=role) if key: entry.with_key = gdata.acl.data.AclWithKey(key='', role=role) else: entry.role = role if scope_type is not None: if scope_value is not None: entry.scope = gdata.acl.data.AclScope(type=scope_type, value=scope_value) else: entry.scope = gdata.acl.data.AclScope(type=scope_type) return entry GetInstance = get_instance class AclFeed(gdata.acl.data.AclFeed): """Resource ACL feed.""" entry = [AclEntry] class Resource(gdata.data.BatchEntry, CategoryFinder): """DocList version of an Atom Entry.""" last_viewed = LastViewed last_modified_by = LastModifiedBy resource_id = ResourceId deleted = Deleted writers_can_invite = WritersCanInvite quota_bytes_used = QuotaBytesUsed feed_link = [gdata.data.FeedLink] filename = Filename suggested_filename = SuggestedFilename description = Description # Only populated if you request /feeds/default/private/expandAcl acl_feed = AclFeed def __init__(self, type=None, title=None, **kwargs): super(Resource, self).__init__(**kwargs) if isinstance(type, basestring): self.set_resource_type(type) if title is not None: if isinstance(title, basestring): self.title = atom.data.Title(text=title) else: self.title = title def get_acl_feed_link(self): """Extracts the Resource's ACL feed <gd:feedLink>. Returns: A gdata.data.FeedLink object. """ for feed_link in self.feed_link: if feed_link.rel == ACL_FEEDLINK_REL: return feed_link return None GetAclFeedLink = get_acl_feed_link def get_revisions_feed_link(self): """Extracts the Resource's revisions feed <gd:feedLink>. Returns: A gdata.data.FeedLink object. """ for feed_link in self.feed_link: if feed_link.rel == REVISION_FEEDLINK_REL: return feed_link return None GetRevisionsFeedLink = get_revisions_feed_link def get_resumable_create_media_link(self): """Extracts the Resource's resumable create link. Returns: A gdata.data.FeedLink object. """ return self.get_link(RESUMABLE_CREATE_MEDIA_LINK_REL) GetResumableCreateMediaLink = get_resumable_create_media_link def get_resumable_edit_media_link(self): """Extracts the Resource's resumable update link. Returns: A gdata.data.FeedLink object. """ return self.get_link(RESUMABLE_EDIT_MEDIA_LINK_REL) GetResumableEditMediaLink = get_resumable_edit_media_link def in_collections(self): """Returns the parents link(s) (collections) of this entry.""" links = [] for link in self.link: if link.rel == PARENT_LINK_REL and link.href: links.append(link) return links InCollections = in_collections class ResourceFeed(gdata.data.BatchFeed): """Main feed containing a list of resources.""" entry = [Resource] class Revision(gdata.data.GDEntry): """Resource Revision entry.""" publish = Publish publish_auto = PublishAuto publish_outside_domain = PublishOutsideDomain def find_publish_link(self): """Get the link that points to the published resource on the web. Returns: A str for the URL in the link with a rel ending in #publish. """ return self.find_url(PUBLISH_LINK_REL) FindPublishLink = find_publish_link def get_publish_link(self): """Get the link that points to the published resource on the web. Returns: A gdata.data.Link for the link with a rel ending in #publish. """ return self.get_link(PUBLISH_LINK_REL) GetPublishLink = get_publish_link class RevisionFeed(gdata.data.GDFeed): """A DocList Revision feed.""" entry = [Revision] class ArchiveResourceId(atom.core.XmlElement): """The DocList docs:removed element.""" _qname = DOCUMENTS_TEMPLATE % 'archiveResourceId' class ArchiveFailure(atom.core.XmlElement): """The DocList docs:archiveFailure element.""" _qname = DOCUMENTS_TEMPLATE % 'archiveFailure' class ArchiveComplete(atom.core.XmlElement): """The DocList docs:archiveComplete element.""" _qname = DOCUMENTS_TEMPLATE % 'archiveComplete' class ArchiveTotal(atom.core.XmlElement): """The DocList docs:archiveTotal element.""" _qname = DOCUMENTS_TEMPLATE % 'archiveTotal' class ArchiveTotalComplete(atom.core.XmlElement): """The DocList docs:archiveTotalComplete element.""" _qname = DOCUMENTS_TEMPLATE % 'archiveTotalComplete' class ArchiveTotalFailure(atom.core.XmlElement): """The DocList docs:archiveTotalFailure element.""" _qname = DOCUMENTS_TEMPLATE % 'archiveTotalFailure' class ArchiveConversion(atom.core.XmlElement): """The DocList docs:removed element.""" _qname = DOCUMENTS_TEMPLATE % 'archiveConversion' source = 'source' target = 'target' class ArchiveNotify(atom.core.XmlElement): """The DocList docs:archiveNotify element.""" _qname = DOCUMENTS_TEMPLATE % 'archiveNotify' class ArchiveStatus(atom.core.XmlElement): """The DocList docs:archiveStatus element.""" _qname = DOCUMENTS_TEMPLATE % 'archiveStatus' class ArchiveNotifyStatus(atom.core.XmlElement): """The DocList docs:archiveNotifyStatus element.""" _qname = DOCUMENTS_TEMPLATE % 'archiveNotifyStatus' class Archive(gdata.data.GDEntry): """Archive entry.""" archive_resource_ids = [ArchiveResourceId] status = ArchiveStatus date_completed = ArchiveComplete num_resources = ArchiveTotal num_complete_resources = ArchiveTotalComplete num_failed_resources = ArchiveTotalFailure failed_resource_ids = [ArchiveFailure] notify_status = ArchiveNotifyStatus conversions = [ArchiveConversion] notification_email = ArchiveNotify size = QuotaBytesUsed @staticmethod def from_resource_list(resources): resource_ids = [] for resource in resources: id = ArchiveResourceId(text=resource.resource_id.text) resource_ids.append(id) return Archive(archive_resource_ids=resource_ids) FromResourceList = from_resource_list class Removed(atom.core.XmlElement): """The DocList docs:removed element.""" _qname = DOCUMENTS_TEMPLATE % 'removed' class Changestamp(atom.core.XmlElement): """The DocList docs:changestamp element.""" _qname = DOCUMENTS_TEMPLATE % 'changestamp' value = 'value' class Change(Resource): """Change feed entry.""" changestamp = Changestamp removed = Removed class ChangeFeed(gdata.data.GDFeed): """DocList Changes feed.""" entry = [Change] class QuotaBytesTotal(atom.core.XmlElement): """The DocList gd:quotaBytesTotal element.""" _qname = gdata.data.GDATA_TEMPLATE % 'quotaBytesTotal' class QuotaBytesUsedInTrash(atom.core.XmlElement): """The DocList docs:quotaBytesUsedInTrash element.""" _qname = DOCUMENTS_TEMPLATE % 'quotaBytesUsedInTrash' class ImportFormat(atom.core.XmlElement): """The DocList docs:importFormat element.""" _qname = DOCUMENTS_TEMPLATE % 'importFormat' source = 'source' target = 'target' class ExportFormat(atom.core.XmlElement): """The DocList docs:exportFormat element.""" _qname = DOCUMENTS_TEMPLATE % 'exportFormat' source = 'source' target = 'target' class FeatureName(atom.core.XmlElement): """The DocList docs:featureName element.""" _qname = DOCUMENTS_TEMPLATE % 'featureName' class FeatureRate(atom.core.XmlElement): """The DocList docs:featureRate element.""" _qname = DOCUMENTS_TEMPLATE % 'featureRate' class Feature(atom.core.XmlElement): """The DocList docs:feature element.""" _qname = DOCUMENTS_TEMPLATE % 'feature' name = FeatureName rate = FeatureRate class MaxUploadSize(atom.core.XmlElement): """The DocList docs:maxUploadSize element.""" _qname = DOCUMENTS_TEMPLATE % 'maxUploadSize' kind = 'kind' class AdditionalRoleSet(atom.core.XmlElement): """The DocList docs:additionalRoleSet element.""" _qname = DOCUMENTS_TEMPLATE % 'additionalRoleSet' primaryRole = 'primaryRole' additional_role = [gdata.acl.data.AclAdditionalRole] class AdditionalRoleInfo(atom.core.XmlElement): """The DocList docs:additionalRoleInfo element.""" _qname = DOCUMENTS_TEMPLATE % 'additionalRoleInfo' kind = 'kind' additional_role_set = [AdditionalRoleSet] class Metadata(gdata.data.GDEntry): """Metadata entry for a user.""" quota_bytes_total = QuotaBytesTotal quota_bytes_used = QuotaBytesUsed quota_bytes_used_in_trash = QuotaBytesUsedInTrash import_formats = [ImportFormat] export_formats = [ExportFormat] features = [Feature] max_upload_sizes = [MaxUploadSize] additional_role_info = [AdditionalRoleInfo] ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������gdata-2.0.18/src/gdata/docs/__init__.py�������������������������������������������������������������0000640�0364663�0011610�00000021441�12156622363�017650� 0����������������������������������������������������������������������������������������������������ustar �afshar��������������������������eng�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/python # # Copyright 2009 Google Inc. All Rights Reserved. # # 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. """Contains extensions to Atom objects used with Google Documents.""" __author__ = ('api.jfisher (Jeff Fisher), ' 'api.eric@google.com (Eric Bidelman)') import atom import gdata DOCUMENTS_NAMESPACE = 'http://schemas.google.com/docs/2007' class Scope(atom.AtomBase): """The DocList ACL scope element""" _tag = 'scope' _namespace = gdata.GACL_NAMESPACE _children = atom.AtomBase._children.copy() _attributes = atom.AtomBase._attributes.copy() _attributes['value'] = 'value' _attributes['type'] = 'type' def __init__(self, value=None, type=None, extension_elements=None, extension_attributes=None, text=None): self.value = value self.type = type self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} class Role(atom.AtomBase): """The DocList ACL role element""" _tag = 'role' _namespace = gdata.GACL_NAMESPACE _children = atom.AtomBase._children.copy() _attributes = atom.AtomBase._attributes.copy() _attributes['value'] = 'value' def __init__(self, value=None, extension_elements=None, extension_attributes=None, text=None): self.value = value self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} class FeedLink(atom.AtomBase): """The DocList gd:feedLink element""" _tag = 'feedLink' _namespace = gdata.GDATA_NAMESPACE _attributes = atom.AtomBase._attributes.copy() _attributes['rel'] = 'rel' _attributes['href'] = 'href' def __init__(self, href=None, rel=None, text=None, extension_elements=None, extension_attributes=None): self.href = href self.rel = rel atom.AtomBase.__init__(self, extension_elements=extension_elements, extension_attributes=extension_attributes, text=text) class ResourceId(atom.AtomBase): """The DocList gd:resourceId element""" _tag = 'resourceId' _namespace = gdata.GDATA_NAMESPACE _children = atom.AtomBase._children.copy() _attributes = atom.AtomBase._attributes.copy() _attributes['value'] = 'value' def __init__(self, value=None, extension_elements=None, extension_attributes=None, text=None): self.value = value self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} class LastModifiedBy(atom.Person): """The DocList gd:lastModifiedBy element""" _tag = 'lastModifiedBy' _namespace = gdata.GDATA_NAMESPACE class LastViewed(atom.Person): """The DocList gd:lastViewed element""" _tag = 'lastViewed' _namespace = gdata.GDATA_NAMESPACE class WritersCanInvite(atom.AtomBase): """The DocList docs:writersCanInvite element""" _tag = 'writersCanInvite' _namespace = DOCUMENTS_NAMESPACE _attributes = atom.AtomBase._attributes.copy() _attributes['value'] = 'value' class DocumentListEntry(gdata.GDataEntry): """The Google Documents version of an Atom Entry""" _tag = gdata.GDataEntry._tag _namespace = atom.ATOM_NAMESPACE _children = gdata.GDataEntry._children.copy() _attributes = gdata.GDataEntry._attributes.copy() _children['{%s}feedLink' % gdata.GDATA_NAMESPACE] = ('feedLink', FeedLink) _children['{%s}resourceId' % gdata.GDATA_NAMESPACE] = ('resourceId', ResourceId) _children['{%s}lastModifiedBy' % gdata.GDATA_NAMESPACE] = ('lastModifiedBy', LastModifiedBy) _children['{%s}lastViewed' % gdata.GDATA_NAMESPACE] = ('lastViewed', LastViewed) _children['{%s}writersCanInvite' % DOCUMENTS_NAMESPACE] = ( 'writersCanInvite', WritersCanInvite) def __init__(self, resourceId=None, feedLink=None, lastViewed=None, lastModifiedBy=None, writersCanInvite=None, author=None, category=None, content=None, atom_id=None, link=None, published=None, title=None, updated=None, text=None, extension_elements=None, extension_attributes=None): self.feedLink = feedLink self.lastViewed = lastViewed self.lastModifiedBy = lastModifiedBy self.resourceId = resourceId self.writersCanInvite = writersCanInvite gdata.GDataEntry.__init__( self, author=author, category=category, content=content, atom_id=atom_id, link=link, published=published, title=title, updated=updated, extension_elements=extension_elements, extension_attributes=extension_attributes, text=text) def GetAclLink(self): """Extracts the DocListEntry's <gd:feedLink>. Returns: A FeedLink object. """ return self.feedLink def GetDocumentType(self): """Extracts the type of document from the DocListEntry. This method returns the type of document the DocListEntry represents. Possible values are document, presentation, spreadsheet, folder, or pdf. Returns: A string representing the type of document. """ if self.category: for category in self.category: if category.scheme == gdata.GDATA_NAMESPACE + '#kind': return category.label else: return None def DocumentListEntryFromString(xml_string): """Converts an XML string into a DocumentListEntry object. Args: xml_string: string The XML describing a Document List feed entry. Returns: A DocumentListEntry object corresponding to the given XML. """ return atom.CreateClassFromXMLString(DocumentListEntry, xml_string) class DocumentListAclEntry(gdata.GDataEntry): """A DocList ACL Entry flavor of an Atom Entry""" _tag = gdata.GDataEntry._tag _namespace = gdata.GDataEntry._namespace _children = gdata.GDataEntry._children.copy() _attributes = gdata.GDataEntry._attributes.copy() _children['{%s}scope' % gdata.GACL_NAMESPACE] = ('scope', Scope) _children['{%s}role' % gdata.GACL_NAMESPACE] = ('role', Role) def __init__(self, category=None, atom_id=None, link=None, title=None, updated=None, scope=None, role=None, extension_elements=None, extension_attributes=None, text=None): gdata.GDataEntry.__init__(self, author=None, category=category, content=None, atom_id=atom_id, link=link, published=None, title=title, updated=updated, text=None) self.scope = scope self.role = role def DocumentListAclEntryFromString(xml_string): """Converts an XML string into a DocumentListAclEntry object. Args: xml_string: string The XML describing a Document List ACL feed entry. Returns: A DocumentListAclEntry object corresponding to the given XML. """ return atom.CreateClassFromXMLString(DocumentListAclEntry, xml_string) class DocumentListFeed(gdata.GDataFeed): """A feed containing a list of Google Documents Items""" _tag = gdata.GDataFeed._tag _namespace = atom.ATOM_NAMESPACE _children = gdata.GDataFeed._children.copy() _attributes = gdata.GDataFeed._attributes.copy() _children['{%s}entry' % atom.ATOM_NAMESPACE] = ('entry', [DocumentListEntry]) def DocumentListFeedFromString(xml_string): """Converts an XML string into a DocumentListFeed object. Args: xml_string: string The XML describing a DocumentList feed. Returns: A DocumentListFeed object corresponding to the given XML. """ return atom.CreateClassFromXMLString(DocumentListFeed, xml_string) class DocumentListAclFeed(gdata.GDataFeed): """A DocList ACL feed flavor of a Atom feed""" _tag = gdata.GDataFeed._tag _namespace = atom.ATOM_NAMESPACE _children = gdata.GDataFeed._children.copy() _attributes = gdata.GDataFeed._attributes.copy() _children['{%s}entry' % atom.ATOM_NAMESPACE] = ('entry', [DocumentListAclEntry]) def DocumentListAclFeedFromString(xml_string): """Converts an XML string into a DocumentListAclFeed object. Args: xml_string: string The XML describing a DocumentList feed. Returns: A DocumentListFeed object corresponding to the given XML. """ return atom.CreateClassFromXMLString(DocumentListAclFeed, xml_string) �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������gdata-2.0.18/src/gdata/docs/client.py���������������������������������������������������������������0000750�0364663�0011610�00000121570�12156622363�017375� 0����������������������������������������������������������������������������������������������������ustar �afshar��������������������������eng�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/python # # Copyright 2011 Google Inc. All Rights Reserved. # # 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. """DocsClient simplifies interactions with the Documents List API.""" __author__ = 'vicfryzel@google.com (Vic Fryzel)' import copy import mimetypes import re import urllib import atom.data import atom.http_core import gdata.client import gdata.docs.data import gdata.gauth # Feed URIs that are given by the API, but cannot be obtained without # making a mostly unnecessary HTTP request. RESOURCE_FEED_URI = '/feeds/default/private/full' RESOURCE_SELF_LINK_TEMPLATE = RESOURCE_FEED_URI + '/%s' RESOURCE_UPLOAD_URI = '/feeds/upload/create-session/default/private/full' ARCHIVE_FEED_URI = '/feeds/default/private/archive' METADATA_URI = '/feeds/metadata/default' CHANGE_FEED_URI = '/feeds/default/private/changes' class DocsClient(gdata.client.GDClient): """Client for all features of the Google Documents List API.""" host = 'docs.google.com' api_version = '3.0' auth_service = 'writely' alt_auth_service = 'wise' alt_auth_token = None auth_scopes = gdata.gauth.AUTH_SCOPES['writely'] ssl = True def request(self, method=None, uri=None, **kwargs): """Add support for imitating other users via 2-Legged OAuth. Args: uri: (optional) URI of the request in which to replace default with self.xoauth_requestor_id. Returns: Result of super(DocsClient, self).request(). """ if self.xoauth_requestor_id is not None and uri is not None: if isinstance(uri, (str, unicode)): uri = atom.http_core.Uri.parse_uri(uri) uri.path.replace('/default', '/%s' % self.xoauth_requestor_id) return super(DocsClient, self).request(method=method, uri=uri, **kwargs) Request = request def get_metadata(self, **kwargs): """Retrieves the metadata of a user account. Args: kwargs: Other parameters to pass to self.get_entry(). Returns: gdata.docs.data.Metadata representing metadata of user's account. """ return self.get_entry( METADATA_URI, desired_class=gdata.docs.data.Metadata, **kwargs) GetMetadata = get_metadata def get_changes(self, changestamp=None, max_results=None, show_root=None, **kwargs): """Retrieves changes to a user's documents list. Args: changestamp: (optional) String changestamp value to query since. If provided, returned changes will have a changestamp larger than the given one. max_results: (optional) Number of results to fetch. API will limit this number to 100 at most. show_root: (optional) True to include indications if a resource is in the root collection. kwargs: Other parameters to pass to self.get_feed(). Returns: gdata.docs.data.ChangeFeed. """ uri = atom.http_core.Uri.parse_uri(CHANGE_FEED_URI) if changestamp is not None: uri.query['start-index'] = changestamp if max_results is not None: uri.query['max-results'] = max_results if show_root is not None: uri.query['showroot'] = str(show_root).lower() return self.get_feed( uri, desired_class=gdata.docs.data.ChangeFeed, **kwargs) GetChanges = get_changes def get_resources(self, uri=None, limit=None, show_root=None, **kwargs): """Retrieves the resources in a user's docslist, or the given URI. Args: uri: (optional) URI to query for resources. If None, then gdata.docs.client.DocsClient.RESOURCE_FEED_URI is used, which will query for all non-collections. limit: int (optional) A maximum cap for the number of results to return in the feed. By default, the API returns a maximum of 100 per page. Thus, if you set limit=5000, you will get <= 5000 documents (guarenteed no more than 5000), and will need to follow the feed's next links (feed.GetNextLink()) to the rest. See get_everything(). Similarly, if you set limit=50, only <= 50 documents are returned. Note: if the max-results parameter is set in the uri parameter, it is chosen over a value set for limit. show_root: (optional) True to include indications if a resource is in the root collection. kwargs: Other parameters to pass to self.get_feed(). Returns: gdata.docs.data.ResourceFeed feed. """ if uri is None: uri = RESOURCE_FEED_URI if isinstance(uri, basestring): uri = atom.http_core.Uri.parse_uri(uri) # Add max-results param if it wasn't included in the uri. if limit is not None and not 'max-results' in uri.query: uri.query['max-results'] = limit if show_root is not None: uri.query['showroot'] = str(show_root).lower() return self.get_feed(uri, desired_class=gdata.docs.data.ResourceFeed, **kwargs) GetResources = get_resources def get_all_resources(self, uri=None, show_root=None, **kwargs): """Retrieves all of a user's non-collections or everything at the given URI. Folders are not included in this by default. Pass in a custom URI to include collections in your query. The DocsQuery class is an easy way to generate such a URI. This method makes multiple HTTP requests (by following the feed's next links) in order to fetch the user's entire document list. Args: uri: (optional) URI to query the doclist feed with. If None, then use DocsClient.RESOURCE_FEED_URI, which will retrieve all non-collections. show_root: (optional) True to include indications if a resource is in the root collection. kwargs: Other parameters to pass to self.GetResources(). Returns: List of gdata.docs.data.Resource objects representing the retrieved entries. """ if uri is None: uri = RESOURCE_FEED_URI if isinstance(uri, basestring): uri = atom.http_core.Uri.parse_uri(uri) if show_root is not None: uri.query['showroot'] = str(show_root).lower() feed = self.GetResources(uri=uri, **kwargs) entries = feed.entry while feed.GetNextLink() is not None: feed = self.GetResources(feed.GetNextLink().href, **kwargs) entries.extend(feed.entry) return entries GetAllResources = get_all_resources def get_resource(self, entry, **kwargs): """Retrieves a resource again given its entry. Args: entry: gdata.docs.data.Resource to fetch and return. kwargs: Other args to pass to GetResourceBySelfLink(). Returns: gdata.docs.data.Resource representing retrieved resource. """ return self.GetResourceBySelfLink(entry.GetSelfLink().href, **kwargs) GetResource = get_resource def get_resource_by_id(self, resource_id, **kwargs): """Retrieves a resource again given its resource ID. Args: resource_id: Typed or untyped resource ID of a resource. kwargs: Other args to pass to GetResourceBySelfLink(). Returns: gdata.docs.data.Resource representing retrieved resource. """ return self.GetResourceBySelfLink( RESOURCE_SELF_LINK_TEMPLATE % resource_id, **kwargs) GetResourceById = get_resource_by_id def get_resource_by_self_link(self, uri, etag=None, show_root=None, **kwargs): """Retrieves a particular resource by its self link. Args: uri: str URI at which to query for given resource. This can be found using entry.GetSelfLink(). etag: str (optional) The document/item's etag value to be used in a conditional GET. See http://code.google.com/apis/documents/docs/3.0/ developers_guide_protocol.html#RetrievingCached. show_root: (optional) True to include indications if a resource is in the root collection. kwargs: Other parameters to pass to self.get_entry(). Returns: gdata.docs.data.Resource representing the retrieved resource. """ if isinstance(uri, basestring): uri = atom.http_core.Uri.parse_uri(uri) if show_root is not None: uri.query['showroot'] = str(show_root).lower() return self.get_entry( uri , etag=etag, desired_class=gdata.docs.data.Resource, **kwargs) GetResourceBySelfLink = get_resource_by_self_link def get_resource_acl(self, entry, **kwargs): """Retrieves the ACL sharing permissions for the given entry. Args: entry: gdata.docs.data.Resource for which to get ACL. kwargs: Other parameters to pass to self.get_feed(). Returns: gdata.docs.data.AclFeed representing the resource's ACL. """ self._check_entry_is_resource(entry) return self.get_feed(entry.GetAclFeedLink().href, desired_class=gdata.docs.data.AclFeed, **kwargs) GetResourceAcl = get_resource_acl def create_resource(self, entry, media=None, collection=None, create_uri=None, **kwargs): """Creates new entries in Google Docs, and uploads their contents. Args: entry: gdata.docs.data.Resource representing initial version of entry being created. If media is also provided, the entry will first be created with the given metadata and content. media: (optional) gdata.data.MediaSource containing the file to be uploaded. collection: (optional) gdata.docs.data.Resource representing a collection in which this new entry should be created. If provided along with create_uri, create_uri will win (e.g. entry will be created at create_uri, not necessarily in given collection). create_uri: (optional) String URI at which to create the given entry. If collection, media and create_uri are None, use gdata.docs.client.RESOURCE_FEED_URI. If collection and create_uri are None, use gdata.docs.client.RESOURCE_UPLOAD_URI. If collection and media are not None, collection.GetResumableCreateMediaLink() is used, with the collection's resource ID substituted in. kwargs: Other parameters to pass to self.post() and self.update(). Returns: gdata.docs.data.Resource containing information about new entry. """ if media is not None: if create_uri is None and collection is not None: create_uri = collection.GetResumableCreateMediaLink().href elif create_uri is None: create_uri = RESOURCE_UPLOAD_URI uploader = gdata.client.ResumableUploader( self, media.file_handle, media.content_type, media.content_length, desired_class=gdata.docs.data.Resource) return uploader.upload_file(create_uri, entry, **kwargs) else: if create_uri is None and collection is not None: create_uri = collection.content.src elif create_uri is None: create_uri = RESOURCE_FEED_URI return self.post( entry, create_uri, desired_class=gdata.docs.data.Resource, **kwargs) CreateResource = create_resource def update_resource(self, entry, media=None, update_metadata=True, new_revision=False, **kwargs): """Updates an entry in Google Docs with new metadata and/or new data. Args: entry: Entry to update. Make any metadata changes to this entry. media: (optional) gdata.data.MediaSource object containing the file with which to replace the entry's data. update_metadata: (optional) True to update the metadata from the entry itself. You might set this to False to only update an entry's file content, and not its metadata. new_revision: (optional) True to create a new revision with this update, False otherwise. kwargs: Other parameters to pass to self.post(). Returns: gdata.docs.data.Resource representing the updated entry. """ uri_params = {} if new_revision: uri_params['new-revision'] = 'true' if update_metadata and media is None: uri = atom.http_core.parse_uri(entry.GetEditLink().href) uri.query.update(uri_params) return super(DocsClient, self).update(entry, **kwargs) else: uploader = gdata.client.ResumableUploader( self, media.file_handle, media.content_type, media.content_length, desired_class=gdata.docs.data.Resource) return uploader.UpdateFile(entry_or_resumable_edit_link=entry, update_metadata=update_metadata, uri_params=uri_params, **kwargs) UpdateResource = update_resource def download_resource(self, entry, file_path, extra_params=None, **kwargs): """Downloads the contents of the given entry to disk. Note: to download a file in memory, use the DownloadResourceToMemory() method. Args: entry: gdata.docs.data.Resource whose contents to fetch. file_path: str Full path to which to save file. extra_params: dict (optional) A map of any further parameters to control how the document is downloaded/exported. For example, exporting a spreadsheet as a .csv: extra_params={'gid': 0, 'exportFormat': 'csv'} kwargs: Other parameters to pass to self._download_file(). Raises: gdata.client.RequestError if the download URL is malformed or the server's response was not successful. """ self._check_entry_is_not_collection(entry) self._check_entry_has_content(entry) uri = self._get_download_uri(entry.content.src, extra_params) self._download_file(uri, file_path, **kwargs) DownloadResource = download_resource def download_resource_to_memory(self, entry, extra_params=None, **kwargs): """Returns the contents of the given entry. Args: entry: gdata.docs.data.Resource whose contents to fetch. extra_params: dict (optional) A map of any further parameters to control how the document is downloaded/exported. For example, exporting a spreadsheet as a .csv: extra_params={'gid': 0, 'exportFormat': 'csv'} kwargs: Other parameters to pass to self._get_content(). Returns: Content of given resource after being downloaded. Raises: gdata.client.RequestError if the download URL is malformed or the server's response was not successful. """ self._check_entry_is_not_collection(entry) self._check_entry_has_content(entry) uri = self._get_download_uri(entry.content.src, extra_params) return self._get_content(uri, **kwargs) DownloadResourceToMemory = download_resource_to_memory def _get_download_uri(self, base_uri, extra_params=None): uri = base_uri.replace('&', '&') if extra_params is not None: if 'exportFormat' in extra_params and '/Export?' not in uri: raise gdata.client.Error, ('This entry type cannot be exported ' 'as a different format.') if 'gid' in extra_params and uri.find('spreadsheets') == -1: raise gdata.client.Error, 'gid param is not valid for this resource type.' uri += '&' + urllib.urlencode(extra_params) return uri def _get_content(self, uri, extra_params=None, auth_token=None, **kwargs): """Fetches the given resource's content. This method is useful for downloading/exporting a file within enviornments like Google App Engine, where the user may not have the ability to write the file to a local disk. Be warned, this method will use as much memory as needed to store the fetched content. This could cause issues in your environment or app. This is only different from Download() in that you will probably retain an open reference to the data returned from this method, where as the data from Download() will be immediately written to disk and the memory freed. This client library currently doesn't support reading server responses into a buffer or yielding an open file pointer to responses. Args: entry: Resource to fetch. extra_params: dict (optional) A map of any further parameters to control how the document is downloaded/exported. For example, exporting a spreadsheet as a .csv: extra_params={'gid': 0, 'exportFormat': 'csv'} auth_token: (optional) gdata.gauth.ClientLoginToken, AuthSubToken, or OAuthToken which authorizes this client to edit the user's data. kwargs: Other parameters to pass to self.request(). Returns: The binary file content. Raises: gdata.client.RequestError: on error response from server. """ server_response = None token = auth_token if 'spreadsheets' in uri and token is None \ and self.alt_auth_token is not None: token = self.alt_auth_token server_response = self.request( 'GET', uri, auth_token=token, **kwargs) if server_response.status != 200: raise gdata.client.RequestError, {'status': server_response.status, 'reason': server_response.reason, 'body': server_response.read()} return server_response.read() def _download_file(self, uri, file_path, **kwargs): """Downloads a file to disk from the specified URI. Note: to download a file in memory, use the GetContent() method. Args: uri: str The full URL to download the file from. file_path: str The full path to save the file to. kwargs: Other parameters to pass to self.get_content(). Raises: gdata.client.RequestError: on error response from server. """ f = open(file_path, 'wb') try: f.write(self._get_content(uri, **kwargs)) except gdata.client.RequestError, e: f.close() raise e f.flush() f.close() _DownloadFile = _download_file def copy_resource(self, entry, title, **kwargs): """Copies the given entry to a new entry with the given title. Note: Files do not support this feature. Args: entry: gdata.docs.data.Resource to copy. title: String title for the new entry. kwargs: Other parameters to pass to self.post(). Returns: gdata.docs.data.Resource representing duplicated resource. """ self._check_entry_is_resource(entry) new_entry = gdata.docs.data.Resource( title=atom.data.Title(text=title), id=atom.data.Id(text=entry.GetSelfLink().href)) return self.post(new_entry, RESOURCE_FEED_URI, **kwargs) CopyResource = copy_resource def move_resource(self, entry, collection=None, keep_in_collections=False, **kwargs): """Moves an item into a different collection (or out of all collections). Args: entry: gdata.docs.data.Resource to move. collection: gdata.docs.data.Resource (optional) An object representing the destination collection. If None, set keep_in_collections to False to remove the item from all collections. keep_in_collections: boolean (optional) If True, the given entry is not removed from any existing collections it is already in. kwargs: Other parameters to pass to self.post(). Returns: gdata.docs.data.Resource of the moved entry. """ self._check_entry_is_resource(entry) # Remove the item from any collections it is already in. if not keep_in_collections: for current_collection in entry.InCollections(): uri = '%s/contents/%s' % ( current_collection.href, urllib.quote(entry.resource_id.text)) self.delete(uri, force=True) if collection is not None: self._check_entry_is_collection(collection) entry = self.post(entry, collection.content.src, **kwargs) return entry MoveResource = move_resource def delete_resource(self, entry, permanent=False, **kwargs): """Trashes or deletes the given entry. Args: entry: gdata.docs.data.Resource to trash or delete. permanent: True to skip the trash and delete the entry forever. kwargs: Other args to pass to gdata.client.GDClient.Delete() Returns: Result of delete request. """ if permanent: kwargs['delete'] = 'true' return gdata.client.GDClient.delete(self, entry, **kwargs) DeleteResource = delete_resource def _check_entry_is_resource(self, entry): """Ensures given entry is a gdata.docs.data.Resource. Args: entry: Entry to test. Raises: ValueError: If given entry is not a resource. """ if not isinstance(entry, gdata.docs.data.Resource): raise ValueError('%s is not a gdata.docs.data.Resource' % str(entry)) def _check_entry_is_collection(self, entry): """Ensures given entry is a collection. Args: entry: Entry to test. Raises: ValueError: If given entry is a collection. """ self._check_entry_is_resource(entry) if entry.get_resource_type() != gdata.docs.data.COLLECTION_LABEL: raise ValueError('%s is not a collection' % str(entry)) def _check_entry_is_not_collection(self, entry): """Ensures given entry is not a collection. Args: entry: Entry to test. Raises: ValueError: If given entry is a collection. """ try: self._check_entry_is_resource(entry) except ValueError: return if entry.get_resource_type() == gdata.docs.data.COLLECTION_LABEL: raise ValueError( '%s is a collection, which is not valid in this method' % str(entry)) def _check_entry_has_content(self, entry): """Ensures given entry has a content element with src. Args: entry: Entry to test. Raises: ValueError: If given entry is not an entry or has no content src. """ # Don't use _check_entry_is_resource since could be Revision etc if not (hasattr(entry, 'content') and entry.content and entry.content.src): raise ValueError('Entry %s has no downloadable content.' % entry) def get_acl(self, entry, **kwargs): """Retrieves an AclFeed for the given resource. Args: entry: gdata.docs.data.Resource to fetch AclFeed for. kwargs: Other args to pass to GetFeed(). Returns: gdata.docs.data.AclFeed representing retrieved entries. """ self._check_entry_is_resource(entry) return self.get_feed( entry.GetAclFeedLink().href, desired_class=gdata.docs.data.AclFeed, **kwargs) GetAcl = get_acl def get_acl_entry(self, entry, **kwargs): """Retrieves an AclEntry again. This is useful if you need to poll for an ACL changing. Args: entry: gdata.docs.data.AclEntry to fetch and return. kwargs: Other args to pass to GetAclEntryBySelfLink(). Returns: gdata.docs.data.AclEntry representing retrieved entry. """ return self.GetAclEntryBySelfLink(entry.GetSelfLink().href, **kwargs) GetAclEntry = get_acl_entry def get_acl_entry_by_self_link(self, self_link, **kwargs): """Retrieves a particular AclEntry by its self link. Args: self_link: URI at which to query for given ACL entry. This can be found using entry.GetSelfLink(). kwargs: Other parameters to pass to self.get_entry(). Returns: gdata.docs.data.AclEntry representing the retrieved entry. """ if isinstance(self_link, atom.data.Link): self_link = self_link.href return self.get_entry(self_link, desired_class=gdata.docs.data.AclEntry, **kwargs) GetAclEntryBySelfLink = get_acl_entry_by_self_link def add_acl_entry(self, resource, acl_entry, send_notifications=None, **kwargs): """Adds the given AclEntry to the given Resource. Args: resource: gdata.docs.data.Resource to which to add AclEntry. acl_entry: gdata.docs.data.AclEntry representing ACL entry to add. send_notifications: True if users should be notified by email when this AclEntry is added. kwargs: Other parameters to pass to self.post(). Returns: gdata.docs.data.AclEntry containing information about new entry. Raises: ValueError: If given resource has no ACL link. """ uri = resource.GetAclLink().href if uri is None: raise ValueError(('Given resource has no ACL link. Did you fetch this' 'resource from the API?')) if send_notifications is not None: if not send_notifications: uri += '?send-notification-emails=false' return self.post(acl_entry, uri, desired_class=gdata.docs.data.AclEntry, **kwargs) AddAclEntry = add_acl_entry def update_acl_entry(self, entry, send_notifications=None, **kwargs): """Updates the given AclEntry with new metadata. Args: entry: AclEntry to update. Make any metadata changes to this entry. send_notifications: True if users should be notified by email when this AclEntry is updated. kwargs: Other parameters to pass to super(DocsClient, self).update(). Returns: gdata.docs.data.AclEntry representing the updated ACL entry. """ uri = entry.GetEditLink().href if not send_notifications: uri += '?send-notification-emails=false' return super(DocsClient, self).update(entry, uri=uri, **kwargs) UpdateAclEntry = update_acl_entry def delete_acl_entry(self, entry, **kwargs): """Deletes the given AclEntry. Args: entry: gdata.docs.data.AclEntry to delete. kwargs: Other args to pass to gdata.client.GDClient.Delete() Returns: Result of delete request. """ return super(DocsClient, self).delete(entry.GetEditLink().href, **kwargs) DeleteAclEntry = delete_acl_entry def batch_process_acl_entries(self, resource, entries, **kwargs): """Applies the specified operation of each entry in a single request. To use this, simply set acl_entry.batch_operation to one of ['query', 'insert', 'update', 'delete'], and optionally set acl_entry.batch_id to a string of your choice. Then, put all of your modified AclEntry objects into a list and pass that list as the entries parameter. Args: resource: gdata.docs.data.Resource to which the given entries belong. entries: [gdata.docs.data.AclEntry] to modify in some way. kwargs: Other args to pass to gdata.client.GDClient.post() Returns: Resulting gdata.docs.data.AclFeed of changes. """ feed = gdata.docs.data.AclFeed() feed.entry = entries return super(DocsClient, self).post( feed, uri=resource.GetAclLink().href + '/batch', **kwargs) BatchProcessAclEntries = batch_process_acl_entries def get_revisions(self, entry, **kwargs): """Retrieves the revision history for a resource. Args: entry: gdata.docs.data.Resource for which to get revisions. kwargs: Other parameters to pass to self.get_feed(). Returns: gdata.docs.data.RevisionFeed representing the resource's revisions. """ self._check_entry_is_resource(entry) return self.get_feed( entry.GetRevisionsFeedLink().href, desired_class=gdata.docs.data.RevisionFeed, **kwargs) GetRevisions = get_revisions def get_revision(self, entry, **kwargs): """Retrieves a revision again given its entry. Args: entry: gdata.docs.data.Revision to fetch and return. kwargs: Other args to pass to GetRevisionBySelfLink(). Returns: gdata.docs.data.Revision representing retrieved revision. """ return self.GetRevisionBySelfLink(entry.GetSelfLink().href, **kwargs) GetRevision = get_revision def get_revision_by_self_link(self, self_link, **kwargs): """Retrieves a particular reivision by its self link. Args: self_link: URI at which to query for given revision. This can be found using entry.GetSelfLink(). kwargs: Other parameters to pass to self.get_entry(). Returns: gdata.docs.data.Revision representing the retrieved revision. """ if isinstance(self_link, atom.data.Link): self_link = self_link.href return self.get_entry(self_link, desired_class=gdata.docs.data.Revision, **kwargs) GetRevisionBySelfLink = get_revision_by_self_link def download_revision(self, entry, file_path, extra_params=None, **kwargs): """Downloads the contents of the given revision to disk. Note: to download a revision in memory, use the DownloadRevisionToMemory() method. Args: entry: gdata.docs.data.Revision whose contents to fetch. file_path: str Full path to which to save file. extra_params: dict (optional) A map of any further parameters to control how the document is downloaded. kwargs: Other parameters to pass to self._download_file(). Raises: gdata.client.RequestError if the download URL is malformed or the server's response was not successful. """ self._check_entry_is_not_collection(entry) self._check_entry_has_content(entry) uri = self._get_download_uri(entry.content.src, extra_params) self._download_file(uri, file_path, **kwargs) DownloadRevision = download_revision def download_revision_to_memory(self, entry, extra_params=None, **kwargs): """Returns the contents of the given revision. Args: entry: gdata.docs.data.Revision whose contents to fetch. extra_params: dict (optional) A map of any further parameters to control how the document is downloaded/exported. kwargs: Other parameters to pass to self._get_content(). Returns: Content of given revision after being downloaded. Raises: gdata.client.RequestError if the download URL is malformed or the server's response was not successful. """ self._check_entry_is_not_collection(entry) self._check_entry_has_content(entry) uri = self._get_download_uri(entry.content.src, extra_params) return self._get_content(uri, **kwargs) DownloadRevisionToMemory = download_revision_to_memory def publish_revision(self, entry, publish_auto=None, publish_outside_domain=False, **kwargs): """Publishes the given revision. This method can only be used for document revisions. Args: entry: Revision to update. publish_auto: True to automatically publish future revisions of the document. False to not automatically publish future revisions. None to take no action and use the default value. publish_outside_domain: True to make the published revision available outside of a Google Apps domain. False to not publish outside the domain. None to use the default value. kwargs: Other parameters to pass to super(DocsClient, self).update(). Returns: gdata.docs.data.Revision representing the updated revision. """ entry.publish = gdata.docs.data.Publish(value='true') if publish_auto == True: entry.publish_auto = gdata.docs.data.PublishAuto(value='true') elif publish_auto == False: entry.publish_auto = gdata.docs.data.PublishAuto(value='false') if publish_outside_domain == True: entry.publish_outside_domain = \ gdata.docs.data.PublishOutsideDomain(value='true') elif publish_outside_domain == False: entry.publish_outside_domain = \ gdata.docs.data.PublishOutsideDomain(value='false') return super(DocsClient, self).update(entry, force=True, **kwargs) PublishRevision = publish_revision def unpublish_revision(self, entry, **kwargs): """Unpublishes the given revision. This method can only be used for document revisions. Args: entry: Revision to update. kwargs: Other parameters to pass to super(DocsClient, self).update(). Returns: gdata.docs.data.Revision representing the updated revision. """ entry.publish = gdata.docs.data.Publish(value='false') return super(DocsClient, self).update(entry, force=True, **kwargs) UnpublishRevision = unpublish_revision def delete_revision(self, entry, **kwargs): """Deletes the given Revision. Args: entry: gdata.docs.data.Revision to delete. kwargs: Other args to pass to gdata.client.GDClient.Delete() Returns: Result of delete request. """ return super(DocsClient, self).delete(entry, force=True, **kwargs) DeleteRevision = delete_revision def get_archive(self, entry, **kwargs): """Retrieves an archive again given its entry. This is useful if you need to poll for an archive completing. Args: entry: gdata.docs.data.Archive to fetch and return. kwargs: Other args to pass to GetArchiveBySelfLink(). Returns: gdata.docs.data.Archive representing retrieved archive. """ return self.GetArchiveBySelfLink(entry.GetSelfLink().href, **kwargs) GetArchive = get_archive def get_archive_by_self_link(self, self_link, **kwargs): """Retrieves a particular archive by its self link. Args: self_link: URI at which to query for given archive. This can be found using entry.GetSelfLink(). kwargs: Other parameters to pass to self.get_entry(). Returns: gdata.docs.data.Archive representing the retrieved archive. """ if isinstance(self_link, atom.data.Link): self_link = self_link.href return self.get_entry(self_link, desired_class=gdata.docs.data.Archive, **kwargs) GetArchiveBySelfLink = get_archive_by_self_link def create_archive(self, entry, **kwargs): """Creates a new archive of resources. Args: entry: gdata.docs.data.Archive representing metadata of archive to create. kwargs: Other parameters to pass to self.post(). Returns: gdata.docs.data.Archive containing information about new archive. """ return self.post(entry, ARCHIVE_FEED_URI, desired_class=gdata.docs.data.Archive, **kwargs) CreateArchive = create_archive def update_archive(self, entry, **kwargs): """Updates the given Archive with new metadata. This method is really only useful for updating the notification email address of an archive that is being processed. Args: entry: Archive to update. Make any metadata changes to this entry. kwargs: Other parameters to pass to super(DocsClient, self).update(). Returns: gdata.docs.data.Archive representing the updated archive. """ return super(DocsClient, self).update(entry, **kwargs) UpdateArchive = update_archive download_archive = DownloadResource DownloadArchive = download_archive download_archive_to_memory = DownloadResourceToMemory DownloadArchiveToMemory = download_archive_to_memory def delete_archive(self, entry, **kwargs): """Aborts the given Archive operation, or deletes the Archive. Args: entry: gdata.docs.data.Archive to delete. kwargs: Other args to pass to gdata.client.GDClient.Delete() Returns: Result of delete request. """ return super(DocsClient, self).delete(entry, force=True, **kwargs) DeleteArchive = delete_archive class DocsQuery(gdata.client.Query): def __init__(self, title=None, title_exact=None, opened_min=None, opened_max=None, edited_min=None, edited_max=None, owner=None, writer=None, reader=None, show_collections=None, show_root=None, show_deleted=None, ocr=None, target_language=None, source_language=None, convert=None, query=None, **kwargs): """Constructs a query URL for the Google Documents List API. Args: title: str (optional) Specifies the search terms for the title of a document. This parameter used without title_exact will only submit partial queries, not exact queries. title_exact: str (optional) Meaningless without title. Possible values are 'true' and 'false'. Note: Matches are case-insensitive. opened_min: str (optional) Lower bound on the last time a document was opened by the current user. Use the RFC 3339 timestamp format. For example: opened_min='2005-08-09T09:57:00-08:00'. opened_max: str (optional) Upper bound on the last time a document was opened by the current user. (See also opened_min.) edited_min: str (optional) Lower bound on the last time a document was edited by the current user. This value corresponds to the edited.text value in the doc's entry object, which represents changes to the document's content or metadata. Use the RFC 3339 timestamp format. For example: edited_min='2005-08-09T09:57:00-08:00' edited_max: str (optional) Upper bound on the last time a document was edited by the user. (See also edited_min.) owner: str (optional) Searches for documents with a specific owner. Use the email address of the owner. For example: owner='user@gmail.com' writer: str (optional) Searches for documents which can be written to by specific users. Use a single email address or a comma separated list of email addresses. For example: writer='user1@gmail.com,user@example.com' reader: str (optional) Searches for documents which can be read by specific users. (See also writer.) show_collections: str (optional) Specifies whether the query should return collections as well as documents and files. Possible values are 'true' and 'false'. Default is 'false'. show_root: (optional) 'true' to specify when an item is in the root collection. Default is 'false' show_deleted: str (optional) Specifies whether the query should return documents which are in the trash as well as other documents. Possible values are 'true' and 'false'. Default is false. ocr: str (optional) Specifies whether to attempt OCR on a .jpg, .png, or .gif upload. Possible values are 'true' and 'false'. Default is false. See OCR in the Protocol Guide: http://code.google.com/apis/documents/docs/3.0/developers_guide_protocol.html#OCR target_language: str (optional) Specifies the language to translate a document into. See Document Translation in the Protocol Guide for a table of possible values: http://code.google.com/apis/documents/docs/3.0/developers_guide_protocol.html#DocumentTranslation source_language: str (optional) Specifies the source language of the original document. Optional when using the translation service. If not provided, Google will attempt to auto-detect the source language. See Document Translation in the Protocol Guide for a table of possible values (link in target_language). convert: str (optional) Used when uploading files specify if document uploads should convert to a native Google Docs format. Possible values are 'true' and 'false'. The default is 'true'. query: str (optional) Full-text query to use. See the 'q' parameter in the documentation. """ gdata.client.Query.__init__(self, **kwargs) self.convert = convert self.title = title self.title_exact = title_exact self.opened_min = opened_min self.opened_max = opened_max self.edited_min = edited_min self.edited_max = edited_max self.owner = owner self.writer = writer self.reader = reader self.show_collections = show_collections self.show_root = show_root self.show_deleted = show_deleted self.ocr = ocr self.target_language = target_language self.source_language = source_language self.query = query def modify_request(self, http_request): gdata.client._add_query_param('convert', self.convert, http_request) gdata.client._add_query_param('title', self.title, http_request) gdata.client._add_query_param('title-exact', self.title_exact, http_request) gdata.client._add_query_param('opened-min', self.opened_min, http_request) gdata.client._add_query_param('opened-max', self.opened_max, http_request) gdata.client._add_query_param('edited-min', self.edited_min, http_request) gdata.client._add_query_param('edited-max', self.edited_max, http_request) gdata.client._add_query_param('owner', self.owner, http_request) gdata.client._add_query_param('writer', self.writer, http_request) gdata.client._add_query_param('reader', self.reader, http_request) gdata.client._add_query_param('query', self.query, http_request) gdata.client._add_query_param('showfolders', self.show_collections, http_request) gdata.client._add_query_param('showroot', self.show_root, http_request) gdata.client._add_query_param('showdeleted', self.show_deleted, http_request) gdata.client._add_query_param('ocr', self.ocr, http_request) gdata.client._add_query_param('targetLanguage', self.target_language, http_request) gdata.client._add_query_param('sourceLanguage', self.source_language, http_request) gdata.client.Query.modify_request(self, http_request) ModifyRequest = modify_request ����������������������������������������������������������������������������������������������������������������������������������������gdata-2.0.18/src/gdata/docs/service.py��������������������������������������������������������������0000640�0364663�0011610�00000055155�12156622363�017562� 0����������������������������������������������������������������������������������������������������ustar �afshar��������������������������eng�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/python # # Copyright 2009 Google Inc. All Rights Reserved. # # 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. """DocsService extends the GDataService to streamline Google Documents operations. DocsService: Provides methods to query feeds and manipulate items. Extends GDataService. DocumentQuery: Queries a Google Document list feed. DocumentAclQuery: Queries a Google Document Acl feed. """ __author__ = ('api.jfisher (Jeff Fisher), ' 'e.bidelman (Eric Bidelman)') import re import atom import gdata.service import gdata.docs import urllib # XML Namespaces used in Google Documents entities. DATA_KIND_SCHEME = gdata.GDATA_NAMESPACE + '#kind' DOCUMENT_LABEL = 'document' SPREADSHEET_LABEL = 'spreadsheet' PRESENTATION_LABEL = 'presentation' FOLDER_LABEL = 'folder' PDF_LABEL = 'pdf' LABEL_SCHEME = gdata.GDATA_NAMESPACE + '/labels' STARRED_LABEL_TERM = LABEL_SCHEME + '#starred' TRASHED_LABEL_TERM = LABEL_SCHEME + '#trashed' HIDDEN_LABEL_TERM = LABEL_SCHEME + '#hidden' MINE_LABEL_TERM = LABEL_SCHEME + '#mine' PRIVATE_LABEL_TERM = LABEL_SCHEME + '#private' SHARED_WITH_DOMAIN_LABEL_TERM = LABEL_SCHEME + '#shared-with-domain' VIEWED_LABEL_TERM = LABEL_SCHEME + '#viewed' FOLDERS_SCHEME_PREFIX = gdata.docs.DOCUMENTS_NAMESPACE + '/folders/' # File extensions of documents that are permitted to be uploaded or downloaded. SUPPORTED_FILETYPES = { 'CSV': 'text/csv', 'TSV': 'text/tab-separated-values', 'TAB': 'text/tab-separated-values', 'DOC': 'application/msword', 'DOCX': ('application/vnd.openxmlformats-officedocument.' 'wordprocessingml.document'), 'ODS': 'application/x-vnd.oasis.opendocument.spreadsheet', 'ODT': 'application/vnd.oasis.opendocument.text', 'RTF': 'application/rtf', 'SXW': 'application/vnd.sun.xml.writer', 'TXT': 'text/plain', 'XLS': 'application/vnd.ms-excel', 'XLSX': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'PDF': 'application/pdf', 'PNG': 'image/png', 'PPT': 'application/vnd.ms-powerpoint', 'PPS': 'application/vnd.ms-powerpoint', 'HTM': 'text/html', 'HTML': 'text/html', 'ZIP': 'application/zip', 'SWF': 'application/x-shockwave-flash' } class DocsService(gdata.service.GDataService): """Client extension for the Google Documents service Document List feed.""" __FILE_EXT_PATTERN = re.compile('.*\.([a-zA-Z]{3,}$)') __RESOURCE_ID_PATTERN = re.compile('^([a-z]*)(:|%3A)([\w-]*)$') def __init__(self, email=None, password=None, source=None, server='docs.google.com', additional_headers=None, **kwargs): """Creates a client for the Google Documents service. Args: email: string (optional) The user's email address, used for authentication. password: string (optional) The user's password. source: string (optional) The name of the user's application. server: string (optional) The name of the server to which a connection will be opened. Default value: 'docs.google.com'. **kwargs: The other parameters to pass to gdata.service.GDataService constructor. """ gdata.service.GDataService.__init__( self, email=email, password=password, service='writely', source=source, server=server, additional_headers=additional_headers, **kwargs) self.ssl = True def _MakeKindCategory(self, label): if label is None: return None return atom.Category(scheme=DATA_KIND_SCHEME, term=gdata.docs.DOCUMENTS_NAMESPACE + '#' + label, label=label) def _MakeContentLinkFromId(self, resource_id): match = self.__RESOURCE_ID_PATTERN.match(resource_id) label = match.group(1) doc_id = match.group(3) if label == DOCUMENT_LABEL: return '/feeds/download/documents/Export?docId=%s' % doc_id if label == PRESENTATION_LABEL: return '/feeds/download/presentations/Export?docId=%s' % doc_id if label == SPREADSHEET_LABEL: return ('https://spreadsheets.google.com/feeds/download/spreadsheets/' 'Export?key=%s' % doc_id) raise ValueError, 'Invalid resource id: %s' % resource_id def _UploadFile(self, media_source, title, category, folder_or_uri=None): """Uploads a file to the Document List feed. Args: media_source: A gdata.MediaSource object containing the file to be uploaded. title: string The title of the document on the server after being uploaded. category: An atom.Category object specifying the appropriate document type. folder_or_uri: DocumentListEntry or string (optional) An object with a link to a folder or a uri to a folder to upload to. Note: A valid uri for a folder is of the form: /feeds/folders/private/full/folder%3Afolder_id Returns: A DocumentListEntry containing information about the document created on the Google Documents service. """ if folder_or_uri: try: uri = folder_or_uri.content.src except AttributeError: uri = folder_or_uri else: uri = '/feeds/documents/private/full' entry = gdata.docs.DocumentListEntry() entry.title = atom.Title(text=title) if category is not None: entry.category.append(category) entry = self.Post(entry, uri, media_source=media_source, extra_headers={'Slug': media_source.file_name}, converter=gdata.docs.DocumentListEntryFromString) return entry def _DownloadFile(self, uri, file_path): """Downloads a file. 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() timeout = 5 while server_response.status == 302 and timeout > 0: server_response = self.request('GET', server_response.getheader('Location')) response_body = server_response.read() timeout -= 1 if server_response.status != 200: raise gdata.service.RequestError, {'status': server_response.status, 'reason': server_response.reason, 'body': response_body} f = open(file_path, 'wb') f.write(response_body) f.flush() f.close() def MoveIntoFolder(self, source_entry, folder_entry): """Moves a document into a folder in the Document List Feed. Args: source_entry: DocumentListEntry An object representing the source document/folder. folder_entry: DocumentListEntry An object with a link to the destination folder. Returns: A DocumentListEntry containing information about the document created on the Google Documents service. """ entry = gdata.docs.DocumentListEntry() entry.id = source_entry.id entry = self.Post(entry, folder_entry.content.src, converter=gdata.docs.DocumentListEntryFromString) return entry def Query(self, uri, converter=gdata.docs.DocumentListFeedFromString): """Queries the Document List feed and returns the resulting feed of entries. Args: uri: string The full URI to be queried. This can contain query parameters, a hostname, or simply the relative path to a Document List feed. The DocumentQuery object is useful when constructing query parameters. converter: func (optional) A function which will be executed on the retrieved item, generally to render it into a Python object. By default the DocumentListFeedFromString function is used to return a DocumentListFeed object. This is because most feed queries will result in a feed and not a single entry. """ return self.Get(uri, converter=converter) def QueryDocumentListFeed(self, uri): """Retrieves a DocumentListFeed by retrieving a URI based off the Document List feed, including any query parameters. A DocumentQuery object can be used to construct these parameters. Args: uri: string The URI of the feed being retrieved possibly with query parameters. Returns: A DocumentListFeed object representing the feed returned by the server. """ return self.Get(uri, converter=gdata.docs.DocumentListFeedFromString) def GetDocumentListEntry(self, uri): """Retrieves a particular DocumentListEntry by its unique URI. Args: uri: string The unique URI of an entry in a Document List feed. Returns: A DocumentListEntry object representing the retrieved entry. """ return self.Get(uri, converter=gdata.docs.DocumentListEntryFromString) def GetDocumentListFeed(self, uri=None): """Retrieves a feed containing all of a user's documents. Args: uri: string A full URI to query the Document List feed. """ if not uri: uri = gdata.docs.service.DocumentQuery().ToUri() return self.QueryDocumentListFeed(uri) def GetDocumentListAclEntry(self, uri): """Retrieves a particular DocumentListAclEntry by its unique URI. Args: uri: string The unique URI of an entry in a Document List feed. Returns: A DocumentListAclEntry object representing the retrieved entry. """ return self.Get(uri, converter=gdata.docs.DocumentListAclEntryFromString) def GetDocumentListAclFeed(self, uri): """Retrieves a feed containing all of a user's documents. Args: uri: string The URI of a document's Acl feed to retrieve. Returns: A DocumentListAclFeed object representing the ACL feed returned by the server. """ return self.Get(uri, converter=gdata.docs.DocumentListAclFeedFromString) def Upload(self, media_source, title, folder_or_uri=None, label=None): """Uploads a document inside of a MediaSource object to the Document List feed with the given title. Args: media_source: MediaSource The gdata.MediaSource object containing a document file to be uploaded. title: string The title of the document on the server after being uploaded. folder_or_uri: DocumentListEntry or string (optional) An object with a link to a folder or a uri to a folder to upload to. Note: A valid uri for a folder is of the form: /feeds/folders/private/full/folder%3Afolder_id label: optional label describing the type of the document to be created. Returns: A DocumentListEntry containing information about the document created on the Google Documents service. """ return self._UploadFile(media_source, title, self._MakeKindCategory(label), folder_or_uri) def Download(self, entry_or_id_or_url, file_path, export_format=None, gid=None, extra_params=None): """Downloads a document from the Document List. Args: entry_or_id_or_url: a DocumentListEntry, or the resource id of an entry, or a url to download from (such as the content src). file_path: string The full path to save the file to. export_format: the format to convert to, if conversion is required. gid: grid id, for downloading a single grid of a spreadsheet extra_params: a map of any further parameters to control how the document is downloaded Raises: RequestError if the service does not respond with success """ if isinstance(entry_or_id_or_url, gdata.docs.DocumentListEntry): url = entry_or_id_or_url.content.src else: if self.__RESOURCE_ID_PATTERN.match(entry_or_id_or_url): url = self._MakeContentLinkFromId(entry_or_id_or_url) else: url = entry_or_id_or_url if export_format is not None: if url.find('/Export?') == -1: raise gdata.service.Error, ('This entry cannot be exported ' 'as a different format') url += '&exportFormat=%s' % export_format if gid is not None: if url.find('spreadsheets') == -1: raise gdata.service.Error, 'grid id param is not valid for this entry' url += '&gid=%s' % gid if extra_params: url += '&' + urllib.urlencode(extra_params) self._DownloadFile(url, file_path) def Export(self, entry_or_id_or_url, file_path, gid=None, extra_params=None): """Downloads a document from the Document List in a different format. Args: entry_or_id_or_url: a DocumentListEntry, or the resource id of an entry, or a url to download from (such as the content src). file_path: string The full path to save the file to. The export format is inferred from the the file extension. gid: grid id, for downloading a single grid of a spreadsheet extra_params: a map of any further parameters to control how the document is downloaded Raises: RequestError if the service does not respond with success """ ext = None match = self.__FILE_EXT_PATTERN.match(file_path) if match: ext = match.group(1) self.Download(entry_or_id_or_url, file_path, ext, gid, extra_params) def CreateFolder(self, title, folder_or_uri=None): """Creates a folder in the Document List feed. Args: title: string The title of the folder on the server after being created. folder_or_uri: DocumentListEntry or string (optional) An object with a link to a folder or a uri to a folder to upload to. Note: A valid uri for a folder is of the form: /feeds/folders/private/full/folder%3Afolder_id Returns: A DocumentListEntry containing information about the folder created on the Google Documents service. """ 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(self._MakeKindCategory(FOLDER_LABEL)) folder_entry = self.Post(folder_entry, uri, converter=gdata.docs.DocumentListEntryFromString) return folder_entry def MoveOutOfFolder(self, source_entry): """Moves a document into a folder in the Document List Feed. Args: source_entry: DocumentListEntry An object representing the source document/folder. Returns: True if the entry was moved out. """ return self.Delete(source_entry.GetEditLink().href) # Deprecated methods #@atom.deprecated('Please use Upload instead') def UploadPresentation(self, media_source, title, folder_or_uri=None): """Uploads a presentation inside of a MediaSource object to the Document List feed with the given title. This method is deprecated, use Upload instead. Args: media_source: MediaSource The MediaSource object containing a presentation file to be uploaded. title: string The title of the presentation on the server after being uploaded. folder_or_uri: DocumentListEntry or string (optional) An object with a link to a folder or a uri to a folder to upload to. Note: A valid uri for a folder is of the form: /feeds/folders/private/full/folder%3Afolder_id Returns: A DocumentListEntry containing information about the presentation created on the Google Documents service. """ return self._UploadFile( media_source, title, self._MakeKindCategory(PRESENTATION_LABEL), folder_or_uri=folder_or_uri) UploadPresentation = atom.deprecated('Please use Upload instead')( UploadPresentation) #@atom.deprecated('Please use Upload instead') def UploadSpreadsheet(self, media_source, title, folder_or_uri=None): """Uploads a spreadsheet inside of a MediaSource object to the Document List feed with the given title. This method is deprecated, use Upload instead. Args: media_source: MediaSource The MediaSource object containing a spreadsheet file to be uploaded. title: string The title of the spreadsheet on the server after being uploaded. folder_or_uri: DocumentListEntry or string (optional) An object with a link to a folder or a uri to a folder to upload to. Note: A valid uri for a folder is of the form: /feeds/folders/private/full/folder%3Afolder_id Returns: A DocumentListEntry containing information about the spreadsheet created on the Google Documents service. """ return self._UploadFile( media_source, title, self._MakeKindCategory(SPREADSHEET_LABEL), folder_or_uri=folder_or_uri) UploadSpreadsheet = atom.deprecated('Please use Upload instead')( UploadSpreadsheet) #@atom.deprecated('Please use Upload instead') def UploadDocument(self, media_source, title, folder_or_uri=None): """Uploads a document inside of a MediaSource object to the Document List feed with the given title. This method is deprecated, use Upload instead. Args: media_source: MediaSource The gdata.MediaSource object containing a document file to be uploaded. title: string The title of the document on the server after being uploaded. folder_or_uri: DocumentListEntry or string (optional) An object with a link to a folder or a uri to a folder to upload to. Note: A valid uri for a folder is of the form: /feeds/folders/private/full/folder%3Afolder_id Returns: A DocumentListEntry containing information about the document created on the Google Documents service. """ return self._UploadFile( media_source, title, self._MakeKindCategory(DOCUMENT_LABEL), folder_or_uri=folder_or_uri) UploadDocument = atom.deprecated('Please use Upload instead')( UploadDocument) """Calling any of these functions is the same as calling Export""" DownloadDocument = atom.deprecated('Please use Export instead')(Export) DownloadPresentation = atom.deprecated('Please use Export instead')(Export) DownloadSpreadsheet = atom.deprecated('Please use Export instead')(Export) """Calling any of these functions is the same as calling MoveIntoFolder""" MoveDocumentIntoFolder = atom.deprecated( 'Please use MoveIntoFolder instead')(MoveIntoFolder) MovePresentationIntoFolder = atom.deprecated( 'Please use MoveIntoFolder instead')(MoveIntoFolder) MoveSpreadsheetIntoFolder = atom.deprecated( 'Please use MoveIntoFolder instead')(MoveIntoFolder) MoveFolderIntoFolder = atom.deprecated( 'Please use MoveIntoFolder instead')(MoveIntoFolder) class DocumentQuery(gdata.service.Query): """Object used to construct a URI to query the Google Document List feed""" def __init__(self, feed='/feeds/documents', visibility='private', projection='full', text_query=None, params=None, categories=None): """Constructor for Document List Query Args: feed: string (optional) The path for the feed. (e.g. '/feeds/documents') visibility: string (optional) The visibility chosen for the current feed. projection: string (optional) The projection chosen for the current feed. text_query: string (optional) The contents of the q query parameter. This string is URL escaped upon conversion to a URI. params: dict (optional) Parameter value string pairs which become URL params when translated to a URI. These parameters are added to the query's items. categories: list (optional) List of category strings which should be included as query categories. See gdata.service.Query for additional documentation. Yields: A DocumentQuery object used to construct a URI based on the Document List feed. """ self.visibility = visibility self.projection = projection gdata.service.Query.__init__(self, feed, text_query, params, categories) def ToUri(self): """Generates a URI from the query parameters set in the object. Returns: A string containing the URI used to retrieve entries from the Document List feed. """ old_feed = self.feed self.feed = '/'.join([old_feed, self.visibility, self.projection]) new_feed = gdata.service.Query.ToUri(self) self.feed = old_feed return new_feed def AddNamedFolder(self, email, folder_name): """Adds a named folder category, qualified by a schema. This function lets you query for documents that are contained inside a named folder without fear of collision with other categories. Args: email: string The email of the user who owns the folder. folder_name: string The name of the folder. Returns: The string of the category that was added to the object. """ category = '{%s%s}%s' % (FOLDERS_SCHEME_PREFIX, email, folder_name) self.categories.append(category) return category def RemoveNamedFolder(self, email, folder_name): """Removes a named folder category, qualified by a schema. Args: email: string The email of the user who owns the folder. folder_name: string The name of the folder. Returns: The string of the category that was removed to the object. """ category = '{%s%s}%s' % (FOLDERS_SCHEME_PREFIX, email, folder_name) self.categories.remove(category) return category class DocumentAclQuery(gdata.service.Query): """Object used to construct a URI to query a Document's ACL feed""" def __init__(self, resource_id, feed='/feeds/acl/private/full'): """Constructor for Document ACL Query Args: resource_id: string The resource id. (e.g. 'document%3Adocument_id', 'spreadsheet%3Aspreadsheet_id', etc.) feed: string (optional) The path for the feed. (e.g. '/feeds/acl/private/full') Yields: A DocumentAclQuery object used to construct a URI based on the Document ACL feed. """ self.resource_id = resource_id gdata.service.Query.__init__(self, feed) def ToUri(self): """Generates a URI from the query parameters set in the object. Returns: A string containing the URI used to retrieve entries from the Document ACL feed. """ return '%s/%s' % (gdata.service.Query.ToUri(self), self.resource_id) �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������gdata-2.0.18/src/gdata/exif/������������������������������������������������������������������������0000750�0364663�0011610�00000000000�12156625015�015534� 5����������������������������������������������������������������������������������������������������ustar �afshar��������������������������eng�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������gdata-2.0.18/src/gdata/exif/__init__.py�������������������������������������������������������������0000640�0364663�0011610�00000015505�12156622363�017657� 0����������������������������������������������������������������������������������������������������ustar �afshar��������������������������eng�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# -*-*- encoding: utf-8 -*-*- # # This is gdata.photos.exif, implementing the exif namespace in gdata # # $Id: __init__.py 81 2007-10-03 14:41:42Z havard.gulldahl $ # # Copyright 2007 HÃ¥vard Gulldahl # Portions copyright 2007 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. """This module maps elements from the {EXIF} namespace[1] to GData objects. These elements describe image data, using exif attributes[2]. Picasa Web Albums uses the exif namespace to represent Exif data encoded in a photo [3]. Picasa Web Albums uses the following exif elements: exif:distance exif:exposure exif:flash exif:focallength exif:fstop exif:imageUniqueID exif:iso exif:make exif:model exif:tags exif:time [1]: http://schemas.google.com/photos/exif/2007. [2]: http://en.wikipedia.org/wiki/Exif [3]: http://code.google.com/apis/picasaweb/reference.html#exif_reference """ __author__ = u'havard@gulldahl.no'# (HÃ¥vard Gulldahl)' #BUG: pydoc chokes on non-ascii chars in __author__ __license__ = 'Apache License v2' import atom import gdata EXIF_NAMESPACE = 'http://schemas.google.com/photos/exif/2007' class ExifBaseElement(atom.AtomBase): """Base class for elements in the EXIF_NAMESPACE (%s). To add new elements, you only need to add the element tag name to self._tag """ % EXIF_NAMESPACE _tag = '' _namespace = EXIF_NAMESPACE _children = atom.AtomBase._children.copy() _attributes = atom.AtomBase._attributes.copy() def __init__(self, name=None, extension_elements=None, extension_attributes=None, text=None): self.name = name self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} class Distance(ExifBaseElement): "(float) The distance to the subject, e.g. 0.0" _tag = 'distance' def DistanceFromString(xml_string): return atom.CreateClassFromXMLString(Distance, xml_string) class Exposure(ExifBaseElement): "(float) The exposure time used, e.g. 0.025 or 8.0E4" _tag = 'exposure' def ExposureFromString(xml_string): return atom.CreateClassFromXMLString(Exposure, xml_string) class Flash(ExifBaseElement): """(string) Boolean value indicating whether the flash was used. The .text attribute will either be `true' or `false' As a convenience, this object's .bool method will return what you want, so you can say: flash_used = bool(Flash) """ _tag = 'flash' def __bool__(self): if self.text.lower() in ('true','false'): return self.text.lower() == 'true' def FlashFromString(xml_string): return atom.CreateClassFromXMLString(Flash, xml_string) class Focallength(ExifBaseElement): "(float) The focal length used, e.g. 23.7" _tag = 'focallength' def FocallengthFromString(xml_string): return atom.CreateClassFromXMLString(Focallength, xml_string) class Fstop(ExifBaseElement): "(float) The fstop value used, e.g. 5.0" _tag = 'fstop' def FstopFromString(xml_string): return atom.CreateClassFromXMLString(Fstop, xml_string) class ImageUniqueID(ExifBaseElement): "(string) The unique image ID for the photo. Generated by Google Photo servers" _tag = 'imageUniqueID' def ImageUniqueIDFromString(xml_string): return atom.CreateClassFromXMLString(ImageUniqueID, xml_string) class Iso(ExifBaseElement): "(int) The iso equivalent value used, e.g. 200" _tag = 'iso' def IsoFromString(xml_string): return atom.CreateClassFromXMLString(Iso, xml_string) class Make(ExifBaseElement): "(string) The make of the camera used, e.g. Fictitious Camera Company" _tag = 'make' def MakeFromString(xml_string): return atom.CreateClassFromXMLString(Make, xml_string) class Model(ExifBaseElement): "(string) The model of the camera used,e.g AMAZING-100D" _tag = 'model' def ModelFromString(xml_string): return atom.CreateClassFromXMLString(Model, xml_string) class Time(ExifBaseElement): """(int) The date/time the photo was taken, e.g. 1180294337000. Represented as the number of milliseconds since January 1st, 1970. The value of this element will always be identical to the value of the <gphoto:timestamp>. Look at this object's .isoformat() for a human friendly datetime string: photo_epoch = Time.text # 1180294337000 photo_isostring = Time.isoformat() # '2007-05-27T19:32:17.000Z' Alternatively: photo_datetime = Time.datetime() # (requires python >= 2.3) """ _tag = 'time' def isoformat(self): """(string) Return the timestamp as a ISO 8601 formatted string, e.g. '2007-05-27T19:32:17.000Z' """ import time epoch = float(self.text)/1000 return time.strftime('%Y-%m-%dT%H:%M:%S.000Z', time.gmtime(epoch)) def datetime(self): """(datetime.datetime) Return the timestamp as a datetime.datetime object Requires python 2.3 """ import datetime epoch = float(self.text)/1000 return datetime.datetime.fromtimestamp(epoch) def TimeFromString(xml_string): return atom.CreateClassFromXMLString(Time, xml_string) class Tags(ExifBaseElement): """The container for all exif elements. The <exif:tags> element can appear as a child of a photo entry. """ _tag = 'tags' _children = atom.AtomBase._children.copy() _children['{%s}fstop' % EXIF_NAMESPACE] = ('fstop', Fstop) _children['{%s}make' % EXIF_NAMESPACE] = ('make', Make) _children['{%s}model' % EXIF_NAMESPACE] = ('model', Model) _children['{%s}distance' % EXIF_NAMESPACE] = ('distance', Distance) _children['{%s}exposure' % EXIF_NAMESPACE] = ('exposure', Exposure) _children['{%s}flash' % EXIF_NAMESPACE] = ('flash', Flash) _children['{%s}focallength' % EXIF_NAMESPACE] = ('focallength', Focallength) _children['{%s}iso' % EXIF_NAMESPACE] = ('iso', Iso) _children['{%s}time' % EXIF_NAMESPACE] = ('time', Time) _children['{%s}imageUniqueID' % EXIF_NAMESPACE] = ('imageUniqueID', ImageUniqueID) def __init__(self, extension_elements=None, extension_attributes=None, text=None): ExifBaseElement.__init__(self, extension_elements=extension_elements, extension_attributes=extension_attributes, text=text) self.fstop=None self.make=None self.model=None self.distance=None self.exposure=None self.flash=None self.focallength=None self.iso=None self.time=None self.imageUniqueID=None def TagsFromString(xml_string): return atom.CreateClassFromXMLString(Tags, xml_string) �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������gdata-2.0.18/src/gdata/service.py�������������������������������������������������������������������0000750�0364663�0011610�00000207751�12156622363�016635� 0����������������������������������������������������������������������������������������������������ustar �afshar��������������������������eng�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/python # # Copyright (C) 2006,2008 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. """GDataService provides CRUD ops. and programmatic login for GData services. Error: A base exception class for all exceptions in the gdata_client module. CaptchaRequired: This exception is thrown when a login attempt results in a captcha challenge from the ClientLogin service. When this exception is thrown, the captcha_token and captcha_url are set to the values provided in the server's response. BadAuthentication: Raised when a login attempt is made with an incorrect username or password. NotAuthenticated: Raised if an operation requiring authentication is called before a user has authenticated. NonAuthSubToken: Raised if a method to modify an AuthSub token is used when the user is either not authenticated or is authenticated through another authentication mechanism. NonOAuthToken: Raised if a method to modify an OAuth token is used when the user is either not authenticated or is authenticated through another authentication mechanism. RequestError: Raised if a CRUD request returned a non-success code. UnexpectedReturnType: Raised if the response from the server was not of the desired type. For example, this would be raised if the server sent a feed when the client requested an entry. GDataService: Encapsulates user credentials needed to perform insert, update and delete operations with the GData API. An instance can perform user authentication, query, insertion, deletion, and update. Query: Eases query URI creation by allowing URI parameters to be set as dictionary attributes. For example a query with a feed of '/base/feeds/snippets' and ['bq'] set to 'digital camera' will produce '/base/feeds/snippets?bq=digital+camera' when .ToUri() is called on it. """ __author__ = 'api.jscudder (Jeffrey Scudder)' import re import urllib import urlparse try: from xml.etree import cElementTree as ElementTree except ImportError: try: import cElementTree as ElementTree except ImportError: try: from xml.etree import ElementTree except ImportError: from elementtree import ElementTree import atom.service import gdata import atom import atom.http_interface import atom.token_store import gdata.auth import gdata.gauth AUTH_SERVER_HOST = 'https://www.google.com' # When requesting an AuthSub token, it is often helpful to track the scope # which is being requested. One way to accomplish this is to add a URL # parameter to the 'next' URL which contains the requested scope. This # constant is the default name (AKA key) for the URL parameter. SCOPE_URL_PARAM_NAME = 'authsub_token_scope' # When requesting an OAuth access token or authorization of an existing OAuth # request token, it is often helpful to track the scope(s) which is/are being # requested. One way to accomplish this is to add a URL parameter to the # 'callback' URL which contains the requested scope. This constant is the # default name (AKA key) for the URL parameter. OAUTH_SCOPE_URL_PARAM_NAME = 'oauth_token_scope' # Maps the service names used in ClientLogin to scope URLs. CLIENT_LOGIN_SCOPES = gdata.gauth.AUTH_SCOPES # Default parameters for GDataService.GetWithRetries method DEFAULT_NUM_RETRIES = 3 DEFAULT_DELAY = 1 DEFAULT_BACKOFF = 2 def lookup_scopes(service_name): """Finds the scope URLs for the desired service. In some cases, an unknown service may be used, and in those cases this function will return None. """ if service_name in CLIENT_LOGIN_SCOPES: return CLIENT_LOGIN_SCOPES[service_name] return None # Module level variable specifies which module should be used by GDataService # objects to make HttpRequests. This setting can be overridden on each # instance of GDataService. # This module level variable is deprecated. Reassign the http_client member # of a GDataService object instead. http_request_handler = atom.service class Error(Exception): pass class CaptchaRequired(Error): pass class BadAuthentication(Error): pass class NotAuthenticated(Error): pass class NonAuthSubToken(Error): pass class NonOAuthToken(Error): pass class RequestError(Error): pass class UnexpectedReturnType(Error): pass class BadAuthenticationServiceURL(Error): pass class FetchingOAuthRequestTokenFailed(RequestError): pass class TokenUpgradeFailed(RequestError): pass class RevokingOAuthTokenFailed(RequestError): pass class AuthorizationRequired(Error): pass class TokenHadNoScope(Error): pass class RanOutOfTries(Error): pass class GDataService(atom.service.AtomService): """Contains elements needed for GData login and CRUD request headers. Maintains additional headers (tokens for example) needed for the GData services to allow a user to perform inserts, updates, and deletes. """ # The hander member is deprecated, use http_client instead. handler = None # The auth_token member is deprecated, use the token_store instead. auth_token = None # The tokens dict is deprecated in favor of the token_store. tokens = None def __init__(self, email=None, password=None, account_type='HOSTED_OR_GOOGLE', service=None, auth_service_url=None, source=None, server=None, additional_headers=None, handler=None, tokens=None, http_client=None, token_store=None): """Creates an object of type GDataService. Args: email: string (optional) The user's email address, used for authentication. password: string (optional) The user's password. account_type: string (optional) The type of account to use. Use 'GOOGLE' for regular Google accounts or 'HOSTED' for Google Apps accounts, or 'HOSTED_OR_GOOGLE' to try finding a HOSTED account first and, if it doesn't exist, try finding a regular GOOGLE account. Default value: 'HOSTED_OR_GOOGLE'. service: string (optional) The desired service for which credentials will be obtained. auth_service_url: string (optional) User-defined auth token request URL allows users to explicitly specify where to send auth token requests. source: string (optional) The name of the user's application. server: string (optional) The name of the server to which a connection will be opened. Default value: 'base.google.com'. additional_headers: dictionary (optional) Any additional headers which should be included with CRUD operations. handler: module (optional) This parameter is deprecated and has been replaced by http_client. tokens: This parameter is deprecated, calls should be made to token_store instead. http_client: An object responsible for making HTTP requests using a request method. If none is provided, a new instance of atom.http.ProxiedHttpClient will be used. token_store: Keeps a collection of authorization tokens which can be applied to requests for a specific URLs. Critical methods are find_token based on a URL (atom.url.Url or a string), add_token, and remove_token. """ atom.service.AtomService.__init__(self, http_client=http_client, token_store=token_store) self.email = email self.password = password self.account_type = account_type self.service = service self.auth_service_url = auth_service_url self.server = server self.additional_headers = additional_headers or {} self._oauth_input_params = None self.__SetSource(source) self.__captcha_token = None self.__captcha_url = None self.__gsessionid = None if http_request_handler.__name__ == 'gdata.urlfetch': import gdata.alt.appengine self.http_client = gdata.alt.appengine.AppEngineHttpClient() def _SetSessionId(self, session_id): """Used in unit tests to simulate a 302 which sets a gsessionid.""" self.__gsessionid = session_id # Define properties for GDataService def _SetAuthSubToken(self, auth_token, scopes=None): """Deprecated, use SetAuthSubToken instead.""" self.SetAuthSubToken(auth_token, scopes=scopes) def __SetAuthSubToken(self, auth_token, scopes=None): """Deprecated, use SetAuthSubToken instead.""" self._SetAuthSubToken(auth_token, scopes=scopes) def _GetAuthToken(self): """Returns the auth token used for authenticating requests. Returns: string """ current_scopes = lookup_scopes(self.service) if current_scopes: token = self.token_store.find_token(current_scopes[0]) if hasattr(token, 'auth_header'): return token.auth_header return None def _GetCaptchaToken(self): """Returns a captcha token if the most recent login attempt generated one. The captcha token is only set if the Programmatic Login attempt failed because the Google service issued a captcha challenge. Returns: string """ return self.__captcha_token def __GetCaptchaToken(self): return self._GetCaptchaToken() captcha_token = property(__GetCaptchaToken, doc="""Get the captcha token for a login request.""") def _GetCaptchaURL(self): """Returns the URL of the captcha image if a login attempt generated one. The captcha URL is only set if the Programmatic Login attempt failed because the Google service issued a captcha challenge. Returns: string """ return self.__captcha_url def __GetCaptchaURL(self): return self._GetCaptchaURL() captcha_url = property(__GetCaptchaURL, doc="""Get the captcha URL for a login request.""") def GetGeneratorFromLinkFinder(self, link_finder, func, num_retries=DEFAULT_NUM_RETRIES, delay=DEFAULT_DELAY, backoff=DEFAULT_BACKOFF): """returns a generator for pagination""" yield link_finder next = link_finder.GetNextLink() while next is not None: next_feed = func(str(self.GetWithRetries( next.href, num_retries=num_retries, delay=delay, backoff=backoff))) yield next_feed next = next_feed.GetNextLink() def _GetElementGeneratorFromLinkFinder(self, link_finder, func, num_retries=DEFAULT_NUM_RETRIES, delay=DEFAULT_DELAY, backoff=DEFAULT_BACKOFF): for element in self.GetGeneratorFromLinkFinder(link_finder, func, num_retries=num_retries, delay=delay, backoff=backoff).entry: yield element def GetOAuthInputParameters(self): return self._oauth_input_params def SetOAuthInputParameters(self, signature_method, consumer_key, consumer_secret=None, rsa_key=None, two_legged_oauth=False, requestor_id=None): """Sets parameters required for using OAuth authentication mechanism. NOTE: Though consumer_secret and rsa_key are optional, either of the two is required depending on the value of the signature_method. Args: signature_method: class which provides implementation for strategy class oauth.oauth.OAuthSignatureMethod. Signature method to be used for signing each request. Valid implementations are provided as the constants defined by gdata.auth.OAuthSignatureMethod. Currently they are gdata.auth.OAuthSignatureMethod.RSA_SHA1 and gdata.auth.OAuthSignatureMethod.HMAC_SHA1 consumer_key: string Domain identifying third_party web application. consumer_secret: string (optional) Secret generated during registration. Required only for HMAC_SHA1 signature method. rsa_key: string (optional) Private key required for RSA_SHA1 signature method. two_legged_oauth: boolean (optional) Enables two-legged OAuth process. requestor_id: string (optional) User email adress to make requests on their behalf. This parameter should only be set when two_legged_oauth is True. """ self._oauth_input_params = gdata.auth.OAuthInputParams( signature_method, consumer_key, consumer_secret=consumer_secret, rsa_key=rsa_key, requestor_id=requestor_id) if two_legged_oauth: oauth_token = gdata.auth.OAuthToken( oauth_input_params=self._oauth_input_params) self.SetOAuthToken(oauth_token) def FetchOAuthRequestToken(self, scopes=None, extra_parameters=None, request_url='%s/accounts/OAuthGetRequestToken' % \ AUTH_SERVER_HOST, oauth_callback=None): """Fetches and sets the OAuth request token and returns it. Args: scopes: string or list of string base URL(s) of the service(s) to be accessed. If None, then this method tries to determine the scope(s) from the current service. extra_parameters: dict (optional) key-value pairs as any additional parameters to be included in the URL and signature while making a request for fetching an OAuth request token. All the OAuth parameters are added by default. But if provided through this argument, any default parameters will be overwritten. For e.g. a default parameter oauth_version 1.0 can be overwritten if extra_parameters = {'oauth_version': '2.0'} request_url: Request token URL. The default is 'https://www.google.com/accounts/OAuthGetRequestToken'. oauth_callback: str (optional) If set, it is assume the client is using the OAuth v1.0a protocol where the callback url is sent in the request token step. If the oauth_callback is also set in extra_params, this value will override that one. Returns: The fetched request token as a gdata.auth.OAuthToken object. Raises: FetchingOAuthRequestTokenFailed if the server responded to the request with an error. """ if scopes is None: scopes = lookup_scopes(self.service) if not isinstance(scopes, (list, tuple)): scopes = [scopes,] if oauth_callback: if extra_parameters is not None: extra_parameters['oauth_callback'] = oauth_callback else: extra_parameters = {'oauth_callback': oauth_callback} request_token_url = gdata.auth.GenerateOAuthRequestTokenUrl( self._oauth_input_params, scopes, request_token_url=request_url, extra_parameters=extra_parameters) response = self.http_client.request('GET', str(request_token_url)) if response.status == 200: token = gdata.auth.OAuthToken() token.set_token_string(response.read()) token.scopes = scopes token.oauth_input_params = self._oauth_input_params self.SetOAuthToken(token) return token error = { 'status': response.status, 'reason': 'Non 200 response on fetch request token', 'body': response.read() } raise FetchingOAuthRequestTokenFailed(error) def SetOAuthToken(self, oauth_token): """Attempts to set the current token and add it to the token store. The oauth_token can be any OAuth token i.e. unauthorized request token, authorized request token or access token. This method also attempts to add the token to the token store. Use this method any time you want the current token to point to the oauth_token passed. For e.g. call this method with the request token you receive from FetchOAuthRequestToken. Args: request_token: gdata.auth.OAuthToken OAuth request token. """ if self.auto_set_current_token: self.current_token = oauth_token if self.auto_store_tokens: self.token_store.add_token(oauth_token) def GenerateOAuthAuthorizationURL( self, request_token=None, callback_url=None, extra_params=None, include_scopes_in_callback=False, scopes_param_prefix=OAUTH_SCOPE_URL_PARAM_NAME, request_url='%s/accounts/OAuthAuthorizeToken' % AUTH_SERVER_HOST): """Generates URL at which user will login to authorize the request token. Args: request_token: gdata.auth.OAuthToken (optional) OAuth request token. If not specified, then the current token will be used if it is of type <gdata.auth.OAuthToken>, else it is found by looking in the token_store by looking for a token for the current scope. callback_url: string (optional) The URL user will be sent to after logging in and granting access. extra_params: dict (optional) Additional parameters to be sent. include_scopes_in_callback: Boolean (default=False) if set to True, and if 'callback_url' is present, the 'callback_url' will be modified to include the scope(s) from the request token as a URL parameter. The key for the 'callback' URL's scope parameter will be OAUTH_SCOPE_URL_PARAM_NAME. The benefit of including the scope URL as a parameter to the 'callback' URL, is that the page which receives the OAuth token will be able to tell which URLs the token grants access to. scopes_param_prefix: string (default='oauth_token_scope') The URL parameter key which maps to the list of valid scopes for the token. This URL parameter will be included in the callback URL along with the scopes of the token as value if include_scopes_in_callback=True. request_url: Authorization URL. The default is 'https://www.google.com/accounts/OAuthAuthorizeToken'. Returns: A string URL at which the user is required to login. Raises: NonOAuthToken if the user's request token is not an OAuth token or if a request token was not available. """ if request_token and not isinstance(request_token, gdata.auth.OAuthToken): raise NonOAuthToken if not request_token: if isinstance(self.current_token, gdata.auth.OAuthToken): request_token = self.current_token else: current_scopes = lookup_scopes(self.service) if current_scopes: token = self.token_store.find_token(current_scopes[0]) if isinstance(token, gdata.auth.OAuthToken): request_token = token if not request_token: raise NonOAuthToken return str(gdata.auth.GenerateOAuthAuthorizationUrl( request_token, authorization_url=request_url, callback_url=callback_url, extra_params=extra_params, include_scopes_in_callback=include_scopes_in_callback, scopes_param_prefix=scopes_param_prefix)) def UpgradeToOAuthAccessToken(self, authorized_request_token=None, request_url='%s/accounts/OAuthGetAccessToken' \ % AUTH_SERVER_HOST, oauth_version='1.0', oauth_verifier=None): """Upgrades the authorized request token to an access token and returns it Args: authorized_request_token: gdata.auth.OAuthToken (optional) OAuth request token. If not specified, then the current token will be used if it is of type <gdata.auth.OAuthToken>, else it is found by looking in the token_store by looking for a token for the current scope. request_url: Access token URL. The default is 'https://www.google.com/accounts/OAuthGetAccessToken'. oauth_version: str (default='1.0') oauth_version parameter. All other 'oauth_' parameters are added by default. This parameter too, is added by default but here you can override it's value. oauth_verifier: str (optional) If present, it is assumed that the client will use the OAuth v1.0a protocol which includes passing the oauth_verifier (as returned by the SP) in the access token step. Returns: Access token Raises: NonOAuthToken if the user's authorized request token is not an OAuth token or if an authorized request token was not available. TokenUpgradeFailed if the server responded to the request with an error. """ if (authorized_request_token and not isinstance(authorized_request_token, gdata.auth.OAuthToken)): raise NonOAuthToken if not authorized_request_token: if isinstance(self.current_token, gdata.auth.OAuthToken): authorized_request_token = self.current_token else: current_scopes = lookup_scopes(self.service) if current_scopes: token = self.token_store.find_token(current_scopes[0]) if isinstance(token, gdata.auth.OAuthToken): authorized_request_token = token if not authorized_request_token: raise NonOAuthToken access_token_url = gdata.auth.GenerateOAuthAccessTokenUrl( authorized_request_token, self._oauth_input_params, access_token_url=request_url, oauth_version=oauth_version, oauth_verifier=oauth_verifier) response = self.http_client.request('GET', str(access_token_url)) if response.status == 200: token = gdata.auth.OAuthTokenFromHttpBody(response.read()) token.scopes = authorized_request_token.scopes token.oauth_input_params = authorized_request_token.oauth_input_params self.SetOAuthToken(token) return token else: raise TokenUpgradeFailed({'status': response.status, 'reason': 'Non 200 response on upgrade', 'body': response.read()}) def RevokeOAuthToken(self, request_url='%s/accounts/AuthSubRevokeToken' % \ AUTH_SERVER_HOST): """Revokes an existing OAuth token. request_url: Token revoke URL. The default is 'https://www.google.com/accounts/AuthSubRevokeToken'. Raises: NonOAuthToken if the user's auth token is not an OAuth token. RevokingOAuthTokenFailed if request for revoking an OAuth token failed. """ scopes = lookup_scopes(self.service) token = self.token_store.find_token(scopes[0]) if not isinstance(token, gdata.auth.OAuthToken): raise NonOAuthToken response = token.perform_request(self.http_client, 'GET', request_url, headers={'Content-Type':'application/x-www-form-urlencoded'}) if response.status == 200: self.token_store.remove_token(token) else: raise RevokingOAuthTokenFailed def GetAuthSubToken(self): """Returns the AuthSub token as a string. If the token is an gdta.auth.AuthSubToken, the Authorization Label ("AuthSub token") is removed. This method examines the current_token to see if it is an AuthSubToken or SecureAuthSubToken. If not, it searches the token_store for a token which matches the current scope. The current scope is determined by the service name string member. Returns: If the current_token is set to an AuthSubToken/SecureAuthSubToken, return the token string. If there is no current_token, a token string for a token which matches the service object's default scope is returned. If there are no tokens valid for the scope, returns None. """ if isinstance(self.current_token, gdata.auth.AuthSubToken): return self.current_token.get_token_string() current_scopes = lookup_scopes(self.service) if current_scopes: token = self.token_store.find_token(current_scopes[0]) if isinstance(token, gdata.auth.AuthSubToken): return token.get_token_string() else: token = self.token_store.find_token(atom.token_store.SCOPE_ALL) if isinstance(token, gdata.auth.ClientLoginToken): return token.get_token_string() return None def SetAuthSubToken(self, token, scopes=None, rsa_key=None): """Sets the token sent in requests to an AuthSub token. Sets the current_token and attempts to add the token to the token_store. Only use this method if you have received a token from the AuthSub service. The auth token is set automatically when UpgradeToSessionToken() is used. See documentation for Google AuthSub here: http://code.google.com/apis/accounts/AuthForWebApps.html Args: token: gdata.auth.AuthSubToken or gdata.auth.SecureAuthSubToken or string The token returned by the AuthSub service. If the token is an AuthSubToken or SecureAuthSubToken, the scope information stored in the token is used. If the token is a string, the scopes parameter is used to determine the valid scopes. scopes: list of URLs for which the token is valid. This is only used if the token parameter is a string. rsa_key: string (optional) Private key required for RSA_SHA1 signature method. This parameter is necessary if the token is a string representing a secure token. """ if not isinstance(token, gdata.auth.AuthSubToken): token_string = token if rsa_key: token = gdata.auth.SecureAuthSubToken(rsa_key) else: token = gdata.auth.AuthSubToken() token.set_token_string(token_string) # If no scopes were set for the token, use the scopes passed in, or # try to determine the scopes based on the current service name. If # all else fails, set the token to match all requests. if not token.scopes: if scopes is None: scopes = lookup_scopes(self.service) if scopes is None: scopes = [atom.token_store.SCOPE_ALL] token.scopes = scopes if self.auto_set_current_token: self.current_token = token if self.auto_store_tokens: self.token_store.add_token(token) def GetClientLoginToken(self): """Returns the token string for the current token or a token matching the service scope. If the current_token is a ClientLoginToken, the token string for the current token is returned. If the current_token is not set, this method searches for a token in the token_store which is valid for the service object's current scope. The current scope is determined by the service name string member. The token string is the end of the Authorization header, it doesn not include the ClientLogin label. """ if isinstance(self.current_token, gdata.auth.ClientLoginToken): return self.current_token.get_token_string() current_scopes = lookup_scopes(self.service) if current_scopes: token = self.token_store.find_token(current_scopes[0]) if isinstance(token, gdata.auth.ClientLoginToken): return token.get_token_string() else: token = self.token_store.find_token(atom.token_store.SCOPE_ALL) if isinstance(token, gdata.auth.ClientLoginToken): return token.get_token_string() return None def SetClientLoginToken(self, token, scopes=None): """Sets the token sent in requests to a ClientLogin token. This method sets the current_token to a new ClientLoginToken and it also attempts to add the ClientLoginToken to the token_store. Only use this method if you have received a token from the ClientLogin service. The auth_token is set automatically when ProgrammaticLogin() is used. See documentation for Google ClientLogin here: http://code.google.com/apis/accounts/docs/AuthForInstalledApps.html Args: token: string or instance of a ClientLoginToken. """ if not isinstance(token, gdata.auth.ClientLoginToken): token_string = token token = gdata.auth.ClientLoginToken() token.set_token_string(token_string) if not token.scopes: if scopes is None: scopes = lookup_scopes(self.service) if scopes is None: scopes = [atom.token_store.SCOPE_ALL] token.scopes = scopes if self.auto_set_current_token: self.current_token = token if self.auto_store_tokens: self.token_store.add_token(token) # Private methods to create the source property. def __GetSource(self): return self.__source def __SetSource(self, new_source): self.__source = new_source # Update the UserAgent header to include the new application name. self.additional_headers['User-Agent'] = atom.http_interface.USER_AGENT % ( self.__source,) source = property(__GetSource, __SetSource, doc="""The source is the name of the application making the request. It should be in the form company_id-app_name-app_version""") # Authentication operations def ProgrammaticLogin(self, captcha_token=None, captcha_response=None): """Authenticates the user and sets the GData Auth token. Login retreives a temporary auth token which must be used with all requests to GData services. The auth token is stored in the GData client object. Login is also used to respond to a captcha challenge. If the user's login attempt failed with a CaptchaRequired error, the user can respond by calling Login with the captcha token and the answer to the challenge. Args: captcha_token: string (optional) The identifier for the captcha challenge which was presented to the user. captcha_response: string (optional) The user's answer to the captch challenge. Raises: CaptchaRequired if the login service will require a captcha response BadAuthentication if the login service rejected the username or password Error if the login service responded with a 403 different from the above """ request_body = gdata.auth.generate_client_login_request_body(self.email, self.password, self.service, self.source, self.account_type, captcha_token, captcha_response) # If the user has defined their own authentication service URL, # send the ClientLogin requests to this URL: if not self.auth_service_url: auth_request_url = AUTH_SERVER_HOST + '/accounts/ClientLogin' else: auth_request_url = self.auth_service_url auth_response = self.http_client.request('POST', auth_request_url, data=request_body, headers={'Content-Type':'application/x-www-form-urlencoded'}) response_body = auth_response.read() if auth_response.status == 200: # TODO: insert the token into the token_store directly. self.SetClientLoginToken( gdata.auth.get_client_login_token(response_body)) self.__captcha_token = None self.__captcha_url = None elif auth_response.status == 403: # Examine each line to find the error type and the captcha token and # captch URL if they are present. captcha_parameters = gdata.auth.get_captcha_challenge(response_body, captcha_base_url='%s/accounts/' % AUTH_SERVER_HOST) if captcha_parameters: self.__captcha_token = captcha_parameters['token'] self.__captcha_url = captcha_parameters['url'] raise CaptchaRequired, 'Captcha Required' elif response_body.splitlines()[0] == 'Error=BadAuthentication': self.__captcha_token = None self.__captcha_url = None raise BadAuthentication, 'Incorrect username or password' else: self.__captcha_token = None self.__captcha_url = None raise Error, 'Server responded with a 403 code' elif auth_response.status == 302: self.__captcha_token = None self.__captcha_url = None # Google tries to redirect all bad URLs back to # http://www.google.<locale>. If a redirect # attempt is made, assume the user has supplied an incorrect authentication URL raise BadAuthenticationServiceURL, 'Server responded with a 302 code.' def ClientLogin(self, username, password, account_type=None, service=None, auth_service_url=None, source=None, captcha_token=None, captcha_response=None): """Convenience method for authenticating using ProgrammaticLogin. Sets values for email, password, and other optional members. Args: username: password: account_type: string (optional) service: string (optional) auth_service_url: string (optional) captcha_token: string (optional) captcha_response: string (optional) """ self.email = username self.password = password if account_type: self.account_type = account_type if service: self.service = service if source: self.source = source if auth_service_url: self.auth_service_url = auth_service_url self.ProgrammaticLogin(captcha_token, captcha_response) def GenerateAuthSubURL(self, next, scope, secure=False, session=True, domain='default'): """Generate a URL at which the user will login and be redirected back. Users enter their credentials on a Google login page and a token is sent to the URL specified in next. See documentation for AuthSub login at: http://code.google.com/apis/accounts/docs/AuthSub.html Args: next: string The URL user will be sent to after logging in. scope: string or list of strings. The URLs of the services to be accessed. secure: boolean (optional) Determines whether or not the issued token is a secure token. session: boolean (optional) Determines whether or not the issued token can be upgraded to a session token. """ if not isinstance(scope, (list, tuple)): scope = (scope,) return gdata.auth.generate_auth_sub_url(next, scope, secure=secure, session=session, request_url='%s/accounts/AuthSubRequest' % AUTH_SERVER_HOST, domain=domain) def UpgradeToSessionToken(self, token=None): """Upgrades a single use AuthSub token to a session token. Args: token: A gdata.auth.AuthSubToken or gdata.auth.SecureAuthSubToken (optional) which is good for a single use but can be upgraded to a session token. If no token is passed in, the token is found by looking in the token_store by looking for a token for the current scope. Raises: NonAuthSubToken if the user's auth token is not an AuthSub token TokenUpgradeFailed if the server responded to the request with an error. """ if token is None: scopes = lookup_scopes(self.service) if scopes: token = self.token_store.find_token(scopes[0]) else: token = self.token_store.find_token(atom.token_store.SCOPE_ALL) if not isinstance(token, gdata.auth.AuthSubToken): raise NonAuthSubToken self.SetAuthSubToken(self.upgrade_to_session_token(token)) def upgrade_to_session_token(self, token): """Upgrades a single use AuthSub token to a session token. Args: token: A gdata.auth.AuthSubToken or gdata.auth.SecureAuthSubToken which is good for a single use but can be upgraded to a session token. Returns: The upgraded token as a gdata.auth.AuthSubToken object. Raises: TokenUpgradeFailed if the server responded to the request with an error. """ response = token.perform_request(self.http_client, 'GET', AUTH_SERVER_HOST + '/accounts/AuthSubSessionToken', headers={'Content-Type':'application/x-www-form-urlencoded'}) response_body = response.read() if response.status == 200: token.set_token_string( gdata.auth.token_from_http_body(response_body)) return token else: raise TokenUpgradeFailed({'status': response.status, 'reason': 'Non 200 response on upgrade', 'body': response_body}) def RevokeAuthSubToken(self): """Revokes an existing AuthSub token. Raises: NonAuthSubToken if the user's auth token is not an AuthSub token """ scopes = lookup_scopes(self.service) token = self.token_store.find_token(scopes[0]) if not isinstance(token, gdata.auth.AuthSubToken): raise NonAuthSubToken response = token.perform_request(self.http_client, 'GET', AUTH_SERVER_HOST + '/accounts/AuthSubRevokeToken', headers={'Content-Type':'application/x-www-form-urlencoded'}) if response.status == 200: self.token_store.remove_token(token) def AuthSubTokenInfo(self): """Fetches the AuthSub token's metadata from the server. Raises: NonAuthSubToken if the user's auth token is not an AuthSub token """ scopes = lookup_scopes(self.service) token = self.token_store.find_token(scopes[0]) if not isinstance(token, gdata.auth.AuthSubToken): raise NonAuthSubToken response = token.perform_request(self.http_client, 'GET', AUTH_SERVER_HOST + '/accounts/AuthSubTokenInfo', headers={'Content-Type':'application/x-www-form-urlencoded'}) result_body = response.read() if response.status == 200: return result_body else: raise RequestError, {'status': response.status, 'body': result_body} def GetWithRetries(self, uri, extra_headers=None, redirects_remaining=4, encoding='UTF-8', converter=None, num_retries=DEFAULT_NUM_RETRIES, delay=DEFAULT_DELAY, backoff=DEFAULT_BACKOFF, logger=None): """This is a wrapper method for Get with retrying capability. To avoid various errors while retrieving bulk entities by retrying specified times. Note this method relies on the time module and so may not be usable by default in Python2.2. Args: num_retries: Integer; the retry count. delay: Integer; the initial delay for retrying. backoff: Integer; how much the delay should lengthen after each failure. logger: An object which has a debug(str) method to receive logging messages. Recommended that you pass in the logging module. Raises: ValueError if any of the parameters has an invalid value. RanOutOfTries on failure after number of retries. """ # Moved import for time module inside this method since time is not a # default module in Python2.2. This method will not be usable in # Python2.2. import time if backoff <= 1: raise ValueError("backoff must be greater than 1") num_retries = int(num_retries) if num_retries < 0: raise ValueError("num_retries must be 0 or greater") if delay <= 0: raise ValueError("delay must be greater than 0") # Let's start mtries, mdelay = num_retries, delay while mtries > 0: if mtries != num_retries: if logger: logger.debug("Retrying: %s" % uri) try: rv = self.Get(uri, extra_headers=extra_headers, redirects_remaining=redirects_remaining, encoding=encoding, converter=converter) except SystemExit: # Allow this error raise except RequestError, e: # Error 500 is 'internal server error' and warrants a retry # Error 503 is 'service unavailable' and warrants a retry if e[0]['status'] not in [500, 503]: raise e # Else, fall through to the retry code... except Exception, e: if logger: logger.debug(e) # Fall through to the retry code... else: # This is the right path. return rv mtries -= 1 time.sleep(mdelay) mdelay *= backoff raise RanOutOfTries('Ran out of tries.') # CRUD operations def Get(self, uri, extra_headers=None, redirects_remaining=4, encoding='UTF-8', converter=None): """Query the GData API with the given URI The uri is the portion of the URI after the server value (ex: www.google.com). To perform a query against Google Base, set the server to 'base.google.com' and set the uri to '/base/feeds/...', where ... is your query. For example, to find snippets for all digital cameras uri should be set to: '/base/feeds/snippets?bq=digital+camera' Args: uri: string The query in the form of a URI. Example: '/base/feeds/snippets?bq=digital+camera'. extra_headers: dictionary (optional) Extra HTTP headers to be included in the GET request. These headers are in addition to those stored in the client's additional_headers property. The client automatically sets the Content-Type and Authorization headers. redirects_remaining: int (optional) Tracks the number of additional redirects this method will allow. If the service object receives a redirect and remaining is 0, it will not follow the redirect. This was added to avoid infinite redirect loops. encoding: string (optional) The character encoding for the server's response. Default is UTF-8 converter: func (optional) A function which will transform the server's results before it is returned. Example: use GDataFeedFromString to parse the server response as if it were a GDataFeed. Returns: If there is no ResultsTransformer specified in the call, a GDataFeed or GDataEntry depending on which is sent from the server. If the response is niether a feed or entry and there is no ResultsTransformer, return a string. If there is a ResultsTransformer, the returned value will be that of the ResultsTransformer function. """ if extra_headers is None: extra_headers = {} if self.__gsessionid is not None: if uri.find('gsessionid=') < 0: if uri.find('?') > -1: uri += '&gsessionid=%s' % (self.__gsessionid,) else: uri += '?gsessionid=%s' % (self.__gsessionid,) server_response = self.request('GET', uri, headers=extra_headers) result_body = server_response.read() if server_response.status == 200: if converter: return converter(result_body) # There was no ResultsTransformer specified, so try to convert the # server's response into a GDataFeed. feed = gdata.GDataFeedFromString(result_body) if not feed: # If conversion to a GDataFeed failed, try to convert the server's # response to a GDataEntry. entry = gdata.GDataEntryFromString(result_body) if not entry: # The server's response wasn't a feed, or an entry, so return the # response body as a string. return result_body return entry return feed elif server_response.status == 302: if redirects_remaining > 0: location = (server_response.getheader('Location') or server_response.getheader('location')) if location is not None: m = re.compile('[\?\&]gsessionid=(\w*\-)').search(location) if m is not None: self.__gsessionid = m.group(1) return GDataService.Get(self, location, extra_headers, redirects_remaining - 1, encoding=encoding, converter=converter) else: raise RequestError, {'status': server_response.status, 'reason': '302 received without Location header', 'body': result_body} else: raise RequestError, {'status': server_response.status, 'reason': 'Redirect received, but redirects_remaining <= 0', 'body': result_body} else: raise RequestError, {'status': server_response.status, 'reason': server_response.reason, 'body': result_body} def GetMedia(self, uri, extra_headers=None): """Returns a MediaSource containing media and its metadata from the given URI string. """ response_handle = self.request('GET', uri, headers=extra_headers) return gdata.MediaSource(response_handle, response_handle.getheader( 'Content-Type'), response_handle.getheader('Content-Length')) def GetEntry(self, uri, extra_headers=None): """Query the GData API with the given URI and receive an Entry. See also documentation for gdata.service.Get Args: uri: string The query in the form of a URI. Example: '/base/feeds/snippets?bq=digital+camera'. extra_headers: dictionary (optional) Extra HTTP headers to be included in the GET request. These headers are in addition to those stored in the client's additional_headers property. The client automatically sets the Content-Type and Authorization headers. Returns: A GDataEntry built from the XML in the server's response. """ result = GDataService.Get(self, uri, extra_headers, converter=atom.EntryFromString) if isinstance(result, atom.Entry): return result else: raise UnexpectedReturnType, 'Server did not send an entry' def GetFeed(self, uri, extra_headers=None, converter=gdata.GDataFeedFromString): """Query the GData API with the given URI and receive a Feed. See also documentation for gdata.service.Get Args: uri: string The query in the form of a URI. Example: '/base/feeds/snippets?bq=digital+camera'. extra_headers: dictionary (optional) Extra HTTP headers to be included in the GET request. These headers are in addition to those stored in the client's additional_headers property. The client automatically sets the Content-Type and Authorization headers. Returns: A GDataFeed built from the XML in the server's response. """ result = GDataService.Get(self, uri, extra_headers, converter=converter) if isinstance(result, atom.Feed): return result else: raise UnexpectedReturnType, 'Server did not send a feed' def GetNext(self, feed): """Requests the next 'page' of results in the feed. This method uses the feed's next link to request an additional feed and uses the class of the feed to convert the results of the GET request. Args: feed: atom.Feed or a subclass. The feed should contain a next link and the type of the feed will be applied to the results from the server. The new feed which is returned will be of the same class as this feed which was passed in. Returns: A new feed representing the next set of results in the server's feed. The type of this feed will match that of the feed argument. """ next_link = feed.GetNextLink() # Create a closure which will convert an XML string to the class of # the feed object passed in. def ConvertToFeedClass(xml_string): return atom.CreateClassFromXMLString(feed.__class__, xml_string) # Make a GET request on the next link and use the above closure for the # converted which processes the XML string from the server. if next_link and next_link.href: return GDataService.Get(self, next_link.href, converter=ConvertToFeedClass) else: return None def Post(self, data, uri, extra_headers=None, url_params=None, escape_params=True, redirects_remaining=4, media_source=None, converter=None): """Insert or update data into a GData service at the given URI. Args: data: string, ElementTree._Element, atom.Entry, or gdata.GDataEntry The XML to be sent to the uri. uri: string The location (feed) to which the data should be inserted. Example: '/base/feeds/items'. extra_headers: dict (optional) HTTP headers which are to be included. The client automatically sets the Content-Type, Authorization, and Content-Length headers. url_params: dict (optional) Additional URL parameters to be included in the URI. These are translated into query arguments in the form '&dict_key=value&...'. Example: {'max-results': '250'} becomes &max-results=250 escape_params: boolean (optional) If false, the calling code has already ensured that the query will form a valid URL (all reserved characters have been escaped). If true, this method will escape the query and any URL parameters provided. media_source: MediaSource (optional) Container for the media to be sent along with the entry, if provided. converter: func (optional) A function which will be executed on the server's response. Often this is a function like GDataEntryFromString which will parse the body of the server's response and return a GDataEntry. Returns: If the post succeeded, this method will return a GDataFeed, GDataEntry, or the results of running converter on the server's result body (if converter was specified). """ return GDataService.PostOrPut(self, 'POST', data, uri, extra_headers=extra_headers, url_params=url_params, escape_params=escape_params, redirects_remaining=redirects_remaining, media_source=media_source, converter=converter) def PostOrPut(self, verb, data, uri, extra_headers=None, url_params=None, escape_params=True, redirects_remaining=4, media_source=None, converter=None): """Insert data into a GData service at the given URI. Args: verb: string, either 'POST' or 'PUT' data: string, ElementTree._Element, atom.Entry, or gdata.GDataEntry The XML to be sent to the uri. uri: string The location (feed) to which the data should be inserted. Example: '/base/feeds/items'. extra_headers: dict (optional) HTTP headers which are to be included. The client automatically sets the Content-Type, Authorization, and Content-Length headers. url_params: dict (optional) Additional URL parameters to be included in the URI. These are translated into query arguments in the form '&dict_key=value&...'. Example: {'max-results': '250'} becomes &max-results=250 escape_params: boolean (optional) If false, the calling code has already ensured that the query will form a valid URL (all reserved characters have been escaped). If true, this method will escape the query and any URL parameters provided. media_source: MediaSource (optional) Container for the media to be sent along with the entry, if provided. converter: func (optional) A function which will be executed on the server's response. Often this is a function like GDataEntryFromString which will parse the body of the server's response and return a GDataEntry. Returns: If the post succeeded, this method will return a GDataFeed, GDataEntry, or the results of running converter on the server's result body (if converter was specified). """ if extra_headers is None: extra_headers = {} if self.__gsessionid is not None: if uri.find('gsessionid=') < 0: if url_params is None: url_params = {} url_params['gsessionid'] = self.__gsessionid if data and media_source: if ElementTree.iselement(data): data_str = ElementTree.tostring(data) else: data_str = str(data) multipart = [] multipart.append('Media multipart posting\r\n--END_OF_PART\r\n' + \ 'Content-Type: application/atom+xml\r\n\r\n') multipart.append('\r\n--END_OF_PART\r\nContent-Type: ' + \ media_source.content_type+'\r\n\r\n') multipart.append('\r\n--END_OF_PART--\r\n') extra_headers['MIME-version'] = '1.0' extra_headers['Content-Length'] = str(len(multipart[0]) + len(multipart[1]) + len(multipart[2]) + len(data_str) + media_source.content_length) extra_headers['Content-Type'] = 'multipart/related; boundary=END_OF_PART' server_response = self.request(verb, uri, data=[multipart[0], data_str, multipart[1], media_source.file_handle, multipart[2]], headers=extra_headers, url_params=url_params) result_body = server_response.read() elif media_source or isinstance(data, gdata.MediaSource): if isinstance(data, gdata.MediaSource): media_source = data extra_headers['Content-Length'] = str(media_source.content_length) extra_headers['Content-Type'] = media_source.content_type server_response = self.request(verb, uri, data=media_source.file_handle, headers=extra_headers, url_params=url_params) result_body = server_response.read() else: http_data = data if 'Content-Type' not in extra_headers: content_type = 'application/atom+xml' extra_headers['Content-Type'] = content_type server_response = self.request(verb, uri, data=http_data, headers=extra_headers, url_params=url_params) result_body = server_response.read() # Server returns 201 for most post requests, but when performing a batch # request the server responds with a 200 on success. if server_response.status == 201 or server_response.status == 200: if converter: return converter(result_body) feed = gdata.GDataFeedFromString(result_body) if not feed: entry = gdata.GDataEntryFromString(result_body) if not entry: return result_body return entry return feed elif server_response.status == 302: if redirects_remaining > 0: location = (server_response.getheader('Location') or server_response.getheader('location')) if location is not None: m = re.compile('[\?\&]gsessionid=(\w*\-)').search(location) if m is not None: self.__gsessionid = m.group(1) return GDataService.PostOrPut(self, verb, data, location, extra_headers, url_params, escape_params, redirects_remaining - 1, media_source, converter=converter) else: raise RequestError, {'status': server_response.status, 'reason': '302 received without Location header', 'body': result_body} else: raise RequestError, {'status': server_response.status, 'reason': 'Redirect received, but redirects_remaining <= 0', 'body': result_body} else: raise RequestError, {'status': server_response.status, 'reason': server_response.reason, 'body': result_body} def Put(self, data, uri, extra_headers=None, url_params=None, escape_params=True, redirects_remaining=3, media_source=None, converter=None): """Updates an entry at the given URI. Args: data: string, ElementTree._Element, or xml_wrapper.ElementWrapper The XML containing the updated data. uri: string A URI indicating entry to which the update will be applied. Example: '/base/feeds/items/ITEM-ID' extra_headers: dict (optional) HTTP headers which are to be included. The client automatically sets the Content-Type, Authorization, and Content-Length headers. url_params: dict (optional) Additional URL parameters to be included in the URI. These are translated into query arguments in the form '&dict_key=value&...'. Example: {'max-results': '250'} becomes &max-results=250 escape_params: boolean (optional) If false, the calling code has already ensured that the query will form a valid URL (all reserved characters have been escaped). If true, this method will escape the query and any URL parameters provided. converter: func (optional) A function which will be executed on the server's response. Often this is a function like GDataEntryFromString which will parse the body of the server's response and return a GDataEntry. Returns: If the put succeeded, this method will return a GDataFeed, GDataEntry, or the results of running converter on the server's result body (if converter was specified). """ return GDataService.PostOrPut(self, 'PUT', data, uri, extra_headers=extra_headers, url_params=url_params, escape_params=escape_params, redirects_remaining=redirects_remaining, media_source=media_source, converter=converter) def Delete(self, uri, extra_headers=None, url_params=None, escape_params=True, redirects_remaining=4): """Deletes the entry at the given URI. Args: uri: string The URI of the entry to be deleted. Example: '/base/feeds/items/ITEM-ID' extra_headers: dict (optional) HTTP headers which are to be included. The client automatically sets the Content-Type and Authorization headers. url_params: dict (optional) Additional URL parameters to be included in the URI. These are translated into query arguments in the form '&dict_key=value&...'. Example: {'max-results': '250'} becomes &max-results=250 escape_params: boolean (optional) If false, the calling code has already ensured that the query will form a valid URL (all reserved characters have been escaped). If true, this method will escape the query and any URL parameters provided. Returns: True if the entry was deleted. """ if extra_headers is None: extra_headers = {} if self.__gsessionid is not None: if uri.find('gsessionid=') < 0: if url_params is None: url_params = {} url_params['gsessionid'] = self.__gsessionid server_response = self.request('DELETE', uri, headers=extra_headers, url_params=url_params) result_body = server_response.read() if server_response.status == 200: return True elif server_response.status == 302: if redirects_remaining > 0: location = (server_response.getheader('Location') or server_response.getheader('location')) if location is not None: m = re.compile('[\?\&]gsessionid=(\w*\-)').search(location) if m is not None: self.__gsessionid = m.group(1) return GDataService.Delete(self, location, extra_headers, url_params, escape_params, redirects_remaining - 1) else: raise RequestError, {'status': server_response.status, 'reason': '302 received without Location header', 'body': result_body} else: raise RequestError, {'status': server_response.status, 'reason': 'Redirect received, but redirects_remaining <= 0', 'body': result_body} else: raise RequestError, {'status': server_response.status, 'reason': server_response.reason, 'body': result_body} def ExtractToken(url, scopes_included_in_next=True): """Gets the AuthSub token from the current page's URL. Designed to be used on the URL that the browser is sent to after the user authorizes this application at the page given by GenerateAuthSubRequestUrl. Args: url: The current page's URL. It should contain the token as a URL parameter. Example: 'http://example.com/?...&token=abcd435' scopes_included_in_next: If True, this function looks for a scope value associated with the token. The scope is a URL parameter with the key set to SCOPE_URL_PARAM_NAME. This parameter should be present if the AuthSub request URL was generated using GenerateAuthSubRequestUrl with include_scope_in_next set to True. Returns: A tuple containing the token string and a list of scope strings for which this token should be valid. If the scope was not included in the URL, the tuple will contain (token, None). """ parsed = urlparse.urlparse(url) token = gdata.auth.AuthSubTokenFromUrl(parsed[4]) scopes = '' if scopes_included_in_next: for pair in parsed[4].split('&'): if pair.startswith('%s=' % SCOPE_URL_PARAM_NAME): scopes = urllib.unquote_plus(pair.split('=')[1]) return (token, scopes.split(' ')) def GenerateAuthSubRequestUrl(next, scopes, hd='default', secure=False, session=True, request_url='https://www.google.com/accounts/AuthSubRequest', include_scopes_in_next=True): """Creates a URL to request an AuthSub token to access Google services. For more details on AuthSub, see the documentation here: http://code.google.com/apis/accounts/docs/AuthSub.html Args: next: The URL where the browser should be sent after the user authorizes the application. This page is responsible for receiving the token which is embeded in the URL as a parameter. scopes: The base URL to which access will be granted. Example: 'http://www.google.com/calendar/feeds' will grant access to all URLs in the Google Calendar data API. If you would like a token for multiple scopes, pass in a list of URL strings. hd: The domain to which the user's account belongs. This is set to the domain name if you are using Google Apps. Example: 'example.org' Defaults to 'default' secure: If set to True, all requests should be signed. The default is False. session: If set to True, the token received by the 'next' URL can be upgraded to a multiuse session token. If session is set to False, the token may only be used once and cannot be upgraded. Default is True. request_url: The base of the URL to which the user will be sent to authorize this application to access their data. The default is 'https://www.google.com/accounts/AuthSubRequest'. include_scopes_in_next: Boolean if set to true, the 'next' parameter will be modified to include the requested scope as a URL parameter. The key for the next's scope parameter will be SCOPE_URL_PARAM_NAME. The benefit of including the scope URL as a parameter to the next URL, is that the page which receives the AuthSub token will be able to tell which URLs the token grants access to. Returns: A URL string to which the browser should be sent. """ if isinstance(scopes, list): scope = ' '.join(scopes) else: scope = scopes if include_scopes_in_next: if next.find('?') > -1: next += '&%s' % urllib.urlencode({SCOPE_URL_PARAM_NAME:scope}) else: next += '?%s' % urllib.urlencode({SCOPE_URL_PARAM_NAME:scope}) return gdata.auth.GenerateAuthSubUrl(next=next, scope=scope, secure=secure, session=session, request_url=request_url, domain=hd) class Query(dict): """Constructs a query URL to be used in GET requests Url parameters are created by adding key-value pairs to this object as a dict. For example, to add &max-results=25 to the URL do my_query['max-results'] = 25 Category queries are created by adding category strings to the categories member. All items in the categories list will be concatenated with the / symbol (symbolizing a category x AND y restriction). If you would like to OR 2 categories, append them as one string with a | between the categories. For example, do query.categories.append('Fritz|Laurie') to create a query like this feed/-/Fritz%7CLaurie . This query will look for results in both categories. """ def __init__(self, feed=None, text_query=None, params=None, categories=None): """Constructor for Query Args: feed: str (optional) The path for the feed (Examples: '/base/feeds/snippets' or 'calendar/feeds/jo@gmail.com/private/full' text_query: str (optional) The contents of the q query parameter. The contents of the text_query are URL escaped upon conversion to a URI. params: dict (optional) Parameter value string pairs which become URL params when translated to a URI. These parameters are added to the query's items (key-value pairs). categories: list (optional) List of category strings which should be included as query categories. See http://code.google.com/apis/gdata/reference.html#Queries for details. If you want to get results from category A or B (both categories), specify a single list item 'A|B'. """ self.feed = feed self.categories = [] if text_query: self.text_query = text_query if isinstance(params, dict): for param in params: self[param] = params[param] if isinstance(categories, list): for category in categories: self.categories.append(category) def _GetTextQuery(self): if 'q' in self.keys(): return self['q'] else: return None def _SetTextQuery(self, query): self['q'] = query text_query = property(_GetTextQuery, _SetTextQuery, doc="""The feed query's q parameter""") def _GetAuthor(self): if 'author' in self.keys(): return self['author'] else: return None def _SetAuthor(self, query): self['author'] = query author = property(_GetAuthor, _SetAuthor, doc="""The feed query's author parameter""") def _GetAlt(self): if 'alt' in self.keys(): return self['alt'] else: return None def _SetAlt(self, query): self['alt'] = query alt = property(_GetAlt, _SetAlt, doc="""The feed query's alt parameter""") def _GetUpdatedMin(self): if 'updated-min' in self.keys(): return self['updated-min'] else: return None def _SetUpdatedMin(self, query): self['updated-min'] = query updated_min = property(_GetUpdatedMin, _SetUpdatedMin, doc="""The feed query's updated-min parameter""") def _GetUpdatedMax(self): if 'updated-max' in self.keys(): return self['updated-max'] else: return None def _SetUpdatedMax(self, query): self['updated-max'] = query updated_max = property(_GetUpdatedMax, _SetUpdatedMax, doc="""The feed query's updated-max parameter""") def _GetPublishedMin(self): if 'published-min' in self.keys(): return self['published-min'] else: return None def _SetPublishedMin(self, query): self['published-min'] = query published_min = property(_GetPublishedMin, _SetPublishedMin, doc="""The feed query's published-min parameter""") def _GetPublishedMax(self): if 'published-max' in self.keys(): return self['published-max'] else: return None def _SetPublishedMax(self, query): self['published-max'] = query published_max = property(_GetPublishedMax, _SetPublishedMax, doc="""The feed query's published-max parameter""") def _GetStartIndex(self): if 'start-index' in self.keys(): return self['start-index'] else: return None def _SetStartIndex(self, query): if not isinstance(query, str): query = str(query) self['start-index'] = query start_index = property(_GetStartIndex, _SetStartIndex, doc="""The feed query's start-index parameter""") def _GetMaxResults(self): if 'max-results' in self.keys(): return self['max-results'] else: return None def _SetMaxResults(self, query): if not isinstance(query, str): query = str(query) self['max-results'] = query max_results = property(_GetMaxResults, _SetMaxResults, doc="""The feed query's max-results parameter""") def _GetOrderBy(self): if 'orderby' in self.keys(): return self['orderby'] else: return None def _SetOrderBy(self, query): self['orderby'] = query orderby = property(_GetOrderBy, _SetOrderBy, doc="""The feed query's orderby parameter""") def ToUri(self): q_feed = self.feed or '' category_string = '/'.join( [urllib.quote_plus(c) for c in self.categories]) # Add categories to the feed if there are any. if len(self.categories) > 0: q_feed = q_feed + '/-/' + category_string return atom.service.BuildUri(q_feed, self) def __str__(self): return self.ToUri() �����������������������gdata-2.0.18/src/gdata/opensearch/������������������������������������������������������������������0000750�0364663�0011610�00000000000�12156625015�016730� 5����������������������������������������������������������������������������������������������������ustar �afshar��������������������������eng�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������gdata-2.0.18/src/gdata/opensearch/data.py�����������������������������������������������������������0000640�0364663�0011610�00000002776�12156622363�020233� 0����������������������������������������������������������������������������������������������������ustar �afshar��������������������������eng�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/python # # Copyright (C) 2009 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. """Contains the data classes of the OpenSearch Extension""" __author__ = 'j.s@google.com (Jeff Scudder)' import atom.core OPENSEARCH_TEMPLATE_V1 = '{http://a9.com/-/spec/opensearchrss/1.0//}%s' OPENSEARCH_TEMPLATE_V2 = '{http://a9.com/-/spec/opensearch/1.1//}%s' class ItemsPerPage(atom.core.XmlElement): """Describes the number of items that will be returned per page for paged feeds""" _qname = (OPENSEARCH_TEMPLATE_V1 % 'itemsPerPage', OPENSEARCH_TEMPLATE_V2 % 'itemsPerPage') class StartIndex(atom.core.XmlElement): """Describes the starting index of the contained entries for paged feeds""" _qname = (OPENSEARCH_TEMPLATE_V1 % 'startIndex', OPENSEARCH_TEMPLATE_V2 % 'startIndex') class TotalResults(atom.core.XmlElement): """Describes the total number of results associated with this feed""" _qname = (OPENSEARCH_TEMPLATE_V1 % 'totalResults', OPENSEARCH_TEMPLATE_V2 % 'totalResults') ��gdata-2.0.18/src/gdata/opensearch/__init__.py�������������������������������������������������������0000640�0364663�0011610�00000001130�12156622363�021040� 0����������������������������������������������������������������������������������������������������ustar �afshar��������������������������eng�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/python # # Copyright (C) 2009 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. ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������gdata-2.0.18/src/gdata/marketplace/�����������������������������������������������������������������0000750�0364663�0011610�00000000000�12156625015�017071� 5����������������������������������������������������������������������������������������������������ustar �afshar��������������������������eng�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������gdata-2.0.18/src/gdata/marketplace/data.py����������������������������������������������������������0000640�0364663�0011610�00000004773�12156622363�020373� 0����������������������������������������������������������������������������������������������������ustar �afshar��������������������������eng�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/python # # Copyright 2009 Google Inc. All Rights Reserved. # # 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. """Data model for parsing and generating XML for the Google Apps Marketplace Licensing API.""" __author__ = 'Alexandre Vivien <alex@simplecode.fr>' import atom.core import gdata import gdata.data LICENSES_NAMESPACE = 'http://www.w3.org/2005/Atom' LICENSES_TEMPLATE = '{%s}%%s' % LICENSES_NAMESPACE class Enabled(atom.core.XmlElement): """ """ _qname = LICENSES_TEMPLATE % 'enabled' class Id(atom.core.XmlElement): """ """ _qname = LICENSES_TEMPLATE % 'id' class CustomerId(atom.core.XmlElement): """ """ _qname = LICENSES_TEMPLATE % 'customerid' class DomainName(atom.core.XmlElement): """ """ _qname = LICENSES_TEMPLATE % 'domainname' class InstallerEmail(atom.core.XmlElement): """ """ _qname = LICENSES_TEMPLATE % 'installeremail' class TosAcceptanceTime(atom.core.XmlElement): """ """ _qname = LICENSES_TEMPLATE % 'tosacceptancetime' class LastChangeTime(atom.core.XmlElement): """ """ _qname = LICENSES_TEMPLATE % 'lastchangetime' class ProductConfigId(atom.core.XmlElement): """ """ _qname = LICENSES_TEMPLATE % 'productconfigid' class State(atom.core.XmlElement): """ """ _qname = LICENSES_TEMPLATE % 'state' class Entity(atom.core.XmlElement): """ The entity representing the License. """ _qname = LICENSES_TEMPLATE % 'entity' enabled = Enabled id = Id customer_id = CustomerId domain_name = DomainName installer_email = InstallerEmail tos_acceptance_time = TosAcceptanceTime last_change_time = LastChangeTime product_config_id = ProductConfigId state = State class Content(atom.data.Content): entity = Entity class LicenseEntry(gdata.data.GDEntry): """ Represents a LicenseEntry object. """ content = Content class LicenseFeed(gdata.data.GDFeed): """ Represents a feed of LicenseEntry objects. """ # Override entry so that this feed knows how to type its list of entries. entry = [LicenseEntry] �����gdata-2.0.18/src/gdata/marketplace/__init__.py������������������������������������������������������0000640�0364663�0011610�00000000001�12156622363�021175� 0����������������������������������������������������������������������������������������������������ustar �afshar��������������������������eng�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������gdata-2.0.18/src/gdata/marketplace/client.py��������������������������������������������������������0000640�0364663�0011610�00000014541�12156622363�020732� 0����������������������������������������������������������������������������������������������������ustar �afshar��������������������������eng�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/python # # Copyright 2009 Google Inc. All Rights Reserved. # # 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. """LicensingClient simplifies Google Apps Marketplace Licensing API calls. LicensingClient extends gdata.client.GDClient to ease interaction with the Google Apps Marketplace Licensing API. These interactions include the ability to retrieve License informations for an application in the Google Apps Marketplace. """ __author__ = 'Alexandre Vivien <alex@simplecode.fr>' import gdata.marketplace.data import gdata.client import urllib # Feed URI template. This must end with a / # The strings in this template are eventually replaced with the API version # and Google Apps domain name, respectively. LICENSE_ROOT_URL = 'http://feedserver-enterprise.googleusercontent.com' LICENSE_FEED_TEMPLATE = '%s/license?bq=' % LICENSE_ROOT_URL LICENSE_NOTIFICATIONS_FEED_TEMPLATE = '%s/licensenotification?bq=' % LICENSE_ROOT_URL class LicensingClient(gdata.client.GDClient): """Client extension for the Google Apps Marketplace Licensing API service. Attributes: host: string The hostname for the Google Apps Marketplace Licensing API service. api_version: string The version of the Google Apps Marketplace Licensing API. """ api_version = '1.0' auth_service = 'apps' auth_scopes = gdata.gauth.AUTH_SCOPES['apps'] ssl = False def __init__(self, domain, auth_token=None, **kwargs): """Constructs a new client for the Google Apps Marketplace Licensing API. Args: domain: string The Google Apps domain with the application installed. auth_token: (optional) gdata.gauth.OAuthToken which authorizes this client to retrieve the License information. kwargs: The other parameters to pass to the gdata.client.GDClient constructor. """ gdata.client.GDClient.__init__(self, auth_token=auth_token, **kwargs) self.domain = domain def make_license_feed_uri(self, app_id=None, params=None): """Creates a license feed URI for the Google Apps Marketplace Licensing API. Using this client's Google Apps domain, create a license feed URI for a particular application in this domain. If params are provided, append them as GET params. Args: app_id: string The ID of the application for which to make a license feed URI. params: dict (optional) key -> value params to append as GET vars to the URI. Example: params={'start': 'my-resource-id'} Returns: A string giving the URI for the application's license for this client's Google Apps domain. """ parameters = '[appid=%s][domain=%s]' % (app_id, self.domain) uri = LICENSE_FEED_TEMPLATE + urllib.quote_plus(parameters) if params: uri += '&' + urllib.urlencode(params) return uri MakeLicenseFeedUri = make_license_feed_uri def make_license_notifications_feed_uri(self, app_id=None, startdatetime=None, max_results=None, params=None): """Creates a license notifications feed URI for the Google Apps Marketplace Licensing API. Using this client's Google Apps domain, create a license notifications feed URI for a particular application. If params are provided, append them as GET params. Args: app_id: string The ID of the application for which to make a license feed URI. startdatetime: Start date to retrieve the License notifications. max_results: Number of results per page. Maximum is 100. params: dict (optional) key -> value params to append as GET vars to the URI. Example: params={'start': 'my-resource-id'} Returns: A string giving the URI for the application's license notifications for this client's Google Apps domain. """ parameters = '[appid=%s]' % (app_id) if startdatetime: parameters += '[startdatetime=%s]' % startdatetime else: parameters += '[startdatetime=1970-01-01T00:00:00Z]' if max_results: parameters += '[max-results=%s]' % max_results else: parameters += '[max-results=100]' uri = LICENSE_NOTIFICATIONS_FEED_TEMPLATE + urllib.quote_plus(parameters) if params: uri += '&' + urllib.urlencode(params) return uri MakeLicenseNotificationsFeedUri = make_license_notifications_feed_uri def get_license(self, uri=None, app_id=None, **kwargs): """Fetches the application's license by application ID. Args: uri: string The base URI of the feed from which to fetch the license. app_id: string The string ID of the application for which to fetch the license. kwargs: The other parameters to pass to gdata.client.GDClient.get_entry(). Returns: A License feed object representing the license with the given base URI and application ID. """ if uri is None: uri = self.MakeLicenseFeedUri(app_id) return self.get_feed(uri, desired_class=gdata.marketplace.data.LicenseFeed, **kwargs) GetLicense = get_license def get_license_notifications(self, uri=None, app_id=None, startdatetime=None, max_results=None, **kwargs): """Fetches the application's license notifications by application ID. Args: uri: string The base URI of the feed from which to fetch the license. app_id: string The string ID of the application for which to fetch the license. startdatetime: Start date to retrieve the License notifications. max_results: Number of results per page. Maximum is 100. kwargs: The other parameters to pass to gdata.client.GDClient.get_entry(). Returns: A License feed object representing the license notifications with the given base URI and application ID. """ if uri is None: uri = self.MakeLicenseNotificationsFeedUri(app_id, startdatetime, max_results) return self.get_feed(uri, desired_class=gdata.marketplace.data.LicenseFeed, **kwargs) GetLicenseNotifications = get_license_notifications ���������������������������������������������������������������������������������������������������������������������������������������������������������������gdata-2.0.18/src/atom/������������������������������������������������������������������������������0000750�0364663�0011610�00000000000�12156625015�014461� 5����������������������������������������������������������������������������������������������������ustar �afshar��������������������������eng�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������gdata-2.0.18/src/atom/mock_http_core.py�������������������������������������������������������������0000640�0364663�0011610�00000027350�12156622362�020045� 0����������������������������������������������������������������������������������������������������ustar �afshar��������������������������eng�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/env python # # Copyright (C) 2009 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. # This module is used for version 2 of the Google Data APIs. __author__ = 'j.s@google.com (Jeff Scudder)' import StringIO import pickle import os.path import tempfile import atom.http_core class Error(Exception): pass class NoRecordingFound(Error): pass class MockHttpClient(object): debug = None real_client = None last_request_was_live = False # The following members are used to construct the session cache temp file # name. # These are combined to form the file name # /tmp/cache_prefix.cache_case_name.cache_test_name cache_name_prefix = 'gdata_live_test' cache_case_name = '' cache_test_name = '' def __init__(self, recordings=None, real_client=None): self._recordings = recordings or [] if real_client is not None: self.real_client = real_client def add_response(self, http_request, status, reason, headers=None, body=None): response = MockHttpResponse(status, reason, headers, body) # TODO Scrub the request and the response. self._recordings.append((http_request._copy(), response)) AddResponse = add_response def request(self, http_request): """Provide a recorded response, or record a response for replay. If the real_client is set, the request will be made using the real_client, and the response from the server will be recorded. If the real_client is None (the default), this method will examine the recordings and find the first which matches. """ request = http_request._copy() _scrub_request(request) if self.real_client is None: self.last_request_was_live = False for recording in self._recordings: if _match_request(recording[0], request): return recording[1] else: # Pass along the debug settings to the real client. self.real_client.debug = self.debug # Make an actual request since we can use the real HTTP client. self.last_request_was_live = True response = self.real_client.request(http_request) scrubbed_response = _scrub_response(response) self.add_response(request, scrubbed_response.status, scrubbed_response.reason, dict(atom.http_core.get_headers(scrubbed_response)), scrubbed_response.read()) # Return the recording which we just added. return self._recordings[-1][1] raise NoRecordingFound('No recoding was found for request: %s %s' % ( request.method, str(request.uri))) Request = request def _save_recordings(self, filename): recording_file = open(os.path.join(tempfile.gettempdir(), filename), 'wb') pickle.dump(self._recordings, recording_file) recording_file.close() def _load_recordings(self, filename): recording_file = open(os.path.join(tempfile.gettempdir(), filename), 'rb') self._recordings = pickle.load(recording_file) recording_file.close() def _delete_recordings(self, filename): full_path = os.path.join(tempfile.gettempdir(), filename) if os.path.exists(full_path): os.remove(full_path) def _load_or_use_client(self, filename, http_client): if os.path.exists(os.path.join(tempfile.gettempdir(), filename)): self._load_recordings(filename) else: self.real_client = http_client def use_cached_session(self, name=None, real_http_client=None): """Attempts to load recordings from a previous live request. If a temp file with the recordings exists, then it is used to fulfill requests. If the file does not exist, then a real client is used to actually make the desired HTTP requests. Requests and responses are recorded and will be written to the desired temprary cache file when close_session is called. Args: name: str (optional) The file name of session file to be used. The file is loaded from the temporary directory of this machine. If no name is passed in, a default name will be constructed using the cache_name_prefix, cache_case_name, and cache_test_name of this object. real_http_client: atom.http_core.HttpClient the real client to be used if the cached recordings are not found. If the default value is used, this will be an atom.http_core.HttpClient. """ if real_http_client is None: real_http_client = atom.http_core.HttpClient() if name is None: self._recordings_cache_name = self.get_cache_file_name() else: self._recordings_cache_name = name self._load_or_use_client(self._recordings_cache_name, real_http_client) def close_session(self): """Saves recordings in the temporary file named in use_cached_session.""" if self.real_client is not None: self._save_recordings(self._recordings_cache_name) def delete_session(self, name=None): """Removes recordings from a previous live request.""" if name is None: self._delete_recordings(self._recordings_cache_name) else: self._delete_recordings(name) def get_cache_file_name(self): return '%s.%s.%s' % (self.cache_name_prefix, self.cache_case_name, self.cache_test_name) def _dump(self): """Provides debug information in a string.""" output = 'MockHttpClient\n real_client: %s\n cache file name: %s\n' % ( self.real_client, self.get_cache_file_name()) output += ' recordings:\n' i = 0 for recording in self._recordings: output += ' recording %i is for: %s %s\n' % ( i, recording[0].method, str(recording[0].uri)) i += 1 return output def _match_request(http_request, stored_request): """Determines whether a request is similar enough to a stored request to cause the stored response to be returned.""" # Check to see if the host names match. if (http_request.uri.host is not None and http_request.uri.host != stored_request.uri.host): return False # Check the request path in the URL (/feeds/private/full/x) elif http_request.uri.path != stored_request.uri.path: return False # Check the method used in the request (GET, POST, etc.) elif http_request.method != stored_request.method: return False # If there is a gsession ID in either request, make sure that it is matched # exactly. elif ('gsessionid' in http_request.uri.query or 'gsessionid' in stored_request.uri.query): if 'gsessionid' not in stored_request.uri.query: return False elif 'gsessionid' not in http_request.uri.query: return False elif (http_request.uri.query['gsessionid'] != stored_request.uri.query['gsessionid']): return False # Ignores differences in the query params (?start-index=5&max-results=20), # the body of the request, the port number, HTTP headers, just to name a # few. return True def _scrub_request(http_request): """ Removes email address and password from a client login request. Since the mock server saves the request and response in plantext, sensitive information like the password should be removed before saving the recordings. At the moment only requests sent to a ClientLogin url are scrubbed. """ if (http_request and http_request.uri and http_request.uri.path and http_request.uri.path.endswith('ClientLogin')): # Remove the email and password from a ClientLogin request. http_request._body_parts = [] http_request.add_form_inputs( {'form_data': 'client login request has been scrubbed'}) else: # We can remove the body of the post from the recorded request, since # the request body is not used when finding a matching recording. http_request._body_parts = [] return http_request def _scrub_response(http_response): return http_response class EchoHttpClient(object): """Sends the request data back in the response. Used to check the formatting of the request as it was sent. Always responds with a 200 OK, and some information from the HTTP request is returned in special Echo-X headers in the response. The following headers are added in the response: 'Echo-Host': The host name and port number to which the HTTP connection is made. If no port was passed in, the header will contain host:None. 'Echo-Uri': The path portion of the URL being requested. /example?x=1&y=2 'Echo-Scheme': The beginning of the URL, usually 'http' or 'https' 'Echo-Method': The HTTP method being used, 'GET', 'POST', 'PUT', etc. """ def request(self, http_request): return self._http_request(http_request.uri, http_request.method, http_request.headers, http_request._body_parts) def _http_request(self, uri, method, headers=None, body_parts=None): body = StringIO.StringIO() response = atom.http_core.HttpResponse(status=200, reason='OK', body=body) if headers is None: response._headers = {} else: # Copy headers from the request to the response but convert values to # strings. Server response headers always come in as strings, so an int # should be converted to a corresponding string when echoing. for header, value in headers.iteritems(): response._headers[header] = str(value) response._headers['Echo-Host'] = '%s:%s' % (uri.host, str(uri.port)) response._headers['Echo-Uri'] = uri._get_relative_path() response._headers['Echo-Scheme'] = uri.scheme response._headers['Echo-Method'] = method for part in body_parts: if isinstance(part, str): body.write(part) elif hasattr(part, 'read'): body.write(part.read()) body.seek(0) return response class SettableHttpClient(object): """An HTTP Client which responds with the data given in set_response.""" def __init__(self, status, reason, body, headers): """Configures the response for the server. See set_response for details on the arguments to the constructor. """ self.set_response(status, reason, body, headers) self.last_request = None def set_response(self, status, reason, body, headers): """Determines the response which will be sent for each request. Args: status: An int for the HTTP status code, example: 200, 404, etc. reason: String for the HTTP reason, example: OK, NOT FOUND, etc. body: The body of the HTTP response as a string or a file-like object (something with a read method). headers: dict of strings containing the HTTP headers in the response. """ self.response = atom.http_core.HttpResponse(status=status, reason=reason, body=body) self.response._headers = headers.copy() def request(self, http_request): self.last_request = http_request return self.response class MockHttpResponse(atom.http_core.HttpResponse): def __init__(self, status=None, reason=None, headers=None, body=None): self._headers = headers or {} if status is not None: self.status = status if reason is not None: self.reason = reason if body is not None: # Instead of using a file-like object for the body, store as a string # so that reads can be repeated. if hasattr(body, 'read'): self._body = body.read() else: self._body = body def read(self): return self._body ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������gdata-2.0.18/src/atom/mock_http.py������������������������������������������������������������������0000640�0364663�0011610�00000010572�12156622362�017033� 0����������������������������������������������������������������������������������������������������ustar �afshar��������������������������eng�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/python # # Copyright (C) 2008 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. __author__ = 'api.jscudder (Jeff Scudder)' import atom.http_interface import atom.url class Error(Exception): pass class NoRecordingFound(Error): pass class MockRequest(object): """Holds parameters of an HTTP request for matching against future requests. """ def __init__(self, operation, url, data=None, headers=None): self.operation = operation if isinstance(url, (str, unicode)): url = atom.url.parse_url(url) self.url = url self.data = data self.headers = headers class MockResponse(atom.http_interface.HttpResponse): """Simulates an httplib.HTTPResponse object.""" def __init__(self, body=None, status=None, reason=None, headers=None): if body and hasattr(body, 'read'): self.body = body.read() else: self.body = body if status is not None: self.status = int(status) else: self.status = None self.reason = reason self._headers = headers or {} def read(self): return self.body class MockHttpClient(atom.http_interface.GenericHttpClient): def __init__(self, headers=None, recordings=None, real_client=None): """An HttpClient which responds to request with stored data. The request-response pairs are stored as tuples in a member list named recordings. The MockHttpClient can be switched from replay mode to record mode by setting the real_client member to an instance of an HttpClient which will make real HTTP requests and store the server's response in list of recordings. Args: headers: dict containing HTTP headers which should be included in all HTTP requests. recordings: The initial recordings to be used for responses. This list contains tuples in the form: (MockRequest, MockResponse) real_client: An HttpClient which will make a real HTTP request. The response will be converted into a MockResponse and stored in recordings. """ self.recordings = recordings or [] self.real_client = real_client self.headers = headers or {} def add_response(self, response, operation, url, data=None, headers=None): """Adds a request-response pair to the recordings list. After the recording is added, future matching requests will receive the response. Args: response: MockResponse operation: str url: str data: str, Currently the data is ignored when looking for matching requests. headers: dict of strings: Currently the headers are ignored when looking for matching requests. """ request = MockRequest(operation, url, data=data, headers=headers) self.recordings.append((request, response)) def request(self, operation, url, data=None, headers=None): """Returns a matching MockResponse from the recordings. If the real_client is set, the request will be passed along and the server's response will be added to the recordings and also returned. If there is no match, a NoRecordingFound error will be raised. """ if self.real_client is None: if isinstance(url, (str, unicode)): url = atom.url.parse_url(url) for recording in self.recordings: if recording[0].operation == operation and recording[0].url == url: return recording[1] raise NoRecordingFound('No recodings found for %s %s' % ( operation, url)) else: # There is a real HTTP client, so make the request, and record the # response. response = self.real_client.request(operation, url, data=data, headers=headers) # TODO: copy the headers stored_response = MockResponse(body=response, status=response.status, reason=response.reason) self.add_response(stored_response, operation, url, data=data, headers=headers) return stored_response ��������������������������������������������������������������������������������������������������������������������������������������gdata-2.0.18/src/atom/core.py�����������������������������������������������������������������������0000640�0364663�0011610�00000050427�12156622362�015776� 0����������������������������������������������������������������������������������������������������ustar �afshar��������������������������eng�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/env python # # Copyright (C) 2008 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. # This module is used for version 2 of the Google Data APIs. __author__ = 'j.s@google.com (Jeff Scudder)' import inspect try: from xml.etree import cElementTree as ElementTree except ImportError: try: import cElementTree as ElementTree except ImportError: try: from xml.etree import ElementTree except ImportError: from elementtree import ElementTree try: from xml.dom.minidom import parseString as xmlString except ImportError: xmlString = None STRING_ENCODING = 'utf-8' class XmlElement(object): """Represents an element node in an XML document. The text member is a UTF-8 encoded str or unicode. """ _qname = None _other_elements = None _other_attributes = None # The rule set contains mappings for XML qnames to child members and the # appropriate member classes. _rule_set = None _members = None text = None def __init__(self, text=None, *args, **kwargs): if ('_members' not in self.__class__.__dict__ or self.__class__._members is None): self.__class__._members = tuple(self.__class__._list_xml_members()) for member_name, member_type in self.__class__._members: if member_name in kwargs: setattr(self, member_name, kwargs[member_name]) else: if isinstance(member_type, list): setattr(self, member_name, []) else: setattr(self, member_name, None) self._other_elements = [] self._other_attributes = {} if text is not None: self.text = text def _list_xml_members(cls): """Generator listing all members which are XML elements or attributes. The following members would be considered XML members: foo = 'abc' - indicates an XML attribute with the qname abc foo = SomeElement - indicates an XML child element foo = [AnElement] - indicates a repeating XML child element, each instance will be stored in a list in this member foo = ('att1', '{http://example.com/namespace}att2') - indicates an XML attribute which has different parsing rules in different versions of the protocol. Version 1 of the XML parsing rules will look for an attribute with the qname 'att1' but verion 2 of the parsing rules will look for a namespaced attribute with the local name of 'att2' and an XML namespace of 'http://example.com/namespace'. """ members = [] for pair in inspect.getmembers(cls): if not pair[0].startswith('_') and pair[0] != 'text': member_type = pair[1] if (isinstance(member_type, tuple) or isinstance(member_type, list) or isinstance(member_type, (str, unicode)) or (inspect.isclass(member_type) and issubclass(member_type, XmlElement))): members.append(pair) return members _list_xml_members = classmethod(_list_xml_members) def _get_rules(cls, version): """Initializes the _rule_set for the class which is used when parsing XML. This method is used internally for parsing and generating XML for an XmlElement. It is not recommended that you call this method directly. Returns: A tuple containing the XML parsing rules for the appropriate version. The tuple looks like: (qname, {sub_element_qname: (member_name, member_class, repeating), ..}, {attribute_qname: member_name}) To give a couple of concrete example, the atom.data.Control _get_rules with version of 2 will return: ('{http://www.w3.org/2007/app}control', {'{http://www.w3.org/2007/app}draft': ('draft', <class 'atom.data.Draft'>, False)}, {}) Calling _get_rules with version 1 on gdata.data.FeedLink will produce: ('{http://schemas.google.com/g/2005}feedLink', {'{http://www.w3.org/2005/Atom}feed': ('feed', <class 'gdata.data.GDFeed'>, False)}, {'href': 'href', 'readOnly': 'read_only', 'countHint': 'count_hint', 'rel': 'rel'}) """ # Initialize the _rule_set to make sure there is a slot available to store # the parsing rules for this version of the XML schema. # Look for rule set in the class __dict__ proxy so that only the # _rule_set for this class will be found. By using the dict proxy # we avoid finding rule_sets defined in superclasses. # The four lines below provide support for any number of versions, but it # runs a bit slower then hard coding slots for two versions, so I'm using # the below two lines. #if '_rule_set' not in cls.__dict__ or cls._rule_set is None: # cls._rule_set = [] #while len(cls.__dict__['_rule_set']) < version: # cls._rule_set.append(None) # If there is no rule set cache in the class, provide slots for two XML # versions. If and when there is a version 3, this list will need to be # expanded. if '_rule_set' not in cls.__dict__ or cls._rule_set is None: cls._rule_set = [None, None] # If a version higher than 2 is requested, fall back to version 2 because # 2 is currently the highest supported version. if version > 2: return cls._get_rules(2) # Check the dict proxy for the rule set to avoid finding any rule sets # which belong to the superclass. We only want rule sets for this class. if cls._rule_set[version-1] is None: # The rule set for each version consists of the qname for this element # ('{namespace}tag'), a dictionary (elements) for looking up the # corresponding class member when given a child element's qname, and a # dictionary (attributes) for looking up the corresponding class member # when given an XML attribute's qname. elements = {} attributes = {} if ('_members' not in cls.__dict__ or cls._members is None): cls._members = tuple(cls._list_xml_members()) for member_name, target in cls._members: if isinstance(target, list): # This member points to a repeating element. elements[_get_qname(target[0], version)] = (member_name, target[0], True) elif isinstance(target, tuple): # This member points to a versioned XML attribute. if version <= len(target): attributes[target[version-1]] = member_name else: attributes[target[-1]] = member_name elif isinstance(target, (str, unicode)): # This member points to an XML attribute. attributes[target] = member_name elif issubclass(target, XmlElement): # This member points to a single occurance element. elements[_get_qname(target, version)] = (member_name, target, False) version_rules = (_get_qname(cls, version), elements, attributes) cls._rule_set[version-1] = version_rules return version_rules else: return cls._rule_set[version-1] _get_rules = classmethod(_get_rules) def get_elements(self, tag=None, namespace=None, version=1): """Find all sub elements which match the tag and namespace. To find all elements in this object, call get_elements with the tag and namespace both set to None (the default). This method searches through the object's members and the elements stored in _other_elements which did not match any of the XML parsing rules for this class. Args: tag: str namespace: str version: int Specifies the version of the XML rules to be used when searching for matching elements. Returns: A list of the matching XmlElements. """ matches = [] ignored1, elements, ignored2 = self.__class__._get_rules(version) if elements: for qname, element_def in elements.iteritems(): member = getattr(self, element_def[0]) if member: if _qname_matches(tag, namespace, qname): if element_def[2]: # If this is a repeating element, copy all instances into the # result list. matches.extend(member) else: matches.append(member) for element in self._other_elements: if _qname_matches(tag, namespace, element._qname): matches.append(element) return matches GetElements = get_elements # FindExtensions and FindChildren are provided for backwards compatibility # to the atom.AtomBase class. # However, FindExtensions may return more results than the v1 atom.AtomBase # method does, because get_elements searches both the expected children # and the unexpected "other elements". The old AtomBase.FindExtensions # method searched only "other elements" AKA extension_elements. FindExtensions = get_elements FindChildren = get_elements def get_attributes(self, tag=None, namespace=None, version=1): """Find all attributes which match the tag and namespace. To find all attributes in this object, call get_attributes with the tag and namespace both set to None (the default). This method searches through the object's members and the attributes stored in _other_attributes which did not fit any of the XML parsing rules for this class. Args: tag: str namespace: str version: int Specifies the version of the XML rules to be used when searching for matching attributes. Returns: A list of XmlAttribute objects for the matching attributes. """ matches = [] ignored1, ignored2, attributes = self.__class__._get_rules(version) if attributes: for qname, attribute_def in attributes.iteritems(): if isinstance(attribute_def, (list, tuple)): attribute_def = attribute_def[0] member = getattr(self, attribute_def) # TODO: ensure this hasn't broken existing behavior. #member = getattr(self, attribute_def[0]) if member: if _qname_matches(tag, namespace, qname): matches.append(XmlAttribute(qname, member)) for qname, value in self._other_attributes.iteritems(): if _qname_matches(tag, namespace, qname): matches.append(XmlAttribute(qname, value)) return matches GetAttributes = get_attributes def _harvest_tree(self, tree, version=1): """Populates object members from the data in the tree Element.""" qname, elements, attributes = self.__class__._get_rules(version) for element in tree: if elements and element.tag in elements: definition = elements[element.tag] # If this is a repeating element, make sure the member is set to a # list. if definition[2]: if getattr(self, definition[0]) is None: setattr(self, definition[0], []) getattr(self, definition[0]).append(_xml_element_from_tree(element, definition[1], version)) else: setattr(self, definition[0], _xml_element_from_tree(element, definition[1], version)) else: self._other_elements.append(_xml_element_from_tree(element, XmlElement, version)) for attrib, value in tree.attrib.iteritems(): if attributes and attrib in attributes: setattr(self, attributes[attrib], value) else: self._other_attributes[attrib] = value if tree.text: self.text = tree.text def _to_tree(self, version=1, encoding=None): new_tree = ElementTree.Element(_get_qname(self, version)) self._attach_members(new_tree, version, encoding) return new_tree def _attach_members(self, tree, version=1, encoding=None): """Convert members to XML elements/attributes and add them to the tree. Args: tree: An ElementTree.Element which will be modified. The members of this object will be added as child elements or attributes according to the rules described in _expected_elements and _expected_attributes. The elements and attributes stored in other_attributes and other_elements are also added a children of this tree. version: int Ingnored in this method but used by VersionedElement. encoding: str (optional) """ qname, elements, attributes = self.__class__._get_rules(version) encoding = encoding or STRING_ENCODING # Add the expected elements and attributes to the tree. if elements: for tag, element_def in elements.iteritems(): member = getattr(self, element_def[0]) # If this is a repeating element and there are members in the list. if member and element_def[2]: for instance in member: instance._become_child(tree, version) elif member: member._become_child(tree, version) if attributes: for attribute_tag, member_name in attributes.iteritems(): value = getattr(self, member_name) if value: tree.attrib[attribute_tag] = value # Add the unexpected (other) elements and attributes to the tree. for element in self._other_elements: element._become_child(tree, version) for key, value in self._other_attributes.iteritems(): # I'm not sure if unicode can be used in the attribute name, so for now # we assume the encoding is correct for the attribute name. if not isinstance(value, unicode): value = value.decode(encoding) tree.attrib[key] = value if self.text: if isinstance(self.text, unicode): tree.text = self.text else: tree.text = self.text.decode(encoding) def to_string(self, version=1, encoding=None, pretty_print=None): """Converts this object to XML.""" tree_string = ElementTree.tostring(self._to_tree(version, encoding)) if pretty_print and xmlString is not None: return xmlString(tree_string).toprettyxml() return tree_string ToString = to_string def __str__(self): return self.to_string() def _become_child(self, tree, version=1): """Adds a child element to tree with the XML data in self.""" new_child = ElementTree.Element('') tree.append(new_child) new_child.tag = _get_qname(self, version) self._attach_members(new_child, version) def __get_extension_elements(self): return self._other_elements def __set_extension_elements(self, elements): self._other_elements = elements extension_elements = property(__get_extension_elements, __set_extension_elements, """Provides backwards compatibility for v1 atom.AtomBase classes.""") def __get_extension_attributes(self): return self._other_attributes def __set_extension_attributes(self, attributes): self._other_attributes = attributes extension_attributes = property(__get_extension_attributes, __set_extension_attributes, """Provides backwards compatibility for v1 atom.AtomBase classes.""") def _get_tag(self, version=1): qname = _get_qname(self, version) if qname: return qname[qname.find('}')+1:] return None def _get_namespace(self, version=1): qname = _get_qname(self, version) if qname.startswith('{'): return qname[1:qname.find('}')] else: return None def _set_tag(self, tag): if isinstance(self._qname, tuple): self._qname = self._qname.copy() if self._qname[0].startswith('{'): self._qname[0] = '{%s}%s' % (self._get_namespace(1), tag) else: self._qname[0] = tag else: if self._qname is not None and self._qname.startswith('{'): self._qname = '{%s}%s' % (self._get_namespace(), tag) else: self._qname = tag def _set_namespace(self, namespace): tag = self._get_tag(1) if tag is None: tag = '' if isinstance(self._qname, tuple): self._qname = self._qname.copy() if namespace: self._qname[0] = '{%s}%s' % (namespace, tag) else: self._qname[0] = tag else: if namespace: self._qname = '{%s}%s' % (namespace, tag) else: self._qname = tag tag = property(_get_tag, _set_tag, """Provides backwards compatibility for v1 atom.AtomBase classes.""") namespace = property(_get_namespace, _set_namespace, """Provides backwards compatibility for v1 atom.AtomBase classes.""") # Provided for backwards compatibility to atom.ExtensionElement children = extension_elements attributes = extension_attributes def _get_qname(element, version): if isinstance(element._qname, tuple): if version <= len(element._qname): return element._qname[version-1] else: return element._qname[-1] else: return element._qname def _qname_matches(tag, namespace, qname): """Logic determines if a QName matches the desired local tag and namespace. This is used in XmlElement.get_elements and XmlElement.get_attributes to find matches in the element's members (among all expected-and-unexpected elements-and-attributes). Args: expected_tag: string expected_namespace: string qname: string in the form '{xml_namespace}localtag' or 'tag' if there is no namespace. Returns: boolean True if the member's tag and namespace fit the expected tag and namespace. """ # If there is no expected namespace or tag, then everything will match. if qname is None: member_tag = None member_namespace = None else: if qname.startswith('{'): member_namespace = qname[1:qname.index('}')] member_tag = qname[qname.index('}') + 1:] else: member_namespace = None member_tag = qname return ((tag is None and namespace is None) # If there is a tag, but no namespace, see if the local tag matches. or (namespace is None and member_tag == tag) # There was no tag, but there was a namespace so see if the namespaces # match. or (tag is None and member_namespace == namespace) # There was no tag, and the desired elements have no namespace, so check # to see that the member's namespace is None. or (tag is None and namespace == '' and member_namespace is None) # The tag and the namespace both match. or (tag == member_tag and namespace == member_namespace) # The tag matches, and the expected namespace is the empty namespace, # check to make sure the member's namespace is None. or (tag == member_tag and namespace == '' and member_namespace is None)) def parse(xml_string, target_class=None, version=1, encoding=None): """Parses the XML string according to the rules for the target_class. Args: xml_string: str or unicode target_class: XmlElement or a subclass. If None is specified, the XmlElement class is used. version: int (optional) The version of the schema which should be used when converting the XML into an object. The default is 1. encoding: str (optional) The character encoding of the bytes in the xml_string. Default is 'UTF-8'. """ if target_class is None: target_class = XmlElement if isinstance(xml_string, unicode): if encoding is None: xml_string = xml_string.encode(STRING_ENCODING) else: xml_string = xml_string.encode(encoding) tree = ElementTree.fromstring(xml_string) return _xml_element_from_tree(tree, target_class, version) Parse = parse xml_element_from_string = parse XmlElementFromString = xml_element_from_string def _xml_element_from_tree(tree, target_class, version=1): if target_class._qname is None: instance = target_class() instance._qname = tree.tag instance._harvest_tree(tree, version) return instance # TODO handle the namespace-only case # Namespace only will be used with Google Spreadsheets rows and # Google Base item attributes. elif tree.tag == _get_qname(target_class, version): instance = target_class() instance._harvest_tree(tree, version) return instance return None class XmlAttribute(object): def __init__(self, qname, value): self._qname = qname self.value = value �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������gdata-2.0.18/src/atom/data.py�����������������������������������������������������������������������0000640�0364663�0011610�00000017706�12156622362�015762� 0����������������������������������������������������������������������������������������������������ustar �afshar��������������������������eng�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/env python # # Copyright (C) 2009 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. # This module is used for version 2 of the Google Data APIs. __author__ = 'j.s@google.com (Jeff Scudder)' import atom.core XML_TEMPLATE = '{http://www.w3.org/XML/1998/namespace}%s' ATOM_TEMPLATE = '{http://www.w3.org/2005/Atom}%s' APP_TEMPLATE_V1 = '{http://purl.org/atom/app#}%s' APP_TEMPLATE_V2 = '{http://www.w3.org/2007/app}%s' class Name(atom.core.XmlElement): """The atom:name element.""" _qname = ATOM_TEMPLATE % 'name' class Email(atom.core.XmlElement): """The atom:email element.""" _qname = ATOM_TEMPLATE % 'email' class Uri(atom.core.XmlElement): """The atom:uri element.""" _qname = ATOM_TEMPLATE % 'uri' class Person(atom.core.XmlElement): """A foundation class which atom:author and atom:contributor extend. A person contains information like name, email address, and web page URI for an author or contributor to an Atom feed. """ name = Name email = Email uri = Uri class Author(Person): """The atom:author element. An author is a required element in Feed unless each Entry contains an Author. """ _qname = ATOM_TEMPLATE % 'author' class Contributor(Person): """The atom:contributor element.""" _qname = ATOM_TEMPLATE % 'contributor' class Link(atom.core.XmlElement): """The atom:link element.""" _qname = ATOM_TEMPLATE % 'link' href = 'href' rel = 'rel' type = 'type' hreflang = 'hreflang' title = 'title' length = 'length' class Generator(atom.core.XmlElement): """The atom:generator element.""" _qname = ATOM_TEMPLATE % 'generator' uri = 'uri' version = 'version' class Text(atom.core.XmlElement): """A foundation class from which atom:title, summary, etc. extend. This class should never be instantiated. """ type = 'type' class Title(Text): """The atom:title element.""" _qname = ATOM_TEMPLATE % 'title' class Subtitle(Text): """The atom:subtitle element.""" _qname = ATOM_TEMPLATE % 'subtitle' class Rights(Text): """The atom:rights element.""" _qname = ATOM_TEMPLATE % 'rights' class Summary(Text): """The atom:summary element.""" _qname = ATOM_TEMPLATE % 'summary' class Content(Text): """The atom:content element.""" _qname = ATOM_TEMPLATE % 'content' src = 'src' class Category(atom.core.XmlElement): """The atom:category element.""" _qname = ATOM_TEMPLATE % 'category' term = 'term' scheme = 'scheme' label = 'label' class Id(atom.core.XmlElement): """The atom:id element.""" _qname = ATOM_TEMPLATE % 'id' class Icon(atom.core.XmlElement): """The atom:icon element.""" _qname = ATOM_TEMPLATE % 'icon' class Logo(atom.core.XmlElement): """The atom:logo element.""" _qname = ATOM_TEMPLATE % 'logo' class Draft(atom.core.XmlElement): """The app:draft element which indicates if this entry should be public.""" _qname = (APP_TEMPLATE_V1 % 'draft', APP_TEMPLATE_V2 % 'draft') class Control(atom.core.XmlElement): """The app:control element indicating restrictions on publication. The APP control element may contain a draft element indicating whether or not this entry should be publicly available. """ _qname = (APP_TEMPLATE_V1 % 'control', APP_TEMPLATE_V2 % 'control') draft = Draft class Date(atom.core.XmlElement): """A parent class for atom:updated, published, etc.""" class Updated(Date): """The atom:updated element.""" _qname = ATOM_TEMPLATE % 'updated' class Published(Date): """The atom:published element.""" _qname = ATOM_TEMPLATE % 'published' class LinkFinder(object): """An "interface" providing methods to find link elements Entry elements often contain multiple links which differ in the rel attribute or content type. Often, developers are interested in a specific type of link so this class provides methods to find specific classes of links. This class is used as a mixin in Atom entries and feeds. """ def find_url(self, rel): """Returns the URL (as a string) in a link with the desired rel value.""" for link in self.link: if link.rel == rel and link.href: return link.href return None FindUrl = find_url def get_link(self, rel): """Returns a link object which has the desired rel value. If you are interested in the URL instead of the link object, consider using find_url instead. """ for link in self.link: if link.rel == rel and link.href: return link return None GetLink = get_link def find_self_link(self): """Find the first link with rel set to 'self' Returns: A str containing the link's href or None if none of the links had rel equal to 'self' """ return self.find_url('self') FindSelfLink = find_self_link def get_self_link(self): return self.get_link('self') GetSelfLink = get_self_link def find_edit_link(self): return self.find_url('edit') FindEditLink = find_edit_link def get_edit_link(self): return self.get_link('edit') GetEditLink = get_edit_link def find_edit_media_link(self): link = self.find_url('edit-media') # Search for media-edit as well since Picasa API used media-edit instead. if link is None: return self.find_url('media-edit') return link FindEditMediaLink = find_edit_media_link def get_edit_media_link(self): link = self.get_link('edit-media') if link is None: return self.get_link('media-edit') return link GetEditMediaLink = get_edit_media_link def find_next_link(self): return self.find_url('next') FindNextLink = find_next_link def get_next_link(self): return self.get_link('next') GetNextLink = get_next_link def find_license_link(self): return self.find_url('license') FindLicenseLink = find_license_link def get_license_link(self): return self.get_link('license') GetLicenseLink = get_license_link def find_alternate_link(self): return self.find_url('alternate') FindAlternateLink = find_alternate_link def get_alternate_link(self): return self.get_link('alternate') GetAlternateLink = get_alternate_link class FeedEntryParent(atom.core.XmlElement, LinkFinder): """A super class for atom:feed and entry, contains shared attributes""" author = [Author] category = [Category] contributor = [Contributor] id = Id link = [Link] rights = Rights title = Title updated = Updated def __init__(self, atom_id=None, text=None, *args, **kwargs): if atom_id is not None: self.id = atom_id atom.core.XmlElement.__init__(self, text=text, *args, **kwargs) class Source(FeedEntryParent): """The atom:source element.""" _qname = ATOM_TEMPLATE % 'source' generator = Generator icon = Icon logo = Logo subtitle = Subtitle class Entry(FeedEntryParent): """The atom:entry element.""" _qname = ATOM_TEMPLATE % 'entry' content = Content published = Published source = Source summary = Summary control = Control class Feed(Source): """The atom:feed element which contains entries.""" _qname = ATOM_TEMPLATE % 'feed' entry = [Entry] class ExtensionElement(atom.core.XmlElement): """Provided for backwards compatibility to the v1 atom.ExtensionElement.""" def __init__(self, tag=None, namespace=None, attributes=None, children=None, text=None, *args, **kwargs): if namespace: self._qname = '{%s}%s' % (namespace, tag) else: self._qname = tag self.children = children or [] self.attributes = attributes or {} self.text = text _BecomeChildElement = atom.core.XmlElement._become_child ����������������������������������������������������������gdata-2.0.18/src/atom/__init__.py�������������������������������������������������������������������0000750�0364663�0011610�00000140246�12156622362�016606� 0����������������������������������������������������������������������������������������������������ustar �afshar��������������������������eng�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/python # # Copyright (C) 2006 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. """Contains classes representing Atom elements. Module objective: provide data classes for Atom constructs. These classes hide the XML-ness of Atom and provide a set of native Python classes to interact with. Conversions to and from XML should only be necessary when the Atom classes "touch the wire" and are sent over HTTP. For this reason this module provides methods and functions to convert Atom classes to and from strings. For more information on the Atom data model, see RFC 4287 (http://www.ietf.org/rfc/rfc4287.txt) AtomBase: A foundation class on which Atom classes are built. It handles the parsing of attributes and children which are common to all Atom classes. By default, the AtomBase class translates all XML child nodes into ExtensionElements. ExtensionElement: Atom allows Atom objects to contain XML which is not part of the Atom specification, these are called extension elements. If a classes parser encounters an unexpected XML construct, it is translated into an ExtensionElement instance. ExtensionElement is designed to fully capture the information in the XML. Child nodes in an XML extension are turned into ExtensionElements as well. """ __author__ = 'api.jscudder (Jeffrey Scudder)' try: from xml.etree import cElementTree as ElementTree except ImportError: try: import cElementTree as ElementTree except ImportError: try: from xml.etree import ElementTree except ImportError: from elementtree import ElementTree import warnings # XML namespaces which are often used in Atom entities. ATOM_NAMESPACE = 'http://www.w3.org/2005/Atom' ELEMENT_TEMPLATE = '{http://www.w3.org/2005/Atom}%s' APP_NAMESPACE = 'http://purl.org/atom/app#' APP_TEMPLATE = '{http://purl.org/atom/app#}%s' # This encoding is used for converting strings before translating the XML # into an object. XML_STRING_ENCODING = 'utf-8' # The desired string encoding for object members. set or monkey-patch to # unicode if you want object members to be Python unicode strings, instead of # encoded strings MEMBER_STRING_ENCODING = 'utf-8' #MEMBER_STRING_ENCODING = unicode # If True, all methods which are exclusive to v1 will raise a # DeprecationWarning ENABLE_V1_WARNINGS = False def v1_deprecated(warning=None): """Shows a warning if ENABLE_V1_WARNINGS is True. Function decorator used to mark methods used in v1 classes which may be removed in future versions of the library. """ warning = warning or '' # This closure is what is returned from the deprecated function. def mark_deprecated(f): # The deprecated_function wraps the actual call to f. def optional_warn_function(*args, **kwargs): if ENABLE_V1_WARNINGS: warnings.warn(warning, DeprecationWarning, stacklevel=2) return f(*args, **kwargs) # Preserve the original name to avoid masking all decorated functions as # 'deprecated_function' try: optional_warn_function.func_name = f.func_name except TypeError: pass # In Python2.3 we can't set the func_name return optional_warn_function return mark_deprecated def CreateClassFromXMLString(target_class, xml_string, string_encoding=None): """Creates an instance of the target class from the string contents. Args: target_class: class The class which will be instantiated and populated with the contents of the XML. This class must have a _tag and a _namespace class variable. xml_string: str A string which contains valid XML. The root element of the XML string should match the tag and namespace of the desired class. string_encoding: str The character encoding which the xml_string should be converted to before it is interpreted and translated into objects. The default is None in which case the string encoding is not changed. Returns: An instance of the target class with members assigned according to the contents of the XML - or None if the root XML tag and namespace did not match those of the target class. """ encoding = string_encoding or XML_STRING_ENCODING if encoding and isinstance(xml_string, unicode): xml_string = xml_string.encode(encoding) tree = ElementTree.fromstring(xml_string) return _CreateClassFromElementTree(target_class, tree) CreateClassFromXMLString = v1_deprecated( 'Please use atom.core.parse with atom.data classes instead.')( CreateClassFromXMLString) def _CreateClassFromElementTree(target_class, tree, namespace=None, tag=None): """Instantiates the class and populates members according to the tree. Note: Only use this function with classes that have _namespace and _tag class members. Args: target_class: class The class which will be instantiated and populated with the contents of the XML. tree: ElementTree An element tree whose contents will be converted into members of the new target_class instance. namespace: str (optional) The namespace which the XML tree's root node must match. If omitted, the namespace defaults to the _namespace of the target class. tag: str (optional) The tag which the XML tree's root node must match. If omitted, the tag defaults to the _tag class member of the target class. Returns: An instance of the target class - or None if the tag and namespace of the XML tree's root node did not match the desired namespace and tag. """ if namespace is None: namespace = target_class._namespace if tag is None: tag = target_class._tag if tree.tag == '{%s}%s' % (namespace, tag): target = target_class() target._HarvestElementTree(tree) return target else: return None class ExtensionContainer(object): def __init__(self, extension_elements=None, extension_attributes=None, text=None): self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} self.text = text __init__ = v1_deprecated( 'Please use data model classes in atom.data instead.')( __init__) # Three methods to create an object from an ElementTree def _HarvestElementTree(self, tree): # Fill in the instance members from the contents of the XML tree. for child in tree: self._ConvertElementTreeToMember(child) for attribute, value in tree.attrib.iteritems(): self._ConvertElementAttributeToMember(attribute, value) # Encode the text string according to the desired encoding type. (UTF-8) if tree.text: if MEMBER_STRING_ENCODING is unicode: self.text = tree.text else: self.text = tree.text.encode(MEMBER_STRING_ENCODING) def _ConvertElementTreeToMember(self, child_tree, current_class=None): self.extension_elements.append(_ExtensionElementFromElementTree( child_tree)) def _ConvertElementAttributeToMember(self, attribute, value): # Encode the attribute value's string with the desired type Default UTF-8 if value: if MEMBER_STRING_ENCODING is unicode: self.extension_attributes[attribute] = value else: self.extension_attributes[attribute] = value.encode( MEMBER_STRING_ENCODING) # One method to create an ElementTree from an object def _AddMembersToElementTree(self, tree): for child in self.extension_elements: child._BecomeChildElement(tree) for attribute, value in self.extension_attributes.iteritems(): if value: if isinstance(value, unicode) or MEMBER_STRING_ENCODING is unicode: tree.attrib[attribute] = value else: # Decode the value from the desired encoding (default UTF-8). tree.attrib[attribute] = value.decode(MEMBER_STRING_ENCODING) if self.text: if isinstance(self.text, unicode) or MEMBER_STRING_ENCODING is unicode: tree.text = self.text else: tree.text = self.text.decode(MEMBER_STRING_ENCODING) def FindExtensions(self, tag=None, namespace=None): """Searches extension elements for child nodes with the desired name. Returns a list of extension elements within this object whose tag and/or namespace match those passed in. To find all extensions in a particular namespace, specify the namespace but not the tag name. If you specify only the tag, the result list may contain extension elements in multiple namespaces. Args: tag: str (optional) The desired tag namespace: str (optional) The desired namespace Returns: A list of elements whose tag and/or namespace match the parameters values """ results = [] if tag and namespace: for element in self.extension_elements: if element.tag == tag and element.namespace == namespace: results.append(element) elif tag and not namespace: for element in self.extension_elements: if element.tag == tag: results.append(element) elif namespace and not tag: for element in self.extension_elements: if element.namespace == namespace: results.append(element) else: for element in self.extension_elements: results.append(element) return results class AtomBase(ExtensionContainer): _children = {} _attributes = {} def __init__(self, extension_elements=None, extension_attributes=None, text=None): self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} self.text = text __init__ = v1_deprecated( 'Please use data model classes in atom.data instead.')( __init__) def _ConvertElementTreeToMember(self, child_tree): # Find the element's tag in this class's list of child members if self.__class__._children.has_key(child_tree.tag): member_name = self.__class__._children[child_tree.tag][0] member_class = self.__class__._children[child_tree.tag][1] # If the class member is supposed to contain a list, make sure the # matching member is set to a list, then append the new member # instance to the list. if isinstance(member_class, list): if getattr(self, member_name) is None: setattr(self, member_name, []) getattr(self, member_name).append(_CreateClassFromElementTree( member_class[0], child_tree)) else: setattr(self, member_name, _CreateClassFromElementTree(member_class, child_tree)) else: ExtensionContainer._ConvertElementTreeToMember(self, child_tree) def _ConvertElementAttributeToMember(self, attribute, value): # Find the attribute in this class's list of attributes. if self.__class__._attributes.has_key(attribute): # Find the member of this class which corresponds to the XML attribute # (lookup in current_class._attributes) and set this member to the # desired value (using self.__dict__). if value: # Encode the string to capture non-ascii characters (default UTF-8) if MEMBER_STRING_ENCODING is unicode: setattr(self, self.__class__._attributes[attribute], value) else: setattr(self, self.__class__._attributes[attribute], value.encode(MEMBER_STRING_ENCODING)) else: ExtensionContainer._ConvertElementAttributeToMember( self, attribute, value) # Three methods to create an ElementTree from an object def _AddMembersToElementTree(self, tree): # Convert the members of this class which are XML child nodes. # This uses the class's _children dictionary to find the members which # should become XML child nodes. member_node_names = [values[0] for tag, values in self.__class__._children.iteritems()] for member_name in member_node_names: member = getattr(self, member_name) if member is None: pass elif isinstance(member, list): for instance in member: instance._BecomeChildElement(tree) else: member._BecomeChildElement(tree) # Convert the members of this class which are XML attributes. for xml_attribute, member_name in self.__class__._attributes.iteritems(): member = getattr(self, member_name) if member is not None: if isinstance(member, unicode) or MEMBER_STRING_ENCODING is unicode: tree.attrib[xml_attribute] = member else: tree.attrib[xml_attribute] = member.decode(MEMBER_STRING_ENCODING) # Lastly, call the ExtensionContainers's _AddMembersToElementTree to # convert any extension attributes. ExtensionContainer._AddMembersToElementTree(self, tree) def _BecomeChildElement(self, tree): """ Note: Only for use with classes that have a _tag and _namespace class member. It is in AtomBase so that it can be inherited but it should not be called on instances of AtomBase. """ new_child = ElementTree.Element('') tree.append(new_child) new_child.tag = '{%s}%s' % (self.__class__._namespace, self.__class__._tag) self._AddMembersToElementTree(new_child) def _ToElementTree(self): """ Note, this method is designed to be used only with classes that have a _tag and _namespace. It is placed in AtomBase for inheritance but should not be called on this class. """ new_tree = ElementTree.Element('{%s}%s' % (self.__class__._namespace, self.__class__._tag)) self._AddMembersToElementTree(new_tree) return new_tree def ToString(self, string_encoding='UTF-8'): """Converts the Atom object to a string containing XML.""" return ElementTree.tostring(self._ToElementTree(), encoding=string_encoding) def __str__(self): return self.ToString() class Name(AtomBase): """The atom:name element""" _tag = 'name' _namespace = ATOM_NAMESPACE _children = AtomBase._children.copy() _attributes = AtomBase._attributes.copy() def __init__(self, text=None, extension_elements=None, extension_attributes=None): """Constructor for Name Args: text: str The text data in the this element extension_elements: list A list of ExtensionElement instances extension_attributes: dict A dictionary of attribute value string pairs """ self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} def NameFromString(xml_string): return CreateClassFromXMLString(Name, xml_string) class Email(AtomBase): """The atom:email element""" _tag = 'email' _namespace = ATOM_NAMESPACE _children = AtomBase._children.copy() _attributes = AtomBase._attributes.copy() def __init__(self, text=None, extension_elements=None, extension_attributes=None): """Constructor for Email Args: extension_elements: list A list of ExtensionElement instances extension_attributes: dict A dictionary of attribute value string pairs text: str The text data in the this element """ self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} def EmailFromString(xml_string): return CreateClassFromXMLString(Email, xml_string) class Uri(AtomBase): """The atom:uri element""" _tag = 'uri' _namespace = ATOM_NAMESPACE _children = AtomBase._children.copy() _attributes = AtomBase._attributes.copy() def __init__(self, text=None, extension_elements=None, extension_attributes=None): """Constructor for Uri Args: extension_elements: list A list of ExtensionElement instances extension_attributes: dict A dictionary of attribute value string pairs text: str The text data in the this element """ self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} def UriFromString(xml_string): return CreateClassFromXMLString(Uri, xml_string) class Person(AtomBase): """A foundation class from which atom:author and atom:contributor extend. A person contains information like name, email address, and web page URI for an author or contributor to an Atom feed. """ _children = AtomBase._children.copy() _attributes = AtomBase._attributes.copy() _children['{%s}name' % (ATOM_NAMESPACE)] = ('name', Name) _children['{%s}email' % (ATOM_NAMESPACE)] = ('email', Email) _children['{%s}uri' % (ATOM_NAMESPACE)] = ('uri', Uri) def __init__(self, name=None, email=None, uri=None, extension_elements=None, extension_attributes=None, text=None): """Foundation from which author and contributor are derived. The constructor is provided for illustrative purposes, you should not need to instantiate a Person. Args: name: Name The person's name email: Email The person's email address uri: Uri The URI of the person's webpage extension_elements: list A list of ExtensionElement instances which are children of this element. extension_attributes: dict A dictionary of strings which are the values for additional XML attributes of this element. text: String The text contents of the element. This is the contents of the Entry's XML text node. (Example: <foo>This is the text</foo>) """ self.name = name self.email = email self.uri = uri self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} self.text = text class Author(Person): """The atom:author element An author is a required element in Feed. """ _tag = 'author' _namespace = ATOM_NAMESPACE _children = Person._children.copy() _attributes = Person._attributes.copy() #_children = {} #_attributes = {} def __init__(self, name=None, email=None, uri=None, extension_elements=None, extension_attributes=None, text=None): """Constructor for Author Args: name: Name email: Email uri: Uri extension_elements: list A list of ExtensionElement instances extension_attributes: dict A dictionary of attribute value string pairs text: str The text data in the this element """ self.name = name self.email = email self.uri = uri self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} self.text = text def AuthorFromString(xml_string): return CreateClassFromXMLString(Author, xml_string) class Contributor(Person): """The atom:contributor element""" _tag = 'contributor' _namespace = ATOM_NAMESPACE _children = Person._children.copy() _attributes = Person._attributes.copy() def __init__(self, name=None, email=None, uri=None, extension_elements=None, extension_attributes=None, text=None): """Constructor for Contributor Args: name: Name email: Email uri: Uri extension_elements: list A list of ExtensionElement instances extension_attributes: dict A dictionary of attribute value string pairs text: str The text data in the this element """ self.name = name self.email = email self.uri = uri self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} self.text = text def ContributorFromString(xml_string): return CreateClassFromXMLString(Contributor, xml_string) class Link(AtomBase): """The atom:link element""" _tag = 'link' _namespace = ATOM_NAMESPACE _children = AtomBase._children.copy() _attributes = AtomBase._attributes.copy() _attributes['rel'] = 'rel' _attributes['href'] = 'href' _attributes['type'] = 'type' _attributes['title'] = 'title' _attributes['length'] = 'length' _attributes['hreflang'] = 'hreflang' def __init__(self, href=None, rel=None, link_type=None, hreflang=None, title=None, length=None, text=None, extension_elements=None, extension_attributes=None): """Constructor for Link Args: href: string The href attribute of the link rel: string type: string hreflang: string The language for the href title: string length: string The length of the href's destination extension_elements: list A list of ExtensionElement instances extension_attributes: dict A dictionary of attribute value string pairs text: str The text data in the this element """ self.href = href self.rel = rel self.type = link_type self.hreflang = hreflang self.title = title self.length = length self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} def LinkFromString(xml_string): return CreateClassFromXMLString(Link, xml_string) class Generator(AtomBase): """The atom:generator element""" _tag = 'generator' _namespace = ATOM_NAMESPACE _children = AtomBase._children.copy() _attributes = AtomBase._attributes.copy() _attributes['uri'] = 'uri' _attributes['version'] = 'version' def __init__(self, uri=None, version=None, text=None, extension_elements=None, extension_attributes=None): """Constructor for Generator Args: uri: string version: string text: str The text data in the this element extension_elements: list A list of ExtensionElement instances extension_attributes: dict A dictionary of attribute value string pairs """ self.uri = uri self.version = version self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} def GeneratorFromString(xml_string): return CreateClassFromXMLString(Generator, xml_string) class Text(AtomBase): """A foundation class from which atom:title, summary, etc. extend. This class should never be instantiated. """ _children = AtomBase._children.copy() _attributes = AtomBase._attributes.copy() _attributes['type'] = 'type' def __init__(self, text_type=None, text=None, extension_elements=None, extension_attributes=None): """Constructor for Text Args: text_type: string text: str The text data in the this element extension_elements: list A list of ExtensionElement instances extension_attributes: dict A dictionary of attribute value string pairs """ self.type = text_type self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} class Title(Text): """The atom:title element""" _tag = 'title' _namespace = ATOM_NAMESPACE _children = Text._children.copy() _attributes = Text._attributes.copy() def __init__(self, title_type=None, text=None, extension_elements=None, extension_attributes=None): """Constructor for Title Args: title_type: string text: str The text data in the this element extension_elements: list A list of ExtensionElement instances extension_attributes: dict A dictionary of attribute value string pairs """ self.type = title_type self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} def TitleFromString(xml_string): return CreateClassFromXMLString(Title, xml_string) class Subtitle(Text): """The atom:subtitle element""" _tag = 'subtitle' _namespace = ATOM_NAMESPACE _children = Text._children.copy() _attributes = Text._attributes.copy() def __init__(self, subtitle_type=None, text=None, extension_elements=None, extension_attributes=None): """Constructor for Subtitle Args: subtitle_type: string text: str The text data in the this element extension_elements: list A list of ExtensionElement instances extension_attributes: dict A dictionary of attribute value string pairs """ self.type = subtitle_type self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} def SubtitleFromString(xml_string): return CreateClassFromXMLString(Subtitle, xml_string) class Rights(Text): """The atom:rights element""" _tag = 'rights' _namespace = ATOM_NAMESPACE _children = Text._children.copy() _attributes = Text._attributes.copy() def __init__(self, rights_type=None, text=None, extension_elements=None, extension_attributes=None): """Constructor for Rights Args: rights_type: string text: str The text data in the this element extension_elements: list A list of ExtensionElement instances extension_attributes: dict A dictionary of attribute value string pairs """ self.type = rights_type self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} def RightsFromString(xml_string): return CreateClassFromXMLString(Rights, xml_string) class Summary(Text): """The atom:summary element""" _tag = 'summary' _namespace = ATOM_NAMESPACE _children = Text._children.copy() _attributes = Text._attributes.copy() def __init__(self, summary_type=None, text=None, extension_elements=None, extension_attributes=None): """Constructor for Summary Args: summary_type: string text: str The text data in the this element extension_elements: list A list of ExtensionElement instances extension_attributes: dict A dictionary of attribute value string pairs """ self.type = summary_type self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} def SummaryFromString(xml_string): return CreateClassFromXMLString(Summary, xml_string) class Content(Text): """The atom:content element""" _tag = 'content' _namespace = ATOM_NAMESPACE _children = Text._children.copy() _attributes = Text._attributes.copy() _attributes['src'] = 'src' def __init__(self, content_type=None, src=None, text=None, extension_elements=None, extension_attributes=None): """Constructor for Content Args: content_type: string src: string text: str The text data in the this element extension_elements: list A list of ExtensionElement instances extension_attributes: dict A dictionary of attribute value string pairs """ self.type = content_type self.src = src self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} def ContentFromString(xml_string): return CreateClassFromXMLString(Content, xml_string) class Category(AtomBase): """The atom:category element""" _tag = 'category' _namespace = ATOM_NAMESPACE _children = AtomBase._children.copy() _attributes = AtomBase._attributes.copy() _attributes['term'] = 'term' _attributes['scheme'] = 'scheme' _attributes['label'] = 'label' def __init__(self, term=None, scheme=None, label=None, text=None, extension_elements=None, extension_attributes=None): """Constructor for Category Args: term: str scheme: str label: str text: str The text data in the this element extension_elements: list A list of ExtensionElement instances extension_attributes: dict A dictionary of attribute value string pairs """ self.term = term self.scheme = scheme self.label = label self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} def CategoryFromString(xml_string): return CreateClassFromXMLString(Category, xml_string) class Id(AtomBase): """The atom:id element.""" _tag = 'id' _namespace = ATOM_NAMESPACE _children = AtomBase._children.copy() _attributes = AtomBase._attributes.copy() def __init__(self, text=None, extension_elements=None, extension_attributes=None): """Constructor for Id Args: text: str The text data in the this element extension_elements: list A list of ExtensionElement instances extension_attributes: dict A dictionary of attribute value string pairs """ self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} def IdFromString(xml_string): return CreateClassFromXMLString(Id, xml_string) class Icon(AtomBase): """The atom:icon element.""" _tag = 'icon' _namespace = ATOM_NAMESPACE _children = AtomBase._children.copy() _attributes = AtomBase._attributes.copy() def __init__(self, text=None, extension_elements=None, extension_attributes=None): """Constructor for Icon Args: text: str The text data in the this element extension_elements: list A list of ExtensionElement instances extension_attributes: dict A dictionary of attribute value string pairs """ self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} def IconFromString(xml_string): return CreateClassFromXMLString(Icon, xml_string) class Logo(AtomBase): """The atom:logo element.""" _tag = 'logo' _namespace = ATOM_NAMESPACE _children = AtomBase._children.copy() _attributes = AtomBase._attributes.copy() def __init__(self, text=None, extension_elements=None, extension_attributes=None): """Constructor for Logo Args: text: str The text data in the this element extension_elements: list A list of ExtensionElement instances extension_attributes: dict A dictionary of attribute value string pairs """ self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} def LogoFromString(xml_string): return CreateClassFromXMLString(Logo, xml_string) class Draft(AtomBase): """The app:draft element which indicates if this entry should be public.""" _tag = 'draft' _namespace = APP_NAMESPACE _children = AtomBase._children.copy() _attributes = AtomBase._attributes.copy() def __init__(self, text=None, extension_elements=None, extension_attributes=None): """Constructor for app:draft Args: text: str The text data in the this element extension_elements: list A list of ExtensionElement instances extension_attributes: dict A dictionary of attribute value string pairs """ self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} def DraftFromString(xml_string): return CreateClassFromXMLString(Draft, xml_string) class Control(AtomBase): """The app:control element indicating restrictions on publication. The APP control element may contain a draft element indicating whether or not this entry should be publicly available. """ _tag = 'control' _namespace = APP_NAMESPACE _children = AtomBase._children.copy() _attributes = AtomBase._attributes.copy() _children['{%s}draft' % APP_NAMESPACE] = ('draft', Draft) def __init__(self, draft=None, text=None, extension_elements=None, extension_attributes=None): """Constructor for app:control""" self.draft = draft self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} def ControlFromString(xml_string): return CreateClassFromXMLString(Control, xml_string) class Date(AtomBase): """A parent class for atom:updated, published, etc.""" #TODO Add text to and from time conversion methods to allow users to set # the contents of a Date to a python DateTime object. _children = AtomBase._children.copy() _attributes = AtomBase._attributes.copy() def __init__(self, text=None, extension_elements=None, extension_attributes=None): self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} class Updated(Date): """The atom:updated element.""" _tag = 'updated' _namespace = ATOM_NAMESPACE _children = Date._children.copy() _attributes = Date._attributes.copy() def __init__(self, text=None, extension_elements=None, extension_attributes=None): """Constructor for Updated Args: text: str The text data in the this element extension_elements: list A list of ExtensionElement instances extension_attributes: dict A dictionary of attribute value string pairs """ self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} def UpdatedFromString(xml_string): return CreateClassFromXMLString(Updated, xml_string) class Published(Date): """The atom:published element.""" _tag = 'published' _namespace = ATOM_NAMESPACE _children = Date._children.copy() _attributes = Date._attributes.copy() def __init__(self, text=None, extension_elements=None, extension_attributes=None): """Constructor for Published Args: text: str The text data in the this element extension_elements: list A list of ExtensionElement instances extension_attributes: dict A dictionary of attribute value string pairs """ self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} def PublishedFromString(xml_string): return CreateClassFromXMLString(Published, xml_string) class LinkFinder(object): """An "interface" providing methods to find link elements Entry elements often contain multiple links which differ in the rel attribute or content type. Often, developers are interested in a specific type of link so this class provides methods to find specific classes of links. This class is used as a mixin in Atom entries and feeds. """ def GetSelfLink(self): """Find the first link with rel set to 'self' Returns: An atom.Link or none if none of the links had rel equal to 'self' """ for a_link in self.link: if a_link.rel == 'self': return a_link return None def GetEditLink(self): for a_link in self.link: if a_link.rel == 'edit': return a_link return None def GetEditMediaLink(self): for a_link in self.link: if a_link.rel == 'edit-media': return a_link return None def GetNextLink(self): for a_link in self.link: if a_link.rel == 'next': return a_link return None def GetLicenseLink(self): for a_link in self.link: if a_link.rel == 'license': return a_link return None def GetAlternateLink(self): for a_link in self.link: if a_link.rel == 'alternate': return a_link return None class FeedEntryParent(AtomBase, LinkFinder): """A super class for atom:feed and entry, contains shared attributes""" _children = AtomBase._children.copy() _attributes = AtomBase._attributes.copy() _children['{%s}author' % ATOM_NAMESPACE] = ('author', [Author]) _children['{%s}category' % ATOM_NAMESPACE] = ('category', [Category]) _children['{%s}contributor' % ATOM_NAMESPACE] = ('contributor', [Contributor]) _children['{%s}id' % ATOM_NAMESPACE] = ('id', Id) _children['{%s}link' % ATOM_NAMESPACE] = ('link', [Link]) _children['{%s}rights' % ATOM_NAMESPACE] = ('rights', Rights) _children['{%s}title' % ATOM_NAMESPACE] = ('title', Title) _children['{%s}updated' % ATOM_NAMESPACE] = ('updated', Updated) def __init__(self, author=None, category=None, contributor=None, atom_id=None, link=None, rights=None, title=None, updated=None, text=None, extension_elements=None, extension_attributes=None): self.author = author or [] self.category = category or [] self.contributor = contributor or [] self.id = atom_id self.link = link or [] self.rights = rights self.title = title self.updated = updated self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} class Source(FeedEntryParent): """The atom:source element""" _tag = 'source' _namespace = ATOM_NAMESPACE _children = FeedEntryParent._children.copy() _attributes = FeedEntryParent._attributes.copy() _children['{%s}generator' % ATOM_NAMESPACE] = ('generator', Generator) _children['{%s}icon' % ATOM_NAMESPACE] = ('icon', Icon) _children['{%s}logo' % ATOM_NAMESPACE] = ('logo', Logo) _children['{%s}subtitle' % ATOM_NAMESPACE] = ('subtitle', Subtitle) def __init__(self, author=None, category=None, contributor=None, generator=None, icon=None, atom_id=None, link=None, logo=None, rights=None, subtitle=None, title=None, updated=None, text=None, extension_elements=None, extension_attributes=None): """Constructor for Source Args: author: list (optional) A list of Author instances which belong to this class. category: list (optional) A list of Category instances contributor: list (optional) A list on Contributor instances generator: Generator (optional) icon: Icon (optional) id: Id (optional) The entry's Id element link: list (optional) A list of Link instances logo: Logo (optional) rights: Rights (optional) The entry's Rights element subtitle: Subtitle (optional) The entry's subtitle element title: Title (optional) the entry's title element updated: Updated (optional) the entry's updated element text: String (optional) The text contents of the element. This is the contents of the Entry's XML text node. (Example: <foo>This is the text</foo>) extension_elements: list (optional) A list of ExtensionElement instances which are children of this element. extension_attributes: dict (optional) A dictionary of strings which are the values for additional XML attributes of this element. """ self.author = author or [] self.category = category or [] self.contributor = contributor or [] self.generator = generator self.icon = icon self.id = atom_id self.link = link or [] self.logo = logo self.rights = rights self.subtitle = subtitle self.title = title self.updated = updated self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} def SourceFromString(xml_string): return CreateClassFromXMLString(Source, xml_string) class Entry(FeedEntryParent): """The atom:entry element""" _tag = 'entry' _namespace = ATOM_NAMESPACE _children = FeedEntryParent._children.copy() _attributes = FeedEntryParent._attributes.copy() _children['{%s}content' % ATOM_NAMESPACE] = ('content', Content) _children['{%s}published' % ATOM_NAMESPACE] = ('published', Published) _children['{%s}source' % ATOM_NAMESPACE] = ('source', Source) _children['{%s}summary' % ATOM_NAMESPACE] = ('summary', Summary) _children['{%s}control' % APP_NAMESPACE] = ('control', Control) def __init__(self, author=None, category=None, content=None, contributor=None, atom_id=None, link=None, published=None, rights=None, source=None, summary=None, control=None, title=None, updated=None, extension_elements=None, extension_attributes=None, text=None): """Constructor for atom:entry Args: author: list A list of Author instances which belong to this class. category: list A list of Category instances content: Content The entry's Content contributor: list A list on Contributor instances id: Id The entry's Id element link: list A list of Link instances published: Published The entry's Published element rights: Rights The entry's Rights element source: Source the entry's source element summary: Summary the entry's summary element title: Title the entry's title element updated: Updated the entry's updated element control: The entry's app:control element which can be used to mark an entry as a draft which should not be publicly viewable. text: String The text contents of the element. This is the contents of the Entry's XML text node. (Example: <foo>This is the text</foo>) extension_elements: list A list of ExtensionElement instances which are children of this element. extension_attributes: dict A dictionary of strings which are the values for additional XML attributes of this element. """ self.author = author or [] self.category = category or [] self.content = content self.contributor = contributor or [] self.id = atom_id self.link = link or [] self.published = published self.rights = rights self.source = source self.summary = summary self.title = title self.updated = updated self.control = control self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} __init__ = v1_deprecated('Please use atom.data.Entry instead.')(__init__) def EntryFromString(xml_string): return CreateClassFromXMLString(Entry, xml_string) class Feed(Source): """The atom:feed element""" _tag = 'feed' _namespace = ATOM_NAMESPACE _children = Source._children.copy() _attributes = Source._attributes.copy() _children['{%s}entry' % ATOM_NAMESPACE] = ('entry', [Entry]) def __init__(self, author=None, category=None, contributor=None, generator=None, icon=None, atom_id=None, link=None, logo=None, rights=None, subtitle=None, title=None, updated=None, entry=None, text=None, extension_elements=None, extension_attributes=None): """Constructor for Source Args: author: list (optional) A list of Author instances which belong to this class. category: list (optional) A list of Category instances contributor: list (optional) A list on Contributor instances generator: Generator (optional) icon: Icon (optional) id: Id (optional) The entry's Id element link: list (optional) A list of Link instances logo: Logo (optional) rights: Rights (optional) The entry's Rights element subtitle: Subtitle (optional) The entry's subtitle element title: Title (optional) the entry's title element updated: Updated (optional) the entry's updated element entry: list (optional) A list of the Entry instances contained in the feed. text: String (optional) The text contents of the element. This is the contents of the Entry's XML text node. (Example: <foo>This is the text</foo>) extension_elements: list (optional) A list of ExtensionElement instances which are children of this element. extension_attributes: dict (optional) A dictionary of strings which are the values for additional XML attributes of this element. """ self.author = author or [] self.category = category or [] self.contributor = contributor or [] self.generator = generator self.icon = icon self.id = atom_id self.link = link or [] self.logo = logo self.rights = rights self.subtitle = subtitle self.title = title self.updated = updated self.entry = entry or [] self.text = text self.extension_elements = extension_elements or [] self.extension_attributes = extension_attributes or {} __init__ = v1_deprecated('Please use atom.data.Feed instead.')(__init__) def FeedFromString(xml_string): return CreateClassFromXMLString(Feed, xml_string) class ExtensionElement(object): """Represents extra XML elements contained in Atom classes.""" def __init__(self, tag, namespace=None, attributes=None, children=None, text=None): """Constructor for EtensionElement Args: namespace: string (optional) The XML namespace for this element. tag: string (optional) The tag (without the namespace qualifier) for this element. To reconstruct the full qualified name of the element, combine this tag with the namespace. attributes: dict (optinal) The attribute value string pairs for the XML attributes of this element. children: list (optional) A list of ExtensionElements which represent the XML child nodes of this element. """ self.namespace = namespace self.tag = tag self.attributes = attributes or {} self.children = children or [] self.text = text def ToString(self): element_tree = self._TransferToElementTree(ElementTree.Element('')) return ElementTree.tostring(element_tree, encoding="UTF-8") def _TransferToElementTree(self, element_tree): if self.tag is None: return None if self.namespace is not None: element_tree.tag = '{%s}%s' % (self.namespace, self.tag) else: element_tree.tag = self.tag for key, value in self.attributes.iteritems(): element_tree.attrib[key] = value for child in self.children: child._BecomeChildElement(element_tree) element_tree.text = self.text return element_tree def _BecomeChildElement(self, element_tree): """Converts this object into an etree element and adds it as a child node. Adds self to the ElementTree. This method is required to avoid verbose XML which constantly redefines the namespace. Args: element_tree: ElementTree._Element The element to which this object's XML will be added. """ new_element = ElementTree.Element('') element_tree.append(new_element) self._TransferToElementTree(new_element) def FindChildren(self, tag=None, namespace=None): """Searches child nodes for objects with the desired tag/namespace. Returns a list of extension elements within this object whose tag and/or namespace match those passed in. To find all children in a particular namespace, specify the namespace but not the tag name. If you specify only the tag, the result list may contain extension elements in multiple namespaces. Args: tag: str (optional) The desired tag namespace: str (optional) The desired namespace Returns: A list of elements whose tag and/or namespace match the parameters values """ results = [] if tag and namespace: for element in self.children: if element.tag == tag and element.namespace == namespace: results.append(element) elif tag and not namespace: for element in self.children: if element.tag == tag: results.append(element) elif namespace and not tag: for element in self.children: if element.namespace == namespace: results.append(element) else: for element in self.children: results.append(element) return results def ExtensionElementFromString(xml_string): element_tree = ElementTree.fromstring(xml_string) return _ExtensionElementFromElementTree(element_tree) def _ExtensionElementFromElementTree(element_tree): element_tag = element_tree.tag if '}' in element_tag: namespace = element_tag[1:element_tag.index('}')] tag = element_tag[element_tag.index('}')+1:] else: namespace = None tag = element_tag extension = ExtensionElement(namespace=namespace, tag=tag) for key, value in element_tree.attrib.iteritems(): extension.attributes[key] = value for child in element_tree: extension.children.append(_ExtensionElementFromElementTree(child)) extension.text = element_tree.text return extension def deprecated(warning=None): """Decorator to raise warning each time the function is called. Args: warning: The warning message to be displayed as a string (optinoal). """ warning = warning or '' # This closure is what is returned from the deprecated function. def mark_deprecated(f): # The deprecated_function wraps the actual call to f. def deprecated_function(*args, **kwargs): warnings.warn(warning, DeprecationWarning, stacklevel=2) return f(*args, **kwargs) # Preserve the original name to avoid masking all decorated functions as # 'deprecated_function' try: deprecated_function.func_name = f.func_name except TypeError: # Setting the func_name is not allowed in Python2.3. pass return deprecated_function return mark_deprecated ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������gdata-2.0.18/src/atom/client.py���������������������������������������������������������������������0000750�0364663�0011610�00000017475�12156622731�016334� 0����������������������������������������������������������������������������������������������������ustar �afshar��������������������������eng�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/env python # # Copyright (C) 2009 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. """AtomPubClient provides CRUD ops. in line with the Atom Publishing Protocol. """ __author__ = 'j.s@google.com (Jeff Scudder)' import atom.http_core class Error(Exception): pass class MissingHost(Error): pass class AtomPubClient(object): host = None auth_token = None ssl = False # Whether to force all requests over https xoauth_requestor_id = None def __init__(self, http_client=None, host=None, auth_token=None, source=None, xoauth_requestor_id=None, **kwargs): """Creates a new AtomPubClient instance. Args: source: The name of your application. http_client: An object capable of performing HTTP requests through a request method. This object is used to perform the request when the AtomPubClient's request method is called. Used to allow HTTP requests to be directed to a mock server, or use an alternate library instead of the default of httplib to make HTTP requests. host: str The default host name to use if a host is not specified in the requested URI. auth_token: An object which sets the HTTP Authorization header when its modify_request method is called. """ self.http_client = http_client or atom.http_core.ProxiedHttpClient() if host is not None: self.host = host if auth_token is not None: self.auth_token = auth_token self.xoauth_requestor_id = xoauth_requestor_id self.source = source def request(self, method=None, uri=None, auth_token=None, http_request=None, **kwargs): """Performs an HTTP request to the server indicated. Uses the http_client instance to make the request. Args: method: The HTTP method as a string, usually one of 'GET', 'POST', 'PUT', or 'DELETE' uri: The URI desired as a string or atom.http_core.Uri. http_request: auth_token: An authorization token object whose modify_request method sets the HTTP Authorization header. Returns: The results of calling self.http_client.request. With the default http_client, this is an HTTP response object. """ # Modify the request based on the AtomPubClient settings and parameters # passed in to the request. http_request = self.modify_request(http_request) if isinstance(uri, (str, unicode)): uri = atom.http_core.Uri.parse_uri(uri) if uri is not None: uri.modify_request(http_request) if isinstance(method, (str, unicode)): http_request.method = method # Any unrecognized arguments are assumed to be capable of modifying the # HTTP request. for name, value in kwargs.iteritems(): if value is not None: if hasattr(value, 'modify_request'): value.modify_request(http_request) else: http_request.uri.query[name] = str(value) # Default to an http request if the protocol scheme is not set. if http_request.uri.scheme is None: http_request.uri.scheme = 'http' # Override scheme. Force requests over https. if self.ssl: http_request.uri.scheme = 'https' if http_request.uri.path is None: http_request.uri.path = '/' # Add the Authorization header at the very end. The Authorization header # value may need to be calculated using information in the request. if auth_token: auth_token.modify_request(http_request) elif self.auth_token: self.auth_token.modify_request(http_request) # Check to make sure there is a host in the http_request. if http_request.uri.host is None: raise MissingHost('No host provided in request %s %s' % ( http_request.method, str(http_request.uri))) # Perform the fully specified request using the http_client instance. # Sends the request to the server and returns the server's response. return self.http_client.request(http_request) Request = request def get(self, uri=None, auth_token=None, http_request=None, **kwargs): """Performs a request using the GET method, returns an HTTP response.""" return self.request(method='GET', uri=uri, auth_token=auth_token, http_request=http_request, **kwargs) Get = get def post(self, uri=None, data=None, auth_token=None, http_request=None, **kwargs): """Sends data using the POST method, returns an HTTP response.""" return self.request(method='POST', uri=uri, auth_token=auth_token, http_request=http_request, data=data, **kwargs) Post = post def put(self, uri=None, data=None, auth_token=None, http_request=None, **kwargs): """Sends data using the PUT method, returns an HTTP response.""" return self.request(method='PUT', uri=uri, auth_token=auth_token, http_request=http_request, data=data, **kwargs) Put = put def delete(self, uri=None, auth_token=None, http_request=None, **kwargs): """Performs a request using the DELETE method, returns an HTTP response.""" return self.request(method='DELETE', uri=uri, auth_token=auth_token, http_request=http_request, **kwargs) Delete = delete def modify_request(self, http_request): """Changes the HTTP request before sending it to the server. Sets the User-Agent HTTP header and fills in the HTTP host portion of the URL if one was not included in the request (for this it uses the self.host member if one is set). This method is called in self.request. Args: http_request: An atom.http_core.HttpRequest() (optional) If one is not provided, a new HttpRequest is instantiated. Returns: An atom.http_core.HttpRequest() with the User-Agent header set and if this client has a value in its host member, the host in the request URL is set. """ if http_request is None: http_request = atom.http_core.HttpRequest() if self.host is not None and http_request.uri.host is None: http_request.uri.host = self.host if self.xoauth_requestor_id is not None: http_request.uri.query['xoauth_requestor_id'] = self.xoauth_requestor_id # Set the user agent header for logging purposes. if self.source: http_request.headers['User-Agent'] = '%s gdata-py/2.0.18' % self.source else: http_request.headers['User-Agent'] = 'gdata-py/2.0.17' return http_request ModifyRequest = modify_request class CustomHeaders(object): """Add custom headers to an http_request. Usage: >>> custom_headers = atom.client.CustomHeaders(header1='value1', header2='value2') >>> client.get(uri, custom_headers=custom_headers) """ def __init__(self, **kwargs): """Creates a CustomHeaders instance. Initialize the headers dictionary with the arguments list. """ self.headers = kwargs def modify_request(self, http_request): """Changes the HTTP request before sending it to the server. Adds the custom headers to the HTTP request. Args: http_request: An atom.http_core.HttpRequest(). Returns: An atom.http_core.HttpRequest() with the added custom headers. """ for name, value in self.headers.iteritems(): if value is not None: http_request.headers[name] = value return http_request ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������gdata-2.0.18/src/atom/mock_service.py���������������������������������������������������������������0000750�0364663�0011610�00000024156�12156622362�017521� 0����������������������������������������������������������������������������������������������������ustar �afshar��������������������������eng�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/python # # Copyright (C) 2008 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. """MockService provides CRUD ops. for mocking calls to AtomPub services. MockService: Exposes the publicly used methods of AtomService to provide a mock interface which can be used in unit tests. """ import atom.service import pickle __author__ = 'api.jscudder (Jeffrey Scudder)' # Recordings contains pairings of HTTP MockRequest objects with MockHttpResponse objects. recordings = [] # If set, the mock service HttpRequest are actually made through this object. real_request_handler = None def ConcealValueWithSha(source): import sha return sha.new(source[:-5]).hexdigest() def DumpRecordings(conceal_func=ConcealValueWithSha): if conceal_func: for recording_pair in recordings: recording_pair[0].ConcealSecrets(conceal_func) return pickle.dumps(recordings) def LoadRecordings(recordings_file_or_string): if isinstance(recordings_file_or_string, str): atom.mock_service.recordings = pickle.loads(recordings_file_or_string) elif hasattr(recordings_file_or_string, 'read'): atom.mock_service.recordings = pickle.loads( recordings_file_or_string.read()) def HttpRequest(service, operation, data, uri, extra_headers=None, url_params=None, escape_params=True, content_type='application/atom+xml'): """Simulates an HTTP call to the server, makes an actual HTTP request if real_request_handler is set. This function operates in two different modes depending on if real_request_handler is set or not. If real_request_handler is not set, HttpRequest will look in this module's recordings list to find a response which matches the parameters in the function call. If real_request_handler is set, this function will call real_request_handler.HttpRequest, add the response to the recordings list, and respond with the actual response. Args: service: atom.AtomService object which contains some of the parameters needed to make the request. The following members are used to construct the HTTP call: server (str), additional_headers (dict), port (int), and ssl (bool). operation: str The HTTP operation to be performed. This is usually one of 'GET', 'POST', 'PUT', or 'DELETE' data: ElementTree, filestream, list of parts, or other object which can be converted to a string. Should be set to None when performing a GET or PUT. If data is a file-like object which can be read, this method will read a chunk of 100K bytes at a time and send them. If the data is a list of parts to be sent, each part will be evaluated and sent. uri: The beginning of the URL to which the request should be sent. Examples: '/', '/base/feeds/snippets', '/m8/feeds/contacts/default/base' extra_headers: dict of strings. HTTP headers which should be sent in the request. These headers are in addition to those stored in service.additional_headers. url_params: dict of strings. Key value pairs to be added to the URL as URL parameters. For example {'foo':'bar', 'test':'param'} will become ?foo=bar&test=param. escape_params: bool default True. If true, the keys and values in url_params will be URL escaped when the form is constructed (Special characters converted to %XX form.) content_type: str The MIME type for the data being sent. Defaults to 'application/atom+xml', this is only used if data is set. """ full_uri = atom.service.BuildUri(uri, url_params, escape_params) (server, port, ssl, uri) = atom.service.ProcessUrl(service, uri) current_request = MockRequest(operation, full_uri, host=server, ssl=ssl, data=data, extra_headers=extra_headers, url_params=url_params, escape_params=escape_params, content_type=content_type) # If the request handler is set, we should actually make the request using # the request handler and record the response to replay later. if real_request_handler: response = real_request_handler.HttpRequest(service, operation, data, uri, extra_headers=extra_headers, url_params=url_params, escape_params=escape_params, content_type=content_type) # TODO: need to copy the HTTP headers from the real response into the # recorded_response. recorded_response = MockHttpResponse(body=response.read(), status=response.status, reason=response.reason) # Insert a tuple which maps the request to the response object returned # when making an HTTP call using the real_request_handler. recordings.append((current_request, recorded_response)) return recorded_response else: # Look through available recordings to see if one matches the current # request. for request_response_pair in recordings: if request_response_pair[0].IsMatch(current_request): return request_response_pair[1] return None class MockRequest(object): """Represents a request made to an AtomPub server. These objects are used to determine if a client request matches a recorded HTTP request to determine what the mock server's response will be. """ def __init__(self, operation, uri, host=None, ssl=False, port=None, data=None, extra_headers=None, url_params=None, escape_params=True, content_type='application/atom+xml'): """Constructor for a MockRequest Args: operation: str One of 'GET', 'POST', 'PUT', or 'DELETE' this is the HTTP operation requested on the resource. uri: str The URL describing the resource to be modified or feed to be retrieved. This should include the protocol (http/https) and the host (aka domain). For example, these are some valud full_uris: 'http://example.com', 'https://www.google.com/accounts/ClientLogin' host: str (optional) The server name which will be placed at the beginning of the URL if the uri parameter does not begin with 'http'. Examples include 'example.com', 'www.google.com', 'www.blogger.com'. ssl: boolean (optional) If true, the request URL will begin with https instead of http. data: ElementTree, filestream, list of parts, or other object which can be converted to a string. (optional) Should be set to None when performing a GET or PUT. If data is a file-like object which can be read, the constructor will read the entire file into memory. If the data is a list of parts to be sent, each part will be evaluated and stored. extra_headers: dict (optional) HTTP headers included in the request. url_params: dict (optional) Key value pairs which should be added to the URL as URL parameters in the request. For example uri='/', url_parameters={'foo':'1','bar':'2'} could become '/?foo=1&bar=2'. escape_params: boolean (optional) Perform URL escaping on the keys and values specified in url_params. Defaults to True. content_type: str (optional) Provides the MIME type of the data being sent. """ self.operation = operation self.uri = _ConstructFullUrlBase(uri, host=host, ssl=ssl) self.data = data self.extra_headers = extra_headers self.url_params = url_params or {} self.escape_params = escape_params self.content_type = content_type def ConcealSecrets(self, conceal_func): """Conceal secret data in this request.""" if self.extra_headers.has_key('Authorization'): self.extra_headers['Authorization'] = conceal_func( self.extra_headers['Authorization']) def IsMatch(self, other_request): """Check to see if the other_request is equivalent to this request. Used to determine if a recording matches an incoming request so that a recorded response should be sent to the client. The matching is not exact, only the operation and URL are examined currently. Args: other_request: MockRequest The request which we want to check this (self) MockRequest against to see if they are equivalent. """ # More accurate matching logic will likely be required. return (self.operation == other_request.operation and self.uri == other_request.uri) def _ConstructFullUrlBase(uri, host=None, ssl=False): """Puts URL components into the form http(s)://full.host.strinf/uri/path Used to construct a roughly canonical URL so that URLs which begin with 'http://example.com/' can be compared to a uri of '/' when the host is set to 'example.com' If the uri contains 'http://host' already, the host and ssl parameters are ignored. Args: uri: str The path component of the URL, examples include '/' host: str (optional) The host name which should prepend the URL. Example: 'example.com' ssl: boolean (optional) If true, the returned URL will begin with https instead of http. Returns: String which has the form http(s)://example.com/uri/string/contents """ if uri.startswith('http'): return uri if ssl: return 'https://%s%s' % (host, uri) else: return 'http://%s%s' % (host, uri) class MockHttpResponse(object): """Returned from MockService crud methods as the server's response.""" def __init__(self, body=None, status=None, reason=None, headers=None): """Construct a mock HTTPResponse and set members. Args: body: str (optional) The HTTP body of the server's response. status: int (optional) reason: str (optional) headers: dict (optional) """ self.body = body self.status = status self.reason = reason self.headers = headers or {} def read(self): return self.body def getheader(self, header_name): return self.headers[header_name] ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������gdata-2.0.18/src/atom/auth.py�����������������������������������������������������������������������0000640�0364663�0011610�00000002257�12156622362�016005� 0����������������������������������������������������������������������������������������������������ustar �afshar��������������������������eng�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/env python # # Copyright (C) 2009 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. # This module is used for version 2 of the Google Data APIs. __author__ = 'j.s@google.com (Jeff Scudder)' import base64 class BasicAuth(object): """Sets the Authorization header as defined in RFC1945""" def __init__(self, user_id, password): self.basic_cookie = base64.encodestring( '%s:%s' % (user_id, password)).strip() def modify_request(self, http_request): http_request.headers['Authorization'] = 'Basic %s' % self.basic_cookie ModifyRequest = modify_request class NoAuth(object): def modify_request(self, http_request): pass �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������gdata-2.0.18/src/atom/http.py�����������������������������������������������������������������������0000640�0364663�0011610�00000030777�12156622362�016033� 0����������������������������������������������������������������������������������������������������ustar �afshar��������������������������eng�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/python # # Copyright (C) 2008 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. """HttpClients in this module use httplib to make HTTP requests. This module make HTTP requests based on httplib, but there are environments in which an httplib based approach will not work (if running in Google App Engine for example). In those cases, higher level classes (like AtomService and GDataService) can swap out the HttpClient to transparently use a different mechanism for making HTTP requests. HttpClient: Contains a request method which performs an HTTP call to the server. ProxiedHttpClient: Contains a request method which connects to a proxy using settings stored in operating system environment variables then performs an HTTP call to the endpoint server. """ __author__ = 'api.jscudder (Jeff Scudder)' import types import os import httplib import atom.url import atom.http_interface import socket import base64 import atom.http_core ssl_imported = False ssl = None try: import ssl ssl_imported = True except ImportError: pass class ProxyError(atom.http_interface.Error): pass class TestConfigurationError(Exception): pass DEFAULT_CONTENT_TYPE = 'application/atom+xml' class HttpClient(atom.http_interface.GenericHttpClient): # Added to allow old v1 HttpClient objects to use the new # http_code.HttpClient. Used in unit tests to inject a mock client. v2_http_client = None def __init__(self, headers=None): self.debug = False self.headers = headers or {} def request(self, operation, url, data=None, headers=None): """Performs an HTTP call to the server, supports GET, POST, PUT, and DELETE. Usage example, perform and HTTP GET on http://www.google.com/: import atom.http client = atom.http.HttpClient() http_response = client.request('GET', 'http://www.google.com/') Args: operation: str The HTTP operation to be performed. This is usually one of 'GET', 'POST', 'PUT', or 'DELETE' data: filestream, list of parts, or other object which can be converted to a string. Should be set to None when performing a GET or DELETE. If data is a file-like object which can be read, this method will read a chunk of 100K bytes at a time and send them. If the data is a list of parts to be sent, each part will be evaluated and sent. url: The full URL to which the request should be sent. Can be a string or atom.url.Url. headers: dict of strings. HTTP headers which should be sent in the request. """ all_headers = self.headers.copy() if headers: all_headers.update(headers) # If the list of headers does not include a Content-Length, attempt to # calculate it based on the data object. if data and 'Content-Length' not in all_headers: if isinstance(data, types.StringTypes): all_headers['Content-Length'] = str(len(data)) else: raise atom.http_interface.ContentLengthRequired('Unable to calculate ' 'the length of the data parameter. Specify a value for ' 'Content-Length') # Set the content type to the default value if none was set. if 'Content-Type' not in all_headers: all_headers['Content-Type'] = DEFAULT_CONTENT_TYPE if self.v2_http_client is not None: http_request = atom.http_core.HttpRequest(method=operation) atom.http_core.Uri.parse_uri(str(url)).modify_request(http_request) http_request.headers = all_headers if data: http_request._body_parts.append(data) return self.v2_http_client.request(http_request=http_request) if not isinstance(url, atom.url.Url): if isinstance(url, types.StringTypes): url = atom.url.parse_url(url) else: raise atom.http_interface.UnparsableUrlObject('Unable to parse url ' 'parameter because it was not a string or atom.url.Url') connection = self._prepare_connection(url, all_headers) if self.debug: connection.debuglevel = 1 connection.putrequest(operation, self._get_access_url(url), skip_host=True) if url.port is not None: connection.putheader('Host', '%s:%s' % (url.host, url.port)) else: connection.putheader('Host', url.host) # Overcome a bug in Python 2.4 and 2.5 # httplib.HTTPConnection.putrequest adding # HTTP request header 'Host: www.google.com:443' instead of # 'Host: www.google.com', and thus resulting the error message # 'Token invalid - AuthSub token has wrong scope' in the HTTP response. if (url.protocol == 'https' and int(url.port or 443) == 443 and hasattr(connection, '_buffer') and isinstance(connection._buffer, list)): header_line = 'Host: %s:443' % url.host replacement_header_line = 'Host: %s' % url.host try: connection._buffer[connection._buffer.index(header_line)] = ( replacement_header_line) except ValueError: # header_line missing from connection._buffer pass # Send the HTTP headers. for header_name in all_headers: connection.putheader(header_name, all_headers[header_name]) connection.endheaders() # If there is data, send it in the request. if data: if isinstance(data, list): for data_part in data: _send_data_part(data_part, connection) else: _send_data_part(data, connection) # Return the HTTP Response from the server. return connection.getresponse() def _prepare_connection(self, url, headers): if not isinstance(url, atom.url.Url): if isinstance(url, types.StringTypes): url = atom.url.parse_url(url) else: raise atom.http_interface.UnparsableUrlObject('Unable to parse url ' 'parameter because it was not a string or atom.url.Url') if url.protocol == 'https': if not url.port: return httplib.HTTPSConnection(url.host) return httplib.HTTPSConnection(url.host, int(url.port)) else: if not url.port: return httplib.HTTPConnection(url.host) return httplib.HTTPConnection(url.host, int(url.port)) def _get_access_url(self, url): return url.to_string() class ProxiedHttpClient(HttpClient): """Performs an HTTP request through a proxy. The proxy settings are obtained from enviroment variables. The URL of the proxy server is assumed to be stored in the environment variables 'https_proxy' and 'http_proxy' respectively. If the proxy server requires a Basic Auth authorization header, the username and password are expected to be in the 'proxy-username' or 'proxy_username' variable and the 'proxy-password' or 'proxy_password' variable, or in 'http_proxy' or 'https_proxy' as "protocol://[username:password@]host:port". After connecting to the proxy server, the request is completed as in HttpClient.request. """ def _prepare_connection(self, url, headers): proxy_settings = os.environ.get('%s_proxy' % url.protocol) if not proxy_settings: # The request was HTTP or HTTPS, but there was no appropriate proxy set. return HttpClient._prepare_connection(self, url, headers) else: proxy_auth = _get_proxy_auth(proxy_settings) proxy_netloc = _get_proxy_net_location(proxy_settings) if url.protocol == 'https': # Set any proxy auth headers if proxy_auth: proxy_auth = 'Proxy-authorization: %s' % proxy_auth # Construct the proxy connect command. port = url.port if not port: port = '443' proxy_connect = 'CONNECT %s:%s HTTP/1.0\r\n' % (url.host, port) # Set the user agent to send to the proxy if headers and 'User-Agent' in headers: user_agent = 'User-Agent: %s\r\n' % (headers['User-Agent']) else: user_agent = 'User-Agent: python\r\n' proxy_pieces = '%s%s%s\r\n' % (proxy_connect, proxy_auth, user_agent) # Find the proxy host and port. proxy_url = atom.url.parse_url(proxy_netloc) if not proxy_url.port: proxy_url.port = '80' # Connect to the proxy server, very simple recv and error checking p_sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM) p_sock.connect((proxy_url.host, int(proxy_url.port))) p_sock.sendall(proxy_pieces) response = '' # Wait for the full response. while response.find("\r\n\r\n") == -1: response += p_sock.recv(8192) p_status = response.split()[1] if p_status != str(200): raise ProxyError('Error status=%s' % str(p_status)) # Trivial setup for ssl socket. sslobj = None if ssl_imported: sslobj = ssl.wrap_socket(p_sock, None, None) else: sock_ssl = socket.ssl(p_sock, None, None) sslobj = httplib.FakeSocket(p_sock, sock_ssl) # Initalize httplib and replace with the proxy socket. connection = httplib.HTTPConnection(proxy_url.host) connection.sock = sslobj return connection else: # If protocol was not https. # Find the proxy host and port. proxy_url = atom.url.parse_url(proxy_netloc) if not proxy_url.port: proxy_url.port = '80' if proxy_auth: headers['Proxy-Authorization'] = proxy_auth.strip() return httplib.HTTPConnection(proxy_url.host, int(proxy_url.port)) def _get_access_url(self, url): return url.to_string() def _get_proxy_auth(proxy_settings): """Returns proxy authentication string for header. Will check environment variables for proxy authentication info, starting with proxy(_/-)username and proxy(_/-)password before checking the given proxy_settings for a [protocol://]username:password@host[:port] string. Args: proxy_settings: String from http_proxy or https_proxy environment variable. Returns: Authentication string for proxy, or empty string if no proxy username was found. """ proxy_username = None proxy_password = None proxy_username = os.environ.get('proxy-username') if not proxy_username: proxy_username = os.environ.get('proxy_username') proxy_password = os.environ.get('proxy-password') if not proxy_password: proxy_password = os.environ.get('proxy_password') if not proxy_username: if '@' in proxy_settings: protocol_and_proxy_auth = proxy_settings.split('@')[0].split(':') if len(protocol_and_proxy_auth) == 3: # 3 elements means we have [<protocol>, //<user>, <password>] proxy_username = protocol_and_proxy_auth[1].lstrip('/') proxy_password = protocol_and_proxy_auth[2] elif len(protocol_and_proxy_auth) == 2: # 2 elements means we have [<user>, <password>] proxy_username = protocol_and_proxy_auth[0] proxy_password = protocol_and_proxy_auth[1] if proxy_username: user_auth = base64.encodestring('%s:%s' % (proxy_username, proxy_password)) return 'Basic %s\r\n' % (user_auth.strip()) else: return '' def _get_proxy_net_location(proxy_settings): """Returns proxy host and port. Args: proxy_settings: String from http_proxy or https_proxy environment variable. Must be in the form of protocol://[username:password@]host:port Returns: String in the form of protocol://host:port """ if '@' in proxy_settings: protocol = proxy_settings.split(':')[0] netloc = proxy_settings.split('@')[1] return '%s://%s' % (protocol, netloc) else: return proxy_settings def _send_data_part(data, connection): if isinstance(data, types.StringTypes): connection.send(data) return # Check to see if data is a file-like object that has a read method. elif hasattr(data, 'read'): # Read the file and send it a chunk at a time. while 1: binarydata = data.read(100000) if binarydata == '': break connection.send(binarydata) return else: # The data object was not a file. # Try to convert to a string and send the data. connection.send(str(data)) return �gdata-2.0.18/src/atom/url.py������������������������������������������������������������������������0000640�0364663�0011610�00000010265�12156622362�015644� 0����������������������������������������������������������������������������������������������������ustar �afshar��������������������������eng�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/python # # Copyright (C) 2008 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. __author__ = 'api.jscudder (Jeff Scudder)' import urlparse import urllib DEFAULT_PROTOCOL = 'http' DEFAULT_PORT = 80 def parse_url(url_string): """Creates a Url object which corresponds to the URL string. This method can accept partial URLs, but it will leave missing members of the Url unset. """ parts = urlparse.urlparse(url_string) url = Url() if parts[0]: url.protocol = parts[0] if parts[1]: host_parts = parts[1].split(':') if host_parts[0]: url.host = host_parts[0] if len(host_parts) > 1: url.port = host_parts[1] if parts[2]: url.path = parts[2] if parts[4]: param_pairs = parts[4].split('&') for pair in param_pairs: pair_parts = pair.split('=') if len(pair_parts) > 1: url.params[urllib.unquote_plus(pair_parts[0])] = ( urllib.unquote_plus(pair_parts[1])) elif len(pair_parts) == 1: url.params[urllib.unquote_plus(pair_parts[0])] = None return url class Url(object): """Represents a URL and implements comparison logic. URL strings which are not identical can still be equivalent, so this object provides a better interface for comparing and manipulating URLs than strings. URL parameters are represented as a dictionary of strings, and defaults are used for the protocol (http) and port (80) if not provided. """ def __init__(self, protocol=None, host=None, port=None, path=None, params=None): self.protocol = protocol self.host = host self.port = port self.path = path self.params = params or {} def to_string(self): url_parts = ['', '', '', '', '', ''] if self.protocol: url_parts[0] = self.protocol if self.host: if self.port: url_parts[1] = ':'.join((self.host, str(self.port))) else: url_parts[1] = self.host if self.path: url_parts[2] = self.path if self.params: url_parts[4] = self.get_param_string() return urlparse.urlunparse(url_parts) def get_param_string(self): param_pairs = [] for key, value in self.params.iteritems(): param_pairs.append('='.join((urllib.quote_plus(key), urllib.quote_plus(str(value))))) return '&'.join(param_pairs) def get_request_uri(self): """Returns the path with the parameters escaped and appended.""" param_string = self.get_param_string() if param_string: return '?'.join([self.path, param_string]) else: return self.path def __cmp__(self, other): if not isinstance(other, Url): return cmp(self.to_string(), str(other)) difference = 0 # Compare the protocol if self.protocol and other.protocol: difference = cmp(self.protocol, other.protocol) elif self.protocol and not other.protocol: difference = cmp(self.protocol, DEFAULT_PROTOCOL) elif not self.protocol and other.protocol: difference = cmp(DEFAULT_PROTOCOL, other.protocol) if difference != 0: return difference # Compare the host difference = cmp(self.host, other.host) if difference != 0: return difference # Compare the port if self.port and other.port: difference = cmp(self.port, other.port) elif self.port and not other.port: difference = cmp(self.port, DEFAULT_PORT) elif not self.port and other.port: difference = cmp(DEFAULT_PORT, other.port) if difference != 0: return difference # Compare the path difference = cmp(self.path, other.path) if difference != 0: return difference # Compare the parameters return cmp(self.params, other.params) def __str__(self): return self.to_string() �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������gdata-2.0.18/src/atom/http_interface.py�������������������������������������������������������������0000640�0364663�0011610�00000012076�12156622621�020041� 0����������������������������������������������������������������������������������������������������ustar �afshar��������������������������eng�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/python # # Copyright (C) 2008 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. """This module provides a common interface for all HTTP requests. HttpResponse: Represents the server's response to an HTTP request. Provides an interface identical to httplib.HTTPResponse which is the response expected from higher level classes which use HttpClient.request. GenericHttpClient: Provides an interface (superclass) for an object responsible for making HTTP requests. Subclasses of this object are used in AtomService and GDataService to make requests to the server. By changing the http_client member object, the AtomService is able to make HTTP requests using different logic (for example, when running on Google App Engine, the http_client makes requests using the App Engine urlfetch API). """ __author__ = 'api.jscudder (Jeff Scudder)' import StringIO USER_AGENT = '%s GData-Python/2.0.18' class Error(Exception): pass class UnparsableUrlObject(Error): pass class ContentLengthRequired(Error): pass class HttpResponse(object): def __init__(self, body=None, status=None, reason=None, headers=None): """Constructor for an HttpResponse object. HttpResponse represents the server's response to an HTTP request from the client. The HttpClient.request method returns a httplib.HTTPResponse object and this HttpResponse class is designed to mirror the interface exposed by httplib.HTTPResponse. Args: body: A file like object, with a read() method. The body could also be a string, and the constructor will wrap it so that HttpResponse.read(self) will return the full string. status: The HTTP status code as an int. Example: 200, 201, 404. reason: The HTTP status message which follows the code. Example: OK, Created, Not Found headers: A dictionary containing the HTTP headers in the server's response. A common header in the response is Content-Length. """ if body: if hasattr(body, 'read'): self._body = body else: self._body = StringIO.StringIO(body) else: self._body = None if status is not None: self.status = int(status) else: self.status = None self.reason = reason self._headers = headers or {} def getheader(self, name, default=None): if name in self._headers: return self._headers[name] else: return default def read(self, amt=None): if not amt: return self._body.read() else: return self._body.read(amt) class GenericHttpClient(object): debug = False def __init__(self, http_client, headers=None): """ Args: http_client: An object which provides a request method to make an HTTP request. The request method in GenericHttpClient performs a call-through to the contained HTTP client object. headers: A dictionary containing HTTP headers which should be included in every HTTP request. Common persistent headers include 'User-Agent'. """ self.http_client = http_client self.headers = headers or {} def request(self, operation, url, data=None, headers=None): all_headers = self.headers.copy() if headers: all_headers.update(headers) return self.http_client.request(operation, url, data=data, headers=all_headers) def get(self, url, headers=None): return self.request('GET', url, headers=headers) def post(self, url, data, headers=None): return self.request('POST', url, data=data, headers=headers) def put(self, url, data, headers=None): return self.request('PUT', url, data=data, headers=headers) def delete(self, url, headers=None): return self.request('DELETE', url, headers=headers) class GenericToken(object): """Represents an Authorization token to be added to HTTP requests. Some Authorization headers included calculated fields (digital signatures for example) which are based on the parameters of the HTTP request. Therefore the token is responsible for signing the request and adding the Authorization header. """ def perform_request(self, http_client, operation, url, data=None, headers=None): """For the GenericToken, no Authorization token is set.""" return http_client.request(operation, url, data=data, headers=headers) def valid_for_scope(self, url): """Tells the caller if the token authorizes access to the desired URL. Since the generic token doesn't add an auth header, it is not valid for any scope. """ return False ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������gdata-2.0.18/src/atom/token_store.py����������������������������������������������������������������0000640�0364663�0011610�00000007720�12156622362�017400� 0����������������������������������������������������������������������������������������������������ustar �afshar��������������������������eng�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/python # # Copyright (C) 2008 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. """This module provides a TokenStore class which is designed to manage auth tokens required for different services. Each token is valid for a set of scopes which is the start of a URL. An HTTP client will use a token store to find a valid Authorization header to send in requests to the specified URL. If the HTTP client determines that a token has expired or been revoked, it can remove the token from the store so that it will not be used in future requests. """ __author__ = 'api.jscudder (Jeff Scudder)' import atom.http_interface import atom.url SCOPE_ALL = 'http' class TokenStore(object): """Manages Authorization tokens which will be sent in HTTP headers.""" def __init__(self, scoped_tokens=None): self._tokens = scoped_tokens or {} def add_token(self, token): """Adds a new token to the store (replaces tokens with the same scope). Args: token: A subclass of http_interface.GenericToken. The token object is responsible for adding the Authorization header to the HTTP request. The scopes defined in the token are used to determine if the token is valid for a requested scope when find_token is called. Returns: True if the token was added, False if the token was not added becase no scopes were provided. """ if not hasattr(token, 'scopes') or not token.scopes: return False for scope in token.scopes: self._tokens[str(scope)] = token return True def find_token(self, url): """Selects an Authorization header token which can be used for the URL. Args: url: str or atom.url.Url or a list containing the same. The URL which is going to be requested. All tokens are examined to see if any scopes begin match the beginning of the URL. The first match found is returned. Returns: The token object which should execute the HTTP request. If there was no token for the url (the url did not begin with any of the token scopes available), then the atom.http_interface.GenericToken will be returned because the GenericToken calls through to the http client without adding an Authorization header. """ if url is None: return None if isinstance(url, (str, unicode)): url = atom.url.parse_url(url) if url in self._tokens: token = self._tokens[url] if token.valid_for_scope(url): return token else: del self._tokens[url] for scope, token in self._tokens.iteritems(): if token.valid_for_scope(url): return token return atom.http_interface.GenericToken() def remove_token(self, token): """Removes the token from the token_store. This method is used when a token is determined to be invalid. If the token was found by find_token, but resulted in a 401 or 403 error stating that the token was invlid, then the token should be removed to prevent future use. Returns: True if a token was found and then removed from the token store. False if the token was not in the TokenStore. """ token_found = False scopes_to_delete = [] for scope, stored_token in self._tokens.iteritems(): if stored_token == token: scopes_to_delete.append(scope) token_found = True for scope in scopes_to_delete: del self._tokens[scope] return token_found def remove_all_tokens(self): self._tokens = {} ������������������������������������������������gdata-2.0.18/src/atom/http_core.py������������������������������������������������������������������0000640�0364663�0011610�00000047030�12156622362�017031� 0����������������������������������������������������������������������������������������������������ustar �afshar��������������������������eng�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/env python # # Copyright (C) 2009 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. # This module is used for version 2 of the Google Data APIs. # TODO: add proxy handling. __author__ = 'j.s@google.com (Jeff Scudder)' import os import StringIO import urlparse import urllib import httplib ssl = None try: import ssl except ImportError: pass class Error(Exception): pass class UnknownSize(Error): pass class ProxyError(Error): pass MIME_BOUNDARY = 'END_OF_PART' def get_headers(http_response): """Retrieves all HTTP headers from an HTTP response from the server. This method is provided for backwards compatibility for Python2.2 and 2.3. The httplib.HTTPResponse object in 2.2 and 2.3 does not have a getheaders method so this function will use getheaders if available, but if not it will retrieve a few using getheader. """ if hasattr(http_response, 'getheaders'): return http_response.getheaders() else: headers = [] for header in ( 'location', 'content-type', 'content-length', 'age', 'allow', 'cache-control', 'content-location', 'content-encoding', 'date', 'etag', 'expires', 'last-modified', 'pragma', 'server', 'set-cookie', 'transfer-encoding', 'vary', 'via', 'warning', 'www-authenticate', 'gdata-version'): value = http_response.getheader(header, None) if value is not None: headers.append((header, value)) return headers class HttpRequest(object): """Contains all of the parameters for an HTTP 1.1 request. The HTTP headers are represented by a dictionary, and it is the responsibility of the user to ensure that duplicate field names are combined into one header value according to the rules in section 4.2 of RFC 2616. """ method = None uri = None def __init__(self, uri=None, method=None, headers=None): """Construct an HTTP request. Args: uri: The full path or partial path as a Uri object or a string. method: The HTTP method for the request, examples include 'GET', 'POST', etc. headers: dict of strings The HTTP headers to include in the request. """ self.headers = headers or {} self._body_parts = [] if method is not None: self.method = method if isinstance(uri, (str, unicode)): uri = Uri.parse_uri(uri) self.uri = uri or Uri() def add_body_part(self, data, mime_type, size=None): """Adds data to the HTTP request body. If more than one part is added, this is assumed to be a mime-multipart request. This method is designed to create MIME 1.0 requests as specified in RFC 1341. Args: data: str or a file-like object containing a part of the request body. mime_type: str The MIME type describing the data size: int Required if the data is a file like object. If the data is a string, the size is calculated so this parameter is ignored. """ if isinstance(data, str): size = len(data) if size is None: # TODO: support chunked transfer if some of the body is of unknown size. raise UnknownSize('Each part of the body must have a known size.') if 'Content-Length' in self.headers: content_length = int(self.headers['Content-Length']) else: content_length = 0 # If this is the first part added to the body, then this is not a multipart # request. if len(self._body_parts) == 0: self.headers['Content-Type'] = mime_type content_length = size self._body_parts.append(data) elif len(self._body_parts) == 1: # This is the first member in a mime-multipart request, so change the # _body_parts list to indicate a multipart payload. self._body_parts.insert(0, 'Media multipart posting') boundary_string = '\r\n--%s\r\n' % (MIME_BOUNDARY,) content_length += len(boundary_string) + size self._body_parts.insert(1, boundary_string) content_length += len('Media multipart posting') # Put the content type of the first part of the body into the multipart # payload. original_type_string = 'Content-Type: %s\r\n\r\n' % ( self.headers['Content-Type'],) self._body_parts.insert(2, original_type_string) content_length += len(original_type_string) boundary_string = '\r\n--%s\r\n' % (MIME_BOUNDARY,) self._body_parts.append(boundary_string) content_length += len(boundary_string) # Change the headers to indicate this is now a mime multipart request. self.headers['Content-Type'] = 'multipart/related; boundary="%s"' % ( MIME_BOUNDARY,) self.headers['MIME-version'] = '1.0' # Include the mime type of this part. type_string = 'Content-Type: %s\r\n\r\n' % (mime_type) self._body_parts.append(type_string) content_length += len(type_string) self._body_parts.append(data) ending_boundary_string = '\r\n--%s--' % (MIME_BOUNDARY,) self._body_parts.append(ending_boundary_string) content_length += len(ending_boundary_string) else: # This is a mime multipart request. boundary_string = '\r\n--%s\r\n' % (MIME_BOUNDARY,) self._body_parts.insert(-1, boundary_string) content_length += len(boundary_string) + size # Include the mime type of this part. type_string = 'Content-Type: %s\r\n\r\n' % (mime_type) self._body_parts.insert(-1, type_string) content_length += len(type_string) self._body_parts.insert(-1, data) self.headers['Content-Length'] = str(content_length) # I could add an "append_to_body_part" method as well. AddBodyPart = add_body_part def add_form_inputs(self, form_data, mime_type='application/x-www-form-urlencoded'): """Form-encodes and adds data to the request body. Args: form_data: dict or sequnce or two member tuples which contains the form keys and values. mime_type: str The MIME type of the form data being sent. Defaults to 'application/x-www-form-urlencoded'. """ body = urllib.urlencode(form_data) self.add_body_part(body, mime_type) AddFormInputs = add_form_inputs def _copy(self): """Creates a deep copy of this request.""" copied_uri = Uri(self.uri.scheme, self.uri.host, self.uri.port, self.uri.path, self.uri.query.copy()) new_request = HttpRequest(uri=copied_uri, method=self.method, headers=self.headers.copy()) new_request._body_parts = self._body_parts[:] return new_request def _dump(self): """Converts to a printable string for debugging purposes. In order to preserve the request, it does not read from file-like objects in the body. """ output = 'HTTP Request\n method: %s\n url: %s\n headers:\n' % ( self.method, str(self.uri)) for header, value in self.headers.iteritems(): output += ' %s: %s\n' % (header, value) output += ' body sections:\n' i = 0 for part in self._body_parts: if isinstance(part, (str, unicode)): output += ' %s: %s\n' % (i, part) else: output += ' %s: <file like object>\n' % i i += 1 return output def _apply_defaults(http_request): if http_request.uri.scheme is None: if http_request.uri.port == 443: http_request.uri.scheme = 'https' else: http_request.uri.scheme = 'http' class Uri(object): """A URI as used in HTTP 1.1""" scheme = None host = None port = None path = None def __init__(self, scheme=None, host=None, port=None, path=None, query=None): """Constructor for a URI. Args: scheme: str This is usually 'http' or 'https'. host: str The host name or IP address of the desired server. post: int The server's port number. path: str The path of the resource following the host. This begins with a /, example: '/calendar/feeds/default/allcalendars/full' query: dict of strings The URL query parameters. The keys and values are both escaped so this dict should contain the unescaped values. For example {'my key': 'val', 'second': '!!!'} will become '?my+key=val&second=%21%21%21' which is appended to the path. """ self.query = query or {} if scheme is not None: self.scheme = scheme if host is not None: self.host = host if port is not None: self.port = port if path: self.path = path def _get_query_string(self): param_pairs = [] for key, value in self.query.iteritems(): quoted_key = urllib.quote_plus(str(key)) if value is None: param_pairs.append(quoted_key) else: quoted_value = urllib.quote_plus(str(value)) param_pairs.append('%s=%s' % (quoted_key, quoted_value)) return '&'.join(param_pairs) def _get_relative_path(self): """Returns the path with the query parameters escaped and appended.""" param_string = self._get_query_string() if self.path is None: path = '/' else: path = self.path if param_string: return '?'.join([path, param_string]) else: return path def _to_string(self): if self.scheme is None and self.port == 443: scheme = 'https' elif self.scheme is None: scheme = 'http' else: scheme = self.scheme if self.path is None: path = '/' else: path = self.path if self.port is None: return '%s://%s%s' % (scheme, self.host, self._get_relative_path()) else: return '%s://%s:%s%s' % (scheme, self.host, str(self.port), self._get_relative_path()) def __str__(self): return self._to_string() def modify_request(self, http_request=None): """Sets HTTP request components based on the URI.""" if http_request is None: http_request = HttpRequest() if http_request.uri is None: http_request.uri = Uri() # Determine the correct scheme. if self.scheme: http_request.uri.scheme = self.scheme if self.port: http_request.uri.port = self.port if self.host: http_request.uri.host = self.host # Set the relative uri path if self.path: http_request.uri.path = self.path if self.query: http_request.uri.query = self.query.copy() return http_request ModifyRequest = modify_request def parse_uri(uri_string): """Creates a Uri object which corresponds to the URI string. This method can accept partial URIs, but it will leave missing members of the Uri unset. """ parts = urlparse.urlparse(uri_string) uri = Uri() if parts[0]: uri.scheme = parts[0] if parts[1]: host_parts = parts[1].split(':') if host_parts[0]: uri.host = host_parts[0] if len(host_parts) > 1: uri.port = int(host_parts[1]) if parts[2]: uri.path = parts[2] if parts[4]: param_pairs = parts[4].split('&') for pair in param_pairs: pair_parts = pair.split('=') if len(pair_parts) > 1: uri.query[urllib.unquote_plus(pair_parts[0])] = ( urllib.unquote_plus(pair_parts[1])) elif len(pair_parts) == 1: uri.query[urllib.unquote_plus(pair_parts[0])] = None return uri parse_uri = staticmethod(parse_uri) ParseUri = parse_uri parse_uri = Uri.parse_uri ParseUri = Uri.parse_uri class HttpResponse(object): status = None reason = None _body = None def __init__(self, status=None, reason=None, headers=None, body=None): self._headers = headers or {} if status is not None: self.status = status if reason is not None: self.reason = reason if body is not None: if hasattr(body, 'read'): self._body = body else: self._body = StringIO.StringIO(body) def getheader(self, name, default=None): if name in self._headers: return self._headers[name] else: return default def getheaders(self): return self._headers def read(self, amt=None): if self._body is None: return None if not amt: return self._body.read() else: return self._body.read(amt) def _dump_response(http_response): """Converts to a string for printing debug messages. Does not read the body since that may consume the content. """ output = 'HttpResponse\n status: %s\n reason: %s\n headers:' % ( http_response.status, http_response.reason) headers = get_headers(http_response) if isinstance(headers, dict): for header, value in headers.iteritems(): output += ' %s: %s\n' % (header, value) else: for pair in headers: output += ' %s: %s\n' % (pair[0], pair[1]) return output class HttpClient(object): """Performs HTTP requests using httplib.""" debug = None def request(self, http_request): return self._http_request(http_request.method, http_request.uri, http_request.headers, http_request._body_parts) Request = request def _get_connection(self, uri, headers=None): """Opens a socket connection to the server to set up an HTTP request. Args: uri: The full URL for the request as a Uri object. headers: A dict of string pairs containing the HTTP headers for the request. """ connection = None if uri.scheme == 'https': if not uri.port: connection = httplib.HTTPSConnection(uri.host) else: connection = httplib.HTTPSConnection(uri.host, int(uri.port)) else: if not uri.port: connection = httplib.HTTPConnection(uri.host) else: connection = httplib.HTTPConnection(uri.host, int(uri.port)) return connection def _http_request(self, method, uri, headers=None, body_parts=None): """Makes an HTTP request using httplib. Args: method: str example: 'GET', 'POST', 'PUT', 'DELETE', etc. uri: str or atom.http_core.Uri headers: dict of strings mapping to strings which will be sent as HTTP headers in the request. body_parts: list of strings, objects with a read method, or objects which can be converted to strings using str. Each of these will be sent in order as the body of the HTTP request. """ if isinstance(uri, (str, unicode)): uri = Uri.parse_uri(uri) connection = self._get_connection(uri, headers=headers) if self.debug: connection.debuglevel = 1 if connection.host != uri.host: connection.putrequest(method, str(uri)) else: connection.putrequest(method, uri._get_relative_path()) # Overcome a bug in Python 2.4 and 2.5 # httplib.HTTPConnection.putrequest adding # HTTP request header 'Host: www.google.com:443' instead of # 'Host: www.google.com', and thus resulting the error message # 'Token invalid - AuthSub token has wrong scope' in the HTTP response. if (uri.scheme == 'https' and int(uri.port or 443) == 443 and hasattr(connection, '_buffer') and isinstance(connection._buffer, list)): header_line = 'Host: %s:443' % uri.host replacement_header_line = 'Host: %s' % uri.host try: connection._buffer[connection._buffer.index(header_line)] = ( replacement_header_line) except ValueError: # header_line missing from connection._buffer pass # Send the HTTP headers. for header_name, value in headers.iteritems(): connection.putheader(header_name, value) connection.endheaders() # If there is data, send it in the request. if body_parts and filter(lambda x: x != '', body_parts): for part in body_parts: _send_data_part(part, connection) # Return the HTTP Response from the server. return connection.getresponse() def _send_data_part(data, connection): if isinstance(data, (str, unicode)): # I might want to just allow str, not unicode. connection.send(data) return # Check to see if data is a file-like object that has a read method. elif hasattr(data, 'read'): # Read the file and send it a chunk at a time. while 1: binarydata = data.read(100000) if binarydata == '': break connection.send(binarydata) return else: # The data object was not a file. # Try to convert to a string and send the data. connection.send(str(data)) return class ProxiedHttpClient(HttpClient): def _get_connection(self, uri, headers=None): # Check to see if there are proxy settings required for this request. proxy = None if uri.scheme == 'https': proxy = os.environ.get('https_proxy') elif uri.scheme == 'http': proxy = os.environ.get('http_proxy') if not proxy: return HttpClient._get_connection(self, uri, headers=headers) # Now we have the URL of the appropriate proxy server. # Get a username and password for the proxy if required. proxy_auth = _get_proxy_auth() if uri.scheme == 'https': import socket if proxy_auth: proxy_auth = 'Proxy-authorization: %s' % proxy_auth # Construct the proxy connect command. port = uri.port if not port: port = 443 proxy_connect = 'CONNECT %s:%s HTTP/1.0\r\n' % (uri.host, port) # Set the user agent to send to the proxy user_agent = '' if headers and 'User-Agent' in headers: user_agent = 'User-Agent: %s\r\n' % (headers['User-Agent']) proxy_pieces = '%s%s%s\r\n' % (proxy_connect, proxy_auth, user_agent) # Find the proxy host and port. proxy_uri = Uri.parse_uri(proxy) if not proxy_uri.port: proxy_uri.port = '80' # Connect to the proxy server, very simple recv and error checking p_sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM) p_sock.connect((proxy_uri.host, int(proxy_uri.port))) p_sock.sendall(proxy_pieces) response = '' # Wait for the full response. while response.find("\r\n\r\n") == -1: response += p_sock.recv(8192) p_status = response.split()[1] if p_status != str(200): raise ProxyError('Error status=%s' % str(p_status)) # Trivial setup for ssl socket. sslobj = None if ssl is not None: sslobj = ssl.wrap_socket(p_sock, None, None) else: sock_ssl = socket.ssl(p_sock, None, Nonesock_) sslobj = httplib.FakeSocket(p_sock, sock_ssl) # Initalize httplib and replace with the proxy socket. connection = httplib.HTTPConnection(proxy_uri.host) connection.sock = sslobj return connection elif uri.scheme == 'http': proxy_uri = Uri.parse_uri(proxy) if not proxy_uri.port: proxy_uri.port = '80' if proxy_auth: headers['Proxy-Authorization'] = proxy_auth.strip() return httplib.HTTPConnection(proxy_uri.host, int(proxy_uri.port)) return None def _get_proxy_auth(): import base64 proxy_username = os.environ.get('proxy-username') if not proxy_username: proxy_username = os.environ.get('proxy_username') proxy_password = os.environ.get('proxy-password') if not proxy_password: proxy_password = os.environ.get('proxy_password') if proxy_username: user_auth = base64.b64encode('%s:%s' % (proxy_username, proxy_password)) return 'Basic %s\r\n' % (user_auth.strip()) else: return '' ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������gdata-2.0.18/src/atom/service.py��������������������������������������������������������������������0000750�0364663�0011610�00000070377�12156622362�016516� 0����������������������������������������������������������������������������������������������������ustar �afshar��������������������������eng�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/python # # Copyright (C) 2006, 2007, 2008 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. """AtomService provides CRUD ops. in line with the Atom Publishing Protocol. AtomService: Encapsulates the ability to perform insert, update and delete operations with the Atom Publishing Protocol on which GData is based. An instance can perform query, insertion, deletion, and update. HttpRequest: Function that performs a GET, POST, PUT, or DELETE HTTP request to the specified end point. An AtomService object or a subclass can be used to specify information about the request. """ __author__ = 'api.jscudder (Jeff Scudder)' import atom.http_interface import atom.url import atom.http import atom.token_store import os import httplib import urllib import re import base64 import socket import warnings try: from xml.etree import cElementTree as ElementTree except ImportError: try: import cElementTree as ElementTree except ImportError: try: from xml.etree import ElementTree except ImportError: from elementtree import ElementTree import atom class AtomService(object): """Performs Atom Publishing Protocol CRUD operations. The AtomService contains methods to perform HTTP CRUD operations. """ # Default values for members port = 80 ssl = False # Set the current_token to force the AtomService to use this token # instead of searching for an appropriate token in the token_store. current_token = None auto_store_tokens = True auto_set_current_token = True def _get_override_token(self): return self.current_token def _set_override_token(self, token): self.current_token = token override_token = property(_get_override_token, _set_override_token) #@atom.v1_deprecated('Please use atom.client.AtomPubClient instead.') def __init__(self, server=None, additional_headers=None, application_name='', http_client=None, token_store=None): """Creates a new AtomService client. Args: server: string (optional) The start of a URL for the server to which all operations should be directed. Example: 'www.google.com' additional_headers: dict (optional) Any additional HTTP headers which should be included with CRUD operations. http_client: An object responsible for making HTTP requests using a request method. If none is provided, a new instance of atom.http.ProxiedHttpClient will be used. token_store: Keeps a collection of authorization tokens which can be applied to requests for a specific URLs. Critical methods are find_token based on a URL (atom.url.Url or a string), add_token, and remove_token. """ self.http_client = http_client or atom.http.ProxiedHttpClient() self.token_store = token_store or atom.token_store.TokenStore() self.server = server self.additional_headers = additional_headers or {} self.additional_headers['User-Agent'] = atom.http_interface.USER_AGENT % ( application_name,) # If debug is True, the HTTPConnection will display debug information self._set_debug(False) __init__ = atom.v1_deprecated( 'Please use atom.client.AtomPubClient instead.')( __init__) def _get_debug(self): return self.http_client.debug def _set_debug(self, value): self.http_client.debug = value debug = property(_get_debug, _set_debug, doc='If True, HTTP debug information is printed.') def use_basic_auth(self, username, password, scopes=None): if username is not None and password is not None: if scopes is None: scopes = [atom.token_store.SCOPE_ALL] base_64_string = base64.encodestring('%s:%s' % (username, password)) token = BasicAuthToken('Basic %s' % base_64_string.strip(), scopes=[atom.token_store.SCOPE_ALL]) if self.auto_set_current_token: self.current_token = token if self.auto_store_tokens: return self.token_store.add_token(token) return True return False def UseBasicAuth(self, username, password, for_proxy=False): """Sets an Authenticaiton: Basic HTTP header containing plaintext. Deprecated, use use_basic_auth instead. The username and password are base64 encoded and added to an HTTP header which will be included in each request. Note that your username and password are sent in plaintext. Args: username: str password: str """ self.use_basic_auth(username, password) #@atom.v1_deprecated('Please use atom.client.AtomPubClient for requests.') def request(self, operation, url, data=None, headers=None, url_params=None): if isinstance(url, (str, unicode)): if url.startswith('http:') and self.ssl: # Force all requests to be https if self.ssl is True. url = atom.url.parse_url('https:' + url[5:]) elif not url.startswith('http') and self.ssl: url = atom.url.parse_url('https://%s%s' % (self.server, url)) elif not url.startswith('http'): url = atom.url.parse_url('http://%s%s' % (self.server, url)) else: url = atom.url.parse_url(url) if url_params: for name, value in url_params.iteritems(): url.params[name] = value all_headers = self.additional_headers.copy() if headers: all_headers.update(headers) # If the list of headers does not include a Content-Length, attempt to # calculate it based on the data object. if data and 'Content-Length' not in all_headers: content_length = CalculateDataLength(data) if content_length: all_headers['Content-Length'] = str(content_length) # Find an Authorization token for this URL if one is available. if self.override_token: auth_token = self.override_token else: auth_token = self.token_store.find_token(url) return auth_token.perform_request(self.http_client, operation, url, data=data, headers=all_headers) request = atom.v1_deprecated( 'Please use atom.client.AtomPubClient for requests.')( request) # CRUD operations def Get(self, uri, extra_headers=None, url_params=None, escape_params=True): """Query the APP server with the given URI The uri is the portion of the URI after the server value (server example: 'www.google.com'). Example use: To perform a query against Google Base, set the server to 'base.google.com' and set the uri to '/base/feeds/...', where ... is your query. For example, to find snippets for all digital cameras uri should be set to: '/base/feeds/snippets?bq=digital+camera' Args: uri: string The query in the form of a URI. Example: '/base/feeds/snippets?bq=digital+camera'. extra_headers: dicty (optional) Extra HTTP headers to be included in the GET request. These headers are in addition to those stored in the client's additional_headers property. The client automatically sets the Content-Type and Authorization headers. url_params: dict (optional) Additional URL parameters to be included in the query. These are translated into query arguments in the form '&dict_key=value&...'. Example: {'max-results': '250'} becomes &max-results=250 escape_params: boolean (optional) If false, the calling code has already ensured that the query will form a valid URL (all reserved characters have been escaped). If true, this method will escape the query and any URL parameters provided. Returns: httplib.HTTPResponse The server's response to the GET request. """ return self.request('GET', uri, data=None, headers=extra_headers, url_params=url_params) def Post(self, data, uri, extra_headers=None, url_params=None, escape_params=True, content_type='application/atom+xml'): """Insert data into an APP server at the given URI. Args: data: string, ElementTree._Element, or something with a __str__ method The XML to be sent to the uri. uri: string The location (feed) to which the data should be inserted. Example: '/base/feeds/items'. extra_headers: dict (optional) HTTP headers which are to be included. The client automatically sets the Content-Type, Authorization, and Content-Length headers. url_params: dict (optional) Additional URL parameters to be included in the URI. These are translated into query arguments in the form '&dict_key=value&...'. Example: {'max-results': '250'} becomes &max-results=250 escape_params: boolean (optional) If false, the calling code has already ensured that the query will form a valid URL (all reserved characters have been escaped). If true, this method will escape the query and any URL parameters provided. Returns: httplib.HTTPResponse Server's response to the POST request. """ if extra_headers is None: extra_headers = {} if content_type: extra_headers['Content-Type'] = content_type return self.request('POST', uri, data=data, headers=extra_headers, url_params=url_params) def Put(self, data, uri, extra_headers=None, url_params=None, escape_params=True, content_type='application/atom+xml'): """Updates an entry at the given URI. Args: data: string, ElementTree._Element, or xml_wrapper.ElementWrapper The XML containing the updated data. uri: string A URI indicating entry to which the update will be applied. Example: '/base/feeds/items/ITEM-ID' extra_headers: dict (optional) HTTP headers which are to be included. The client automatically sets the Content-Type, Authorization, and Content-Length headers. url_params: dict (optional) Additional URL parameters to be included in the URI. These are translated into query arguments in the form '&dict_key=value&...'. Example: {'max-results': '250'} becomes &max-results=250 escape_params: boolean (optional) If false, the calling code has already ensured that the query will form a valid URL (all reserved characters have been escaped). If true, this method will escape the query and any URL parameters provided. Returns: httplib.HTTPResponse Server's response to the PUT request. """ if extra_headers is None: extra_headers = {} if content_type: extra_headers['Content-Type'] = content_type return self.request('PUT', uri, data=data, headers=extra_headers, url_params=url_params) def Delete(self, uri, extra_headers=None, url_params=None, escape_params=True): """Deletes the entry at the given URI. Args: uri: string The URI of the entry to be deleted. Example: '/base/feeds/items/ITEM-ID' extra_headers: dict (optional) HTTP headers which are to be included. The client automatically sets the Content-Type and Authorization headers. url_params: dict (optional) Additional URL parameters to be included in the URI. These are translated into query arguments in the form '&dict_key=value&...'. Example: {'max-results': '250'} becomes &max-results=250 escape_params: boolean (optional) If false, the calling code has already ensured that the query will form a valid URL (all reserved characters have been escaped). If true, this method will escape the query and any URL parameters provided. Returns: httplib.HTTPResponse Server's response to the DELETE request. """ return self.request('DELETE', uri, data=None, headers=extra_headers, url_params=url_params) class BasicAuthToken(atom.http_interface.GenericToken): def __init__(self, auth_header, scopes=None): """Creates a token used to add Basic Auth headers to HTTP requests. Args: auth_header: str The value for the Authorization header. scopes: list of str or atom.url.Url specifying the beginnings of URLs for which this token can be used. For example, if scopes contains 'http://example.com/foo', then this token can be used for a request to 'http://example.com/foo/bar' but it cannot be used for a request to 'http://example.com/baz' """ self.auth_header = auth_header self.scopes = scopes or [] def perform_request(self, http_client, operation, url, data=None, headers=None): """Sets the Authorization header to the basic auth string.""" if headers is None: headers = {'Authorization':self.auth_header} else: headers['Authorization'] = self.auth_header return http_client.request(operation, url, data=data, headers=headers) def __str__(self): return self.auth_header def valid_for_scope(self, url): """Tells the caller if the token authorizes access to the desired URL. """ if isinstance(url, (str, unicode)): url = atom.url.parse_url(url) for scope in self.scopes: if scope == atom.token_store.SCOPE_ALL: return True if isinstance(scope, (str, unicode)): scope = atom.url.parse_url(scope) if scope == url: return True # Check the host and the path, but ignore the port and protocol. elif scope.host == url.host and not scope.path: return True elif scope.host == url.host and scope.path and not url.path: continue elif scope.host == url.host and url.path.startswith(scope.path): return True return False def PrepareConnection(service, full_uri): """Opens a connection to the server based on the full URI. This method is deprecated, instead use atom.http.HttpClient.request. Examines the target URI and the proxy settings, which are set as environment variables, to open a connection with the server. This connection is used to make an HTTP request. Args: service: atom.AtomService or a subclass. It must have a server string which represents the server host to which the request should be made. It may also have a dictionary of additional_headers to send in the HTTP request. full_uri: str Which is the target relative (lacks protocol and host) or absolute URL to be opened. Example: 'https://www.google.com/accounts/ClientLogin' or 'base/feeds/snippets' where the server is set to www.google.com. Returns: A tuple containing the httplib.HTTPConnection and the full_uri for the request. """ deprecation('calling deprecated function PrepareConnection') (server, port, ssl, partial_uri) = ProcessUrl(service, full_uri) if ssl: # destination is https proxy = os.environ.get('https_proxy') if proxy: (p_server, p_port, p_ssl, p_uri) = ProcessUrl(service, proxy, True) proxy_username = os.environ.get('proxy-username') if not proxy_username: proxy_username = os.environ.get('proxy_username') proxy_password = os.environ.get('proxy-password') if not proxy_password: proxy_password = os.environ.get('proxy_password') if proxy_username: user_auth = base64.encodestring('%s:%s' % (proxy_username, proxy_password)) proxy_authorization = ('Proxy-authorization: Basic %s\r\n' % ( user_auth.strip())) else: proxy_authorization = '' proxy_connect = 'CONNECT %s:%s HTTP/1.0\r\n' % (server, port) user_agent = 'User-Agent: %s\r\n' % ( service.additional_headers['User-Agent']) proxy_pieces = (proxy_connect + proxy_authorization + user_agent + '\r\n') #now connect, very simple recv and error checking p_sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM) p_sock.connect((p_server,p_port)) p_sock.sendall(proxy_pieces) response = '' # Wait for the full response. while response.find("\r\n\r\n") == -1: response += p_sock.recv(8192) p_status=response.split()[1] if p_status!=str(200): raise atom.http.ProxyError('Error status=%s' % p_status) # Trivial setup for ssl socket. ssl = socket.ssl(p_sock, None, None) fake_sock = httplib.FakeSocket(p_sock, ssl) # Initalize httplib and replace with the proxy socket. connection = httplib.HTTPConnection(server) connection.sock=fake_sock full_uri = partial_uri else: connection = httplib.HTTPSConnection(server, port) full_uri = partial_uri else: # destination is http proxy = os.environ.get('http_proxy') if proxy: (p_server, p_port, p_ssl, p_uri) = ProcessUrl(service.server, proxy, True) proxy_username = os.environ.get('proxy-username') if not proxy_username: proxy_username = os.environ.get('proxy_username') proxy_password = os.environ.get('proxy-password') if not proxy_password: proxy_password = os.environ.get('proxy_password') if proxy_username: UseBasicAuth(service, proxy_username, proxy_password, True) connection = httplib.HTTPConnection(p_server, p_port) if not full_uri.startswith("http://"): if full_uri.startswith("/"): full_uri = "http://%s%s" % (service.server, full_uri) else: full_uri = "http://%s/%s" % (service.server, full_uri) else: connection = httplib.HTTPConnection(server, port) full_uri = partial_uri return (connection, full_uri) def UseBasicAuth(service, username, password, for_proxy=False): """Sets an Authenticaiton: Basic HTTP header containing plaintext. Deprecated, use AtomService.use_basic_auth insread. The username and password are base64 encoded and added to an HTTP header which will be included in each request. Note that your username and password are sent in plaintext. The auth header is added to the additional_headers dictionary in the service object. Args: service: atom.AtomService or a subclass which has an additional_headers dict as a member. username: str password: str """ deprecation('calling deprecated function UseBasicAuth') base_64_string = base64.encodestring('%s:%s' % (username, password)) base_64_string = base_64_string.strip() if for_proxy: header_name = 'Proxy-Authorization' else: header_name = 'Authorization' service.additional_headers[header_name] = 'Basic %s' % (base_64_string,) def ProcessUrl(service, url, for_proxy=False): """Processes a passed URL. If the URL does not begin with https?, then the default value for server is used This method is deprecated, use atom.url.parse_url instead. """ if not isinstance(url, atom.url.Url): url = atom.url.parse_url(url) server = url.host ssl = False port = 80 if not server: if hasattr(service, 'server'): server = service.server else: server = service if not url.protocol and hasattr(service, 'ssl'): ssl = service.ssl if hasattr(service, 'port'): port = service.port else: if url.protocol == 'https': ssl = True elif url.protocol == 'http': ssl = False if url.port: port = int(url.port) elif port == 80 and ssl: port = 443 return (server, port, ssl, url.get_request_uri()) def DictionaryToParamList(url_parameters, escape_params=True): """Convert a dictionary of URL arguments into a URL parameter string. This function is deprcated, use atom.url.Url instead. Args: url_parameters: The dictionaty of key-value pairs which will be converted into URL parameters. For example, {'dry-run': 'true', 'foo': 'bar'} will become ['dry-run=true', 'foo=bar']. Returns: A list which contains a string for each key-value pair. The strings are ready to be incorporated into a URL by using '&'.join([] + parameter_list) """ # Choose which function to use when modifying the query and parameters. # Use quote_plus when escape_params is true. transform_op = [str, urllib.quote_plus][bool(escape_params)] # Create a list of tuples containing the escaped version of the # parameter-value pairs. parameter_tuples = [(transform_op(param), transform_op(value)) for param, value in (url_parameters or {}).items()] # Turn parameter-value tuples into a list of strings in the form # 'PARAMETER=VALUE'. return ['='.join(x) for x in parameter_tuples] def BuildUri(uri, url_params=None, escape_params=True): """Converts a uri string and a collection of parameters into a URI. This function is deprcated, use atom.url.Url instead. Args: uri: string url_params: dict (optional) escape_params: boolean (optional) uri: string The start of the desired URI. This string can alrady contain URL parameters. Examples: '/base/feeds/snippets', '/base/feeds/snippets?bq=digital+camera' url_parameters: dict (optional) Additional URL parameters to be included in the query. These are translated into query arguments in the form '&dict_key=value&...'. Example: {'max-results': '250'} becomes &max-results=250 escape_params: boolean (optional) If false, the calling code has already ensured that the query will form a valid URL (all reserved characters have been escaped). If true, this method will escape the query and any URL parameters provided. Returns: string The URI consisting of the escaped URL parameters appended to the initial uri string. """ # Prepare URL parameters for inclusion into the GET request. parameter_list = DictionaryToParamList(url_params, escape_params) # Append the URL parameters to the URL. if parameter_list: if uri.find('?') != -1: # If there are already URL parameters in the uri string, add the # parameters after a new & character. full_uri = '&'.join([uri] + parameter_list) else: # The uri string did not have any URL parameters (no ? character) # so put a ? between the uri and URL parameters. full_uri = '%s%s' % (uri, '?%s' % ('&'.join([] + parameter_list))) else: full_uri = uri return full_uri def HttpRequest(service, operation, data, uri, extra_headers=None, url_params=None, escape_params=True, content_type='application/atom+xml'): """Performs an HTTP call to the server, supports GET, POST, PUT, and DELETE. This method is deprecated, use atom.http.HttpClient.request instead. Usage example, perform and HTTP GET on http://www.google.com/: import atom.service client = atom.service.AtomService() http_response = client.Get('http://www.google.com/') or you could set the client.server to 'www.google.com' and use the following: client.server = 'www.google.com' http_response = client.Get('/') Args: service: atom.AtomService object which contains some of the parameters needed to make the request. The following members are used to construct the HTTP call: server (str), additional_headers (dict), port (int), and ssl (bool). operation: str The HTTP operation to be performed. This is usually one of 'GET', 'POST', 'PUT', or 'DELETE' data: ElementTree, filestream, list of parts, or other object which can be converted to a string. Should be set to None when performing a GET or PUT. If data is a file-like object which can be read, this method will read a chunk of 100K bytes at a time and send them. If the data is a list of parts to be sent, each part will be evaluated and sent. uri: The beginning of the URL to which the request should be sent. Examples: '/', '/base/feeds/snippets', '/m8/feeds/contacts/default/base' extra_headers: dict of strings. HTTP headers which should be sent in the request. These headers are in addition to those stored in service.additional_headers. url_params: dict of strings. Key value pairs to be added to the URL as URL parameters. For example {'foo':'bar', 'test':'param'} will become ?foo=bar&test=param. escape_params: bool default True. If true, the keys and values in url_params will be URL escaped when the form is constructed (Special characters converted to %XX form.) content_type: str The MIME type for the data being sent. Defaults to 'application/atom+xml', this is only used if data is set. """ deprecation('call to deprecated function HttpRequest') full_uri = BuildUri(uri, url_params, escape_params) (connection, full_uri) = PrepareConnection(service, full_uri) if extra_headers is None: extra_headers = {} # Turn on debug mode if the debug member is set. if service.debug: connection.debuglevel = 1 connection.putrequest(operation, full_uri) # If the list of headers does not include a Content-Length, attempt to # calculate it based on the data object. if (data and not service.additional_headers.has_key('Content-Length') and not extra_headers.has_key('Content-Length')): content_length = CalculateDataLength(data) if content_length: extra_headers['Content-Length'] = str(content_length) if content_type: extra_headers['Content-Type'] = content_type # Send the HTTP headers. if isinstance(service.additional_headers, dict): for header in service.additional_headers: connection.putheader(header, service.additional_headers[header]) if isinstance(extra_headers, dict): for header in extra_headers: connection.putheader(header, extra_headers[header]) connection.endheaders() # If there is data, send it in the request. if data: if isinstance(data, list): for data_part in data: __SendDataPart(data_part, connection) else: __SendDataPart(data, connection) # Return the HTTP Response from the server. return connection.getresponse() def __SendDataPart(data, connection): """This method is deprecated, use atom.http._send_data_part""" deprecated('call to deprecated function __SendDataPart') if isinstance(data, str): #TODO add handling for unicode. connection.send(data) return elif ElementTree.iselement(data): connection.send(ElementTree.tostring(data)) return # Check to see if data is a file-like object that has a read method. elif hasattr(data, 'read'): # Read the file and send it a chunk at a time. while 1: binarydata = data.read(100000) if binarydata == '': break connection.send(binarydata) return else: # The data object was not a file. # Try to convert to a string and send the data. connection.send(str(data)) return def CalculateDataLength(data): """Attempts to determine the length of the data to send. This method will respond with a length only if the data is a string or and ElementTree element. Args: data: object If this is not a string or ElementTree element this funtion will return None. """ if isinstance(data, str): return len(data) elif isinstance(data, list): return None elif ElementTree.iselement(data): return len(ElementTree.tostring(data)) elif hasattr(data, 'read'): # If this is a file-like object, don't try to guess the length. return None else: return len(str(data)) def deprecation(message): warnings.warn(message, DeprecationWarning, stacklevel=2) �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������gdata-2.0.18/PKG-INFO�������������������������������������������������������������������������������0000640�0364663�0011610�00000003135�12156625015�014032� 0����������������������������������������������������������������������������������������������������ustar �afshar��������������������������eng�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������Metadata-Version: 1.0 Name: gdata Version: 2.0.18 Summary: Python client library for Google data APIs Home-page: http://code.google.com/p/gdata-python-client/ Author: Jeffrey Scudder Author-email: j.s@google.com License: Apache 2.0 Description: The Google data Python client library makes it easy to interact with Google services through the Google Data APIs. This library provides data models and service modules for the the following Google data services: - Google Calendar data API - Google Contacts data API - Google Spreadsheets data API - Google Document List data APIs - Google Base data API - Google Apps Provisioning API - Google Apps Email Migration API - Google Apps Email Settings API - Picasa Web Albums Data API - Google Code Search Data API - YouTube Data API - Google Webmaster Tools Data API - Blogger Data API - Google Health API - Google Book Search API - Google Analytics API - Google Finance API - Google Sites Data API - Google Content API For Shopping - Google App Marketplace API - Google Content API for Shopping - core Google data API functionality The core Google data code provides sufficient functionality to use this library with any Google data API (even if a module hasn't been written for it yet). For example, this client can be used with the Notebook API. This library may also be used with any Atom Publishing Protocol service (AtomPub). Platform: UNKNOWN �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������gdata-2.0.18/pydocs/��������������������������������������������������������������������������������0000750�0364663�0011610�00000000000�12156625015�014233� 5����������������������������������������������������������������������������������������������������ustar �afshar��������������������������eng�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������gdata-2.0.18/pydocs/generate_docs�������������������������������������������������������������������0000750�0364663�0011610�00000001042�12156622362�016762� 0����������������������������������������������������������������������������������������������������ustar �afshar��������������������������eng�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/bin/bash # # Copyright 2010 Google Inc. All Rights Reserved. # Author: jcgregorio@google.com (Joe Gregorio) # # Creates the documentation set for the library by # running pydoc on all the files in apiclient. # # Notes: You may have to update the location of the # App Engine library for your local system. export GOOGLE_APPENGINE=/usr/local/google_appengine/ export PYTHONPATH=`pwd`/../src:$GOOGLE_APPENGINE find ../src/ -name "*.py" | sed "s/\/__init__.py//" | sed "s/\.py//" | sed "s#^\.\.\/src\/##" | sed "s#/#.#g" | xargs pydoc -w ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������gdata-2.0.18/upload-diffs.py������������������������������������������������������������������������0000640�0364663�0011610�00000171375�12156622363�015703� 0����������������������������������������������������������������������������������������������������ustar �afshar��������������������������eng�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/env python # # Copyright 2007 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. """Tool for uploading diffs from a version control system to the codereview app. Usage summary: upload.py [options] [-- diff_options] Diff options are passed to the diff command of the underlying system. Supported version control systems: Git Mercurial Subversion It is important for Git/Mercurial users to specify a tree/node/branch to diff against by using the '--rev' option. """ # This code is derived from appcfg.py in the App Engine SDK (open source), # and from ASPN recipe #146306. import ConfigParser import cookielib import fnmatch import getpass import logging import mimetypes import optparse import os import re import socket import subprocess import sys import urllib import urllib2 import urlparse # The md5 module was deprecated in Python 2.5. try: from hashlib import md5 except ImportError: from md5 import md5 try: import readline except ImportError: pass # The logging verbosity: # 0: Errors only. # 1: Status messages. # 2: Info logs. # 3: Debug logs. verbosity = 1 # Max size of patch or base file. MAX_UPLOAD_SIZE = 900 * 1024 # Constants for version control names. Used by GuessVCSName. VCS_GIT = "Git" VCS_MERCURIAL = "Mercurial" VCS_SUBVERSION = "Subversion" VCS_UNKNOWN = "Unknown" # whitelist for non-binary filetypes which do not start with "text/" # .mm (Objective-C) shows up as application/x-freemind on my Linux box. TEXT_MIMETYPES = ['application/javascript', 'application/x-javascript', 'application/x-freemind'] VCS_ABBREVIATIONS = { VCS_MERCURIAL.lower(): VCS_MERCURIAL, "hg": VCS_MERCURIAL, VCS_SUBVERSION.lower(): VCS_SUBVERSION, "svn": VCS_SUBVERSION, VCS_GIT.lower(): VCS_GIT, } # The result of parsing Subversion's [auto-props] setting. svn_auto_props_map = None def GetEmail(prompt): """Prompts the user for their email address and returns it. The last used email address is saved to a file and offered up as a suggestion to the user. If the user presses enter without typing in anything the last used email address is used. If the user enters a new address, it is saved for next time we prompt. """ last_email_file_name = os.path.expanduser("~/.last_codereview_email_address") last_email = "" if os.path.exists(last_email_file_name): try: last_email_file = open(last_email_file_name, "r") last_email = last_email_file.readline().strip("\n") last_email_file.close() prompt += " [%s]" % last_email except IOError, e: pass email = raw_input(prompt + ": ").strip() if email: try: last_email_file = open(last_email_file_name, "w") last_email_file.write(email) last_email_file.close() except IOError, e: pass else: email = last_email return email def StatusUpdate(msg): """Print a status message to stdout. If 'verbosity' is greater than 0, print the message. Args: msg: The string to print. """ if verbosity > 0: print msg def ErrorExit(msg): """Print an error message to stderr and exit.""" print >>sys.stderr, msg sys.exit(1) class ClientLoginError(urllib2.HTTPError): """Raised to indicate there was an error authenticating with ClientLogin.""" def __init__(self, url, code, msg, headers, args): urllib2.HTTPError.__init__(self, url, code, msg, headers, None) self.args = args self.reason = args["Error"] class AbstractRpcServer(object): """Provides a common interface for a simple RPC server.""" def __init__(self, host, auth_function, host_override=None, extra_headers={}, save_cookies=False): """Creates a new HttpRpcServer. Args: host: The host to send requests to. auth_function: A function that takes no arguments and returns an (email, password) tuple when called. Will be called if authentication is required. host_override: The host header to send to the server (defaults to host). extra_headers: A dict of extra headers to append to every request. save_cookies: If True, save the authentication cookies to local disk. If False, use an in-memory cookiejar instead. Subclasses must implement this functionality. Defaults to False. """ self.host = host self.host_override = host_override self.auth_function = auth_function self.authenticated = False self.extra_headers = extra_headers self.save_cookies = save_cookies self.opener = self._GetOpener() if self.host_override: logging.info("Server: %s; Host: %s", self.host, self.host_override) else: logging.info("Server: %s", self.host) def _GetOpener(self): """Returns an OpenerDirector for making HTTP requests. Returns: A urllib2.OpenerDirector object. """ raise NotImplementedError() def _CreateRequest(self, url, data=None): """Creates a new urllib request.""" logging.debug("Creating request for: '%s' with payload:\n%s", url, data) req = urllib2.Request(url, data=data) if self.host_override: req.add_header("Host", self.host_override) for key, value in self.extra_headers.iteritems(): req.add_header(key, value) return req def _GetAuthToken(self, email, password): """Uses ClientLogin to authenticate the user, returning an auth token. Args: email: The user's email address password: The user's password Raises: ClientLoginError: If there was an error authenticating with ClientLogin. HTTPError: If there was some other form of HTTP error. Returns: The authentication token returned by ClientLogin. """ account_type = "GOOGLE" if self.host.endswith(".google.com"): # Needed for use inside Google. account_type = "HOSTED" req = self._CreateRequest( url="https://www.google.com/accounts/ClientLogin", data=urllib.urlencode({ "Email": email, "Passwd": password, "service": "ah", "source": "rietveld-codereview-upload", "accountType": account_type, }), ) try: response = self.opener.open(req) response_body = response.read() response_dict = dict(x.split("=") for x in response_body.split("\n") if x) return response_dict["Auth"] except urllib2.HTTPError, e: if e.code == 403: body = e.read() response_dict = dict(x.split("=", 1) for x in body.split("\n") if x) raise ClientLoginError(req.get_full_url(), e.code, e.msg, e.headers, response_dict) else: raise def _GetAuthCookie(self, auth_token): """Fetches authentication cookies for an authentication token. Args: auth_token: The authentication token returned by ClientLogin. Raises: HTTPError: If there was an error fetching the authentication cookies. """ # This is a dummy value to allow us to identify when we're successful. continue_location = "http://localhost/" args = {"continue": continue_location, "auth": auth_token} req = self._CreateRequest("http://%s/_ah/login?%s" % (self.host, urllib.urlencode(args))) try: response = self.opener.open(req) except urllib2.HTTPError, e: response = e if (response.code != 302 or response.info()["location"] != continue_location): raise urllib2.HTTPError(req.get_full_url(), response.code, response.msg, response.headers, response.fp) self.authenticated = True def _Authenticate(self): """Authenticates the user. The authentication process works as follows: 1) We get a username and password from the user 2) We use ClientLogin to obtain an AUTH token for the user (see http://code.google.com/apis/accounts/AuthForInstalledApps.html). 3) We pass the auth token to /_ah/login on the server to obtain an authentication cookie. If login was successful, it tries to redirect us to the URL we provided. If we attempt to access the upload API without first obtaining an authentication cookie, it returns a 401 response (or a 302) and directs us to authenticate ourselves with ClientLogin. """ for i in range(3): credentials = self.auth_function() try: auth_token = self._GetAuthToken(credentials[0], credentials[1]) except ClientLoginError, e: if e.reason == "BadAuthentication": print >>sys.stderr, "Invalid username or password." continue if e.reason == "CaptchaRequired": print >>sys.stderr, ( "Please go to\n" "https://www.google.com/accounts/DisplayUnlockCaptcha\n" "and verify you are a human. Then try again.") break if e.reason == "NotVerified": print >>sys.stderr, "Account not verified." break if e.reason == "TermsNotAgreed": print >>sys.stderr, "User has not agreed to TOS." break if e.reason == "AccountDeleted": print >>sys.stderr, "The user account has been deleted." break if e.reason == "AccountDisabled": print >>sys.stderr, "The user account has been disabled." break if e.reason == "ServiceDisabled": print >>sys.stderr, ("The user's access to the service has been " "disabled.") break if e.reason == "ServiceUnavailable": print >>sys.stderr, "The service is not available; try again later." break raise self._GetAuthCookie(auth_token) return def Send(self, request_path, payload=None, content_type="application/octet-stream", timeout=None, **kwargs): """Sends an RPC and returns the response. Args: request_path: The path to send the request to, eg /api/appversion/create. payload: The body of the request, or None to send an empty request. content_type: The Content-Type header to use. timeout: timeout in seconds; default None i.e. no timeout. (Note: for large requests on OS X, the timeout doesn't work right.) kwargs: Any keyword arguments are converted into query string parameters. Returns: The response body, as a string. """ # TODO: Don't require authentication. Let the server say # whether it is necessary. if not self.authenticated: self._Authenticate() old_timeout = socket.getdefaulttimeout() socket.setdefaulttimeout(timeout) try: tries = 0 while True: tries += 1 args = dict(kwargs) url = "http://%s%s" % (self.host, request_path) if args: url += "?" + urllib.urlencode(args) req = self._CreateRequest(url=url, data=payload) req.add_header("Content-Type", content_type) try: f = self.opener.open(req) response = f.read() f.close() return response except urllib2.HTTPError, e: if tries > 3: raise elif e.code == 401 or e.code == 302: self._Authenticate() ## elif e.code >= 500 and e.code < 600: ## # Server Error - try again. ## continue else: raise finally: socket.setdefaulttimeout(old_timeout) class HttpRpcServer(AbstractRpcServer): """Provides a simplified RPC-style interface for HTTP requests.""" def _Authenticate(self): """Save the cookie jar after authentication.""" super(HttpRpcServer, self)._Authenticate() if self.save_cookies: StatusUpdate("Saving authentication cookies to %s" % self.cookie_file) self.cookie_jar.save() def _GetOpener(self): """Returns an OpenerDirector that supports cookies and ignores redirects. Returns: A urllib2.OpenerDirector object. """ opener = urllib2.OpenerDirector() opener.add_handler(urllib2.ProxyHandler()) opener.add_handler(urllib2.UnknownHandler()) opener.add_handler(urllib2.HTTPHandler()) opener.add_handler(urllib2.HTTPDefaultErrorHandler()) opener.add_handler(urllib2.HTTPSHandler()) opener.add_handler(urllib2.HTTPErrorProcessor()) if self.save_cookies: self.cookie_file = os.path.expanduser("~/.codereview_upload_cookies") self.cookie_jar = cookielib.MozillaCookieJar(self.cookie_file) if os.path.exists(self.cookie_file): try: self.cookie_jar.load() self.authenticated = True StatusUpdate("Loaded authentication cookies from %s" % self.cookie_file) except (cookielib.LoadError, IOError): # Failed to load cookies - just ignore them. pass else: # Create an empty cookie file with mode 600 fd = os.open(self.cookie_file, os.O_CREAT, 0600) os.close(fd) # Always chmod the cookie file os.chmod(self.cookie_file, 0600) else: # Don't save cookies across runs of update.py. self.cookie_jar = cookielib.CookieJar() opener.add_handler(urllib2.HTTPCookieProcessor(self.cookie_jar)) return opener parser = optparse.OptionParser(usage="%prog [options] [-- diff_options]") parser.add_option("-y", "--assume_yes", action="store_true", dest="assume_yes", default=False, help="Assume that the answer to yes/no questions is 'yes'.") # Logging group = parser.add_option_group("Logging options") group.add_option("-q", "--quiet", action="store_const", const=0, dest="verbose", help="Print errors only.") group.add_option("-v", "--verbose", action="store_const", const=2, dest="verbose", default=1, help="Print info level logs (default).") group.add_option("--noisy", action="store_const", const=3, dest="verbose", help="Print all logs.") # Review server group = parser.add_option_group("Review server options") group.add_option("-s", "--server", action="store", dest="server", default="codereview.appspot.com", metavar="SERVER", help=("The server to upload to. The format is host[:port]. " "Defaults to '%default'.")) group.add_option("-e", "--email", action="store", dest="email", metavar="EMAIL", default=None, help="The username to use. Will prompt if omitted.") group.add_option("-H", "--host", action="store", dest="host", metavar="HOST", default=None, help="Overrides the Host header sent with all RPCs.") group.add_option("--no_cookies", action="store_false", dest="save_cookies", default=True, help="Do not save authentication cookies to local disk.") # Issue group = parser.add_option_group("Issue options") group.add_option("-d", "--description", action="store", dest="description", metavar="DESCRIPTION", default=None, help="Optional description when creating an issue.") group.add_option("-f", "--description_file", action="store", dest="description_file", metavar="DESCRIPTION_FILE", default=None, help="Optional path of a file that contains " "the description when creating an issue.") group.add_option("-r", "--reviewers", action="store", dest="reviewers", metavar="REVIEWERS", default=",afshar@google.com", help="Add reviewers (comma separated email addresses).") group.add_option("--cc", action="store", dest="cc", metavar="CC", default="gdata-python-client-library-contributors@googlegroups.com", help="Add CC (comma separated email addresses).") group.add_option("--private", action="store_true", dest="private", default=False, help="Make the issue restricted to reviewers and those CCed") # Upload options group = parser.add_option_group("Patch options") group.add_option("-m", "--message", action="store", dest="message", metavar="MESSAGE", default=None, help="A message to identify the patch. " "Will prompt if omitted.") group.add_option("-i", "--issue", type="int", action="store", metavar="ISSUE", default=None, help="Issue number to which to add. Defaults to new issue.") group.add_option("--base_url", action="store", dest="base_url", default=None, help="Base repository URL (listed as \"Base URL\" when " "viewing issue). If omitted, will be guessed automatically " "for SVN repos and left blank for others.") group.add_option("--download_base", action="store_true", dest="download_base", default=False, help="Base files will be downloaded by the server " "(side-by-side diffs may not work on files with CRs).") group.add_option("--rev", action="store", dest="revision", metavar="REV", default=None, help="Base revision/branch/tree to diff against. Use " "rev1:rev2 range to review already committed changeset.") group.add_option("--send_mail", action="store_true", dest="send_mail", default=True, help="Send notification email to reviewers.") group.add_option("--vcs", action="store", dest="vcs", metavar="VCS", default=None, help=("Version control system (optional, usually upload.py " "already guesses the right VCS).")) group.add_option("--emulate_svn_auto_props", action="store_true", dest="emulate_svn_auto_props", default=False, help=("Emulate Subversion's auto properties feature.")) def GetRpcServer(options): """Returns an instance of an AbstractRpcServer. Returns: A new AbstractRpcServer, on which RPC calls can be made. """ rpc_server_class = HttpRpcServer def GetUserCredentials(): """Prompts the user for a username and password.""" email = options.email if email is None: email = GetEmail("Email (login for uploading to %s)" % options.server) password = getpass.getpass("Password for %s: " % email) return (email, password) # If this is the dev_appserver, use fake authentication. host = (options.host or options.server).lower() if host == "localhost" or host.startswith("localhost:"): email = options.email if email is None: email = "test@example.com" logging.info("Using debug user %s. Override with --email" % email) server = rpc_server_class( options.server, lambda: (email, "password"), host_override=options.host, extra_headers={"Cookie": 'dev_appserver_login="%s:False"' % email}, save_cookies=options.save_cookies) # Don't try to talk to ClientLogin. server.authenticated = True return server return rpc_server_class(options.server, GetUserCredentials, host_override=options.host, save_cookies=options.save_cookies) def EncodeMultipartFormData(fields, files): """Encode form fields for multipart/form-data. Args: fields: A sequence of (name, value) elements for regular form fields. files: A sequence of (name, filename, value) elements for data to be uploaded as files. Returns: (content_type, body) ready for httplib.HTTP instance. Source: http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/146306 """ BOUNDARY = '-M-A-G-I-C---B-O-U-N-D-A-R-Y-' CRLF = '\r\n' lines = [] for (key, value) in fields: lines.append('--' + BOUNDARY) lines.append('Content-Disposition: form-data; name="%s"' % key) lines.append('') lines.append(value) for (key, filename, value) in files: lines.append('--' + BOUNDARY) lines.append('Content-Disposition: form-data; name="%s"; filename="%s"' % (key, filename)) lines.append('Content-Type: %s' % GetContentType(filename)) lines.append('') lines.append(value) lines.append('--' + BOUNDARY + '--') lines.append('') body = CRLF.join(lines) content_type = 'multipart/form-data; boundary=%s' % BOUNDARY return content_type, body def GetContentType(filename): """Helper to guess the content-type from the filename.""" return mimetypes.guess_type(filename)[0] or 'application/octet-stream' # Use a shell for subcommands on Windows to get a PATH search. use_shell = sys.platform.startswith("win") def RunShellWithReturnCode(command, print_output=False, universal_newlines=True, env=os.environ): """Executes a command and returns the output from stdout and the return code. Args: command: Command to execute. print_output: If True, the output is printed to stdout. If False, both stdout and stderr are ignored. universal_newlines: Use universal_newlines flag (default: True). Returns: Tuple (output, return code) """ logging.info("Running %s", command) p = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=use_shell, universal_newlines=universal_newlines, env=env) if print_output: output_array = [] while True: line = p.stdout.readline() if not line: break print line.strip("\n") output_array.append(line) output = "".join(output_array) else: output = p.stdout.read() p.wait() errout = p.stderr.read() if print_output and errout: print >>sys.stderr, errout p.stdout.close() p.stderr.close() return output, p.returncode def RunShell(command, silent_ok=False, universal_newlines=True, print_output=False, env=os.environ): data, retcode = RunShellWithReturnCode(command, print_output, universal_newlines, env) if retcode: ErrorExit("Got error status from %s:\n%s" % (command, data)) if not silent_ok and not data: ErrorExit("No output from %s" % command) return data class VersionControlSystem(object): """Abstract base class providing an interface to the VCS.""" def __init__(self, options): """Constructor. Args: options: Command line options. """ self.options = options def GenerateDiff(self, args): """Return the current diff as a string. Args: args: Extra arguments to pass to the diff command. """ raise NotImplementedError( "abstract method -- subclass %s must override" % self.__class__) def GetUnknownFiles(self): """Return a list of files unknown to the VCS.""" raise NotImplementedError( "abstract method -- subclass %s must override" % self.__class__) def CheckForUnknownFiles(self): """Show an "are you sure?" prompt if there are unknown files.""" unknown_files = self.GetUnknownFiles() if unknown_files: print "The following files are not added to version control:" for line in unknown_files: print line prompt = "Are you sure to continue?(y/N) " answer = raw_input(prompt).strip() if answer != "y": ErrorExit("User aborted") def GetBaseFile(self, filename): """Get the content of the upstream version of a file. Returns: A tuple (base_content, new_content, is_binary, status) base_content: The contents of the base file. new_content: For text files, this is empty. For binary files, this is the contents of the new file, since the diff output won't contain information to reconstruct the current file. is_binary: True iff the file is binary. status: The status of the file. """ raise NotImplementedError( "abstract method -- subclass %s must override" % self.__class__) def GetBaseFiles(self, diff): """Helper that calls GetBase file for each file in the patch. Returns: A dictionary that maps from filename to GetBaseFile's tuple. Filenames are retrieved based on lines that start with "Index:" or "Property changes on:". """ files = {} for line in diff.splitlines(True): if line.startswith('Index:') or line.startswith('Property changes on:'): unused, filename = line.split(':', 1) # On Windows if a file has property changes its filename uses '\' # instead of '/'. filename = filename.strip().replace('\\', '/') files[filename] = self.GetBaseFile(filename) return files def UploadBaseFiles(self, issue, rpc_server, patch_list, patchset, options, files): """Uploads the base files (and if necessary, the current ones as well).""" def UploadFile(filename, file_id, content, is_binary, status, is_base): """Uploads a file to the server.""" file_too_large = False if is_base: type = "base" else: type = "current" if len(content) > MAX_UPLOAD_SIZE: print ("Not uploading the %s file for %s because it's too large." % (type, filename)) file_too_large = True content = "" checksum = md5(content).hexdigest() if options.verbose > 0 and not file_too_large: print "Uploading %s file for %s" % (type, filename) url = "/%d/upload_content/%d/%d" % (int(issue), int(patchset), file_id) form_fields = [("filename", filename), ("status", status), ("checksum", checksum), ("is_binary", str(is_binary)), ("is_current", str(not is_base)), ] if file_too_large: form_fields.append(("file_too_large", "1")) if options.email: form_fields.append(("user", options.email)) ctype, body = EncodeMultipartFormData(form_fields, [("data", filename, content)]) response_body = rpc_server.Send(url, body, content_type=ctype) if not response_body.startswith("OK"): StatusUpdate(" --> %s" % response_body) sys.exit(1) patches = dict() [patches.setdefault(v, k) for k, v in patch_list] for filename in patches.keys(): base_content, new_content, is_binary, status = files[filename] file_id_str = patches.get(filename) if file_id_str.find("nobase") != -1: base_content = None file_id_str = file_id_str[file_id_str.rfind("_") + 1:] file_id = int(file_id_str) if base_content != None: UploadFile(filename, file_id, base_content, is_binary, status, True) if new_content != None: UploadFile(filename, file_id, new_content, is_binary, status, False) def IsImage(self, filename): """Returns true if the filename has an image extension.""" mimetype = mimetypes.guess_type(filename)[0] if not mimetype: return False return mimetype.startswith("image/") def IsBinary(self, filename): """Returns true if the guessed mimetyped isnt't in text group.""" mimetype = mimetypes.guess_type(filename)[0] if not mimetype: return False # e.g. README, "real" binaries usually have an extension # special case for text files which don't start with text/ if mimetype in TEXT_MIMETYPES: return False return not mimetype.startswith("text/") class SubversionVCS(VersionControlSystem): """Implementation of the VersionControlSystem interface for Subversion.""" def __init__(self, options): super(SubversionVCS, self).__init__(options) if self.options.revision: match = re.match(r"(\d+)(:(\d+))?", self.options.revision) if not match: ErrorExit("Invalid Subversion revision %s." % self.options.revision) self.rev_start = match.group(1) self.rev_end = match.group(3) else: self.rev_start = self.rev_end = None # Cache output from "svn list -r REVNO dirname". # Keys: dirname, Values: 2-tuple (ouput for start rev and end rev). self.svnls_cache = {} # Base URL is required to fetch files deleted in an older revision. # Result is cached to not guess it over and over again in GetBaseFile(). required = self.options.download_base or self.options.revision is not None self.svn_base = self._GuessBase(required) def GuessBase(self, required): """Wrapper for _GuessBase.""" return self.svn_base def _GuessBase(self, required): """Returns the SVN base URL. Args: required: If true, exits if the url can't be guessed, otherwise None is returned. """ info = RunShell(["svn", "info"]) for line in info.splitlines(): words = line.split() if len(words) == 2 and words[0] == "URL:": url = words[1] scheme, netloc, path, params, query, fragment = urlparse.urlparse(url) username, netloc = urllib.splituser(netloc) if username: logging.info("Removed username from base URL") if netloc.endswith("svn.python.org"): if netloc == "svn.python.org": if path.startswith("/projects/"): path = path[9:] elif netloc != "pythondev@svn.python.org": ErrorExit("Unrecognized Python URL: %s" % url) base = "http://svn.python.org/view/*checkout*%s/" % path logging.info("Guessed Python base = %s", base) elif netloc.endswith("svn.collab.net"): if path.startswith("/repos/"): path = path[6:] base = "http://svn.collab.net/viewvc/*checkout*%s/" % path logging.info("Guessed CollabNet base = %s", base) elif netloc.endswith(".googlecode.com"): path = path + "/" base = urlparse.urlunparse(("http", netloc, path, params, query, fragment)) logging.info("Guessed Google Code base = %s", base) else: path = path + "/" base = urlparse.urlunparse((scheme, netloc, path, params, query, fragment)) logging.info("Guessed base = %s", base) return base if required: ErrorExit("Can't find URL in output from svn info") return None def GenerateDiff(self, args): cmd = ["svn", "diff"] if self.options.revision: cmd += ["-r", self.options.revision] cmd.extend(args) data = RunShell(cmd) count = 0 for line in data.splitlines(): if line.startswith("Index:") or line.startswith("Property changes on:"): count += 1 logging.info(line) if not count: ErrorExit("No valid patches found in output from svn diff") return data def _CollapseKeywords(self, content, keyword_str): """Collapses SVN keywords.""" # svn cat translates keywords but svn diff doesn't. As a result of this # behavior patching.PatchChunks() fails with a chunk mismatch error. # This part was originally written by the Review Board development team # who had the same problem (http://reviews.review-board.org/r/276/). # Mapping of keywords to known aliases svn_keywords = { # Standard keywords 'Date': ['Date', 'LastChangedDate'], 'Revision': ['Revision', 'LastChangedRevision', 'Rev'], 'Author': ['Author', 'LastChangedBy'], 'HeadURL': ['HeadURL', 'URL'], 'Id': ['Id'], # Aliases 'LastChangedDate': ['LastChangedDate', 'Date'], 'LastChangedRevision': ['LastChangedRevision', 'Rev', 'Revision'], 'LastChangedBy': ['LastChangedBy', 'Author'], 'URL': ['URL', 'HeadURL'], } def repl(m): if m.group(2): return "$%s::%s$" % (m.group(1), " " * len(m.group(3))) return "$%s$" % m.group(1) keywords = [keyword for name in keyword_str.split(" ") for keyword in svn_keywords.get(name, [])] return re.sub(r"\$(%s):(:?)([^\$]+)\$" % '|'.join(keywords), repl, content) def GetUnknownFiles(self): status = RunShell(["svn", "status", "--ignore-externals"], silent_ok=True) unknown_files = [] for line in status.split("\n"): if line and line[0] == "?": unknown_files.append(line) return unknown_files def ReadFile(self, filename): """Returns the contents of a file.""" file = open(filename, 'rb') result = "" try: result = file.read() finally: file.close() return result def GetStatus(self, filename): """Returns the status of a file.""" if not self.options.revision: status = RunShell(["svn", "status", "--ignore-externals", filename]) if not status: ErrorExit("svn status returned no output for %s" % filename) status_lines = status.splitlines() # If file is in a cl, the output will begin with # "\n--- Changelist 'cl_name':\n". See # http://svn.collab.net/repos/svn/trunk/notes/changelist-design.txt if (len(status_lines) == 3 and not status_lines[0] and status_lines[1].startswith("--- Changelist")): status = status_lines[2] else: status = status_lines[0] # If we have a revision to diff against we need to run "svn list" # for the old and the new revision and compare the results to get # the correct status for a file. else: dirname, relfilename = os.path.split(filename) if dirname not in self.svnls_cache: cmd = ["svn", "list", "-r", self.rev_start, dirname or "."] out, returncode = RunShellWithReturnCode(cmd) if returncode: ErrorExit("Failed to get status for %s." % filename) old_files = out.splitlines() args = ["svn", "list"] if self.rev_end: args += ["-r", self.rev_end] cmd = args + [dirname or "."] out, returncode = RunShellWithReturnCode(cmd) if returncode: ErrorExit("Failed to run command %s" % cmd) self.svnls_cache[dirname] = (old_files, out.splitlines()) old_files, new_files = self.svnls_cache[dirname] if relfilename in old_files and relfilename not in new_files: status = "D " elif relfilename in old_files and relfilename in new_files: status = "M " else: status = "A " return status def GetBaseFile(self, filename): status = self.GetStatus(filename) base_content = None new_content = None # If a file is copied its status will be "A +", which signifies # "addition-with-history". See "svn st" for more information. We need to # upload the original file or else diff parsing will fail if the file was # edited. if status[0] == "A" and status[3] != "+": # We'll need to upload the new content if we're adding a binary file # since diff's output won't contain it. mimetype = RunShell(["svn", "propget", "svn:mime-type", filename], silent_ok=True) base_content = "" is_binary = bool(mimetype) and not mimetype.startswith("text/") if is_binary and self.IsImage(filename): new_content = self.ReadFile(filename) elif (status[0] in ("M", "D", "R") or (status[0] == "A" and status[3] == "+") or # Copied file. (status[0] == " " and status[1] == "M")): # Property change. args = [] if self.options.revision: url = "%s/%s@%s" % (self.svn_base, filename, self.rev_start) else: # Don't change filename, it's needed later. url = filename args += ["-r", "BASE"] cmd = ["svn"] + args + ["propget", "svn:mime-type", url] mimetype, returncode = RunShellWithReturnCode(cmd) if returncode: # File does not exist in the requested revision. # Reset mimetype, it contains an error message. mimetype = "" get_base = False is_binary = bool(mimetype) and not mimetype.startswith("text/") if status[0] == " ": # Empty base content just to force an upload. base_content = "" elif is_binary: if self.IsImage(filename): get_base = True if status[0] == "M": if not self.rev_end: new_content = self.ReadFile(filename) else: url = "%s/%s@%s" % (self.svn_base, filename, self.rev_end) new_content = RunShell(["svn", "cat", url], universal_newlines=True, silent_ok=True) else: base_content = "" else: get_base = True if get_base: if is_binary: universal_newlines = False else: universal_newlines = True if self.rev_start: # "svn cat -r REV delete_file.txt" doesn't work. cat requires # the full URL with "@REV" appended instead of using "-r" option. url = "%s/%s@%s" % (self.svn_base, filename, self.rev_start) base_content = RunShell(["svn", "cat", url], universal_newlines=universal_newlines, silent_ok=True) else: base_content = RunShell(["svn", "cat", filename], universal_newlines=universal_newlines, silent_ok=True) if not is_binary: args = [] if self.rev_start: url = "%s/%s@%s" % (self.svn_base, filename, self.rev_start) else: url = filename args += ["-r", "BASE"] cmd = ["svn"] + args + ["propget", "svn:keywords", url] keywords, returncode = RunShellWithReturnCode(cmd) if keywords and not returncode: base_content = self._CollapseKeywords(base_content, keywords) else: StatusUpdate("svn status returned unexpected output: %s" % status) sys.exit(1) return base_content, new_content, is_binary, status[0:5] class GitVCS(VersionControlSystem): """Implementation of the VersionControlSystem interface for Git.""" def __init__(self, options): super(GitVCS, self).__init__(options) # Map of filename -> (hash before, hash after) of base file. # Hashes for "no such file" are represented as None. self.hashes = {} # Map of new filename -> old filename for renames. self.renames = {} def GenerateDiff(self, extra_args): # This is more complicated than svn's GenerateDiff because we must convert # the diff output to include an svn-style "Index:" line as well as record # the hashes of the files, so we can upload them along with our diff. # Special used by git to indicate "no such content". NULL_HASH = "0"*40 extra_args = extra_args[:] if self.options.revision: extra_args = [self.options.revision] + extra_args # --no-ext-diff is broken in some versions of Git, so try to work around # this by overriding the environment (but there is still a problem if the # git config key "diff.external" is used). env = os.environ.copy() if 'GIT_EXTERNAL_DIFF' in env: del env['GIT_EXTERNAL_DIFF'] gitdiff = RunShell(["git", "diff", "--no-ext-diff", "--full-index", "-M"] + extra_args, env=env) def IsFileNew(filename): return filename in self.hashes and self.hashes[filename][0] is None def AddSubversionPropertyChange(filename): """Add svn's property change information into the patch if given file is new file. We use Subversion's auto-props setting to retrieve its property. See http://svnbook.red-bean.com/en/1.1/ch07.html#svn-ch-7-sect-1.3.2 for Subversion's [auto-props] setting. """ if self.options.emulate_svn_auto_props and IsFileNew(filename): svnprops = GetSubversionPropertyChanges(filename) if svnprops: svndiff.append("\n" + svnprops + "\n") svndiff = [] filecount = 0 filename = None for line in gitdiff.splitlines(): match = re.match(r"diff --git a/(.*) b/(.*)$", line) if match: # Add auto property here for previously seen file. if filename is not None: AddSubversionPropertyChange(filename) filecount += 1 # Intentionally use the "after" filename so we can show renames. filename = match.group(2) svndiff.append("Index: %s\n" % filename) if match.group(1) != match.group(2): self.renames[match.group(2)] = match.group(1) else: # The "index" line in a git diff looks like this (long hashes elided): # index 82c0d44..b2cee3f 100755 # We want to save the left hash, as that identifies the base file. match = re.match(r"index (\w+)\.\.(\w+)", line) if match: before, after = (match.group(1), match.group(2)) if before == NULL_HASH: before = None if after == NULL_HASH: after = None self.hashes[filename] = (before, after) svndiff.append(line + "\n") if not filecount: ErrorExit("No valid patches found in output from git diff") # Add auto property for the last seen file. assert filename is not None AddSubversionPropertyChange(filename) return "".join(svndiff) def GetUnknownFiles(self): status = RunShell(["git", "ls-files", "--exclude-standard", "--others"], silent_ok=True) return status.splitlines() def GetFileContent(self, file_hash, is_binary): """Returns the content of a file identified by its git hash.""" data, retcode = RunShellWithReturnCode(["git", "show", file_hash], universal_newlines=not is_binary) if retcode: ErrorExit("Got error status from 'git show %s'" % file_hash) return data def GetBaseFile(self, filename): hash_before, hash_after = self.hashes.get(filename, (None,None)) base_content = None new_content = None is_binary = self.IsBinary(filename) status = None if filename in self.renames: status = "A +" # Match svn attribute name for renames. if filename not in self.hashes: # If a rename doesn't change the content, we never get a hash. base_content = RunShell(["git", "show", "HEAD:" + filename]) elif not hash_before: status = "A" base_content = "" elif not hash_after: status = "D" else: status = "M" is_image = self.IsImage(filename) # Grab the before/after content if we need it. # We should include file contents if it's text or it's an image. if not is_binary or is_image: # Grab the base content if we don't have it already. if base_content is None and hash_before: base_content = self.GetFileContent(hash_before, is_binary) # Only include the "after" file if it's an image; otherwise it # it is reconstructed from the diff. if is_image and hash_after: new_content = self.GetFileContent(hash_after, is_binary) return (base_content, new_content, is_binary, status) class MercurialVCS(VersionControlSystem): """Implementation of the VersionControlSystem interface for Mercurial.""" def __init__(self, options, repo_dir): super(MercurialVCS, self).__init__(options) # Absolute path to repository (we can be in a subdir) self.repo_dir = os.path.normpath(repo_dir) # Compute the subdir cwd = os.path.normpath(os.getcwd()) assert cwd.startswith(self.repo_dir) self.subdir = cwd[len(self.repo_dir):].lstrip(r"\/") if self.options.revision: self.base_rev = self.options.revision else: self.base_rev = RunShell(["hg", "parent", "-q"]).split(':')[1].strip() def _GetRelPath(self, filename): """Get relative path of a file according to the current directory, given its logical path in the repo.""" assert filename.startswith(self.subdir), (filename, self.subdir) return filename[len(self.subdir):].lstrip(r"\/") def GenerateDiff(self, extra_args): # If no file specified, restrict to the current subdir extra_args = extra_args or ["."] cmd = ["hg", "diff", "--git", "-r", self.base_rev] + extra_args data = RunShell(cmd, silent_ok=True) svndiff = [] filecount = 0 for line in data.splitlines(): m = re.match("diff --git a/(\S+) b/(\S+)", line) if m: # Modify line to make it look like as it comes from svn diff. # With this modification no changes on the server side are required # to make upload.py work with Mercurial repos. # NOTE: for proper handling of moved/copied files, we have to use # the second filename. filename = m.group(2) svndiff.append("Index: %s" % filename) svndiff.append("=" * 67) filecount += 1 logging.info(line) else: svndiff.append(line) if not filecount: ErrorExit("No valid patches found in output from hg diff") return "\n".join(svndiff) + "\n" def GetUnknownFiles(self): """Return a list of files unknown to the VCS.""" args = [] status = RunShell(["hg", "status", "--rev", self.base_rev, "-u", "."], silent_ok=True) unknown_files = [] for line in status.splitlines(): st, fn = line.split(" ", 1) if st == "?": unknown_files.append(fn) return unknown_files def GetBaseFile(self, filename): # "hg status" and "hg cat" both take a path relative to the current subdir # rather than to the repo root, but "hg diff" has given us the full path # to the repo root. base_content = "" new_content = None is_binary = False oldrelpath = relpath = self._GetRelPath(filename) # "hg status -C" returns two lines for moved/copied files, one otherwise out = RunShell(["hg", "status", "-C", "--rev", self.base_rev, relpath]) out = out.splitlines() # HACK: strip error message about missing file/directory if it isn't in # the working copy if out[0].startswith('%s: ' % relpath): out = out[1:] if len(out) > 1: # Moved/copied => considered as modified, use old filename to # retrieve base contents oldrelpath = out[1].strip() status = "M" else: status, _ = out[0].split(' ', 1) if ":" in self.base_rev: base_rev = self.base_rev.split(":", 1)[0] else: base_rev = self.base_rev if status != "A": base_content = RunShell(["hg", "cat", "-r", base_rev, oldrelpath], silent_ok=True) is_binary = "\0" in base_content # Mercurial's heuristic if status != "R": new_content = open(relpath, "rb").read() is_binary = is_binary or "\0" in new_content if is_binary and base_content: # Fetch again without converting newlines base_content = RunShell(["hg", "cat", "-r", base_rev, oldrelpath], silent_ok=True, universal_newlines=False) if not is_binary or not self.IsImage(relpath): new_content = None return base_content, new_content, is_binary, status # NOTE: The SplitPatch function is duplicated in engine.py, keep them in sync. def SplitPatch(data): """Splits a patch into separate pieces for each file. Args: data: A string containing the output of svn diff. Returns: A list of 2-tuple (filename, text) where text is the svn diff output pertaining to filename. """ patches = [] filename = None diff = [] for line in data.splitlines(True): new_filename = None if line.startswith('Index:'): unused, new_filename = line.split(':', 1) new_filename = new_filename.strip() elif line.startswith('Property changes on:'): unused, temp_filename = line.split(':', 1) # When a file is modified, paths use '/' between directories, however # when a property is modified '\' is used on Windows. Make them the same # otherwise the file shows up twice. temp_filename = temp_filename.strip().replace('\\', '/') if temp_filename != filename: # File has property changes but no modifications, create a new diff. new_filename = temp_filename if new_filename: if filename and diff: patches.append((filename, ''.join(diff))) filename = new_filename diff = [line] continue if diff is not None: diff.append(line) if filename and diff: patches.append((filename, ''.join(diff))) return patches def UploadSeparatePatches(issue, rpc_server, patchset, data, options): """Uploads a separate patch for each file in the diff output. Returns a list of [patch_key, filename] for each file. """ patches = SplitPatch(data) rv = [] for patch in patches: if len(patch[1]) > MAX_UPLOAD_SIZE: print ("Not uploading the patch for " + patch[0] + " because the file is too large.") continue form_fields = [("filename", patch[0])] if not options.download_base: form_fields.append(("content_upload", "1")) files = [("data", "data.diff", patch[1])] ctype, body = EncodeMultipartFormData(form_fields, files) url = "/%d/upload_patch/%d" % (int(issue), int(patchset)) print "Uploading patch for " + patch[0] response_body = rpc_server.Send(url, body, content_type=ctype) lines = response_body.splitlines() if not lines or lines[0] != "OK": StatusUpdate(" --> %s" % response_body) sys.exit(1) rv.append([lines[1], patch[0]]) return rv def GuessVCSName(): """Helper to guess the version control system. This examines the current directory, guesses which VersionControlSystem we're using, and returns an string indicating which VCS is detected. Returns: A pair (vcs, output). vcs is a string indicating which VCS was detected and is one of VCS_GIT, VCS_MERCURIAL, VCS_SUBVERSION, or VCS_UNKNOWN. output is a string containing any interesting output from the vcs detection routine, or None if there is nothing interesting. """ # Mercurial has a command to get the base directory of a repository # Try running it, but don't die if we don't have hg installed. # NOTE: we try Mercurial first as it can sit on top of an SVN working copy. try: out, returncode = RunShellWithReturnCode(["hg", "root"]) if returncode == 0: return (VCS_MERCURIAL, out.strip()) except OSError, (errno, message): if errno != 2: # ENOENT -- they don't have hg installed. raise # Subversion has a .svn in all working directories. if os.path.isdir('.svn'): logging.info("Guessed VCS = Subversion") return (VCS_SUBVERSION, None) # Git has a command to test if you're in a git tree. # Try running it, but don't die if we don't have git installed. try: out, returncode = RunShellWithReturnCode(["git", "rev-parse", "--is-inside-work-tree"]) if returncode == 0: return (VCS_GIT, None) except OSError, (errno, message): if errno != 2: # ENOENT -- they don't have git installed. raise return (VCS_UNKNOWN, None) def GuessVCS(options): """Helper to guess the version control system. This verifies any user-specified VersionControlSystem (by command line or environment variable). If the user didn't specify one, this examines the current directory, guesses which VersionControlSystem we're using, and returns an instance of the appropriate class. Exit with an error if we can't figure it out. Returns: A VersionControlSystem instance. Exits if the VCS can't be guessed. """ vcs = options.vcs if not vcs: vcs = os.environ.get("CODEREVIEW_VCS") if vcs: v = VCS_ABBREVIATIONS.get(vcs.lower()) if v is None: ErrorExit("Unknown version control system %r specified." % vcs) (vcs, extra_output) = (v, None) else: (vcs, extra_output) = GuessVCSName() if vcs == VCS_MERCURIAL: if extra_output is None: extra_output = RunShell(["hg", "root"]).strip() return MercurialVCS(options, extra_output) elif vcs == VCS_SUBVERSION: return SubversionVCS(options) elif vcs == VCS_GIT: return GitVCS(options) ErrorExit(("Could not guess version control system. " "Are you in a working copy directory?")) def CheckReviewer(reviewer): """Validate a reviewer -- either a nickname or an email addres. Args: reviewer: A nickname or an email address. Calls ErrorExit() if it is an invalid email address. """ if "@" not in reviewer: return # Assume nickname parts = reviewer.split("@") if len(parts) > 2: ErrorExit("Invalid email address: %r" % reviewer) assert len(parts) == 2 if "." not in parts[1]: ErrorExit("Invalid email address: %r" % reviewer) def LoadSubversionAutoProperties(): """Returns the content of [auto-props] section of Subversion's config file as a dictionary. Returns: A dictionary whose key-value pair corresponds the [auto-props] section's key-value pair. In following cases, returns empty dictionary: - config file doesn't exist, or - 'enable-auto-props' is not set to 'true-like-value' in [miscellany]. """ # Todo(hayato): Windows users might use different path for configuration file. subversion_config = os.path.expanduser("~/.subversion/config") if not os.path.exists(subversion_config): return {} config = ConfigParser.ConfigParser() config.read(subversion_config) if (config.has_section("miscellany") and config.has_option("miscellany", "enable-auto-props") and config.getboolean("miscellany", "enable-auto-props") and config.has_section("auto-props")): props = {} for file_pattern in config.options("auto-props"): props[file_pattern] = ParseSubversionPropertyValues( config.get("auto-props", file_pattern)) return props else: return {} def ParseSubversionPropertyValues(props): """Parse the given property value which comes from [auto-props] section and returns a list whose element is a (svn_prop_key, svn_prop_value) pair. See the following doctest for example. >>> ParseSubversionPropertyValues('svn:eol-style=LF') [('svn:eol-style', 'LF')] >>> ParseSubversionPropertyValues('svn:mime-type=image/jpeg') [('svn:mime-type', 'image/jpeg')] >>> ParseSubversionPropertyValues('svn:eol-style=LF;svn:executable') [('svn:eol-style', 'LF'), ('svn:executable', '*')] """ key_value_pairs = [] for prop in props.split(";"): key_value = prop.split("=") assert len(key_value) <= 2 if len(key_value) == 1: # If value is not given, use '*' as a Subversion's convention. key_value_pairs.append((key_value[0], "*")) else: key_value_pairs.append((key_value[0], key_value[1])) return key_value_pairs def GetSubversionPropertyChanges(filename): """Return a Subversion's 'Property changes on ...' string, which is used in the patch file. Args: filename: filename whose property might be set by [auto-props] config. Returns: A string like 'Property changes on |filename| ...' if given |filename| matches any entries in [auto-props] section. None, otherwise. """ global svn_auto_props_map if svn_auto_props_map is None: svn_auto_props_map = LoadSubversionAutoProperties() all_props = [] for file_pattern, props in svn_auto_props_map.items(): if fnmatch.fnmatch(filename, file_pattern): all_props.extend(props) if all_props: return FormatSubversionPropertyChanges(filename, all_props) return None def FormatSubversionPropertyChanges(filename, props): """Returns Subversion's 'Property changes on ...' strings using given filename and properties. Args: filename: filename props: A list whose element is a (svn_prop_key, svn_prop_value) pair. Returns: A string which can be used in the patch file for Subversion. See the following doctest for example. >>> print FormatSubversionPropertyChanges('foo.cc', [('svn:eol-style', 'LF')]) Property changes on: foo.cc ___________________________________________________________________ Added: svn:eol-style + LF <BLANKLINE> """ prop_changes_lines = [ "Property changes on: %s" % filename, "___________________________________________________________________"] for key, value in props: prop_changes_lines.append("Added: " + key) prop_changes_lines.append(" + " + value) return "\n".join(prop_changes_lines) + "\n" def RealMain(argv, data=None): """The real main function. Args: argv: Command line arguments. data: Diff contents. If None (default) the diff is generated by the VersionControlSystem implementation returned by GuessVCS(). Returns: A 2-tuple (issue id, patchset id). The patchset id is None if the base files are not uploaded by this script (applies only to SVN checkouts). """ logging.basicConfig(format=("%(asctime).19s %(levelname)s %(filename)s:" "%(lineno)s %(message)s ")) os.environ['LC_ALL'] = 'C' options, args = parser.parse_args(argv[1:]) global verbosity verbosity = options.verbose if verbosity >= 3: logging.getLogger().setLevel(logging.DEBUG) elif verbosity >= 2: logging.getLogger().setLevel(logging.INFO) vcs = GuessVCS(options) base = options.base_url if isinstance(vcs, SubversionVCS): # Guessing the base field is only supported for Subversion. # Note: Fetching base files may become deprecated in future releases. guessed_base = vcs.GuessBase(options.download_base) if base: if guessed_base and base != guessed_base: print "Using base URL \"%s\" from --base_url instead of \"%s\"" % \ (base, guessed_base) else: base = guessed_base if not base and options.download_base: options.download_base = True logging.info("Enabled upload of base file") if not options.assume_yes: vcs.CheckForUnknownFiles() if data is None: data = vcs.GenerateDiff(args) files = vcs.GetBaseFiles(data) if verbosity >= 1: print "Upload server:", options.server, "(change with -s/--server)" if options.issue: prompt = "Message describing this patch set: " else: prompt = "New issue subject: " message = options.message or raw_input(prompt).strip() if not message: ErrorExit("A non-empty message is required") rpc_server = GetRpcServer(options) form_fields = [("subject", message)] if base: form_fields.append(("base", base)) if options.issue: form_fields.append(("issue", str(options.issue))) if options.email: form_fields.append(("user", options.email)) if options.reviewers: for reviewer in options.reviewers.split(','): CheckReviewer(reviewer) form_fields.append(("reviewers", options.reviewers)) if options.cc: for cc in options.cc.split(','): CheckReviewer(cc) form_fields.append(("cc", options.cc)) description = options.description if options.description_file: if options.description: ErrorExit("Can't specify description and description_file") file = open(options.description_file, 'r') description = file.read() file.close() if description: form_fields.append(("description", description)) # Send a hash of all the base file so the server can determine if a copy # already exists in an earlier patchset. base_hashes = "" for file, info in files.iteritems(): if not info[0] is None: checksum = md5(info[0]).hexdigest() if base_hashes: base_hashes += "|" base_hashes += checksum + ":" + file form_fields.append(("base_hashes", base_hashes)) if options.private: if options.issue: print "Warning: Private flag ignored when updating an existing issue." else: form_fields.append(("private", "1")) # If we're uploading base files, don't send the email before the uploads, so # that it contains the file status. if options.send_mail and options.download_base: form_fields.append(("send_mail", "1")) if not options.download_base: form_fields.append(("content_upload", "1")) if len(data) > MAX_UPLOAD_SIZE: print "Patch is large, so uploading file patches separately." uploaded_diff_file = [] form_fields.append(("separate_patches", "1")) else: uploaded_diff_file = [("data", "data.diff", data)] ctype, body = EncodeMultipartFormData(form_fields, uploaded_diff_file) response_body = rpc_server.Send("/upload", body, content_type=ctype) patchset = None if not options.download_base or not uploaded_diff_file: lines = response_body.splitlines() if len(lines) >= 2: msg = lines[0] patchset = lines[1].strip() patches = [x.split(" ", 1) for x in lines[2:]] else: msg = response_body else: msg = response_body StatusUpdate(msg) if not response_body.startswith("Issue created.") and \ not response_body.startswith("Issue updated."): sys.exit(0) issue = msg[msg.rfind("/")+1:] if not uploaded_diff_file: result = UploadSeparatePatches(issue, rpc_server, patchset, data, options) if not options.download_base: patches = result if not options.download_base: vcs.UploadBaseFiles(issue, rpc_server, patches, patchset, options, files) if options.send_mail: rpc_server.Send("/" + issue + "/mail", payload="") return issue, patchset def main(): try: RealMain(sys.argv) except KeyboardInterrupt: print StatusUpdate("Interrupted.") sys.exit(1) if __name__ == "__main__": main() �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������gdata-2.0.18/tests/���������������������������������������������������������������������������������0000750�0364663�0011610�00000000000�12156625015�014074� 5����������������������������������������������������������������������������������������������������ustar �afshar��������������������������eng�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������gdata-2.0.18/tests/files/���������������������������������������������������������������������������0000750�0364663�0011610�00000000000�12156625015�015176� 5����������������������������������������������������������������������������������������������������ustar �afshar��������������������������eng�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������gdata-2.0.18/tests/files/testimage.jpg��������������������������������������������������������������0000750�0364663�0011610�00000001261�12156622363�017670� 0����������������������������������������������������������������������������������������������������ustar �afshar��������������������������eng�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ÿØÿà�JFIF��`�`��ÿá�6Exif��II*���������&��������ÿÿÿ���� †�±��ÿÛ�C�    $.' ",#(7),01444'9=82<.342ÿÛ�C  2!!22222222222222222222222222222222222222222222222222ÿÀ���"�ÿÄ����������� ÿÄ�µ���}�!1AQa"q2‘¡#B±ÁRÑð$3br‚ %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyzƒ„…†‡ˆ‰Š’“”•–—˜™š¢£¤¥¦§¨©ª²³´µ¶·¸¹ºÂÃÄÅÆÇÈÉÊÒÓÔÕÖרÙÚáâãäåæçèéêñòóôõö÷øùúÿÄ�������� ÿÄ�µ��w�!1AQaq"2B‘¡±Á #3RðbrÑ $4á%ñ&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz‚ƒ„…†‡ˆ‰Š’“”•–—˜™š¢£¤¥¦§¨©ª²³´µ¶·¸¹ºÂÃÄÅÆÇÈÉÊÒÓÔÕÖרÙÚâãäåæçèéêòóôõö÷øùúÿÚ� ��?�ö*(¢¿ =3ÿÙ�����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������gdata-2.0.18/tests/gdata_tests/���������������������������������������������������������������������0000750�0364663�0011610�00000000000�12156625015�016376� 5����������������������������������������������������������������������������������������������������ustar �afshar��������������������������eng�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������gdata-2.0.18/tests/gdata_tests/core_test.py���������������������������������������������������������0000750�0364663�0011610�00000033746�12156622363�020762� 0����������������������������������������������������������������������������������������������������ustar �afshar��������������������������eng�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/env python # # 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. # This module is used for version 2 of the Google Data APIs. __author__ = 'j.s@google.com (Jeff Scudder)' import unittest import gdata.core import gdata.test_config as conf PLAYLIST_EXAMPLE = ( '{"apiVersion": "2.0","data": {"totalResults": 347,"startIndex": 1,"it' 'emsPerPage": 2,"items": [{"id": "4DAEFAF23BB3CDD0","created": "2008-1' '2-09T20:23:06.000Z","updated": "2010-01-04T02:56:19.000Z","author": "' 'GoogleDevelopers","title": "Google Web Toolkit Developers","descripti' 'on": "Developers talk about using Google Web Toolkit ...","tags": ["g' 'oogle","web","toolkit","developers","gwt"],"size": 12},{"id": "586D32' '2B5E2764CF","created": "2007-11-13T19:41:21.000Z","updated": "2010-01' '-04T17:41:16.000Z","author": "GoogleDevelopers","title": "Android","d' 'escription": "Demos and tutorials about the new Android platform.","t' 'ags": ["android","google","developers","mobile"],"size": 32}]}}') VIDEO_EXAMPLE = ( '{"apiVersion": "2.0","data": {"updated": "2010-01-07T19:58:42.949Z","' 'totalItems": 800,"startIndex": 1,"itemsPerPage": 1, "items": [{"id": ' '"hYB0mn5zh2c","uploaded": "2007-06-05T22:07:03.000Z","updated": "2010' '-01-07T13:26:50.000Z","uploader": "GoogleDeveloperDay","category": "N' 'ews","title": "Google Developers Day US - Maps API Introduction","des' 'cription": "Google Maps API Introduction ...","tags": ["GDD07","GDD07' 'US","Maps"],"thumbnail": {"default": "http://i.ytimg.com/vi/hYB0mn5zh' '2c/default.jpg","hqDefault": "http://i.ytimg.com/vi/hYB0mn5zh2c/hqdef' 'ault.jpg"},"player": {"default": "http://www.youtube.com/watch?v' '\u003dhYB0mn5zh2c"},"content": {"1": "rtsp://v5.cache3.c.youtube.com/' 'CiILENy.../0/0/0/video.3gp","5": "http://www.youtube.com/v/hYB0mn5zh2' 'c?f...","6": "rtsp://v1.cache1.c.youtube.com/CiILENy.../0/0/0/video.3' 'gp"},"duration": 2840,"rating": 4.63,"ratingCount": 68,"viewCount": 2' '20101,"favoriteCount": 201,"commentCount": 22}]}}') class JsoncConversionTest(unittest.TestCase): # See http://code.google.com/apis/youtube/2.0/developers_guide_jsonc.html def test_from_and_to_old_json(self): json = ('{"media$group":{"media$credit":[{"$t":"GoogleDevelopers", ' '"role":"uploader", "scheme":"urn:youtube"}]}}') jsonc_obj = gdata.core.parse_json(json) self.assert_(isinstance(jsonc_obj, gdata.core.Jsonc)) raw = gdata.core._convert_to_object(jsonc_obj) self.assertEqual(raw['media$group']['media$credit'][0]['$t'], 'GoogleDevelopers') def test_to_and_from_jsonc(self): x = {'a': 1} jsonc_obj = gdata.core._convert_to_jsonc(x) self.assertEqual(jsonc_obj.a, 1) # Convert the json_obj back to a dict and compare. self.assertEqual(x, gdata.core._convert_to_object(jsonc_obj)) def test_from_and_to_new_json(self): x = gdata.core.parse_json(PLAYLIST_EXAMPLE) self.assertEqual(x._dict['apiVersion'], '2.0') self.assertEqual(x._dict['data']._dict['items'][0]._dict['id'], '4DAEFAF23BB3CDD0') self.assertEqual(x._dict['data']._dict['items'][1]._dict['id'], '586D322B5E2764CF') x = gdata.core.parse_json(VIDEO_EXAMPLE) self.assertEqual(x._dict['apiVersion'], '2.0') self.assertEqual(x.data._dict['totalItems'], 800) self.assertEqual(x.data.items[0]._dict['viewCount'], 220101) def test_pretty_print(self): x = gdata.core.Jsonc(x=1, y=2, z=3) pretty = gdata.core.prettify_jsonc(x) self.assert_(isinstance(pretty, (str, unicode))) pretty = gdata.core.prettify_jsonc(x, 4) self.assert_(isinstance(pretty, (str, unicode))) class MemberNameConversionTest(unittest.TestCase): def test_member_to_jsonc(self): self.assertEqual(gdata.core._to_jsonc_name(''), '') self.assertEqual(gdata.core._to_jsonc_name('foo'), 'foo') self.assertEqual(gdata.core._to_jsonc_name('Foo'), 'Foo') self.assertEqual(gdata.core._to_jsonc_name('test_x'), 'testX') self.assertEqual(gdata.core._to_jsonc_name('test_x_y_zabc'), 'testXYZabc') def build_test_object(): return gdata.core.Jsonc( api_version='2.0', data=gdata.core.Jsonc( total_items=800, items=[ gdata.core.Jsonc( view_count=220101, comment_count=22, favorite_count=201, content={ '1': ('rtsp://v5.cache3.c.youtube.com' '/CiILENy.../0/0/0/video.3gp')})])) class JsoncObjectTest(unittest.TestCase): def check_video_json(self, x): """Validates a JsoncObject similar to VIDEO_EXAMPLE.""" self.assert_(isinstance(x._dict, dict)) self.assert_(isinstance(x.data, gdata.core.Jsonc)) self.assert_(isinstance(x._dict['data'], gdata.core.Jsonc)) self.assert_(isinstance(x.data._dict, dict)) self.assert_(isinstance(x._dict['data']._dict, dict)) self.assert_(isinstance(x._dict['apiVersion'], (str, unicode))) self.assert_(isinstance(x.api_version, (str, unicode))) self.assert_(isinstance(x.data._dict['items'], list)) self.assert_(isinstance(x.data.items[0]._dict['commentCount'], (int, long))) self.assert_(isinstance(x.data.items[0].favorite_count, (int, long))) self.assertEqual(x.data.total_items, 800) self.assertEqual(x._dict['data']._dict['totalItems'], 800) self.assertEqual(x.data.items[0].view_count, 220101) self.assertEqual(x._dict['data']._dict['items'][0]._dict['viewCount'], 220101) self.assertEqual(x.data.items[0].comment_count, 22) self.assertEqual(x.data.items[0]._dict['commentCount'], 22) self.assertEqual(x.data.items[0].favorite_count, 201) self.assertEqual(x.data.items[0]._dict['favoriteCount'], 201) self.assertEqual( x.data.items[0].content._dict['1'], 'rtsp://v5.cache3.c.youtube.com/CiILENy.../0/0/0/video.3gp') self.assertEqual(x.api_version, '2.0') self.assertEqual(x.api_version, x._dict['apiVersion']) def test_convert_to_jsonc(self): x = gdata.core._convert_to_jsonc(1) self.assert_(isinstance(x, (int, long))) self.assertEqual(x, 1) x = gdata.core._convert_to_jsonc([1, 'a']) self.assert_(isinstance(x, list)) self.assertEqual(len(x), 2) self.assert_(isinstance(x[0], (int, long))) self.assertEqual(x[0], 1) self.assert_(isinstance(x[1], (str, unicode))) self.assertEqual(x[1], 'a') x = gdata.core._convert_to_jsonc([{'b': 1}, 'a']) self.assert_(isinstance(x, list)) self.assertEqual(len(x), 2) self.assert_(isinstance(x[0], gdata.core.Jsonc)) self.assertEqual(x[0].b, 1) def test_non_json_members(self): x = gdata.core.Jsonc(alpha=1, _beta=2, deep={'_bbb': 3, 'aaa': 2}) x.test = 'a' x._bar = 'bacon' # Should be able to access the _beta member. self.assertEqual(x._beta, 2) self.assertEqual(getattr(x, '_beta'), 2) try: self.assertEqual(getattr(x.deep, '_bbb'), 3) except AttributeError: pass # There should not be a letter 'B' anywhere in the generated JSON. self.assertEqual(gdata.core.jsonc_to_string(x).find('B'), -1) # We should find a 'b' becuse we don't consider names of dict keys in # the constructor as aliases to camelCase names. self.assert_(not gdata.core.jsonc_to_string(x).find('b') == -1) def test_constructor(self): x = gdata.core.Jsonc(a=[{'x': 'y'}, 2]) self.assert_(isinstance(x, gdata.core.Jsonc)) self.assert_(isinstance(x.a, list)) self.assert_(isinstance(x.a[0], gdata.core.Jsonc)) self.assertEqual(x.a[0].x, 'y') self.assertEqual(x.a[1], 2) def test_read_json(self): x = gdata.core.parse_json(PLAYLIST_EXAMPLE) self.assert_(isinstance(x._dict, dict)) self.assertEqual(x._dict['apiVersion'], '2.0') self.assertEqual(x.api_version, '2.0') x = gdata.core.parse_json(VIDEO_EXAMPLE) self.assert_(isinstance(x._dict, dict)) self.assertEqual(x._dict['apiVersion'], '2.0') self.assertEqual(x.api_version, '2.0') x = gdata.core.parse_json(VIDEO_EXAMPLE) self.check_video_json(x) def test_write_json(self): x = gdata.core.Jsonc() x._dict['apiVersion'] = '2.0' x.data = {'totalItems': 800} x.data.items = [] x.data.items.append(gdata.core.Jsonc(view_count=220101)) x.data.items[0]._dict['favoriteCount'] = 201 x.data.items[0].comment_count = 22 x.data.items[0].content = { '1': 'rtsp://v5.cache3.c.youtube.com/CiILENy.../0/0/0/video.3gp'} self.check_video_json(x) def test_build_using_contructor(self): x = build_test_object() self.check_video_json(x) def test_to_dict(self): x = build_test_object() self.assertEqual( gdata.core._convert_to_object(x), {'data': {'totalItems': 800, 'items': [ {'content': { '1': 'rtsp://v5.cache3.c.youtube.com/CiILENy.../0/0/0/video.3gp'}, 'viewCount': 220101, 'commentCount': 22, 'favoriteCount': 201}]}, 'apiVersion': '2.0'}) def test_try_json_syntax(self): x = build_test_object() self.assertEqual(x.data.items[0].commentCount, 22) x.data.items[0].commentCount = 33 self.assertEqual(x.data.items[0].commentCount, 33) self.assertEqual(x.data.items[0].comment_count, 33) self.assertEqual(x.data.items[0]._dict['commentCount'], 33) def test_to_string(self): self.check_video_json( gdata.core.parse_json( gdata.core.jsonc_to_string( gdata.core._convert_to_object( build_test_object())))) def test_del_attr(self): x = build_test_object() self.assertEqual(x.data.items[0].commentCount, 22) del x.data.items[0].comment_count try: x.data.items[0].commentCount self.fail('Should not be able to access commentCount after deletion') except AttributeError: pass self.assertEqual(x.data.items[0].favorite_count, 201) del x.data.items[0].favorite_count try: x.data.items[0].favorite_count self.fail('Should not be able to access favorite_count after deletion') except AttributeError: pass try: x.data.items[0]._dict['favoriteCount'] self.fail('Should not see [\'favoriteCount\'] after deletion') except KeyError: pass self.assertEqual(x.data.items[0].view_count, 220101) del x.data.items[0]._dict['viewCount'] try: x.data.items[0].view_count self.fail('Should not be able to access view_count after deletion') except AttributeError: pass try: del x.data.missing self.fail('Should not delete a missing attribute') except AttributeError: pass def test_del_protected_attribute(self): x = gdata.core.Jsonc(public='x', _private='y') self.assertEqual(x.public, 'x') self.assertEqual(x._private, 'y') self.assertEqual(x['public'], 'x') try: x['_private'] self.fail('Should not be able to getitem with _name') except KeyError: pass del x._private try: x._private self.fail('Should not be able to access deleted member') except AttributeError: pass def test_get_set_del_item(self): x = build_test_object() # Check for expected members using different access patterns. self.assert_(isinstance(x._dict, dict)) self.assert_(isinstance(x['data'], gdata.core.Jsonc)) self.assert_(isinstance(x._dict['data'], gdata.core.Jsonc)) self.assert_(isinstance(x['data']._dict, dict)) self.assert_(isinstance(x._dict['data']._dict, dict)) self.assert_(isinstance(x['apiVersion'], (str, unicode))) try: x['api_version'] self.fail('Should not find using Python style name') except KeyError: pass self.assert_(isinstance(x.data['items'], list)) self.assert_(isinstance(x.data['items'][0]._dict['commentCount'], (int, long))) self.assert_(isinstance(x['data'].items[0]['favoriteCount'], (int, long))) self.assertEqual(x['data'].total_items, 800) self.assertEqual(x['data']['totalItems'], 800) self.assertEqual(x.data['items'][0]['viewCount'], 220101) self.assertEqual(x._dict['data'].items[0]._dict['viewCount'], 220101) self.assertEqual(x['data'].items[0].comment_count, 22) self.assertEqual(x.data.items[0]['commentCount'], 22) self.assertEqual(x.data.items[0]['favoriteCount'], 201) self.assertEqual(x.data.items[0]._dict['favoriteCount'], 201) self.assertEqual( x.data.items[0].content['1'], 'rtsp://v5.cache3.c.youtube.com/CiILENy.../0/0/0/video.3gp') self.assertEqual( x.data.items[0]['content']['1'], 'rtsp://v5.cache3.c.youtube.com/CiILENy.../0/0/0/video.3gp') self.assertEqual(x.api_version, '2.0') self.assertEqual(x['apiVersion'], x._dict['apiVersion']) # Set properties using setitem x['apiVersion'] = '3.2' self.assertEqual(x.api_version, '3.2') x.data['totalItems'] = 500 self.assertEqual(x['data'].total_items, 500) self.assertEqual(x['data'].items[0].favoriteCount, 201) try: del x['data']['favoriteCount'] self.fail('Should not be able to delete missing item') except KeyError: pass del x.data['items'][0]['favoriteCount'] try: x['data'].items[0].favoriteCount self.fail('Should not find favoriteCount removed using del item') except AttributeError: pass def suite(): return conf.build_suite([JsoncConversionTest, MemberNameConversionTest, JsoncObjectTest]) if __name__ == '__main__': unittest.main() ��������������������������gdata-2.0.18/tests/gdata_tests/spreadsheets/��������������������������������������������������������0000750�0364663�0011610�00000000000�12156625015�021070� 5����������������������������������������������������������������������������������������������������ustar �afshar��������������������������eng�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������gdata-2.0.18/tests/gdata_tests/spreadsheets/data_test.py��������������������������������������������0000750�0364663�0011610�00000070736�12156622363�023435� 0����������������������������������������������������������������������������������������������������ustar �afshar��������������������������eng�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/env python # # Copyright (C) 2009 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. # This module is used for version 2 of the Google Data APIs. __author__ = 'j.s@google.com (Jeff Scudder)' import unittest import gdata.spreadsheets.data import gdata.test_config as conf import atom.core SPREADSHEET = """<entry xmlns="http://www.w3.org/2005/Atom" xmlns:gd="http://schemas.google.com/g/2005" gd:etag='"BxAUSQUJRCp7ImBq"'> <id>http://spreadsheets.google.com/feeds/spreadsheets/private/full/key</id> <updated>2006-11-17T18:24:18.231Z</updated> <title type="text">Groceries R Us Groceries R Us Fitzwilliam Darcy fitz@gmail.com """ WORKSHEETS_FEED = """ http://spreadsheets.google.com/feeds/worksheets/key/private/full 2006-11-17T18:23:45.173Z Groceries R Us Fitzwilliam Darcy fitz@gmail.com 1 1 1 http://spreadsheets.google.com/feeds/worksheets/0/private/full/1 2006-11-17T18:23:45.173Z Sheet1 Sheet1 100 20 """ NEW_WORKSHEET = """ Expenses 50 10 """ EDIT_WORKSHEET = """ http://spreadsheets.google.com/feeds/worksheets/k/private/full/w 2007-07-30T18:51:30.666Z Income Expenses 45 15 """ NEW_TABLE = """ Table 1 This is a list of all who have registered to vote and whether or not they qualify to vote. """ TABLES_FEED = """ http://spreadsheets.google.com/feeds/key/tables 2009-04-28T02:38:53.134Z Sample table and record feed Liz liz@gmail.com 2 1 http://spreadsheets.google.com/feeds/key/tables/0 2009-04-28T01:20:32.707Z 2009-04-28T01:20:32.707Z Table 1 This is a list of all who have registered to vote and whether or not they qualify to vote. http://spreadsheets.google.com/feeds/key/tables/1 2009-04-28T01:20:38.313Z 2009-04-28T01:20:38.313Z Table 2 List of detailed information about each voter. """ NEW_RECORD = """ Darcy 2/10/1785 28 Darcy No """ RECORDS_FEED = """ http://spreadsheets.google.com/feeds/key/records/0 2009-04-28T02:38:53.134Z Table 1 Liz liz@gmail.com 2 1 http://spreadsheets.google.com/feeds/key/records/0/cn6ca 2009-04-28T02:38:53.134Z 2009-04-28T02:38:53.134Z Darcy Birthday: 2/10/1785, Age: 28, Name: Darcy, CanVote: No 2/10/1785 28 Darcy No http://spreadsheets.google.com/feeds/key/records/0/cokwr 2009-04-28T02:38:53.134Z 2009-04-28T02:38:53.134Z Jane Birthday: 1/6/1791, Age: 22, Name: Jane, CanVote: Yes 1/6/1791 22 Jane Yes """ LIST_FEED = """ http://spreadsheets.google.com/feeds/list/key/worksheetId/private/full 2006-11-17T18:23:45.173Z Sheet1 Fitzwilliam Darcy fitz@gmail.com 8 1 8 http://spreadsheets.google.com/feeds/list/k/w/private/full/r 2006-11-17T18:23:45.173Z Bingley Hours: 10, Items: 2, IPM: 0.0033 Bingley 10 2 0.0033 http://spreadsheets.google.com/feeds/list/k/w/private/full/rowId 2006-11-17T18:23:45.173Z Charlotte Hours: 60, Items: 18000, IPM: 5 Charlotte 60 18000 5 """ NEW_ROW = """ 1 1 60 Elizabeth Bennet """ UPDATED_ROW = """ http://spreadsheets.google.com/feeds/list/k/w/private/full/rowId 2006-11-17T18:23:45.173Z Bingley Hours: 10, Items: 2, IPM: 0.0033 Bingley 20 4 0.0033 """ CELLS_FEED = """ http://spreadsheets.google.com/feeds/cells/key/worksheetId/private/full 2006-11-17T18:27:32.543Z Sheet1 Fitzwilliam Darcy fitz@gmail.com 1 36 100 20 http://spreadsheets.google.com/feeds/cells/k/w/private/full/R1C1 2006-11-17T18:27:32.543Z A1 Name Name http://spreadsheets.google.com/feeds/cells/k/w/private/full/R1C2 2006-11-17T18:27:32.543Z B1 Hours Hours http://spreadsheets.google.com/feeds/cells/k/w/private/full/R9C4 2006-11-17T18:27:32.543Z D9 5 5 """ BATCH_CELLS = """ http://spreadsheets.google.com/feeds/cells/key/worksheetId/private/full A1 http://spreadsheets.google.com/feeds/cells/k/w/private/full/cellId A2 A2 http://spreadsheets.google.com/feeds/cells/k/w/private/full/cellId """ class SpreadsheetEntryTest(unittest.TestCase): def setUp(self): self.spreadsheet = atom.core.parse( SPREADSHEET, gdata.spreadsheets.data.Spreadsheet) def test_check_parsing(self): self.assertEqual(self.spreadsheet.etag, '"BxAUSQUJRCp7ImBq"') self.assertEqual(self.spreadsheet.id.text, 'http://spreadsheets.google.com/feeds/spreadsheets' '/private/full/key') self.assertEqual(self.spreadsheet.updated.text, '2006-11-17T18:24:18.231Z') self.assertEqual(self.spreadsheet.find_worksheets_feed(), 'http://spreadsheets.google.com/feeds/worksheets' '/key/private/full') self.assertEqual(self.spreadsheet.find_self_link(), 'http://spreadsheets.google.com/feeds/spreadsheets' '/private/full/key') def test_get_spreadsheet_key(self): self.assertEqual(self.spreadsheet.get_spreadsheet_key(), 'key') # Change the value of the self link. self.spreadsheet.id.text = '42' self.assertEqual(self.spreadsheet.GetSpreadsheetKey(), '42') class WorksheetEntryTest(unittest.TestCase): def setUp(self): self.worksheets = atom.core.parse( WORKSHEETS_FEED, gdata.spreadsheets.data.WorksheetsFeed) def test_check_parsing(self): self.assertEqual(len(self.worksheets.entry), 1) self.assertEqual(self.worksheets.entry[0].get_id(), 'http://spreadsheets.google.com/feeds/worksheets/0/private/full/1') def test_get_worksheet_id(self): self.assertEqual(self.worksheets.entry[0].get_worksheet_id(), '1') self.worksheets.entry[0].id.text = '////spam' self.assertEqual(self.worksheets.entry[0].GetWorksheetId(), 'spam') class ListEntryTest(unittest.TestCase): def test_get_and_set_column_value(self): row = atom.core.parse(NEW_ROW, gdata.spreadsheets.data.ListEntry) row.set_value('hours', '3') row.set_value('name', 'Lizzy') self.assertEqual(row.get_value('hours'), '3') self.assertEqual(row.get_value('ipm'), '1') self.assertEqual(row.get_value('items'), '60') self.assertEqual(row.get_value('name'), 'Lizzy') self.assertEqual(row.get_value('x'), None) row.set_value('x', 'Test') self.assertEqual(row.get_value('x'), 'Test') row_xml = str(row) self.assert_(row_xml.find(':x') > -1) self.assert_(row_xml.find('>Test -1) self.assert_(row_xml.find(':hours') > -1) self.assert_(row_xml.find('>3 -1) self.assert_(row_xml.find(':ipm') > -1) self.assert_(row_xml.find('>1 -1) self.assert_(row_xml.find(':items') > -1) self.assert_(row_xml.find('>60 -1) self.assert_(row_xml.find(':name') > -1) self.assert_(row_xml.find('>Lizzy -1) self.assertEqual(row_xml.find(':zzz'), -1) self.assertEqual(row_xml.find('>foo= 1) if len(db_list) >= 1: self.assert_(db_list[0].entry.title.text == db_title) # Test finding the database using the spreadsheet key db_list = self.client.GetDatabases(spreadsheet_key=db.spreadsheet_key) self.assert_(len(db_list) == 1) self.assert_(db_list[0].entry.title.text == db_title) # Delete the test spreadsheet time.sleep(10) db.Delete() class DatabaseTest(unittest.TestCase): def setUp(self): client = gdata.spreadsheet.text_db.DatabaseClient(username, password) self.db = client.CreateDatabase('google_spreadsheets_db unit test 2') def tearDown(self): time.sleep(10) self.db.Delete() def testCreateGetAndDeleteTable(self): table = self.db.CreateTable('test1', ['1','2','3']) # Try to get the new table using the worksheet id. table_list = self.db.GetTables(worksheet_id=table.worksheet_id) self.assert_(len(table_list) == 1) self.assert_(table_list[0].entry.title.text, 'test1') # Try to get the table using the name table_list = self.db.GetTables(name='test1') self.assert_(len(table_list) == 1) self.assert_(table_list[0].entry.title.text, 'test1') # Delete the table table.Delete() class TableTest(unittest.TestCase): def setUp(self): client = gdata.spreadsheet.text_db.DatabaseClient(username, password) self.db = client.CreateDatabase('google_spreadsheets_db unit test 3') self.table = self.db.CreateTable('test1', ['a','b','c_d','a', 'd:e']) def tearDown(self): time.sleep(10) self.db.Delete() def testCreateGetAndDeleteRecord(self): new_record = self.table.AddRecord({'a':'test1', 'b':'test2', 'cd':'test3', 'a_2':'test4', 'de':'test5'}) # Test getting record by line number. record = self.table.GetRecord(row_number=1) self.assert_(record is not None) self.assert_(record.content['a'] == 'test1') self.assert_(record.content['b'] == 'test2') self.assert_(record.content['cd'] == 'test3') self.assert_(record.content['a_2'] == 'test4') # Test getting record using the id. record_list = self.table.GetRecord(row_id=new_record.row_id) self.assert_(record is not None) # Delete the record. time.sleep(10) new_record.Delete() def testPushPullSyncing(self): # Get two copies of the same row. first_copy = self.table.AddRecord({'a':'1', 'b':'2', 'cd':'3', 'a_2':'4', 'de':'5'}) second_copy = self.table.GetRecord(first_copy.row_id) # Make changes in the first copy first_copy.content['a'] = '7' first_copy.content['b'] = '9' # Try to get the changes before they've been committed second_copy.Pull() self.assert_(second_copy.content['a'] == '1') self.assert_(second_copy.content['b'] == '2') # Commit the changes, the content should now be different first_copy.Push() second_copy.Pull() self.assert_(second_copy.content['a'] == '7') self.assert_(second_copy.content['b'] == '9') # Make changes to the second copy, push, then try to push changes from # the first copy. first_copy.content['a'] = '10' second_copy.content['a'] = '15' first_copy.Push() try: second_copy.Push() # The second update should raise and exception due to a 409 conflict. self.fail() except gdata.spreadsheet.service.RequestError: pass except Exception, error: #TODO: Why won't the except RequestError catch this? pass def testFindRecords(self): # Add lots of test records: self.table.AddRecord({'a':'1', 'b':'2', 'cd':'3', 'a_2':'4', 'de':'5'}) self.table.AddRecord({'a':'hi', 'b':'2', 'cd':'20', 'a_2':'4', 'de':'5'}) self.table.AddRecord({'a':'2', 'b':'2', 'cd':'3'}) self.table.AddRecord({'a':'2', 'b':'2', 'cd':'15', 'de':'7'}) self.table.AddRecord({'a':'hi hi hi', 'b':'2', 'cd':'15', 'de':'7'}) self.table.AddRecord({'a':'"5"', 'b':'5', 'cd':'15', 'de':'7'}) self.table.AddRecord({'a':'5', 'b':'5', 'cd':'15', 'de':'7'}) time.sleep(10) matches = self.table.FindRecords('a == 1') self.assert_(len(matches) == 1) self.assert_(matches[0].content['a'] == '1') self.assert_(matches[0].content['b'] == '2') matches = self.table.FindRecords('a > 1 && cd < 20') self.assert_(len(matches) == 4) matches = self.table.FindRecords('cd < de') self.assert_(len(matches) == 7) matches = self.table.FindRecords('a == b') self.assert_(len(matches) == 0) matches = self.table.FindRecords('a == 5') self.assert_(len(matches) == 1) def testIterateResultSet(self): # Populate the table with test data. self.table.AddRecord({'a':'1', 'b':'2', 'cd':'3', 'a_2':'4', 'de':'5'}) self.table.AddRecord({'a':'hi', 'b':'2', 'cd':'20', 'a_2':'4', 'de':'5'}) self.table.AddRecord({'a':'2', 'b':'2', 'cd':'3'}) self.table.AddRecord({'a':'2', 'b':'2', 'cd':'15', 'de':'7'}) self.table.AddRecord({'a':'hi hi hi', 'b':'2', 'cd':'15', 'de':'7'}) self.table.AddRecord({'a':'"5"', 'b':'5', 'cd':'15', 'de':'7'}) self.table.AddRecord({'a':'5', 'b':'5', 'cd':'15', 'de':'7'}) # Get the first two rows. records = self.table.GetRecords(1, 2) self.assert_(len(records) == 2) self.assert_(records[0].content['a'] == '1') self.assert_(records[1].content['a'] == 'hi') # Then get the next two rows. next_records = records.GetNext() self.assert_(len(next_records) == 2) self.assert_(next_records[0].content['a'] == '2') self.assert_(next_records[0].content['cd'] == '3') self.assert_(next_records[1].content['cd'] == '15') self.assert_(next_records[1].content['de'] == '7') def testLookupFieldsOnPreexistingTable(self): existing_table = self.db.GetTables(name='test1')[0] existing_table.LookupFields() self.assertEquals(existing_table.fields, ['a', 'b', 'cd', 'a_2', 'de']) if __name__ == '__main__': if not username: username = raw_input('Spreadsheets API | Text DB Tests\n' 'Please enter your username: ') if not password: password = getpass.getpass() unittest.main() gdata-2.0.18/tests/gdata_tests/spreadsheet/__init__.py0000640036466300116100000000000012156622363023010 0ustar afshareng00000000000000gdata-2.0.18/tests/gdata_tests/resumable_upload_test.py0000750036466300116100000001073412156622363023345 0ustar afshareng00000000000000#!/usr/bin/env python # # Copyright (C) 2008, 2009 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. # This module is used for version 2 of the Google Data APIs. __author__ = 'e.bidelman@google.com (Eric Bidelman)' import os import unittest import atom.data import gdata.client import gdata.data import gdata.gauth import gdata.docs.client import gdata.docs.data import gdata.test_config as conf TEST_FILE_LOCATION_OPTION = conf.Option( 'file', 'Please enter the full path to a test file to upload', description=('This test file will be uploaded to DocList which. An example ' 'file can be found in tests/gdata_tests/docs/test.doc')) CONTENT_TYPE_OPTION = conf.Option( 'contenttype', 'Please enter the mimetype of the file', description='The content type should match that of the upload file.') conf.options.register_option(TEST_FILE_LOCATION_OPTION) conf.options.register_option(CONTENT_TYPE_OPTION) class ResumableUploadTestCase(unittest.TestCase): def setUp(self): self.client = None if conf.options.get_value('runlive') == 'true': self.client = gdata.docs.client.DocsClient(source='ResumableUploadTest') if conf.options.get_value('ssl') == 'true': self.client.ssl = True self.f = open(conf.options.get_value('file')) self.content_type = conf.options.get_value('contenttype') conf.configure_client( self.client, 'ResumableUploadTest', self.client.auth_service) def tearDown(self): conf.close_client(self.client) self.f.close() def testUploadEntireDocumentAndUpdate(self): if not conf.options.get_value('runlive') == 'true': return # Either load the recording or prepare to make a live request. conf.configure_cache(self.client, 'testUploadDocument') uploader = gdata.client.ResumableUploader( self.client, self.f, self.content_type, os.path.getsize(self.f.name), chunk_size=20000, # 20000 bytes. desired_class=gdata.docs.data.DocsEntry) e = gdata.docs.data.DocsEntry( title=atom.data.Title(text='MyResumableTitleEntireFile')) e.category.append(gdata.docs.data.make_kind_category('document')) e.writers_can_invite = gdata.docs.data.WritersCanInvite(value='false') entry = uploader.UploadFile( '/feeds/upload/create-session/default/private/full', entry=e) # Verify upload has really completed. self.assertEqual(uploader.QueryUploadStatus(), True) self.assert_(isinstance(entry, gdata.docs.data.DocsEntry)) self.assertEqual(entry.title.text, 'MyResumableTitleEntireFile') self.assertEqual(entry.GetDocumentType(), 'document') self.assertEqual(entry.writers_can_invite.value, 'false') self.assertEqual(int(entry.quota_bytes_used.text), 0) self.client.Delete(entry, force=True) def testUploadDocumentInChunks(self): if not conf.options.get_value('runlive') == 'true': return # Either load the recording or prepare to make a live request. conf.configure_cache(self.client, 'testUploadDocumentInChunks') uploader = gdata.client.ResumableUploader( self.client, self.f, self.content_type, os.path.getsize(self.f.name), desired_class=gdata.docs.data.DocsEntry) uploader._InitSession( '/feeds/upload/create-session/default/private/full', headers={'Slug': 'MyManualChunksNoAtomTitle'}) start_byte = 0 entry = None while not entry: entry = uploader.UploadChunk( start_byte, uploader.file_handle.read(uploader.chunk_size)) start_byte += uploader.chunk_size # Verify upload has really completed. self.assertEqual(uploader.QueryUploadStatus(), True) self.assert_(isinstance(entry, gdata.docs.data.DocsEntry)) self.assertEqual(entry.title.text, 'MyManualChunksNoAtomTitle') self.assertEqual(entry.GetDocumentType(), 'document') self.client.Delete(entry, force=True) def suite(): return conf.build_suite([ResumableUploadTestCase]) if __name__ == '__main__': unittest.TextTestRunner().run(suite()) gdata-2.0.18/tests/gdata_tests/blogger/0000750036466300116100000000000012156625015020017 5ustar afshareng00000000000000gdata-2.0.18/tests/gdata_tests/blogger/service_test.py0000640036466300116100000000677212156622363023110 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright (C) 2007 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. """Unit tests to exercise server interactions for blogger.""" __author__ = 'api.jscudder (Jeffrey Scudder)' import unittest import getpass import atom from gdata import test_data import gdata.blogger import gdata.blogger.service username = '' password = '' test_blog_id = '' class BloggerCrudTests(unittest.TestCase): def setUp(self): self.client = gdata.blogger.service.BloggerService(email=username, password=password, source='GoogleInc-PythonBloggerUnitTests-1') # TODO: if the test_blog_id is not set, get the list of the user's blogs # and prompt for which blog to add the test posts to. self.client.ProgrammaticLogin() def testPostDraftUpdateAndDelete(self): new_entry = gdata.blogger.BlogPostEntry(title=atom.Title( text='Unit Test Post')) new_entry.content = atom.Content('text', None, 'Hello World') # Make this post a draft so it will not appear publicly on the blog. new_entry.control = atom.Control(draft=atom.Draft(text='yes')) new_entry.AddLabel('test') posted = self.client.AddPost(new_entry, blog_id=test_blog_id) self.assertEquals(posted.title.text, new_entry.title.text) # Should be one category in the posted entry for the 'test' label. self.assertEquals(len(posted.category), 1) self.assert_(isinstance(posted, gdata.blogger.BlogPostEntry)) # Change the title and add more labels. posted.title.text = 'Updated' posted.AddLabel('second') updated = self.client.UpdatePost(entry=posted) self.assertEquals(updated.title.text, 'Updated') self.assertEquals(len(updated.category), 2) # Cleanup and delete the draft blog post. self.client.DeletePost(entry=posted) def testAddComment(self): # Create a test post to add comments to. new_entry = gdata.blogger.BlogPostEntry(title=atom.Title( text='Comments Test Post')) new_entry.content = atom.Content('text', None, 'Hello Comments') target_post = self.client.AddPost(new_entry, blog_id=test_blog_id) blog_id = target_post.GetBlogId() post_id = target_post.GetPostId() new_comment = gdata.blogger.CommentEntry() new_comment.content = atom.Content(text='Test comment') posted = self.client.AddComment(new_comment, blog_id=blog_id, post_id=post_id) self.assertEquals(posted.content.text, new_comment.content.text) # Cleanup and delete the comment test blog post. self.client.DeletePost(entry=target_post) class BloggerQueryTests(unittest.TestCase): def testConstructBlogQuery(self): pass def testConstructBlogQuery(self): pass def testConstructBlogQuery(self): pass if __name__ == '__main__': print ('NOTE: Please run these tests only with a test account. ' + 'The tests may delete or update your data.') username = raw_input('Please enter your username: ') password = getpass.getpass() test_blog_id = raw_input('Please enter the blog id for the test blog: ') unittest.main() gdata-2.0.18/tests/gdata_tests/blogger/data_test.py0000750036466300116100000001015612156622363022352 0ustar afshareng00000000000000#!/usr/bin/env python # # Copyright (C) 2009 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. __author__ = 'j.s@google.com (Jeff Scudder)' import unittest from gdata import test_data import gdata.blogger.data import atom.core import gdata.test_config as conf class BlogEntryTest(unittest.TestCase): def testBlogEntryFromString(self): entry = atom.core.parse(test_data.BLOG_ENTRY, gdata.blogger.data.Blog) self.assertEquals(entry.GetBlogName(), 'blogName') self.assertEquals(entry.GetBlogId(), 'blogID') self.assertEquals(entry.title.text, 'Lizzy\'s Diary') def testBlogPostFeedFromString(self): feed = atom.core.parse(test_data.BLOG_POSTS_FEED, gdata.blogger.data.BlogPostFeed) self.assertEquals(len(feed.entry), 1) self.assert_(isinstance(feed, gdata.blogger.data.BlogPostFeed)) self.assert_(isinstance(feed.entry[0], gdata.blogger.data.BlogPost)) self.assertEquals(feed.entry[0].GetPostId(), 'postID') self.assertEquals(feed.entry[0].GetBlogId(), 'blogID') self.assertEquals(feed.entry[0].title.text, 'Quite disagreeable') def testCommentFeedFromString(self): feed = atom.core.parse(test_data.BLOG_COMMENTS_FEED, gdata.blogger.data.CommentFeed) self.assertEquals(len(feed.entry), 1) self.assert_(isinstance(feed, gdata.blogger.data.CommentFeed)) self.assert_(isinstance(feed.entry[0], gdata.blogger.data.Comment)) self.assertEquals(feed.entry[0].get_blog_id(), 'blogID') self.assertEquals(feed.entry[0].get_blog_name(), 'a-blogName') self.assertEquals(feed.entry[0].get_comment_id(), 'commentID') self.assertEquals(feed.entry[0].title.text, 'This is my first comment') self.assertEquals(feed.entry[0].in_reply_to.source, 'http://blogName.blogspot.com/feeds/posts/default/postID') self.assertEquals(feed.entry[0].in_reply_to.ref, 'tag:blogger.com,1999:blog-blogID.post-postID') self.assertEquals(feed.entry[0].in_reply_to.href, 'http://blogName.blogspot.com/2007/04/first-post.html') self.assertEquals(feed.entry[0].in_reply_to.type, 'text/html') def testIdParsing(self): entry = gdata.blogger.data.Blog() entry.id = atom.data.Id( text='tag:blogger.com,1999:user-146606542.blog-4023408167658848') self.assertEquals(entry.GetBlogId(), '4023408167658848') entry.id = atom.data.Id(text='tag:blogger.com,1999:blog-4023408167658848') self.assertEquals(entry.GetBlogId(), '4023408167658848') class InReplyToTest(unittest.TestCase): def testToAndFromString(self): in_reply_to = gdata.blogger.data.InReplyTo( href='http://example.com/href', ref='http://example.com/ref', source='http://example.com/my_post', type='text/html') xml_string = str(in_reply_to) parsed = atom.core.parse(xml_string, gdata.blogger.data.InReplyTo) self.assertEquals(parsed.source, in_reply_to.source) self.assertEquals(parsed.href, in_reply_to.href) self.assertEquals(parsed.ref, in_reply_to.ref) self.assertEquals(parsed.type, in_reply_to.type) class CommentTest(unittest.TestCase): def testToAndFromString(self): comment = gdata.blogger.data.Comment( content=atom.data.Content(text='Nifty!'), in_reply_to=gdata.blogger.data.InReplyTo( source='http://example.com/my_post')) parsed = atom.core.parse(str(comment), gdata.blogger.data.Comment) self.assertEquals(parsed.in_reply_to.source, comment.in_reply_to.source) self.assertEquals(parsed.content.text, comment.content.text) def suite(): return conf.build_suite([BlogEntryTest, InReplyToTest, CommentTest]) if __name__ == '__main__': unittest.main() gdata-2.0.18/tests/gdata_tests/blogger/__init__.py0000640036466300116100000000000012156622363022122 0ustar afshareng00000000000000gdata-2.0.18/tests/gdata_tests/blogger/live_client_test.py0000750036466300116100000001330712156622363023737 0ustar afshareng00000000000000#!/usr/bin/env python # # Copyright (C) 2009 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. # This module is used for version 2 of the Google Data APIs. # These tests attempt to connect to Google servers. __author__ = 'j.s@google.com (Jeff Scudder)' import unittest import gdata.blogger.client import gdata.blogger.data import gdata.gauth import gdata.client import atom.http_core import atom.mock_http_core import atom.core import gdata.data import gdata.test_config as conf conf.options.register_option(conf.BLOG_ID_OPTION) class BloggerClientTest(unittest.TestCase): def setUp(self): self.client = None if conf.options.get_value('runlive') == 'true': self.client = gdata.blogger.client.BloggerClient() conf.configure_client(self.client, 'BloggerTest', 'blogger') def tearDown(self): conf.close_client(self.client) def test_create_update_delete(self): if not conf.options.get_value('runlive') == 'true': return # Either load the recording or prepare to make a live request. conf.configure_cache(self.client, 'test_create_update_delete') # Add a blog post. created = self.client.add_post(conf.options.get_value('blogid'), 'test post from BloggerClientTest', 'Hey look, another test!', labels=['test', 'python']) self.assertEqual(created.title.text, 'test post from BloggerClientTest') self.assertEqual(created.content.text, 'Hey look, another test!') self.assertEqual(len(created.category), 2) self.assert_(created.control is None) # Change the title of the blog post we just added. created.title.text = 'Edited' updated = self.client.update(created) self.assertEqual(updated.title.text, 'Edited') self.assert_(isinstance(updated, gdata.blogger.data.BlogPost)) self.assertEqual(updated.content.text, created.content.text) # Delete the test entry from the blog. self.client.delete(updated) def test_create_draft_post(self): if not conf.options.get_value('runlive') == 'true': return conf.configure_cache(self.client, 'test_create_draft_post') # Add a draft blog post. created = self.client.add_post(conf.options.get_value('blogid'), 'draft test post from BloggerClientTest', 'This should only be a draft.', labels=['test2', 'python'], draft=True) self.assertEqual(created.title.text, 'draft test post from BloggerClientTest') self.assertEqual(created.content.text, 'This should only be a draft.') self.assertEqual(len(created.category), 2) self.assert_(created.control is not None) self.assert_(created.control.draft is not None) self.assertEqual(created.control.draft.text, 'yes') # Publish the blog post. created.control.draft.text = 'no' updated = self.client.update(created) if updated.control is not None and updated.control.draft is not None: self.assertNotEqual(updated.control.draft.text, 'yes') # Delete the test entry from the blog using the URL instead of the entry. self.client.delete(updated.find_edit_link()) def test_create_draft_page(self): if not conf.options.get_value('runlive') == 'true': return conf.configure_cache(self.client, 'test_create_draft_page') # List all pages on the blog. pages_before = self.client.get_pages(conf.options.get_value('blogid')) # Add a draft page to blog. created = self.client.add_page(conf.options.get_value('blogid'), 'draft page from BloggerClientTest', 'draft content', draft=True) self.assertEqual(created.title.text, 'draft page from BloggerClientTest') self.assertEqual(created.content.text, 'draft content') self.assert_(created.control is not None) self.assert_(created.control.draft is not None) self.assertEqual(created.control.draft.text, 'yes') self.assertEqual(str(int(created.get_page_id())), created.get_page_id()) # List all pages after adding one. pages_after = self.client.get_pages(conf.options.get_value('blogid')) self.assertEqual(len(pages_before.entry) + 1, len(pages_after.entry)) # Publish page. created.control.draft.text = 'no' updated = self.client.update(created) if updated.control is not None and updated.control.draft is not None: self.assertNotEqual(updated.control.draft.text, 'yes') # Delete test page. self.client.delete(updated.find_edit_link()) pages_after = self.client.get_pages(conf.options.get_value('blogid')) self.assertEqual(len(pages_before.entry), len(pages_after.entry)) def test_retrieve_post_with_categories(self): if not conf.options.get_value('runlive') == 'true': return conf.configure_cache(self.client, 'test_retrieve_post_with_categories') query = gdata.blogger.client.Query(categories=["news"], strict=True) posts = self.client.get_posts(conf.options.get_value('blogid'), query=query) def suite(): return conf.build_suite([BloggerClientTest]) if __name__ == '__main__': unittest.TextTestRunner().run(suite()) gdata-2.0.18/tests/gdata_tests/analytics/0000750036466300116100000000000012156625015020365 5ustar afshareng00000000000000gdata-2.0.18/tests/gdata_tests/analytics/data_test.py0000750036466300116100000003746612156622363022735 0ustar afshareng00000000000000#!/usr/bin/env python # # 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. """Unit Tests for Google Analytics Data Export API and Management APIs. Although the Data Export API and Management API conceptually operate on different parts of Google Analytics, the APIs share some code so they are released in the same module. AccountFeedTest: All unit tests for AccountFeed class. DataFeedTest: All unit tests for DataFeed class. ManagementFeedAccountTest: Unit tests for ManagementFeed class. ManagementFeedGoalTest: Unit tests for ManagementFeed class. ManagementFeedAdvSegTest: Unit tests for ManagementFeed class. """ __author__ = 'api.nickm@google.com (Nick Mihailovski)' import unittest from gdata import test_data import gdata.analytics.data import atom.core import gdata.test_config as conf class AccountFeedTest(unittest.TestCase): """Unit test for all custom elements in the Account Feed.""" def setUp(self): """Retrieves the test XML feed into a AccountFeed object.""" self.feed = atom.core.parse(test_data.ANALYTICS_ACCOUNT_FEED, gdata.analytics.data.AccountFeed) def testSegment(self): """Tests Segment class in Google Analytics Account Feed.""" segment = self.feed.segment[0] self.assertEquals(segment.id, 'gaid::-11') self.assertEquals(segment.name, 'Visits from iPhones') def testSegmentDefinition(self): """Tests Definition class in Google Analytics Account Feed.""" definition = self.feed.segment[0].definition self.assertEquals(definition.text, 'ga:operatingSystem==iPhone') def testEntryTableId(self): """Tests custom classes in Google Analytics Account Feed.""" entry = self.feed.entry[0] self.assertEquals(entry.table_id.text, 'ga:1174') def testEntryProperty(self): """Tests the property classes in Google Analytics Account Feed.""" property = self.feed.entry[0].property self.assertEquals(property[0].name, 'ga:accountId') self.assertEquals(property[0].value, '30481') self.assertEquals(property[1].name, 'ga:accountName') self.assertEquals(property[1].value, 'Google Store') self.assertEquals(property[2].name, 'ga:profileId') self.assertEquals(property[2].value, '1174') self.assertEquals(property[3].name, 'ga:webPropertyId') self.assertEquals(property[3].value, 'UA-30481-1') self.assertEquals(property[4].name, 'ga:currency') self.assertEquals(property[4].value, 'USD') self.assertEquals(property[5].name, 'ga:timezone') self.assertEquals(property[5].value, 'America/Los_Angeles') def testEntryGetProperty(self): """Tests GetProperty inherited class in the AccountEntry class.""" entry = self.feed.entry[0] self.assertEquals(entry.GetProperty('ga:accountId').value, '30481') self.assertEquals(entry.GetProperty('ga:accountName').value, 'Google Store') self.assertEquals(entry.GetProperty('ga:profileId').value, '1174') self.assertEquals(entry.GetProperty('ga:webPropertyId').value, 'UA-30481-1') self.assertEquals(entry.GetProperty('ga:currency').value, 'USD') self.assertEquals(entry.GetProperty('ga:timezone').value, 'America/Los_Angeles') def testGoal(self): """Tests Goal class in Google Anlaytics Account Feed.""" goal = self.feed.entry[0].goal[0] self.assertEquals(goal.number, '1') self.assertEquals(goal.name, 'Completing Order') self.assertEquals(goal.value, '10.0') self.assertEquals(goal.active, 'true') def testDestination(self): """Tests Destination class in Google Analytics Account Feed.""" destination = self.feed.entry[0].goal[0].destination self.assertEquals(destination.expression, '/purchaseComplete.html') self.assertEquals(destination.case_sensitive, 'false') self.assertEquals(destination.match_type, 'regex') self.assertEquals(destination.step1_required, 'false') def testStep(self): """Tests Step class in Google Analytics Account Feed.""" step = self.feed.entry[0].goal[0].destination.step[0] self.assertEquals(step.number, '1') self.assertEquals(step.name, 'View Product Categories') self.assertEquals(step.path, '/Apps|Accessories|Fun|Kid\+s|Office') def testEngagemet(self): """Tests Engagement class in Google Analytics Account Feed.""" engagement = self.feed.entry[0].goal[1].engagement self.assertEquals(engagement.type, 'timeOnSite') self.assertEquals(engagement.comparison, '>') self.assertEquals(engagement.threshold_value, '300') def testCustomVariable(self): """Tests CustomVariable class in Google Analytics Account Feed.""" customVar = self.feed.entry[0].custom_variable[0] self.assertEquals(customVar.index, '1') self.assertEquals(customVar.name, 'My Custom Variable') self.assertEquals(customVar.scope, '3') class DataFeedTest(unittest.TestCase): """Unit test for all custom elements in the Data Feed.""" def setUp(self): """Retrieves the test XML feed into a DataFeed object.""" self.feed = atom.core.parse(test_data.ANALYTICS_DATA_FEED, gdata.analytics.data.DataFeed) def testDataFeed(self): """Tests custom classes in Google Analytics Data Feed.""" self.assertEquals(self.feed.start_date.text, '2008-10-01') self.assertEquals(self.feed.end_date.text, '2008-10-31') def testAggregates(self): """Tests Aggregates class in Google Analytics Data Feed.""" self.assert_(self.feed.aggregates is not None) def testContainsSampledData(self): """Tests ContainsSampledData class in Google Analytics Data Feed.""" contains_sampled_data = self.feed.contains_sampled_data.text self.assertEquals(contains_sampled_data, 'true') self.assertTrue(self.feed.HasSampledData()) def testAggregatesElements(self): """Tests Metrics class in Aggregates class.""" metric = self.feed.aggregates.metric[0] self.assertEquals(metric.confidence_interval, '0.0') self.assertEquals(metric.name, 'ga:visits') self.assertEquals(metric.type, 'integer') self.assertEquals(metric.value, '136540') metric = self.feed.aggregates.GetMetric('ga:visits') self.assertEquals(metric.confidence_interval, '0.0') self.assertEquals(metric.name, 'ga:visits') self.assertEquals(metric.type, 'integer') self.assertEquals(metric.value, '136540') def testDataSource(self): """Tests DataSources class in Google Analytics Data Feed.""" self.assert_(self.feed.data_source[0] is not None) def testDataSourceTableId(self): """Tests TableId class in the DataSource class.""" table_id = self.feed.data_source[0].table_id self.assertEquals(table_id.text, 'ga:1174') def testDataSourceTableName(self): """Tests TableName class in the DataSource class.""" table_name = self.feed.data_source[0].table_name self.assertEquals(table_name.text, 'www.googlestore.com') def testDataSourceProperty(self): """Tests Property class in the DataSource class.""" property = self.feed.data_source[0].property self.assertEquals(property[0].name, 'ga:profileId') self.assertEquals(property[0].value, '1174') self.assertEquals(property[1].name, 'ga:webPropertyId') self.assertEquals(property[1].value, 'UA-30481-1') self.assertEquals(property[2].name, 'ga:accountName') self.assertEquals(property[2].value, 'Google Store') def testDataSourceGetProperty(self): """Tests GetProperty utility method in the DataSource class.""" ds = self.feed.data_source[0] self.assertEquals(ds.GetProperty('ga:profileId').value, '1174') self.assertEquals(ds.GetProperty('ga:webPropertyId').value, 'UA-30481-1') self.assertEquals(ds.GetProperty('ga:accountName').value, 'Google Store') def testSegment(self): """Tests Segment class in DataFeed class.""" segment = self.feed.segment self.assertEquals(segment.id, 'gaid::-11') self.assertEquals(segment.name, 'Visits from iPhones') def testSegmentDefinition(self): """Tests Definition class in Segment class.""" definition = self.feed.segment.definition self.assertEquals(definition.text, 'ga:operatingSystem==iPhone') def testEntryDimension(self): """Tests Dimension class in Entry class.""" dim = self.feed.entry[0].dimension[0] self.assertEquals(dim.name, 'ga:source') self.assertEquals(dim.value, 'blogger.com') def testEntryGetDimension(self): """Tests GetDimension utility method in the Entry class.""" dim = self.feed.entry[0].GetDimension('ga:source') self.assertEquals(dim.name, 'ga:source') self.assertEquals(dim.value, 'blogger.com') error = self.feed.entry[0].GetDimension('foo') self.assertEquals(error, None) def testEntryMetric(self): """Tests Metric class in Entry class.""" met = self.feed.entry[0].metric[0] self.assertEquals(met.confidence_interval, '0.0') self.assertEquals(met.name, 'ga:visits') self.assertEquals(met.type, 'integer') self.assertEquals(met.value, '68140') def testEntryGetMetric(self): """Tests GetMetric utility method in the Entry class.""" met = self.feed.entry[0].GetMetric('ga:visits') self.assertEquals(met.confidence_interval, '0.0') self.assertEquals(met.name, 'ga:visits') self.assertEquals(met.type, 'integer') self.assertEquals(met.value, '68140') error = self.feed.entry[0].GetMetric('foo') self.assertEquals(error, None) def testEntryGetObject(self): """Tests GetObjectOf utility method in Entry class.""" entry = self.feed.entry[0] dimension = entry.GetObject('ga:source') self.assertEquals(dimension.name, 'ga:source') self.assertEquals(dimension.value, 'blogger.com') metric = entry.GetObject('ga:visits') self.assertEquals(metric.name, 'ga:visits') self.assertEquals(metric.value, '68140') self.assertEquals(metric.type, 'integer') self.assertEquals(metric.confidence_interval, '0.0') error = entry.GetObject('foo') self.assertEquals(error, None) class ManagementFeedProfileTest(unittest.TestCase): """Unit test for all property elements in Google Analytics Management Feed. Since the Account, Web Property and Profile feed all have the same structure and XML elements, this single test case covers all three feeds. """ def setUp(self): """Retrieves the test XML feed into a DataFeed object.""" self.feed = atom.core.parse(test_data.ANALYTICS_MGMT_PROFILE_FEED, gdata.analytics.data.ManagementFeed) def testFeedKindAttribute(self): """Tests the kind attribute in the feed.""" self.assertEqual(self.feed.kind, 'analytics#profiles') def testEntryKindAttribute(self): """tests the kind attribute in the entry.""" entry_kind = self.feed.entry[0].kind self.assertEqual(entry_kind, 'analytics#profile') def testEntryProperty(self): """Tests property classes in Managment Entry class.""" property = self.feed.entry[0].property self.assertEquals(property[0].name, 'ga:accountId') self.assertEquals(property[0].value, '30481') def testEntryGetProperty(self): """Tests GetProperty helper method in Management Entry class.""" entry = self.feed.entry[0] self.assertEquals(entry.GetProperty('ga:accountId').value, '30481') def testGetParentLinks(self): """Tests GetParentLinks utility method.""" parent_links = self.feed.entry[0].GetParentLinks() self.assertEquals(len(parent_links), 1) parent_link = parent_links[0] self.assertEquals(parent_link.rel, 'http://schemas.google.com/ga/2009#parent') self.assertEquals(parent_link.type, 'application/atom+xml') self.assertEquals(parent_link.href, 'https://www.google.com/analytics/feeds/datasources' '/ga/accounts/30481/webproperties/UA-30481-1') self.assertEquals(parent_link.target_kind, 'analytics#webproperty') def testGetChildLinks(self): """Tests GetChildLinks utility method.""" child_links = self.feed.entry[0].GetChildLinks() self.assertEquals(len(child_links), 1) self.ChildLinkTestHelper(child_links[0]) def testGetChildLink(self): """Tests getChildLink utility method.""" child_link = self.feed.entry[0].GetChildLink('analytics#goals') self.ChildLinkTestHelper(child_link) child_link = self.feed.entry[0].GetChildLink('foo_bar') self.assertEquals(child_link, None) def ChildLinkTestHelper(self, child_link): """Common method to test a child link.""" self.assertEquals(child_link.rel, 'http://schemas.google.com/ga/2009#child') self.assertEquals(child_link.type, 'application/atom+xml') self.assertEquals(child_link.href, 'https://www.google.com/analytics/feeds/datasources' '/ga/accounts/30481/webproperties/UA-30481-1/profiles/1174/goals') self.assertEquals(child_link.target_kind, 'analytics#goals') class ManagementFeedGoalTest(unittest.TestCase): """Unit test for all Goal elements in Management Feed.""" def setUp(self): """Retrieves the test XML feed into a DataFeed object.""" self.feed = atom.core.parse(test_data.ANALYTICS_MGMT_GOAL_FEED, gdata.analytics.data.ManagementFeed) def testEntryGoal(self): """Tests Goal class in Google Anlaytics Account Feed.""" goal = self.feed.entry[0].goal self.assertEquals(goal.number, '1') self.assertEquals(goal.name, 'Completing Order') self.assertEquals(goal.value, '10.0') self.assertEquals(goal.active, 'true') def testGoalDestination(self): """Tests Destination class in Google Analytics Account Feed.""" destination = self.feed.entry[0].goal.destination self.assertEquals(destination.expression, '/purchaseComplete.html') self.assertEquals(destination.case_sensitive, 'false') self.assertEquals(destination.match_type, 'regex') self.assertEquals(destination.step1_required, 'false') def testGoalDestinationStep(self): """Tests Step class in Google Analytics Account Feed.""" step = self.feed.entry[0].goal.destination.step[0] self.assertEquals(step.number, '1') self.assertEquals(step.name, 'View Product Categories') self.assertEquals(step.path, '/Apps|Accessories') def testGoalEngagemet(self): """Tests Engagement class in Google Analytics Account Feed.""" engagement = self.feed.entry[1].goal.engagement self.assertEquals(engagement.type, 'timeOnSite') self.assertEquals(engagement.comparison, '>') self.assertEquals(engagement.threshold_value, '300') class ManagementFeedAdvSegTest(unittest.TestCase): """Unit test for all Advanced Segment elements in Management Feed.""" def setUp(self): """Retrieves the test XML feed into a DataFeed object.""" self.feed = atom.core.parse(test_data.ANALYTICS_MGMT_ADV_SEGMENT_FEED, gdata.analytics.data.ManagementFeed) def testEntrySegment(self): """Tests Segment class in ManagementEntry class.""" segment = self.feed.entry[0].segment self.assertEquals(segment.id, 'gaid::0') self.assertEquals(segment.name, 'Sources Form Google') def testSegmentDefinition(self): """Tests Definition class in Segment class.""" definition = self.feed.entry[0].segment.definition self.assertEquals(definition.text, 'ga:source=~^\Qgoogle\E') def suite(): """Test Account Feed, Data Feed and Management API Feeds.""" return conf.build_suite([ AccountFeedTest, DataFeedTest, ManagementFeedProfileTest, ManagementFeedGoalTest, ManagementFeedAdvSegTest]) if __name__ == '__main__': unittest.main() gdata-2.0.18/tests/gdata_tests/analytics/__init__.py0000640036466300116100000000000012156622363022470 0ustar afshareng00000000000000gdata-2.0.18/tests/gdata_tests/analytics/live_client_test.py0000640036466300116100000000700412156622363024300 0ustar afshareng00000000000000#!/usr/bin/env python # # 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. """Functional Tests for Google Analytics Account Feed and Data Feed. AnalyticsClientTest: Tests making live requests to Google Analytics API. """ __author__ = 'api.nickm@google.com (Nick Mihailovski)' import unittest import gdata.client import gdata.data import gdata.gauth import gdata.analytics.client import gdata.test_config as conf conf.options.register_option(conf.GA_TABLE_ID) class AnalyticsClientTest(unittest.TestCase): """Tests creating an Account Feed query and making a request to the Google Analytics Account Feed.""" def setUp(self): """Creates an AnalyticsClient object.""" self.client = None if conf.options.get_value('runlive') == 'true': self.client = gdata.analytics.client.AnalyticsClient() self.client.http_client.debug = True conf.configure_client( self.client, 'AnalyticsClientTest', self.client.auth_service) def testAccountFeed(self): """Tests if the Account Feed exists.""" if not conf.options.get_value('runlive') == 'true': return conf.configure_cache(self.client, 'testAccountFeed') account_query = gdata.analytics.client.AccountFeedQuery({ 'max-results': '1' }) feed = self.client.GetAccountFeed(account_query) self.assert_(feed.entry is not None) properties = [ 'ga:accountId', 'ga:accountName', 'ga:profileId', 'ga:webPropertyId', 'ga:currency', 'ga:timezone' ] entry = feed.entry[0] for prop in properties: property = entry.GetProperty(prop) self.assertEquals(property.name, prop) def testDataFeed(self): """Tests if the Data Feed exists.""" start_date = '2008-10-01' end_date = '2008-10-02' metrics = 'ga:visits' if not conf.options.get_value('runlive') == 'true': return conf.configure_cache(self.client, 'testDataFeed') data_query = gdata.analytics.client.DataFeedQuery({ 'ids': conf.options.get_value('table_id'), 'start-date': start_date, 'end-date': end_date, 'metrics' : metrics, 'max-results': '1' }) feed = self.client.GetDataFeed(data_query) self.assert_(feed.entry is not None) self.assertEquals(feed.start_date.text, start_date) self.assertEquals(feed.end_date.text, end_date) self.assertEquals(feed.entry[0].GetMetric(metrics).name, metrics) def testManagementFeed(self): """Tests of the Management Feed exists.""" if not conf.options.get_value('runlive') == 'true': return conf.configure_cache(self.client, 'testManagementFeed') account_query = gdata.analytics.client.AccountQuery() feed = self.client.GetManagementFeed(account_query) self.assert_(feed.entry is not None) def tearDown(self): """Closes client connection.""" conf.close_client(self.client) def suite(): return conf.build_suite([AnalyticsClientTest]) if __name__ == '__main__': unittest.TextTestRunner().run(suite()) gdata-2.0.18/tests/gdata_tests/analytics/query_test.py0000640036466300116100000001300712156622363023150 0ustar afshareng00000000000000#!/usr/bin/env python # # 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. """Unit Tests for Google Analytics API query objects. AnalyticsClientTest: Tests making live requests to Google Analytics API. """ __author__ = 'api.nickm@google.com (Nick Mihailovski)' import unittest from gdata.analytics import client class DataExportQueryTest(unittest.TestCase): """Tests making Data Export API Queries.""" def testAccountFeed(self): """Tests Account Feed queries.""" queryTest1 = client.AccountFeedQuery() self.assertEquals(str(queryTest1), 'https://www.google.com/analytics/feeds/accounts/default') queryTest2 = client.AccountFeedQuery({'max-results': 50}) self.assertEquals(str(queryTest2), 'https://www.google.com/analytics/feeds/accounts/default' '?max-results=50') queryTest3 = client.AccountFeedQuery() queryTest3.query['max-results'] = 100 self.assertEquals(str(queryTest3), 'https://www.google.com/analytics/feeds/accounts/default' '?max-results=100') def testDataFeed(self): """Tests Data Feed queries.""" queryTest1 = client.DataFeedQuery() self.assertEquals(str(queryTest1), 'https://www.google.com/analytics/feeds/data') queryTest2 = client.DataFeedQuery({'ids': 'ga:1234'}) self.assertEquals(str(queryTest2), 'https://www.google.com/analytics/feeds/data?ids=ga%3A1234') queryTest3 = client.DataFeedQuery() queryTest3.query['ids'] = 'ga:1234' self.assertEquals(str(queryTest3), 'https://www.google.com/analytics/feeds/data?ids=ga%3A1234') class ManagementQueryTest(unittest.TestCase): """Tests making Management API queries.""" def setUp(self): self.base_url = 'https://www.google.com/analytics/feeds/datasources/ga' def testAccountFeedQuery(self): """Tests Account Feed queries.""" queryTest1 = client.AccountQuery() self.assertEquals(str(queryTest1), '%s/accounts' % self.base_url) queryTest2 = client.AccountQuery({'max-results': 50}) self.assertEquals(str(queryTest2), '%s/accounts?max-results=50' % self.base_url) def testWebPropertyFeedQuery(self): """Tests Web Property Feed queries.""" queryTest1 = client.WebPropertyQuery() self.assertEquals(str(queryTest1), '%s/accounts/~all/webproperties' % self.base_url) queryTest2 = client.WebPropertyQuery('123') self.assertEquals(str(queryTest2), '%s/accounts/123/webproperties' % self.base_url) queryTest3 = client.WebPropertyQuery('123', {'max-results': 100}) self.assertEquals(str(queryTest3), '%s/accounts/123/webproperties?max-results=100' % self.base_url) def testProfileFeedQuery(self): """Tests Profile Feed queries.""" queryTest1 = client.ProfileQuery() self.assertEquals(str(queryTest1), '%s/accounts/~all/webproperties/~all/profiles' % self.base_url) queryTest2 = client.ProfileQuery('123', 'UA-123-1') self.assertEquals(str(queryTest2), '%s/accounts/123/webproperties/UA-123-1/profiles' % self.base_url) queryTest3 = client.ProfileQuery('123', 'UA-123-1', {'max-results': 100}) self.assertEquals(str(queryTest3), '%s/accounts/123/webproperties/UA-123-1/profiles?max-results=100' % self.base_url) queryTest4 = client.ProfileQuery() queryTest4.acct_id = '123' queryTest4.web_prop_id = 'UA-123-1' queryTest4.query['max-results'] = 100 self.assertEquals(str(queryTest4), '%s/accounts/123/webproperties/UA-123-1/profiles?max-results=100' % self.base_url) def testGoalFeedQuery(self): """Tests Goal Feed queries.""" queryTest1 = client.GoalQuery() self.assertEquals(str(queryTest1), '%s/accounts/~all/webproperties/~all/profiles/~all/goals' % self.base_url) queryTest2 = client.GoalQuery('123', 'UA-123-1', '555') self.assertEquals(str(queryTest2), '%s/accounts/123/webproperties/UA-123-1/profiles/555/goals' % self.base_url) queryTest3 = client.GoalQuery('123', 'UA-123-1', '555', {'max-results': 100}) self.assertEquals(str(queryTest3), '%s/accounts/123/webproperties/UA-123-1/profiles/555/goals' '?max-results=100' % self.base_url) queryTest4 = client.GoalQuery() queryTest4.acct_id = '123' queryTest4.web_prop_id = 'UA-123-1' queryTest4.profile_id = '555' queryTest4.query['max-results'] = 100 self.assertEquals(str(queryTest3), '%s/accounts/123/webproperties/UA-123-1/profiles/555/goals' '?max-results=100' % self.base_url) def testAdvSegQuery(self): """Tests Advanced Segment Feed queries.""" queryTest1 = client.AdvSegQuery() self.assertEquals(str(queryTest1), '%s/segments' % self.base_url) queryTest2 = client.AdvSegQuery({'max-results': 100}) self.assertEquals(str(queryTest2), '%s/segments?max-results=100' % self.base_url) def suite(): return conf.build_suite([DataExportQueryTest, ManagementQueryTest]) if __name__ == '__main__': unittest.main() gdata-2.0.18/tests/gdata_tests/service_test.py0000750036466300116100000005337312156622363021470 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright (C) 2006 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. __author__ = 'api.jscudder (Jeff Scudder)' import unittest import getpass try: from xml.etree import ElementTree except ImportError: from elementtree import ElementTree import gdata.service import gdata import gdata.auth import atom import atom.service import atom.token_store import os.path from gdata import test_data import atom.mock_http import atom.mock_http_core username = '' password = '' test_image_location = '../testimage.jpg' test_image_name = 'testimage.jpg' class GDataServiceMediaUnitTest(unittest.TestCase): def setUp(self): self.gd_client = gdata.service.GDataService() self.gd_client.email = username self.gd_client.password = password self.gd_client.service = 'lh2' self.gd_client.source = 'GDataService Media "Unit" Tests' try: self.gd_client.ProgrammaticLogin() except gdata.service.CaptchaRequired: self.fail('Required Captcha') except gdata.service.BadAuthentication: self.fail('Bad Authentication') except gdata.service.Error: self.fail('Login Error') # create a test album gd_entry = gdata.GDataEntry() gd_entry.title = atom.Title(text='GData Test Album') gd_entry.category.append(atom.Category( scheme='http://schemas.google.com/g/2005#kind', term='http://schemas.google.com/photos/2007#album')) self.album_entry = self.gd_client.Post(gd_entry, 'http://picasaweb.google.com/data/feed/api/user/' + username) def tearDown(self): album_entry = self.gd_client.Get(self.album_entry.id.text) self.gd_client.Delete(album_entry.GetEditLink().href) def testSourceGeneratesUserAgentHeader(self): self.gd_client.source = 'GoogleInc-ServiceUnitTest-1' self.assert_(self.gd_client.additional_headers['User-Agent'].startswith( 'GoogleInc-ServiceUnitTest-1 GData-Python')) def testMedia1(self): # Create media-only ms = gdata.MediaSource() ms.setFile(test_image_location, 'image/jpeg') media_entry = self.gd_client.Post(None, self.album_entry.GetFeedLink().href, media_source = ms) self.assert_(media_entry is not None) self.assert_(isinstance(media_entry, gdata.GDataEntry)) self.assert_(media_entry.IsMedia()) # Update media & metadata ms = gdata.MediaSource() ms.setFile(test_image_location, 'image/jpeg') media_entry.summary = atom.Summary(text='Test Image') media_entry2 = self.gd_client.Put(media_entry, media_entry.GetEditLink().href, media_source = ms) self.assert_(media_entry2 is not None) self.assert_(isinstance(media_entry2, gdata.GDataEntry)) self.assert_(media_entry2.IsMedia()) self.assert_(media_entry2.summary.text == 'Test Image') # Read media binary imageSource = self.gd_client.GetMedia(media_entry2.GetMediaURL()) self.assert_(isinstance(imageSource, gdata.MediaSource)) self.assert_(imageSource.content_type == 'image/jpeg') self.assert_(imageSource.content_length) imageData = imageSource.file_handle.read() self.assert_(imageData) # Delete entry response = self.gd_client.Delete(media_entry2.GetEditLink().href) self.assert_(response) def testMedia2(self): # Create media & metadata ms = gdata.MediaSource() ms.setFile(test_image_location, 'image/jpeg') new_media_entry = gdata.GDataEntry() new_media_entry.title = atom.Title(text='testimage1.jpg') new_media_entry.summary = atom.Summary(text='Test Image') new_media_entry.category.append(atom.Category(scheme = 'http://schemas.google.com/g/2005#kind', term = 'http://schemas.google.com/photos/2007#photo')) media_entry = self.gd_client.Post(new_media_entry, self.album_entry.GetFeedLink().href, media_source = ms) self.assert_(media_entry is not None) self.assert_(isinstance(media_entry, gdata.GDataEntry)) self.assert_(media_entry.IsMedia()) self.assert_(media_entry.summary.text == 'Test Image') # Update media only ms = gdata.MediaSource() ms.setFile(test_image_location, 'image/jpeg') media_entry = self.gd_client.Put(None, media_entry.GetEditMediaLink().href, media_source = ms) self.assert_(media_entry is not None) self.assert_(isinstance(media_entry, gdata.GDataEntry)) self.assert_(media_entry.IsMedia()) # Delete entry response = self.gd_client.Delete(media_entry.GetEditLink().href) self.assert_(response) def testMediaConstructorDefaults(self): ms = gdata.MediaSource() ms.setFile(test_image_location, 'image/jpeg') self.assert_(ms is not None) self.assert_(isinstance(ms, gdata.MediaSource)) self.assertEquals(ms.file_name, test_image_name) self.assertEquals(ms.content_type, 'image/jpeg') def testMediaConstructorWithFilePath(self): ms = gdata.MediaSource(file_path=test_image_location, content_type='image/jpeg') self.assert_(ms is not None) self.assert_(isinstance(ms, gdata.MediaSource)) self.assertEquals(ms.file_name, test_image_name) self.assertEquals(ms.content_type, 'image/jpeg') def testMediaConstructorWithFileHandle(self): fh = open(test_image_location, 'r') len = os.path.getsize(test_image_location) ms = gdata.MediaSource(fh, 'image/jpeg', len, file_name=test_image_location) self.assert_(ms is not None) self.assert_(isinstance(ms, gdata.MediaSource)) self.assertEquals(ms.file_name, test_image_location) self.assertEquals(ms.content_type, 'image/jpeg') class GDataServiceUnitTest(unittest.TestCase): def setUp(self): self.gd_client = gdata.service.GDataService() self.gd_client.email = username self.gd_client.password = password self.gd_client.service = 'gbase' self.gd_client.source = 'GDataClient "Unit" Tests' def testProperties(self): email_string = 'Test Email' password_string = 'Passwd' self.gd_client.email = email_string self.assertEquals(self.gd_client.email, email_string) self.gd_client.password = password_string self.assertEquals(self.gd_client.password, password_string) def testCorrectLogin(self): try: self.gd_client.ProgrammaticLogin() self.assert_(isinstance( self.gd_client.token_store.find_token( 'http://base.google.com/base/feeds/'), gdata.auth.ClientLoginToken)) self.assert_(self.gd_client.captcha_token is None) self.assert_(self.gd_client.captcha_url is None) except gdata.service.CaptchaRequired: self.fail('Required Captcha') def testDefaultHttpClient(self): self.assert_(isinstance(self.gd_client.http_client, atom.http.HttpClient)) def testGet(self): try: self.gd_client.ProgrammaticLogin() except gdata.service.CaptchaRequired: self.fail('Required Captcha') except gdata.service.BadAuthentication: self.fail('Bad Authentication') except gdata.service.Error: self.fail('Login Error') self.gd_client.additional_headers = {'X-Google-Key': 'ABQIAAAAoLioN3buSs9KqIIq9V' + 'mkFxT2yXp_ZAY8_ufC3CFXhHIE' + '1NvwkxRK8C1Q8OWhsWA2AIKv-c' + 'VKlVrNhQ'} self.gd_client.server = 'base.google.com' result = self.gd_client.Get('/base/feeds/snippets?bq=digital+camera') self.assert_(result is not None) self.assert_(isinstance(result, atom.Feed)) def testGetWithAuthentication(self): try: self.gd_client.ProgrammaticLogin() except gdata.service.CaptchaRequired: self.fail('Required Captcha') except gdata.service.BadAuthentication: self.fail('Bad Authentication') except gdata.service.Error: self.fail('Login Error') self.gd_client.additional_headers = {'X-Google-Key': 'ABQIAAAAoLioN3buSs9KqIIq9V' + 'mkFxT2yXp_ZAY8_ufC3CFXhHIE' + '1NvwkxRK8C1Q8OWhsWA2AIKv-c' + 'VKlVrNhQ'} self.gd_client.server = 'base.google.com' result = self.gd_client.Get('/base/feeds/items?bq=digital+camera') self.assert_(result is not None) self.assert_(isinstance(result, atom.Feed)) def testGetEntry(self): try: self.gd_client.ProgrammaticLogin() except gdata.service.CaptchaRequired: self.fail('Required Captcha') except gdata.service.BadAuthentication: self.fail('Bad Authentication') except gdata.service.Error: self.fail('Login Error') self.gd_client.server = 'base.google.com' try: result = self.gd_client.GetEntry('/base/feeds/items?bq=digital+camera') self.fail( 'Result from server in GetEntry should have raised an exception') except gdata.service.UnexpectedReturnType: pass def testGetFeed(self): try: self.gd_client.ProgrammaticLogin() except gdata.service.CaptchaRequired: self.fail('Required Captcha') except gdata.service.BadAuthentication: self.fail('Bad Authentication') except gdata.service.Error: self.fail('Login Error') self.gd_client.server = 'base.google.com' result = self.gd_client.GetFeed('/base/feeds/items?bq=digital+camera') self.assert_(result is not None) self.assert_(isinstance(result, atom.Feed)) def testGetWithResponseTransformer(self): # Query Google Base and interpret the results as a GBaseSnippetFeed. feed = self.gd_client.Get( 'http://www.google.com/base/feeds/snippets?bq=digital+camera', converter=gdata.base.GBaseSnippetFeedFromString) self.assertEquals(isinstance(feed, gdata.base.GBaseSnippetFeed), True) def testPostPutAndDelete(self): try: self.gd_client.ProgrammaticLogin() except gdata.service.CaptchaRequired: self.fail('Required Captcha') except gdata.service.BadAuthentication: self.fail('Bad Authentication') except gdata.service.Error: self.fail('Login Error') self.gd_client.additional_headers = {'X-Google-Key': 'ABQIAAAAoLioN3buSs9KqIIq9V' + 'mkFxT2yXp_ZAY8_ufC3CFXhHIE' + '1NvwkxRK8C1Q8OWhsWA2AIKv-c' + 'VKlVrNhQ'} self.gd_client.server = 'base.google.com' # Insert a new item response = self.gd_client.Post(test_data.TEST_BASE_ENTRY, '/base/feeds/items') self.assert_(response is not None) self.assert_(isinstance(response, atom.Entry)) self.assert_(response.category[0].term == 'products') # Find the item id of the created item item_id = response.id.text.lstrip( 'http://www.google.com/base/feeds/items/') self.assert_(item_id is not None) updated_xml = gdata.base.GBaseItemFromString(test_data.TEST_BASE_ENTRY) # Change one of the labels in the item updated_xml.label[2].text = 'beach ball' # Update the item response = self.gd_client.Put(updated_xml, '/base/feeds/items/%s' % item_id) self.assert_(response is not None) new_base_item = gdata.base.GBaseItemFromString(str(response)) self.assert_(isinstance(new_base_item, atom.Entry)) # Delete the item the test just created. response = self.gd_client.Delete('/base/feeds/items/%s' % item_id) self.assert_(response) def testPostPutAndDeleteWithConverters(self): try: self.gd_client.ProgrammaticLogin() except gdata.service.CaptchaRequired: self.fail('Required Captcha') except gdata.service.BadAuthentication: self.fail('Bad Authentication') except gdata.service.Error: self.fail('Login Error') self.gd_client.additional_headers = {'X-Google-Key': 'ABQIAAAAoLioN3buSs9KqIIq9V' + 'mkFxT2yXp_ZAY8_ufC3CFXhHIE' + '1NvwkxRK8C1Q8OWhsWA2AIKv-c' + 'VKlVrNhQ'} self.gd_client.server = 'base.google.com' # Insert a new item response = self.gd_client.Post(test_data.TEST_BASE_ENTRY, '/base/feeds/items', converter=gdata.base.GBaseItemFromString) self.assert_(response is not None) self.assert_(isinstance(response, atom.Entry)) self.assert_(isinstance(response, gdata.base.GBaseItem)) self.assert_(response.category[0].term == 'products') updated_xml = gdata.base.GBaseItemFromString(test_data.TEST_BASE_ENTRY) # Change one of the labels in the item updated_xml.label[2].text = 'beach ball' # Update the item response = self.gd_client.Put(updated_xml, response.id.text, converter=gdata.base.GBaseItemFromString) self.assertEquals(response is not None, True) self.assertEquals(isinstance(response, gdata.base.GBaseItem), True) # Delete the item the test just created. response = self.gd_client.Delete(response.id.text) self.assert_(response) def testCaptchaUrlGeneration(self): # Populate the mock server with a pairing for a ClientLogin request to a # CAPTCHA challenge. mock_client = atom.mock_http.MockHttpClient() captcha_response = atom.mock_http.MockResponse( body="""Url=http://www.google.com/login/captcha Error=CaptchaRequired CaptchaToken=DQAAAGgAdkI1LK9 CaptchaUrl=Captcha?ctoken=HiteT4b0Bk5Xg18_AcVoP6-yFkHPibe7O9EqxeiI7lUSN """, status=403, reason='Access Forbidden') mock_client.add_response(captcha_response, 'POST', 'https://www.google.com/accounts/ClientLogin') # Set the exising client's handler so that it will make requests to the # mock service instead of the real server. self.gd_client.http_client = mock_client try: self.gd_client.ProgrammaticLogin() self.fail('Login attempt should have caused a CAPTCHA challenge.') except gdata.service.CaptchaRequired, error: self.assertEquals(self.gd_client.captcha_url, ('https://www.google.com/accounts/Captcha?ctoken=HiteT4b0Bk5Xg18_' 'AcVoP6-yFkHPibe7O9EqxeiI7lUSN')) class DeleteWithUrlParamsTest(unittest.TestCase): def setUp(self): self.gd_client = gdata.service.GDataService() # Set the client to echo the request back in the response. self.gd_client.http_client.v2_http_client = ( atom.mock_http_core.SettableHttpClient(200, 'OK', '', {})) def testDeleteWithUrlParams(self): self.assert_(self.gd_client.Delete('http://example.com/test', {'TestHeader': '123'}, {'urlParam1': 'a', 'urlParam2': 'test'})) request = self.gd_client.http_client.v2_http_client.last_request self.assertEqual(request.uri.host, 'example.com') self.assertEqual(request.uri.path, '/test') self.assertEqual(request.uri.query, {'urlParam1': 'a', 'urlParam2': 'test'}) def testDeleteWithSessionId(self): self.gd_client._SetSessionId('test_session_id') self.assert_(self.gd_client.Delete('http://example.com/test', {'TestHeader': '123'}, {'urlParam1': 'a', 'urlParam2': 'test'})) request = self.gd_client.http_client.v2_http_client.last_request self.assertEqual(request.uri.host, 'example.com') self.assertEqual(request.uri.path, '/test') self.assertEqual(request.uri.query, {'urlParam1': 'a', 'urlParam2': 'test', 'gsessionid': 'test_session_id'}) class QueryTest(unittest.TestCase): def setUp(self): self.query = gdata.service.Query() def testQueryShouldBehaveLikeDict(self): try: self.query['zap'] self.fail() except KeyError: pass self.query['zap'] = 'x' self.assert_(self.query['zap'] == 'x') def testContructorShouldRejectBadInputs(self): test_q = gdata.service.Query(params=[1,2,3,4]) self.assert_(len(test_q.keys()) == 0) def testTextQueryProperty(self): self.assert_(self.query.text_query is None) self.query['q'] = 'test1' self.assert_(self.query.text_query == 'test1') self.query.text_query = 'test2' self.assert_(self.query.text_query == 'test2') def testOrderByQueryProperty(self): self.assert_(self.query.orderby is None) self.query['orderby'] = 'updated' self.assert_(self.query.orderby == 'updated') self.query.orderby = 'starttime' self.assert_(self.query.orderby == 'starttime') def testQueryShouldProduceExampleUris(self): self.query.feed = '/base/feeds/snippets' self.query.text_query = 'This is a test' self.assert_(self.query.ToUri() == '/base/feeds/snippets?q=This+is+a+test') def testCategoriesFormattedCorrectly(self): self.query.feed = '/x' self.query.categories.append('Fritz') self.query.categories.append('Laurie') self.assert_(self.query.ToUri() == '/x/-/Fritz/Laurie') # The query's feed should not have been changed self.assert_(self.query.feed == '/x') self.assert_(self.query.ToUri() == '/x/-/Fritz/Laurie') def testCategoryQueriesShouldEscapeOrSymbols(self): self.query.feed = '/x' self.query.categories.append('Fritz|Laurie') self.assert_(self.query.ToUri() == '/x/-/Fritz%7CLaurie') def testTypeCoercionOnIntParams(self): self.query.feed = '/x' self.query.max_results = 10 self.query.start_index = 5 self.assert_(isinstance(self.query.max_results, str)) self.assert_(isinstance(self.query.start_index, str)) self.assertEquals(self.query['max-results'], '10') self.assertEquals(self.query['start-index'], '5') def testPassInCategoryListToConstructor(self): query = gdata.service.Query(feed='/feed/sample', categories=['foo', 'bar', 'eggs|spam']) url = query.ToUri() self.assert_(url.find('/foo') > -1) self.assert_(url.find('/bar') > -1) self.assert_(url.find('/eggs%7Cspam') > -1) class GetNextPageInFeedTest(unittest.TestCase): def setUp(self): self.gd_client = gdata.service.GDataService() def testGetNextPage(self): feed = self.gd_client.Get( 'http://www.google.com/base/feeds/snippets?max-results=2', converter=gdata.base.GBaseSnippetFeedFromString) self.assert_(len(feed.entry) > 0) first_id = feed.entry[0].id.text feed2 = self.gd_client.GetNext(feed) self.assert_(len(feed2.entry) > 0) next_id = feed2.entry[0].id.text self.assert_(first_id != next_id) self.assert_(feed2.__class__ == feed.__class__) class ScopeLookupTest(unittest.TestCase): def testLookupScopes(self): scopes = gdata.service.lookup_scopes('cl') self.assertEquals(scopes, gdata.service.CLIENT_LOGIN_SCOPES['cl']) scopes = gdata.service.lookup_scopes(None) self.assert_(scopes is None) scopes = gdata.service.lookup_scopes('UNKNOWN_SERVICE') self.assert_(scopes is None) class TokenLookupTest(unittest.TestCase): def setUp(self): self.client = gdata.service.GDataService() def testSetAndGetClientLoginTokenWithNoService(self): self.assert_(self.client.auth_token is None) self.client.SetClientLoginToken('foo') self.assert_(self.client.auth_token is None) self.assert_(self.client.token_store.find_token( atom.token_store.SCOPE_ALL) is not None) self.assertEquals(self.client.GetClientLoginToken(), 'foo') self.client.SetClientLoginToken('foo2') self.assertEquals(self.client.GetClientLoginToken(), 'foo2') def testSetAndGetClientLoginTokenWithService(self): self.client.service = 'cp' self.client.SetClientLoginToken('bar') self.assertEquals(self.client.GetClientLoginToken(), 'bar') # Changing the service should cause the token to no longer be found. self.client.service = 'gbase' self.client.current_token = None self.assert_(self.client.GetClientLoginToken() is None) def testSetAndGetClientLoginTokenWithScopes(self): scopes = gdata.service.CLIENT_LOGIN_SCOPES['cl'][:] scopes.extend(gdata.service.CLIENT_LOGIN_SCOPES['gbase']) self.client.SetClientLoginToken('baz', scopes=scopes) self.client.current_token = None self.assert_(self.client.GetClientLoginToken() is None) self.client.service = 'cl' self.assertEquals(self.client.GetClientLoginToken(), 'baz') self.client.service = 'gbase' self.assertEquals(self.client.GetClientLoginToken(), 'baz') self.client.service = 'wise' self.assert_(self.client.GetClientLoginToken() is None) def testLookupUsingTokenStore(self): scopes = gdata.service.CLIENT_LOGIN_SCOPES['cl'][:] scopes.extend(gdata.service.CLIENT_LOGIN_SCOPES['gbase']) self.client.SetClientLoginToken('baz', scopes=scopes) token = self.client.token_store.find_token( 'http://www.google.com/calendar/feeds/foo') self.assertEquals(token.get_token_string(), 'baz') self.assertEquals(token.auth_header, '%s%s' % ( gdata.auth.PROGRAMMATIC_AUTH_LABEL, 'baz')) token = self.client.token_store.find_token( 'http://www.google.com/calendar/') self.assert_(isinstance(token, gdata.auth.ClientLoginToken) == False) token = self.client.token_store.find_token( 'http://www.google.com/base/feeds/snippets') self.assertEquals(token.get_token_string(), 'baz') if __name__ == '__main__': print ('GData Service Media Unit Tests\nNOTE: Please run these tests only ' 'with a test account. The tests may delete or update your data.') username = raw_input('Please enter your username: ') password = getpass.getpass() unittest.main() gdata-2.0.18/tests/gdata_tests/data_test.py0000750036466300116100000005214112156622363020731 0ustar afshareng00000000000000#!/usr/bin/env python # # Copyright (C) 2009 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. # This module is used for version 2 of the Google Data APIs. __author__ = 'j.s@google.com (Jeff Scudder)' import unittest import gdata.data from gdata import test_data import gdata.test_config as conf import atom.core import atom.data SIMPLE_V2_FEED_TEST_DATA = """ Elizabeth Bennet's Contacts http://www.google.com/m8/feeds/contacts/liz%40gmail.com/base/c9e Fitzwilliam """ XML_ENTRY_1 = """ http://www.google.com/test/id/url Testing 2000 series laptop
    A Testing Laptop
    Computer Laptop testing laptop products
    """ def parse(xml_string, target_class): """Convenience wrapper for converting an XML string to an XmlElement.""" return atom.core.xml_element_from_string(xml_string, target_class) class StartIndexTest(unittest.TestCase): def setUp(self): self.start_index = gdata.data.StartIndex() def testToAndFromString(self): self.start_index.text = '1' self.assert_(self.start_index.text == '1') new_start_index = parse(self.start_index.ToString(), gdata.data.StartIndex) self.assert_(self.start_index.text == new_start_index.text) class ItemsPerPageTest(unittest.TestCase): def setUp(self): self.items_per_page = gdata.data.ItemsPerPage() def testToAndFromString(self): self.items_per_page.text = '10' self.assert_(self.items_per_page.text == '10') new_items_per_page = parse(self.items_per_page.ToString(), gdata.data.ItemsPerPage) self.assert_(self.items_per_page.text == new_items_per_page.text) class GDataEntryTest(unittest.TestCase): def testIdShouldBeCleaned(self): entry = parse(XML_ENTRY_1, gdata.data.GDEntry) tree = parse(XML_ENTRY_1, atom.core.XmlElement) self.assert_(tree.get_elements('id', 'http://www.w3.org/2005/Atom')[0].text != entry.get_id()) self.assertEqual(entry.get_id(), 'http://www.google.com/test/id/url') def testGeneratorShouldBeCleaned(self): feed = parse(test_data.GBASE_FEED, gdata.data.GDFeed) tree = parse(test_data.GBASE_FEED, atom.core.XmlElement) self.assert_(tree.get_elements('generator', 'http://www.w3.org/2005/Atom')[0].text != feed.get_generator()) self.assertEqual(feed.get_generator(), 'GoogleBase') def testAllowsEmptyId(self): entry = gdata.data.GDEntry() try: entry.id = atom.data.Id() except AttributeError: self.fail('Empty id should not raise an attribute error.') class LinkFinderTest(unittest.TestCase): def setUp(self): self.entry = parse(XML_ENTRY_1, gdata.data.GDEntry) def testLinkFinderGetsLicenseLink(self): self.assertEquals(isinstance(self.entry.FindLicenseLink(), str), True) self.assertEquals(self.entry.FindLicenseLink(), 'http://creativecommons.org/licenses/by-nc/2.5/rdf') def testLinkFinderGetsAlternateLink(self): self.assert_(isinstance(self.entry.FindAlternateLink(), str)) self.assertEquals(self.entry.FindAlternateLink(), 'http://www.provider-host.com/123456789') def testFindAclLink(self): entry = gdata.data.GDEntry() self.assert_(entry.get_acl_link() is None) self.assert_(entry.find_acl_link() is None) entry.link.append(atom.data.Link( rel=gdata.data.ACL_REL, href='http://example.com/acl')) self.assertEqual(entry.get_acl_link().href, 'http://example.com/acl') self.assertEqual(entry.find_acl_link(), 'http://example.com/acl') del entry.link[0] self.assert_(entry.get_acl_link() is None) self.assert_(entry.find_acl_link() is None) # We should also find an ACL link which is a feed_link. entry.feed_link = [gdata.data.FeedLink( rel=gdata.data.ACL_REL, href='http://example.com/acl2')] self.assertEqual(entry.get_acl_link().href, 'http://example.com/acl2') self.assertEqual(entry.find_acl_link(), 'http://example.com/acl2') class GDataFeedTest(unittest.TestCase): def testCorrectConversionToElementTree(self): test_feed = parse(test_data.GBASE_FEED, gdata.data.GDFeed) self.assert_(test_feed.total_results is not None) self.assert_(test_feed.get_elements('totalResults', 'http://a9.com/-/spec/opensearchrss/1.0/') is not None) self.assert_(len(test_feed.get_elements('totalResults', 'http://a9.com/-/spec/opensearchrss/1.0/')) > 0) def testAllowsEmptyId(self): feed = gdata.data.GDFeed() try: feed.id = atom.data.Id() except AttributeError: self.fail('Empty id should not raise an attribute error.') class BatchEntryTest(unittest.TestCase): def testCorrectConversionFromAndToString(self): batch_entry = parse(test_data.BATCH_ENTRY, gdata.data.BatchEntry) self.assertEquals(batch_entry.batch_id.text, 'itemB') self.assertEquals(batch_entry.id.text, 'http://www.google.com/base/feeds/items/' '2173859253842813008') self.assertEquals(batch_entry.batch_operation.type, 'insert') self.assertEquals(batch_entry.batch_status.code, '201') self.assertEquals(batch_entry.batch_status.reason, 'Created') new_entry = parse(str(batch_entry), gdata.data.BatchEntry) self.assertEquals(batch_entry.batch_id.text, new_entry.batch_id.text) self.assertEquals(batch_entry.id.text, new_entry.id.text) self.assertEquals(batch_entry.batch_operation.type, new_entry.batch_operation.type) self.assertEquals(batch_entry.batch_status.code, new_entry.batch_status.code) self.assertEquals(batch_entry.batch_status.reason, new_entry.batch_status.reason) class BatchFeedTest(unittest.TestCase): def setUp(self): self.batch_feed = gdata.data.BatchFeed() self.example_entry = gdata.data.BatchEntry( id=atom.data.Id(text='http://example.com/1'), text='This is a test') def testConvertRequestFeed(self): batch_feed = parse(test_data.BATCH_FEED_REQUEST, gdata.data.BatchFeed) self.assertEquals(len(batch_feed.entry), 4) for entry in batch_feed.entry: self.assert_(isinstance(entry, gdata.data.BatchEntry)) self.assertEquals(batch_feed.title.text, 'My Batch Feed') new_feed = parse(batch_feed.to_string(), gdata.data.BatchFeed) self.assertEquals(len(new_feed.entry), 4) for entry in new_feed.entry: self.assert_(isinstance(entry, gdata.data.BatchEntry)) self.assertEquals(new_feed.title.text, 'My Batch Feed') def testConvertResultFeed(self): batch_feed = parse(test_data.BATCH_FEED_RESULT, gdata.data.BatchFeed) self.assertEquals(len(batch_feed.entry), 4) for entry in batch_feed.entry: self.assert_(isinstance(entry, gdata.data.BatchEntry)) if entry.id.text == ('http://www.google.com/base/feeds/items/' '2173859253842813008'): self.assertEquals(entry.batch_operation.type, 'insert') self.assertEquals(entry.batch_id.text, 'itemB') self.assertEquals(entry.batch_status.code, '201') self.assertEquals(entry.batch_status.reason, 'Created') self.assertEquals(batch_feed.title.text, 'My Batch') new_feed = parse(str(batch_feed), gdata.data.BatchFeed) self.assertEquals(len(new_feed.entry), 4) for entry in new_feed.entry: self.assert_(isinstance(entry, gdata.data.BatchEntry)) if entry.id.text == ('http://www.google.com/base/feeds/items/' '2173859253842813008'): self.assertEquals(entry.batch_operation.type, 'insert') self.assertEquals(entry.batch_id.text, 'itemB') self.assertEquals(entry.batch_status.code, '201') self.assertEquals(entry.batch_status.reason, 'Created') self.assertEquals(new_feed.title.text, 'My Batch') def testAddBatchEntry(self): try: self.batch_feed.AddBatchEntry(batch_id_string='a') self.fail('AddBatchEntry with neither entry or URL should raise Error') except gdata.data.MissingRequiredParameters: pass new_entry = self.batch_feed.AddBatchEntry( id_url_string='http://example.com/1') self.assertEquals(len(self.batch_feed.entry), 1) self.assertEquals(self.batch_feed.entry[0].get_id(), 'http://example.com/1') self.assertEquals(self.batch_feed.entry[0].batch_id.text, '0') self.assertEquals(new_entry.id.text, 'http://example.com/1') self.assertEquals(new_entry.batch_id.text, '0') to_add = gdata.data.BatchEntry(id=atom.data.Id(text='originalId')) new_entry = self.batch_feed.AddBatchEntry(entry=to_add, batch_id_string='foo') self.assertEquals(new_entry.batch_id.text, 'foo') self.assertEquals(new_entry.id.text, 'originalId') to_add = gdata.data.BatchEntry(id=atom.data.Id(text='originalId'), batch_id=gdata.data.BatchId(text='bar')) new_entry = self.batch_feed.AddBatchEntry(entry=to_add, id_url_string='newId', batch_id_string='foo') self.assertEquals(new_entry.batch_id.text, 'foo') self.assertEquals(new_entry.id.text, 'originalId') to_add = gdata.data.BatchEntry(id=atom.data.Id(text='originalId'), batch_id=gdata.data.BatchId(text='bar')) new_entry = self.batch_feed.AddBatchEntry(entry=to_add, id_url_string='newId') self.assertEquals(new_entry.batch_id.text, 'bar') self.assertEquals(new_entry.id.text, 'originalId') to_add = gdata.data.BatchEntry(id=atom.data.Id(text='originalId'), batch_id=gdata.data.BatchId(text='bar'), batch_operation=gdata.data.BatchOperation( type=gdata.data.BATCH_INSERT)) self.assertEquals(to_add.batch_operation.type, gdata.data.BATCH_INSERT) new_entry = self.batch_feed.AddBatchEntry(entry=to_add, id_url_string='newId', batch_id_string='foo', operation_string=gdata.data.BATCH_UPDATE) self.assertEquals(new_entry.batch_operation.type, gdata.data.BATCH_UPDATE) def testAddInsert(self): first_entry = gdata.data.BatchEntry( id=atom.data.Id(text='http://example.com/1'), text='This is a test1') self.batch_feed.AddInsert(first_entry) self.assertEquals(self.batch_feed.entry[0].batch_operation.type, gdata.data.BATCH_INSERT) self.assertEquals(self.batch_feed.entry[0].batch_id.text, '0') second_entry = gdata.data.BatchEntry( id=atom.data.Id(text='http://example.com/2'), text='This is a test2') self.batch_feed.AddInsert(second_entry, batch_id_string='foo') self.assertEquals(self.batch_feed.entry[1].batch_operation.type, gdata.data.BATCH_INSERT) self.assertEquals(self.batch_feed.entry[1].batch_id.text, 'foo') third_entry = gdata.data.BatchEntry( id=atom.data.Id(text='http://example.com/3'), text='This is a test3') third_entry.batch_operation = gdata.data.BatchOperation( type=gdata.data.BATCH_DELETE) # Add an entry with a delete operation already assigned. self.batch_feed.AddInsert(third_entry) # The batch entry should not have the original operation, it should # have been changed to an insert. self.assertEquals(self.batch_feed.entry[2].batch_operation.type, gdata.data.BATCH_INSERT) self.assertEquals(self.batch_feed.entry[2].batch_id.text, '2') def testAddDelete(self): # Try deleting an entry delete_entry = gdata.data.BatchEntry( id=atom.data.Id(text='http://example.com/1'), text='This is a test') self.batch_feed.AddDelete(entry=delete_entry) self.assertEquals(self.batch_feed.entry[0].batch_operation.type, gdata.data.BATCH_DELETE) self.assertEquals(self.batch_feed.entry[0].get_id(), 'http://example.com/1') self.assertEquals(self.batch_feed.entry[0].text, 'This is a test') # Try deleting a URL self.batch_feed.AddDelete(url_string='http://example.com/2') self.assertEquals(self.batch_feed.entry[0].batch_operation.type, gdata.data.BATCH_DELETE) self.assertEquals(self.batch_feed.entry[1].id.text, 'http://example.com/2') self.assert_(self.batch_feed.entry[1].text is None) def testAddQuery(self): # Try querying with an existing batch entry delete_entry = gdata.data.BatchEntry( id=atom.data.Id(text='http://example.com/1')) self.batch_feed.AddQuery(entry=delete_entry) self.assertEquals(self.batch_feed.entry[0].batch_operation.type, gdata.data.BATCH_QUERY) self.assertEquals(self.batch_feed.entry[0].get_id(), 'http://example.com/1') # Try querying a URL self.batch_feed.AddQuery(url_string='http://example.com/2') self.assertEquals(self.batch_feed.entry[0].batch_operation.type, gdata.data.BATCH_QUERY) self.assertEquals(self.batch_feed.entry[1].id.text, 'http://example.com/2') def testAddUpdate(self): # Try updating an entry delete_entry = gdata.data.BatchEntry( id=atom.data.Id(text='http://example.com/1'), text='This is a test') self.batch_feed.AddUpdate(entry=delete_entry) self.assertEquals(self.batch_feed.entry[0].batch_operation.type, gdata.data.BATCH_UPDATE) self.assertEquals(self.batch_feed.entry[0].get_id(), 'http://example.com/1') self.assertEquals(self.batch_feed.entry[0].text, 'This is a test') class ExtendedPropertyTest(unittest.TestCase): def testXmlBlobRoundTrip(self): ep = gdata.data.ExtendedProperty(name='blobby') ep.SetXmlBlob('') extension = ep.GetXmlBlob() self.assertEquals(extension.tag, 'some_xml') self.assert_(extension.namespace is None) self.assertEquals(extension.attributes['attr'], 'test') ep2 = parse(ep.ToString(), gdata.data.ExtendedProperty) extension = ep2.GetXmlBlob() self.assertEquals(extension.tag, 'some_xml') self.assert_(extension.namespace is None) self.assertEquals(extension.attributes['attr'], 'test') def testGettersShouldReturnNoneWithNoBlob(self): ep = gdata.data.ExtendedProperty(name='no blob') self.assert_(ep.GetXmlBlob() is None) def testGettersReturnCorrectTypes(self): ep = gdata.data.ExtendedProperty(name='has blob') ep.SetXmlBlob('') self.assert_(isinstance(ep.GetXmlBlob(), atom.core.XmlElement)) self.assert_(isinstance(ep.GetXmlBlob().to_string(), str)) class FeedLinkTest(unittest.TestCase): def testCorrectFromStringType(self): link = parse( '', gdata.data.FeedLink) self.assert_(isinstance(link, gdata.data.FeedLink)) self.assertEqual(link.count_hint, '5') class SimpleV2FeedTest(unittest.TestCase): def test_parsing_etags_and_edit_url(self): feed = atom.core.parse(SIMPLE_V2_FEED_TEST_DATA, gdata.data.GDFeed) # General parsing assertions. self.assertEqual(feed.get_elements('title')[0].text, 'Elizabeth Bennet\'s Contacts') self.assertEqual(len(feed.entry), 2) for entry in feed.entry: self.assert_(isinstance(entry, gdata.data.GDEntry)) self.assertEqual(feed.entry[0].GetElements('title')[0].text, 'Fitzwilliam') self.assertEqual(feed.entry[0].get_elements('id')[0].text, 'http://www.google.com/m8/feeds/contacts/liz%40gmail.com/base/c9e') # ETags checks. self.assertEqual(feed.etag, 'W/"CUMBRHo_fip7ImA9WxRbGU0."') self.assertEqual(feed.entry[0].etag, '"Qn04eTVSLyp7ImA9WxRbGEUORAQ."') self.assertEqual(feed.entry[1].etag, '"123456"') # Look for Edit URLs. self.assertEqual(feed.entry[0].find_edit_link(), 'http://www.google.com/m8/feeds/contacts/liz%40gmail.com/full/c9e') self.assertEqual(feed.entry[1].FindEditLink(), 'http://example.com/1') # Look for Next URLs. self.assertEqual(feed.find_next_link(), 'http://www.google.com/m8/feeds/contacts/.../more') def test_constructor_defauls(self): feed = gdata.data.GDFeed() self.assert_(feed.etag is None) self.assertEqual(feed.link, []) self.assertEqual(feed.entry, []) entry = gdata.data.GDEntry() self.assert_(entry.etag is None) self.assertEqual(entry.link, []) link = atom.data.Link() self.assert_(link.href is None) self.assert_(link.rel is None) link1 = atom.data.Link(href='http://example.com', rel='test') self.assertEqual(link1.href, 'http://example.com') self.assertEqual(link1.rel, 'test') link2 = atom.data.Link(href='http://example.org/', rel='alternate') entry = gdata.data.GDEntry(etag='foo', link=[link1, link2]) feed = gdata.data.GDFeed(etag='12345', entry=[entry]) self.assertEqual(feed.etag, '12345') self.assertEqual(len(feed.entry), 1) self.assertEqual(feed.entry[0].etag, 'foo') self.assertEqual(len(feed.entry[0].link), 2) class DataClassSanityTest(unittest.TestCase): def test_basic_element_structure(self): conf.check_data_classes(self, [ gdata.data.TotalResults, gdata.data.StartIndex, gdata.data.ItemsPerPage, gdata.data.ExtendedProperty, gdata.data.GDEntry, gdata.data.GDFeed, gdata.data.BatchId, gdata.data.BatchOperation, gdata.data.BatchStatus, gdata.data.BatchEntry, gdata.data.BatchInterrupted, gdata.data.BatchFeed, gdata.data.EntryLink, gdata.data.FeedLink, gdata.data.AdditionalName, gdata.data.Comments, gdata.data.Country, gdata.data.Email, gdata.data.FamilyName, gdata.data.Im, gdata.data.GivenName, gdata.data.NamePrefix, gdata.data.NameSuffix, gdata.data.FullName, gdata.data.Name, gdata.data.OrgDepartment, gdata.data.OrgName, gdata.data.OrgSymbol, gdata.data.OrgTitle, gdata.data.Organization, gdata.data.When, gdata.data.Who, gdata.data.OriginalEvent, gdata.data.PhoneNumber, gdata.data.PostalAddress, gdata.data.Rating, gdata.data.Recurrence, gdata.data.RecurrenceException, gdata.data.Reminder, gdata.data.Agent, gdata.data.HouseName, gdata.data.Street, gdata.data.PoBox, gdata.data.Neighborhood, gdata.data.City, gdata.data.Subregion, gdata.data.Region, gdata.data.Postcode, gdata.data.Country, gdata.data.FormattedAddress, gdata.data.StructuredPostalAddress, gdata.data.Where, gdata.data.AttendeeType, gdata.data.AttendeeStatus]) def test_member_values(self): self.assertEqual( gdata.data.TotalResults._qname, ('{http://a9.com/-/spec/opensearchrss/1.0/}totalResults', '{http://a9.com/-/spec/opensearch/1.1/}totalResults')) self.assertEqual( gdata.data.RecurrenceException._qname, '{http://schemas.google.com/g/2005}recurrenceException') self.assertEqual(gdata.data.RecurrenceException.specialized, 'specialized') def suite(): return conf.build_suite([StartIndexTest, StartIndexTest, GDataEntryTest, LinkFinderTest, GDataFeedTest, BatchEntryTest, BatchFeedTest, ExtendedPropertyTest, FeedLinkTest, SimpleV2FeedTest, DataClassSanityTest]) if __name__ == '__main__': unittest.main() gdata-2.0.18/tests/gdata_tests/youtube/0000750036466300116100000000000012156625015020072 5ustar afshareng00000000000000gdata-2.0.18/tests/gdata_tests/youtube/service_test.py0000640036466300116100000005574512156622363023167 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright (C) 2008 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. __author__ = 'api.jhartmann@gmail.com (Jochen Hartmann)' import getpass import time import StringIO import random import unittest import atom import gdata.youtube import gdata.youtube.service YOUTUBE_TEST_CLIENT_ID = 'ytapi-pythonclientlibrary_servicetest' class YouTubeServiceTest(unittest.TestCase): def setUp(self): self.client = gdata.youtube.service.YouTubeService() self.client.email = username self.client.password = password self.client.source = YOUTUBE_TEST_CLIENT_ID self.client.developer_key = developer_key self.client.client_id = YOUTUBE_TEST_CLIENT_ID self.client.ProgrammaticLogin() def testRetrieveVideoFeed(self): feed = self.client.GetYouTubeVideoFeed( 'https://gdata.youtube.com/feeds/api/standardfeeds/recently_featured'); self.assert_(isinstance(feed, gdata.youtube.YouTubeVideoFeed)) self.assert_(len(feed.entry) > 0) for entry in feed.entry: self.assert_(entry.title.text != '') def testRetrieveTopRatedVideoFeed(self): feed = self.client.GetTopRatedVideoFeed() self.assert_(isinstance(feed, gdata.youtube.YouTubeVideoFeed)) self.assert_(len(feed.entry) > 10) def testRetrieveMostViewedVideoFeed(self): feed = self.client.GetMostViewedVideoFeed() self.assert_(isinstance(feed, gdata.youtube.YouTubeVideoFeed)) self.assert_(len(feed.entry) > 10) def testRetrieveRecentlyFeaturedVideoFeed(self): feed = self.client.GetRecentlyFeaturedVideoFeed() self.assert_(isinstance(feed, gdata.youtube.YouTubeVideoFeed)) self.assert_(len(feed.entry) > 10) def testRetrieveWatchOnMobileVideoFeed(self): feed = self.client.GetWatchOnMobileVideoFeed() self.assert_(isinstance(feed, gdata.youtube.YouTubeVideoFeed)) self.assert_(len(feed.entry) > 10) def testRetrieveTopFavoritesVideoFeed(self): feed = self.client.GetTopFavoritesVideoFeed() self.assert_(isinstance(feed, gdata.youtube.YouTubeVideoFeed)) self.assert_(len(feed.entry) > 10) def testRetrieveMostRecentVideoFeed(self): feed = self.client.GetMostRecentVideoFeed() self.assert_(isinstance(feed, gdata.youtube.YouTubeVideoFeed)) self.assert_(len(feed.entry) > 10) def testRetrieveMostDiscussedVideoFeed(self): feed = self.client.GetMostDiscussedVideoFeed() self.assert_(isinstance(feed, gdata.youtube.YouTubeVideoFeed)) self.assert_(len(feed.entry) > 10) def testRetrieveMostLinkedVideoFeed(self): feed = self.client.GetMostLinkedVideoFeed() self.assert_(isinstance(feed, gdata.youtube.YouTubeVideoFeed)) self.assert_(len(feed.entry) > 10) def testRetrieveMostRespondedVideoFeed(self): feed = self.client.GetMostRespondedVideoFeed() self.assert_(isinstance(feed, gdata.youtube.YouTubeVideoFeed)) self.assert_(len(feed.entry) > 10) def testRetrieveVideoEntryByUri(self): entry = self.client.GetYouTubeVideoEntry( 'https://gdata.youtube.com/feeds/videos/Ncakifd_16k') self.assert_(isinstance(entry, gdata.youtube.YouTubeVideoEntry)) self.assert_(entry.title.text != '') def testRetrieveVideoEntryByVideoId(self): entry = self.client.GetYouTubeVideoEntry(video_id='Ncakifd_16k') self.assert_(isinstance(entry, gdata.youtube.YouTubeVideoEntry)) self.assert_(entry.title.text != '') def testRetrieveUserVideosbyUri(self): feed = self.client.GetYouTubeUserFeed( 'https://gdata.youtube.com/feeds/users/gdpython/uploads') self.assert_(isinstance(feed, gdata.youtube.YouTubeVideoFeed)) self.assert_(len(feed.entry) > 0) def testRetrieveUserVideosbyUsername(self): feed = self.client.GetYouTubeUserFeed(username='gdpython') self.assert_(isinstance(feed, gdata.youtube.YouTubeVideoFeed)) self.assert_(len(feed.entry) > 0) def testSearchWithVideoQuery(self): query = gdata.youtube.service.YouTubeVideoQuery() query.vq = 'google' query.max_results = 8 feed = self.client.YouTubeQuery(query) self.assert_(isinstance(feed, gdata.youtube.YouTubeVideoFeed)) self.assertEquals(len(feed.entry), 8) def testDirectVideoUploadStatusUpdateAndDeletion(self): self.assertEquals(self.client.developer_key, developer_key) self.assertEquals(self.client.client_id, YOUTUBE_TEST_CLIENT_ID) self.assertEquals(self.client.additional_headers['X-GData-Key'], 'key=' + developer_key) self.assertEquals(self.client.additional_headers['X-Gdata-Client'], YOUTUBE_TEST_CLIENT_ID) test_video_title = 'my cool video ' + str(random.randint(1000,5000)) test_video_description = 'description ' + str(random.randint(1000,5000)) my_media_group = gdata.media.Group( title = gdata.media.Title(text=test_video_title), description = gdata.media.Description(description_type='plain', text=test_video_description), keywords = gdata.media.Keywords(text='video, foo'), category = gdata.media.Category( text='Autos', scheme='http://gdata.youtube.com/schemas/2007/categories.cat', label='Autos'), player=None ) self.assert_(isinstance(my_media_group, gdata.media.Group)) # Set Geo location to 37,-122 lat, long where = gdata.geo.Where() where.set_location((37.0,-122.0)) video_entry = gdata.youtube.YouTubeVideoEntry(media=my_media_group, geo=where) self.assert_(isinstance(video_entry, gdata.youtube.YouTubeVideoEntry)) new_entry = self.client.InsertVideoEntry(video_entry, video_file_location) self.assert_(isinstance(new_entry, gdata.youtube.YouTubeVideoEntry)) self.assertEquals(new_entry.title.text, test_video_title) self.assertEquals(new_entry.media.description.text, test_video_description) self.assert_(new_entry.id.text) # check upload status also upload_status = self.client.CheckUploadStatus(new_entry) self.assert_(upload_status[0] != '') # test updating entry meta-data new_video_description = 'description ' + str(random.randint(1000,5000)) new_entry.media.description.text = new_video_description updated_entry = self.client.UpdateVideoEntry(new_entry) self.assert_(isinstance(updated_entry, gdata.youtube.YouTubeVideoEntry)) self.assertEquals(updated_entry.media.description.text, new_video_description) # sleep for 10 seconds time.sleep(10) # test to delete the entry value = self.client.DeleteVideoEntry(updated_entry) if not value: # sleep more and try again time.sleep(20) # test to delete the entry value = self.client.DeleteVideoEntry(updated_entry) self.assert_(value == True) def testDirectVideoUploadWithDeveloperTags(self): self.assertEquals(self.client.developer_key, developer_key) self.assertEquals(self.client.client_id, YOUTUBE_TEST_CLIENT_ID) self.assertEquals(self.client.additional_headers['X-GData-Key'], 'key=' + developer_key) self.assertEquals(self.client.additional_headers['X-Gdata-Client'], YOUTUBE_TEST_CLIENT_ID) test_video_title = 'my cool video ' + str(random.randint(1000,5000)) test_video_description = 'description ' + str(random.randint(1000,5000)) test_developer_tag_01 = 'tag' + str(random.randint(1000,5000)) test_developer_tag_02 = 'tag' + str(random.randint(1000,5000)) test_developer_tag_03 = 'tag' + str(random.randint(1000,5000)) my_media_group = gdata.media.Group( title = gdata.media.Title(text=test_video_title), description = gdata.media.Description(description_type='plain', text=test_video_description), keywords = gdata.media.Keywords(text='video, foo'), category = [gdata.media.Category( text='Autos', scheme='http://gdata.youtube.com/schemas/2007/categories.cat', label='Autos')], player=None ) self.assert_(isinstance(my_media_group, gdata.media.Group)) video_entry = gdata.youtube.YouTubeVideoEntry(media=my_media_group) original_developer_tags = [test_developer_tag_01, test_developer_tag_02, test_developer_tag_03] dev_tags = video_entry.AddDeveloperTags(original_developer_tags) for dev_tag in dev_tags: self.assert_(dev_tag.text in original_developer_tags) self.assert_(isinstance(video_entry, gdata.youtube.YouTubeVideoEntry)) new_entry = self.client.InsertVideoEntry(video_entry, video_file_location) self.assert_(isinstance(new_entry, gdata.youtube.YouTubeVideoEntry)) self.assertEquals(new_entry.title.text, test_video_title) self.assertEquals(new_entry.media.description.text, test_video_description) self.assert_(new_entry.id.text) developer_tags_from_new_entry = new_entry.GetDeveloperTags() for dev_tag in developer_tags_from_new_entry: self.assert_(dev_tag.text in original_developer_tags) self.assertEquals(len(developer_tags_from_new_entry), len(original_developer_tags)) # sleep for 10 seconds time.sleep(10) # test to delete the entry value = self.client.DeleteVideoEntry(new_entry) if not value: # sleep more and try again time.sleep(20) # test to delete the entry value = self.client.DeleteVideoEntry(new_entry) self.assert_(value == True) def testBrowserBasedVideoUpload(self): self.assertEquals(self.client.developer_key, developer_key) self.assertEquals(self.client.client_id, YOUTUBE_TEST_CLIENT_ID) self.assertEquals(self.client.additional_headers['X-GData-Key'], 'key=' + developer_key) self.assertEquals(self.client.additional_headers['X-Gdata-Client'], YOUTUBE_TEST_CLIENT_ID) test_video_title = 'my cool video ' + str(random.randint(1000,5000)) test_video_description = 'description ' + str(random.randint(1000,5000)) my_media_group = gdata.media.Group( title = gdata.media.Title(text=test_video_title), description = gdata.media.Description(description_type='plain', text=test_video_description), keywords = gdata.media.Keywords(text='video, foo'), category = gdata.media.Category( text='Autos', scheme='http://gdata.youtube.com/schemas/2007/categories.cat', label='Autos'), player=None ) self.assert_(isinstance(my_media_group, gdata.media.Group)) video_entry = gdata.youtube.YouTubeVideoEntry(media=my_media_group) self.assert_(isinstance(video_entry, gdata.youtube.YouTubeVideoEntry)) response = self.client.GetFormUploadToken(video_entry) self.assert_(response[0].startswith( 'https://uploads.gdata.youtube.com/action/FormDataUpload/')) self.assert_(len(response[0]) > 55) self.assert_(len(response[1]) > 100) def testRetrieveRelatedVideoFeedByUri(self): feed = self.client.GetYouTubeRelatedVideoFeed( 'https://gdata.youtube.com/feeds/videos/Ncakifd_16k/related') self.assert_(isinstance(feed, gdata.youtube.YouTubeVideoFeed)) self.assert_(len(feed.entry) > 0) def testRetrieveRelatedVideoFeedById(self): feed = self.client.GetYouTubeRelatedVideoFeed(video_id = 'Ncakifd_16k') self.assert_(isinstance(feed, gdata.youtube.YouTubeVideoFeed)) self.assert_(len(feed.entry) > 0) def testRetrieveResponseVideoFeedByUri(self): feed = self.client.GetYouTubeVideoResponseFeed( 'https://gdata.youtube.com/feeds/videos/Ncakifd_16k/responses') self.assert_(isinstance(feed, gdata.youtube.YouTubeVideoResponseFeed)) self.assert_(len(feed.entry) > 0) def testRetrieveResponseVideoFeedById(self): feed = self.client.GetYouTubeVideoResponseFeed(video_id='Ncakifd_16k') self.assert_(isinstance(feed, gdata.youtube.YouTubeVideoResponseFeed)) self.assert_(len(feed.entry) > 0) def testRetrieveVideoCommentFeedByUri(self): feed = self.client.GetYouTubeVideoCommentFeed( 'https://gdata.youtube.com/feeds/api/videos/Ncakifd_16k/comments') self.assert_(isinstance(feed, gdata.youtube.YouTubeVideoCommentFeed)) self.assert_(len(feed.entry) > 0) def testRetrieveVideoCommentFeedByVideoId(self): feed = self.client.GetYouTubeVideoCommentFeed(video_id='Ncakifd_16k') self.assert_(isinstance(feed, gdata.youtube.YouTubeVideoCommentFeed)) self.assert_(len(feed.entry) > 0) def testAddComment(self): video_id = '9g6buYJTt_g' video_entry = self.client.GetYouTubeVideoEntry(video_id=video_id) random_comment_text = 'test_comment_' + str(random.randint(1000,50000)) self.client.AddComment(comment_text=random_comment_text, video_entry=video_entry) comment_feed = self.client.GetYouTubeVideoCommentFeed(video_id=video_id) comment_found = False for item in comment_feed.entry: if (item.content.text == random_comment_text): comment_found = True self.assertEquals(comment_found, True) def testAddRating(self): video_id_to_rate = 'Ncakifd_16k' video_entry = self.client.GetYouTubeVideoEntry(video_id=video_id_to_rate) response = self.client.AddRating(3, video_entry) self.assert_(isinstance(response, gdata.GDataEntry)) def testRetrievePlaylistFeedByUri(self): feed = self.client.GetYouTubePlaylistFeed( 'https://gdata.youtube.com/feeds/users/gdpython/playlists') self.assert_(isinstance(feed, gdata.youtube.YouTubePlaylistFeed)) self.assert_(len(feed.entry) > 0) def testRetrievePlaylistListFeedByUsername(self): feed = self.client.GetYouTubePlaylistFeed(username='gdpython') self.assert_(isinstance(feed, gdata.youtube.YouTubePlaylistFeed)) self.assert_(len(feed.entry) > 0) def testRetrievePlaylistVideoFeed(self): feed = self.client.GetYouTubePlaylistVideoFeed( 'https://gdata.youtube.com/feeds/api/playlists/BCB3BB96DF51B505') self.assert_(isinstance(feed, gdata.youtube.YouTubePlaylistVideoFeed)) self.assert_(len(feed.entry) > 0) self.assert_(isinstance(feed.entry[0], gdata.youtube.YouTubePlaylistVideoEntry)) def testAddUpdateAndDeletePlaylist(self): test_playlist_title = 'my test playlist ' + str(random.randint(1000,3000)) test_playlist_description = 'test playlist ' response = self.client.AddPlaylist(test_playlist_title, test_playlist_description) self.assert_(isinstance(response, gdata.youtube.YouTubePlaylistEntry)) new_playlist_title = 'my updated playlist ' + str(random.randint(1000,4000)) new_playlist_description = 'my updated playlist ' playlist_entry_id = response.id.text.split('/')[-1] updated_playlist = self.client.UpdatePlaylist(playlist_entry_id, new_playlist_title, new_playlist_description) playlist_feed = self.client.GetYouTubePlaylistFeed() update_successful = False for playlist_entry in playlist_feed.entry: if playlist_entry.title.text == new_playlist_title: update_successful = True break self.assertEquals(update_successful, True) # wait time.sleep(10) # delete it playlist_uri = updated_playlist.id.text response = self.client.DeletePlaylist(playlist_uri) self.assertEquals(response, True) def testAddUpdateAndDeletePrivatePlaylist(self): test_playlist_title = 'my test playlist ' + str(random.randint(1000,3000)) test_playlist_description = 'test playlist ' response = self.client.AddPlaylist(test_playlist_title, test_playlist_description, playlist_private=True) self.assert_(isinstance(response, gdata.youtube.YouTubePlaylistEntry)) new_playlist_title = 'my updated playlist ' + str(random.randint(1000,4000)) new_playlist_description = 'my updated playlist ' playlist_entry_id = response.id.text.split('/')[-1] updated_playlist = self.client.UpdatePlaylist(playlist_entry_id, new_playlist_title, new_playlist_description, playlist_private=True) playlist_feed = self.client.GetYouTubePlaylistFeed() update_successful = False playlist_still_private = False for playlist_entry in playlist_feed.entry: if playlist_entry.title.text == new_playlist_title: update_successful = True if playlist_entry.private is not None: playlist_still_private = True self.assertEquals(update_successful, True) self.assertEquals(playlist_still_private, True) # wait time.sleep(10) # delete it playlist_uri = updated_playlist.id.text response = self.client.DeletePlaylist(playlist_uri) self.assertEquals(response, True) def testAddEditAndDeleteVideoFromPlaylist(self): test_playlist_title = 'my test playlist ' + str(random.randint(1000,3000)) test_playlist_description = 'test playlist ' response = self.client.AddPlaylist(test_playlist_title, test_playlist_description) self.assert_(isinstance(response, gdata.youtube.YouTubePlaylistEntry)) custom_video_title = 'my test video on my test playlist' custom_video_description = 'this is a test video on my test playlist' video_id = 'Ncakifd_16k' playlist_uri = response.feed_link[0].href time.sleep(10) response = self.client.AddPlaylistVideoEntryToPlaylist( playlist_uri, video_id, custom_video_title, custom_video_description) self.assert_(isinstance(response, gdata.youtube.YouTubePlaylistVideoEntry)) playlist_entry_id = response.id.text.split('/')[-1] playlist_uri = response.id.text.split(playlist_entry_id)[0][:-1] new_video_title = 'video number ' + str(random.randint(1000,3000)) new_video_description = 'test video' time.sleep(10) response = self.client.UpdatePlaylistVideoEntryMetaData( playlist_uri, playlist_entry_id, new_video_title, new_video_description, 1) self.assert_(isinstance(response, gdata.youtube.YouTubePlaylistVideoEntry)) time.sleep(10) playlist_entry_id = response.id.text.split('/')[-1] # remove video from playlist response = self.client.DeletePlaylistVideoEntry(playlist_uri, playlist_entry_id) self.assertEquals(response, True) time.sleep(10) # delete the playlist response = self.client.DeletePlaylist(playlist_uri) self.assertEquals(response, True) def testRetrieveSubscriptionFeedByUri(self): feed = self.client.GetYouTubeSubscriptionFeed( 'https://gdata.youtube.com/feeds/users/gdpython/subscriptions') self.assert_(isinstance(feed, gdata.youtube.YouTubeSubscriptionFeed)) self.assert_(len(feed.entry) == 3) subscription_to_channel_found = False subscription_to_favorites_found = False subscription_to_query_found = False all_types_found = False for entry in feed.entry: self.assert_(isinstance(entry, gdata.youtube.YouTubeSubscriptionEntry)) subscription_type = entry.GetSubscriptionType() if subscription_type == 'channel': subscription_to_channel_found = True elif subscription_type == 'favorites': subscription_to_favorites_found = True elif subscription_type == 'query': subscription_to_query_found = True if (subscription_to_channel_found and subscription_to_favorites_found and subscription_to_query_found): all_types_found = True self.assertEquals(all_types_found, True) def testRetrieveSubscriptionFeedByUsername(self): feed = self.client.GetYouTubeSubscriptionFeed(username='gdpython') self.assert_(isinstance(feed, gdata.youtube.YouTubeSubscriptionFeed)) self.assert_(len(feed.entry) == 3) subscription_to_channel_found = False subscription_to_favorites_found = False subscription_to_query_found = False all_types_found = False for entry in feed.entry: self.assert_(isinstance(entry, gdata.youtube.YouTubeSubscriptionEntry)) subscription_type = entry.GetSubscriptionType() if subscription_type == 'channel': subscription_to_channel_found = True elif subscription_type == 'favorites': subscription_to_favorites_found = True elif subscription_type == 'query': subscription_to_query_found = True if (subscription_to_channel_found and subscription_to_favorites_found and subscription_to_query_found): all_types_found = True self.assertEquals(all_types_found, True) def testRetrieveUserProfileByUri(self): user = self.client.GetYouTubeUserEntry( 'https://gdata.youtube.com/feeds/users/gdpython') self.assert_(isinstance(user, gdata.youtube.YouTubeUserEntry)) self.assertEquals(user.location.text, 'US') def testRetrieveUserProfileByUsername(self): user = self.client.GetYouTubeUserEntry(username='gdpython') self.assert_(isinstance(user, gdata.youtube.YouTubeUserEntry)) self.assertEquals(user.location.text, 'US') def testRetrieveUserFavoritesFeed(self): feed = self.client.GetUserFavoritesFeed(username='gdpython') self.assert_(isinstance(feed, gdata.youtube.YouTubeVideoFeed)) self.assert_(len(feed.entry) > 0) def testRetrieveDefaultUserFavoritesFeed(self): feed = self.client.GetUserFavoritesFeed() self.assert_(isinstance(feed, gdata.youtube.YouTubeVideoFeed)) self.assert_(len(feed.entry) > 0) def testAddAndDeleteVideoFromFavorites(self): video_id = 'Ncakifd_16k' video_entry = self.client.GetYouTubeVideoEntry(video_id=video_id) response = self.client.AddVideoEntryToFavorites(video_entry) self.assert_(isinstance(response, gdata.GDataEntry)) time.sleep(10) response = self.client.DeleteVideoEntryFromFavorites(video_id) self.assertEquals(response, True) def testRetrieveContactFeedByUri(self): feed = self.client.GetYouTubeContactFeed( 'https://gdata.youtube.com/feeds/users/gdpython/contacts') self.assert_(isinstance(feed, gdata.youtube.YouTubeContactFeed)) self.assertEquals(len(feed.entry), 1) def testRetrieveContactFeedByUsername(self): feed = self.client.GetYouTubeContactFeed(username='gdpython') self.assert_(isinstance(feed, gdata.youtube.YouTubeContactFeed)) self.assertEquals(len(feed.entry), 1) if __name__ == '__main__': print ('NOTE: Please run these tests only with a test account. ' 'The tests may delete or update your data.') username = raw_input('Please enter your username: ') password = getpass.getpass() developer_key = raw_input('Please enter your developer key: ') video_file_location = raw_input( 'Please enter the absolute path to a video file: ') unittest.main() gdata-2.0.18/tests/gdata_tests/youtube/__init__.py0000640036466300116100000000000012156622363022175 0ustar afshareng00000000000000gdata-2.0.18/tests/gdata_tests/youtube/live_client_test.py0000640036466300116100000001141212156622363024003 0ustar afshareng00000000000000#!/usr/bin/env python # # Copyright (C) 2009 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. # This module is used for version 2 of the Google Data APIs. # These tests attempt to connect to Google servers. __author__ = 's@google.com (John Skidgel)' # Python imports. import unittest import urllib import urllib2 # Google Data APIs imports. import gdata.youtube.client import gdata.youtube.data import gdata.gauth import gdata.client import atom.http_core import atom.mock_http_core import atom.core import gdata.data import gdata.test_config as conf # Constants #DEVELOPER_KEY = 'AI39si4DTx4tY1ZCnIiZJrxtaxzfYuomY20SKDSfIAYrehKForeoHVgAgJZdNcYhmugD103wciae6TRI6M96nSymS8TV1kNP7g' #CLIENT_ID = 'ytapi-Google-CaptionTube-2rj5q0oh-0' conf.options.register_option(conf.YT_DEVELOPER_KEY_OPTION) conf.options.register_option(conf.YT_CLIENT_ID_OPTION) conf.options.register_option(conf.YT_VIDEO_ID_OPTION) TRACK_BODY_SRT = """1 00:00:04,0 --> 00:00:05,75 My other computer is a data center """ class YouTubeClientTest(unittest.TestCase): def setUp(self): self.client = None if conf.options.get_value('runlive') == 'true': self.client = gdata.youtube.client.YouTubeClient() conf.configure_client(self.client, 'YouTubeTest', 'youtube') def tearDown(self): conf.close_client(self.client) def test_retrieve_video_entry(self): if not conf.options.get_value('runlive') == 'true': return # Either load the recording or prepare to make a live request. conf.configure_cache(self.client, 'test_retrieve_video_entry') entry = self.client.get_video_entry(video_id=conf.options.get_value('videoid')) self.assertTrue(entry.etag) def test_retrieve_video_feed(self): if not conf.options.get_value('runlive') == 'true': return # Either load the recording or prepare to make a live request. conf.configure_cache(self.client, 'test_retrieve_video_has_entries') entries = self.client.get_videos() self.assertTrue(len(entries.entry) > 0) def test_retrieve_user_feed(self): if not conf.options.get_value('runlive') == 'true': return # Either load the recording or prepare to make a live request. conf.configure_cache(self.client, 'test_retrieve_video_has_entries') entries = self.client.get_user_feed(username='joegregoriotest') self.assertTrue(len(entries.entry) > 0) def test_create_update_delete_captions(self): if not conf.options.get_value('runlive') == 'true': return # Either load the recording or prepare to make a live request. conf.configure_cache(self.client, 'test_create_update_delete_captions') # Add a track. created = self.client.create_track(conf.options.get_value('videoid'), 'Test', 'en', TRACK_BODY_SRT, conf.options.get_value('clientid'), conf.options.get_value('developerkey')) self.assertEqual(created.__class__, gdata.youtube.data.TrackEntry) # Update the contents of a track. Language and title cannot be # updated due to limitations. A workaround is to delete the original # track and replace it with captions that have the desired contents, # title, and name. # @see 'Updating a caption track' in the protocol guide for captions: # http://code.google.com/intl/en/apis/youtube/2.0/ # developers_guide_protocol_captions.html updated = self.client.update_track(conf.options.get_value('videoid'), created, TRACK_BODY_SRT, conf.options.get_value('clientid'), conf.options.get_value('developerkey')) self.assertEqual(updated.__class__, gdata.youtube.data.TrackEntry) # Retrieve the captions for the track for comparision testing. track_url = updated.content.src track = self.client.get_caption_track( track_url, conf.options.get_value('clientid'), conf.options.get_value('developerkey')) track_contents = track.read() self.assertEqual(track_contents, TRACK_BODY_SRT) # Delete a track. resp = self.client.delete_track(conf.options.get_value('videoid'), created, conf.options.get_value('clientid'), conf.options.get_value('developerkey')) self.assertEqual(200, resp.status) def suite(): return conf.build_suite([YouTubeClientTest]) if __name__ == '__main__': unittest.TextTestRunner().run(suite()) gdata-2.0.18/tests/gdata_tests/data_smoke_test.py0000750036466300116100000003343712156622363022136 0ustar afshareng00000000000000#!/usr/bin/env python # # Copyright (C) 2009 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. # This module is used for version 2 of the Google Data APIs. __author__ = 'j.s@google.com (Jeff Scudder)' import unittest import gdata.test_config as conf import gdata.data import gdata.acl.data import gdata.analytics.data import gdata.dublincore.data import gdata.books.data import gdata.calendar.data import gdata.geo.data import gdata.finance.data import gdata.notebook.data import gdata.media.data import gdata.youtube.data import gdata.webmastertools.data import gdata.contacts.data import gdata.opensearch.data class DataSmokeTest(unittest.TestCase): def test_check_all_data_classes(self): conf.check_data_classes(self, ( gdata.data.TotalResults, gdata.data.StartIndex, gdata.data.ItemsPerPage, gdata.data.ExtendedProperty, gdata.data.GDEntry, gdata.data.GDFeed, gdata.data.BatchId, gdata.data.BatchOperation, gdata.data.BatchStatus, gdata.data.BatchEntry, gdata.data.BatchInterrupted, gdata.data.BatchFeed, gdata.data.EntryLink, gdata.data.FeedLink, gdata.data.AdditionalName, gdata.data.Comments, gdata.data.Country, gdata.data.Email, gdata.data.FamilyName, gdata.data.Im, gdata.data.GivenName, gdata.data.NamePrefix, gdata.data.NameSuffix, gdata.data.FullName, gdata.data.Name, gdata.data.OrgDepartment, gdata.data.OrgName, gdata.data.OrgSymbol, gdata.data.OrgTitle, gdata.data.Organization, gdata.data.When, gdata.data.Who, gdata.data.OriginalEvent, gdata.data.PhoneNumber, gdata.data.PostalAddress, gdata.data.Rating, gdata.data.Recurrence, gdata.data.RecurrenceException, gdata.data.Reminder, gdata.data.Agent, gdata.data.HouseName, gdata.data.Street, gdata.data.PoBox, gdata.data.Neighborhood, gdata.data.City, gdata.data.Subregion, gdata.data.Region, gdata.data.Postcode, gdata.data.Country, gdata.data.FormattedAddress, gdata.data.StructuredPostalAddress, gdata.data.Where, gdata.data.AttendeeType, gdata.data.AttendeeStatus, gdata.data.Deleted, gdata.data.Money, gdata.acl.data.AclRole, gdata.acl.data.AclScope, gdata.acl.data.AclWithKey, gdata.acl.data.AclEntry, gdata.acl.data.AclFeed, gdata.analytics.data.Dimension, gdata.analytics.data.EndDate, gdata.analytics.data.Metric, gdata.analytics.data.Aggregates, gdata.analytics.data.DataEntry, gdata.analytics.data.Property, gdata.analytics.data.StartDate, gdata.analytics.data.TableId, gdata.analytics.data.AccountEntry, gdata.analytics.data.TableName, gdata.analytics.data.DataSource, gdata.analytics.data.AccountFeed, gdata.analytics.data.DataFeed, gdata.dublincore.data.Creator, gdata.dublincore.data.Date, gdata.dublincore.data.Description, gdata.dublincore.data.Format, gdata.dublincore.data.Identifier, gdata.dublincore.data.Language, gdata.dublincore.data.Publisher, gdata.dublincore.data.Rights, gdata.dublincore.data.Subject, gdata.dublincore.data.Title, gdata.books.data.CollectionEntry, gdata.books.data.CollectionFeed, gdata.books.data.Embeddability, gdata.books.data.OpenAccess, gdata.books.data.Review, gdata.books.data.Viewability, gdata.books.data.VolumeEntry, gdata.books.data.VolumeFeed, gdata.calendar.data.AccessLevelProperty, gdata.calendar.data.AllowGSync2Property, gdata.calendar.data.AllowGSyncProperty, gdata.calendar.data.AnyoneCanAddSelfProperty, gdata.calendar.data.CalendarAclRole, gdata.calendar.data.CalendarCommentEntry, gdata.calendar.data.CalendarCommentFeed, gdata.calendar.data.CalendarComments, gdata.calendar.data.CalendarExtendedProperty, gdata.calendar.data.CalendarWhere, gdata.calendar.data.ColorProperty, gdata.calendar.data.GuestsCanInviteOthersProperty, gdata.calendar.data.GuestsCanModifyProperty, gdata.calendar.data.GuestsCanSeeGuestsProperty, gdata.calendar.data.HiddenProperty, gdata.calendar.data.IcalUIDProperty, gdata.calendar.data.OverrideNameProperty, gdata.calendar.data.PrivateCopyProperty, gdata.calendar.data.QuickAddProperty, gdata.calendar.data.ResourceProperty, gdata.calendar.data.EventWho, gdata.calendar.data.SelectedProperty, gdata.calendar.data.SendAclNotificationsProperty, gdata.calendar.data.CalendarAclEntry, gdata.calendar.data.CalendarAclFeed, gdata.calendar.data.SendEventNotificationsProperty, gdata.calendar.data.SequenceNumberProperty, gdata.calendar.data.CalendarRecurrenceExceptionEntry, gdata.calendar.data.CalendarRecurrenceException, gdata.calendar.data.SettingsProperty, gdata.calendar.data.SettingsEntry, gdata.calendar.data.CalendarSettingsFeed, gdata.calendar.data.SuppressReplyNotificationsProperty, gdata.calendar.data.SyncEventProperty, gdata.calendar.data.CalendarEventEntry, gdata.calendar.data.TimeZoneProperty, gdata.calendar.data.TimesCleanedProperty, gdata.calendar.data.CalendarEntry, gdata.calendar.data.CalendarEventFeed, gdata.calendar.data.CalendarFeed, gdata.calendar.data.WebContentGadgetPref, gdata.calendar.data.WebContent, gdata.finance.data.Commission, gdata.finance.data.CostBasis, gdata.finance.data.DaysGain, gdata.finance.data.Gain, gdata.finance.data.MarketValue, gdata.finance.data.PortfolioData, gdata.finance.data.PortfolioEntry, gdata.finance.data.PortfolioFeed, gdata.finance.data.PositionData, gdata.finance.data.Price, gdata.finance.data.Symbol, gdata.finance.data.PositionEntry, gdata.finance.data.PositionFeed, gdata.finance.data.TransactionData, gdata.finance.data.TransactionEntry, gdata.finance.data.TransactionFeed, gdata.notebook.data.ComesAfter, gdata.notebook.data.NoteEntry, gdata.notebook.data.NotebookFeed, gdata.notebook.data.NotebookListEntry, gdata.notebook.data.NotebookListFeed, gdata.youtube.data.ComplaintEntry, gdata.youtube.data.ComplaintFeed, gdata.youtube.data.RatingEntry, gdata.youtube.data.RatingFeed, gdata.youtube.data.YouTubeMediaContent, gdata.youtube.data.YtAge, gdata.youtube.data.YtBooks, gdata.youtube.data.YtCompany, gdata.youtube.data.YtDescription, gdata.youtube.data.YtDuration, gdata.youtube.data.YtFirstName, gdata.youtube.data.YtGender, gdata.youtube.data.YtHobbies, gdata.youtube.data.YtHometown, gdata.youtube.data.YtLastName, gdata.youtube.data.YtLocation, gdata.youtube.data.YtMovies, gdata.youtube.data.YtMusic, gdata.youtube.data.YtNoEmbed, gdata.youtube.data.YtOccupation, gdata.youtube.data.YtPlaylistId, gdata.youtube.data.YtPosition, gdata.youtube.data.YtPrivate, gdata.youtube.data.YtQueryString, gdata.youtube.data.YtRacy, gdata.youtube.data.YtRecorded, gdata.youtube.data.YtRelationship, gdata.youtube.data.YtSchool, gdata.youtube.data.YtStatistics, gdata.youtube.data.YtStatus, gdata.youtube.data.YtUserProfileStatistics, gdata.youtube.data.YtUsername, gdata.youtube.data.FriendEntry, gdata.youtube.data.FriendFeed, gdata.youtube.data.YtVideoStatistics, gdata.youtube.data.ChannelEntry, gdata.youtube.data.ChannelFeed, gdata.youtube.data.FavoriteEntry, gdata.youtube.data.FavoriteFeed, gdata.youtube.data.YouTubeMediaCredit, gdata.youtube.data.YouTubeMediaRating, gdata.youtube.data.YtAboutMe, gdata.youtube.data.UserProfileEntry, gdata.youtube.data.UserProfileFeed, gdata.youtube.data.YtAspectRatio, gdata.youtube.data.YtBasePublicationState, gdata.youtube.data.YtPublicationState, gdata.youtube.data.YouTubeAppControl, gdata.youtube.data.YtCaptionPublicationState, gdata.youtube.data.YouTubeCaptionAppControl, gdata.youtube.data.CaptionTrackEntry, gdata.youtube.data.CaptionTrackFeed, gdata.youtube.data.YtCountHint, gdata.youtube.data.PlaylistLinkEntry, gdata.youtube.data.PlaylistLinkFeed, gdata.youtube.data.YtModerationStatus, gdata.youtube.data.YtPlaylistTitle, gdata.youtube.data.SubscriptionEntry, gdata.youtube.data.SubscriptionFeed, gdata.youtube.data.YtSpam, gdata.youtube.data.CommentEntry, gdata.youtube.data.CommentFeed, gdata.youtube.data.YtUploaded, gdata.youtube.data.YtVideoId, gdata.youtube.data.YouTubeMediaGroup, gdata.youtube.data.VideoEntryBase, gdata.youtube.data.PlaylistEntry, gdata.youtube.data.PlaylistFeed, gdata.youtube.data.VideoEntry, gdata.youtube.data.VideoFeed, gdata.youtube.data.VideoMessageEntry, gdata.youtube.data.VideoMessageFeed, gdata.youtube.data.UserEventEntry, gdata.youtube.data.UserEventFeed, gdata.youtube.data.VideoModerationEntry, gdata.youtube.data.VideoModerationFeed, gdata.media.data.MediaCategory, gdata.media.data.MediaCopyright, gdata.media.data.MediaCredit, gdata.media.data.MediaDescription, gdata.media.data.MediaHash, gdata.media.data.MediaKeywords, gdata.media.data.MediaPlayer, gdata.media.data.MediaRating, gdata.media.data.MediaRestriction, gdata.media.data.MediaText, gdata.media.data.MediaThumbnail, gdata.media.data.MediaTitle, gdata.media.data.MediaContent, gdata.media.data.MediaGroup, gdata.webmastertools.data.CrawlIssueCrawlType, gdata.webmastertools.data.CrawlIssueDateDetected, gdata.webmastertools.data.CrawlIssueDetail, gdata.webmastertools.data.CrawlIssueIssueType, gdata.webmastertools.data.CrawlIssueLinkedFromUrl, gdata.webmastertools.data.CrawlIssueUrl, gdata.webmastertools.data.CrawlIssueEntry, gdata.webmastertools.data.CrawlIssuesFeed, gdata.webmastertools.data.Indexed, gdata.webmastertools.data.Keyword, gdata.webmastertools.data.KeywordEntry, gdata.webmastertools.data.KeywordsFeed, gdata.webmastertools.data.LastCrawled, gdata.webmastertools.data.MessageBody, gdata.webmastertools.data.MessageDate, gdata.webmastertools.data.MessageLanguage, gdata.webmastertools.data.MessageRead, gdata.webmastertools.data.MessageSubject, gdata.webmastertools.data.SiteId, gdata.webmastertools.data.MessageEntry, gdata.webmastertools.data.MessagesFeed, gdata.webmastertools.data.SitemapEntry, gdata.webmastertools.data.SitemapMobileMarkupLanguage, gdata.webmastertools.data.SitemapMobile, gdata.webmastertools.data.SitemapNewsPublicationLabel, gdata.webmastertools.data.SitemapNews, gdata.webmastertools.data.SitemapType, gdata.webmastertools.data.SitemapUrlCount, gdata.webmastertools.data.SitemapsFeed, gdata.webmastertools.data.VerificationMethod, gdata.webmastertools.data.Verified, gdata.webmastertools.data.SiteEntry, gdata.webmastertools.data.SitesFeed, gdata.contacts.data.BillingInformation, gdata.contacts.data.Birthday, gdata.contacts.data.CalendarLink, gdata.contacts.data.DirectoryServer, gdata.contacts.data.Event, gdata.contacts.data.ExternalId, gdata.contacts.data.Gender, gdata.contacts.data.Hobby, gdata.contacts.data.Initials, gdata.contacts.data.Jot, gdata.contacts.data.Language, gdata.contacts.data.MaidenName, gdata.contacts.data.Mileage, gdata.contacts.data.NickName, gdata.contacts.data.Occupation, gdata.contacts.data.Priority, gdata.contacts.data.Relation, gdata.contacts.data.Sensitivity, gdata.contacts.data.UserDefinedField, gdata.contacts.data.Website, gdata.contacts.data.HouseName, gdata.contacts.data.Street, gdata.contacts.data.POBox, gdata.contacts.data.Neighborhood, gdata.contacts.data.City, gdata.contacts.data.SubRegion, gdata.contacts.data.Region, gdata.contacts.data.PostalCode, gdata.contacts.data.Country, gdata.contacts.data.PersonEntry, gdata.contacts.data.Deleted, gdata.contacts.data.GroupMembershipInfo, gdata.contacts.data.ContactEntry, gdata.contacts.data.ContactsFeed, gdata.contacts.data.SystemGroup, gdata.contacts.data.GroupEntry, gdata.contacts.data.GroupsFeed, gdata.contacts.data.ProfileEntry, gdata.contacts.data.ProfilesFeed, gdata.opensearch.data.ItemsPerPage, gdata.opensearch.data.StartIndex, gdata.opensearch.data.TotalResults, )) def suite(): return conf.build_suite([DataSmokeTest]) if __name__ == '__main__': unittest.main() gdata-2.0.18/tests/gdata_tests/client_smoke_test.py0000750036466300116100000000331712156622363022475 0ustar afshareng00000000000000#!/usr/bin/env python # # 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. # This module is used for version 2 of the Google Data APIs. __author__ = 'j.s@google.com (Jeff Scudder)' import unittest import gdata.test_config as conf import gdata.analytics.client import gdata.apps.emailsettings.client import gdata.blogger.client import gdata.spreadsheets.client import gdata.calendar_resource.client import gdata.contacts.client import gdata.docs.client import gdata.projecthosting.client import gdata.sites.client class ClientSmokeTest(unittest.TestCase): def test_check_auth_client_classes(self): conf.check_clients_with_auth(self, ( gdata.analytics.client.AnalyticsClient, gdata.apps.emailsettings.client.EmailSettingsClient, gdata.blogger.client.BloggerClient, gdata.spreadsheets.client.SpreadsheetsClient, gdata.calendar_resource.client.CalendarResourceClient, gdata.contacts.client.ContactsClient, gdata.docs.client.DocsClient, gdata.projecthosting.client.ProjectHostingClient, gdata.sites.client.SitesClient )) def suite(): return conf.build_suite([ClientSmokeTest]) if __name__ == '__main__': unittest.main() gdata-2.0.18/tests/gdata_tests/__init__.py0000640036466300116100000000000012156622363020501 0ustar afshareng00000000000000gdata-2.0.18/tests/gdata_tests/calendar_test.py0000750036466300116100000011425012156622363021571 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright (C) 2006 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. __author__ = 'api.jscudder@gmail.com (Jeff Scudder)' import unittest try: from xml.etree import ElementTree except ImportError: from elementtree import ElementTree import atom import gdata from gdata import test_data import gdata.calendar class CalendarFeedTest(unittest.TestCase): def setUp(self): self.calendar_feed = gdata.calendar.CalendarListFeedFromString( test_data.CALENDAR_FEED) def testEntryCount(self): # Assert the number of items in the feed of calendars self.assertEquals(len(self.calendar_feed.entry),2) def testToAndFromString(self): # Assert the appropriate type for each entry for an_entry in self.calendar_feed.entry: self.assert_(isinstance(an_entry, gdata.calendar.CalendarListEntry), 'Entry must be an instance of CalendarListEntry') # Regenerate feed from xml text new_calendar_feed = ( gdata.calendar.CalendarListFeedFromString(str(self.calendar_feed))) for an_entry in new_calendar_feed.entry: self.assert_(isinstance(an_entry, gdata.calendar.CalendarListEntry), 'Entry in regenerated feed must be an instance of CalendarListEntry') def testAuthor(self): """Tests the existence of a and verifies the name and email""" # Assert that each element in the feed author list is an atom.Author for an_author in self.calendar_feed.author: self.assert_(isinstance(an_author, atom.Author), "Calendar feed element must be an instance of " + "atom.Author: %s" % an_author) # Assert the feed author name is as expected self.assertEquals(self.calendar_feed.author[0].name.text, 'GData Ops Demo') # Assert the feed author name is as expected self.assertEquals(self.calendar_feed.author[0].email.text, 'gdata.ops.demo@gmail.com') # Assert one of the values for an entry author self.assertEquals(self.calendar_feed.entry[0].author[0].name.text, 'GData Ops Demo') self.assertEquals(self.calendar_feed.entry[0].author[0].email.text, 'gdata.ops.demo@gmail.com') def testId(self): """Tests the existence of a in the feed and entries and verifies the value""" # Assert the feed id exists and is an atom.Id self.assert_(isinstance(self.calendar_feed.id, atom.Id), "Calendar feed element must be an instance of atom.Id: %s" % ( self.calendar_feed.id)) # Assert the feed id value is as expected self.assertEquals(self.calendar_feed.id.text, 'http://www.google.com/calendar/feeds/default') # Assert that each entry has an id which is an atom.Id for an_entry in self.calendar_feed.entry: self.assert_(isinstance(an_entry.id, atom.Id), "Calendar entry element must be an instance of " + "atom.Id: %s" % an_entry.id) # Assert one of the values for an id self.assertEquals(self.calendar_feed.entry[1].id.text, 'http://www.google.com/calendar/feeds/default/' + 'jnh21ovnjgfph21h32gvms2758%40group.calendar.google.com') def testPublished(self): """Tests the existence of a in the entries and verifies the value""" # Assert that each entry has a published value which is an atom.Published for an_entry in self.calendar_feed.entry: self.assert_(isinstance(an_entry.published, atom.Published), "Calendar entry element must be an instance of " + "atom.Published: %s" % an_entry.published) # Assert one of the values for published is as expected self.assertEquals(self.calendar_feed.entry[1].published.text, '2007-03-20T22:48:57.837Z') def testUpdated(self): """Tests the existence of a in the feed and the entries and verifies the value""" # Assert that the feed updated element exists and is an atom.Updated self.assert_(isinstance(self.calendar_feed.updated, atom.Updated), "Calendar feed element must be an instance of " + "atom.Updated: %s" % self.calendar_feed.updated) # Assert that each entry has a updated value which is an atom.Updated for an_entry in self.calendar_feed.entry: self.assert_(isinstance(an_entry.updated, atom.Updated), "Calendar entry element must be an instance of" + "atom.Updated: %s" % an_entry.updated) # Assert the feed updated value is as expected self.assertEquals(self.calendar_feed.updated.text, '2007-03-20T22:48:57.833Z') # Assert one of the values for updated self.assertEquals(self.calendar_feed.entry[0].updated.text, '2007-03-20T22:48:52.000Z') def testTitle(self): """Tests the existence of a in the feed and the entries and verifies the value""" # Assert that the feed title element exists and is an atom.Title self.assert_(isinstance(self.calendar_feed.title, atom.Title), "Calendar feed element must be an instance of " + "atom.Title: %s" % self.calendar_feed.title) # Assert that each entry has a title value which is an atom.Title for an_entry in self.calendar_feed.entry: self.assert_(isinstance(an_entry.title, atom.Title), "Calendar entry element must be an instance of " + "atom.Title: %s" % an_entry.title) # Assert the feed title value is as expected self.assertEquals(self.calendar_feed.title.text, 'GData Ops Demo\'s Calendar List') # Assert one of the values for title self.assertEquals(self.calendar_feed.entry[0].title.text, 'GData Ops Demo') def testColor(self): """Tests the existence of a and verifies the value""" # Assert the color is present and is a gdata.calendar.Color for an_entry in self.calendar_feed.entry: self.assert_(isinstance(an_entry.color, gdata.calendar.Color), "Calendar feed element must be an instance of " + "gdata.calendar.Color: %s" % an_entry.color) # Assert the color value is as expected self.assertEquals(self.calendar_feed.entry[0].color.value, '#2952A3') def testAccessLevel(self): """Tests the existence of a element and verifies the value""" # Assert the access_level is present and is a gdata.calendar.AccessLevel for an_entry in self.calendar_feed.entry: self.assert_(isinstance(an_entry.access_level, gdata.calendar.AccessLevel), "Calendar feed element must be an instance of " + "gdata.calendar.AccessLevel: %s" % an_entry.access_level) # Assert the access_level value is as expected self.assertEquals(self.calendar_feed.entry[0].access_level.value, 'owner') def testTimezone(self): """Tests the existence of a element and verifies the value""" # Assert the timezone is present and is a gdata.calendar.Timezone for an_entry in self.calendar_feed.entry: self.assert_(isinstance(an_entry.timezone, gdata.calendar.Timezone), "Calendar feed element must be an instance of " + "gdata.calendar.Timezone: %s" % an_entry.timezone) # Assert the timezone value is as expected self.assertEquals(self.calendar_feed.entry[0].timezone.value, 'America/Los_Angeles') def testHidden(self): """Tests the existence of a element and verifies the value""" # Assert the hidden is present and is a gdata.calendar.Hidden for an_entry in self.calendar_feed.entry: self.assert_(isinstance(an_entry.hidden, gdata.calendar.Hidden), "Calendar feed element must be an instance of " + "gdata.calendar.Hidden: %s" % an_entry.hidden) # Assert the hidden value is as expected self.assertEquals(self.calendar_feed.entry[0].hidden.value, 'false') def testOpenSearch(self): """Tests the existence of """ # Assert that the elements exist and are the appropriate type self.assert_(isinstance(self.calendar_feed.start_index, gdata.StartIndex), "Calendar feed element must be an " + "instance of gdata.StartIndex: %s" % self.calendar_feed.start_index) # Assert the values for each openSearch element are as expected self.assertEquals(self.calendar_feed.start_index.text, '1') def testGenerator(self): """Tests the existence of and verifies the value""" # Assert that the element exists and is of the appropriate type self.assert_(isinstance(self.calendar_feed.generator, atom.Generator), "Calendar feed element must be an instance of " + "atom.Generator: %s" % self.calendar_feed.generator) # Assert the generator version, uri and text are as expected self.assertEquals(self.calendar_feed.generator.text, 'Google Calendar') self.assertEquals(self.calendar_feed.generator.version, '1.0') self.assertEquals(self.calendar_feed.generator.uri, 'http://www.google.com/calendar') def testEntryLink(self): """Makes sure entry links in the private composite feed are parsed.""" entry = gdata.calendar.CalendarEventEntryFromString( test_data.RECURRENCE_EXCEPTION_ENTRY) self.assert_(isinstance(entry.recurrence_exception, list)) self.assert_(isinstance(entry.recurrence_exception[0].entry_link, gdata.EntryLink)) self.assert_(isinstance(entry.recurrence_exception[0].entry_link.entry, gdata.calendar.CalendarEventEntry)) self.assertEquals( entry.recurrence_exception[0].entry_link.entry.author[0].name.text, 'gdata ops') def testSequence(self): entry = gdata.calendar.CalendarEventEntry( sequence=gdata.calendar.Sequence(value='1')) entry2 = gdata.calendar.CalendarEventEntryFromString(str(entry)) self.assertEqual(entry.sequence.value, entry2.sequence.value) entry = gdata.calendar.CalendarEventEntryFromString( '' % ( atom.ATOM_NAMESPACE, gdata.calendar.GCAL_NAMESPACE)) self.assertEqual(entry.sequence.value, '7') def testOriginalEntry(self): """Make sure original entry in the private composite feed are parsed.""" entry = gdata.calendar.CalendarEventEntryFromString( test_data.RECURRENCE_EXCEPTION_ENTRY) self.assertEquals( entry.recurrence_exception[0].entry_link.entry.original_event.id, 'i7lgfj69mjqjgnodklif3vbm7g') class CalendarFeedTestRegenerated(CalendarFeedTest): def setUp(self): old_calendar_feed = ( gdata.calendar.CalendarListFeedFromString(test_data.CALENDAR_FEED)) self.calendar_feed = ( gdata.calendar.CalendarListFeedFromString(str(old_calendar_feed))) tree = ElementTree.fromstring(str(old_calendar_feed)) class CalendarEventFeedTest(unittest.TestCase): def setUp(self): self.calendar_event_feed = ( gdata.calendar.CalendarEventFeedFromString( test_data.CALENDAR_FULL_EVENT_FEED)) def testEntryCount(self): # Assert the number of items in the feed of events self.assertEquals(len(self.calendar_event_feed.entry),11) def testToAndFromString(self): # Assert the appropriate type for each entry for an_entry in self.calendar_event_feed.entry: self.assert_(isinstance(an_entry, gdata.calendar.CalendarEventEntry), "Entry must be an instance of a CalendarEventEntry") # Regenerate feed from xml text new_calendar_event_feed = gdata.calendar.CalendarEventFeedFromString( str(self.calendar_event_feed)) for an_entry in new_calendar_event_feed.entry: self.assert_(isinstance(an_entry, gdata.calendar.CalendarEventEntry), "Entry in regenerated feed must be an instance of CalendarEventEntry") def testAuthor(self): """Tests the existence of a and verifies the name and email""" # Assert that each element in the feed author list is an atom.Author for an_author in self.calendar_event_feed.author: self.assert_(isinstance(an_author, atom.Author), "Calendar event feed element must be an instance of " + "atom.Author: %s" % an_author) # Assert the feed author name is as expected self.assertEquals(self.calendar_event_feed.author[0].name.text, 'GData Ops Demo') # Assert the feed author name is as expected self.assertEquals(self.calendar_event_feed.author[0].email.text, 'gdata.ops.demo@gmail.com') # Assert one of the values for an entry author self.assertEquals(self.calendar_event_feed.entry[0].author[0].name.text, 'GData Ops Demo') self.assertEquals(self.calendar_event_feed.entry[0].author[0].email.text, 'gdata.ops.demo@gmail.com') def testId(self): """Tests the existence of a in the feed and entries and verifies the value""" # Assert the feed id exists and is an atom.Id self.assert_(isinstance(self.calendar_event_feed.id, atom.Id), "Calendar event feed element must be an instance of " + "atom.Id: %s" % self.calendar_event_feed.id) # Assert the feed id value is as expected self.assertEquals(self.calendar_event_feed.id.text, 'http://www.google.com/calendar/feeds/default/private/full') # Assert that each entry has an id which is an atom.Id for an_entry in self.calendar_event_feed.entry: self.assert_(isinstance(an_entry.id, atom.Id), "Calendar event entry element must be an " + "instance of atom.Id: %s" % an_entry.id) # Assert one of the values for an id self.assertEquals(self.calendar_event_feed.entry[1].id.text, 'http://www.google.com/calendar/feeds/default/private/full/' + '2qt3ao5hbaq7m9igr5ak9esjo0') def testPublished(self): """Tests the existence of a in the entries and verifies the value""" # Assert that each entry has a published value which is an atom.Published for an_entry in self.calendar_event_feed.entry: self.assert_(isinstance(an_entry.published, atom.Published), "Calendar event entry element must be an instance " + "of atom.Published: %s" % an_entry.published) # Assert one of the values for published is as expected self.assertEquals(self.calendar_event_feed.entry[1].published.text, '2007-03-20T21:26:04.000Z') def testUpdated(self): """Tests the existence of a in the feed and the entries and verifies the value""" # Assert that the feed updated element exists and is an atom.Updated self.assert_(isinstance(self.calendar_event_feed.updated, atom.Updated), "Calendar feed element must be an instance of " + "atom.Updated: %s" % self.calendar_event_feed.updated) # Assert that each entry has a updated value which is an atom.Updated for an_entry in self.calendar_event_feed.entry: self.assert_(isinstance(an_entry.updated, atom.Updated), "Calendar event entry element must be an instance " + "of atom.Updated: %s" % an_entry.updated) # Assert the feed updated value is as expected self.assertEquals(self.calendar_event_feed.updated.text, '2007-03-20T21:29:57.000Z') # Assert one of the values for updated self.assertEquals(self.calendar_event_feed.entry[3].updated.text, '2007-03-20T21:25:46.000Z') def testTitle(self): """Tests the existence of a in the feed and the entries and verifies the value""" # Assert that the feed title element exists and is an atom.Title self.assert_(isinstance(self.calendar_event_feed.title, atom.Title), "Calendar feed element must be an instance of " + "atom.Title: %s" % self.calendar_event_feed.title) # Assert that each entry has a title value which is an atom.Title for an_entry in self.calendar_event_feed.entry: self.assert_(isinstance(an_entry.title, atom.Title), "Calendar event entry element must be an instance of " + "atom.Title: %s" % an_entry.title) # Assert the feed title value is as expected self.assertEquals(self.calendar_event_feed.title.text, 'GData Ops Demo') # Assert one of the values for title self.assertEquals(self.calendar_event_feed.entry[0].title.text, 'test deleted') def testPostLink(self): """Tests the existence of a with a rel='...#post' and verifies the value""" # Assert that each link in the feed is an atom.Link for a_link in self.calendar_event_feed.link: self.assert_(isinstance(a_link, atom.Link), "Calendar event entry element must be an instance of " + "atom.Link: %s" % a_link) # Assert post link exists self.assert_(self.calendar_event_feed.GetPostLink() is not None) # Assert the post link value is as expected self.assertEquals(self.calendar_event_feed.GetPostLink().href, 'http://www.google.com/calendar/feeds/default/private/full') def testEditLink(self): """Tests the existence of a with a rel='edit' in each entry and verifies the value""" # Assert that each link in the feed is an atom.Link for a_link in self.calendar_event_feed.link: self.assert_(isinstance(a_link, atom.Link), "Calendar event entry element must be an instance of " + "atom.Link: %s" % a_link) # Assert edit link exists for a_entry in self.calendar_event_feed.entry: self.assert_(a_entry.GetEditLink() is not None) # Assert the edit link value is as expected self.assertEquals(self.calendar_event_feed.entry[0].GetEditLink().href, 'http://www.google.com/calendar/feeds/default/private/full/o99flmgm' + 'kfkfrr8u745ghr3100/63310109397') self.assertEquals(self.calendar_event_feed.entry[0].GetEditLink().type, 'application/atom+xml') def testOpenSearch(self): """Tests the existence of , , """ # Assert that the elements exist and are the appropriate type self.assert_(isinstance(self.calendar_event_feed.total_results, gdata.TotalResults), "Calendar event feed element must be an " + "instance of gdata.TotalResults: %s" % ( self.calendar_event_feed.total_results)) self.assert_( isinstance(self.calendar_event_feed.start_index, gdata.StartIndex), "Calendar event feed element must be an " + "instance of gdata.StartIndex: %s" % ( self.calendar_event_feed.start_index)) self.assert_( isinstance(self.calendar_event_feed.items_per_page, gdata.ItemsPerPage), "Calendar event feed element must be an " + "instance of gdata.ItemsPerPage: %s" % ( self.calendar_event_feed.items_per_page)) # Assert the values for each openSearch element are as expected self.assertEquals(self.calendar_event_feed.total_results.text, '10') self.assertEquals(self.calendar_event_feed.start_index.text, '1') self.assertEquals(self.calendar_event_feed.items_per_page.text, '25') def testGenerator(self): """Tests the existence of and verifies the value""" # Assert that the element exists and is of the appropriate type self.assert_(isinstance(self.calendar_event_feed.generator, atom.Generator), "Calendar event feed element must be an instance " + "of atom.Generator: %s" % self.calendar_event_feed.generator) # Assert the generator version, uri and text are as expected self.assertEquals(self.calendar_event_feed.generator.text, 'Google Calendar') self.assertEquals(self.calendar_event_feed.generator.version, '1.0') self.assertEquals(self.calendar_event_feed.generator.uri, 'http://www.google.com/calendar') def testCategory(self): """Tests the existence of and verifies the value""" # Assert that the element exists and is of the appropriate type and value for a_category in self.calendar_event_feed.category: self.assert_(isinstance(a_category, atom.Category), "Calendar event feed element must be an instance " + "of atom.Category: %s" % a_category) self.assertEquals(a_category.scheme, 'http://schemas.google.com/g/2005#kind') self.assertEquals(a_category.term, 'http://schemas.google.com/g/2005#event') for an_event in self.calendar_event_feed.entry: for a_category in an_event.category: self.assert_(isinstance(a_category, atom.Category), "Calendar event feed entry element must be an " + "instance of atom.Category: %s" % a_category) self.assertEquals(a_category.scheme, 'http://schemas.google.com/g/2005#kind') self.assertEquals(a_category.term, 'http://schemas.google.com/g/2005#event') def testSendEventNotifications(self): """Test the existence of and verifies the value""" # Assert that the element exists and is of the appropriate type and value for an_event in self.calendar_event_feed.entry: self.assert_(isinstance(an_event.send_event_notifications, gdata.calendar.SendEventNotifications), ("Calendar event feed entry element " + "must be an instance of gdata.calendar.SendEventNotifications: %s") % ( an_event.send_event_notifications,)) # Assert the are as expected self.assertEquals( self.calendar_event_feed.entry[0].send_event_notifications.value, 'false') self.assertEquals( self.calendar_event_feed.entry[2].send_event_notifications.value, 'true') def testQuickAdd(self): """Test the existence of and verifies the value""" entry = gdata.calendar.CalendarEventEntry() entry.quick_add = gdata.calendar.QuickAdd(value='true') unmarshalled_entry = entry.ToString() tag = '{%s}quickadd' % (gdata.calendar.GCAL_NAMESPACE) marshalled_entry = ElementTree.fromstring(unmarshalled_entry).find(tag) self.assert_(marshalled_entry.attrib['value'],'true') self.assert_(marshalled_entry.tag,tag) def testEventStatus(self): """Test the existence of and verifies the value""" # Assert that the element exists and is of the appropriate type and value for an_event in self.calendar_event_feed.entry: self.assert_(isinstance(an_event.event_status, gdata.calendar.EventStatus), ("Calendar event feed entry element " + "must be an instance of gdata.calendar.EventStatus: %s") % ( an_event.event_status,)) # Assert the are as expected self.assertEquals( self.calendar_event_feed.entry[0].event_status.value, 'CANCELED') self.assertEquals( self.calendar_event_feed.entry[1].event_status.value, 'CONFIRMED') def testComments(self): """Tests the existence of and verifies the value""" # Assert that the element exists and is of the appropriate type and value for an_event in self.calendar_event_feed.entry: self.assert_(an_event.comments is None or isinstance(an_event.comments, gdata.calendar.Comments), ("Calendar event feed entry element " + "must be an instance of gdata.calendar.Comments: %s") % ( an_event.comments,)) def testVisibility(self): """Test the existence of and verifies the value""" # Assert that the element exists and is of the appropriate type and value for an_event in self.calendar_event_feed.entry: self.assert_(isinstance(an_event.visibility, gdata.calendar.Visibility), ("Calendar event feed entry element " + "must be an instance of gdata.calendar.Visibility: %s") % ( an_event.visibility,)) # Assert the are as expected self.assertEquals( self.calendar_event_feed.entry[0].visibility.value, 'DEFAULT') self.assertEquals( self.calendar_event_feed.entry[1].visibility.value, 'PRIVATE') self.assertEquals( self.calendar_event_feed.entry[2].visibility.value, 'PUBLIC') def testTransparency(self): """Test the existence of and verifies the value""" # Assert that the element exists and is of the appropriate type and value for an_event in self.calendar_event_feed.entry: self.assert_(isinstance(an_event.transparency, gdata.calendar.Transparency), ("Calendar event feed entry element " + "must be an instance of gdata.calendar.Transparency: %s") % ( an_event.transparency,)) # Assert the are as expected self.assertEquals( self.calendar_event_feed.entry[0].transparency.value, 'OPAQUE') self.assertEquals( self.calendar_event_feed.entry[1].transparency.value, 'OPAQUE') self.assertEquals( self.calendar_event_feed.entry[2].transparency.value, 'OPAQUE') # TODO: TEST VALUES OF VISIBILITY OTHER THAN OPAQUE def testWhere(self): """Tests the existence of a in the entries and verifies the value""" # Assert that each entry has a where value which is an gdata.calendar.Where for an_entry in self.calendar_event_feed.entry: for a_where in an_entry.where: self.assert_(isinstance(a_where, gdata.calendar.Where), "Calendar event entry element must be an instance of " + "gdata.calendar.Where: %s" % a_where) # Assert one of the values for where is as expected self.assertEquals(self.calendar_event_feed.entry[1].where[0].value_string, 'Dolores Park with Kim') def testWhenAndReminder(self): """Tests the existence of a and in the entries and verifies the values""" # Assert that each entry's when value is a gdata.calendar.When # Assert that each reminder is a gdata.calendar.Reminder for an_entry in self.calendar_event_feed.entry: for a_when in an_entry.when: self.assert_(isinstance(a_when, gdata.calendar.When), "Calendar event entry element must be an instance " + "of gdata.calendar.When: %s" % a_when) for a_reminder in a_when.reminder: self.assert_(isinstance(a_reminder, gdata.calendar.Reminder), "Calendar event entry element must be an " + "instance of gdata.calendar.Reminder: %s" % a_reminder) # Assert one of the values for when is as expected self.assertEquals(self.calendar_event_feed.entry[0].when[0].start_time, '2007-03-23T12:00:00.000-07:00') self.assertEquals(self.calendar_event_feed.entry[0].when[0].end_time, '2007-03-23T13:00:00.000-07:00') # Assert the reminder child of when is as expected self.assertEquals( self.calendar_event_feed.entry[0].when[0].reminder[0].minutes, '10') self.assertEquals( self.calendar_event_feed.entry[1].when[0].reminder[0].minutes, '20') def testBatchRequestParsing(self): batch_request = gdata.calendar.CalendarEventFeedFromString( test_data.CALENDAR_BATCH_REQUEST) self.assertEquals(len(batch_request.entry), 4) # Iterate over the batch request entries and match the operation with # the batch id. These values are hard coded to match the test data. for entry in batch_request.entry: if entry.batch_id.text == '1': self.assertEquals(entry.batch_operation.type, 'insert') if entry.batch_id.text == '2': self.assertEquals(entry.batch_operation.type, 'query') if entry.batch_id.text == '3': self.assertEquals(entry.batch_operation.type, 'update') self.assertEquals(entry.title.text, 'Event updated via batch') if entry.batch_id.text == '4': self.assertEquals(entry.batch_operation.type, 'delete') self.assertEquals(entry.id.text, 'http://www.google.com/calendar/feeds/default/' 'private/full/d8qbg9egk1n6lhsgq1sjbqffqc') self.assertEquals(entry.GetEditLink().href, 'http://www.google.com/calendar/feeds/default/' 'private/full/d8qbg9egk1n6lhsgq1sjbqffqc/' '63326018324') def testBatchResponseParsing(self): batch_response = gdata.calendar.CalendarEventFeedFromString( test_data.CALENDAR_BATCH_RESPONSE) self.assertEquals(len(batch_response.entry), 4) for entry in batch_response.entry: if entry.batch_id.text == '1': self.assertEquals(entry.batch_operation.type, 'insert') self.assertEquals(entry.batch_status.code, '201') self.assertEquals(entry.batch_status.reason, 'Created') self.assertEquals(entry.id.text, 'http://www.google.com/calendar/' 'feeds/default/private/full/' 'n9ug78gd9tv53ppn4hdjvk68ek') if entry.batch_id.text == '2': self.assertEquals(entry.batch_operation.type, 'query') if entry.batch_id.text == '3': self.assertEquals(entry.batch_operation.type, 'update') if entry.batch_id.text == '4': self.assertEquals(entry.batch_operation.type, 'delete') self.assertEquals(entry.id.text, 'http://www.google.com/calendar/' 'feeds/default/private/full/' 'd8qbg9egk1n6lhsgq1sjbqffqc') # TODO add reminder tests for absolute_time and hours/seconds (if possible) # TODO test recurrence and recurrenceexception # TODO test originalEvent class CalendarWebContentTest(unittest.TestCase): def setUp(self): self.calendar_event_feed = ( gdata.calendar.CalendarEventFeedFromString( test_data.CALENDAR_FULL_EVENT_FEED)) def testAddSimpleWebContentEventEntry(self): """Verifies that we can add a web content link to an event entry.""" title = "Al Einstein's Birthday!" href = 'http://gdata.ops.demo.googlepages.com/birthdayicon.gif' type = 'image/jpeg' url = 'http://gdata.ops.demo.googlepages.com/einstein.jpg' width = '300' height = '225' # Create a web content event event = gdata.calendar.CalendarEventEntry() web_content = gdata.calendar.WebContent(url=url, width=width, height=height) web_content_link = gdata.calendar.WebContentLink(title=title, href=href, link_type=type, web_content=web_content) event.link.append(web_content_link) # Verify the web content link exists and contains the expected data web_content_link = event.GetWebContentLink() self.assertValidWebContentLink(title, href, type, web_content_link) # Verify the web content element exists and contains the expected data web_content_element = web_content_link.web_content self.assertValidSimpleWebContent(url, width, height, web_content_element) def testAddWebContentGadgetEventEntry(self): """Verifies that we can add a web content gadget link to an event entry.""" title = "Date and Time Gadget" href = 'http://gdata.ops.demo.googlepages.com/birthdayicon.gif' url = 'http://google.com/ig/modules/datetime.xml' type = 'application/x-google-gadgets+xml' width = '300' height = '200' pref_name = 'color' pref_value = 'green' # Create a web content event event = gdata.calendar.CalendarEventEntry() web_content = gdata.calendar.WebContent(url=url, width=width, height=height) web_content.gadget_pref.append( gdata.calendar.WebContentGadgetPref(name=pref_name, value=pref_value)) web_content_link = gdata.calendar.WebContentLink(title=title, href=href, web_content=web_content, link_type=type) event.link.append(web_content_link) # Verify the web content link exists and contains the expected data web_content_link = event.GetWebContentLink() self.assertValidWebContentLink(title, href, type, web_content_link) # Verify the web content element exists and contains the expected data web_content_element = web_content_link.web_content self.assertValidWebContentGadget(url, width, height, pref_name, pref_value, web_content_element) def testFromXmlToSimpleWebContent(self): """Verifies that we can read a web content link from an event entry.""" # Expected values (from test_data.py file) title = 'World Cup' href = 'http://www.google.com/calendar/images/google-holiday.gif' type = 'image/gif' url = 'http://www.google.com/logos/worldcup06.gif' width = '276' height = '120' # Note: The tenth event entry contains web content web_content_event = self.calendar_event_feed.entry[9] # Verify the web content link exists and contains the expected data web_content_link = web_content_event.GetWebContentLink() self.assertValidWebContentLink(title, href, type, web_content_link) # Verify the web content element exists and contains the expected data web_content_element = web_content_link.web_content self.assertValidSimpleWebContent(url, width, height, web_content_element) def testFromXmlToWebContentGadget(self): """Verifies that we can read a web content link from an event entry.""" # Expected values (from test_data.py file) title = 'Date and Time Gadget' href = 'http://gdata.ops.demo.googlepages.com/birthdayicon.gif' url = 'http://google.com/ig/modules/datetime.xml' type = 'application/x-google-gadgets+xml' width = '300' height = '136' pref_name = 'color' pref_value = 'green' # Note: The eleventh event entry contains web content web_content_event = self.calendar_event_feed.entry[10] # Verify the web content link exists and contains the expected data web_content_link = web_content_event.GetWebContentLink() self.assertValidWebContentLink(title, href, type, web_content_link) # Verify the web content element exists and contains the expected data web_content_element = web_content_link.web_content self.assertValidWebContentGadget(url, width, height, pref_name, pref_value, web_content_element) def assertValidWebContentLink(self, expected_title=None, expected_href=None, expected_type=None, web_content_link=None): """Asserts that the web content link is the correct type and contains the expected values""" self.assert_(isinstance(web_content_link, gdata.calendar.WebContentLink), "Web content link element must be an " + "instance of gdata.calendar.WebContentLink: %s" % web_content_link) expected_rel = '%s/%s' % (gdata.calendar.GCAL_NAMESPACE, 'webContent') self.assertEquals(expected_rel, web_content_link.rel) self.assertEqual(expected_title, web_content_link.title) self.assertEqual(expected_href, web_content_link.href) self.assertEqual(expected_type, web_content_link.type) def assertValidSimpleWebContent(self, expected_url=None, expected_width=None, expected_height=None, web_content_element=None): """Asserts that the web content element is the correct type and contains the expected values""" self.assert_(isinstance(web_content_element, gdata.calendar.WebContent), "Calendar event entry element must be an " + "instance of gdata.calendar.WebContent: %s" % web_content_element) self.assertEquals(expected_width, web_content_element.width) self.assertEquals(expected_height, web_content_element.height) self.assertEquals(expected_url, web_content_element.url) def assertValidWebContentGadget(self, expected_url=None, expected_width=None, expected_height=None, expected_pref_name=None, expected_pref_value=None, web_content_element=None): """Asserts that the web content element is the correct type and contains the expected values""" self.assert_(isinstance(web_content_element, gdata.calendar.WebContent), "Calendar event entry element must be an " + "instance of gdata.calendar.WebContent: %s" % web_content_element) self.assertEquals(expected_width, web_content_element.width) self.assertEquals(expected_height, web_content_element.height) self.assertEquals(expected_url, web_content_element.url) self.assertEquals(expected_pref_name, web_content_element.gadget_pref[0].name) self.assertEquals(expected_pref_value, web_content_element.gadget_pref[0].value) def testSampleCode(self): # From http://code.google.com/apis/calendar/gadgets/event/ wc = gdata.calendar.WebContent() wc.url = 'http://www.thefreedictionary.com/_/WoD/wod-module.xml' wc.width = '300' wc.height = '136' wc.gadget_pref.append(gdata.calendar.WebContentGadgetPref(name='Days', value='1')) wc.gadget_pref.append(gdata.calendar.WebContentGadgetPref(name='Format', value='0')) wcl = gdata.calendar.WebContentLink() wcl.title = 'Word of the Day' wcl.href = 'http://www.thefreedictionary.com/favicon.ico' wcl.type = 'application/x-google-gadgets+xml' wcl.web_content = wc self.assertEqual(wcl.web_content.url, 'http://www.thefreedictionary.com/_/WoD/wod-module.xml') self.assertEqual(wcl.type, 'application/x-google-gadgets+xml') self.assertEqual(wcl.web_content.height, '136') class ExtendedPropertyTest(unittest.TestCase): def testExtendedPropertyToAndFromXml(self): ep = gdata.calendar.ExtendedProperty(name='test') ep.value = 'val' xml_string = ep.ToString() ep2 = gdata.ExtendedPropertyFromString(xml_string) self.assertEquals(ep.name, ep2.name) self.assertEquals(ep.value, ep2.value) if __name__ == '__main__': unittest.main() gdata-2.0.18/tests/gdata_tests/webmastertools_test.py0000640036466300116100000004613012156622363023071 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright (C) 2008 Yu-Jie Lin # # 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. __author__ = 'livibetter (Yu-Jie Lin)' import unittest try: from xml.etree import ElementTree except ImportError: from elementtree import ElementTree import gdata from gdata import test_data import gdata.webmastertools as webmastertools class IndexedTest(unittest.TestCase): def setUp(self): self.indexed = webmastertools.Indexed() def testToAndFromString(self): self.indexed.text = 'true' self.assert_(self.indexed.text == 'true') new_indexed = webmastertools.IndexedFromString(self.indexed.ToString()) self.assert_(self.indexed.text == new_indexed.text) class CrawledTest(unittest.TestCase): def setUp(self): self.crawled = webmastertools.Crawled() def testToAndFromString(self): self.crawled.text = 'true' self.assert_(self.crawled.text == 'true') new_crawled = webmastertools.CrawledFromString(self.crawled.ToString()) self.assert_(self.crawled.text == new_crawled.text) class GeoLocationTest(unittest.TestCase): def setUp(self): self.geolocation = webmastertools.GeoLocation() def testToAndFromString(self): self.geolocation.text = 'US' self.assert_(self.geolocation.text == 'US') new_geolocation = webmastertools.GeoLocationFromString( self.geolocation.ToString()) self.assert_(self.geolocation.text == new_geolocation.text) class PreferredDomainTest(unittest.TestCase): def setUp(self): self.preferred_domain = webmastertools.PreferredDomain() def testToAndFromString(self): self.preferred_domain.text = 'none' self.assert_(self.preferred_domain.text == 'none') new_preferred_domain = webmastertools.PreferredDomainFromString( self.preferred_domain.ToString()) self.assert_(self.preferred_domain.text == new_preferred_domain.text) class CrawlRateTest(unittest.TestCase): def setUp(self): self.crawl_rate = webmastertools.CrawlRate() def testToAndFromString(self): self.crawl_rate.text = 'normal' self.assert_(self.crawl_rate.text == 'normal') new_crawl_rate = webmastertools.CrawlRateFromString( self.crawl_rate.ToString()) self.assert_(self.crawl_rate.text == new_crawl_rate.text) class EnhancedImageSearchTest(unittest.TestCase): def setUp(self): self.enhanced_image_search = webmastertools.EnhancedImageSearch() def testToAndFromString(self): self.enhanced_image_search.text = 'true' self.assert_(self.enhanced_image_search.text == 'true') new_enhanced_image_search = webmastertools.EnhancedImageSearchFromString( self.enhanced_image_search.ToString()) self.assert_(self.enhanced_image_search.text == new_enhanced_image_search.text) class VerifiedTest(unittest.TestCase): def setUp(self): self.verified = webmastertools.Verified() def testToAndFromString(self): self.verified.text = 'true' self.assert_(self.verified.text == 'true') new_verified = webmastertools.VerifiedFromString(self.verified.ToString()) self.assert_(self.verified.text == new_verified.text) class VerificationMethodMetaTest(unittest.TestCase): def setUp(self): self.meta = webmastertools.VerificationMethodMeta() def testToAndFromString(self): self.meta.name = 'verify-vf1' self.meta.content = 'a2Ai' self.assert_(self.meta.name == 'verify-vf1') self.assert_(self.meta.content == 'a2Ai') new_meta = webmastertools.VerificationMethodMetaFromString( self.meta.ToString()) self.assert_(self.meta.name == new_meta.name) self.assert_(self.meta.content == new_meta.content) class VerificationMethodTest(unittest.TestCase): def setUp(self): pass def testMetaTagToAndFromString(self): self.method = webmastertools.VerificationMethod() self.method.type = 'metatag' self.method.in_use = 'false' self.assert_(self.method.type == 'metatag') self.assert_(self.method.in_use == 'false') self.method.meta = webmastertools.VerificationMethodMeta(name='verify-vf1', content='a2Ai') self.assert_(self.method.meta.name == 'verify-vf1') self.assert_(self.method.meta.content == 'a2Ai') new_method = webmastertools.VerificationMethodFromString( self.method.ToString()) self.assert_(self.method.type == new_method.type) self.assert_(self.method.in_use == new_method.in_use) self.assert_(self.method.meta.name == new_method.meta.name) self.assert_(self.method.meta.content == new_method.meta.content) method = webmastertools.VerificationMethod(type='xyz') self.assertEqual(method.type, 'xyz') method = webmastertools.VerificationMethod() self.assert_(method.type is None) def testHtmlPageToAndFromString(self): self.method = webmastertools.VerificationMethod() self.method.type = 'htmlpage' self.method.in_use = 'false' self.method.text = '456456-google.html' self.assert_(self.method.type == 'htmlpage') self.assert_(self.method.in_use == 'false') self.assert_(self.method.text == '456456-google.html') self.assert_(self.method.meta is None) new_method = webmastertools.VerificationMethodFromString( self.method.ToString()) self.assert_(self.method.type == new_method.type) self.assert_(self.method.in_use == new_method.in_use) self.assert_(self.method.text == new_method.text) self.assert_(self.method.meta is None) def testConvertActualData(self): feed = webmastertools.SitesFeedFromString(test_data.SITES_FEED) self.assert_(len(feed.entry[0].verification_method) == 2) check = 0 for method in feed.entry[0].verification_method: self.assert_(isinstance(method, webmastertools.VerificationMethod)) if method.type == 'metatag': self.assert_(method.in_use == 'false') self.assert_(method.text is None) self.assert_(method.meta.name == 'verify-v1') self.assert_(method.meta.content == 'a2Ai') check = check | 1 elif method.type == 'htmlpage': self.assert_(method.in_use == 'false') self.assert_(method.text == '456456-google.html') check = check | 2 else: self.fail('Wrong Verification Method: %s' % method.type) self.assert_(check == 2 ** 2 - 1, 'Should only have two Verification Methods, metatag and htmlpage') class MarkupLanguageTest(unittest.TestCase): def setUp(self): self.markup_language = webmastertools.MarkupLanguage() def testToAndFromString(self): self.markup_language.text = 'HTML' self.assert_(self.markup_language.text == 'HTML') new_markup_language = webmastertools.MarkupLanguageFromString( self.markup_language.ToString()) self.assert_(self.markup_language.text == new_markup_language.text) class SitemapMobileTest(unittest.TestCase): def setUp(self): self.sitemap_mobile = webmastertools.SitemapMobile() def testToAndFromString(self): self.sitemap_mobile.markup_language.append(webmastertools.MarkupLanguage( text = 'HTML')) self.assert_(self.sitemap_mobile.text is None) self.assert_(self.sitemap_mobile.markup_language[0].text == 'HTML') new_sitemap_mobile = webmastertools.SitemapMobileFromString( self.sitemap_mobile.ToString()) self.assert_(new_sitemap_mobile.text is None) self.assert_(self.sitemap_mobile.markup_language[0].text == new_sitemap_mobile.markup_language[0].text) def testConvertActualData(self): feed = webmastertools.SitemapsFeedFromString(test_data.SITEMAPS_FEED) self.assert_(feed.sitemap_mobile.text.strip() == '') self.assert_(len(feed.sitemap_mobile.markup_language) == 2) check = 0 for markup_language in feed.sitemap_mobile.markup_language: self.assert_(isinstance(markup_language, webmastertools.MarkupLanguage)) if markup_language.text == "HTML": check = check | 1 elif markup_language.text == "WAP": check = check | 2 else: self.fail('Unexpected markup language: %s' % markup_language.text) self.assert_(check == 2 ** 2 - 1, "Something is wrong with markup language") class SitemapMobileMarkupLanguageTest(unittest.TestCase): def setUp(self): self.sitemap_mobile_markup_language =\ webmastertools.SitemapMobileMarkupLanguage() def testToAndFromString(self): self.sitemap_mobile_markup_language.text = 'HTML' self.assert_(self.sitemap_mobile_markup_language.text == 'HTML') new_sitemap_mobile_markup_language =\ webmastertools.SitemapMobileMarkupLanguageFromString( self.sitemap_mobile_markup_language.ToString()) self.assert_(self.sitemap_mobile_markup_language.text ==\ new_sitemap_mobile_markup_language.text) class PublicationLabelTest(unittest.TestCase): def setUp(self): self.publication_label = webmastertools.PublicationLabel() def testToAndFromString(self): self.publication_label.text = 'Value1' self.assert_(self.publication_label.text == 'Value1') new_publication_label = webmastertools.PublicationLabelFromString( self.publication_label.ToString()) self.assert_(self.publication_label.text == new_publication_label.text) class SitemapNewsTest(unittest.TestCase): def setUp(self): self.sitemap_news = webmastertools.SitemapNews() def testToAndFromString(self): self.sitemap_news.publication_label.append(webmastertools.PublicationLabel( text = 'Value1')) self.assert_(self.sitemap_news.text is None) self.assert_(self.sitemap_news.publication_label[0].text == 'Value1') new_sitemap_news = webmastertools.SitemapNewsFromString( self.sitemap_news.ToString()) self.assert_(new_sitemap_news.text is None) self.assert_(self.sitemap_news.publication_label[0].text == new_sitemap_news.publication_label[0].text) def testConvertActualData(self): feed = webmastertools.SitemapsFeedFromString(test_data.SITEMAPS_FEED) self.assert_(len(feed.sitemap_news.publication_label) == 3) check = 0 for publication_label in feed.sitemap_news.publication_label: if publication_label.text == "Value1": check = check | 1 elif publication_label.text == "Value2": check = check | 2 elif publication_label.text == "Value3": check = check | 4 else: self.fail('Unexpected publication label: %s' % markup_language.text) self.assert_(check == 2 ** 3 - 1, 'Something is wrong with publication label') class SitemapNewsPublicationLabelTest(unittest.TestCase): def setUp(self): self.sitemap_news_publication_label =\ webmastertools.SitemapNewsPublicationLabel() def testToAndFromString(self): self.sitemap_news_publication_label.text = 'LabelValue' self.assert_(self.sitemap_news_publication_label.text == 'LabelValue') new_sitemap_news_publication_label =\ webmastertools.SitemapNewsPublicationLabelFromString( self.sitemap_news_publication_label.ToString()) self.assert_(self.sitemap_news_publication_label.text ==\ new_sitemap_news_publication_label.text) class SitemapLastDownloadedTest(unittest.TestCase): def setUp(self): self.sitemap_last_downloaded = webmastertools.SitemapLastDownloaded() def testToAndFromString(self): self.sitemap_last_downloaded.text = '2006-11-18T19:27:32.543Z' self.assert_(self.sitemap_last_downloaded.text ==\ '2006-11-18T19:27:32.543Z') new_sitemap_last_downloaded =\ webmastertools.SitemapLastDownloadedFromString( self.sitemap_last_downloaded.ToString()) self.assert_(self.sitemap_last_downloaded.text ==\ new_sitemap_last_downloaded.text) class SitemapTypeTest(unittest.TestCase): def setUp(self): self.sitemap_type = webmastertools.SitemapType() def testToAndFromString(self): self.sitemap_type.text = 'WEB' self.assert_(self.sitemap_type.text == 'WEB') new_sitemap_type = webmastertools.SitemapTypeFromString( self.sitemap_type.ToString()) self.assert_(self.sitemap_type.text == new_sitemap_type.text) class SitemapStatusTest(unittest.TestCase): def setUp(self): self.sitemap_status = webmastertools.SitemapStatus() def testToAndFromString(self): self.sitemap_status.text = 'Pending' self.assert_(self.sitemap_status.text == 'Pending') new_sitemap_status = webmastertools.SitemapStatusFromString( self.sitemap_status.ToString()) self.assert_(self.sitemap_status.text == new_sitemap_status.text) class SitemapUrlCountTest(unittest.TestCase): def setUp(self): self.sitemap_url_count = webmastertools.SitemapUrlCount() def testToAndFromString(self): self.sitemap_url_count.text = '0' self.assert_(self.sitemap_url_count.text == '0') new_sitemap_url_count = webmastertools.SitemapUrlCountFromString( self.sitemap_url_count.ToString()) self.assert_(self.sitemap_url_count.text == new_sitemap_url_count.text) class SitesEntryTest(unittest.TestCase): def setUp(self): pass def testToAndFromString(self): entry = webmastertools.SitesEntry( indexed=webmastertools.Indexed(text='true'), crawled=webmastertools.Crawled(text='2008-09-14T08:59:28.000'), geolocation=webmastertools.GeoLocation(text='US'), preferred_domain=webmastertools.PreferredDomain(text='none'), crawl_rate=webmastertools.CrawlRate(text='normal'), enhanced_image_search=webmastertools.EnhancedImageSearch(text='true'), verified=webmastertools.Verified(text='false'), ) self.assert_(entry.indexed.text == 'true') self.assert_(entry.crawled.text == '2008-09-14T08:59:28.000') self.assert_(entry.geolocation.text == 'US') self.assert_(entry.preferred_domain.text == 'none') self.assert_(entry.crawl_rate.text == 'normal') self.assert_(entry.enhanced_image_search.text == 'true') self.assert_(entry.verified.text == 'false') new_entry = webmastertools.SitesEntryFromString(entry.ToString()) self.assert_(new_entry.indexed.text == 'true') self.assert_(new_entry.crawled.text == '2008-09-14T08:59:28.000') self.assert_(new_entry.geolocation.text == 'US') self.assert_(new_entry.preferred_domain.text == 'none') self.assert_(new_entry.crawl_rate.text == 'normal') self.assert_(new_entry.enhanced_image_search.text == 'true') self.assert_(new_entry.verified.text == 'false') def testConvertActualData(self): feed = webmastertools.SitesFeedFromString(test_data.SITES_FEED) self.assert_(len(feed.entry) == 1) entry = feed.entry[0] self.assert_(isinstance(entry, webmastertools.SitesEntry)) self.assert_(entry.indexed.text == 'true') self.assert_(entry.crawled.text == '2008-09-14T08:59:28.000') self.assert_(entry.geolocation.text == 'US') self.assert_(entry.preferred_domain.text == 'none') self.assert_(entry.crawl_rate.text == 'normal') self.assert_(entry.enhanced_image_search.text == 'true') self.assert_(entry.verified.text == 'false') class SitesFeedTest(unittest.TestCase): def setUp(self): self.feed = gdata.webmastertools.SitesFeedFromString(test_data.SITES_FEED) def testToAndFromString(self): self.assert_(len(self.feed.entry) == 1) for entry in self.feed.entry: self.assert_(isinstance(entry, webmastertools.SitesEntry)) new_feed = webmastertools.SitesFeedFromString(self.feed.ToString()) self.assert_(len(new_feed.entry) == 1) for entry in new_feed.entry: self.assert_(isinstance(entry, webmastertools.SitesEntry)) class SitemapsEntryTest(unittest.TestCase): def testRegularToAndFromString(self): entry = webmastertools.SitemapsEntry( sitemap_type=webmastertools.SitemapType(text='WEB'), sitemap_status=webmastertools.SitemapStatus(text='Pending'), sitemap_last_downloaded=webmastertools.SitemapLastDownloaded( text='2006-11-18T19:27:32.543Z'), sitemap_url_count=webmastertools.SitemapUrlCount(text='102'), ) self.assert_(entry.sitemap_type.text == 'WEB') self.assert_(entry.sitemap_status.text == 'Pending') self.assert_(entry.sitemap_last_downloaded.text ==\ '2006-11-18T19:27:32.543Z') self.assert_(entry.sitemap_url_count.text == '102') new_entry = webmastertools.SitemapsEntryFromString(entry.ToString()) self.assert_(new_entry.sitemap_type.text == 'WEB') self.assert_(new_entry.sitemap_status.text == 'Pending') self.assert_(new_entry.sitemap_last_downloaded.text ==\ '2006-11-18T19:27:32.543Z') self.assert_(new_entry.sitemap_url_count.text == '102') def testConvertActualData(self): feed = gdata.webmastertools.SitemapsFeedFromString(test_data.SITEMAPS_FEED) self.assert_(len(feed.entry) == 3) for entry in feed.entry: self.assert_(entry, webmastertools.SitemapsEntry) self.assert_(entry.sitemap_status, webmastertools.SitemapStatus) self.assert_(entry.sitemap_last_downloaded, webmastertools.SitemapLastDownloaded) self.assert_(entry.sitemap_url_count, webmastertools.SitemapUrlCount) self.assert_(entry.sitemap_status.text == 'StatusValue') self.assert_(entry.sitemap_last_downloaded.text ==\ '2006-11-18T19:27:32.543Z') self.assert_(entry.sitemap_url_count.text == '102') if entry.id.text == 'http://www.example.com/sitemap-index.xml': self.assert_(entry.sitemap_type, webmastertools.SitemapType) self.assert_(entry.sitemap_type.text == 'WEB') self.assert_(entry.sitemap_mobile_markup_language is None) self.assert_(entry.sitemap_news_publication_label is None) elif entry.id.text == 'http://www.example.com/mobile/sitemap-index.xml': self.assert_(entry.sitemap_mobile_markup_language, webmastertools.SitemapMobileMarkupLanguage) self.assert_(entry.sitemap_mobile_markup_language.text == 'HTML') self.assert_(entry.sitemap_type is None) self.assert_(entry.sitemap_news_publication_label is None) elif entry.id.text == 'http://www.example.com/news/sitemap-index.xml': self.assert_(entry.sitemap_news_publication_label, webmastertools.SitemapNewsPublicationLabel) self.assert_(entry.sitemap_news_publication_label.text == 'LabelValue') self.assert_(entry.sitemap_type is None) self.assert_(entry.sitemap_mobile_markup_language is None) class SitemapsFeedTest(unittest.TestCase): def setUp(self): self.feed = gdata.webmastertools.SitemapsFeedFromString( test_data.SITEMAPS_FEED) def testToAndFromString(self): self.assert_(len(self.feed.entry) == 3) for entry in self.feed.entry: self.assert_(isinstance(entry, webmastertools.SitemapsEntry)) new_feed = webmastertools.SitemapsFeedFromString(self.feed.ToString()) self.assert_(len(new_feed.entry) == 3) for entry in new_feed.entry: self.assert_(isinstance(entry, webmastertools.SitemapsEntry)) if __name__ == '__main__': unittest.main() gdata-2.0.18/tests/gdata_tests/gauth_test.py0000750036466300116100000010576612156622363021144 0ustar afshareng00000000000000#!/usr/bin/env python # # Copyright (C) 2009 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. # This module is used for version 2 of the Google Data APIs. __author__ = 'j.s@google.com (Jeff Scudder)' import sys import types import unittest import gdata.gauth import atom.http_core import gdata.test_config as conf PRIVATE_TEST_KEY = """ -----BEGIN PRIVATE KEY----- MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBALRiMLAh9iimur8V A7qVvdqxevEuUkW4K+2KdMXmnQbG9Aa7k7eBjK1S+0LYmVjPKlJGNXHDGuy5Fw/d 7rjVJ0BLB+ubPK8iA/Tw3hLQgXMRRGRXXCn8ikfuQfjUS1uZSatdLB81mydBETlJ hI6GH4twrbDJCR2Bwy/XWXgqgGRzAgMBAAECgYBYWVtleUzavkbrPjy0T5FMou8H X9u2AC2ry8vD/l7cqedtwMPp9k7TubgNFo+NGvKsl2ynyprOZR1xjQ7WgrgVB+mm uScOM/5HVceFuGRDhYTCObE+y1kxRloNYXnx3ei1zbeYLPCHdhxRYW7T0qcynNmw rn05/KO2RLjgQNalsQJBANeA3Q4Nugqy4QBUCEC09SqylT2K9FrrItqL2QKc9v0Z zO2uwllCbg0dwpVuYPYXYvikNHHg+aCWF+VXsb9rpPsCQQDWR9TT4ORdzoj+Nccn qkMsDmzt0EfNaAOwHOmVJ2RVBspPcxt5iN4HI7HNeG6U5YsFBb+/GZbgfBT3kpNG WPTpAkBI+gFhjfJvRw38n3g/+UeAkwMI2TJQS4n8+hid0uus3/zOjDySH3XHCUno cn1xOJAyZODBo47E+67R4jV1/gzbAkEAklJaspRPXP877NssM5nAZMU0/O/NGCZ+ 3jPgDUno6WbJn5cqm8MqWhW1xGkImgRk+fkDBquiq4gPiT898jusgQJAd5Zrr6Q8 AO/0isr/3aa6O6NLQxISLKcPDk2NOccAfS/xOtfOz4sJYM3+Bs4Io9+dZGSDCA54 Lw03eHTNQghS0A== -----END PRIVATE KEY-----""" class AuthSubTest(unittest.TestCase): def test_generate_request_url(self): url = gdata.gauth.generate_auth_sub_url('http://example.com', ['http://example.net/scope1']) self.assert_(isinstance(url, atom.http_core.Uri)) self.assertEqual(url.query['secure'], '0') self.assertEqual(url.query['session'], '1') self.assertEqual(url.query['scope'], 'http://example.net/scope1') self.assertEqual(atom.http_core.Uri.parse_uri( url.query['next']).query['auth_sub_scopes'], 'http://example.net/scope1') self.assertEqual(atom.http_core.Uri.parse_uri(url.query['next']).path, '/') self.assertEqual(atom.http_core.Uri.parse_uri(url.query['next']).host, 'example.com') def test_from_url(self): token_str = gdata.gauth.auth_sub_string_from_url( 'http://example.com/?token=123abc')[0] self.assertEqual(token_str, '123abc') def test_from_http_body(self): token_str = gdata.gauth.auth_sub_string_from_body('Something\n' 'Token=DQAA...7DCTN\n' 'Expiration=20061004T123456Z\n') self.assertEqual(token_str, 'DQAA...7DCTN') def test_modify_request(self): token = gdata.gauth.AuthSubToken('tval') request = atom.http_core.HttpRequest() token.modify_request(request) self.assertEqual(request.headers['Authorization'], 'AuthSub token=tval') def test_create_and_upgrade_tokens(self): token = gdata.gauth.AuthSubToken.from_url( 'http://example.com/?token=123abc') self.assert_(isinstance(token, gdata.gauth.AuthSubToken)) self.assertEqual(token.token_string, '123abc') self.assertEqual(token.scopes, []) token._upgrade_token('Token=456def') self.assertEqual(token.token_string, '456def') self.assertEqual(token.scopes, []) class SecureAuthSubTest(unittest.TestCase): def test_build_data(self): request = atom.http_core.HttpRequest(method='PUT') request.uri = atom.http_core.Uri.parse_uri('http://example.com/foo?a=1') data = gdata.gauth.build_auth_sub_data(request, 1234567890, 'mynonce') self.assertEqual(data, 'PUT http://example.com/foo?a=1 1234567890 mynonce') def test_generate_signature(self): request = atom.http_core.HttpRequest( method='GET', uri=atom.http_core.Uri(host='example.com', path='/foo', query={'a': '1'})) data = gdata.gauth.build_auth_sub_data(request, 1134567890, 'p234908') self.assertEqual(data, 'GET http://example.com/foo?a=1 1134567890 p234908') self.assertEqual( gdata.gauth.generate_signature(data, PRIVATE_TEST_KEY), 'GeBfeIDnT41dvLquPgDB4U5D4hfxqaHk/5LX1kccNBnL4BjsHWU1djbEp7xp3BL9ab' 'QtLrK7oa/aHEHtGRUZGg87O+ND8iDPR76WFXAruuN8O8GCMqCDdPduNPY++LYO4MdJ' 'BZNY974Nn0m6Hc0/T4M1ElqvPhl61fkXMm+ElSM=') class TokensToAndFromBlobsTest(unittest.TestCase): def test_client_login_conversion(self): token = gdata.gauth.ClientLoginToken('test|key') copy = gdata.gauth.token_from_blob(gdata.gauth.token_to_blob(token)) self.assertEqual(token.token_string, copy.token_string) self.assert_(isinstance(copy, gdata.gauth.ClientLoginToken)) def test_authsub_conversion(self): token = gdata.gauth.AuthSubToken('test|key') copy = gdata.gauth.token_from_blob(gdata.gauth.token_to_blob(token)) self.assertEqual(token.token_string, copy.token_string) self.assert_(isinstance(copy, gdata.gauth.AuthSubToken)) scopes = ['http://example.com', 'http://other||test', 'thir|d'] token = gdata.gauth.AuthSubToken('key-=', scopes) copy = gdata.gauth.token_from_blob(gdata.gauth.token_to_blob(token)) self.assertEqual(token.token_string, copy.token_string) self.assert_(isinstance(copy, gdata.gauth.AuthSubToken)) self.assertEqual(token.scopes, scopes) def test_join_and_split(self): token_string = gdata.gauth._join_token_parts('1x', 'test|string', '%x%', '', None) self.assertEqual(token_string, '1x|test%7Cstring|%25x%25||') token_type, a, b, c, d = gdata.gauth._split_token_parts(token_string) self.assertEqual(token_type, '1x') self.assertEqual(a, 'test|string') self.assertEqual(b, '%x%') self.assert_(c is None) self.assert_(d is None) def test_secure_authsub_conversion(self): token = gdata.gauth.SecureAuthSubToken( '%^%', 'myRsaKey', ['http://example.com', 'http://example.org']) copy = gdata.gauth.token_from_blob(gdata.gauth.token_to_blob(token)) self.assertEqual(copy.token_string, '%^%') self.assertEqual(copy.rsa_private_key, 'myRsaKey') self.assertEqual(copy.scopes, ['http://example.com', 'http://example.org']) token = gdata.gauth.SecureAuthSubToken(rsa_private_key='f', token_string='b') blob = gdata.gauth.token_to_blob(token) self.assertEqual(blob, '1s|b|f') copy = gdata.gauth.token_from_blob(blob) self.assertEqual(copy.token_string, 'b') self.assertEqual(copy.rsa_private_key, 'f') self.assertEqual(copy.scopes, []) token = gdata.gauth.SecureAuthSubToken(None, '') blob = gdata.gauth.token_to_blob(token) self.assertEqual(blob, '1s||') copy = gdata.gauth.token_from_blob(blob) self.assertEqual(copy.token_string, None) self.assertEqual(copy.rsa_private_key, None) self.assertEqual(copy.scopes, []) token = gdata.gauth.SecureAuthSubToken('', None) blob = gdata.gauth.token_to_blob(token) self.assertEqual(blob, '1s||') copy = gdata.gauth.token_from_blob(blob) self.assertEqual(copy.token_string, None) self.assertEqual(copy.rsa_private_key, None) self.assertEqual(copy.scopes, []) token = gdata.gauth.SecureAuthSubToken( None, None, ['http://example.net', 'http://google.com']) blob = gdata.gauth.token_to_blob(token) self.assertEqual( blob, '1s|||http%3A%2F%2Fexample.net|http%3A%2F%2Fgoogle.com') copy = gdata.gauth.token_from_blob(blob) self.assert_(copy.token_string is None) self.assert_(copy.rsa_private_key is None) self.assertEqual(copy.scopes, ['http://example.net', 'http://google.com']) def test_oauth_rsa_conversion(self): token = gdata.gauth.OAuthRsaToken( 'consumerKey', 'myRsa', 't', 'secret', gdata.gauth.AUTHORIZED_REQUEST_TOKEN, 'http://example.com/next', 'verifier') blob = gdata.gauth.token_to_blob(token) self.assertEqual( blob, '1r|consumerKey|myRsa|t|secret|2|http%3A%2F%2Fexample.com' '%2Fnext|verifier') copy = gdata.gauth.token_from_blob(blob) self.assert_(isinstance(copy, gdata.gauth.OAuthRsaToken)) self.assertEqual(copy.consumer_key, token.consumer_key) self.assertEqual(copy.rsa_private_key, token.rsa_private_key) self.assertEqual(copy.token, token.token) self.assertEqual(copy.token_secret, token.token_secret) self.assertEqual(copy.auth_state, token.auth_state) self.assertEqual(copy.next, token.next) self.assertEqual(copy.verifier, token.verifier) token = gdata.gauth.OAuthRsaToken( '', 'myRsa', 't', 'secret', 0) blob = gdata.gauth.token_to_blob(token) self.assertEqual(blob, '1r||myRsa|t|secret|0||') copy = gdata.gauth.token_from_blob(blob) self.assert_(isinstance(copy, gdata.gauth.OAuthRsaToken)) self.assert_(copy.consumer_key != token.consumer_key) self.assert_(copy.consumer_key is None) self.assertEqual(copy.rsa_private_key, token.rsa_private_key) self.assertEqual(copy.token, token.token) self.assertEqual(copy.token_secret, token.token_secret) self.assertEqual(copy.auth_state, token.auth_state) self.assertEqual(copy.next, token.next) self.assert_(copy.next is None) self.assertEqual(copy.verifier, token.verifier) self.assert_(copy.verifier is None) token = gdata.gauth.OAuthRsaToken( rsa_private_key='myRsa', token='t', token_secret='secret', auth_state=gdata.gauth.ACCESS_TOKEN, verifier='v', consumer_key=None) blob = gdata.gauth.token_to_blob(token) self.assertEqual(blob, '1r||myRsa|t|secret|3||v') copy = gdata.gauth.token_from_blob(blob) self.assertEqual(copy.consumer_key, token.consumer_key) self.assert_(copy.consumer_key is None) self.assertEqual(copy.rsa_private_key, token.rsa_private_key) self.assertEqual(copy.token, token.token) self.assertEqual(copy.token_secret, token.token_secret) self.assertEqual(copy.auth_state, token.auth_state) self.assertEqual(copy.next, token.next) self.assert_(copy.next is None) self.assertEqual(copy.verifier, token.verifier) def test_oauth_hmac_conversion(self): token = gdata.gauth.OAuthHmacToken( 'consumerKey', 'consumerSecret', 't', 'secret', gdata.gauth.REQUEST_TOKEN, 'http://example.com/next', 'verifier') blob = gdata.gauth.token_to_blob(token) self.assertEqual( blob, '1h|consumerKey|consumerSecret|t|secret|1|http%3A%2F%2F' 'example.com%2Fnext|verifier') copy = gdata.gauth.token_from_blob(blob) self.assert_(isinstance(copy, gdata.gauth.OAuthHmacToken)) self.assertEqual(copy.consumer_key, token.consumer_key) self.assertEqual(copy.consumer_secret, token.consumer_secret) self.assertEqual(copy.token, token.token) self.assertEqual(copy.token_secret, token.token_secret) self.assertEqual(copy.auth_state, token.auth_state) self.assertEqual(copy.next, token.next) self.assertEqual(copy.verifier, token.verifier) token = gdata.gauth.OAuthHmacToken( consumer_secret='c,s', token='t', token_secret='secret', auth_state=7, verifier='v', consumer_key=None) blob = gdata.gauth.token_to_blob(token) self.assertEqual(blob, '1h||c%2Cs|t|secret|7||v') copy = gdata.gauth.token_from_blob(blob) self.assert_(isinstance(copy, gdata.gauth.OAuthHmacToken)) self.assertEqual(copy.consumer_key, token.consumer_key) self.assert_(copy.consumer_key is None) self.assertEqual(copy.consumer_secret, token.consumer_secret) self.assertEqual(copy.token, token.token) self.assertEqual(copy.token_secret, token.token_secret) self.assertEqual(copy.auth_state, token.auth_state) self.assertEqual(copy.next, token.next) self.assert_(copy.next is None) self.assertEqual(copy.verifier, token.verifier) def test_oauth2_conversion(self): token = gdata.gauth.OAuth2Token( 'clientId', 'clientSecret', 'https://www.google.com/calendar/feeds', 'userAgent', 'https://accounts.google.com/o/oauth2/auth', 'https://accounts.google.com/o/oauth2/token', 'accessToken', 'refreshToken') blob = gdata.gauth.token_to_blob(token) self.assertEqual( blob, '2o|clientId|clientSecret|https%3A%2F%2Fwww.google.com%2F' 'calendar%2Ffeeds|userAgent|https%3A%2F%2Faccounts.google.com%2F' 'o%2Foauth2%2Fauth|https%3A%2F%2Faccounts.google.com%2Fo%2Foauth2' '%2Ftoken|accessToken|refreshToken') copy = gdata.gauth.token_from_blob(blob) self.assert_(isinstance(copy, gdata.gauth.OAuth2Token)) self.assertEqual(copy.client_id, token.client_id) self.assertEqual(copy.client_secret, token.client_secret) self.assertEqual(copy.scope, token.scope) self.assertEqual(copy.user_agent, token.user_agent) self.assertEqual(copy.auth_uri, token.auth_uri) self.assertEqual(copy.token_uri, token.token_uri) self.assertEqual(copy.access_token, token.access_token) self.assertEqual(copy.refresh_token, token.refresh_token) token = gdata.gauth.OAuth2Token( 'clientId', 'clientSecret', 'https://www.google.com/calendar/feeds', '', 'https://accounts.google.com/o/oauth2/auth', 'https://accounts.google.com/o/oauth2/token', '', '') blob = gdata.gauth.token_to_blob(token) self.assertEqual( blob, '2o|clientId|clientSecret|https%3A%2F%2Fwww.google.com%2F' 'calendar%2Ffeeds||https%3A%2F%2Faccounts.google.com%2F' 'o%2Foauth2%2Fauth|https%3A%2F%2Faccounts.google.com%2Fo%2Foauth2' '%2Ftoken||') copy = gdata.gauth.token_from_blob(blob) self.assert_(isinstance(copy, gdata.gauth.OAuth2Token)) self.assertEqual(copy.client_id, token.client_id) self.assertEqual(copy.client_secret, token.client_secret) self.assertEqual(copy.scope, token.scope) self.assert_(copy.user_agent is None) self.assertEqual(copy.auth_uri, token.auth_uri) self.assertEqual(copy.token_uri, token.token_uri) self.assert_(copy.access_token is None) self.assert_(copy.refresh_token is None) token = gdata.gauth.OAuth2Token( 'clientId', 'clientSecret', 'https://www.google.com/calendar/feeds', None, 'https://accounts.google.com/o/oauth2/auth', 'https://accounts.google.com/o/oauth2/token') blob = gdata.gauth.token_to_blob(token) self.assertEqual( blob, '2o|clientId|clientSecret|https%3A%2F%2Fwww.google.com%2F' 'calendar%2Ffeeds||https%3A%2F%2Faccounts.google.com%2F' 'o%2Foauth2%2Fauth|https%3A%2F%2Faccounts.google.com%2Fo%2Foauth2' '%2Ftoken||') copy = gdata.gauth.token_from_blob(blob) self.assert_(isinstance(copy, gdata.gauth.OAuth2Token)) self.assertEqual(copy.client_id, token.client_id) self.assertEqual(copy.client_secret, token.client_secret) self.assertEqual(copy.scope, token.scope) self.assert_(copy.user_agent is None) self.assertEqual(copy.auth_uri, token.auth_uri) self.assertEqual(copy.token_uri, token.token_uri) self.assert_(copy.access_token is None) self.assert_(copy.refresh_token is None) def test_illegal_token_types(self): class MyToken(object): pass token = MyToken() self.assertRaises(gdata.gauth.UnsupportedTokenType, gdata.gauth.token_to_blob, token) blob = '~~z' self.assertRaises(gdata.gauth.UnsupportedTokenType, gdata.gauth.token_from_blob, blob) class OAuthHmacTokenTests(unittest.TestCase): def test_build_base_string(self): request = atom.http_core.HttpRequest('http://example.com/', 'GET') base_string = gdata.gauth.build_oauth_base_string( request, 'example.org', '12345', gdata.gauth.HMAC_SHA1, 1246301653, '1.0') self.assertEqual( base_string, 'GET&http%3A%2F%2Fexample.com%2F&oauth_callback%3Doob%2' '6oauth_consumer_key%3Dexample.org%26oauth_nonce%3D12345%26oauth_sig' 'nature_method%3DHMAC-SHA1%26oauth_timestamp%3D1246301653%26oauth_ve' 'rsion%3D1.0') # Test using example from documentation. request = atom.http_core.HttpRequest( 'http://www.google.com/calendar/feeds/default/allcalendars/full' '?orderby=starttime', 'GET') base_string = gdata.gauth.build_oauth_base_string( request, 'example.com', '4572616e48616d6d65724c61686176', gdata.gauth.RSA_SHA1, 137131200, '1.0', token='1%2Fab3cd9j4ks73hf7g', next='http://googlecodesamples.com/oauth_playground/index.php') self.assertEqual( base_string, 'GET&http%3A%2F%2Fwww.google.com%2Fcalendar%2Ffeeds%2Fd' 'efault%2Fallcalendars%2Ffull&oauth_callback%3Dhttp%253A%252F%252Fgo' 'oglecodesamples.com%252Foauth_playground%252Findex.php%26oauth_cons' 'umer_key%3Dexample.com%26oauth_nonce%3D4572616e48616d6d65724c616861' '76%26oauth_signature_method%3DRSA-SHA1%26oauth_timestamp%3D13713120' '0%26oauth_token%3D1%25252Fab3cd9j4ks73hf7g%26oauth_version%3D1.0%26' 'orderby%3Dstarttime') # Test various defaults. request = atom.http_core.HttpRequest('http://eXample.COM', 'get') base_string = gdata.gauth.build_oauth_base_string( request, 'example.org', '12345', gdata.gauth.HMAC_SHA1, 1246301653, '1.0') self.assertEqual( base_string, 'GET&http%3A%2F%2Fexample.com%2F&oauth_callback%3Doob%2' '6oauth_consumer_key%3Dexample.org%26oauth_nonce%3D12345%26oauth_sig' 'nature_method%3DHMAC-SHA1%26oauth_timestamp%3D1246301653%26oauth_ve' 'rsion%3D1.0') request = atom.http_core.HttpRequest('https://eXample.COM:443', 'get') base_string = gdata.gauth.build_oauth_base_string( request, 'example.org', '12345', gdata.gauth.HMAC_SHA1, 1246301653, '1.0', 'http://googlecodesamples.com/oauth_playground/index.php') self.assertEqual( base_string, 'GET&https%3A%2F%2Fexample.com%2F&oauth_callback%3Dhttp' '%253A%252F%252Fgooglecodesamples.com%252Foauth_playground%252Findex' '.php%26oauth_consumer_key%3Dexample.org%26oauth_nonce%3D12345%26oau' 'th_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1246301653%26oa' 'uth_version%3D1.0') request = atom.http_core.HttpRequest('http://eXample.COM:443', 'get') base_string = gdata.gauth.build_oauth_base_string( request, 'example.org', '12345', gdata.gauth.HMAC_SHA1, 1246301653, '1.0') self.assertEqual( base_string, 'GET&http%3A%2F%2Fexample.com%3A443%2F&oauth_callback%3' 'Doob%26oauth_consumer_key%3De' 'xample.org%26oauth_nonce%3D12345%26oauth_signature_method%3DHMAC-SH' 'A1%26oauth_timestamp%3D1246301653%26oauth_version%3D1.0') request = atom.http_core.HttpRequest( atom.http_core.Uri(host='eXample.COM'), 'GET') base_string = gdata.gauth.build_oauth_base_string( request, 'example.org', '12345', gdata.gauth.HMAC_SHA1, 1246301653, '1.0', next='oob') self.assertEqual( base_string, 'GET&http%3A%2F%2Fexample.com%2F&oauth_callback%3Doob%2' '6oauth_consumer_key%3Dexample.org%26oauth_nonce%3D12345%26oauth_sig' 'nature_method%3DHMAC-SHA1%26oauth_timestamp%3D1246301653%26oauth_ve' 'rsion%3D1.0') request = atom.http_core.HttpRequest( 'https://www.google.com/accounts/OAuthGetRequestToken', 'GET') request.uri.query['scope'] = ('https://docs.google.com/feeds/' ' http://docs.google.com/feeds/') base_string = gdata.gauth.build_oauth_base_string( request, 'anonymous', '48522759', gdata.gauth.HMAC_SHA1, 1246489532, '1.0', 'http://googlecodesamples.com/oauth_playground/index.php') self.assertEqual( base_string, 'GET&https%3A%2F%2Fwww.google.com%2Faccounts%2FOAuthGet' 'RequestToken&oauth_callback%3Dhttp%253A%252F%252Fgooglecodesamples.' 'com%252Foauth_playground%252Findex.php%26oauth_consumer_key%3Danony' 'mous%26oauth_nonce%3D4852275' '9%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D12464895' '32%26oauth_version%3D1.0%26scope%3Dhttps%253A%252F%252Fdocs.google.' 'com%252Ffeeds%252F%2520http%253A%252F%252Fdocs.google.com%252Ffeeds' '%252F') def test_generate_hmac_signature(self): # Use the example from the OAuth playground: # http://googlecodesamples.com/oauth_playground/ request = atom.http_core.HttpRequest( 'https://www.google.com/accounts/OAuthGetRequestToken?' 'scope=http%3A%2F%2Fwww.blogger.com%2Ffeeds%2F', 'GET') signature = gdata.gauth.generate_hmac_signature( request, 'anonymous', 'anonymous', '1246491360', 'c0155b3f28697c029e7a62efff44bd46', '1.0', next='http://googlecodesamples.com/oauth_playground/index.php') self.assertEqual(signature, '5a2GPdtAY3LWYv8IdiT3wp1Coeg=') # Try the same request but with a non escaped Uri object. request = atom.http_core.HttpRequest( 'https://www.google.com/accounts/OAuthGetRequestToken', 'GET') request.uri.query['scope'] = 'http://www.blogger.com/feeds/' signature = gdata.gauth.generate_hmac_signature( request, 'anonymous', 'anonymous', '1246491360', 'c0155b3f28697c029e7a62efff44bd46', '1.0', 'http://googlecodesamples.com/oauth_playground/index.php') self.assertEqual(signature, '5a2GPdtAY3LWYv8IdiT3wp1Coeg=') # A different request also checked against the OAuth playground. request = atom.http_core.HttpRequest( 'https://www.google.com/accounts/OAuthGetRequestToken', 'GET') request.uri.query['scope'] = ('https://www.google.com/analytics/feeds/ ' 'http://www.google.com/base/feeds/ ' 'http://www.google.com/calendar/feeds/') signature = gdata.gauth.generate_hmac_signature( request, 'anonymous', 'anonymous', 1246491797, '33209c4d7a09be4eb1d6ff18e00f8548', '1.0', next='http://googlecodesamples.com/oauth_playground/index.php') self.assertEqual(signature, 'kFAgTTFDIWz4/xAabIlrcZZMTq8=') class OAuthRsaTokenTests(unittest.TestCase): def test_generate_rsa_signature(self): request = atom.http_core.HttpRequest( 'https://www.google.com/accounts/OAuthGetRequestToken?' 'scope=http%3A%2F%2Fwww.blogger.com%2Ffeeds%2F', 'GET') signature = gdata.gauth.generate_rsa_signature( request, 'anonymous', PRIVATE_TEST_KEY, '1246491360', 'c0155b3f28697c029e7a62efff44bd46', '1.0', next='http://googlecodesamples.com/oauth_playground/index.php') self.assertEqual( signature, 'bfMantdttKaTrwoxU87JiXmMeXhAiXPiq79a5XmLlOYwwlX06Pu7CafMp7hW1fPeZtL' '4o9Sz3NvPI8GECCaZk7n5vi1EJ5/wfIQbddrC8j45joBG6gFSf4tRJct82dSyn6bd71' 'knwPZH1sKK46Y0ePJvEIDI3JDd7pRZuMM2sN8=') class OAuth2TokenTests(unittest.TestCase): def test_generate_authorize_url(self): token = gdata.gauth.OAuth2Token('clientId', 'clientSecret', 'https://www.google.com/calendar/feeds', 'userAgent') url = token.generate_authorize_url() self.assertEqual( url, 'https://accounts.google.com/o/oauth2/auth?access_type=offline&' 'redirect_uri=urn%3Aietf%3Awg%3Aoauth%3A2.0%3Aoob&response_type=code&' 'client_id=clientId&approval_prompt=auto&' 'scope=https%3A%2F%2Fwww.google.com%2Fcalendar%2Ffeeds') url = token.generate_authorize_url('https://www.example.com/redirect', 'token') self.assertEqual( url, 'https://accounts.google.com/o/oauth2/auth?access_type=offline&' 'redirect_uri=https%3A%2F%2Fwww.example.com%2Fredirect&' 'response_type=token&client_id=clientId&approval_prompt=auto&' 'scope=https%3A%2F%2Fwww.google.com%2Fcalendar%2Ffeeds') url = token.generate_authorize_url(access_type='online') self.assertEqual( url, 'https://accounts.google.com/o/oauth2/auth?access_type=online&' 'redirect_uri=urn%3Aietf%3Awg%3Aoauth%3A2.0%3Aoob&response_type=code&' 'client_id=clientId&approval_prompt=auto&' 'scope=https%3A%2F%2Fwww.google.com%2Fcalendar%2Ffeeds') def test_modify_request(self): token = gdata.gauth.OAuth2Token('clientId', 'clientSecret', 'https://www.google.com/calendar/feeds', 'userAgent', access_token='accessToken') request = atom.http_core.HttpRequest() token.modify_request(request) self.assertEqual(request.headers['Authorization'], 'Bearer accessToken') class OAuth2TokenFromCredentialsTest(unittest.TestCase): class DummyCredentials(object): def __init__(self, *args, **kwargs): self.client_id = 'client_id' self.client_secret = 'client_secret' self.user_agent = 'user_agent' self.token_uri = 'token_uri' self.access_token = 'access_token' self.refresh_token = 'refresh_token' self.token_expiry = 'token_expiry' self.invalid = 'invalid' self._refresh_called = False def _refresh(self, unused_http_request): self._refresh_called = True def setUp(self): # Don't want to force the test to have a dependency on httplib2, # which is a dependency of google-api-python-client self._httplib2 = sys.modules.get('httplib2') dummy_httplib2 = types.ModuleType('httplib2') class DummyHttp(object): def request(self, *args, **kwargs): pass dummy_httplib2.Http = DummyHttp sys.modules['httplib2'] = dummy_httplib2 self.credentials = self.DummyCredentials() self.token = gdata.gauth.OAuth2TokenFromCredentials(self.credentials) def tearDown(self): del sys.modules['httplib2'] if self._httplib2 is not None: sys.modules['httplib2'] = self._httplib2 del self._httplib2 def _check_all_values(self): self.assertEqual(self.token.client_id, self.credentials.client_id) self.assertEqual(self.token.client_secret, self.credentials.client_secret) self.assertEqual(self.token.user_agent, self.credentials.user_agent) self.assertEqual(self.token.token_uri, self.credentials.token_uri) self.assertEqual(self.token.access_token, self.credentials.access_token) self.assertEqual(self.token.refresh_token, self.credentials.refresh_token) self.assertEqual(self.token.token_expiry, self.credentials.token_expiry) self.assertEqual(self.token._invalid, self.credentials.invalid) def test_get_proxied_attributes(self): self._check_all_values() def test_get_proxied_values_notset(self): proxy_keys = ['client_id', 'client_secret', 'user_agent', 'token_uri', 'access_token', 'refresh_token', 'token_expiry', '_invalid'] for key in proxy_keys: self.assertFalse(self.token.__dict__.has_key(key)) def test_get_proxied_values_change_credentials(self): new_value = 'NEW_VALUE' self.credentials.access_token = new_value self.assertEqual(self.token.access_token, new_value) self.credentials.invalid = new_value self.assertEqual(self.token._invalid, new_value) def test_get_proxied_values_change_credentials(self): # Make sure scope is not a valid attribute (ignored in this subclass) self.assertFalse(self.token.__dict__.has_key('scope')) self.assertFalse(self.token.__class__.__dict__.has_key('scope')) # Make sure attribute lookup fails as it should self.assertRaises(AttributeError, getattr, self.token, 'scope') self.assertRaises(AttributeError, lambda: self.token.scope) def test_set_proxied_values(self): # Check all values after each setattr to make sure no side affects self.token.client_id = 'value1' self._check_all_values() self.token.client_secret = 'value2' self._check_all_values() self.token.user_agent = 'value3' self._check_all_values() self.token.token_uri = 'value4' self._check_all_values() self.token.access_token = 'value5' self._check_all_values() self.token.refresh_token = 'value6' self._check_all_values() self.token.token_expiry = 'value7' self._check_all_values() self.token._invalid = 'value8' self._check_all_values() def test_set_proxied_values_nonprotected_attribute(self): self.assertFalse(self.token.__dict__.has_key('scope')) self.assertFalse(self.token.__class__.__dict__.has_key('scope')) self.token.scope = 'value' self.assertEqual(self.token.scope, 'value') self.assertFalse(hasattr(self.credentials, 'scope')) self._check_all_values() def test_disallowed(self): self.assertRaises(NotImplementedError, self.token.generate_authorize_url) self.assertRaises(NotImplementedError, self.token.get_access_token) self.assertRaises(NotImplementedError, self.token.revoke) self.assertRaises(NotImplementedError, self.token._extract_tokens) def test_refresh(self): dummy_gdata_request_object = object() self.assertFalse(self.credentials._refresh_called) self.token._refresh(dummy_gdata_request_object) self.assertTrue(self.credentials._refresh_called) class OAuthHeaderTest(unittest.TestCase): def test_generate_auth_header(self): header = gdata.gauth.generate_auth_header( 'consumerkey', 1234567890, 'mynonce', 'unknown_sig_type', 'sig') self.assert_(header.startswith('OAuth')) self.assert_(header.find('oauth_nonce="mynonce"') > -1) self.assert_(header.find('oauth_timestamp="1234567890"') > -1) self.assert_(header.find('oauth_consumer_key="consumerkey"') > -1) self.assert_( header.find('oauth_signature_method="unknown_sig_type"') > -1) self.assert_(header.find('oauth_version="1.0"') > -1) self.assert_(header.find('oauth_signature="sig"') > -1) header = gdata.gauth.generate_auth_header( 'consumer/key', 1234567890, 'ab%&33', '', 'ab/+-_=') self.assert_(header.find('oauth_nonce="ab%25%2633"') > -1) self.assert_(header.find('oauth_consumer_key="consumer%2Fkey"') > -1) self.assert_(header.find('oauth_signature_method=""') > -1) self.assert_(header.find('oauth_signature="ab%2F%2B-_%3D"') > -1) class OAuthGetRequestToken(unittest.TestCase): def test_request_hmac_request_token(self): request = gdata.gauth.generate_request_for_request_token( 'anonymous', gdata.gauth.HMAC_SHA1, ['http://www.blogger.com/feeds/', 'http://www.google.com/calendar/feeds/'], consumer_secret='anonymous') request_uri = str(request.uri) self.assert_('http%3A%2F%2Fwww.blogger.com%2Ffeeds%2F' in request_uri) self.assert_( 'http%3A%2F%2Fwww.google.com%2Fcalendar%2Ffeeds%2F' in request_uri) auth_header = request.headers['Authorization'] self.assert_('oauth_consumer_key="anonymous"' in auth_header) self.assert_('oauth_signature_method="HMAC-SHA1"' in auth_header) self.assert_('oauth_version="1.0"' in auth_header) self.assert_('oauth_signature="' in auth_header) self.assert_('oauth_nonce="' in auth_header) self.assert_('oauth_timestamp="' in auth_header) def test_request_rsa_request_token(self): request = gdata.gauth.generate_request_for_request_token( 'anonymous', gdata.gauth.RSA_SHA1, ['http://www.blogger.com/feeds/', 'http://www.google.com/calendar/feeds/'], rsa_key=PRIVATE_TEST_KEY) request_uri = str(request.uri) self.assert_('http%3A%2F%2Fwww.blogger.com%2Ffeeds%2F' in request_uri) self.assert_( 'http%3A%2F%2Fwww.google.com%2Fcalendar%2Ffeeds%2F' in request_uri) auth_header = request.headers['Authorization'] self.assert_('oauth_consumer_key="anonymous"' in auth_header) self.assert_('oauth_signature_method="RSA-SHA1"' in auth_header) self.assert_('oauth_version="1.0"' in auth_header) self.assert_('oauth_signature="' in auth_header) self.assert_('oauth_nonce="' in auth_header) self.assert_('oauth_timestamp="' in auth_header) def test_extract_token_from_body(self): body = ('oauth_token=4%2F5bNFM_efIu3yN-E9RrF1KfZzOAZG&oauth_token_secret=' '%2B4O49V9WUOkjXgpOobAtgYzy&oauth_callback_confirmed=true') token, secret = gdata.gauth.oauth_token_info_from_body(body) self.assertEqual(token, '4/5bNFM_efIu3yN-E9RrF1KfZzOAZG') self.assertEqual(secret, '+4O49V9WUOkjXgpOobAtgYzy') def test_hmac_request_token_from_body(self): body = ('oauth_token=4%2F5bNFM_efIu3yN-E9RrF1KfZzOAZG&oauth_token_secret=' '%2B4O49V9WUOkjXgpOobAtgYzy&oauth_callback_confirmed=true') request_token = gdata.gauth.hmac_token_from_body(body, 'myKey', 'mySecret', True) self.assertEqual(request_token.consumer_key, 'myKey') self.assertEqual(request_token.consumer_secret, 'mySecret') self.assertEqual(request_token.token, '4/5bNFM_efIu3yN-E9RrF1KfZzOAZG') self.assertEqual(request_token.token_secret, '+4O49V9WUOkjXgpOobAtgYzy') self.assertEqual(request_token.auth_state, gdata.gauth.REQUEST_TOKEN) def test_rsa_request_token_from_body(self): body = ('oauth_token=4%2F5bNFM_efIu3yN-E9RrF1KfZzOAZG&oauth_token_secret=' '%2B4O49V9WUOkjXgpOobAtgYzy&oauth_callback_confirmed=true') request_token = gdata.gauth.rsa_token_from_body(body, 'myKey', 'rsaKey', True) self.assertEqual(request_token.consumer_key, 'myKey') self.assertEqual(request_token.rsa_private_key, 'rsaKey') self.assertEqual(request_token.token, '4/5bNFM_efIu3yN-E9RrF1KfZzOAZG') self.assertEqual(request_token.token_secret, '+4O49V9WUOkjXgpOobAtgYzy') self.assertEqual(request_token.auth_state, gdata.gauth.REQUEST_TOKEN) class OAuthAuthorizeToken(unittest.TestCase): def test_generate_authorization_url(self): url = gdata.gauth.generate_oauth_authorization_url('/+=aosdpikk') self.assert_(str(url).startswith( 'https://www.google.com/accounts/OAuthAuthorizeToken')) self.assert_('oauth_token=%2F%2B%3Daosdpikk' in str(url)) def test_extract_auth_token(self): url = ('http://www.example.com/test?oauth_token=' 'CKF50YzIHxCT85KMAg&oauth_verifier=123zzz') token = gdata.gauth.oauth_token_info_from_url(url) self.assertEqual(token[0], 'CKF50YzIHxCT85KMAg') self.assertEqual(token[1], '123zzz') class FindScopesForService(unittest.TestCase): def test_find_all_scopes(self): count = 0 for key, scopes in gdata.gauth.AUTH_SCOPES.iteritems(): count += len(scopes) self.assertEqual(count, len(gdata.gauth.find_scopes_for_services())) def test_single_service(self): self.assertEqual( gdata.gauth.FindScopesForServices(('codesearch',)), ['http://www.google.com/codesearch/feeds/']) def test_multiple_services(self): self.assertEqual( set(gdata.gauth.find_scopes_for_services(('jotspot', 'wise'))), set(['http://sites.google.com/feeds/', 'https://sites.google.com/feeds/', 'https://spreadsheets.google.com/feeds/'])) def suite(): return conf.build_suite([AuthSubTest, TokensToAndFromBlobsTest, OAuthHmacTokenTests, OAuthRsaTokenTests, OAuthHeaderTest, OAuthGetRequestToken, OAuthAuthorizeToken, FindScopesForService]) if __name__ == '__main__': unittest.main() gdata-2.0.18/tests/gdata_tests/books_test.py0000750036466300116100000000502412156622363021133 0ustar afshareng00000000000000#!/usr/bin/python __author__ = "James Sams " import unittest from gdata import test_data import gdata.books import atom class BookEntryTest(unittest.TestCase): def testBookEntryFromString(self): entry = gdata.books.Book.FromString(test_data.BOOK_ENTRY) self.assert_(isinstance(entry, gdata.books.Book)) self.assertEquals([x.text for x in entry.creator], ['John Rawls']) self.assertEquals(entry.date.text, '1999') self.assertEquals(entry.format.text, '538 pages') self.assertEquals([x.text for x in entry.identifier], ['b7GZr5Btp30C', 'ISBN:0198250541', 'ISBN:9780198250548']) self.assertEquals([x.text for x in entry.publisher], ['Oxford University Press']) self.assertEquals(entry.subject, None) self.assertEquals([x.text for x in entry.dc_title], ['A theory of justice']) self.assertEquals(entry.viewability.value, 'http://schemas.google.com/books/2008#view_partial') self.assertEquals(entry.embeddability.value, 'http://schemas.google.com/books/2008#embeddable') self.assertEquals(entry.review, None) self.assertEquals([getattr(entry.rating, x) for x in ("min", "max", "average", "value")], ['1', '5', '4.00', None]) self.assertEquals(entry.GetThumbnailLink().href, 'http://bks0.books.google.com/books?id=b7GZr5Btp30C&printsec=frontcover&img=1&zoom=5&sig=ACfU3U121bWZsbjBfVwVRSK2o982jJTd1w&source=gbs_gdata') self.assertEquals(entry.GetInfoLink().href, 'http://books.google.com/books?id=b7GZr5Btp30C&ie=ISO-8859-1&source=gbs_gdata') self.assertEquals(entry.GetPreviewLink(), None) self.assertEquals(entry.GetAnnotationLink().href, 'http://www.google.com/books/feeds/users/me/volumes') self.assertEquals(entry.get_google_id(), 'b7GZr5Btp30C') def testBookFeedFromString(self): feed = gdata.books.BookFeed.FromString(test_data.BOOK_FEED) self.assert_(isinstance(feed, gdata.books.BookFeed)) self.assertEquals( len(feed.entry), 1) self.assert_(isinstance(feed.entry[0], gdata.books.Book)) def testBookEntryToDict(self): book = gdata.books.Book() book.dc_title.append(gdata.books.Title(text='a')) book.dc_title.append(gdata.books.Title(text='b')) book.dc_title.append(gdata.books.Title(text='c')) self.assertEqual(book.to_dict()['title'], 'a b c') if __name__ == "__main__": unittest.main() gdata-2.0.18/tests/gdata_tests/oauth/0000750036466300116100000000000012156625015017516 5ustar afshareng00000000000000gdata-2.0.18/tests/gdata_tests/oauth/data_test.py0000640036466300116100000005544112156622363022055 0ustar afshareng00000000000000#!/usr/bin/python # -*- coding: utf-8 -*- __author__ = 'samuel.cyprian@gmail.com (Samuel Cyprian)' import unittest from gdata import oauth, test_config HTTP_METHOD_POST = 'POST' VERSION = '1.0' class OauthUtilsTest(unittest.TestCase): def test_build_authenticate_header(self): self.assertEqual(oauth.build_authenticate_header(), {'WWW-Authenticate' :'OAuth realm=""'}) self.assertEqual(oauth.build_authenticate_header('foo'), {'WWW-Authenticate': 'OAuth realm="foo"'}) def test_escape(self): #Special cases self.assertEqual(oauth.escape('~'), '~') self.assertEqual(oauth.escape('/'), '%2F') self.assertEqual(oauth.escape('+'), '%2B') self.assertEqual(oauth.escape(' '), '%20') self.assertEqual(oauth.escape('Peter Strömberg'), 'Peter%20Str%C3%B6mberg') def test_generate_timestamp(self): self.assertTrue(oauth.generate_timestamp()>0) self.assertTrue(type(oauth.generate_timestamp()) is type(0)) def test_generate_nonce(self): DEFAULT_NONCE_LENGTH = 8 self.assertTrue(len(oauth.generate_nonce()) is DEFAULT_NONCE_LENGTH) self.assertTrue(type(oauth.generate_nonce()) is type('')) class OAuthConsumerTest(unittest.TestCase): def setUp(self): self.key = 'key' self.secret = 'secret' self.consumer = oauth.OAuthConsumer(self.key, self.secret) def test_OAuthConsumer_attr_key(self): self.assertEqual(self.consumer.key, self.key) def test_OAuthConsumer_attr_secret(self): self.assertEqual(self.consumer.secret, self.secret) class OAuthTokenTest(unittest.TestCase): def setUp(self): self.key = 'key' self.secret = 'secret' self.token = oauth.OAuthToken(self.key, self.secret) def test_OAuthToken_attr_key(self): self.assertEqual(self.token.key, self.key) def test_OAuthToken_attr_secret(self): self.assertEqual(self.token.secret, self.secret) def test_to_string(self): self.assertEqual(self.token.to_string(), 'oauth_token_secret=secret&oauth_token=key') t = oauth.OAuthToken('+', '%') self.assertEqual(t.to_string(), 'oauth_token_secret=%25&oauth_token=%2B') def test_from_string(self): s = 'oauth_token_secret=secret&oauth_token=key' t = oauth.OAuthToken.from_string(s) self.assertEqual(t.key, 'key') self.assertEqual(t.secret, 'secret') t = oauth.OAuthToken.from_string('oauth_token_secret=%25&oauth_token=%2B') self.assertEqual(t.key, '+') self.assertEqual(t.secret, '%') def test___str__(self): self.assertEqual(str(self.token), 'oauth_token_secret=secret&oauth_token=key') t = oauth.OAuthToken('+', '%') self.assertEqual(str(t), 'oauth_token_secret=%25&oauth_token=%2B') class OAuthParameters(object): CONSUMER_KEY = 'oauth_consumer_key' TOKEN = 'oauth_token' SIGNATURE_METHOD = 'oauth_signature_method' SIGNATURE = 'oauth_signature' TIMESTAMP = 'oauth_timestamp' NONCE = 'oauth_nonce' VERSION = 'oauth_version' CALLBACK = 'oauth_callback' ALL_PARAMETERS = (CONSUMER_KEY, TOKEN, SIGNATURE_METHOD, SIGNATURE, TIMESTAMP, NONCE, VERSION) class OAuthTest(unittest.TestCase): def setUp(self): self.consumer = oauth.OAuthConsumer('a56b5ff0a637ab283d1d8e32ced37a9c', '9a3248210c84b264b56b98c0b872bc8a') self.token = oauth.OAuthToken('5b2cafbf20b11bace53b29e37d8a673d', '3f71254637df2002d8819458ae4f6c51') self.http_url = 'http://dev.alicehub.com/server/api/newsfeed/update/' self.http_method = HTTP_METHOD_POST class OAuthRequestTest(OAuthTest): def setUp(self): super(OAuthRequestTest, self).setUp() self.signature_method = oauth.OAuthSignatureMethod_HMAC_SHA1() self.non_oauth_param_message = 'message' self.non_oauth_param_context_id = 'context_id' self.parameters = {OAuthParameters.CONSUMER_KEY:self.consumer.key, OAuthParameters.TOKEN: self.token.key, OAuthParameters.SIGNATURE_METHOD: 'HMAC-SHA1', OAuthParameters.SIGNATURE: '947ysBZiMn6FGZ11AW06Ioco4mo=', OAuthParameters.TIMESTAMP: '1278573584', OAuthParameters.NONCE: '1770704051', OAuthParameters.VERSION: VERSION, self.non_oauth_param_message:'hey', self.non_oauth_param_context_id:'',} oauth_params_string = """ oauth_nonce="1770704051", oauth_timestamp="1278573584", oauth_consumer_key="a56b5ff0a637ab283d1d8e32ced37a9c", oauth_signature_method="HMAC-SHA1", oauth_version="1.0", oauth_token="5b2cafbf20b11bace53b29e37d8a673d", oauth_signature="947ysBZiMn6FGZ11AW06Ioco4mo%3D" """ self.oauth_header_with_realm = {'Authorization': """OAuth realm="http://example.com", %s """ % oauth_params_string} self.oauth_header_without_realm = {'Authorization': 'OAuth %s' % oauth_params_string} self.additional_param = 'foo' self.additional_value = 'bar' self.oauth_request = oauth.OAuthRequest(self.http_method, self.http_url, self.parameters) def test_set_parameter(self): self.oauth_request.set_parameter(self.additional_param, self.additional_value) self.assertEqual(self.oauth_request.get_parameter(self.additional_param), self.additional_value) def test_get_parameter(self): self.assertRaises(oauth.OAuthError, self.oauth_request.get_parameter, self.additional_param) self.oauth_request.set_parameter(self.additional_param, self.additional_value) self.assertEqual(self.oauth_request.get_parameter(self.additional_param), self.additional_value) def test__get_timestamp_nonce(self): self.assertEqual(self.oauth_request._get_timestamp_nonce(), (self.parameters[OAuthParameters.TIMESTAMP], self.parameters[OAuthParameters.NONCE])) def test_get_nonoauth_parameters(self): non_oauth_params = self.oauth_request.get_nonoauth_parameters() self.assertTrue(non_oauth_params.has_key(self.non_oauth_param_message)) self.assertFalse(non_oauth_params.has_key(OAuthParameters.CONSUMER_KEY)) def test_to_header(self): realm = 'google' header_without_realm = self.oauth_request.to_header()\ .get('Authorization') header_with_realm = self.oauth_request.to_header(realm)\ .get('Authorization') self.assertTrue(header_with_realm.find(realm)) for k in OAuthParameters.ALL_PARAMETERS: self.assertTrue(header_without_realm.find(k) > -1) self.assertTrue(header_with_realm.find(k) > -1) def check_for_params_in_string(self, params, s): for k, v in params.iteritems(): self.assertTrue(s.find(oauth.escape(k)) > -1) self.assertTrue(s.find(oauth.escape(v)) > -1) def test_to_postdata(self): post_data = self.oauth_request.to_postdata() self.check_for_params_in_string(self.parameters, post_data) def test_to_url(self): GET_url = self.oauth_request.to_url() self.assertTrue(GET_url\ .find(self.oauth_request.get_normalized_http_url()) > -1) self.assertTrue(GET_url.find('?') > -1) self.check_for_params_in_string(self.parameters, GET_url) def test_get_normalized_parameters(self): _params = self.parameters.copy() normalized_params = self.oauth_request.get_normalized_parameters() self.assertFalse(normalized_params\ .find(OAuthParameters.SIGNATURE + '=') > -1) self.assertTrue(self.parameters.get(OAuthParameters.SIGNATURE) is None) key_values = [tuple(kv.split('=')) for kv in normalized_params.split('&')] del _params[OAuthParameters.SIGNATURE] expected_key_values = _params.items() expected_key_values.sort() for k, v in expected_key_values: self.assertTrue(expected_key_values.index((k,v))\ is key_values.index((oauth.escape(k), oauth.escape(v)))) def test_get_normalized_http_method(self): lower_case_http_method = HTTP_METHOD_POST.lower() self.oauth_request.http_method = lower_case_http_method self.assertEqual(self.oauth_request.get_normalized_http_method(), lower_case_http_method.upper()) def test_get_normalized_http_url(self): url1 = 'HTTP://Example.com:80/resource?id=123' expected_url1 = "http://example.com/resource" self.oauth_request.http_url = url1 self.assertEqual(self.oauth_request.get_normalized_http_url(), expected_url1) url2 = 'HTTPS://Example.com:443/resource?id=123' expected_url2 = "https://example.com/resource" self.oauth_request.http_url = url2 self.assertEqual(self.oauth_request.get_normalized_http_url(), expected_url2) url3 = 'HTTP://Example.com:8080/resource?id=123' expected_url3 = "http://example.com:8080/resource" self.oauth_request.http_url = url3 self.assertEqual(self.oauth_request.get_normalized_http_url(), expected_url3) def test_sign_request(self): expected_signature = self.oauth_request.parameters\ .get(OAuthParameters.SIGNATURE) del self.oauth_request.parameters[OAuthParameters.SIGNATURE] self.oauth_request.sign_request(self.signature_method, self.consumer, self.token) self.assertEqual(self.oauth_request.parameters\ .get(OAuthParameters.SIGNATURE), expected_signature) def test_build_signature(self): expected_signature = self.oauth_request.parameters\ .get(OAuthParameters.SIGNATURE) self.assertEqual(self.oauth_request.build_signature(self.signature_method, self.consumer, self.token), expected_signature) def test_from_request(self): request = oauth.OAuthRequest.from_request(self.http_method, self.http_url, self.oauth_header_with_realm, {}, "message=hey&context_id=") self.assertEqual(request.__dict__, self.oauth_request.__dict__) self.assertTrue(isinstance(request, oauth.OAuthRequest)) def test_from_consumer_and_token(self): request = oauth.OAuthRequest.from_consumer_and_token(self.consumer, self.token, self.http_method, self.http_url) self.assertTrue(isinstance(request, oauth.OAuthRequest)) def test_from_token_and_callback(self): callback = 'http://example.com' request = oauth.OAuthRequest.from_token_and_callback(self.token, callback, self.http_method, self.http_url) self.assertTrue(isinstance(request, oauth.OAuthRequest)) self.assertEqual(request.get_parameter(OAuthParameters.CALLBACK), callback) def test__split_header(self): del self.parameters[self.non_oauth_param_message] del self.parameters[self.non_oauth_param_context_id] self.assertEqual(oauth.OAuthRequest._split_header(self\ .oauth_header_with_realm['Authorization']), self.parameters) self.assertEqual(oauth.OAuthRequest._split_header(self\ .oauth_header_without_realm['Authorization']), self.parameters) def test_split_url_string(self): qs = "a=1&c=hi%20there&empty=" expected_result = {'a': '1', 'c': 'hi there', 'empty': ''} self.assertEqual(oauth.OAuthRequest._split_url_string(qs), expected_result) class OAuthServerTest(OAuthTest): def setUp(self): super(OAuthServerTest, self).setUp() self.signature_method = oauth.OAuthSignatureMethod_HMAC_SHA1() self.data_store = MockOAuthDataStore() self.user = MockUser('Foo Bar') self.request_token_url = "http://example.com/oauth/request_token" self.access_token_url = "http://example.com/oauth/access_token" self.oauth_server = oauth.OAuthServer(self.data_store, {self.signature_method.get_name():self.signature_method}) def _prepare_request(self, request, token = None): request.set_parameter(OAuthParameters.SIGNATURE_METHOD, self.signature_method.get_name()) request.set_parameter(OAuthParameters.NONCE, oauth.generate_nonce()) request.set_parameter(OAuthParameters.TIMESTAMP, oauth.generate_timestamp()) request.sign_request(self.signature_method, self.consumer, token) def _get_token(self, request): self._prepare_request(request) return self.oauth_server.fetch_request_token(request) def _get_authorized_token(self, request): req_token = self._get_token(request) return self.oauth_server.authorize_token(req_token, self.user) def test_set_data_store(self): self.oauth_server.data_store = None self.assertTrue(self.oauth_server.data_store is None) self.oauth_server.set_data_store(self.data_store) self.assertTrue(self.oauth_server.data_store is not None) self.assertTrue(isinstance(self.oauth_server.data_store, oauth.OAuthDataStore)) def test_get_data_store(self): self.assertEqual(self.oauth_server.data_store, self.data_store) def test_add_signature_method(self): signature_method = oauth.OAuthSignatureMethod_PLAINTEXT() self.oauth_server.add_signature_method(signature_method) self.assertTrue(isinstance(self.oauth_server.signature_methods\ .get(signature_method.get_name()), oauth.OAuthSignatureMethod_PLAINTEXT)) def test_fetch_request_token(self): initial_request = oauth.OAuthRequest.from_consumer_and_token( self.consumer, http_method=self.http_method, http_url=self.request_token_url ) req_token_1 = self._get_token(initial_request) authorization_request = oauth.OAuthRequest.from_consumer_and_token( self.consumer, req_token_1, http_method=self.http_method, http_url=self.http_url ) req_token_2 = self._get_token(authorization_request) self.assertEqual(req_token_1.key, req_token_2.key) self.assertEqual(req_token_1.secret, req_token_2.secret) def _get_token_for_authorization(self): request = oauth.OAuthRequest.from_consumer_and_token( self.consumer, http_method=self.http_method, http_url=self.request_token_url ) request_token = self._get_token(request) authorization_request = oauth.OAuthRequest.from_consumer_and_token( self.consumer, request_token, http_method=self.http_method, http_url=self.http_url ) return self._get_authorized_token(authorization_request) def test_authorize_token(self): authorized_token = self._get_token_for_authorization() self.assertTrue(authorized_token is not None) def _get_access_token_request(self, authorized_token): access_token_request = oauth.OAuthRequest.from_consumer_and_token( self.consumer, authorized_token, http_method=self.http_method, http_url=self.access_token_url ) self._prepare_request(access_token_request, authorized_token) return access_token_request def test_fetch_access_token(self): authorized_token = self._get_token_for_authorization() access_token_request = self._get_access_token_request(authorized_token) access_token = self.oauth_server.fetch_access_token(access_token_request) self.assertTrue(access_token is not None) self.assertNotEqual(str(authorized_token), str(access_token)) # Try to fetch access_token with used request token self.assertRaises(oauth.OAuthError, self.oauth_server.fetch_access_token, access_token_request) def test_verify_request(self): authorized_token = self._get_token_for_authorization() access_token_request = self._get_access_token_request(authorized_token) access_token = self.oauth_server.fetch_access_token(access_token_request) param1 = 'p1' value1 = 'v1' api_request = oauth.OAuthRequest.from_consumer_and_token( self.consumer, access_token, http_method=self.http_method, http_url=self.http_url, parameters={param1:value1} ) self._prepare_request(api_request, access_token) result = self.oauth_server.verify_request(api_request) self.assertTrue(result is not None) consumer, token, parameters = result self.assertEqual(parameters.get(param1), value1) def test_get_callback(self): request = oauth.OAuthRequest.from_consumer_and_token( self.consumer, None, http_method=self.http_method, http_url=self.http_url ) self._prepare_request(request) cb_url = 'http://example.com/cb' request.set_parameter(OAuthParameters.CALLBACK, cb_url) self.assertEqual(self.oauth_server.get_callback(request), cb_url) def test_build_authenticate_header(self): self.assertEqual(oauth.build_authenticate_header(), {'WWW-Authenticate': 'OAuth realm=""'}) self.assertEqual(oauth.build_authenticate_header('foo'), {'WWW-Authenticate': 'OAuth realm="foo"'}) class OAuthClientTest(OAuthTest): def setUp(self): super(OAuthClientTest, self).setUp() self.oauth_client = oauth.OAuthClient(self.consumer, self.token) def test_get_consumer(self): consumer = self.oauth_client.get_consumer() self.assertTrue(isinstance(consumer, oauth.OAuthConsumer)) self.assertEqual(consumer.__dict__, self.consumer.__dict__) def test_get_token(self): token = self.oauth_client.get_token() self.assertTrue(isinstance(token, oauth.OAuthToken)) self.assertEqual(token.__dict__, self.token.__dict__) #Mockup OAuthDataStore TOKEN_TYPE_REQUEST = 'request' TOKEN_TYPE_ACCESS = 'access' class MockOAuthDataStore(oauth.OAuthDataStore): def __init__(self): self.consumer = oauth.OAuthConsumer('a56b5ff0a637ab283d1d8e32ced37a9c', '9a3248210c84b264b56b98c0b872bc8a') self.consumer_db = {self.consumer.key: self.consumer} self.request_token_db = {} self.access_token_db = {} self.nonce = None def lookup_consumer(self, key): return self.consumer_db.get(key) def lookup_token(self, oauth_consumer, token_type, token_field): data = None if token_type == TOKEN_TYPE_REQUEST: data = self.request_token_db.get(token_field) elif token_type == TOKEN_TYPE_ACCESS: data = self.access_token_db.get(token_field) if data: token, consumer, authenticated_user = data if consumer.key == oauth_consumer.key: return token return None def lookup_nonce(self, oauth_consumer, oauth_token, nonce): is_used = self.nonce == nonce self.nonce = nonce return is_used def fetch_request_token(self, oauth_consumer): token = oauth.OAuthToken("5b2cafbf20b11bace53b29e37d8a673dRT", "3f71254637df2002d8819458ae4f6c51RT") self.request_token_db[token.key] = (token, oauth_consumer, None) return token def fetch_access_token(self, oauth_consumer, oauth_token): data = self.request_token_db.get(oauth_token.key) if data: del self.request_token_db[oauth_token.key] request_token, consumer, authenticated_user = data access_token = oauth.OAuthToken("5b2cafbf20b11bace53b29e37d8a673dAT", "3f71254637df2002d8819458ae4f6c51AT") self.access_token_db[access_token.key] = (access_token, consumer, authenticated_user) return access_token else: return None def authorize_request_token(self, oauth_token, user): data = self.request_token_db.get(oauth_token.key) if data and data[2] == None: request_token, consumer, authenticated_user = data authenticated_user = user self.request_token_db[request_token.key] = (request_token, consumer, authenticated_user) return request_token else: return None #Mock user class MockUser(object): def __init__(self, name): self.name = name def suite(): return test_config.build_suite([OauthUtilsTest, OAuthConsumerTest, OAuthTokenTest, OAuthRequestTest, OAuthServerTest, OAuthClientTest]) if __name__ == '__main__': unittest.main()gdata-2.0.18/tests/gdata_tests/oauth/__init__.py0000640036466300116100000000000012156622363021621 0ustar afshareng00000000000000gdata-2.0.18/tests/gdata_tests/live_client_test.py0000750036466300116100000002644212156622363022322 0ustar afshareng00000000000000#!/usr/bin/env python # # Copyright (C) 2009 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. # This module is used for version 2 of the Google Data APIs. # These tests attempt to connect to Google servers. __author__ = 'j.s@google.com (Jeff Scudder)' import os import unittest import gdata.client import atom.http_core import atom.mock_http_core import atom.core import gdata.data import gdata.core # TODO: switch to using v2 atom data once it is available. import atom import gdata.test_config as conf conf.options.register_option(conf.BLOG_ID_OPTION) class BloggerTest(unittest.TestCase): def setUp(self): self.client = None if conf.options.get_value('runlive') == 'true': self.client = gdata.client.GDClient() conf.configure_client(self.client, 'BloggerTest', 'blogger') def tearDown(self): conf.close_client(self.client) def test_create_update_delete(self): if not conf.options.get_value('runlive') == 'true': return # Either load the recording or prepare to make a live request. conf.configure_cache(self.client, 'test_create_update_delete') blog_post = atom.Entry( title=atom.Title(text='test from python BloggerTest'), content=atom.Content(text='This is only a test.')) http_request = atom.http_core.HttpRequest() http_request.add_body_part(str(blog_post), 'application/atom+xml') def entry_from_string_wrapper(response): self.assert_(response.getheader('content-type') is not None) self.assert_(response.getheader('gdata-version') is not None) return atom.EntryFromString(response.read()) entry = self.client.request('POST', 'http://www.blogger.com/feeds/%s/posts/default' % ( conf.options.get_value('blogid')), converter=entry_from_string_wrapper, http_request=http_request) self.assertEqual(entry.title.text, 'test from python BloggerTest') self.assertEqual(entry.content.text, 'This is only a test.') # Edit the test entry. edit_link = None for link in entry.link: # Find the edit link for this entry. if link.rel == 'edit': edit_link = link.href entry.title.text = 'Edited' http_request = atom.http_core.HttpRequest() http_request.add_body_part(str(entry), 'application/atom+xml') edited_entry = self.client.request('PUT', edit_link, converter=entry_from_string_wrapper, http_request=http_request) self.assertEqual(edited_entry.title.text, 'Edited') self.assertEqual(edited_entry.content.text, entry.content.text) # Delete the test entry from the blog. edit_link = None for link in edited_entry.link: if link.rel == 'edit': edit_link = link.href response = self.client.request('DELETE', edit_link) self.assertEqual(response.status, 200) def test_use_version_two(self): if not conf.options.get_value('runlive') == 'true': return conf.configure_cache(self.client, 'test_use_version_two') # Use version 2 of the Blogger API. self.client.api_version = '2' # Create a v2 blog post entry to post on the blog. entry = create_element('entry') entry._other_elements.append( create_element('title', text='Marriage!', attributes={'type': 'text'})) entry._other_elements.append( create_element('content', attributes={'type': 'text'}, text='Mr. Darcy has proposed marriage to me!')) entry._other_elements.append( create_element('category', attributes={'scheme': TAG, 'term': 'marriage'})) entry._other_elements.append( create_element('category', attributes={'scheme': TAG, 'term': 'Mr. Darcy'})) http_request = atom.http_core.HttpRequest() http_request.add_body_part(entry.to_string(), 'application/atom+xml') posted = self.client.request('POST', 'http://www.blogger.com/feeds/%s/posts/default' % ( conf.options.get_value('blogid')), converter=element_from_string, http_request=http_request) # Verify that the blog post content is correct. self.assertEqual(posted.get_elements('title', ATOM)[0].text, 'Marriage!') # TODO: uncomment once server bug is fixed. #self.assertEqual(posted.get_elements('content', ATOM)[0].text, # 'Mr. Darcy has proposed marriage to me!') found_tags = [False, False] categories = posted.get_elements('category', ATOM) self.assertEqual(len(categories), 2) for category in categories: if category.get_attributes('term')[0].value == 'marriage': found_tags[0] = True elif category.get_attributes('term')[0].value == 'Mr. Darcy': found_tags[1] = True self.assert_(found_tags[0]) self.assert_(found_tags[1]) # Find the blog post on the blog. self_link = None edit_link = None for link in posted.get_elements('link', ATOM): if link.get_attributes('rel')[0].value == 'self': self_link = link.get_attributes('href')[0].value elif link.get_attributes('rel')[0].value == 'edit': edit_link = link.get_attributes('href')[0].value self.assert_(self_link is not None) self.assert_(edit_link is not None) queried = self.client.request('GET', self_link, converter=element_from_string) # TODO: add additional asserts to check content and etags. # Test queries using ETags. entry = self.client.get_entry(self_link) self.assert_(entry.etag is not None) self.assertRaises(gdata.client.NotModified, self.client.get_entry, self_link, etag=entry.etag) # Delete the test blog post. self.client.request('DELETE', edit_link) class ContactsTest(unittest.TestCase): def setUp(self): self.client = None if conf.options.get_value('runlive') == 'true': self.client = gdata.client.GDClient() conf.configure_client(self.client, 'ContactsTest', 'cp') def tearDown(self): conf.close_client(self.client) # Run this test and profiles fails def test_crud_version_two(self): if not conf.options.get_value('runlive') == 'true': return conf.configure_cache(self.client, 'test_crud_version_two') self.client.api_version = '2' entry = create_element('entry') entry._other_elements.append( create_element('title', ATOM, 'Jeff', {'type': 'text'})) entry._other_elements.append( create_element('email', GD, attributes={'address': 'j.s@google.com', 'rel': WORK_REL})) http_request = atom.http_core.HttpRequest() http_request.add_body_part(entry.to_string(), 'application/atom+xml') posted = self.client.request('POST', 'http://www.google.com/m8/feeds/contacts/default/full', converter=element_from_string, http_request=http_request) self_link = None edit_link = None for link in posted.get_elements('link', ATOM): if link.get_attributes('rel')[0].value == 'self': self_link = link.get_attributes('href')[0].value elif link.get_attributes('rel')[0].value == 'edit': edit_link = link.get_attributes('href')[0].value self.assert_(self_link is not None) self.assert_(edit_link is not None) etag = posted.get_attributes('etag')[0].value self.assert_(etag is not None) self.assert_(len(etag) > 0) # Delete the test contact. http_request = atom.http_core.HttpRequest() http_request.headers['If-Match'] = etag self.client.request('DELETE', edit_link, http_request=http_request) class VersionTwoClientContactsTest(unittest.TestCase): def setUp(self): self.client = None if conf.options.get_value('runlive') == 'true': self.client = gdata.client.GDClient() self.client.api_version = '2' conf.configure_client(self.client, 'VersionTwoClientContactsTest', 'cp') self.old_proxy = os.environ.get('https_proxy') def tearDown(self): if self.old_proxy: os.environ['https_proxy'] = self.old_proxy elif 'https_proxy' in os.environ: del os.environ['https_proxy'] conf.close_client(self.client) def test_version_two_client(self): if not conf.options.get_value('runlive') == 'true': return conf.configure_cache(self.client, 'test_version_two_client') entry = gdata.data.GDEntry() entry._other_elements.append( create_element('title', ATOM, 'Test', {'type': 'text'})) entry._other_elements.append( create_element('email', GD, attributes={'address': 'test@example.com', 'rel': WORK_REL})) # Create the test contact. posted = self.client.post(entry, 'https://www.google.com/m8/feeds/contacts/default/full') self.assert_(isinstance(posted, gdata.data.GDEntry)) self.assertEqual(posted.get_elements('title')[0].text, 'Test') self.assertEqual(posted.get_elements('email')[0].get_attributes( 'address')[0].value, 'test@example.com') posted.get_elements('title')[0].text = 'Doug' edited = self.client.update(posted) self.assert_(isinstance(edited, gdata.data.GDEntry)) self.assertEqual(edited.get_elements('title')[0].text, 'Doug') self.assertEqual(edited.get_elements('email')[0].get_attributes( 'address')[0].value, 'test@example.com') # Delete the test contact. self.client.delete(edited) def notest_crud_over_https_proxy(self): import urllib PROXY_ADDR = '98.192.125.23' try: response = urllib.urlopen('http://' + PROXY_ADDR) except IOError: return # Only bother running the test if the proxy is up if response.getcode() == 200: os.environ['https_proxy'] = PROXY_ADDR # Perform the CRUD test above, this time over a proxy. self.test_version_two_client() class JsoncRequestTest(unittest.TestCase): def setUp(self): self.client = gdata.client.GDClient() def test_get_jsonc(self): jsonc = self.client.get_feed( 'http://gdata.youtube.com/feeds/api/videos?q=surfing&v=2&alt=jsonc', converter=gdata.core.parse_json_file) self.assertTrue(len(jsonc.data.items) > 0) # Utility methods. # The Atom XML namespace. ATOM = 'http://www.w3.org/2005/Atom' # URL used as the scheme for a blog post tag. TAG = 'http://www.blogger.com/atom/ns#' # Namespace for Google Data API elements. GD = 'http://schemas.google.com/g/2005' WORK_REL = 'http://schemas.google.com/g/2005#work' def create_element(tag, namespace=ATOM, text=None, attributes=None): element = atom.core.XmlElement() element._qname = '{%s}%s' % (namespace, tag) if text is not None: element.text = text if attributes is not None: element._other_attributes = attributes.copy() return element def element_from_string(response): return atom.core.xml_element_from_string(response.read(), atom.core.XmlElement) def suite(): return conf.build_suite([BloggerTest, ContactsTest, VersionTwoClientContactsTest, JsoncRequestTest]) if __name__ == '__main__': unittest.TextTestRunner().run(suite()) gdata-2.0.18/tests/gdata_tests/contacts/0000750036466300116100000000000012156625015020214 5ustar afshareng00000000000000gdata-2.0.18/tests/gdata_tests/contacts/service_test.py0000750036466300116100000002450712156622363023303 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright (C) 2007 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. __author__ = 'api.jscudder (Jeff Scudder)' import getpass import re import unittest import urllib import atom import gdata.contacts.service import gdata.test_config as conf conf.options.register_option(conf.TEST_IMAGE_LOCATION_OPTION) class ContactsServiceTest(unittest.TestCase): def setUp(self): self.gd_client = gdata.contacts.service.ContactsService() conf.configure_service(self.gd_client, 'ContactsServiceTest', 'cp') self.gd_client.email = conf.options.get_value('username') def tearDown(self): conf.close_service(self.gd_client) def testGetContactsFeed(self): if not conf.options.get_value('runlive') == 'true': return conf.configure_service_cache(self.gd_client, 'testGetContactsFeed') feed = self.gd_client.GetContactsFeed() self.assert_(isinstance(feed, gdata.contacts.ContactsFeed)) def testDefaultContactList(self): self.assertEquals('default', self.gd_client.contact_list) def testCustomContactList(self): if not conf.options.get_value('runlive') == 'true': return conf.configure_service_cache(self.gd_client, 'testCustomContactList') self.gd_client.contact_list = conf.options.get_value('username') feed = self.gd_client.GetContactsFeed() self.assert_(isinstance(feed, gdata.contacts.ContactsFeed)) def testGetFeedUriDefault(self): self.gd_client.contact_list = 'domain.com' self.assertEquals('/m8/feeds/contacts/domain.com/full', self.gd_client.GetFeedUri()) def testGetFeedUriCustom(self): uri = self.gd_client.GetFeedUri(kind='groups', contact_list='example.com', projection='base/batch', scheme='https') self.assertEquals( 'https://www.google.com/m8/feeds/groups/example.com/base/batch', uri) def testCreateUpdateDeleteContactAndUpdatePhoto(self): if not conf.options.get_value('runlive') == 'true': return conf.configure_service_cache(self.gd_client, 'testCreateUpdateDeleteContactAndUpdatePhoto') DeleteTestContact(self.gd_client) # Create a new entry new_entry = gdata.contacts.ContactEntry() new_entry.title = atom.Title(text='Elizabeth Bennet') new_entry.content = atom.Content(text='Test Notes') new_entry.email.append(gdata.contacts.Email( rel='http://schemas.google.com/g/2005#work', primary='true', address='liz@gmail.com')) new_entry.phone_number.append(gdata.contacts.PhoneNumber( rel='http://schemas.google.com/g/2005#work', text='(206)555-1212')) new_entry.organization = gdata.contacts.Organization( org_name=gdata.contacts.OrgName(text='TestCo.'), rel='http://schemas.google.com/g/2005#work') entry = self.gd_client.CreateContact(new_entry) # Generate and parse the XML for the new entry. self.assertEquals(entry.title.text, new_entry.title.text) self.assertEquals(entry.content.text, 'Test Notes') self.assertEquals(len(entry.email), 1) self.assertEquals(entry.email[0].rel, new_entry.email[0].rel) self.assertEquals(entry.email[0].address, 'liz@gmail.com') self.assertEquals(len(entry.phone_number), 1) self.assertEquals(entry.phone_number[0].rel, new_entry.phone_number[0].rel) self.assertEquals(entry.phone_number[0].text, '(206)555-1212') self.assertEquals(entry.organization.org_name.text, 'TestCo.') # Edit the entry. entry.phone_number[0].text = '(555)555-1212' updated = self.gd_client.UpdateContact(entry.GetEditLink().href, entry) self.assertEquals(updated.content.text, 'Test Notes') self.assertEquals(len(updated.phone_number), 1) self.assertEquals(updated.phone_number[0].rel, entry.phone_number[0].rel) self.assertEquals(updated.phone_number[0].text, '(555)555-1212') # Change the contact's photo. updated_photo = self.gd_client.ChangePhoto( conf.options.get_value('imgpath'), updated, content_type='image/jpeg') # Refetch the contact so that it has the new photo link updated = self.gd_client.GetContact(updated.GetSelfLink().href) self.assert_(updated.GetPhotoLink() is not None) # Fetch the photo data. hosted_image = self.gd_client.GetPhoto(updated) self.assert_(hosted_image is not None) # Delete the entry. self.gd_client.DeleteContact(updated.GetEditLink().href) def testCreateAndDeleteContactUsingBatch(self): if not conf.options.get_value('runlive') == 'true': return conf.configure_service_cache(self.gd_client, 'testCreateAndDeleteContactUsingBatch') # Get random data for creating contact random_contact_number = 'notRandom12' random_contact_title = 'Random Contact %s' % ( random_contact_number) # Set contact data contact = gdata.contacts.ContactEntry() contact.title = atom.Title(text=random_contact_title) contact.email = gdata.contacts.Email( address='user%s@example.com' % random_contact_number, primary='true', rel=gdata.contacts.REL_WORK) contact.content = atom.Content(text='Contact created by ' 'gdata-python-client automated test ' 'suite.') # Form a batch request batch_request = gdata.contacts.ContactsFeed() batch_request.AddInsert(entry=contact) # Execute the batch request to insert the contact. default_batch_url = gdata.contacts.service.DEFAULT_BATCH_URL batch_result = self.gd_client.ExecuteBatch(batch_request, default_batch_url) self.assertEquals(len(batch_result.entry), 1) self.assertEquals(batch_result.entry[0].title.text, random_contact_title) self.assertEquals(batch_result.entry[0].batch_operation.type, gdata.BATCH_INSERT) self.assertEquals(batch_result.entry[0].batch_status.code, '201') expected_batch_url = re.compile('default').sub( urllib.quote(self.gd_client.email), gdata.contacts.service.DEFAULT_BATCH_URL) self.failUnless(batch_result.GetBatchLink().href, expected_batch_url) # Create a batch request to delete the newly created entry. batch_delete_request = gdata.contacts.ContactsFeed() batch_delete_request.AddDelete(entry=batch_result.entry[0]) batch_delete_result = self.gd_client.ExecuteBatch( batch_delete_request, batch_result.GetBatchLink().href) self.assertEquals(len(batch_delete_result.entry), 1) self.assertEquals(batch_delete_result.entry[0].batch_operation.type, gdata.BATCH_DELETE) self.assertEquals(batch_result.entry[0].batch_status.code, '201') def testCleanUriNeedsCleaning(self): self.assertEquals('/relative/uri', self.gd_client._CleanUri( 'http://www.google.com/relative/uri')) def testCleanUriDoesNotNeedCleaning(self): self.assertEquals('/relative/uri', self.gd_client._CleanUri( '/relative/uri')) class ContactsQueryTest(unittest.TestCase): def testConvertToStringDefaultFeed(self): query = gdata.contacts.service.ContactsQuery() self.assertEquals(str(query), '/m8/feeds/contacts/default/full') query.max_results = 10 self.assertEquals(query.ToUri(), '/m8/feeds/contacts/default/full?max-results=10') def testConvertToStringCustomFeed(self): query = gdata.contacts.service.ContactsQuery('/custom/feed/uri') self.assertEquals(str(query), '/custom/feed/uri') query.max_results = '10' self.assertEquals(query.ToUri(), '/custom/feed/uri?max-results=10') def testGroupQueryParameter(self): query = gdata.contacts.service.ContactsQuery() query.group = 'http://google.com/m8/feeds/groups/liz%40gmail.com/full/270f' self.assertEquals(query.ToUri(), '/m8/feeds/contacts/default/full' '?group=http%3A%2F%2Fgoogle.com%2Fm8%2Ffeeds%2Fgroups' '%2Fliz%2540gmail.com%2Ffull%2F270f') class ContactsGroupsTest(unittest.TestCase): def setUp(self): self.gd_client = gdata.contacts.service.ContactsService() conf.configure_service(self.gd_client, 'ContactsServiceTest', 'cp') def tearDown(self): conf.close_service(self.gd_client) def testCreateUpdateDeleteGroup(self): if not conf.options.get_value('runlive') == 'true': return conf.configure_service_cache(self.gd_client, 'testCreateUpdateDeleteGroup') test_group = gdata.contacts.GroupEntry(title=atom.Title( text='test group py')) new_group = self.gd_client.CreateGroup(test_group) self.assert_(isinstance(new_group, gdata.contacts.GroupEntry)) self.assertEquals(new_group.title.text, 'test group py') # Change the group's title new_group.title.text = 'new group name py' updated_group = self.gd_client.UpdateGroup(new_group.GetEditLink().href, new_group) self.assertEquals(updated_group.title.text, new_group.title.text) # Remove the group self.gd_client.DeleteGroup(updated_group.GetEditLink().href) # Utility methods. def DeleteTestContact(client): # Get test contact feed = client.GetContactsFeed() for entry in feed.entry: if (entry.title.text == 'Elizabeth Bennet' and entry.content.text == 'Test Notes' and entry.email[0].address == 'liz@gmail.com'): client.DeleteContact(entry.GetEditLink().href) def suite(): return unittest.TestSuite((unittest.makeSuite(ContactsServiceTest, 'test'), unittest.makeSuite(ContactsQueryTest, 'test'), unittest.makeSuite(ContactsGroupsTest, 'test'),)) if __name__ == '__main__': print ('Contacts Tests\nNOTE: Please run these tests only with a test ' 'account. The tests may delete or update your data.') unittest.TextTestRunner().run(suite()) gdata-2.0.18/tests/gdata_tests/contacts/__init__.py0000640036466300116100000000000012156622363022317 0ustar afshareng00000000000000gdata-2.0.18/tests/gdata_tests/contacts/live_client_test.py0000750036466300116100000001033612156622363024133 0ustar afshareng00000000000000#!/usr/bin/env python # # Copyright (C) 2009 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. # This module is used for version 2 of the Google Data APIs. # These tests attempt to connect to Google servers. __author__ = 'j.s@google.com (Jeff Scudder)' import unittest import gdata.test_config as conf import gdata.contacts.client import atom.core import atom.data import gdata.data class ContactsTest(unittest.TestCase): def setUp(self): self.client = None if conf.options.get_value('runlive') == 'true': self.client = gdata.contacts.client.ContactsClient() conf.configure_client(self.client, 'ContactsTest', 'cp') def tearDown(self): conf.close_client(self.client) def test_create_update_delete_contact(self): if not conf.options.get_value('runlive') == 'true': return # Either load the recording or prepare to make a live request. conf.configure_cache(self.client, 'test_create_update_delete_contact') new_contact = gdata.contacts.data.ContactEntry( nickname=gdata.contacts.data.NickName(text='Joe'), name=gdata.data.Name( given_name=gdata.data.GivenName(text='Joseph'), family_name=gdata.data.FamilyName(text='Testerson'))) new_contact.birthday = gdata.contacts.data.Birthday(when='2009-11-11') new_contact.language.append(gdata.contacts.data.Language( label='German')) created = self.client.create_contact(new_contact) # Add another language. created.language.append(gdata.contacts.data.Language( label='French')) # Create a new membership group for our test contact. new_group = gdata.contacts.data.GroupEntry( title=atom.data.Title(text='a test group')) created_group = self.client.create_group(new_group) self.assert_(created_group.id.text) # Add the contact to the new group. created.group_membership_info.append( gdata.contacts.data.GroupMembershipInfo(href=created_group.id.text)) # Upload the changes to the language and group membership. edited = self.client.update(created) # Delete the group and the test contact. self.client.delete(created_group) self.client.delete(edited) def test_low_level_create_update_delete(self): if not conf.options.get_value('runlive') == 'true': return # Either load the recording or prepare to make a live request. conf.configure_cache(self.client, 'test_low_level_create_update_delete') entry = atom.data.Entry() entry.title = atom.data.Title(text='Jeff') entry._other_elements.append( gdata.data.Email(rel=gdata.data.WORK_REL, address='j.s@google.com')) http_request = atom.http_core.HttpRequest() http_request.add_body_part(entry.to_string(), 'application/atom+xml') posted = self.client.request('POST', 'http://www.google.com/m8/feeds/contacts/default/full', desired_class=atom.data.Entry, http_request=http_request) self_link = None edit_link = None for link in posted.get_elements('link', 'http://www.w3.org/2005/Atom'): if link.get_attributes('rel')[0].value == 'self': self_link = link.get_attributes('href')[0].value elif link.get_attributes('rel')[0].value == 'edit': edit_link = link.get_attributes('href')[0].value self.assert_(self_link is not None) self.assert_(edit_link is not None) etag = posted.get_attributes('etag')[0].value self.assert_(etag is not None) self.assert_(len(etag) > 0) # Delete the test contact. http_request = atom.http_core.HttpRequest() http_request.headers['If-Match'] = etag self.client.request('DELETE', edit_link, http_request=http_request) def suite(): return conf.build_suite([ContactsTest]) if __name__ == '__main__': unittest.TextTestRunner().run(suite()) gdata-2.0.18/tests/gdata_tests/contacts/profiles/0000750036466300116100000000000012156625015022037 5ustar afshareng00000000000000gdata-2.0.18/tests/gdata_tests/contacts/profiles/service_test.py0000750036466300116100000001041712156622363025121 0ustar afshareng00000000000000#!/usr/bin/env python # # Copyright 2009 Google Inc. All Rights Reserved. # # 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. """Contains Unit Tests for Google Profiles API. ProfilesServiceTest: Provides methods to test feeds and manipulate items. ProfilesQueryTest: Constructs a query object for the profiles feed. Extends Query. """ __author__ = 'jtoledo (Julian Toledo)' import getopt import getpass import sys import unittest import gdata.contacts import gdata.contacts.service email = '' password = '' domain = '' server = 'www.google.com' GDATA_VER_HEADER = 'GData-Version' class ProfilesServiceTest(unittest.TestCase): def setUp(self): additional_headers = {GDATA_VER_HEADER: 3} self.gd_client = gdata.contacts.service.ContactsService( contact_list=domain, additional_headers=additional_headers ) self.gd_client.email = email self.gd_client.password = password self.gd_client.source = 'GoogleInc-ProfilesPythonTest-1' self.gd_client.ProgrammaticLogin() def testGetFeedUriCustom(self): uri = self.gd_client.GetFeedUri(kind='profiles', scheme='https') self.assertEquals( 'https://%s/m8/feeds/profiles/domain/%s/full' % (server, domain), uri) def testGetProfileFeedUriDefault(self): self.gd_client.contact_list = 'domain.com' self.assertEquals('/m8/feeds/profiles/domain/domain.com/full', self.gd_client.GetFeedUri('profiles')) def testCleanUriNeedsCleaning(self): self.assertEquals('/relative/uri', self.gd_client._CleanUri( 'http://www.google.com/relative/uri')) def testCleanUriDoesNotNeedCleaning(self): self.assertEquals('/relative/uri', self.gd_client._CleanUri( '/relative/uri')) def testGetProfilesFeed(self): feed = self.gd_client.GetProfilesFeed() self.assert_(isinstance(feed, gdata.contacts.ProfilesFeed)) def testGetProfile(self): # Gets an existing entry feed = self.gd_client.GetProfilesFeed() entry = feed.entry[0] self.assert_(isinstance(entry, gdata.contacts.ProfileEntry)) self.assertEquals(entry.title.text, self.gd_client.GetProfile(entry.id.text).title.text) self.assertEquals(entry._children, self.gd_client.GetProfile(entry.id.text)._children) def testUpdateProfile(self): feed = self.gd_client.GetProfilesFeed() entry = feed.entry[1] original_occupation = entry.occupation entry.occupation = gdata.contacts.Occupation(text='TEST') updated = self.gd_client.UpdateProfile(entry.GetEditLink().href, entry) self.assertEquals('TEST', updated.occupation.text) updated.occupation = original_occupation self.gd_client.UpdateProfile(updated.GetEditLink().href, updated) if __name__ == '__main__': try: opts, args = getopt.getopt(sys.argv[1:], '', ['user=', 'pw=', 'domain=']) except getopt.error, msg: print ('Profiles Tests\nNOTE: Please run these tests only with a test ' 'account. The tests may delete or update your data.\n' '\nUsage: service_test.py --email=EMAIL ' '--password=PASSWORD --domain=DOMAIN\n') sys.exit(2) # Process options for option, arg in opts: if option == '--email': email = arg elif option == '--pw': password = arg elif option == '--domain': domain = arg while not email: print 'NOTE: Please run these tests only with a test account.' email = raw_input('Please enter your email: ') while not password: password = getpass.getpass('Please enter password: ') if not password: print 'Password cannot be blank.' while not domain: print 'NOTE: Please run these tests only with a test account.' domain = raw_input('Please enter your Apps domain: ') suite = unittest.makeSuite(ProfilesServiceTest) unittest.TextTestRunner().run(suite) gdata-2.0.18/tests/gdata_tests/contacts/profiles/__init__.py0000640036466300116100000000000012156622363024142 0ustar afshareng00000000000000gdata-2.0.18/tests/gdata_tests/contacts/profiles/live_client_test.py0000640036466300116100000000650712156622363025761 0ustar afshareng00000000000000#!/usr/bin/env python # # Copyright (C) 2009 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. # This module is used for version 2 of the Google Data APIs. # These tests attempt to connect to Google servers. __author__ = 'jcgregorio@google.com (Joe Gregorio)' import atom.core import atom.data import atom.http_core import gdata.contacts.client import gdata.data import gdata.test_config as conf import unittest conf.options.register_option(conf.APPS_DOMAIN_OPTION) conf.options.register_option(conf.TARGET_USERNAME_OPTION) class ProfileTest(unittest.TestCase): def setUp(self): self.client = gdata.contacts.client.ContactsClient(domain='example.com') if conf.options.get_value('runlive') == 'true': self.client = gdata.contacts.client.ContactsClient( domain=conf.options.get_value('appsdomain')) if conf.options.get_value('ssl') == 'true': self.client.ssl = True conf.configure_client(self.client, 'ProfileTest', self.client.auth_service, True) self.client.username = conf.options.get_value('appsusername').split('@')[0] def tearDown(self): conf.close_client(self.client) def test_profiles_feed(self): if not conf.options.get_value('runlive') == 'true': return # Either load the recording or prepare to make a live request. conf.configure_cache(self.client, 'test_profiles_feed') feed = self.client.get_profiles_feed() self.assert_(isinstance(feed, gdata.contacts.data.ProfilesFeed)) def test_profiles_query(self): if not conf.options.get_value('runlive') == 'true': return # Either load the recording or prepare to make a live request. conf.configure_cache(self.client, 'test_profiles_feed') query = gdata.contacts.client.ProfilesQuery(max_results=1) feed = self.client.get_profiles_feed(q=query) self.assert_(isinstance(feed, gdata.contacts.data.ProfilesFeed)) self.assert_(len(feed.entry) == 1) # Needs at least 2 profiles in the feed to test the start-key # query param. next = feed.GetNextLink() feed = None if next: # Retrieve the start-key query param from the next link. uri = atom.http_core.Uri.parse_uri(next.href) if 'start-key' in uri.query: query.start_key = uri.query['start-key'] feed = self.client.get_profiles_feed(q=query) self.assert_(isinstance(feed, gdata.contacts.data.ProfilesFeed)) self.assert_(len(feed.entry) == 1) self.assert_(feed.GetSelfLink().href == next.href) # Compare with a feed retrieved with the next link. next_feed = self.client.get_profiles_feed(uri=next.href) self.assert_(len(next_feed.entry) == 1) self.assert_(next_feed.entry[0].id.text == feed.entry[0].id.text) def suite(): return conf.build_suite([ProfileTest]) if __name__ == '__main__': unittest.TextTestRunner().run(suite()) gdata-2.0.18/tests/gdata_tests/sample_util_test.py0000750036466300116100000000343312156622363022336 0ustar afshareng00000000000000#!/usr/bin/env python # # Copyright (C) 2009 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. # This module is used for version 2 of the Google Data APIs. __author__ = 'j.s@google.com (Jeff Scudder)' import unittest import sys import gdata.sample_util class SettingsUtilTest(unittest.TestCase): def setUp(self): self.settings = gdata.sample_util.SettingsUtil() def test_get_param(self): self.assert_(self.settings.get_param('missing', ask=False) is None) self.settings.prefs['x'] = 'something' self.assertEqual(self.settings.get_param('x'), 'something') def test_get_param_from_command_line_arg(self): self.assert_('x' not in self.settings.prefs) self.assert_(self.settings.get_param('x', ask=False) is None) sys.argv.append('--x=something') self.assertEqual(self.settings.get_param('x'), 'something') self.assert_('x' not in self.settings.prefs) self.assert_('y' not in self.settings.prefs) self.assert_(self.settings.get_param('y', ask=False) is None) sys.argv.append('--y') sys.argv.append('other') self.assertEqual(self.settings.get_param('y', reuse=True), 'other') self.assertEqual(self.settings.prefs['y'], 'other') def suite(): return conf.build_suite([SettingsUtilTest]) if __name__ == '__main__': unittest.main() gdata-2.0.18/tests/gdata_tests/health_test.py0000750036466300116100000001577212156622363021276 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright 2009 Google Inc. All Rights Reserved. # # 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. __author__ = 'api.eric@google.com (Eric Bidelman)' import unittest from gdata import test_data import gdata.health import gdata.health.service class ProfileEntryTest(unittest.TestCase): def setUp(self): self.profile_entry = gdata.health.ProfileEntryFromString( test_data.HEALTH_PROFILE_ENTRY_DIGEST) def testToAndFromStringWithData(self): entry = gdata.health.ProfileEntryFromString(str(self.profile_entry)) self.assert_(isinstance(entry, gdata.health.ProfileEntry)) self.assert_(isinstance(entry.ccr, gdata.health.Ccr)) self.assertEqual(len(entry.ccr.GetMedications()), 3) self.assertEqual(len(entry.ccr.GetImmunizations()), 1) self.assertEqual(len(entry.ccr.GetAlerts()), 2) self.assertEqual(len(entry.ccr.GetResults()), 1) self.assertEqual(len(entry.ccr.GetProblems()), 2) self.assertEqual(len(entry.ccr.GetProcedures()), 2) def testGetResultsTextFromCcr(self): entry = gdata.health.ProfileEntryFromString(str(self.profile_entry)) result = entry.ccr.GetResults()[0].FindChildren('Test')[0] test_desc = result.FindChildren('Description')[0].FindChildren('Text')[0] self.assertEqual(test_desc.text, 'Acetaldehyde - Blood') def testGetMedicationNameFromCcr(self): entry = gdata.health.ProfileEntryFromString(str(self.profile_entry)) product = entry.ccr.GetMedications()[1].FindChildren('Product')[0] prod_name = product.FindChildren('ProductName')[0].FindChildren('Text')[0] self.assertEqual(prod_name.text, 'A-Fil') def testGetProblemCodeValueFromCcr(self): entry = gdata.health.ProfileEntryFromString(str(self.profile_entry)) problem_desc = entry.ccr.GetProblems()[1].FindChildren('Description')[0] code = problem_desc.FindChildren('Code')[0].FindChildren('Value')[0] self.assertEqual(code.text, '136.9') def testGetGetImmunizationActorIdFromCcr(self): entry = gdata.health.ProfileEntryFromString(str(self.profile_entry)) immun_source = entry.ccr.GetImmunizations()[0].FindChildren('Source')[0] actor_id = immun_source.FindChildren('Actor')[0].FindChildren('ActorID')[0] self.assertEqual(actor_id.text, 'user@gmail.com') def testGetGetProceduresNameFromCcr(self): entry = gdata.health.ProfileEntryFromString(str(self.profile_entry)) proc_desc = entry.ccr.GetProcedures()[1].FindChildren('Description')[0] proc_name = proc_desc.FindChildren('Text')[0] self.assertEqual(proc_name.text, 'Abdominoplasty') def testGetAlertsFromCcr(self): entry = gdata.health.ProfileEntryFromString(str(self.profile_entry)) alert_type = entry.ccr.GetAlerts()[0].FindChildren('Type')[0] self.assertEqual(alert_type.FindChildren('Text')[0].text, 'Allergy') class ProfileListEntryTest(unittest.TestCase): def setUp(self): self.entry = gdata.health.ProfileListEntryFromString( test_data.HEALTH_PROFILE_LIST_ENTRY) def testToAndFromString(self): self.assert_(isinstance(self.entry, gdata.health.ProfileListEntry)) self.assertEqual(self.entry.GetProfileId(), 'vndCn5sdfwdEIY') self.assertEqual(self.entry.GetProfileName(), 'profile name') class ProfileFeedTest(unittest.TestCase): def setUp(self): self.feed = gdata.health.ProfileFeedFromString( test_data.HEALTH_PROFILE_FEED) def testToAndFromString(self): self.assert_(len(self.feed.entry) == 15) for an_entry in self.feed.entry: self.assert_(isinstance(an_entry, gdata.health.ProfileEntry)) new_profile_feed = gdata.health.ProfileFeedFromString(str(self.feed)) for an_entry in new_profile_feed.entry: self.assert_(isinstance(an_entry, gdata.health.ProfileEntry)) def testConvertActualData(self): for an_entry in self.feed.entry: self.assert_(an_entry.ccr is not None) class HealthProfileQueryTest(unittest.TestCase): def testHealthQueryToString(self): query = gdata.health.service.HealthProfileQuery() self.assertEqual(query.ToUri(), '/health/feeds/profile/default') query = gdata.health.service.HealthProfileQuery(feed='feeds/profile') self.assertEqual(query.ToUri(), '/health/feeds/profile/default') query = gdata.health.service.HealthProfileQuery(categories=['medication']) self.assertEqual(query.ToUri(), '/health/feeds/profile/default/-/medication') query = gdata.health.service.HealthProfileQuery(projection='ui', profile_id='12345') self.assertEqual(query.ToUri(), '/health/feeds/profile/ui/12345') query = gdata.health.service.HealthProfileQuery() query.categories.append('medication|condition') self.assertEqual(query.ToUri(), '/health/feeds/profile/default/-/medication%7Ccondition') def testH9QueryToString(self): query = gdata.health.service.HealthProfileQuery(service='h9') self.assertEqual(query.ToUri(), '/h9/feeds/profile/default') query = gdata.health.service.HealthProfileQuery( service='h9', feed='feeds/profile', projection='ui', profile_id='12345') self.assertEqual(query.ToUri(), '/h9/feeds/profile/ui/12345') def testDigestParam(self): query = gdata.health.service.HealthProfileQuery(params={'digest': 'true'}) self.assertEqual(query.ToUri(), '/health/feeds/profile/default?digest=true') query.profile_id = '12345' query.projection = 'ui' self.assertEqual( query.ToUri(), '/health/feeds/profile/ui/12345?digest=true') class HealthProfileListQueryTest(unittest.TestCase): def testHealthProfileListQueryToString(self): query = gdata.health.service.HealthProfileListQuery() self.assertEqual(query.ToUri(), '/health/feeds/profile/list') query = gdata.health.service.HealthProfileListQuery(service='health') self.assertEqual(query.ToUri(), '/health/feeds/profile/list') query = gdata.health.service.HealthProfileListQuery( feed='feeds/profile/list') self.assertEqual(query.ToUri(), '/health/feeds/profile/list') query = gdata.health.service.HealthProfileListQuery( service='health', feed='feeds/profile/list') self.assertEqual(query.ToUri(), '/health/feeds/profile/list') def testH9ProfileListQueryToString(self): query = gdata.health.service.HealthProfileListQuery(service='h9') self.assertEqual(query.ToUri(), '/h9/feeds/profile/list') query = gdata.health.service.HealthProfileListQuery( service='h9', feed='feeds/profile/list') self.assertEqual(query.ToUri(), '/h9/feeds/profile/list') if __name__ == '__main__': unittest.main() gdata-2.0.18/tests/gdata_tests/sites/0000750036466300116100000000000012156625015017525 5ustar afshareng00000000000000gdata-2.0.18/tests/gdata_tests/sites/data_test.py0000750036466300116100000002737412156622363022072 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright 2009 Google Inc. All Rights Reserved. # # 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. __author__ = 'e.bidelman (Eric Bidelman)' import unittest import atom from gdata import test_data import gdata.acl.data import gdata.data import gdata.sites.data import gdata.test_config as conf def parse(xml_string, target_class): """Convenience wrapper for converting an XML string to an XmlElement.""" return atom.core.xml_element_from_string(xml_string, target_class) class CommentEntryTest(unittest.TestCase): def setUp(self): self.entry = parse(test_data.SITES_COMMENT_ENTRY, gdata.sites.data.ContentEntry) def testToAndFromStringCommentEntry(self): self.assertEqual(self.entry.Kind(), 'comment') self.assert_(isinstance(self.entry.in_reply_to, gdata.sites.data.InReplyTo)) self.assertEqual(self.entry.in_reply_to.type, 'text/html') self.assertEqual( self.entry.FindParentLink(), 'http://sites.google.com/feeds/content/site/gdatatestsite/abc123parent') self.assertEqual( self.entry.in_reply_to.href, 'http://sites.google.com/site/gdatatestsite/annoucment/testpost') self.assertEqual( self.entry.in_reply_to.ref, 'http://sites.google.com/feeds/content/site/gdatatestsite/abc123') self.assertEqual( self.entry.in_reply_to.source, 'http://sites.google.com/feeds/content/site/gdatatestsite') class ListPageEntryTest(unittest.TestCase): def setUp(self): self.entry = parse(test_data.SITES_LISTPAGE_ENTRY, gdata.sites.data.ContentEntry) def testToAndFromStringWithData(self): self.assert_(isinstance(self.entry, gdata.sites.data.ContentEntry)) self.assertEqual(self.entry.title.text, 'ListPagesTitle') self.assertEqual(len(self.entry.author), 1) self.assertEqual(self.entry.author[0].name.text, 'Test User') self.assertEqual(self.entry.author[0].email.text, 'test@gmail.com') self.assertEqual(self.entry.worksheet.name, 'listpage') self.assertEqual(self.entry.header.row, '1') self.assertEqual(self.entry.data.startRow, '2') self.assertEqual(len(self.entry.data.column), 5) self.assert_(isinstance(self.entry.data.column[0], gdata.sites.data.Column)) self.assertEqual(self.entry.data.column[0].index, 'A') self.assertEqual(self.entry.data.column[0].name, 'Owner') self.assert_(isinstance(self.entry.feed_link, gdata.data.FeedLink)) self.assertEqual( self.entry.feed_link.href, 'http:///sites.google.com/feeds/content/site/gdatatestsite?parent=abc') self.assert_(isinstance(self.entry.content, gdata.sites.data.Content)) self.assert_(isinstance(self.entry.content.html, atom.core.XmlElement)) self.assertEqual(self.entry.content.type, 'xhtml') class ListItemEntryTest(unittest.TestCase): def setUp(self): self.entry = parse(test_data.SITES_LISTITEM_ENTRY, gdata.sites.data.ContentEntry) def testToAndFromStringWithData(self): self.assert_(isinstance(self.entry, gdata.sites.data.ContentEntry)) self.assertEqual(len(self.entry.field), 5) self.assert_(isinstance(self.entry.field[0], gdata.sites.data.Field)) self.assertEqual(self.entry.field[0].index, 'A') self.assertEqual(self.entry.field[0].name, 'Owner') self.assertEqual(self.entry.field[0].text, 'test value') self.assertEqual( self.entry.FindParentLink(), 'http://sites.google.com/feeds/content/site/gdatatestsite/abc123def') class BaseSiteEntryTest(unittest.TestCase): def testCreateBaseSiteEntry(self): entry = gdata.sites.data.BaseSiteEntry() parent_link = atom.data.Link( rel=gdata.sites.data.SITES_PARENT_LINK_REL, href='abc') entry.link.append(parent_link) entry.category.append( atom.data.Category( scheme=gdata.sites.data.SITES_KIND_SCHEME, term='%s#%s' % (gdata.sites.data.SITES_NAMESPACE, 'webpage'), label='webpage')) self.assertEqual(entry.Kind(), 'webpage') self.assertEqual(entry.category[0].label, 'webpage') self.assertEqual( entry.category[0].term, '%s#%s' % ('http://schemas.google.com/sites/2008', 'webpage')) self.assertEqual(entry.link[0].href, 'abc') self.assertEqual(entry.link[0].rel, 'http://schemas.google.com/sites/2008#parent') entry2 = gdata.sites.data.BaseSiteEntry(kind='webpage') self.assertEqual( entry2.category[0].term, '%s#%s' % ('http://schemas.google.com/sites/2008', 'webpage')) class ContentFeedTest(unittest.TestCase): def setUp(self): self.feed = parse(test_data.SITES_CONTENT_FEED, gdata.sites.data.ContentFeed) def testToAndFromStringContentFeed(self): self.assert_(isinstance(self.feed, gdata.sites.data.ContentFeed)) self.assertEqual(len(self.feed.entry), 8) self.assert_(isinstance(self.feed.entry[0].revision, gdata.sites.data.Revision)) self.assertEqual(int(self.feed.entry[0].revision.text), 2) self.assertEqual(self.feed.entry[0].GetNodeId(), '1712987567114738703') self.assert_(isinstance(self.feed.entry[0].page_name, gdata.sites.data.PageName)) self.assertEqual(self.feed.entry[0].page_name.text, 'home') self.assertEqual(self.feed.entry[0].FindRevisionLink(), 'http:///sites.google.com/feeds/content/site/gdatatestsite/12345') for entry in self.feed.entry: self.assert_(isinstance(entry, gdata.sites.data.ContentEntry)) if entry.deleted is not None: self.assert_(isinstance(entry.deleted, gdata.sites.data.Deleted)) self.assertEqual(entry.IsDeleted(), True) else: self.assertEqual(entry.IsDeleted(), False) def testCreateContentEntry(self): new_entry = gdata.sites.data.ContentEntry() new_entry.content = gdata.sites.data.Content() new_entry.content.html = '

    here is html

    ' self.assert_(isinstance(new_entry, gdata.sites.data.ContentEntry)) self.assert_(isinstance(new_entry.content, gdata.sites.data.Content)) self.assert_(isinstance(new_entry.content.html, atom.core.XmlElement)) new_entry2 = gdata.sites.data.ContentEntry() new_entry2.content = gdata.sites.data.Content( html='

    here is html

    ') self.assert_(isinstance(new_entry2, gdata.sites.data.ContentEntry)) self.assert_(isinstance(new_entry2.content, gdata.sites.data.Content)) self.assert_(isinstance(new_entry2.content.html, atom.core.XmlElement)) def testGetHelpers(self): kinds = {'announcement': self.feed.GetAnnouncements, 'announcementspage': self.feed.GetAnnouncementPages, 'attachment': self.feed.GetAttachments, 'comment': self.feed.GetComments, 'filecabinet': self.feed.GetFileCabinets, 'listitem': self.feed.GetListItems, 'listpage': self.feed.GetListPages, 'webpage': self.feed.GetWebpages} for k, v in kinds.iteritems(): entries = v() self.assertEqual(len(entries), 1) for entry in entries: self.assertEqual(entry.Kind(), k) if k == 'attachment': self.assertEqual(entry.GetAlternateLink().href, 'http://sites.google.com/feeds/SOMELONGURL') class ActivityFeedTest(unittest.TestCase): def setUp(self): self.feed = parse(test_data.SITES_ACTIVITY_FEED, gdata.sites.data.ActivityFeed) def testToAndFromStringActivityFeed(self): self.assert_(isinstance(self.feed, gdata.sites.data.ActivityFeed)) self.assertEqual(len(self.feed.entry), 2) for entry in self.feed.entry: self.assert_(isinstance(entry.summary, gdata.sites.data.Summary)) self.assertEqual(entry.summary.type, 'xhtml') self.assert_(isinstance(entry.summary.html, atom.core.XmlElement)) class RevisionFeedTest(unittest.TestCase): def setUp(self): self.feed = parse(test_data.SITES_REVISION_FEED, gdata.sites.data.RevisionFeed) def testToAndFromStringRevisionFeed(self): self.assert_(isinstance(self.feed, gdata.sites.data.RevisionFeed)) self.assertEqual(len(self.feed.entry), 1) entry = self.feed.entry[0] self.assert_(isinstance(entry.content, gdata.sites.data.Content)) self.assert_(isinstance(entry.content.html, atom.core.XmlElement)) self.assertEqual(entry.content.type, 'xhtml') self.assertEqual( entry.FindParentLink(), 'http://sites.google.com/feeds/content/site/siteName/54395424125706119') class SiteFeedTest(unittest.TestCase): def setUp(self): self.feed = parse(test_data.SITES_SITE_FEED, gdata.sites.data.SiteFeed) def testToAndFromStringSiteFeed(self): self.assert_(isinstance(self.feed, gdata.sites.data.SiteFeed)) self.assertEqual(len(self.feed.entry), 2) entry = self.feed.entry[0] self.assert_(isinstance(entry.site_name, gdata.sites.data.SiteName)) self.assertEqual(entry.title.text, 'New Test Site') self.assertEqual(entry.site_name.text, 'new-test-site') self.assertEqual( entry.FindAclLink(), 'http://sites.google.com/feeds/acl/site/example.com/new-test-site') self.assertEqual( entry.FindSourceLink(), 'http://sites.google.com/feeds/site/example.com/source-site') self.assertEqual(entry.theme.text, 'iceberg') class AclFeedTest(unittest.TestCase): def setUp(self): self.feed = parse(test_data.SITES_ACL_FEED, gdata.sites.data.AclFeed) def testToAndFromStringAclFeed(self): self.assert_(isinstance(self.feed, gdata.sites.data.AclFeed)) self.assertEqual(len(self.feed.entry), 1) entry = self.feed.entry[0] self.assert_(isinstance(entry, gdata.sites.data.AclEntry)) self.assert_(isinstance(entry.scope, gdata.acl.data.AclScope)) self.assertEqual(entry.scope.type, 'user') self.assertEqual(entry.scope.value, 'user@example.com') self.assert_(isinstance(entry.role, gdata.acl.data.AclRole)) self.assertEqual(entry.role.value, 'owner') self.assertEqual( entry.GetSelfLink().href, ('https://sites.google.com/feeds/acl/site/example.com/' 'new-test-site/user%3Auser%40example.com')) class DataClassSanityTest(unittest.TestCase): def test_basic_element_structure(self): conf.check_data_classes(self, [ gdata.sites.data.Revision, gdata.sites.data.PageName, gdata.sites.data.Deleted, gdata.sites.data.Publisher, gdata.sites.data.Worksheet, gdata.sites.data.Header, gdata.sites.data.Column, gdata.sites.data.Data, gdata.sites.data.Field, gdata.sites.data.InReplyTo, gdata.sites.data.BaseSiteEntry, gdata.sites.data.ContentEntry, gdata.sites.data.ContentFeed, gdata.sites.data.ActivityEntry, gdata.sites.data.ActivityFeed, gdata.sites.data.RevisionEntry, gdata.sites.data.RevisionFeed, gdata.sites.data.Content, gdata.sites.data.Summary, gdata.sites.data.SiteName, gdata.sites.data.SiteEntry, gdata.sites.data.SiteFeed, gdata.sites.data.AclEntry, gdata.sites.data.AclFeed, gdata.sites.data.Theme]) def suite(): return conf.build_suite([ CommentEntryTest, ListPageEntryTest, ListItemEntryTest, BaseSiteEntryTest, ContentFeedTest, ActivityFeedTest, RevisionFeedTest, SiteFeedTest, AclFeedTest, DataClassSanityTest]) if __name__ == '__main__': unittest.main() gdata-2.0.18/tests/gdata_tests/sites/__init__.py0000640036466300116100000000000012156622363021630 0ustar afshareng00000000000000gdata-2.0.18/tests/gdata_tests/sites/live_client_test.py0000750036466300116100000000764212156622363023452 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright 2009 Google Inc. All Rights Reserved. # # 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. # This module is used for version 2 of the Google Data APIs. # These tests attempt to connect to Google servers. __author__ = 'e.bidelman (Eric Bidelman)' import unittest import gdata.client import gdata.data import gdata.gauth import gdata.sites.client import gdata.sites.data import gdata.test_config as conf conf.options.register_option(conf.TEST_IMAGE_LOCATION_OPTION) conf.options.register_option(conf.APPS_DOMAIN_OPTION) conf.options.register_option(conf.SITES_NAME_OPTION) class SitesClientTest(unittest.TestCase): def setUp(self): self.client = None if conf.options.get_value('runlive') == 'true': self.client = gdata.sites.client.SitesClient( site=conf.options.get_value('sitename'), domain=conf.options.get_value('appsdomain')) if conf.options.get_value('ssl') == 'true': self.client.ssl = True conf.configure_client(self.client, 'SitesTest', self.client.auth_service, True) def tearDown(self): conf.close_client(self.client) def testCreateUpdateDelete(self): if not conf.options.get_value('runlive') == 'true': return # Either load the recording or prepare to make a live request. conf.configure_cache(self.client, 'testCreateUpdateDelete') new_entry = self.client.CreatePage( 'webpage', 'Title Of Page', 'Your html content') self.assertEqual(new_entry.title.text, 'Title Of Page') self.assertEqual(new_entry.page_name.text, 'title-of-page') self.assert_(new_entry.GetAlternateLink().href is not None) self.assertEqual(new_entry.Kind(), 'webpage') # Change the title of the webpage we just added. new_entry.title.text = 'Edited' updated_entry = self.client.update(new_entry) self.assertEqual(updated_entry.title.text, 'Edited') self.assertEqual(updated_entry.page_name.text, 'title-of-page') self.assert_(isinstance(updated_entry, gdata.sites.data.ContentEntry)) # Delete the test webpage from the Site. self.client.delete(updated_entry) def testCreateAndUploadToFilecabinet(self): if not conf.options.get_value('runlive') == 'true': return # Either load the recording or prepare to make a live request. conf.configure_cache(self.client, 'testCreateAndUploadToFilecabinet') filecabinet = self.client.CreatePage( 'filecabinet', 'FilesGoHere', 'Your html content', page_name='diff-pagename-than-title') self.assertEqual(filecabinet.title.text, 'FilesGoHere') self.assertEqual(filecabinet.page_name.text, 'diff-pagename-than-title') self.assert_(filecabinet.GetAlternateLink().href is not None) self.assertEqual(filecabinet.Kind(), 'filecabinet') # Upload a file to the filecabinet filepath = conf.options.get_value('imgpath') attachment = self.client.UploadAttachment( filepath, filecabinet, content_type='image/jpeg', title='TestImageFile', description='description here') self.assertEqual(attachment.title.text, 'TestImageFile') self.assertEqual(attachment.FindParentLink(), filecabinet.GetSelfLink().href) # Delete the test filecabinet and attachment from the Site. self.client.delete(attachment) self.client.delete(filecabinet) def suite(): return conf.build_suite([SitesClientTest]) if __name__ == '__main__': unittest.TextTestRunner().run(suite()) gdata-2.0.18/tests/gdata_tests/photos_test.py0000750036466300116100000000447012156622363021336 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright (C) 2006 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. __author__ = 'api.jscudder (Jeffrey Scudder)' import unittest from gdata import test_data import gdata.photos class AlbumFeedTest(unittest.TestCase): def setUp(self): self.album_feed = gdata.photos.AlbumFeedFromString(test_data.ALBUM_FEED) def testCorrectXmlParsing(self): self.assert_(self.album_feed.id.text == 'http://picasaweb.google.com/data/feed/api/user/sample.user/albumid/1') self.assert_(self.album_feed.gphoto_id.text == '1') self.assert_(len(self.album_feed.entry) == 4) for entry in self.album_feed.entry: if entry.id.text == 'http://picasaweb.google.com/data/entry/api/user/sample.user/albumid/1/photoid/2': self.assert_(entry.summary.text == 'Blue') class PhotoFeedTest(unittest.TestCase): def setUp(self): self.feed = gdata.photos.PhotoFeedFromString(test_data.ALBUM_FEED) def testCorrectXmlParsing(self): for entry in self.feed.entry: if entry.id.text == 'http://picasaweb.google.com/data/entry/api/user/sample.user/albumid/1/photoid/2': self.assert_(entry.gphoto_id.text == '2') self.assert_(entry.albumid.text == '1') self.assert_(entry.exif.flash.text == 'true') self.assert_(entry.media.title.type == 'plain') self.assert_(entry.media.title.text == 'Aqua Blue.jpg') self.assert_(len(entry.media.thumbnail) == 3) class AnyFeedTest(unittest.TestCase): def setUp(self): self.feed = gdata.photos.AnyFeedFromString(test_data.ALBUM_FEED) def testEntryTypeConversion(self): for entry in self.feed.entry: if entry.id.text == 'http://picasaweb.google.com/data/feed/api/user/sample.user/albumid/': self.assert_(isinstance(entry, gdata.photos.PhotoEntry)) if __name__ == '__main__': unittest.main() gdata-2.0.18/tests/gdata_tests/apps/0000750036466300116100000000000012156625015017341 5ustar afshareng00000000000000gdata-2.0.18/tests/gdata_tests/apps/migration/0000750036466300116100000000000012156625015021332 5ustar afshareng00000000000000gdata-2.0.18/tests/gdata_tests/apps/migration/service_test.py0000750036466300116100000000437312156622363024420 0ustar afshareng00000000000000#!/usr/bin/python2.4 # # Copyright 2008 Google Inc. All Rights Reserved. # # 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. """Test for Email Migration service.""" __author__ = 'google-apps-apis@googlegroups.com' import getpass import unittest import gdata.apps.migration.service domain = '' admin_email = '' admin_password = '' username = '' MESSAGE = """From: joe@blow.com To: jane@doe.com Date: Mon, 29 Sep 2008 20:00:34 -0500 (CDT) Subject: %s %s""" class MigrationTest(unittest.TestCase): """Test for the MigrationService.""" def setUp(self): self.ms = gdata.apps.migration.service.MigrationService( email=admin_email, password=admin_password, domain=domain) self.ms.ProgrammaticLogin() def testImportMail(self): self.ms.ImportMail(user_name=username, mail_message=MESSAGE % ('Test subject', 'Test body'), mail_item_properties=['IS_STARRED'], mail_labels=['Test']) def testImportMultipleMails(self): for i in xrange(1, 10): self.ms.AddMailEntry(mail_message=MESSAGE % ('Test thread %d' % i, 'Test thread'), mail_item_properties=['IS_UNREAD'], mail_labels=['Test', 'Thread'], identifier=str(i)) self.ms.ImportMultipleMails(user_name=username) if __name__ == '__main__': print("Google Apps Email Migration Service Tests\n\n" "NOTE: Please run these tests only with a test user account.\n") domain = raw_input('Google Apps domain: ') admin_email = '%s@%s' % (raw_input('Administrator username: '), domain) admin_password = getpass.getpass('Administrator password: ') username = raw_input('Test username: ') unittest.main() gdata-2.0.18/tests/gdata_tests/apps/migration/__init__.py0000640036466300116100000000000012156622363023435 0ustar afshareng00000000000000gdata-2.0.18/tests/gdata_tests/apps/organization/0000750036466300116100000000000012156625015022045 5ustar afshareng00000000000000gdata-2.0.18/tests/gdata_tests/apps/organization/service_test.py0000640036466300116100000003615712156622363025136 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright (C) 2008 Google # # 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. """Test for Organization service.""" __author__ = 'Alexandre Vivien (alex@simplecode.fr)' import urllib import unittest try: from xml.etree import ElementTree except ImportError: from elementtree import ElementTree import atom import gdata.apps import gdata.apps.service import gdata.apps.organization.service import getpass import time domain = '' admin_email = '' admin_password = '' username = '' class OrganizationTest(unittest.TestCase): """Test for the OrganizationService.""" def setUp(self): self.postfix = time.strftime("%Y%m%d%H%M%S") self.apps_client = gdata.apps.service.AppsService( email=admin_email, domain=domain, password=admin_password, source='OrganizationClient "Unit" Tests') self.apps_client.ProgrammaticLogin() self.organization_client = gdata.apps.organization.service.OrganizationService( email=admin_email, domain=domain, password=admin_password, source='GroupsClient "Unit" Tests') self.organization_client.ProgrammaticLogin() self.created_users = [] self.created_org_units = [] self.cutomer_id = None self.createUsers(); def createUsers(self): user_name = 'yujimatsuo-' + self.postfix family_name = 'Matsuo' given_name = 'Yuji' password = '123$$abc' suspended = 'false' try: self.user_yuji = self.apps_client.CreateUser(user_name=user_name, family_name=family_name, given_name=given_name, password=password, suspended=suspended) print 'User ' + user_name + ' created' except Exception, e: self.fail('Unexpected exception occurred: %s' % e) self.created_users.append(self.user_yuji) user_name = 'taromatsuo-' + self.postfix family_name = 'Matsuo' given_name = 'Taro' password = '123$$abc' suspended = 'false' try: self.user_taro = self.apps_client.CreateUser(user_name=user_name, family_name=family_name, given_name=given_name, password=password, suspended=suspended) print 'User ' + user_name + ' created' except Exception, e: self.fail('Unexpected exception occurred: %s' % e) self.created_users.append(self.user_taro) user_name = 'alexandrevivien-' + self.postfix family_name = 'Vivien' given_name = 'Alexandre' password = '123$$abc' suspended = 'false' try: self.user_alex = self.apps_client.CreateUser(user_name=user_name, family_name=family_name, given_name=given_name, password=password, suspended=suspended) print 'User ' + user_name + ' created' except Exception, e: self.fail('Unexpected exception occurred: %s' % e) self.created_users.append(self.user_alex) def tearDown(self): print '\n' for user in self.created_users: try: self.apps_client.DeleteUser(user.login.user_name) print 'User ' + user.login.user_name + ' deleted' except Exception, e: print e # We reverse to delete sub OrgUnit first self.created_org_units.reverse() for org_unit_path in self.created_org_units: try: self.organization_client.DeleteOrgUnit(self.customer_id, org_unit_path) print 'OrgUnit ' + org_unit_path + ' deleted' except Exception, e: print e def testOrganizationService(self): # tests RetrieveCustomerId method try: customer = self.organization_client.RetrieveCustomerId() self.customer_id = customer['customerId'] except Exception, e: self.fail('Unexpected exception occurred: %s' % e) print 'tests RetrieveCustomerId successful' # tests CreateOrgUnit method orgUnit01_name = 'OrgUnit01-' + self.postfix orgUnit02_name = 'OrgUnit02-' + self.postfix sub0rgUnit01_name = 'SubOrgUnit01-' + self.postfix orgUnit03_name = 'OrgUnit03-' + self.postfix try: orgUnit01 = self.organization_client.CreateOrgUnit(self.customer_id, name=orgUnit01_name, parent_org_unit_path='/', description='OrgUnit Test 01', block_inheritance=False) orgUnit02 = self.organization_client.CreateOrgUnit(self.customer_id, name=orgUnit02_name, parent_org_unit_path='/', description='OrgUnit Test 02', block_inheritance=False) sub0rgUnit01 = self.organization_client.CreateOrgUnit(self.customer_id, name=sub0rgUnit01_name, parent_org_unit_path=orgUnit02['orgUnitPath'], description='SubOrgUnit Test 01', block_inheritance=False) orgUnit03 = self.organization_client.CreateOrgUnit(self.customer_id, name=orgUnit03_name, parent_org_unit_path='/', description='OrgUnit Test 03', block_inheritance=False) except Exception, e: self.fail('Unexpected exception occurred: %s' % e) self.assertEquals(orgUnit01['orgUnitPath'], urllib.quote_plus(orgUnit01_name)) self.assertEquals(orgUnit02['orgUnitPath'], urllib.quote_plus(orgUnit02_name)) self.assertEquals(sub0rgUnit01['orgUnitPath'], urllib.quote_plus(orgUnit02_name) + '/' + urllib.quote_plus(sub0rgUnit01_name)) self.assertEquals(orgUnit03['orgUnitPath'], urllib.quote_plus(orgUnit03_name)) self.created_org_units.append(orgUnit01['orgUnitPath']) self.created_org_units.append(orgUnit02['orgUnitPath']) self.created_org_units.append(sub0rgUnit01['orgUnitPath']) self.created_org_units.append(orgUnit03['orgUnitPath']) print 'tests CreateOrgUnit successful' # tests UpdateOrgUnit method try: updated_orgunit = self.organization_client.UpdateOrgUnit(self.customer_id, org_unit_path=self.created_org_units[3], description='OrgUnit Test 03 Updated description') except Exception, e: self.fail('Unexpected exception occurred: %s' % e) self.assertEquals(updated_orgunit['orgUnitPath'], self.created_org_units[3]) print 'tests UpdateOrgUnit successful' # tests RetrieveOrgUnit method try: retrieved_orgunit = self.organization_client.RetrieveOrgUnit(self.customer_id, org_unit_path=self.created_org_units[1]) retrieved_suborgunit = self.organization_client.RetrieveOrgUnit(self.customer_id, org_unit_path=self.created_org_units[2]) except Exception, e: self.fail('Unexpected exception occurred: %s' % e) self.assertEquals(retrieved_orgunit['orgUnitPath'], self.created_org_units[1]) self.assertEquals(retrieved_suborgunit['orgUnitPath'], self.created_org_units[2]) print 'tests RetrieveOrgUnit successful' # tests RetrieveAllOrgUnits method try: retrieved_orgunits = self.organization_client.RetrieveAllOrgUnits(self.customer_id) except Exception, e: self.fail('Unexpected exception occurred: %s' % e) self.assertTrue(len(retrieved_orgunits) >= len(self.created_org_units)) print 'tests RetrieveAllOrgUnits successful' # tests MoveUserToOrgUnit method try: updated_orgunit01 = self.organization_client.MoveUserToOrgUnit(self.customer_id, org_unit_path=self.created_org_units[0], users_to_move=[self.user_yuji.login.user_name + '@' + domain]) updated_orgunit02 = self.organization_client.MoveUserToOrgUnit(self.customer_id, org_unit_path=self.created_org_units[1], users_to_move=[self.user_taro.login.user_name + '@' + domain, self.user_alex.login.user_name + '@' + domain]) except Exception, e: self.fail('Unexpected exception occurred: %s' % e) self.assertEquals(updated_orgunit01['usersMoved'], self.user_yuji.login.user_name + '@' + domain) self.assertEquals(updated_orgunit02['usersMoved'], self.user_taro.login.user_name + '@' + domain + ',' + \ self.user_alex.login.user_name + '@' + domain) print 'tests MoveUserToOrgUnit successful' # tests RetrieveSubOrgUnits method try: retrieved_suborgunits = self.organization_client.RetrieveSubOrgUnits(self.customer_id, org_unit_path=self.created_org_units[1]) except Exception, e: self.fail('Unexpected exception occurred: %s' % e) self.assertEquals(len(retrieved_suborgunits), 1) self.assertEquals(retrieved_suborgunits[0]['orgUnitPath'], self.created_org_units[2]) print 'tests RetrieveSubOrgUnits successful' # tests RetrieveOrgUser method try: retrieved_orguser = self.organization_client.RetrieveOrgUser(self.customer_id, user_email=self.user_yuji.login.user_name + '@' + domain) except Exception, e: self.fail('Unexpected exception occurred: %s' % e) self.assertEquals(retrieved_orguser['orgUserEmail'], self.user_yuji.login.user_name + '@' + domain) self.assertEquals(retrieved_orguser['orgUnitPath'], self.created_org_units[0]) print 'tests RetrieveOrgUser successful' # tests UpdateOrgUser method try: updated_orguser = self.organization_client.UpdateOrgUser(self.customer_id, org_unit_path=self.created_org_units[0], user_email=self.user_alex.login.user_name + '@' + domain) except Exception, e: self.fail('Unexpected exception occurred: %s' % e) self.assertEquals(updated_orguser['orgUserEmail'], self.user_alex.login.user_name + '@' + domain) self.assertEquals(updated_orguser['orgUnitPath'], self.created_org_units[0]) print 'tests UpdateOrgUser successful' # tests RetrieveAllOrgUsers method try: retrieved_orgusers = self.organization_client.RetrieveAllOrgUsers(self.customer_id) except Exception, e: self.fail('Unexpected exception occurred: %s' % e) self.assertTrue(len(retrieved_orgusers) >= len(self.created_users)) print 'tests RetrieveAllOrgUsers successful' """ This test needs to create more than 100 test users # tests RetrievePageOfOrgUsers method try: retrieved_orgusers01_feed = self.organization_client.RetrievePageOfOrgUsers(self.customer_id) next = retrieved_orgusers01_feed.GetNextLink() self.assertNotEquals(next, None) startKey = next.href.split("startKey=")[1] retrieved_orgusers02_feed = self.organization_client.RetrievePageOfOrgUsers(self.customer_id, startKey=startKey) retrieved_orgusers01_entry = self.organization_client._PropertyEntry2Dict(retrieved_orgusers01_feed.entry[0]) retrieved_orgusers02_entry = self.organization_client._PropertyEntry2Dict(retrieved_orgusers02_feed.entry[0]) self.assertNotEquals(retrieved_orgusers01_entry['orgUserEmail'], retrieved_orgusers02_entry['orgUserEmail']) except Exception, e: self.fail('Unexpected exception occurred: %s' % e) print 'tests RetrievePageOfOrgUsers successful' """ # tests RetrieveOrgUnitUsers method try: retrieved_orgusers = self.organization_client.RetrieveOrgUnitUsers(self.customer_id, org_unit_path=self.created_org_units[0]) except Exception, e: self.fail('Unexpected exception occurred: %s' % e) self.assertEquals(len(retrieved_orgusers), 2) print 'tests RetrieveOrgUnitUsers successful' """ This test needs to create more than 100 test users # tests RetrieveOrgUnitPageOfUsers method try: retrieved_orgusers01_feed = self.organization_client.RetrieveOrgUnitPageOfUsers(self.customer_id, org_unit_path='/') next = retrieved_orgusers01_feed.GetNextLink() self.assertNotEquals(next, None) startKey = next.href.split("startKey=")[1] retrieved_orgusers02_feed = self.organization_client.RetrieveOrgUnitPageOfUsers(self.customer_id, org_unit_path='/', startKey=startKey) retrieved_orgusers01_entry = self.organization_client._PropertyEntry2Dict(retrieved_orgusers01_feed.entry[0]) retrieved_orgusers02_entry = self.organization_client._PropertyEntry2Dict(retrieved_orgusers02_feed.entry[0]) self.assertNotEquals(retrieved_orgusers01_entry['orgUserEmail'], retrieved_orgusers02_entry['orgUserEmail']) except Exception, e: self.fail('Unexpected exception occurred: %s' % e) print 'tests RetrieveOrgUnitPageOfUsers successful' """ if __name__ == '__main__': print("""Google Apps Groups Service Tests NOTE: Please run these tests only with a test user account. """) domain = raw_input('Google Apps domain: ') admin_email = '%s@%s' % (raw_input('Administrator username: '), domain) admin_password = getpass.getpass('Administrator password: ') unittest.main() gdata-2.0.18/tests/gdata_tests/apps/organization/data_test.py0000640036466300116100000001331112156622363024372 0ustar afshareng00000000000000#!/usr/bin/python2.4 # # Copyright 2011 Google Inc. All Rights Reserved. # # 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. """Data model tests for the Organization Unit Provisioning API.""" __author__ = 'Gunjan Sharma ' import unittest import atom.core from gdata import test_data import gdata.apps.organization.data import gdata.test_config as conf class CustomerIdEntryTest(unittest.TestCase): def setUp(self): self.entry = atom.core.parse(test_data.ORGANIZATION_UNIT_CUSTOMER_ID_ENTRY, gdata.apps.organization.data.CustomerIdEntry) def testCustomerIdEntryFromString(self): self.assert_(isinstance(self.entry, gdata.apps.organization.data.CustomerIdEntry)) self.assertEquals(self.entry.customer_id, 'C123A456B') self.assertEquals(self.entry.customer_org_unit_name, 'example.com') self.assertEquals(self.entry.customer_org_unit_description, 'example.com') self.assertEquals(self.entry.org_unit_name, 'example.com') self.assertEquals(self.entry.org_unit_description, 'tempdescription') class OrgUnitEntryTest(unittest.TestCase): def setUp(self): self.entry = atom.core.parse(test_data.ORGANIZATION_UNIT_ORGUNIT_ENTRY, gdata.apps.organization.data.OrgUnitEntry) self.feed = atom.core.parse(test_data.ORGANIZATION_UNIT_ORGUNIT_FEED, gdata.apps.organization.data.OrgUnitFeed) def testOrgUnitEntryFromString(self): self.assert_(isinstance(self.entry, gdata.apps.organization.data.OrgUnitEntry)) self.assertEquals(self.entry.org_unit_description, 'New Test Org') self.assertEquals(self.entry.org_unit_name, 'Test Organization') self.assertEquals(self.entry.org_unit_path, 'Test/Test+Organization') self.assertEquals(self.entry.parent_org_unit_path, 'Test') self.assertEquals(self.entry.org_unit_block_inheritance, 'false') def testOrgUnitFeedFromString(self): self.assertEquals(len(self.feed.entry), 2) self.assert_(isinstance(self.feed, gdata.apps.organization.data.OrgUnitFeed)) self.assert_(isinstance(self.feed.entry[0], gdata.apps.organization.data.OrgUnitEntry)) self.assert_(isinstance(self.feed.entry[1], gdata.apps.organization.data.OrgUnitEntry)) self.assertEquals( self.feed.entry[0].find_edit_link(), ('https://apps-apis.google.com/a/feeds/orgunit/2.0/' 'C123A456B/testOrgUnit92')) self.assertEquals(self.feed.entry[0].org_unit_description, 'test92') self.assertEquals(self.feed.entry[0].org_unit_name, 'testOrgUnit92') self.assertEquals(self.feed.entry[0].org_unit_path, 'Test/testOrgUnit92') self.assertEquals(self.feed.entry[0].parent_org_unit_path, 'Test') self.assertEquals(self.feed.entry[0].org_unit_block_inheritance, 'false') self.assertEquals( self.feed.entry[1].find_edit_link(), ('https://apps-apis.google.com/a/feeds/orgunit/2.0/' 'C123A456B/testOrgUnit93')) self.assertEquals(self.feed.entry[1].org_unit_description, 'test93') self.assertEquals(self.feed.entry[1].org_unit_name, 'testOrgUnit93') self.assertEquals(self.feed.entry[1].org_unit_path, 'Test/testOrgUnit93') self.assertEquals(self.feed.entry[1].parent_org_unit_path, 'Test') self.assertEquals(self.feed.entry[1].org_unit_block_inheritance, 'false') class OrgUserEntryTest(unittest.TestCase): def setUp(self): self.entry = atom.core.parse(test_data.ORGANIZATION_UNIT_ORGUSER_ENTRY, gdata.apps.organization.data.OrgUserEntry) self.feed = atom.core.parse(test_data.ORGANIZATION_UNIT_ORGUSER_FEED, gdata.apps.organization.data.OrgUserFeed) def testOrgUserEntryFromString(self): self.assert_(isinstance(self.entry, gdata.apps.organization.data.OrgUserEntry)) self.assertEquals(self.entry.user_email, 'admin@example.com') self.assertEquals(self.entry.org_unit_path, 'Test') def testOrgUserFeedFromString(self): self.assertEquals(len(self.feed.entry), 2) self.assert_(isinstance(self.feed, gdata.apps.organization.data.OrgUserFeed)) self.assert_(isinstance(self.feed.entry[0], gdata.apps.organization.data.OrgUserEntry)) self.assert_(isinstance(self.feed.entry[1], gdata.apps.organization.data.OrgUserEntry)) self.assertEquals( self.feed.entry[0].find_edit_link(), ('https://apps-apis.google.com/a/feeds/orguser/2.0/' 'C123A456B/user720430%40example.com')) self.assertEquals(self.feed.entry[0].user_email, 'user720430@example.com') self.assertEquals(self.feed.entry[0].org_unit_path, 'Test') self.assertEquals( self.feed.entry[1].find_edit_link(), ('https://apps-apis.google.com/a/feeds/orguser/2.0/' 'C123A456B/user832648%40example.com')) self.assertEquals(self.feed.entry[1].user_email, 'user832648@example.com') self.assertEquals(self.feed.entry[1].org_unit_path, 'Test') def suite(): return conf.build_suite([OrgUnitEntryTest, OrgUserEntryTest]) if __name__ == '__main__': unittest.main() gdata-2.0.18/tests/gdata_tests/apps/organization/__init__.py0000640036466300116100000000000012156622363024150 0ustar afshareng00000000000000gdata-2.0.18/tests/gdata_tests/apps/organization/live_client_test.py0000640036466300116100000001323212156622363025760 0ustar afshareng00000000000000#!/usr/bin/python2.4 # # Copyright 2011 Google Inc. All Rights Reserved. # # 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. """Live client tests for the Organization Unit Provisioning API.""" # This module is used for version 2 of the Google Data APIs. # These tests attempt to connect to Google servers. __author__ = 'Gunjan Sharma ' import random import unittest import gdata.apps.organization.client import gdata.apps.organization.data import gdata.client import gdata.data import gdata.gauth import gdata.test_config as conf conf.options.register_option(conf.APPS_DOMAIN_OPTION) class OrganizationUnitProvisioningClientTest(unittest.TestCase): def setUp(self): self.client = gdata.apps.organization.client.OrganizationUnitProvisioningClient( domain='example.com') if conf.options.get_value('runlive') == 'true': self.client = gdata.apps.organization.client.OrganizationUnitProvisioningClient( domain=conf.options.get_value('appsdomain')) if conf.options.get_value('ssl') == 'true': self.client.ssl = True conf.configure_client(self.client, 'OrganizationUnitProvisioningClientTest', self.client.auth_service, True) def tearDown(self): conf.close_client(self.client) def testClientConfiguration(self): self.assertEqual('apps-apis.google.com', self.client.host) self.assertEqual('2.0', self.client.api_version) self.assertEqual('apps', self.client.auth_service) self.assertEqual( ('https://apps-apis.google.com/a/feeds/user/', 'https://apps-apis.google.com/a/feeds/policies/', 'https://apps-apis.google.com/a/feeds/alias/', 'https://apps-apis.google.com/a/feeds/groups/'), self.client.auth_scopes) if conf.options.get_value('runlive') == 'true': self.assertEqual(self.client.domain, conf.options.get_value('appsdomain')) else: self.assertEqual(self.client.domain, 'example.com') def testMakeCustomerIdFeedUri(self): self.assertEqual('/a/feeds/customer/2.0/customerId', self.client.MakeCustomerIdFeedUri()) def testMakeOrganizationUnitOrgunitProvisioningUri(self): self.customer_id = 'tempo' self.assertEqual('/a/feeds/orgunit/2.0/%s' % self.customer_id, self.client.MakeOrganizationUnitOrgunitProvisioningUri( self.customer_id)) self.assertEqual( '/a/feeds/orgunit/2.0/%s/testing/Test+Test' % self.customer_id, self.client.MakeOrganizationUnitOrgunitProvisioningUri( self.customer_id, org_unit_path='testing/Test+Test')) self.assertEqual( '/a/feeds/orgunit/2.0/%s?get=all' % (self.customer_id), self.client.MakeOrganizationUnitOrgunitProvisioningUri( self.customer_id, params={'get': 'all'})) def testMakeOrganizationUnitOrguserProvisioningUri(self): self.customer_id = 'tempo' self.assertEqual('/a/feeds/orguser/2.0/%s' % self.customer_id, self.client.MakeOrganizationUnitOrguserProvisioningUri( self.customer_id)) self.assertEqual( '/a/feeds/orguser/2.0/%s/admin@example.com' % self.customer_id, self.client.MakeOrganizationUnitOrguserProvisioningUri( self.customer_id, org_user_email='admin@example.com')) self.assertEqual( '/a/feeds/orguser/2.0/%s?get=all' % (self.customer_id), self.client.MakeOrganizationUnitOrguserProvisioningUri( self.customer_id, params={'get': 'all'})) def testCreateRetrieveUpdateDelete(self): if not conf.options.get_value('runlive') == 'true': return # Either load the recording or prepare to make a live request. conf.configure_cache(self.client, 'testCreateRetrieveUpdateDelete') customer_id = self.client.RetrieveCustomerId().GetCustomerId() rnd_number = random.randrange(0, 100001) org_unit_name = 'test_org_unit_name%s' % (rnd_number) org_unit_description = 'test_org_unit_description%s' % (rnd_number) org_unit_path = org_unit_name new_entry = self.client.CreateOrgUnit(customer_id, org_unit_name, parent_org_unit_path='/', description=org_unit_description, block_inheritance=False) self.assert_(isinstance(new_entry, gdata.apps.organization.data.OrgUnitEntry)) self.assertEquals(new_entry.org_unit_path, org_unit_path) entry = self.client.RetrieveOrgUnit(customer_id, org_unit_path) self.assert_(isinstance(entry, gdata.apps.organization.data.OrgUnitEntry)) self.assertEquals(entry.org_unit_name, org_unit_name) self.assertEquals(entry.org_unit_description, org_unit_description) self.assertEquals(entry.parent_org_unit_path, '') self.assertEquals(entry.org_unit_path, org_unit_path) self.assertEquals(entry.org_unit_block_inheritance, 'false') self.client.DeleteOrgUnit(customer_id, org_unit_name) def suite(): return conf.build_suite([OrganizationUnitProvisioningClientTest]) if __name__ == '__main__': unittest.TextTestRunner().run(suite()) gdata-2.0.18/tests/gdata_tests/apps/service_test.py0000750036466300116100000004446512156622363022435 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright (C) 2007 SIOS Technology, 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. __author__ = 'tmatsuo@sios.com (Takashi Matsuo)' import unittest try: from xml.etree import ElementTree except ImportError: from elementtree import ElementTree import atom import gdata.apps import gdata.apps.service import getpass import time apps_domain = '' apps_username = '' apps_password = '' class AppsServiceUnitTest01(unittest.TestCase): def setUp(self): self.postfix = time.strftime("%Y%m%d%H%M%S") email = apps_username + '@' + apps_domain self.apps_client = gdata.apps.service.AppsService( email=email, domain=apps_domain, password=apps_password, source='AppsClient "Unit" Tests') self.apps_client.ProgrammaticLogin() self.created_user = None def tearDown(self): if self.created_user is not None: try: self.apps_client.DeleteUser(self.created_user.login.user_name) except Exception, e: pass def test001RetrieveUser(self): """Tests RetrieveUser method""" try: self_user_entry = self.apps_client.RetrieveUser(apps_username) except: self.fail('Unexpected exception occurred') self.assert_(isinstance(self_user_entry, gdata.apps.UserEntry), "The return value of RetrieveUser() must be an instance of " + "apps.UserEntry: %s" % self_user_entry) self.assertEquals(self_user_entry.login.user_name, apps_username) def test002RetrieveUserRaisesException(self): """Tests if RetrieveUser() raises AppsForYourDomainException with appropriate error code""" try: non_existance = self.apps_client.RetrieveUser('nobody-' + self.postfix) except gdata.apps.service.AppsForYourDomainException, e: self.assertEquals(e.error_code, gdata.apps.service.ENTITY_DOES_NOT_EXIST) except Exception, e: self.fail('Unexpected exception occurred: %s' % e) else: self.fail('No exception occurred') def testSuspendAndRestoreUser(self): # Create a test user user_name = 'an-apps-service-test-account-' + self.postfix family_name = 'Tester' given_name = 'Apps' password = '123$$abc' suspended = 'false' created_user = self.apps_client.CreateUser( user_name=user_name, family_name=family_name, given_name=given_name, password=password, suspended=suspended) # Suspend then restore the new user. entry = self.apps_client.SuspendUser(created_user.login.user_name) self.assertEquals(entry.login.suspended, 'true') entry = self.apps_client.RestoreUser(created_user.login.user_name) self.assertEquals(entry.login.suspended, 'false') # Clean up, delete the test user. self.apps_client.DeleteUser(user_name) def test003MethodsForUser(self): """Tests methods for user""" user_name = 'TakashiMatsuo-' + self.postfix family_name = 'Matsuo' given_name = 'Takashi' password = '123$$abc' suspended = 'false' try: created_user = self.apps_client.CreateUser( user_name=user_name, family_name=family_name, given_name=given_name, password=password, suspended=suspended) except Exception, e: self.assert_(False, 'Unexpected exception occurred: %s' % e) self.created_user = created_user self.assertEquals(created_user.login.user_name, user_name) self.assertEquals(created_user.login.suspended, suspended) self.assertEquals(created_user.name.family_name, family_name) self.assertEquals(created_user.name.given_name, given_name) # self.assertEquals(created_user.quota.limit, # gdata.apps.service.DEFAULT_QUOTA_LIMIT) """Tests RetrieveAllUsers method""" try: user_feed = self.apps_client.RetrieveAllUsers() except Exception, e: self.assert_(False, 'Unexpected exception occurred: %s' % e) succeed = False for a_entry in user_feed.entry: if a_entry.login.user_name == user_name: succeed = True self.assert_(succeed, 'There must be a user: %s' % user_name) """Tests UpdateUser method""" new_family_name = 'NewFamilyName' new_given_name = 'NewGivenName' new_quota = '4096' created_user.name.family_name = new_family_name created_user.name.given_name = new_given_name created_user.quota.limit = new_quota created_user.login.suspended = 'true' try: new_user_entry = self.apps_client.UpdateUser(user_name, created_user) except Exception, e: self.fail('Unexpected exception occurred: %s' % e) self.assert_(isinstance(new_user_entry, gdata.apps.UserEntry), "new user entry must be an instance of gdata.apps.UserEntry: %s" % new_user_entry) self.assertEquals(new_user_entry.name.family_name, new_family_name) self.assertEquals(new_user_entry.name.given_name, new_given_name) self.assertEquals(new_user_entry.login.suspended, 'true') # quota limit update does not always success. # self.assertEquals(new_user_entry.quota.limit, new_quota) nobody = gdata.apps.UserEntry() nobody.login = gdata.apps.Login(user_name='nobody-' + self.postfix) nobody.name = gdata.apps.Name(family_name='nobody', given_name='nobody') # make sure that there is no account with nobody- + self.postfix try: tmp_entry = self.apps_client.RetrieveUser('nobody-' + self.postfix) except gdata.apps.service.AppsForYourDomainException, e: self.assertEquals(e.error_code, gdata.apps.service.ENTITY_DOES_NOT_EXIST) except Exception, e: self.fail('Unexpected exception occurred: %s' % e) else: self.fail('No exception occurred') # make sure that UpdateUser fails with AppsForYourDomainException. try: new_user_entry = self.apps_client.UpdateUser('nobody-' + self.postfix, nobody) except gdata.apps.service.AppsForYourDomainException, e: self.assertEquals(e.error_code, gdata.apps.service.ENTITY_DOES_NOT_EXIST) except Exception, e: self.fail('Unexpected exception occurred: %s' % e) else: self.fail('No exception occurred') """Tests DeleteUser method""" try: self.apps_client.DeleteUser(user_name) except Exception, e: self.assert_(False, 'Unexpected exception occurred: %s' % e) # make sure that the account deleted try: self.apps_client.RetrieveUser(user_name) except gdata.apps.service.AppsForYourDomainException, e: self.assertEquals(e.error_code, gdata.apps.service.ENTITY_DOES_NOT_EXIST) except Exception, e: self.fail('Unexpected exception occurred: %s' % e) else: self.fail('No exception occurred') self.created_user = None # make sure that DeleteUser fails with AppsForYourDomainException. try: self.apps_client.DeleteUser(user_name) except gdata.apps.service.AppsForYourDomainException, e: self.assertEquals(e.error_code, gdata.apps.service.ENTITY_DOES_NOT_EXIST) except Exception, e: self.fail('Unexpected exception occurred: %s' % e) else: self.fail('No exception occurred') def test004MethodsForNickname(self): """Tests methods for nickname""" # first create a user account user_name = 'EmmyMatsuo-' + self.postfix family_name = 'Matsuo' given_name = 'Emmy' password = '123$$abc' suspended = 'false' try: created_user = self.apps_client.CreateUser( user_name=user_name, family_name=family_name, given_name=given_name, password=password, suspended=suspended) except Exception, e: self.fail('Unexpected exception occurred: %s' % e) self.created_user = created_user # tests CreateNickname method nickname = 'emmy-' + self.postfix try: created_nickname = self.apps_client.CreateNickname(user_name, nickname) except Exception, e: self.fail('Unexpected exception occurred: %s' % e) self.assert_(isinstance(created_nickname, gdata.apps.NicknameEntry), "Return value of CreateNickname method must be an instance of " + "gdata.apps.NicknameEntry: %s" % created_nickname) self.assertEquals(created_nickname.login.user_name, user_name) self.assertEquals(created_nickname.nickname.name, nickname) # tests RetrieveNickname method retrieved_nickname = self.apps_client.RetrieveNickname(nickname) self.assert_(isinstance(retrieved_nickname, gdata.apps.NicknameEntry), "Return value of RetrieveNickname method must be an instance of " + "gdata.apps.NicknameEntry: %s" % retrieved_nickname) self.assertEquals(retrieved_nickname.login.user_name, user_name) self.assertEquals(retrieved_nickname.nickname.name, nickname) # tests RetrieveNicknames method nickname_feed = self.apps_client.RetrieveNicknames(user_name) self.assert_(isinstance(nickname_feed, gdata.apps.NicknameFeed), "Return value of RetrieveNicknames method must be an instance of " + "gdata.apps.NicknameFeed: %s" % nickname_feed) self.assertEquals(nickname_feed.entry[0].login.user_name, user_name) self.assertEquals(nickname_feed.entry[0].nickname.name, nickname) # tests RetrieveAllNicknames method nickname_feed = self.apps_client.RetrieveAllNicknames() self.assert_(isinstance(nickname_feed, gdata.apps.NicknameFeed), "Return value of RetrieveAllNicknames method must be an instance of " + "gdata.apps.NicknameFeed: %s" % nickname_feed) succeed = False for a_entry in nickname_feed.entry: if a_entry.login.user_name == user_name and \ a_entry.nickname.name == nickname: succeed = True self.assert_(succeed, "There must be a nickname entry named %s." % nickname) # tests DeleteNickname method self.apps_client.DeleteNickname(nickname) try: non_existence = self.apps_client.RetrieveNickname(nickname) except gdata.apps.service.AppsForYourDomainException, e: self.assertEquals(e.error_code, gdata.apps.service.ENTITY_DOES_NOT_EXIST) except Exception, e: self.fail('Unexpected exception occurred: %s' % e) else: self.fail('No exception occurred') class AppsServiceUnitTest02(unittest.TestCase): def setUp(self): self.postfix = time.strftime("%Y%m%d%H%M%S") email = apps_username + '@' + apps_domain self.apps_client = gdata.apps.service.AppsService( email=email, domain=apps_domain, password=apps_password, source='AppsClient "Unit" Tests') self.apps_client.ProgrammaticLogin() self.created_users = [] self.created_email_lists = [] def tearDown(self): for user in self.created_users: try: self.apps_client.DeleteUser(user.login.user_name) except Exception, e: print e for email_list in self.created_email_lists: try: self.apps_client.DeleteEmailList(email_list.email_list.name) except Exception, e: print e def test001MethodsForEmaillist(self): """Tests methods for emaillist """ user_name = 'YujiMatsuo-' + self.postfix family_name = 'Matsuo' given_name = 'Yuji' password = '123$$abc' suspended = 'false' try: user_yuji = self.apps_client.CreateUser( user_name=user_name, family_name=family_name, given_name=given_name, password=password, suspended=suspended) except Exception, e: self.fail('Unexpected exception occurred: %s' % e) self.created_users.append(user_yuji) user_name = 'TaroMatsuo-' + self.postfix family_name = 'Matsuo' given_name = 'Taro' password = '123$$abc' suspended = 'false' try: user_taro = self.apps_client.CreateUser( user_name=user_name, family_name=family_name, given_name=given_name, password=password, suspended=suspended) except Exception, e: self.fail('Unexpected exception occurred: %s' % e) self.created_users.append(user_taro) # tests CreateEmailList method list_name = 'list01-' + self.postfix try: created_email_list = self.apps_client.CreateEmailList(list_name) except Exception, e: self.fail('Unexpected exception occurred: %s' % e) self.assert_(isinstance(created_email_list, gdata.apps.EmailListEntry), "Return value of CreateEmailList method must be an instance of " + "EmailListEntry: %s" % created_email_list) self.assertEquals(created_email_list.email_list.name, list_name) self.created_email_lists.append(created_email_list) # tests AddRecipientToEmailList method try: recipient = self.apps_client.AddRecipientToEmailList( user_yuji.login.user_name + '@' + apps_domain, list_name) except Exception, e: self.fail('Unexpected exception occurred: %s' % e) self.assert_(isinstance(recipient, gdata.apps.EmailListRecipientEntry), "Return value of AddRecipientToEmailList method must be an instance " + "of EmailListRecipientEntry: %s" % recipient) self.assertEquals(recipient.who.email, user_yuji.login.user_name + '@' + apps_domain) try: recipient = self.apps_client.AddRecipientToEmailList( user_taro.login.user_name + '@' + apps_domain, list_name) except Exception, e: self.fail('Unexpected exception occurred: %s' % e) # tests RetrieveAllRecipients method try: recipient_feed = self.apps_client.RetrieveAllRecipients(list_name) except Exception, e: self.fail('Unexpected exception occurred: %s' % e) self.assert_(isinstance(recipient_feed, gdata.apps.EmailListRecipientFeed), "Return value of RetrieveAllRecipients method must be an instance " + "of EmailListRecipientFeed: %s" % recipient_feed) self.assertEquals(len(recipient_feed.entry), 2) # tests RemoveRecipientFromEmailList method try: self.apps_client.RemoveRecipientFromEmailList( user_taro.login.user_name + '@' + apps_domain, list_name) except Exception, e: self.fail('Unexpected exception occurred: %s' % e) # make sure that removal succeeded. try: recipient_feed = self.apps_client.RetrieveAllRecipients(list_name) except Exception, e: self.fail('Unexpected exception occurred: %s' % e) self.assert_(isinstance(recipient_feed, gdata.apps.EmailListRecipientFeed), "Return value of RetrieveAllRecipients method must be an instance " + "of EmailListRecipientFeed: %s" % recipient_feed) self.assertEquals(len(recipient_feed.entry), 1) # tests RetrieveAllEmailLists try: list_feed = self.apps_client.RetrieveAllEmailLists() except Exception, e: self.fail('Unexpected exception occurred: %s' % e) self.assert_(isinstance(list_feed, gdata.apps.EmailListFeed), "Return value of RetrieveAllEmailLists method must be an instance" + "of EmailListFeed: %s" % list_feed) succeed = False for email_list in list_feed.entry: if email_list.email_list.name == list_name: succeed = True self.assert_(succeed, "There must be an email list named %s" % list_name) # tests RetrieveEmailLists method. try: list_feed = self.apps_client.RetrieveEmailLists( user_yuji.login.user_name + '@' + apps_domain) except Exception, e: self.fail('Unexpected exception occurred: %s' % e) self.assert_(isinstance(list_feed, gdata.apps.EmailListFeed), "Return value of RetrieveEmailLists method must be an instance" + "of EmailListFeed: %s" % list_feed) succeed = False for email_list in list_feed.entry: if email_list.email_list.name == list_name: succeed = True self.assert_(succeed, "There must be an email list named %s" % list_name) def testRetrieveEmailList(self): new_list = self.apps_client.CreateEmailList('my_testing_email_list') retrieved_list = self.apps_client.RetrieveEmailList('my_testing_email_list') self.assertEquals(new_list.title.text, retrieved_list.title.text) self.assertEquals(new_list.id.text, retrieved_list.id.text) self.assertEquals(new_list.email_list.name, retrieved_list.email_list.name) self.apps_client.DeleteEmailList('my_testing_email_list') # Should not be able to retrieve the deleted list. try: removed_list = self.apps_client.RetrieveEmailList('my_testing_email_list') self.fail() except gdata.apps.service.AppsForYourDomainException: pass class AppsServiceUnitTest03(unittest.TestCase): def setUp(self): self.postfix = time.strftime("%Y%m%d%H%M%S") email = apps_username + '@' + apps_domain self.apps_client = gdata.apps.service.AppsService( email=email, domain=apps_domain, password=apps_password, source='AppsClient "Unit" Tests') self.apps_client.ProgrammaticLogin() self.created_users = [] self.created_email_lists = [] def tearDown(self): for user in self.created_users: try: self.apps_client.DeleteUser(user.login.user_name) except Exception, e: print e for email_list in self.created_email_lists: try: self.apps_client.DeleteEmailList(email_list.email_list.name) except Exception, e: print e def test001Pagenation(self): """Tests for pagination. It takes toooo long.""" list_feed = self.apps_client.RetrieveAllEmailLists() quantity = len(list_feed.entry) list_nums = 101 for i in range(list_nums): list_name = 'list%03d-' % i + self.postfix try: created_email_list = self.apps_client.CreateEmailList(list_name) except Exception, e: self.fail('Unexpected exception occurred: %s' % e) self.created_email_lists.append(created_email_list) list_feed = self.apps_client.RetrieveAllEmailLists() self.assertEquals(len(list_feed.entry), list_nums + quantity) if __name__ == '__main__': print ('Google Apps Service Tests\nNOTE: Please run these tests only with ' 'a test domain. The tests may delete or update your domain\'s ' 'account data.') apps_domain = raw_input('Please enter your domain: ') apps_username = raw_input('Please enter your username of admin account: ') apps_password = getpass.getpass() unittest.main() gdata-2.0.18/tests/gdata_tests/apps/data_test.py0000640036466300116100000001130512156622363021667 0ustar afshareng00000000000000#!/usr/bin/python2.4 # # Copyright 2011 Google Inc. All Rights Reserved. # # 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. """Data model tests for the Provisioning API.""" __author__ = 'Shraddha Gupta ' import unittest import atom.core from gdata import test_data import gdata.apps.data import gdata.test_config as conf class UserEntryTest(unittest.TestCase): def setUp(self): self.entry = atom.core.parse(test_data.USER_ENTRY1, gdata.apps.data.UserEntry) self.feed = atom.core.parse(test_data.USER_FEED1, gdata.apps.data.UserFeed) def testUserEntryFromString(self): self.assert_(isinstance(self.entry, gdata.apps.data.UserEntry)) self.assertEquals(self.entry.name.given_name, 'abcd33') self.assertEquals(self.entry.name.family_name, 'efgh3') self.assertEquals(self.entry.login.user_name, 'abcd12310') self.assertEquals(self.entry.login.suspended, 'false') self.assertEquals(self.entry.login.admin, 'false') self.assertEquals(self.entry.quota.limit, '25600') def testUserFeedFromString(self): self.assertEquals(len(self.feed.entry), 2) self.assert_(isinstance(self.feed, gdata.apps.data.UserFeed)) self.assert_(isinstance(self.feed.entry[0], gdata.apps.data.UserEntry)) self.assert_(isinstance(self.feed.entry[1], gdata.apps.data.UserEntry)) self.assertEquals(self.feed.entry[0].find_edit_link(), ('https://apps-apis.google.com/a/feeds/srkapps.com/user/2.0/user8306')) self.assertEquals(self.feed.entry[0].name.given_name, 'FirstName8306') self.assertEquals(self.feed.entry[0].name.family_name, 'LastName8306') self.assertEquals(self.feed.entry[0].login.user_name, 'user8306') self.assertEquals(self.feed.entry[0].login.admin, 'false') self.assertEquals(self.feed.entry[0].login.suspended, 'false') self.assertEquals(self.feed.entry[0].login.change_password, 'false') self.assertEquals(self.feed.entry[0].login.ip_whitelisted, 'false') self.assertEquals(self.feed.entry[0].quota.limit, '25600') self.assertEquals( self.feed.entry[1].find_edit_link(), ('https://apps-apis.google.com/a/feeds/srkapps.com/user/2.0/user8307')) self.assertEquals(self.feed.entry[1].name.given_name, 'FirstName8307') self.assertEquals(self.feed.entry[1].name.family_name, 'LastName8307') self.assertEquals(self.feed.entry[1].login.user_name, 'user8307') self.assertEquals(self.feed.entry[1].login.admin, 'false') self.assertEquals(self.feed.entry[1].login.suspended, 'false') self.assertEquals(self.feed.entry[1].login.change_password, 'false') self.assertEquals(self.feed.entry[1].login.ip_whitelisted, 'false') self.assertEquals(self.feed.entry[1].quota.limit, '25600') class NicknameEntryTest(unittest.TestCase): def setUp(self): self.entry = atom.core.parse(test_data.NICKNAME_ENTRY, gdata.apps.data.NicknameEntry) self.feed = atom.core.parse(test_data.NICKNAME_FEED, gdata.apps.data.NicknameFeed) def testNicknameEntryFromString(self): self.assert_(isinstance(self.entry, gdata.apps.data.NicknameEntry)) self.assertEquals(self.entry.nickname.name, 'nehag') self.assertEquals(self.entry.login.user_name, 'neha') def testNicknameFeedFromString(self): self.assertEquals(len(self.feed.entry), 2) self.assert_(isinstance(self.feed, gdata.apps.data.NicknameFeed)) self.assert_(isinstance(self.feed.entry[0], gdata.apps.data.NicknameEntry)) self.assert_(isinstance(self.feed.entry[1], gdata.apps.data.NicknameEntry)) self.assertEquals( self.feed.entry[0].find_edit_link(), ('https://apps-apis.google.com/a/feeds/srkapps.net/' 'nickname/2.0/nehag')) self.assertEquals(self.feed.entry[0].nickname.name, 'nehag') self.assertEquals(self.feed.entry[0].login.user_name, 'neha') self.assertEquals( self.feed.entry[1].find_edit_link(), ('https://apps-apis.google.com/a/feeds/srkapps.net/' 'nickname/2.0/richag')) self.assertEquals(self.feed.entry[1].nickname.name, 'richag') self.assertEquals(self.feed.entry[1].login.user_name, 'richa') def suite(): return conf.build_suite([UserEntryTest, NicknameEntryTest]) if __name__ == '__main__': unittest.main() gdata-2.0.18/tests/gdata_tests/apps/AppsServiceTestForGetGeneratorForAllUsers.pickle0000640036466300116100000035636412156622363030743 0ustar afshareng00000000000000(lp0 (ccopy_reg _reconstructor p1 (catom.mock_http MockRequest p2 c__builtin__ object p3 Ntp4 Rp5 (dp6 S'url' p7 g1 (catom.url Url p8 g3 Ntp9 Rp10 (dp11 S'path' p12 S'/accounts/ClientLogin' p13 sS'host' p14 S'www.google.com' p15 sS'protocol' p16 S'https' p17 sS'port' p18 NsS'params' p19 (dp20 sbsS'headers' p21 (dp22 S'Content-Type' p23 S'application/x-www-form-urlencoded' p24 ssS'operation' p25 S'POST' p26 sS'data' p27 S'Passwd=hogehoge&source=AppsClient+%22Unit%22+Tests&service=apps&Email=zzz%40test.shehas.net&accountType=HOSTED_OR_GOOGLE' p28 sbg1 (catom.mock_http MockResponse p29 g3 Ntp30 Rp31 (dp32 S'body' p33 S'SID=hogehoge\nLSID=hogehoge\nAuth=hogehoge\n' p34 sS'status' p35 I200 sS'reason' p36 S'OK' p37 sS'_headers' p38 (dp39 sbtp40 a(g1 (g2 g3 Ntp41 Rp42 (dp43 g7 g1 (g8 g3 Ntp44 Rp45 (dp46 g12 S'/a/feeds/test.shehas.net/user/2.0' p47 sg14 S'apps-apis.google.com' p48 sg16 S'https' p49 sg18 Nsg19 (dp50 sbsg21 (dp51 S'Authorization' p52 S'hogehoge' p53 sS'User-Agent' p54 S'AppsClient "Unit" Tests GData-Python/2.0.2' p55 ssg25 S'GET' p56 sg27 Nsbg1 (g29 g3 Ntp57 Rp58 (dp59 g33 S"https://apps-apis.google.com/a/feeds/test.shehas.net/user/2.01970-01-01T00:00:00.000ZUsers1https://apps-apis.google.com/a/feeds/test.shehas.net/user/2.0/b000-200910011324011970-01-01T00:00:00.000Zb000-20091001132401https://apps-apis.google.com/a/feeds/test.shehas.net/user/2.0/b001-200910011324011970-01-01T00:00:00.000Zb001-20091001132401https://apps-apis.google.com/a/feeds/test.shehas.net/user/2.0/b002-200910011324011970-01-01T00:00:00.000Zb002-20091001132401https://apps-apis.google.com/a/feeds/test.shehas.net/user/2.0/b003-200910011324011970-01-01T00:00:00.000Zb003-20091001132401https://apps-apis.google.com/a/feeds/test.shehas.net/user/2.0/b004-200910011324011970-01-01T00:00:00.000Zb004-20091001132401https://apps-apis.google.com/a/feeds/test.shehas.net/user/2.0/b005-200910011324011970-01-01T00:00:00.000Zb005-20091001132401https://apps-apis.google.com/a/feeds/test.shehas.net/user/2.0/b006-200910011324011970-01-01T00:00:00.000Zb006-20091001132401https://apps-apis.google.com/a/feeds/test.shehas.net/user/2.0/b007-200910011324011970-01-01T00:00:00.000Zb007-20091001132401https://apps-apis.google.com/a/feeds/test.shehas.net/user/2.0/b008-200910011324011970-01-01T00:00:00.000Zb008-20091001132401https://apps-apis.google.com/a/feeds/test.shehas.net/user/2.0/b009-200910011324011970-01-01T00:00:00.000Zb009-20091001132401https://apps-apis.google.com/a/feeds/test.shehas.net/user/2.0/b010-200910011324011970-01-01T00:00:00.000Zb010-20091001132401https://apps-apis.google.com/a/feeds/test.shehas.net/user/2.0/b011-200910011324011970-01-01T00:00:00.000Zb011-20091001132401https://apps-apis.google.com/a/feeds/test.shehas.net/user/2.0/b012-200910011324011970-01-01T00:00:00.000Zb012-20091001132401https://apps-apis.google.com/a/feeds/test.shehas.net/user/2.0/b013-200910011324011970-01-01T00:00:00.000Zb013-20091001132401https://apps-apis.google.com/a/feeds/test.shehas.net/user/2.0/b014-200910011324011970-01-01T00:00:00.000Zb014-20091001132401https://apps-apis.google.com/a/feeds/test.shehas.net/user/2.0/b015-200910011324011970-01-01T00:00:00.000Zb015-20091001132401https://apps-apis.google.com/a/feeds/test.shehas.net/user/2.0/b016-200910011324011970-01-01T00:00:00.000Zb016-20091001132401https://apps-apis.google.com/a/feeds/test.shehas.net/user/2.0/b017-200910011324011970-01-01T00:00:00.000Zb017-20091001132401https://apps-apis.google.com/a/feeds/test.shehas.net/user/2.0/b018-200910011324011970-01-01T00:00:00.000Zb018-20091001132401https://apps-apis.google.com/a/feeds/test.shehas.net/user/2.0/b019-200910011324011970-01-01T00:00:00.000Zb019-20091001132401https://apps-apis.google.com/a/feeds/test.shehas.net/user/2.0/b020-200910011324011970-01-01T00:00:00.000Zb020-20091001132401https://apps-apis.google.com/a/feeds/test.shehas.net/user/2.0/b021-200910011324011970-01-01T00:00:00.000Zb021-20091001132401https://apps-apis.google.com/a/feeds/test.shehas.net/user/2.0/b022-200910011324011970-01-01T00:00:00.000Zb022-20091001132401https://apps-apis.google.com/a/feeds/test.shehas.net/user/2.0/b023-200910011324011970-01-01T00:00:00.000Zb023-20091001132401https://apps-apis.google.com/a/feeds/test.shehas.net/user/2.0/b024-200910011324011970-01-01T00:00:00.000Zb024-20091001132401https://apps-apis.google.com/a/feeds/test.shehas.net/user/2.0/b025-200910011324011970-01-01T00:00:00.000Zb025-20091001132401https://apps-apis.google.com/a/feeds/test.shehas.net/user/2.0/b026-200910011324011970-01-01T00:00:00.000Zb026-20091001132401https://apps-apis.google.com/a/feeds/test.shehas.net/user/2.0/b027-200910011324011970-01-01T00:00:00.000Zb027-20091001132401https://apps-apis.google.com/a/feeds/test.shehas.net/user/2.0/b028-200910011324011970-01-01T00:00:00.000Zb028-20091001132401https://apps-apis.google.com/a/feeds/test.shehas.net/user/2.0/b029-200910011324011970-01-01T00:00:00.000Zb029-20091001132401https://apps-apis.google.com/a/feeds/test.shehas.net/user/2.0/b030-200910011324011970-01-01T00:00:00.000Zb030-20091001132401https://apps-apis.google.com/a/feeds/test.shehas.net/user/2.0/b031-200910011324011970-01-01T00:00:00.000Zb031-20091001132401https://apps-apis.google.com/a/feeds/test.shehas.net/user/2.0/b032-200910011324011970-01-01T00:00:00.000Zb032-20091001132401https://apps-apis.google.com/a/feeds/test.shehas.net/user/2.0/b033-200910011324011970-01-01T00:00:00.000Zb033-20091001132401https://apps-apis.google.com/a/feeds/test.shehas.net/user/2.0/b034-200910011324011970-01-01T00:00:00.000Zb034-20091001132401https://apps-apis.google.com/a/feeds/test.shehas.net/user/2.0/b035-200910011324011970-01-01T00:00:00.000Zb035-20091001132401https://apps-apis.google.com/a/feeds/test.shehas.net/user/2.0/b036-200910011324011970-01-01T00:00:00.000Zb036-20091001132401https://apps-apis.google.com/a/feeds/test.shehas.net/user/2.0/b037-200910011324011970-01-01T00:00:00.000Zb037-20091001132401https://apps-apis.google.com/a/feeds/test.shehas.net/user/2.0/b038-200910011324011970-01-01T00:00:00.000Zb038-20091001132401https://apps-apis.google.com/a/feeds/test.shehas.net/user/2.0/b039-200910011324011970-01-01T00:00:00.000Zb039-20091001132401https://apps-apis.google.com/a/feeds/test.shehas.net/user/2.0/b040-200910011324011970-01-01T00:00:00.000Zb040-20091001132401https://apps-apis.google.com/a/feeds/test.shehas.net/user/2.0/b041-200910011324011970-01-01T00:00:00.000Zb041-20091001132401https://apps-apis.google.com/a/feeds/test.shehas.net/user/2.0/b042-200910011324011970-01-01T00:00:00.000Zb042-20091001132401https://apps-apis.google.com/a/feeds/test.shehas.net/user/2.0/b043-200910011324011970-01-01T00:00:00.000Zb043-20091001132401https://apps-apis.google.com/a/feeds/test.shehas.net/user/2.0/b044-200910011324011970-01-01T00:00:00.000Zb044-20091001132401https://apps-apis.google.com/a/feeds/test.shehas.net/user/2.0/b045-200910011324011970-01-01T00:00:00.000Zb045-20091001132401https://apps-apis.google.com/a/feeds/test.shehas.net/user/2.0/b046-200910011324011970-01-01T00:00:00.000Zb046-20091001132401https://apps-apis.google.com/a/feeds/test.shehas.net/user/2.0/b047-200910011324011970-01-01T00:00:00.000Zb047-20091001132401https://apps-apis.google.com/a/feeds/test.shehas.net/user/2.0/b048-200910011324011970-01-01T00:00:00.000Zb048-20091001132401https://apps-apis.google.com/a/feeds/test.shehas.net/user/2.0/b049-200910011324011970-01-01T00:00:00.000Zb049-20091001132401https://apps-apis.google.com/a/feeds/test.shehas.net/user/2.0/b050-200910011324011970-01-01T00:00:00.000Zb050-20091001132401https://apps-apis.google.com/a/feeds/test.shehas.net/user/2.0/b051-200910011324011970-01-01T00:00:00.000Zb051-20091001132401https://apps-apis.google.com/a/feeds/test.shehas.net/user/2.0/b052-200910011324011970-01-01T00:00:00.000Zb052-20091001132401https://apps-apis.google.com/a/feeds/test.shehas.net/user/2.0/b053-200910011324011970-01-01T00:00:00.000Zb053-20091001132401https://apps-apis.google.com/a/feeds/test.shehas.net/user/2.0/b054-200910011324011970-01-01T00:00:00.000Zb054-20091001132401https://apps-apis.google.com/a/feeds/test.shehas.net/user/2.0/b055-200910011324011970-01-01T00:00:00.000Zb055-20091001132401https://apps-apis.google.com/a/feeds/test.shehas.net/user/2.0/b056-200910011324011970-01-01T00:00:00.000Zb056-20091001132401https://apps-apis.google.com/a/feeds/test.shehas.net/user/2.0/b057-200910011324011970-01-01T00:00:00.000Zb057-20091001132401https://apps-apis.google.com/a/feeds/test.shehas.net/user/2.0/b058-200910011324011970-01-01T00:00:00.000Zb058-20091001132401https://apps-apis.google.com/a/feeds/test.shehas.net/user/2.0/b059-200910011324011970-01-01T00:00:00.000Zb059-20091001132401https://apps-apis.google.com/a/feeds/test.shehas.net/user/2.0/b060-200910011324011970-01-01T00:00:00.000Zb060-20091001132401https://apps-apis.google.com/a/feeds/test.shehas.net/user/2.0/b061-200910011324011970-01-01T00:00:00.000Zb061-20091001132401https://apps-apis.google.com/a/feeds/test.shehas.net/user/2.0/b062-200910011324011970-01-01T00:00:00.000Zb062-20091001132401https://apps-apis.google.com/a/feeds/test.shehas.net/user/2.0/b063-200910011324011970-01-01T00:00:00.000Zb063-20091001132401https://apps-apis.google.com/a/feeds/test.shehas.net/user/2.0/b064-200910011324011970-01-01T00:00:00.000Zb064-20091001132401https://apps-apis.google.com/a/feeds/test.shehas.net/user/2.0/b065-200910011324011970-01-01T00:00:00.000Zb065-20091001132401https://apps-apis.google.com/a/feeds/test.shehas.net/user/2.0/b066-200910011324011970-01-01T00:00:00.000Zb066-20091001132401https://apps-apis.google.com/a/feeds/test.shehas.net/user/2.0/b067-200910011324011970-01-01T00:00:00.000Zb067-20091001132401https://apps-apis.google.com/a/feeds/test.shehas.net/user/2.0/b068-200910011324011970-01-01T00:00:00.000Zb068-20091001132401https://apps-apis.google.com/a/feeds/test.shehas.net/user/2.0/b069-200910011324011970-01-01T00:00:00.000Zb069-20091001132401https://apps-apis.google.com/a/feeds/test.shehas.net/user/2.0/b070-200910011324011970-01-01T00:00:00.000Zb070-20091001132401https://apps-apis.google.com/a/feeds/test.shehas.net/user/2.0/b071-200910011324011970-01-01T00:00:00.000Zb071-20091001132401https://apps-apis.google.com/a/feeds/test.shehas.net/user/2.0/b072-200910011324011970-01-01T00:00:00.000Zb072-20091001132401https://apps-apis.google.com/a/feeds/test.shehas.net/user/2.0/b073-200910011324011970-01-01T00:00:00.000Zb073-20091001132401https://apps-apis.google.com/a/feeds/test.shehas.net/user/2.0/b074-200910011324011970-01-01T00:00:00.000Zb074-20091001132401https://apps-apis.google.com/a/feeds/test.shehas.net/user/2.0/b075-200910011324011970-01-01T00:00:00.000Zb075-20091001132401https://apps-apis.google.com/a/feeds/test.shehas.net/user/2.0/b076-200910011324011970-01-01T00:00:00.000Zb076-20091001132401https://apps-apis.google.com/a/feeds/test.shehas.net/user/2.0/b077-200910011324011970-01-01T00:00:00.000Zb077-20091001132401https://apps-apis.google.com/a/feeds/test.shehas.net/user/2.0/b078-200910011324011970-01-01T00:00:00.000Zb078-20091001132401https://apps-apis.google.com/a/feeds/test.shehas.net/user/2.0/b079-200910011324011970-01-01T00:00:00.000Zb079-20091001132401https://apps-apis.google.com/a/feeds/test.shehas.net/user/2.0/b080-200910011324011970-01-01T00:00:00.000Zb080-20091001132401https://apps-apis.google.com/a/feeds/test.shehas.net/user/2.0/b081-200910011324011970-01-01T00:00:00.000Zb081-20091001132401https://apps-apis.google.com/a/feeds/test.shehas.net/user/2.0/b082-200910011324011970-01-01T00:00:00.000Zb082-20091001132401https://apps-apis.google.com/a/feeds/test.shehas.net/user/2.0/b083-200910011324011970-01-01T00:00:00.000Zb083-20091001132401https://apps-apis.google.com/a/feeds/test.shehas.net/user/2.0/b084-200910011324011970-01-01T00:00:00.000Zb084-20091001132401https://apps-apis.google.com/a/feeds/test.shehas.net/user/2.0/b085-200910011324011970-01-01T00:00:00.000Zb085-20091001132401https://apps-apis.google.com/a/feeds/test.shehas.net/user/2.0/b086-200910011324011970-01-01T00:00:00.000Zb086-20091001132401https://apps-apis.google.com/a/feeds/test.shehas.net/user/2.0/b087-200910011324011970-01-01T00:00:00.000Zb087-20091001132401https://apps-apis.google.com/a/feeds/test.shehas.net/user/2.0/b088-200910011324011970-01-01T00:00:00.000Zb088-20091001132401https://apps-apis.google.com/a/feeds/test.shehas.net/user/2.0/b089-200910011324011970-01-01T00:00:00.000Zb089-20091001132401https://apps-apis.google.com/a/feeds/test.shehas.net/user/2.0/b090-200910011324011970-01-01T00:00:00.000Zb090-20091001132401https://apps-apis.google.com/a/feeds/test.shehas.net/user/2.0/b091-200910011324011970-01-01T00:00:00.000Zb091-20091001132401https://apps-apis.google.com/a/feeds/test.shehas.net/user/2.0/b092-200910011324011970-01-01T00:00:00.000Zb092-20091001132401https://apps-apis.google.com/a/feeds/test.shehas.net/user/2.0/b093-200910011324011970-01-01T00:00:00.000Zb093-20091001132401https://apps-apis.google.com/a/feeds/test.shehas.net/user/2.0/b094-200910011324011970-01-01T00:00:00.000Zb094-20091001132401https://apps-apis.google.com/a/feeds/test.shehas.net/user/2.0/b095-200910011324011970-01-01T00:00:00.000Zb095-20091001132401https://apps-apis.google.com/a/feeds/test.shehas.net/user/2.0/b096-200910011324011970-01-01T00:00:00.000Zb096-20091001132401https://apps-apis.google.com/a/feeds/test.shehas.net/user/2.0/b097-200910011324011970-01-01T00:00:00.000Zb097-20091001132401https://apps-apis.google.com/a/feeds/test.shehas.net/user/2.0/john.amin1970-01-01T00:00:00.000Zjohn.aminhttps://apps-apis.google.com/a/feeds/test.shehas.net/user/2.0/john.doe1970-01-01T00:00:00.000Zjohn.doe" p60 sg35 I200 sg36 S'OK' p61 sg38 (dp62 sbtp63 a(g1 (g2 g3 Ntp64 Rp65 (dp66 g7 g1 (g8 g3 Ntp67 Rp68 (dp69 g12 S'/a/feeds/test.shehas.net/user/2.0' p70 sg14 S'apps-apis.google.com' p71 sg16 S'https' p72 sg18 Nsg19 (dp73 S'startUsername' p74 S'john' p75 ssbsg21 (dp76 g52 g53 sg54 g55 ssg25 g56 sg27 Nsbg1 (g29 g3 Ntp77 Rp78 (dp79 g33 S"https://apps-apis.google.com/a/feeds/test.shehas.net/user/2.01970-01-01T00:00:00.000ZUsers1https://apps-apis.google.com/a/feeds/test.shehas.net/user/2.0/john1970-01-01T00:00:00.000Zjohnhttps://apps-apis.google.com/a/feeds/test.shehas.net/user/2.0/zzz1970-01-01T00:00:00.000Zzzz" p80 sg35 I200 sg36 S'OK' p81 sg38 (dp82 sbtp83 a.gdata-2.0.18/tests/gdata_tests/apps/emailsettings/0000750036466300116100000000000012156625015022211 5ustar afshareng00000000000000gdata-2.0.18/tests/gdata_tests/apps/emailsettings/service_test.py0000640036466300116100000001123412156622363025267 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright (C) 2008 Google # # 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. """Test for Email Settings service.""" __author__ = 'google-apps-apis@googlegroups.com' import getpass import gdata.apps.emailsettings.service import unittest domain = '' admin_email = '' admin_password = '' username = '' class EmailSettingsTest(unittest.TestCase): """Test for the EmailSettingsService.""" def setUp(self): self.es = gdata.apps.emailsettings.service.EmailSettingsService( email=admin_email, password=admin_password, domain=domain) self.es.ProgrammaticLogin() def testCreateLabel(self): result = self.es.CreateLabel(username, label='New label!!!') self.assertEquals(result['label'], 'New label!!!') def testCreateFilter(self): result = self.es.CreateFilter(username, from_='from_foo', to='to_foo', subject='subject_foo', has_the_word='has_the_words_foo', does_not_have_the_word='doesnt_have_foo', has_attachment=True, label='label_foo', should_mark_as_read=True, should_archive=True) self.assertEquals(result['from'], 'from_foo') self.assertEquals(result['to'], 'to_foo') self.assertEquals(result['subject'], 'subject_foo') def testCreateSendAsAlias(self): result = self.es.CreateSendAsAlias(username, name='Send-as Alias', address='user2@sizzles.org', reply_to='user3@sizzles.org', make_default=True) self.assertEquals(result['name'], 'Send-as Alias') def testUpdateWebClipSettings(self): result = self.es.UpdateWebClipSettings(username, enable=True) self.assertEquals(result['enable'], 'true') def testUpdateForwarding(self): result = self.es.UpdateForwarding(username, enable=True, forward_to='user4@sizzles.org', action=gdata.apps.emailsettings.service.KEEP) self.assertEquals(result['enable'], 'true') def testUpdatePop(self): result = self.es.UpdatePop(username, enable=True, enable_for=gdata.apps.emailsettings.service.ALL_MAIL, action=gdata.apps.emailsettings.service.ARCHIVE) self.assertEquals(result['enable'], 'true') def testUpdateImap(self): result = self.es.UpdateImap(username, enable=True) self.assertEquals(result['enable'], 'true') def testUpdateVacation(self): result = self.es.UpdateVacation(username, enable=True, subject='Hawaii', message='Wish you were here!', contacts_only=True) self.assertEquals(result['subject'], 'Hawaii') def testUpdateSignature(self): result = self.es.UpdateSignature(username, signature='Signature') self.assertEquals(result['signature'], 'Signature') def testUpdateLanguage(self): result = self.es.UpdateLanguage(username, language='fr') self.assertEquals(result['language'], 'fr') def testUpdateGeneral(self): result = self.es.UpdateGeneral(username, page_size=100, shortcuts=True, arrows=True, snippets=True, unicode=True) self.assertEquals(result['pageSize'], '100') if __name__ == '__main__': print("""Google Apps Email Settings Service Tests NOTE: Please run these tests only with a test user account. """) domain = raw_input('Google Apps domain: ') admin_email = '%s@%s' % (raw_input('Administrator username: '), domain) admin_password = getpass.getpass('Administrator password: ') username = raw_input('Test username: ') unittest.main() gdata-2.0.18/tests/gdata_tests/apps/emailsettings/data_test.py0000640036466300116100000001576112156622363024551 0ustar afshareng00000000000000#!/usr/bin/python # # 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. __author__ = 'Claudio Cherubino ' import unittest import gdata.apps.emailsettings.data import gdata.test_config as conf class EmailSettingsLabelTest(unittest.TestCase): def setUp(self): self.entry = gdata.apps.emailsettings.data.EmailSettingsLabel() def testName(self): self.entry.name = 'test label' self.assertEquals(self.entry.name, 'test label') class EmailSettingsFilterTest(unittest.TestCase): def setUp(self): self.entry = gdata.apps.emailsettings.data.EmailSettingsFilter() def testFrom(self): self.entry.from_address = 'abc@example.com' self.assertEquals(self.entry.from_address, 'abc@example.com') def testTo(self): self.entry.to_address = 'to@example.com' self.assertEquals(self.entry.to_address, 'to@example.com') def testFrom(self): self.entry.from_address = 'abc@example.com' self.assertEquals(self.entry.from_address, 'abc@example.com') def testSubject(self): self.entry.subject = 'Read me' self.assertEquals(self.entry.subject, 'Read me') def testHasTheWord(self): self.entry.has_the_word = 'important' self.assertEquals(self.entry.has_the_word, 'important') def testDoesNotHaveTheWord(self): self.entry.does_not_have_the_word = 'spam' self.assertEquals(self.entry.does_not_have_the_word, 'spam') def testHasAttachments(self): self.entry.has_attachments = True self.assertEquals(self.entry.has_attachments, True) def testLabel(self): self.entry.label = 'Trip reports' self.assertEquals(self.entry.label, 'Trip reports') def testMarkHasRead(self): self.entry.mark_has_read = True self.assertEquals(self.entry.mark_has_read, True) def testArchive(self): self.entry.archive = True self.assertEquals(self.entry.archive, True) class EmailSettingsSendAsAliasTest(unittest.TestCase): def setUp(self): self.entry = gdata.apps.emailsettings.data.EmailSettingsSendAsAlias() def testName(self): self.entry.name = 'Sales' self.assertEquals(self.entry.name, 'Sales') def testAddress(self): self.entry.address = 'sales@example.com' self.assertEquals(self.entry.address, 'sales@example.com') def testReplyTo(self): self.entry.reply_to = 'support@example.com' self.assertEquals(self.entry.reply_to, 'support@example.com') def testMakeDefault(self): self.entry.make_default = True self.assertEquals(self.entry.make_default, True) class EmailSettingsWebClipTest(unittest.TestCase): def setUp(self): self.entry = gdata.apps.emailsettings.data.EmailSettingsWebClip() def testEnable(self): self.entry.enable = True self.assertEquals(self.entry.enable, True) class EmailSettingsForwardingTest(unittest.TestCase): def setUp(self): self.entry = gdata.apps.emailsettings.data.EmailSettingsForwarding() def testEnable(self): self.entry.enable = True self.assertEquals(self.entry.enable, True) def testForwardTo(self): self.entry.forward_to = 'fred@example.com' self.assertEquals(self.entry.forward_to, 'fred@example.com') def testAction(self): self.entry.action = 'KEEP' self.assertEquals(self.entry.action, 'KEEP') class EmailSettingsPopTest(unittest.TestCase): def setUp(self): self.entry = gdata.apps.emailsettings.data.EmailSettingsPop() def testEnable(self): self.entry.enable = True self.assertEquals(self.entry.enable, True) def testForwardTo(self): self.entry.enable_for = 'ALL_MAIL' self.assertEquals(self.entry.enable_for, 'ALL_MAIL') def testAction(self): self.entry.action = 'KEEP' self.assertEquals(self.entry.action, 'KEEP') class EmailSettingsImapTest(unittest.TestCase): def setUp(self): self.entry = gdata.apps.emailsettings.data.EmailSettingsImap() def testEnable(self): self.entry.enable = True self.assertEquals(self.entry.enable, True) class EmailSettingsVacationResponderTest(unittest.TestCase): def setUp(self): self.entry = gdata.apps.emailsettings.data.EmailSettingsVacationResponder() def testEnable(self): self.entry.enable = True self.assertEquals(self.entry.enable, True) def testSubject(self): self.entry.subject = 'On vacation!' self.assertEquals(self.entry.subject, 'On vacation!') def testMessage(self): self.entry.message = 'See you on September 1st' self.assertEquals(self.entry.message, 'See you on September 1st') def testStartDate(self): self.entry.start_date = '2011-12-05' self.assertEquals(self.entry.start_date, '2011-12-05') def testEndDate(self): self.entry.end_date = '2011-12-06' self.assertEquals(self.entry.end_date, '2011-12-06') def testContactsOnly(self): self.entry.contacts_only = True self.assertEquals(self.entry.contacts_only, True) def testDomainOnly(self): self.entry.domain_only = True self.assertEquals(self.entry.domain_only, True) class EmailSettingsSignatureTest(unittest.TestCase): def setUp(self): self.entry = gdata.apps.emailsettings.data.EmailSettingsSignature() def testValue(self): self.entry.signature_value = 'Regards, Joe' self.assertEquals(self.entry.signature_value, 'Regards, Joe') class EmailSettingsLanguageTest(unittest.TestCase): def setUp(self): self.entry = gdata.apps.emailsettings.data.EmailSettingsLanguage() def testLanguage(self): self.entry.language_tag = 'es' self.assertEquals(self.entry.language_tag, 'es') class EmailSettingsGeneralTest(unittest.TestCase): def setUp(self): self.entry = gdata.apps.emailsettings.data.EmailSettingsGeneral() def testPageSize(self): self.entry.page_size = 25 self.assertEquals(self.entry.page_size, 25) def testShortcuts(self): self.entry.shortcuts = True self.assertEquals(self.entry.shortcuts, True) def testArrows(self): self.entry.arrows = True self.assertEquals(self.entry.arrows, True) def testSnippets(self): self.entry.snippets = True self.assertEquals(self.entry.snippets, True) def testUnicode(self): self.entry.use_unicode = True self.assertEquals(self.entry.use_unicode, True) def suite(): return conf.build_suite([EmailSettingsLabelTest, EmailSettingsFilterTest, EmailSettingsSendAsAliasTest, EmailSettingsWebClipTest, EmailSettingsForwardingTest, EmailSettingsPopTest, EmailSettingsImapTest, EmailSettingsVacationResponderTest, EmailSettingsSignatureTest, EmailSettingsLanguageTest, EmailSettingsGeneralTest]) if __name__ == '__main__': unittest.main() gdata-2.0.18/tests/gdata_tests/apps/emailsettings/__init__.py0000640036466300116100000000000012156622363024314 0ustar afshareng00000000000000gdata-2.0.18/tests/gdata_tests/apps/emailsettings/live_client_test.py0000640036466300116100000003106512156622363026130 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright 2010 Google Inc. All Rights Reserved. # # 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. # This module is used for version 2 of the Google Data APIs. # These tests attempt to connect to Google servers. __author__ = 'Claudio Cherubino ' import unittest import gdata.apps.emailsettings.client import gdata.apps.emailsettings.data import gdata.client import gdata.data import gdata.gauth import gdata.test_config as conf conf.options.register_option(conf.APPS_DOMAIN_OPTION) conf.options.register_option(conf.TARGET_USERNAME_OPTION) class EmailSettingsClientTest(unittest.TestCase): def setUp(self): self.client = gdata.apps.emailsettings.client.EmailSettingsClient( domain='example.com') if conf.options.get_value('runlive') == 'true': self.client = gdata.apps.emailsettings.client.EmailSettingsClient( domain=conf.options.get_value('appsdomain')) if conf.options.get_value('ssl') == 'true': self.client.ssl = True conf.configure_client(self.client, 'EmailSettingsClientTest', self.client.auth_service, True) self.username = conf.options.get_value('appsusername').split('@')[0] def tearDown(self): conf.close_client(self.client) def testClientConfiguration(self): self.assertEqual('apps-apis.google.com', self.client.host) self.assertEqual('2.0', self.client.api_version) self.assertEqual('apps', self.client.auth_service) if conf.options.get_value('runlive') == 'true': self.assertEqual(self.client.domain, conf.options.get_value('appsdomain')) else: self.assertEqual(self.client.domain, 'example.com') def testMakeEmailSettingsUri(self): self.assertEqual('/a/feeds/emailsettings/2.0/%s/%s/%s' % (self.client.domain, 'abc', 'label'), self.client.MakeEmailSettingsUri('abc', 'label')) def testCreateDeleteLabel(self): if not conf.options.get_value('runlive') == 'true': return # Either load the recording or prepare to make a live request. conf.configure_cache(self.client, 'testCreateLabel') new_label = self.client.CreateLabel( username=conf.options.get_value('targetusername'), name='status updates') self.assert_(isinstance(new_label, gdata.apps.emailsettings.data.EmailSettingsLabel)) self.assertEqual(new_label.name, 'status updates') self.client.DeleteLabel( username=conf.options.get_value('targetusername'), label='status updates') def testCreateFilter(self): if not conf.options.get_value('runlive') == 'true': return # Either load the recording or prepare to make a live request. conf.configure_cache(self.client, 'testCreateFilter') new_filter = self.client.CreateFilter( username=conf.options.get_value('targetusername'), from_address='alice@gmail.com', has_the_word='project proposal', mark_as_read=True) self.assert_(isinstance(new_filter, gdata.apps.emailsettings.data.EmailSettingsFilter)) self.assertEqual(new_filter.from_address, 'alice@gmail.com') self.assertEqual(new_filter.has_the_word, 'project proposal') self.assertEqual(new_filter.mark_as_read, 'True') new_filter = self.client.CreateFilter( username=conf.options.get_value('targetusername'), to_address='announcements@example.com', label="announcements") self.assert_(isinstance(new_filter, gdata.apps.emailsettings.data.EmailSettingsFilter)) self.assertEqual(new_filter.to_address, 'announcements@example.com') self.assertEqual(new_filter.label, 'announcements') new_filter = self.client.CreateFilter( username=conf.options.get_value('targetusername'), subject='urgent', does_not_have_the_word='spam', has_attachments=True, archive=True) self.assert_(isinstance(new_filter, gdata.apps.emailsettings.data.EmailSettingsFilter)) self.assertEqual(new_filter.subject, 'urgent') self.assertEqual(new_filter.does_not_have_the_word, 'spam') self.assertEqual(new_filter.has_attachments, 'True') self.assertEqual(new_filter.archive, 'True') def testCreateSendAs(self): if not conf.options.get_value('runlive') == 'true': return # Either load the recording or prepare to make a live request. conf.configure_cache(self.client, 'testCreateSendAs') new_sendas = self.client.CreateSendAs( username=conf.options.get_value('targetusername'), name='Sales', address=conf.options.get_value('appsusername'), reply_to='abc@gmail.com', make_default=True) self.assert_(isinstance(new_sendas, gdata.apps.emailsettings.data.EmailSettingsSendAsAlias)) self.assertEqual(new_sendas.name, 'Sales') self.assertEqual(new_sendas.address, conf.options.get_value('appsusername')) self.assertEqual(new_sendas.reply_to, 'abc@gmail.com') self.assertEqual(new_sendas.make_default, 'True') def testUpdateWebclip(self): if not conf.options.get_value('runlive') == 'true': return # Either load the recording or prepare to make a live request. conf.configure_cache(self.client, 'testUpdateWebclip') new_webclip = self.client.UpdateWebclip( username=conf.options.get_value('targetusername'), enable=True) self.assert_(isinstance(new_webclip, gdata.apps.emailsettings.data.EmailSettingsWebClip)) self.assertEqual(new_webclip.enable, 'True') new_webclip = self.client.UpdateWebclip( username=conf.options.get_value('targetusername'), enable=False) self.assert_(isinstance(new_webclip, gdata.apps.emailsettings.data.EmailSettingsWebClip)) self.assertEqual(new_webclip.enable, 'False') def testUpdateForwarding(self): if not conf.options.get_value('runlive') == 'true': return # Either load the recording or prepare to make a live request. conf.configure_cache(self.client, 'testUpdateForwarding') new_forwarding = self.client.UpdateForwarding( username=conf.options.get_value('targetusername'), enable=True, forward_to=conf.options.get_value('appsusername'), action='KEEP') self.assert_(isinstance(new_forwarding, gdata.apps.emailsettings.data.EmailSettingsForwarding)) self.assertEqual(new_forwarding.enable, 'True') self.assertEqual(new_forwarding.forward_to, conf.options.get_value('appsusername')) self.assertEqual(new_forwarding.action, 'KEEP') new_forwarding = self.client.UpdateForwarding( username=conf.options.get_value('targetusername'), enable=False) self.assert_(isinstance(new_forwarding, gdata.apps.emailsettings.data.EmailSettingsForwarding)) self.assertEqual(new_forwarding.enable, 'False') def testUpdatePop(self): if not conf.options.get_value('runlive') == 'true': return # Either load the recording or prepare to make a live request. conf.configure_cache(self.client, 'testUpdatePop') new_pop = self.client.UpdatePop( username=conf.options.get_value('targetusername'), enable=True, enable_for='MAIL_FROM_NOW_ON', action='KEEP') self.assert_(isinstance(new_pop, gdata.apps.emailsettings.data.EmailSettingsPop)) self.assertEqual(new_pop.enable, 'True') self.assertEqual(new_pop.enable_for, 'MAIL_FROM_NOW_ON') self.assertEqual(new_pop.action, 'KEEP') new_pop = self.client.UpdatePop( username=conf.options.get_value('targetusername'), enable=False) self.assert_(isinstance(new_pop, gdata.apps.emailsettings.data.EmailSettingsPop)) self.assertEqual(new_pop.enable, 'False') def testUpdateImap(self): if not conf.options.get_value('runlive') == 'true': return # Either load the recording or prepare to make a live request. conf.configure_cache(self.client, 'testUpdateImap') new_imap = self.client.UpdateImap( username=conf.options.get_value('targetusername'), enable=True) self.assert_(isinstance(new_imap, gdata.apps.emailsettings.data.EmailSettingsImap)) self.assertEqual(new_imap.enable, 'True') new_imap = self.client.UpdateImap( username=conf.options.get_value('targetusername'), enable=False) self.assert_(isinstance(new_imap, gdata.apps.emailsettings.data.EmailSettingsImap)) self.assertEqual(new_imap.enable, 'False') def testUpdateVacation(self): if not conf.options.get_value('runlive') == 'true': return # Either load the recording or prepare to make a live request. conf.configure_cache(self.client, 'testUpdateVacation') new_vacation = self.client.UpdateVacation( username=conf.options.get_value('targetusername'), enable=True, subject='Out of office', message='If urgent call me at 555-5555.', start_date='2011-12-05', end_date='2011-12-06', contacts_only=True, domain_only=False) self.assert_(isinstance(new_vacation, gdata.apps.emailsettings.data.EmailSettingsVacationResponder)) self.assertEqual(new_vacation.enable, 'True') self.assertEqual(new_vacation.subject, 'Out of office') self.assertEqual(new_vacation.message, 'If urgent call me at 555-5555.') self.assertEqual(new_vacation.start_date, '2011-12-05') self.assertEqual(new_vacation.end_date, '2011-12-06') self.assertEqual(new_vacation.contacts_only, 'True') self.assertEqual(new_vacation.domain_only, 'False') new_vacation = self.client.UpdateVacation( username=conf.options.get_value('targetusername'), enable=False) self.assert_(isinstance(new_vacation, gdata.apps.emailsettings.data.EmailSettingsVacationResponder)) self.assertEqual(new_vacation.enable, 'False') def testUpdateSignature(self): if not conf.options.get_value('runlive') == 'true': return # Either load the recording or prepare to make a live request. conf.configure_cache(self.client, 'testUpdateSignature') new_signature = self.client.UpdateSignature( username=conf.options.get_value('targetusername'), signature='Regards, Joe') self.assert_(isinstance(new_signature, gdata.apps.emailsettings.data.EmailSettingsSignature)) self.assertEqual(new_signature.signature_value, 'Regards, Joe') new_signature = self.client.UpdateSignature( username=conf.options.get_value('targetusername'), signature='') self.assert_(isinstance(new_signature, gdata.apps.emailsettings.data.EmailSettingsSignature)) self.assertEqual(new_signature.signature_value, '') def testUpdateLanguage(self): if not conf.options.get_value('runlive') == 'true': return # Either load the recording or prepare to make a live request. conf.configure_cache(self.client, 'testUpdateLanguage') new_language = self.client.UpdateLanguage( username=conf.options.get_value('targetusername'), language='es') self.assert_(isinstance(new_language, gdata.apps.emailsettings.data.EmailSettingsLanguage)) self.assertEqual(new_language.language_tag, 'es') def testUpdateGeneral(self): if not conf.options.get_value('runlive') == 'true': return # Either load the recording or prepare to make a live request. conf.configure_cache(self.client, 'testUpdateGeneral') new_general = self.client.UpdateGeneralSettings( username=conf.options.get_value('targetusername'), page_size=25, arrows=True) self.assert_(isinstance(new_general, gdata.apps.emailsettings.data.EmailSettingsGeneral)) self.assertEqual(new_general.page_size, '25') self.assertEqual(new_general.arrows, 'True') new_general = self.client.UpdateGeneralSettings( username=conf.options.get_value('targetusername'), shortcuts=False, snippets=True, use_unicode=False) self.assert_(isinstance(new_general, gdata.apps.emailsettings.data.EmailSettingsGeneral)) self.assertEqual(new_general.shortcuts, 'False') self.assertEqual(new_general.snippets, 'True') self.assertEqual(new_general.use_unicode, 'False') def suite(): return conf.build_suite([EmailSettingsClientTest]) if __name__ == '__main__': unittest.TextTestRunner().run(suite()) gdata-2.0.18/tests/gdata_tests/apps/__init__.py0000640036466300116100000000000012156622363021444 0ustar afshareng00000000000000gdata-2.0.18/tests/gdata_tests/apps/live_client_test.py0000640036466300116100000001133012156622363023251 0ustar afshareng00000000000000#!/usr/bin/python2.4 # # Copyright 2011 Google Inc. All Rights Reserved. # # 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. """Live client tests for the Provisioning API.""" # This module is used for version 2 of the Google Data APIs. # These tests attempt to connect to Google servers. __author__ = 'Shraddha Gupta ' import random import unittest import gdata.apps.client import gdata.apps.data import gdata.client import gdata.data import gdata.gauth import gdata.test_config as conf conf.options.register_option(conf.APPS_DOMAIN_OPTION) class AppsClientTest(unittest.TestCase): def setUp(self): self.client = gdata.apps.client.AppsClient( domain='example.com') if conf.options.get_value('runlive') == 'true': self.client = gdata.apps.client.AppsClient( domain=conf.options.get_value('appsdomain')) if conf.options.get_value('ssl') == 'true': self.client.ssl = True conf.configure_client(self.client, 'AppsClientTest', self.client.auth_service, True) def tearDown(self): conf.close_client(self.client) def testClientConfiguration(self): self.assertEqual('apps-apis.google.com', self.client.host) self.assertEqual('2.0', self.client.api_version) self.assertEqual('apps', self.client.auth_service) self.assertEqual( ('https://apps-apis.google.com/a/feeds/user/', 'https://apps-apis.google.com/a/feeds/policies/', 'https://apps-apis.google.com/a/feeds/alias/', 'https://apps-apis.google.com/a/feeds/groups/'), self.client.auth_scopes) if conf.options.get_value('runlive') == 'true': self.assertEqual(self.client.domain, conf.options.get_value('appsdomain')) else: self.assertEqual(self.client.domain, 'example.com') def testMakeUserProvisioningUri(self): self.assertEqual('/a/feeds/%s/user/2.0' % self.client.domain, self.client._userURL()) def testMakeNicknameProvisioningUri(self): self.assertEqual('/a/feeds/%s/nickname/2.0' % self.client.domain, self.client._nicknameURL()) def testCreateRetrieveUpdateDelete(self): if not conf.options.get_value('runlive') == 'true': return # Either load the recording or prepare to make a live request. conf.configure_cache(self.client, 'testCreateUpdateDelete') rnd_number = random.randrange(0, 100001) username = 'test_user%s' % (rnd_number) nickname = 'test_alias%s' % (rnd_number) new_entry = self.client.CreateUser( user_name=username, given_name='Elizabeth', family_name='Smith', password='password', admin='true') self.assert_(isinstance(new_entry, gdata.apps.data.UserEntry)) self.assertEquals(new_entry.name.given_name, 'Elizabeth') self.assertEquals(new_entry.name.family_name, 'Smith') self.assertEquals(new_entry.login.user_name, username) self.assertEquals(new_entry.login.admin, 'true') fetched_entry = self.client.RetrieveUser(user_name=username) self.assertEquals(fetched_entry.name.given_name, 'Elizabeth') self.assertEquals(fetched_entry.name.family_name, 'Smith') self.assertEquals(fetched_entry.login.user_name, username) self.assertEquals(fetched_entry.login.admin, 'true') new_entry.name.given_name = 'Joe' new_entry.name.family_name = 'Brown' updated_entry = self.client.UpdateUser( user_name=username, user_entry=new_entry) self.assert_(isinstance(updated_entry, gdata.apps.data.UserEntry)) self.assertEqual(updated_entry.name.given_name, 'Joe') self.assertEqual(updated_entry.name.family_name, 'Brown') new_nickname = self.client.CreateNickname(user_name=username, nickname=nickname) self.assert_(isinstance(new_nickname, gdata.apps.data.NicknameEntry)) self.assertEquals(new_nickname.login.user_name, username) self.assertEquals(new_nickname.nickname.name, nickname) fetched_alias = self.client.RetrieveNickname(nickname) self.assertEquals(fetched_alias.login.user_name, username) self.assertEquals(fetched_alias.nickname.name, nickname) self.client.DeleteNickname(nickname) self.client.DeleteUser(username) def suite(): return conf.build_suite([AppsClientTest]) if __name__ == '__main__': unittest.TextTestRunner().run(suite()) gdata-2.0.18/tests/gdata_tests/apps/AppsServiceTestForGetGeneratorForAllEmailLists.pickle0000640036466300116100000025607612156622363031707 0ustar afshareng00000000000000(lp0 (ccopy_reg _reconstructor p1 (catom.mock_http MockRequest p2 c__builtin__ object p3 Ntp4 Rp5 (dp6 S'url' p7 g1 (catom.url Url p8 g3 Ntp9 Rp10 (dp11 S'path' p12 S'/accounts/ClientLogin' p13 sS'host' p14 S'www.google.com' p15 sS'protocol' p16 S'https' p17 sS'port' p18 NsS'params' p19 (dp20 sbsS'headers' p21 (dp22 S'Content-Type' p23 S'application/x-www-form-urlencoded' p24 ssS'operation' p25 S'POST' p26 sS'data' p27 S'Passwd=hogehoge&source=AppsClient+%22Unit%22+Tests&service=apps&Email=zzz%40test.shehas.net&accountType=HOSTED_OR_GOOGLE' p28 sbg1 (catom.mock_http MockResponse p29 g3 Ntp30 Rp31 (dp32 S'body' p33 S'SID=hogehoge\nLSID=hogehoge\nAuth=hogehoge\n' p34 sS'status' p35 I200 sS'reason' p36 S'OK' p37 sS'_headers' p38 (dp39 sbtp40 a(g1 (g2 g3 Ntp41 Rp42 (dp43 g7 g1 (g8 g3 Ntp44 Rp45 (dp46 g12 S'/a/feeds/test.shehas.net/emailList/2.0' p47 sg14 S'apps-apis.google.com' p48 sg16 S'https' p49 sg18 Nsg19 (dp50 sbsg21 (dp51 S'Authorization' p52 S'hogehoge' p53 sS'User-Agent' p54 S'AppsClient "Unit" Tests GData-Python/2.0.2' p55 ssg25 S'GET' p56 sg27 Nsbg1 (g29 g3 Ntp57 Rp58 (dp59 g33 S"https://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.01970-01-01T00:00:00.000ZEmailLists1https://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b000-200910131510511970-01-01T00:00:00.000Zb000-20091013151051https://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b001-200910131510511970-01-01T00:00:00.000Zb001-20091013151051https://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b002-200910131510511970-01-01T00:00:00.000Zb002-20091013151051https://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b003-200910131510511970-01-01T00:00:00.000Zb003-20091013151051https://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b004-200910131510511970-01-01T00:00:00.000Zb004-20091013151051https://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b005-200910131510511970-01-01T00:00:00.000Zb005-20091013151051https://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b006-200910131510511970-01-01T00:00:00.000Zb006-20091013151051https://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b007-200910131510511970-01-01T00:00:00.000Zb007-20091013151051https://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b008-200910131510511970-01-01T00:00:00.000Zb008-20091013151051https://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b009-200910131510511970-01-01T00:00:00.000Zb009-20091013151051https://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b010-200910131510511970-01-01T00:00:00.000Zb010-20091013151051https://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b011-200910131510511970-01-01T00:00:00.000Zb011-20091013151051https://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b012-200910131510511970-01-01T00:00:00.000Zb012-20091013151051https://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b013-200910131510511970-01-01T00:00:00.000Zb013-20091013151051https://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b014-200910131510511970-01-01T00:00:00.000Zb014-20091013151051https://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b015-200910131510511970-01-01T00:00:00.000Zb015-20091013151051https://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b016-200910131510511970-01-01T00:00:00.000Zb016-20091013151051https://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b017-200910131510511970-01-01T00:00:00.000Zb017-20091013151051https://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b018-200910131510511970-01-01T00:00:00.000Zb018-20091013151051https://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b019-200910131510511970-01-01T00:00:00.000Zb019-20091013151051https://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b020-200910131510511970-01-01T00:00:00.000Zb020-20091013151051https://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b021-200910131510511970-01-01T00:00:00.000Zb021-20091013151051https://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b022-200910131510511970-01-01T00:00:00.000Zb022-20091013151051https://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b023-200910131510511970-01-01T00:00:00.000Zb023-20091013151051https://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b024-200910131510511970-01-01T00:00:00.000Zb024-20091013151051https://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b025-200910131510511970-01-01T00:00:00.000Zb025-20091013151051https://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b026-200910131510511970-01-01T00:00:00.000Zb026-20091013151051https://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b027-200910131510511970-01-01T00:00:00.000Zb027-20091013151051https://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b028-200910131510511970-01-01T00:00:00.000Zb028-20091013151051https://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b029-200910131510511970-01-01T00:00:00.000Zb029-20091013151051https://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b030-200910131510511970-01-01T00:00:00.000Zb030-20091013151051https://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b031-200910131510511970-01-01T00:00:00.000Zb031-20091013151051https://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b032-200910131510511970-01-01T00:00:00.000Zb032-20091013151051https://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b033-200910131510511970-01-01T00:00:00.000Zb033-20091013151051https://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b034-200910131510511970-01-01T00:00:00.000Zb034-20091013151051https://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b035-200910131510511970-01-01T00:00:00.000Zb035-20091013151051https://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b036-200910131510511970-01-01T00:00:00.000Zb036-20091013151051https://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b037-200910131510511970-01-01T00:00:00.000Zb037-20091013151051https://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b038-200910131510511970-01-01T00:00:00.000Zb038-20091013151051https://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b039-200910131510511970-01-01T00:00:00.000Zb039-20091013151051https://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b040-200910131510511970-01-01T00:00:00.000Zb040-20091013151051https://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b041-200910131510511970-01-01T00:00:00.000Zb041-20091013151051https://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b042-200910131510511970-01-01T00:00:00.000Zb042-20091013151051https://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b043-200910131510511970-01-01T00:00:00.000Zb043-20091013151051https://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b044-200910131510511970-01-01T00:00:00.000Zb044-20091013151051https://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b045-200910131510511970-01-01T00:00:00.000Zb045-20091013151051https://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b046-200910131510511970-01-01T00:00:00.000Zb046-20091013151051https://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b047-200910131510511970-01-01T00:00:00.000Zb047-20091013151051https://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b048-200910131510511970-01-01T00:00:00.000Zb048-20091013151051https://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b049-200910131510511970-01-01T00:00:00.000Zb049-20091013151051https://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b050-200910131510511970-01-01T00:00:00.000Zb050-20091013151051https://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b051-200910131510511970-01-01T00:00:00.000Zb051-20091013151051https://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b052-200910131510511970-01-01T00:00:00.000Zb052-20091013151051https://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b053-200910131510511970-01-01T00:00:00.000Zb053-20091013151051https://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b054-200910131510511970-01-01T00:00:00.000Zb054-20091013151051https://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b055-200910131510511970-01-01T00:00:00.000Zb055-20091013151051https://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b056-200910131510511970-01-01T00:00:00.000Zb056-20091013151051https://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b057-200910131510511970-01-01T00:00:00.000Zb057-20091013151051https://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b058-200910131510511970-01-01T00:00:00.000Zb058-20091013151051https://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b059-200910131510511970-01-01T00:00:00.000Zb059-20091013151051https://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b060-200910131510511970-01-01T00:00:00.000Zb060-20091013151051https://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b061-200910131510511970-01-01T00:00:00.000Zb061-20091013151051https://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b062-200910131510511970-01-01T00:00:00.000Zb062-20091013151051https://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b063-200910131510511970-01-01T00:00:00.000Zb063-20091013151051https://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b064-200910131510511970-01-01T00:00:00.000Zb064-20091013151051https://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b065-200910131510511970-01-01T00:00:00.000Zb065-20091013151051https://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b066-200910131510511970-01-01T00:00:00.000Zb066-20091013151051https://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b067-200910131510511970-01-01T00:00:00.000Zb067-20091013151051https://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b068-200910131510511970-01-01T00:00:00.000Zb068-20091013151051https://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b069-200910131510511970-01-01T00:00:00.000Zb069-20091013151051https://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b070-200910131510511970-01-01T00:00:00.000Zb070-20091013151051https://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b071-200910131510511970-01-01T00:00:00.000Zb071-20091013151051https://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b072-200910131510511970-01-01T00:00:00.000Zb072-20091013151051https://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b073-200910131510511970-01-01T00:00:00.000Zb073-20091013151051https://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b074-200910131510511970-01-01T00:00:00.000Zb074-20091013151051https://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b075-200910131510511970-01-01T00:00:00.000Zb075-20091013151051https://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b076-200910131510511970-01-01T00:00:00.000Zb076-20091013151051https://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b077-200910131510511970-01-01T00:00:00.000Zb077-20091013151051https://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b078-200910131510511970-01-01T00:00:00.000Zb078-20091013151051https://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b079-200910131510511970-01-01T00:00:00.000Zb079-20091013151051https://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b080-200910131510511970-01-01T00:00:00.000Zb080-20091013151051https://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b081-200910131510511970-01-01T00:00:00.000Zb081-20091013151051https://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b082-200910131510511970-01-01T00:00:00.000Zb082-20091013151051https://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b083-200910131510511970-01-01T00:00:00.000Zb083-20091013151051https://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b084-200910131510511970-01-01T00:00:00.000Zb084-20091013151051https://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b085-200910131510511970-01-01T00:00:00.000Zb085-20091013151051https://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b086-200910131510511970-01-01T00:00:00.000Zb086-20091013151051https://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b087-200910131510511970-01-01T00:00:00.000Zb087-20091013151051https://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b088-200910131510511970-01-01T00:00:00.000Zb088-20091013151051https://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b089-200910131510511970-01-01T00:00:00.000Zb089-20091013151051https://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b090-200910131510511970-01-01T00:00:00.000Zb090-20091013151051https://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b091-200910131510511970-01-01T00:00:00.000Zb091-20091013151051https://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b092-200910131510511970-01-01T00:00:00.000Zb092-20091013151051https://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b093-200910131510511970-01-01T00:00:00.000Zb093-20091013151051https://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b094-200910131510511970-01-01T00:00:00.000Zb094-20091013151051https://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b095-200910131510511970-01-01T00:00:00.000Zb095-20091013151051https://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b096-200910131510511970-01-01T00:00:00.000Zb096-20091013151051https://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b097-200910131510511970-01-01T00:00:00.000Zb097-20091013151051https://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b098-200910131510511970-01-01T00:00:00.000Zb098-20091013151051https://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b099-200910131510511970-01-01T00:00:00.000Zb099-20091013151051" p60 sg35 I200 sg36 S'OK' p61 sg38 (dp62 sbtp63 a(g1 (g2 g3 Ntp64 Rp65 (dp66 g7 g1 (g8 g3 Ntp67 Rp68 (dp69 g12 S'/a/feeds/test.shehas.net/emailList/2.0' p70 sg14 S'apps-apis.google.com' p71 sg16 S'https' p72 sg18 Nsg19 (dp73 S'startEmailListName' p74 S'b100-20091013151051' p75 ssbsg21 (dp76 g52 g53 sg54 g55 ssg25 g56 sg27 Nsbg1 (g29 g3 Ntp77 Rp78 (dp79 g33 S"https://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.01970-01-01T00:00:00.000ZEmailLists1https://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b100-200910131510511970-01-01T00:00:00.000Zb100-20091013151051https://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b101-200910131510511970-01-01T00:00:00.000Zb101-20091013151051https://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/info1970-01-01T00:00:00.000Zinfohttps://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/testgroup21970-01-01T00:00:00.000Ztestgroup2https://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/testgroup1970-01-01T00:00:00.000Ztestgroup" p80 sg35 I200 sg36 S'OK' p81 sg38 (dp82 sbtp83 a. gdata-2.0.18/tests/gdata_tests/apps/TestDataForGeneratorTest.p0000640036466300116100000036603312156622363024430 0ustar afshareng00000000000000(lp0 (ccopy_reg _reconstructor p1 (catom.mock_service MockRequest p2 c__builtin__ object p3 Ntp4 Rp5 (dp6 S'url_params' p7 (dp8 sS'extra_headers' p9 (dp10 S'Content-Type' p11 S'application/atom+xml' p12 sS'Authorization' p13 NssS'escape_params' p14 I01 sS'uri' p15 S'https://www.google.com/a/feeds/test.shehas.net/user/2.0' p16 sS'content_type' p17 g12 sS'operation' p18 S'GET' p19 sS'data' p20 Nsbg1 (catom.mock_service MockHttpResponse p21 g3 Ntp22 Rp23 (dp24 S'body' p25 S"https://www.google.com:443/a/feeds/test.shehas.net/user/2.01970-01-01T00:00:00.000ZUsers1https://www.google.com:443/a/feeds/test.shehas.net/user/2.0/testuser-20080307140302-0001970-01-01T00:00:00.000Ztestuser-20080307140302-000https://www.google.com:443/a/feeds/test.shehas.net/user/2.0/testuser-20080307140302-0011970-01-01T00:00:00.000Ztestuser-20080307140302-001https://www.google.com:443/a/feeds/test.shehas.net/user/2.0/testuser-20080307140302-0021970-01-01T00:00:00.000Ztestuser-20080307140302-002https://www.google.com:443/a/feeds/test.shehas.net/user/2.0/testuser-20080307140302-0031970-01-01T00:00:00.000Ztestuser-20080307140302-003https://www.google.com:443/a/feeds/test.shehas.net/user/2.0/testuser-20080307140302-0041970-01-01T00:00:00.000Ztestuser-20080307140302-004https://www.google.com:443/a/feeds/test.shehas.net/user/2.0/testuser-20080307140302-0051970-01-01T00:00:00.000Ztestuser-20080307140302-005https://www.google.com:443/a/feeds/test.shehas.net/user/2.0/testuser-20080307140302-0061970-01-01T00:00:00.000Ztestuser-20080307140302-006https://www.google.com:443/a/feeds/test.shehas.net/user/2.0/testuser-20080307140302-0071970-01-01T00:00:00.000Ztestuser-20080307140302-007https://www.google.com:443/a/feeds/test.shehas.net/user/2.0/testuser-20080307140302-0081970-01-01T00:00:00.000Ztestuser-20080307140302-008https://www.google.com:443/a/feeds/test.shehas.net/user/2.0/testuser-20080307140302-0091970-01-01T00:00:00.000Ztestuser-20080307140302-009https://www.google.com:443/a/feeds/test.shehas.net/user/2.0/testuser-20080307140302-0101970-01-01T00:00:00.000Ztestuser-20080307140302-010https://www.google.com:443/a/feeds/test.shehas.net/user/2.0/testuser-20080307140302-0111970-01-01T00:00:00.000Ztestuser-20080307140302-011https://www.google.com:443/a/feeds/test.shehas.net/user/2.0/testuser-20080307140302-0121970-01-01T00:00:00.000Ztestuser-20080307140302-012https://www.google.com:443/a/feeds/test.shehas.net/user/2.0/testuser-20080307140302-0131970-01-01T00:00:00.000Ztestuser-20080307140302-013https://www.google.com:443/a/feeds/test.shehas.net/user/2.0/testuser-20080307140302-0141970-01-01T00:00:00.000Ztestuser-20080307140302-014https://www.google.com:443/a/feeds/test.shehas.net/user/2.0/testuser-20080307140302-0151970-01-01T00:00:00.000Ztestuser-20080307140302-015https://www.google.com:443/a/feeds/test.shehas.net/user/2.0/testuser-20080307140302-0161970-01-01T00:00:00.000Ztestuser-20080307140302-016https://www.google.com:443/a/feeds/test.shehas.net/user/2.0/testuser-20080307140302-0171970-01-01T00:00:00.000Ztestuser-20080307140302-017https://www.google.com:443/a/feeds/test.shehas.net/user/2.0/testuser-20080307140302-0181970-01-01T00:00:00.000Ztestuser-20080307140302-018https://www.google.com:443/a/feeds/test.shehas.net/user/2.0/testuser-20080307140302-0191970-01-01T00:00:00.000Ztestuser-20080307140302-019https://www.google.com:443/a/feeds/test.shehas.net/user/2.0/testuser-20080307140302-0201970-01-01T00:00:00.000Ztestuser-20080307140302-020https://www.google.com:443/a/feeds/test.shehas.net/user/2.0/testuser-20080307140302-0211970-01-01T00:00:00.000Ztestuser-20080307140302-021https://www.google.com:443/a/feeds/test.shehas.net/user/2.0/testuser-20080307140302-0221970-01-01T00:00:00.000Ztestuser-20080307140302-022https://www.google.com:443/a/feeds/test.shehas.net/user/2.0/testuser-20080307140302-0231970-01-01T00:00:00.000Ztestuser-20080307140302-023https://www.google.com:443/a/feeds/test.shehas.net/user/2.0/testuser-20080307140302-0241970-01-01T00:00:00.000Ztestuser-20080307140302-024https://www.google.com:443/a/feeds/test.shehas.net/user/2.0/testuser-20080307140302-0251970-01-01T00:00:00.000Ztestuser-20080307140302-025https://www.google.com:443/a/feeds/test.shehas.net/user/2.0/testuser-20080307140302-0261970-01-01T00:00:00.000Ztestuser-20080307140302-026https://www.google.com:443/a/feeds/test.shehas.net/user/2.0/testuser-20080307140302-0271970-01-01T00:00:00.000Ztestuser-20080307140302-027https://www.google.com:443/a/feeds/test.shehas.net/user/2.0/testuser-20080307140302-0281970-01-01T00:00:00.000Ztestuser-20080307140302-028https://www.google.com:443/a/feeds/test.shehas.net/user/2.0/testuser-20080307140302-0291970-01-01T00:00:00.000Ztestuser-20080307140302-029https://www.google.com:443/a/feeds/test.shehas.net/user/2.0/testuser-20080307140302-0301970-01-01T00:00:00.000Ztestuser-20080307140302-030https://www.google.com:443/a/feeds/test.shehas.net/user/2.0/testuser-20080307140302-0311970-01-01T00:00:00.000Ztestuser-20080307140302-031https://www.google.com:443/a/feeds/test.shehas.net/user/2.0/testuser-20080307140302-0321970-01-01T00:00:00.000Ztestuser-20080307140302-032https://www.google.com:443/a/feeds/test.shehas.net/user/2.0/testuser-20080307140302-0331970-01-01T00:00:00.000Ztestuser-20080307140302-033https://www.google.com:443/a/feeds/test.shehas.net/user/2.0/testuser-20080307140302-0341970-01-01T00:00:00.000Ztestuser-20080307140302-034https://www.google.com:443/a/feeds/test.shehas.net/user/2.0/testuser-20080307140302-0351970-01-01T00:00:00.000Ztestuser-20080307140302-035https://www.google.com:443/a/feeds/test.shehas.net/user/2.0/testuser-20080307140302-0361970-01-01T00:00:00.000Ztestuser-20080307140302-036https://www.google.com:443/a/feeds/test.shehas.net/user/2.0/testuser-20080307140302-0371970-01-01T00:00:00.000Ztestuser-20080307140302-037https://www.google.com:443/a/feeds/test.shehas.net/user/2.0/testuser-20080307140302-0381970-01-01T00:00:00.000Ztestuser-20080307140302-038https://www.google.com:443/a/feeds/test.shehas.net/user/2.0/testuser-20080307140302-0391970-01-01T00:00:00.000Ztestuser-20080307140302-039https://www.google.com:443/a/feeds/test.shehas.net/user/2.0/testuser-20080307140302-0401970-01-01T00:00:00.000Ztestuser-20080307140302-040https://www.google.com:443/a/feeds/test.shehas.net/user/2.0/testuser-20080307140302-0411970-01-01T00:00:00.000Ztestuser-20080307140302-041https://www.google.com:443/a/feeds/test.shehas.net/user/2.0/testuser-20080307140302-0421970-01-01T00:00:00.000Ztestuser-20080307140302-042https://www.google.com:443/a/feeds/test.shehas.net/user/2.0/testuser-20080307140302-0431970-01-01T00:00:00.000Ztestuser-20080307140302-043https://www.google.com:443/a/feeds/test.shehas.net/user/2.0/testuser-20080307140302-0441970-01-01T00:00:00.000Ztestuser-20080307140302-044https://www.google.com:443/a/feeds/test.shehas.net/user/2.0/testuser-20080307140302-0451970-01-01T00:00:00.000Ztestuser-20080307140302-045https://www.google.com:443/a/feeds/test.shehas.net/user/2.0/testuser-20080307140302-0461970-01-01T00:00:00.000Ztestuser-20080307140302-046https://www.google.com:443/a/feeds/test.shehas.net/user/2.0/testuser-20080307140302-0471970-01-01T00:00:00.000Ztestuser-20080307140302-047https://www.google.com:443/a/feeds/test.shehas.net/user/2.0/testuser-20080307140302-0481970-01-01T00:00:00.000Ztestuser-20080307140302-048https://www.google.com:443/a/feeds/test.shehas.net/user/2.0/testuser-20080307140302-0491970-01-01T00:00:00.000Ztestuser-20080307140302-049https://www.google.com:443/a/feeds/test.shehas.net/user/2.0/testuser-20080307140302-0501970-01-01T00:00:00.000Ztestuser-20080307140302-050https://www.google.com:443/a/feeds/test.shehas.net/user/2.0/testuser-20080307140302-0511970-01-01T00:00:00.000Ztestuser-20080307140302-051https://www.google.com:443/a/feeds/test.shehas.net/user/2.0/testuser-20080307140302-0521970-01-01T00:00:00.000Ztestuser-20080307140302-052https://www.google.com:443/a/feeds/test.shehas.net/user/2.0/testuser-20080307140302-0531970-01-01T00:00:00.000Ztestuser-20080307140302-053https://www.google.com:443/a/feeds/test.shehas.net/user/2.0/testuser-20080307140302-0541970-01-01T00:00:00.000Ztestuser-20080307140302-054https://www.google.com:443/a/feeds/test.shehas.net/user/2.0/testuser-20080307140302-0551970-01-01T00:00:00.000Ztestuser-20080307140302-055https://www.google.com:443/a/feeds/test.shehas.net/user/2.0/testuser-20080307140302-0561970-01-01T00:00:00.000Ztestuser-20080307140302-056https://www.google.com:443/a/feeds/test.shehas.net/user/2.0/testuser-20080307140302-0571970-01-01T00:00:00.000Ztestuser-20080307140302-057https://www.google.com:443/a/feeds/test.shehas.net/user/2.0/testuser-20080307140302-0581970-01-01T00:00:00.000Ztestuser-20080307140302-058https://www.google.com:443/a/feeds/test.shehas.net/user/2.0/testuser-20080307140302-0591970-01-01T00:00:00.000Ztestuser-20080307140302-059https://www.google.com:443/a/feeds/test.shehas.net/user/2.0/testuser-20080307140302-0601970-01-01T00:00:00.000Ztestuser-20080307140302-060https://www.google.com:443/a/feeds/test.shehas.net/user/2.0/testuser-20080307140302-0611970-01-01T00:00:00.000Ztestuser-20080307140302-061https://www.google.com:443/a/feeds/test.shehas.net/user/2.0/testuser-20080307140302-0621970-01-01T00:00:00.000Ztestuser-20080307140302-062https://www.google.com:443/a/feeds/test.shehas.net/user/2.0/testuser-20080307140302-0631970-01-01T00:00:00.000Ztestuser-20080307140302-063https://www.google.com:443/a/feeds/test.shehas.net/user/2.0/testuser-20080307140302-0641970-01-01T00:00:00.000Ztestuser-20080307140302-064https://www.google.com:443/a/feeds/test.shehas.net/user/2.0/testuser-20080307140302-0651970-01-01T00:00:00.000Ztestuser-20080307140302-065https://www.google.com:443/a/feeds/test.shehas.net/user/2.0/testuser-20080307140302-0661970-01-01T00:00:00.000Ztestuser-20080307140302-066https://www.google.com:443/a/feeds/test.shehas.net/user/2.0/testuser-20080307140302-0671970-01-01T00:00:00.000Ztestuser-20080307140302-067https://www.google.com:443/a/feeds/test.shehas.net/user/2.0/testuser-20080307140302-0681970-01-01T00:00:00.000Ztestuser-20080307140302-068https://www.google.com:443/a/feeds/test.shehas.net/user/2.0/testuser-20080307140302-0691970-01-01T00:00:00.000Ztestuser-20080307140302-069https://www.google.com:443/a/feeds/test.shehas.net/user/2.0/testuser-20080307140302-0701970-01-01T00:00:00.000Ztestuser-20080307140302-070https://www.google.com:443/a/feeds/test.shehas.net/user/2.0/testuser-20080307140302-0711970-01-01T00:00:00.000Ztestuser-20080307140302-071https://www.google.com:443/a/feeds/test.shehas.net/user/2.0/testuser-20080307140302-0721970-01-01T00:00:00.000Ztestuser-20080307140302-072https://www.google.com:443/a/feeds/test.shehas.net/user/2.0/testuser-20080307140302-0731970-01-01T00:00:00.000Ztestuser-20080307140302-073https://www.google.com:443/a/feeds/test.shehas.net/user/2.0/testuser-20080307140302-0741970-01-01T00:00:00.000Ztestuser-20080307140302-074https://www.google.com:443/a/feeds/test.shehas.net/user/2.0/testuser-20080307140302-0751970-01-01T00:00:00.000Ztestuser-20080307140302-075https://www.google.com:443/a/feeds/test.shehas.net/user/2.0/testuser-20080307140302-0761970-01-01T00:00:00.000Ztestuser-20080307140302-076https://www.google.com:443/a/feeds/test.shehas.net/user/2.0/testuser-20080307140302-0771970-01-01T00:00:00.000Ztestuser-20080307140302-077https://www.google.com:443/a/feeds/test.shehas.net/user/2.0/testuser-20080307140302-0781970-01-01T00:00:00.000Ztestuser-20080307140302-078https://www.google.com:443/a/feeds/test.shehas.net/user/2.0/testuser-20080307140302-0791970-01-01T00:00:00.000Ztestuser-20080307140302-079https://www.google.com:443/a/feeds/test.shehas.net/user/2.0/testuser-20080307140302-0801970-01-01T00:00:00.000Ztestuser-20080307140302-080https://www.google.com:443/a/feeds/test.shehas.net/user/2.0/testuser-20080307140302-0811970-01-01T00:00:00.000Ztestuser-20080307140302-081https://www.google.com:443/a/feeds/test.shehas.net/user/2.0/testuser-20080307140302-0821970-01-01T00:00:00.000Ztestuser-20080307140302-082https://www.google.com:443/a/feeds/test.shehas.net/user/2.0/testuser-20080307140302-0831970-01-01T00:00:00.000Ztestuser-20080307140302-083https://www.google.com:443/a/feeds/test.shehas.net/user/2.0/testuser-20080307140302-0841970-01-01T00:00:00.000Ztestuser-20080307140302-084https://www.google.com:443/a/feeds/test.shehas.net/user/2.0/testuser-20080307140302-0851970-01-01T00:00:00.000Ztestuser-20080307140302-085https://www.google.com:443/a/feeds/test.shehas.net/user/2.0/testuser-20080307140302-0861970-01-01T00:00:00.000Ztestuser-20080307140302-086https://www.google.com:443/a/feeds/test.shehas.net/user/2.0/testuser-20080307140302-0871970-01-01T00:00:00.000Ztestuser-20080307140302-087https://www.google.com:443/a/feeds/test.shehas.net/user/2.0/testuser-20080307140302-0881970-01-01T00:00:00.000Ztestuser-20080307140302-088https://www.google.com:443/a/feeds/test.shehas.net/user/2.0/testuser-20080307140302-0891970-01-01T00:00:00.000Ztestuser-20080307140302-089https://www.google.com:443/a/feeds/test.shehas.net/user/2.0/testuser-20080307140302-0901970-01-01T00:00:00.000Ztestuser-20080307140302-090https://www.google.com:443/a/feeds/test.shehas.net/user/2.0/testuser-20080307140302-0911970-01-01T00:00:00.000Ztestuser-20080307140302-091https://www.google.com:443/a/feeds/test.shehas.net/user/2.0/testuser-20080307140302-0921970-01-01T00:00:00.000Ztestuser-20080307140302-092https://www.google.com:443/a/feeds/test.shehas.net/user/2.0/testuser-20080307140302-0931970-01-01T00:00:00.000Ztestuser-20080307140302-093https://www.google.com:443/a/feeds/test.shehas.net/user/2.0/testuser-20080307140302-0941970-01-01T00:00:00.000Ztestuser-20080307140302-094https://www.google.com:443/a/feeds/test.shehas.net/user/2.0/testuser-20080307140302-0951970-01-01T00:00:00.000Ztestuser-20080307140302-095https://www.google.com:443/a/feeds/test.shehas.net/user/2.0/testuser-20080307140302-0961970-01-01T00:00:00.000Ztestuser-20080307140302-096https://www.google.com:443/a/feeds/test.shehas.net/user/2.0/testuser-20080307140302-0971970-01-01T00:00:00.000Ztestuser-20080307140302-097https://www.google.com:443/a/feeds/test.shehas.net/user/2.0/testuser-20080307140302-0981970-01-01T00:00:00.000Ztestuser-20080307140302-098https://www.google.com:443/a/feeds/test.shehas.net/user/2.0/testuser-20080307140302-0991970-01-01T00:00:00.000Ztestuser-20080307140302-099" p26 sS'status' p27 I200 sS'reason' p28 S'OK' p29 sS'headers' p30 (dp31 sbtp32 a(g1 (g2 g3 Ntp33 Rp34 (dp35 g7 (dp36 sg9 (dp37 g11 g12 sg13 Nssg14 I01 sg15 S'https://www.google.com:443/a/feeds/test.shehas.net/user/2.0?startUsername=tmatsuo' p38 sg17 g12 sg18 g19 sg20 Nsbg1 (g21 g3 Ntp39 Rp40 (dp41 g25 S"https://www.google.com:443/a/feeds/test.shehas.net/user/2.01970-01-01T00:00:00.000ZUsers1https://www.google.com:443/a/feeds/test.shehas.net/user/2.0/tmatsuo1970-01-01T00:00:00.000Ztmatsuo" p42 sg27 I200 sg28 S'OK' p43 sg30 (dp44 sbtp45 a.gdata-2.0.18/tests/gdata_tests/apps/multidomain/0000750036466300116100000000000012156625015021663 5ustar afshareng00000000000000gdata-2.0.18/tests/gdata_tests/apps/multidomain/data_test.py0000750036466300116100000001245112156622363024216 0ustar afshareng00000000000000#!/usr/bin/python2.4 # # Copyright 2011 Google Inc. All Rights Reserved. # # 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. """Data model tests for the Multidomain Provisioning API.""" __author__ = 'Claudio Cherubino ' import unittest import atom.core from gdata import test_data import gdata.apps.multidomain.data import gdata.test_config as conf class UserEntryTest(unittest.TestCase): def setUp(self): self.entry = atom.core.parse(test_data.MULTIDOMAIN_USER_ENTRY, gdata.apps.multidomain.data.UserEntry) self.feed = atom.core.parse(test_data.MULTIDOMAIN_USER_FEED, gdata.apps.multidomain.data.UserFeed) def testUserEntryFromString(self): self.assert_(isinstance(self.entry, gdata.apps.multidomain.data.UserEntry)) self.assertEquals(self.entry.first_name, 'Liz') self.assertEquals(self.entry.last_name, 'Smith') self.assertEquals(self.entry.email, 'liz@example.com') self.assertEquals(self.entry.password, '51eea05d46317fadd5cad6787a8f562be90b4446') self.assertEquals(self.entry.is_admin, 'true') def testUserFeedFromString(self): self.assertEquals(len(self.feed.entry), 2) self.assert_(isinstance(self.feed, gdata.apps.multidomain.data.UserFeed)) self.assert_(isinstance(self.feed.entry[0], gdata.apps.multidomain.data.UserEntry)) self.assert_(isinstance(self.feed.entry[1], gdata.apps.multidomain.data.UserEntry)) self.assertEquals( self.feed.entry[0].find_edit_link(), ('https://apps-apis.google.com/a/feeds/user/2.0/example.com/' 'admin%40example.com')) self.assertEquals(self.feed.entry[0].first_name, 'Joe') self.assertEquals(self.feed.entry[0].last_name, 'Brown') self.assertEquals(self.feed.entry[0].email, 'admin@example.com') self.assertEquals(self.feed.entry[0].is_admin, 'true') self.assertEquals(self.feed.entry[0].suspended, 'false') self.assertEquals(self.feed.entry[0].change_password_at_next_login, 'false') self.assertEquals(self.feed.entry[0].ip_whitelisted, 'false') self.assertEquals( self.feed.entry[1].find_edit_link(), ('https://apps-apis.google.com/a/feeds/user/2.0/example.com/' 'liz%40example.com')) self.assertEquals(self.feed.entry[1].first_name, 'Elizabeth') self.assertEquals(self.feed.entry[1].last_name, 'Smith') self.assertEquals(self.feed.entry[1].email, 'liz@example.com') self.assertEquals(self.feed.entry[1].is_admin, 'true') self.assertEquals(self.feed.entry[1].suspended, 'false') self.assertEquals(self.feed.entry[1].change_password_at_next_login, 'false') self.assertEquals(self.feed.entry[1].ip_whitelisted, 'false') class UserRenameRequestTest(unittest.TestCase): def setUp(self): self.entry = atom.core.parse(test_data.MULTIDOMAIN_USER_RENAME_REQUEST, gdata.apps.multidomain.data.UserRenameRequest) def testUserRenameRequestFromString(self): self.assert_(isinstance(self.entry, gdata.apps.multidomain.data.UserRenameRequest)) self.assertEquals(self.entry.new_email, 'liz@newexample4liz.com') class AliasEntryTest(unittest.TestCase): def setUp(self): self.entry = atom.core.parse(test_data.MULTIDOMAIN_ALIAS_ENTRY, gdata.apps.multidomain.data.AliasEntry) self.feed = atom.core.parse(test_data.MULTIDOMAIN_ALIAS_FEED, gdata.apps.multidomain.data.AliasFeed) def testAliasEntryFromString(self): self.assert_(isinstance(self.entry, gdata.apps.multidomain.data.AliasEntry)) self.assertEquals(self.entry.user_email, 'liz@example.com') self.assertEquals(self.entry.alias_email, 'helpdesk@gethelp_example.com') def testAliasFeedFromString(self): self.assertEquals(len(self.feed.entry), 2) self.assert_(isinstance(self.feed, gdata.apps.multidomain.data.AliasFeed)) self.assert_(isinstance(self.feed.entry[0], gdata.apps.multidomain.data.AliasEntry)) self.assert_(isinstance(self.feed.entry[1], gdata.apps.multidomain.data.AliasEntry)) self.assertEquals( self.feed.entry[0].find_edit_link(), ('https://apps-apis.google.com/a/feeds/alias/2.0/gethelp_example.com/' 'helpdesk%40gethelp_example.com')) self.assertEquals(self.feed.entry[0].user_email, 'liz@example.com') self.assertEquals(self.feed.entry[0].alias_email, 'helpdesk@gethelp_example.com') self.assertEquals( self.feed.entry[1].find_edit_link(), ('https://apps-apis.google.com/a/feeds/alias/2.0/gethelp_example.com/' 'support%40gethelp_example.com')) self.assertEquals(self.feed.entry[1].user_email, 'joe@example.com') self.assertEquals(self.feed.entry[1].alias_email, 'support@gethelp_example.com') def suite(): return conf.build_suite([UserEntryTest, AliasEntryTest]) if __name__ == '__main__': unittest.main() gdata-2.0.18/tests/gdata_tests/apps/multidomain/__init__.py0000750036466300116100000000000012156622363023770 0ustar afshareng00000000000000gdata-2.0.18/tests/gdata_tests/apps/multidomain/live_client_test.py0000750036466300116100000001423712156622363025606 0ustar afshareng00000000000000#!/usr/bin/python2.4 # # Copyright 2011 Google Inc. All Rights Reserved. # # 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. """Live client tests for the Multidomain Provisioning API.""" # This module is used for version 2 of the Google Data APIs. # These tests attempt to connect to Google servers. __author__ = 'Claudio Cherubino ' import random import unittest import gdata.apps.multidomain.client import gdata.apps.multidomain.data import gdata.client import gdata.data import gdata.gauth import gdata.test_config as conf conf.options.register_option(conf.APPS_DOMAIN_OPTION) class MultidomainProvisioningClientTest(unittest.TestCase): def setUp(self): self.client = gdata.apps.multidomain.client.MultiDomainProvisioningClient( domain='example.com') if conf.options.get_value('runlive') == 'true': self.client = gdata.apps.multidomain.client.MultiDomainProvisioningClient( domain=conf.options.get_value('appsdomain')) if conf.options.get_value('ssl') == 'true': self.client.ssl = True conf.configure_client(self.client, 'MultidomainProvisioningClientTest', self.client.auth_service, True) def tearDown(self): conf.close_client(self.client) def testClientConfiguration(self): self.assertEqual('apps-apis.google.com', self.client.host) self.assertEqual('2.0', self.client.api_version) self.assertEqual('apps', self.client.auth_service) self.assertEqual(gdata.gauth.AUTH_SCOPES['apps'], self.client.auth_scopes) if conf.options.get_value('runlive') == 'true': self.assertEqual(self.client.domain, conf.options.get_value('appsdomain')) else: self.assertEqual(self.client.domain, 'example.com') def testMakeMultidomainUserProvisioningUri(self): self.assertEqual('/a/feeds/user/2.0/%s' % self.client.domain, self.client.MakeMultidomainUserProvisioningUri()) self.assertEqual('/a/feeds/user/2.0/%s/liz@example.com' % self.client.domain, self.client.MakeMultidomainUserProvisioningUri( email='liz@example.com')) self.assertEqual('/a/feeds/user/2.0/%s?start=%s' % (self.client.domain, 'liz%40example.com'), self.client.MakeMultidomainUserProvisioningUri( params={'start': 'liz@example.com'})) def testMakeMultidomainAliasProvisioningUri(self): self.assertEqual('/a/feeds/alias/2.0/%s' % self.client.domain, self.client.MakeMultidomainAliasProvisioningUri()) self.assertEqual('/a/feeds/alias/2.0/%s/liz@example.com' % self.client.domain, self.client.MakeMultidomainAliasProvisioningUri( email='liz@example.com')) self.assertEqual('/a/feeds/alias/2.0/%s?start=%s' % (self.client.domain, 'liz%40example.com'), self.client.MakeMultidomainAliasProvisioningUri( params={'start': 'liz@example.com'})) def testCreateRetrieveUpdateDelete(self): if not conf.options.get_value('runlive') == 'true': return # Either load the recording or prepare to make a live request. conf.configure_cache(self.client, 'testCreateUpdateDelete') rnd_number = random.randrange(0, 100001) email = 'test_user%s@%s' % (rnd_number, self.client.domain) alias = 'test_alias%s@%s' % (rnd_number, self.client.domain) new_entry = self.client.CreateUser( email, 'Elizabeth', 'Smith', '51eea05d46317fadd5cad6787a8f562be90b4446', 'true', hash_function='SHA-1') self.assert_(isinstance(new_entry, gdata.apps.multidomain.data.UserEntry)) self.assertEquals(new_entry.first_name, 'Elizabeth') self.assertEquals(new_entry.last_name, 'Smith') self.assertEquals(new_entry.email, email) self.assertEquals(new_entry.password, '51eea05d46317fadd5cad6787a8f562be90b4446') self.assertEquals(new_entry.is_admin, 'true') fetched_entry = self.client.RetrieveUser(email=email) self.assertEquals(fetched_entry.first_name, 'Elizabeth') self.assertEquals(fetched_entry.last_name, 'Smith') self.assertEquals(fetched_entry.email, email) self.assertEquals(fetched_entry.is_admin, 'true') new_entry.first_name = 'Joe' new_entry.last_name = 'Brown' updated_entry = self.client.UpdateUser( email=email, user_entry=new_entry) self.assert_(isinstance(updated_entry, gdata.apps.multidomain.data.UserEntry)) self.assertEqual(updated_entry.first_name, 'Joe') self.assertEqual(updated_entry.last_name, 'Brown') new_email = 'renamed_user%s@%s' % (rnd_number, self.client.domain) renamed_entry = self.client.RenameUser( old_email=email, new_email=new_email) self.assert_(isinstance(renamed_entry, gdata.apps.multidomain.data.UserRenameRequest)) self.assertEqual(renamed_entry.new_email, new_email) new_alias = self.client.CreateAlias(new_email, alias) self.assert_(isinstance(new_alias, gdata.apps.multidomain.data.AliasEntry)) self.assertEquals(new_alias.user_email, new_email) self.assertEquals(new_alias.alias_email, alias) fetched_alias = self.client.RetrieveAlias(alias) self.assertEquals(fetched_alias.user_email, new_email) self.assertEquals(fetched_alias.alias_email, alias) fetched_aliases = self.client.RetrieveAllUserAliases(new_email) self.assertEquals(fetched_aliases.entry[0].user_email, new_email) self.assertEquals(fetched_aliases.entry[0].alias_email, email) self.assertEquals(fetched_aliases.entry[1].user_email, new_email) self.assertEquals(fetched_aliases.entry[1].alias_email, alias) self.client.DeleteAlias(alias) self.client.DeleteUser(new_email) def suite(): return conf.build_suite([MultidomainProvisioningClientTest]) if __name__ == '__main__': unittest.TextTestRunner().run(suite()) gdata-2.0.18/tests/gdata_tests/apps/groups/0000750036466300116100000000000012156625015020660 5ustar afshareng00000000000000gdata-2.0.18/tests/gdata_tests/apps/groups/service_test.py0000640036466300116100000002155012156622363023740 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright (C) 2008 Google # # 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. """Test for Groups service.""" __author__ = 'google-apps-apis@googlegroups.com' import unittest try: from xml.etree import ElementTree except ImportError: from elementtree import ElementTree import atom import gdata.apps import gdata.apps.service import gdata.apps.groups.service import getpass import time domain = '' admin_email = '' admin_password = '' username = '' class GroupsTest(unittest.TestCase): """Test for the GroupsService.""" def setUp(self): self.postfix = time.strftime("%Y%m%d%H%M%S") self.apps_client = gdata.apps.service.AppsService( email=admin_email, domain=domain, password=admin_password, source='GroupsClient "Unit" Tests') self.apps_client.ProgrammaticLogin() self.groups_client = gdata.apps.groups.service.GroupsService( email=admin_email, domain=domain, password=admin_password, source='GroupsClient "Unit" Tests') self.groups_client.ProgrammaticLogin() self.created_users = [] self.created_groups = [] self.createUsers(); def createUsers(self): user_name = 'yujimatsuo-' + self.postfix family_name = 'Matsuo' given_name = 'Yuji' password = '123$$abc' suspended = 'false' try: self.user_yuji = self.apps_client.CreateUser( user_name=user_name, family_name=family_name, given_name=given_name, password=password, suspended=suspended) print 'User ' + user_name + ' created' except Exception, e: self.fail('Unexpected exception occurred: %s' % e) self.created_users.append(self.user_yuji) user_name = 'taromatsuo-' + self.postfix family_name = 'Matsuo' given_name = 'Taro' password = '123$$abc' suspended = 'false' try: self.user_taro = self.apps_client.CreateUser( user_name=user_name, family_name=family_name, given_name=given_name, password=password, suspended=suspended) print 'User ' + user_name + ' created' except Exception, e: self.fail('Unexpected exception occurred: %s' % e) self.created_users.append(self.user_taro) def tearDown(self): print '\n' for user in self.created_users: try: self.apps_client.DeleteUser(user.login.user_name) print 'User ' + user.login.user_name + ' deleted' except Exception, e: print e for group in self.created_groups: try: self.groups_client.DeleteGroup(group) print 'Group ' + group + ' deleted' except Exception, e: print e def test001GroupsMethods(self): # tests CreateGroup method group01_id = 'group01-' + self.postfix group02_id = 'group02-' + self.postfix try: created_group01 = self.groups_client.CreateGroup(group01_id, 'US Sales 1', 'Testing', gdata.apps.groups.service.PERMISSION_OWNER) created_group02 = self.groups_client.CreateGroup(group02_id, 'US Sales 2', 'Testing', gdata.apps.groups.service.PERMISSION_OWNER) except Exception, e: self.fail('Unexpected exception occurred: %s' % e) self.assertEquals(created_group01['groupId'], group01_id) self.assertEquals(created_group02['groupId'], group02_id) self.created_groups.append(group01_id) self.created_groups.append(group02_id) # tests UpdateGroup method try: updated_group = self.groups_client.UpdateGroup(group01_id, 'Updated!', 'Testing', gdata.apps.groups.service.PERMISSION_OWNER) except Exception, e: self.fail('Unexpected exception occurred: %s' % e) self.assertEquals(updated_group['groupName'], 'Updated!') # tests RetrieveGroup method try: retrieved_group = self.groups_client.RetrieveGroup(group01_id) except Exception, e: self.fail('Unexpected exception occurred: %s' % e) self.assertEquals(retrieved_group['groupId'], group01_id + '@' + domain) # tests RetrieveAllGroups method try: retrieved_groups = self.groups_client.RetrieveAllGroups() except Exception, e: self.fail('Unexpected exception occurred: %s' % e) self.assertEquals(len(retrieved_groups), len(self.apps_client.RetrieveAllEmailLists().entry)) # tests AddMemberToGroup try: added_member = self.groups_client.AddMemberToGroup( self.user_yuji.login.user_name, group01_id) self.groups_client.AddMemberToGroup( self.user_taro.login.user_name, group02_id) self.groups_client.AddMemberToGroup( group01_id, group02_id) except Exception, e: self.fail('Unexpected exception occurred: %s' % e) self.assertEquals(added_member['memberId'], self.user_yuji.login.user_name) # tests RetrieveGroups method try: retrieved_direct_groups = self.groups_client.RetrieveGroups( self.user_yuji.login.user_name, True) retrieved_groups = self.groups_client.RetrieveGroups( self.user_yuji.login.user_name, False) except Exception, e: self.fail('Unexpected exception occurred: %s' % e) self.assertEquals(len(retrieved_direct_groups), 1) # TODO: Enable this test after a directOnly bug is fixed #self.assertEquals(len(retrieved_groups), 2) # tests IsMember method try: result = self.groups_client.IsMember( self.user_yuji.login.user_name, group01_id) except Exception, e: self.fail('Unexpected exception occurred: %s' % e) self.assertEquals(result, True) # tests RetrieveMember method try: retrieved_member = self.groups_client.RetrieveMember( self.user_yuji.login.user_name, group01_id) except Exception, e: self.fail('Unexpected exception occurred: %s' % e) self.assertEquals(retrieved_member['memberId'], self.user_yuji.login.user_name + '@' + domain) # tests RetrieveAllMembers method try: retrieved_members = self.groups_client.RetrieveAllMembers(group01_id) except Exception, e: self.fail('Unexpected exception occurred: %s' % e) self.assertEquals(len(retrieved_members), 1) # tests RemoveMemberFromGroup method try: self.groups_client.RemoveMemberFromGroup(self.user_yuji.login.user_name, group01_id) retrieved_members = self.groups_client.RetrieveAllMembers(group01_id) except Exception, e: self.fail('Unexpected exception occurred: %s' % e) self.assertEquals(len(retrieved_members), 0) # tests AddOwnerToGroup try: added_owner = self.groups_client.AddOwnerToGroup( self.user_yuji.login.user_name, group01_id) except Exception, e: self.fail('Unexpected exception occurred: %s' % e) self.assertEquals(added_owner['email'], self.user_yuji.login.user_name) # tests IsOwner method try: result = self.groups_client.IsOwner( self.user_yuji.login.user_name, group01_id) except Exception, e: self.fail('Unexpected exception occurred: %s' % e) self.assertEquals(result, True) # tests RetrieveOwner method try: retrieved_owner = self.groups_client.RetrieveOwner( self.user_yuji.login.user_name, group01_id) except Exception, e: self.fail('Unexpected exception occurred: %s' % e) self.assertEquals(retrieved_owner['email'], self.user_yuji.login.user_name + '@' + domain) # tests RetrieveAllOwners method try: retrieved_owners = self.groups_client.RetrieveAllOwners(group01_id) except Exception, e: self.fail('Unexpected exception occurred: %s' % e) self.assertEquals(len(retrieved_owners), 1) # tests RemoveOwnerFromGroup method try: self.groups_client.RemoveOwnerFromGroup(self.user_yuji.login.user_name, group01_id) retrieved_owners = self.groups_client.RetrieveAllOwners(group01_id) except Exception, e: self.fail('Unexpected exception occurred: %s' % e) self.assertEquals(len(retrieved_owners), 0) if __name__ == '__main__': print("""Google Apps Groups Service Tests NOTE: Please run these tests only with a test user account. """) domain = raw_input('Google Apps domain: ') admin_email = '%s@%s' % (raw_input('Administrator username: '), domain) admin_password = getpass.getpass('Administrator password: ') unittest.main() gdata-2.0.18/tests/gdata_tests/apps/groups/data_test.py0000640036466300116100000001103612156622363023207 0ustar afshareng00000000000000#!/usr/bin/python2.4 # # Copyright 2011 Google Inc. All Rights Reserved. # # 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. """Data model tests for the Groups Provisioning API.""" __author__ = 'Shraddha gupta ' import unittest import atom.core from gdata import test_data import gdata.apps.groups.data import gdata.test_config as conf class GroupEntryTest(unittest.TestCase): def setUp(self): self.entry = atom.core.parse(test_data.GROUP_ENTRY, gdata.apps.groups.data.GroupEntry, 2) self.feed = atom.core.parse(test_data.GROUP_FEED, gdata.apps.groups.data.GroupFeed, 2) def testGroupEntryFromString(self): self.assert_(isinstance(self.entry, gdata.apps.groups.data.GroupEntry)) self.assertEquals(self.entry.group_id, 'trial@srkapps.com') self.assertEquals(self.entry.group_name, 'Trial') self.assertEquals(self.entry.email_permission, 'Domain') self.assertEquals(self.entry.description, 'For try') def testGroupFeedFromString(self): self.assertEquals(len(self.feed.entry), 2) self.assert_(isinstance(self.feed, gdata.apps.groups.data.GroupFeed)) self.assert_(isinstance(self.feed.entry[0], gdata.apps.groups.data.GroupEntry)) self.assert_(isinstance(self.feed.entry[1], gdata.apps.groups.data.GroupEntry)) self.assertEquals( self.feed.entry[0].find_edit_link(), ('http://apps-apis.google.com/a/feeds/group/2.0/srkapps.com/' 'firstgroup%40srkapps.com')) self.assertEquals(self.feed.entry[0].group_id, 'firstgroup@srkapps.com') self.assertEquals(self.feed.entry[0].group_name, 'FirstGroup') self.assertEquals(self.feed.entry[0].email_permission, 'Domain') self.assertEquals(self.feed.entry[0].description, 'First group') self.assertEquals( self.feed.entry[1].find_edit_link(), ('http://apps-apis.google.com/a/feeds/group/2.0/srkapps.com/' 'trial%40srkapps.com')) self.assertEquals(self.feed.entry[1].group_id, 'trial@srkapps.com') self.assertEquals(self.feed.entry[1].group_name, 'Trial') self.assertEquals(self.feed.entry[1].email_permission, 'Domain') self.assertEquals(self.feed.entry[1].description, 'For try') class GroupMemberEntryTest(unittest.TestCase): def setUp(self): self.entry = atom.core.parse(test_data.GROUP_MEMBER_ENTRY, gdata.apps.groups.data.GroupMemberEntry) self.feed = atom.core.parse(test_data.GROUP_MEMBER_FEED, gdata.apps.groups.data.GroupMemberFeed) def testGroupMemberEntryFromString(self): self.assert_(isinstance(self.entry, gdata.apps.groups.data.GroupMemberEntry)) self.assertEquals(self.entry.member_id, 'abcd12310@srkapps.com') self.assertEquals(self.entry.member_type, 'User') self.assertEquals(self.entry.direct_member, 'true') def testGroupMemberFeedFromString(self): self.assertEquals(len(self.feed.entry), 2) self.assert_(isinstance(self.feed, gdata.apps.groups.data.GroupMemberFeed)) self.assert_(isinstance(self.feed.entry[0], gdata.apps.groups.data.GroupMemberEntry)) self.assert_(isinstance(self.feed.entry[1], gdata.apps.groups.data.GroupMemberEntry)) self.assertEquals( self.feed.entry[0].find_edit_link(), ('http://apps-apis.google.com/a/feeds/group/2.0/srkapps.com/trial/' 'member/abcd12310%40srkapps.com')) self.assertEquals(self.feed.entry[0].member_id, 'abcd12310@srkapps.com') self.assertEquals(self.feed.entry[0].member_type, 'User') self.assertEquals(self.feed.entry[0].direct_member, 'true') self.assertEquals( self.feed.entry[1].find_edit_link(), ('http://apps-apis.google.com/a/feeds/group/2.0/srkapps.com/trial/' 'member/neha.technocrat%40srkapps.com')) self.assertEquals(self.feed.entry[1].member_id, 'neha.technocrat@srkapps.com') self.assertEquals(self.feed.entry[1].member_type, 'User') self.assertEquals(self.feed.entry[1].direct_member, 'true') def suite(): return conf.build_suite([GroupEntryTest, GroupMemberEntryTest]) if __name__ == '__main__': unittest.main() gdata-2.0.18/tests/gdata_tests/apps/groups/__init__.py0000640036466300116100000000000012156622363022763 0ustar afshareng00000000000000gdata-2.0.18/tests/gdata_tests/apps/groups/live_client_test.py0000640036466300116100000001302012156622363024566 0ustar afshareng00000000000000#!/usr/bin/python2.4 # # Copyright 2011 Google Inc. All Rights Reserved. # # 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. """Live client tests for the Groups Provisioning API.""" # This module is used for version 2 of the Google Data APIs. # These tests attempt to connect to Google servers. __author__ = 'Shraddha gupta ' import random import unittest import gdata.apps.groups.client import gdata.apps.groups.data import gdata.client import gdata.data import gdata.gauth import gdata.test_config as conf conf.options.register_option(conf.APPS_DOMAIN_OPTION) class GroupsProvisioningClientTest(unittest.TestCase): def setUp(self): self.client = gdata.apps.groups.client.GroupsProvisioningClient( domain='example.com') if conf.options.get_value('runlive') == 'true': self.client = gdata.apps.groups.client.GroupsProvisioningClient( domain=conf.options.get_value('appsdomain')) if conf.options.get_value('ssl') == 'true': self.client.ssl = True conf.configure_client(self.client, 'GroupsProvisioningClientTest', self.client.auth_service, True) def tearDown(self): conf.close_client(self.client) def testClientConfiguration(self): self.assertEqual('apps-apis.google.com', self.client.host) self.assertEqual('2.0', self.client.api_version) self.assertEqual('apps', self.client.auth_service) self.assertEqual( ('https://apps-apis.google.com/a/feeds/user/', 'https://apps-apis.google.com/a/feeds/policies/', 'https://apps-apis.google.com/a/feeds/alias/', 'https://apps-apis.google.com/a/feeds/groups/'), self.client.auth_scopes) if conf.options.get_value('runlive') == 'true': self.assertEqual(self.client.domain, conf.options.get_value('appsdomain')) else: self.assertEqual(self.client.domain, 'example.com') def testMakeGroupProvisioningUri(self): self.assertEqual('/a/feeds/group/2.0/%s' % self.client.domain, self.client.MakeGroupProvisioningUri()) self.assertEqual('/a/feeds/group/2.0/%s/firstgroup@example.com' % self.client.domain, self.client.MakeGroupProvisioningUri(group_id='firstgroup@example.com')) self.assertEqual( '/a/feeds/group/2.0/%s?member=member1' % self.client.domain, self.client.MakeGroupProvisioningUri(params={'member':'member1'})) def testMakeGroupMembersUri(self): self.assertEqual('/a/feeds/group/2.0/%s/firstgroup@example.com/member' % self.client.domain, self.client.MakeGroupMembersUri(group_id='firstgroup@example.com')) self.assertEqual( '/a/feeds/group/2.0/%s/firstgroup@example.com/member/liz@example.com' % self.client.domain, self.client.MakeGroupMembersUri( group_id='firstgroup@example.com', member_id='liz@example.com')) def testCreateRetrieveUpdateDelete(self): if not conf.options.get_value('runlive') == 'true': return # Either load the recording or prepare to make a live request. conf.configure_cache(self.client, 'testCreateUpdateDelete') rnd_number = random.randrange(0, 100) group_id = 'test_groupid%s@%s' % (rnd_number, self.client.domain) group_name = 'test_groupname%s' % (rnd_number) member_id = 'test_member%s@%s' % (rnd_number, self.client.domain) new_group = self.client.CreateGroup( group_id=group_id, group_name=group_name, description='Test Group', email_permission='Domain') self.assert_(isinstance(new_group, gdata.apps.groups.data.GroupEntry)) self.assertEquals(new_group.group_id, group_id) self.assertEquals(new_group.group_name, group_name) self.assertEquals(new_group.description, 'Test Group') self.assertEquals(new_group.email_permission, 'Domain') fetched_entry = self.client.RetrieveGroup(group_id=group_id) self.assert_(isinstance(fetched_entry, gdata.apps.groups.data.GroupEntry)) self.assertEquals(new_group.group_id, group_id) self.assertEquals(new_group.group_name, group_name) self.assertEquals(new_group.description, 'Test Group') self.assertEquals(new_group.email_permission, 'Domain') new_group.group_name = 'updated name' updated_group = self.client.UpdateGroup( group_id=group_id, group_entry=new_group) self.assert_(isinstance(updated_group, gdata.apps.groups.data.GroupEntry)) self.assertEqual(updated_group.group_name, 'updated name') new_member = self.client.AddMemberToGroup(group_id=group_id, member_id=member_id) self.assert_(isinstance(new_member, gdata.apps.groups.data.GroupMemberEntry)) self.assertEquals(new_member.member_id, member_id) fetched_member = self.client.RetrieveGroupMember(group_id=group_id, member_id=member_id) self.assertEquals(fetched_member.member_id, member_id) self.client.RemoveMemberFromGroup(group_id=group_id, member_id=member_id) self.client.DeleteGroup(group_id=group_id) def suite(): return conf.build_suite([GroupsProvisioningClientTest]) if __name__ == '__main__': unittest.TextTestRunner().run(suite()) gdata-2.0.18/tests/gdata_tests/apps/service_test_using_mock.py0000750036466300116100000001127012156622363024637 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright (C) 2007 SIOS Technology, 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. __author__ = 'tmatsuo@sios.com (Takashi Matsuo)' import unittest import time, os try: from xml.etree import ElementTree except ImportError: from elementtree import ElementTree import re import pickle import atom import atom.http import atom.service from atom import mock_http import gdata.apps import gdata.apps.service import getpass apps_domain = 'test.shehas.net' apps_username = '' apps_password = '' def conceal_secrets(recordings): ret = [] for rec in recordings: req, res = rec if req.data: req.data = re.sub(r'Passwd=[^&]+', 'Passwd=hogehoge', req.data) if res.body: res.body = re.sub(r'SID=[^\n]+', 'SID=hogehoge', res.body) res.body = re.sub(r'LSID=[^\n]+', 'LSID=hogehoge', res.body) res.body = re.sub(r'Auth=[^\n]+', 'Auth=hogehoge', res.body) if req.headers.has_key('Authorization'): req.headers['Authorization'] = 'hogehoge' ret.append((req, res)) return ret class AppsServiceBaseTest(object): def setUp(self): self.datafile = os.path.join(os.path.dirname(os.path.abspath(__file__)), "%s.pickle" % self.name) if os.path.isfile(self.datafile): f = open(self.datafile, "rb") data = pickle.load(f) f.close() http_client = mock_http.MockHttpClient(recordings=data) else: real_client = atom.http.ProxiedHttpClient() http_client = mock_http.MockHttpClient(real_client=real_client) email = apps_username + '@' + apps_domain self.apps_client = gdata.apps.service.AppsService( email=email, domain=apps_domain, password=apps_password, source='AppsClient "Unit" Tests') self.apps_client.http_client = http_client self.apps_client.ProgrammaticLogin() def tearDown(self): if self.apps_client.http_client.real_client: # create pickle file f = open(self.datafile, "wb") data = conceal_secrets(self.apps_client.http_client.recordings) pickle.dump(data, f) f.close() class AppsServiceTestForGetGeneratorForAllRecipients(AppsServiceBaseTest, unittest.TestCase): name = "AppsServiceTestForGetGeneratorForAllRecipients" def testGetGeneratorForAllRecipients(self): """Tests GetGeneratorForAllRecipientss method""" generator = self.apps_client.GetGeneratorForAllRecipients( "b101-20091013151051") i = 0 for recipient_feed in generator: for a_recipient in recipient_feed.entry: i = i + 1 self.assert_(i == 102) class AppsServiceTestForGetGeneratorForAllEmailLists(AppsServiceBaseTest, unittest.TestCase): name = "AppsServiceTestForGetGeneratorForAllEmailLists" def testGetGeneratorForAllEmailLists(self): """Tests GetGeneratorForAllEmailLists method""" generator = self.apps_client.GetGeneratorForAllEmailLists() i = 0 for emaillist_feed in generator: for a_emaillist in emaillist_feed.entry: i = i + 1 self.assert_(i == 105) class AppsServiceTestForGetGeneratorForAllNicknames(AppsServiceBaseTest, unittest.TestCase): name = "AppsServiceTestForGetGeneratorForAllNicknames" def testGetGeneratorForAllNicknames(self): """Tests GetGeneratorForAllNicknames method""" generator = self.apps_client.GetGeneratorForAllNicknames() i = 0 for nickname_feed in generator: for a_nickname in nickname_feed.entry: i = i + 1 self.assert_(i == 102) class AppsServiceTestForGetGeneratorForAllUsers(AppsServiceBaseTest, unittest.TestCase): name = "AppsServiceTestForGetGeneratorForAllUsers" def testGetGeneratorForAllUsers(self): """Tests GetGeneratorForAllUsers method""" generator = self.apps_client.GetGeneratorForAllUsers() i = 0 for user_feed in generator: for a_user in user_feed.entry: i = i + 1 self.assert_(i == 102) if __name__ == '__main__': print ('The tests may delete or update your data.') apps_username = raw_input('Please enter your username: ') apps_password = getpass.getpass() unittest.main() gdata-2.0.18/tests/gdata_tests/apps/AppsServiceTestForGetGeneratorForAllRecipients.pickle0000640036466300116100000026562612156622363031747 0ustar afshareng00000000000000(lp0 (ccopy_reg _reconstructor p1 (catom.mock_http MockRequest p2 c__builtin__ object p3 Ntp4 Rp5 (dp6 S'url' p7 g1 (catom.url Url p8 g3 Ntp9 Rp10 (dp11 S'path' p12 S'/accounts/ClientLogin' p13 sS'host' p14 S'www.google.com' p15 sS'protocol' p16 S'https' p17 sS'port' p18 NsS'params' p19 (dp20 sbsS'headers' p21 (dp22 S'Content-Type' p23 S'application/x-www-form-urlencoded' p24 ssS'operation' p25 S'POST' p26 sS'data' p27 S'Passwd=hogehoge&source=AppsClient+%22Unit%22+Tests&service=apps&Email=zzz%40test.shehas.net&accountType=HOSTED_OR_GOOGLE' p28 sbg1 (catom.mock_http MockResponse p29 g3 Ntp30 Rp31 (dp32 S'body' p33 S'SID=hogehoge\nLSID=hogehoge\nAuth=hogehoge\n' p34 sS'status' p35 I200 sS'reason' p36 S'OK' p37 sS'_headers' p38 (dp39 sbtp40 a(g1 (g2 g3 Ntp41 Rp42 (dp43 g7 g1 (g8 g3 Ntp44 Rp45 (dp46 g12 S'/a/feeds/test.shehas.net/emailList/2.0/b101-20091013151051/recipient' p47 sg14 S'apps-apis.google.com' p48 sg16 S'https' p49 sg18 Nsg19 (dp50 sbsg21 (dp51 S'Authorization' p52 S'hogehoge' p53 sS'User-Agent' p54 S'AppsClient "Unit" Tests GData-Python/2.0.2' p55 ssg25 S'GET' p56 sg27 Nsbg1 (g29 g3 Ntp57 Rp58 (dp59 g33 S"https://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b101-20091013151051/recipient1970-01-01T00:00:00.000ZRecipients for email list b101-200910131510511https://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b101-20091013151051/recipient/b000-20091001132401%40test.shehas.net1970-01-01T00:00:00.000Zb000-20091001132401@test.shehas.nethttps://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b101-20091013151051/recipient/b001-20091001132401%40test.shehas.net1970-01-01T00:00:00.000Zb001-20091001132401@test.shehas.nethttps://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b101-20091013151051/recipient/b002-20091001132401%40test.shehas.net1970-01-01T00:00:00.000Zb002-20091001132401@test.shehas.nethttps://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b101-20091013151051/recipient/b003-20091001132401%40test.shehas.net1970-01-01T00:00:00.000Zb003-20091001132401@test.shehas.nethttps://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b101-20091013151051/recipient/b004-20091001132401%40test.shehas.net1970-01-01T00:00:00.000Zb004-20091001132401@test.shehas.nethttps://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b101-20091013151051/recipient/b005-20091001132401%40test.shehas.net1970-01-01T00:00:00.000Zb005-20091001132401@test.shehas.nethttps://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b101-20091013151051/recipient/b006-20091001132401%40test.shehas.net1970-01-01T00:00:00.000Zb006-20091001132401@test.shehas.nethttps://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b101-20091013151051/recipient/b007-20091001132401%40test.shehas.net1970-01-01T00:00:00.000Zb007-20091001132401@test.shehas.nethttps://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b101-20091013151051/recipient/b008-20091001132401%40test.shehas.net1970-01-01T00:00:00.000Zb008-20091001132401@test.shehas.nethttps://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b101-20091013151051/recipient/b009-20091001132401%40test.shehas.net1970-01-01T00:00:00.000Zb009-20091001132401@test.shehas.nethttps://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b101-20091013151051/recipient/b010-20091001132401%40test.shehas.net1970-01-01T00:00:00.000Zb010-20091001132401@test.shehas.nethttps://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b101-20091013151051/recipient/b011-20091001132401%40test.shehas.net1970-01-01T00:00:00.000Zb011-20091001132401@test.shehas.nethttps://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b101-20091013151051/recipient/b012-20091001132401%40test.shehas.net1970-01-01T00:00:00.000Zb012-20091001132401@test.shehas.nethttps://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b101-20091013151051/recipient/b013-20091001132401%40test.shehas.net1970-01-01T00:00:00.000Zb013-20091001132401@test.shehas.nethttps://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b101-20091013151051/recipient/b014-20091001132401%40test.shehas.net1970-01-01T00:00:00.000Zb014-20091001132401@test.shehas.nethttps://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b101-20091013151051/recipient/b015-20091001132401%40test.shehas.net1970-01-01T00:00:00.000Zb015-20091001132401@test.shehas.nethttps://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b101-20091013151051/recipient/b016-20091001132401%40test.shehas.net1970-01-01T00:00:00.000Zb016-20091001132401@test.shehas.nethttps://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b101-20091013151051/recipient/b017-20091001132401%40test.shehas.net1970-01-01T00:00:00.000Zb017-20091001132401@test.shehas.nethttps://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b101-20091013151051/recipient/b018-20091001132401%40test.shehas.net1970-01-01T00:00:00.000Zb018-20091001132401@test.shehas.nethttps://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b101-20091013151051/recipient/b019-20091001132401%40test.shehas.net1970-01-01T00:00:00.000Zb019-20091001132401@test.shehas.nethttps://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b101-20091013151051/recipient/b020-20091001132401%40test.shehas.net1970-01-01T00:00:00.000Zb020-20091001132401@test.shehas.nethttps://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b101-20091013151051/recipient/b021-20091001132401%40test.shehas.net1970-01-01T00:00:00.000Zb021-20091001132401@test.shehas.nethttps://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b101-20091013151051/recipient/b022-20091001132401%40test.shehas.net1970-01-01T00:00:00.000Zb022-20091001132401@test.shehas.nethttps://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b101-20091013151051/recipient/b023-20091001132401%40test.shehas.net1970-01-01T00:00:00.000Zb023-20091001132401@test.shehas.nethttps://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b101-20091013151051/recipient/b024-20091001132401%40test.shehas.net1970-01-01T00:00:00.000Zb024-20091001132401@test.shehas.nethttps://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b101-20091013151051/recipient/b025-20091001132401%40test.shehas.net1970-01-01T00:00:00.000Zb025-20091001132401@test.shehas.nethttps://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b101-20091013151051/recipient/b026-20091001132401%40test.shehas.net1970-01-01T00:00:00.000Zb026-20091001132401@test.shehas.nethttps://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b101-20091013151051/recipient/b027-20091001132401%40test.shehas.net1970-01-01T00:00:00.000Zb027-20091001132401@test.shehas.nethttps://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b101-20091013151051/recipient/b028-20091001132401%40test.shehas.net1970-01-01T00:00:00.000Zb028-20091001132401@test.shehas.nethttps://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b101-20091013151051/recipient/b029-20091001132401%40test.shehas.net1970-01-01T00:00:00.000Zb029-20091001132401@test.shehas.nethttps://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b101-20091013151051/recipient/b030-20091001132401%40test.shehas.net1970-01-01T00:00:00.000Zb030-20091001132401@test.shehas.nethttps://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b101-20091013151051/recipient/b031-20091001132401%40test.shehas.net1970-01-01T00:00:00.000Zb031-20091001132401@test.shehas.nethttps://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b101-20091013151051/recipient/b032-20091001132401%40test.shehas.net1970-01-01T00:00:00.000Zb032-20091001132401@test.shehas.nethttps://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b101-20091013151051/recipient/b033-20091001132401%40test.shehas.net1970-01-01T00:00:00.000Zb033-20091001132401@test.shehas.nethttps://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b101-20091013151051/recipient/b034-20091001132401%40test.shehas.net1970-01-01T00:00:00.000Zb034-20091001132401@test.shehas.nethttps://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b101-20091013151051/recipient/b035-20091001132401%40test.shehas.net1970-01-01T00:00:00.000Zb035-20091001132401@test.shehas.nethttps://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b101-20091013151051/recipient/b036-20091001132401%40test.shehas.net1970-01-01T00:00:00.000Zb036-20091001132401@test.shehas.nethttps://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b101-20091013151051/recipient/b037-20091001132401%40test.shehas.net1970-01-01T00:00:00.000Zb037-20091001132401@test.shehas.nethttps://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b101-20091013151051/recipient/b038-20091001132401%40test.shehas.net1970-01-01T00:00:00.000Zb038-20091001132401@test.shehas.nethttps://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b101-20091013151051/recipient/b039-20091001132401%40test.shehas.net1970-01-01T00:00:00.000Zb039-20091001132401@test.shehas.nethttps://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b101-20091013151051/recipient/b040-20091001132401%40test.shehas.net1970-01-01T00:00:00.000Zb040-20091001132401@test.shehas.nethttps://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b101-20091013151051/recipient/b041-20091001132401%40test.shehas.net1970-01-01T00:00:00.000Zb041-20091001132401@test.shehas.nethttps://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b101-20091013151051/recipient/b042-20091001132401%40test.shehas.net1970-01-01T00:00:00.000Zb042-20091001132401@test.shehas.nethttps://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b101-20091013151051/recipient/b043-20091001132401%40test.shehas.net1970-01-01T00:00:00.000Zb043-20091001132401@test.shehas.nethttps://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b101-20091013151051/recipient/b044-20091001132401%40test.shehas.net1970-01-01T00:00:00.000Zb044-20091001132401@test.shehas.nethttps://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b101-20091013151051/recipient/b045-20091001132401%40test.shehas.net1970-01-01T00:00:00.000Zb045-20091001132401@test.shehas.nethttps://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b101-20091013151051/recipient/b046-20091001132401%40test.shehas.net1970-01-01T00:00:00.000Zb046-20091001132401@test.shehas.nethttps://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b101-20091013151051/recipient/b047-20091001132401%40test.shehas.net1970-01-01T00:00:00.000Zb047-20091001132401@test.shehas.nethttps://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b101-20091013151051/recipient/b048-20091001132401%40test.shehas.net1970-01-01T00:00:00.000Zb048-20091001132401@test.shehas.nethttps://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b101-20091013151051/recipient/b049-20091001132401%40test.shehas.net1970-01-01T00:00:00.000Zb049-20091001132401@test.shehas.nethttps://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b101-20091013151051/recipient/b050-20091001132401%40test.shehas.net1970-01-01T00:00:00.000Zb050-20091001132401@test.shehas.nethttps://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b101-20091013151051/recipient/b051-20091001132401%40test.shehas.net1970-01-01T00:00:00.000Zb051-20091001132401@test.shehas.nethttps://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b101-20091013151051/recipient/b052-20091001132401%40test.shehas.net1970-01-01T00:00:00.000Zb052-20091001132401@test.shehas.nethttps://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b101-20091013151051/recipient/b053-20091001132401%40test.shehas.net1970-01-01T00:00:00.000Zb053-20091001132401@test.shehas.nethttps://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b101-20091013151051/recipient/b054-20091001132401%40test.shehas.net1970-01-01T00:00:00.000Zb054-20091001132401@test.shehas.nethttps://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b101-20091013151051/recipient/b055-20091001132401%40test.shehas.net1970-01-01T00:00:00.000Zb055-20091001132401@test.shehas.nethttps://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b101-20091013151051/recipient/b056-20091001132401%40test.shehas.net1970-01-01T00:00:00.000Zb056-20091001132401@test.shehas.nethttps://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b101-20091013151051/recipient/b057-20091001132401%40test.shehas.net1970-01-01T00:00:00.000Zb057-20091001132401@test.shehas.nethttps://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b101-20091013151051/recipient/b058-20091001132401%40test.shehas.net1970-01-01T00:00:00.000Zb058-20091001132401@test.shehas.nethttps://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b101-20091013151051/recipient/b059-20091001132401%40test.shehas.net1970-01-01T00:00:00.000Zb059-20091001132401@test.shehas.nethttps://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b101-20091013151051/recipient/b060-20091001132401%40test.shehas.net1970-01-01T00:00:00.000Zb060-20091001132401@test.shehas.nethttps://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b101-20091013151051/recipient/b061-20091001132401%40test.shehas.net1970-01-01T00:00:00.000Zb061-20091001132401@test.shehas.nethttps://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b101-20091013151051/recipient/b062-20091001132401%40test.shehas.net1970-01-01T00:00:00.000Zb062-20091001132401@test.shehas.nethttps://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b101-20091013151051/recipient/b063-20091001132401%40test.shehas.net1970-01-01T00:00:00.000Zb063-20091001132401@test.shehas.nethttps://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b101-20091013151051/recipient/b064-20091001132401%40test.shehas.net1970-01-01T00:00:00.000Zb064-20091001132401@test.shehas.nethttps://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b101-20091013151051/recipient/b065-20091001132401%40test.shehas.net1970-01-01T00:00:00.000Zb065-20091001132401@test.shehas.nethttps://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b101-20091013151051/recipient/b066-20091001132401%40test.shehas.net1970-01-01T00:00:00.000Zb066-20091001132401@test.shehas.nethttps://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b101-20091013151051/recipient/b067-20091001132401%40test.shehas.net1970-01-01T00:00:00.000Zb067-20091001132401@test.shehas.nethttps://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b101-20091013151051/recipient/b068-20091001132401%40test.shehas.net1970-01-01T00:00:00.000Zb068-20091001132401@test.shehas.nethttps://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b101-20091013151051/recipient/b069-20091001132401%40test.shehas.net1970-01-01T00:00:00.000Zb069-20091001132401@test.shehas.nethttps://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b101-20091013151051/recipient/b070-20091001132401%40test.shehas.net1970-01-01T00:00:00.000Zb070-20091001132401@test.shehas.nethttps://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b101-20091013151051/recipient/b071-20091001132401%40test.shehas.net1970-01-01T00:00:00.000Zb071-20091001132401@test.shehas.nethttps://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b101-20091013151051/recipient/b072-20091001132401%40test.shehas.net1970-01-01T00:00:00.000Zb072-20091001132401@test.shehas.nethttps://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b101-20091013151051/recipient/b073-20091001132401%40test.shehas.net1970-01-01T00:00:00.000Zb073-20091001132401@test.shehas.nethttps://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b101-20091013151051/recipient/b074-20091001132401%40test.shehas.net1970-01-01T00:00:00.000Zb074-20091001132401@test.shehas.nethttps://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b101-20091013151051/recipient/b075-20091001132401%40test.shehas.net1970-01-01T00:00:00.000Zb075-20091001132401@test.shehas.nethttps://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b101-20091013151051/recipient/b076-20091001132401%40test.shehas.net1970-01-01T00:00:00.000Zb076-20091001132401@test.shehas.nethttps://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b101-20091013151051/recipient/b077-20091001132401%40test.shehas.net1970-01-01T00:00:00.000Zb077-20091001132401@test.shehas.nethttps://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b101-20091013151051/recipient/b078-20091001132401%40test.shehas.net1970-01-01T00:00:00.000Zb078-20091001132401@test.shehas.nethttps://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b101-20091013151051/recipient/b079-20091001132401%40test.shehas.net1970-01-01T00:00:00.000Zb079-20091001132401@test.shehas.nethttps://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b101-20091013151051/recipient/b080-20091001132401%40test.shehas.net1970-01-01T00:00:00.000Zb080-20091001132401@test.shehas.nethttps://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b101-20091013151051/recipient/b081-20091001132401%40test.shehas.net1970-01-01T00:00:00.000Zb081-20091001132401@test.shehas.nethttps://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b101-20091013151051/recipient/b082-20091001132401%40test.shehas.net1970-01-01T00:00:00.000Zb082-20091001132401@test.shehas.nethttps://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b101-20091013151051/recipient/b083-20091001132401%40test.shehas.net1970-01-01T00:00:00.000Zb083-20091001132401@test.shehas.nethttps://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b101-20091013151051/recipient/b084-20091001132401%40test.shehas.net1970-01-01T00:00:00.000Zb084-20091001132401@test.shehas.nethttps://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b101-20091013151051/recipient/b085-20091001132401%40test.shehas.net1970-01-01T00:00:00.000Zb085-20091001132401@test.shehas.nethttps://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b101-20091013151051/recipient/b086-20091001132401%40test.shehas.net1970-01-01T00:00:00.000Zb086-20091001132401@test.shehas.nethttps://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b101-20091013151051/recipient/b087-20091001132401%40test.shehas.net1970-01-01T00:00:00.000Zb087-20091001132401@test.shehas.nethttps://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b101-20091013151051/recipient/b088-20091001132401%40test.shehas.net1970-01-01T00:00:00.000Zb088-20091001132401@test.shehas.nethttps://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b101-20091013151051/recipient/b089-20091001132401%40test.shehas.net1970-01-01T00:00:00.000Zb089-20091001132401@test.shehas.nethttps://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b101-20091013151051/recipient/b090-20091001132401%40test.shehas.net1970-01-01T00:00:00.000Zb090-20091001132401@test.shehas.nethttps://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b101-20091013151051/recipient/b091-20091001132401%40test.shehas.net1970-01-01T00:00:00.000Zb091-20091001132401@test.shehas.nethttps://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b101-20091013151051/recipient/b092-20091001132401%40test.shehas.net1970-01-01T00:00:00.000Zb092-20091001132401@test.shehas.nethttps://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b101-20091013151051/recipient/b093-20091001132401%40test.shehas.net1970-01-01T00:00:00.000Zb093-20091001132401@test.shehas.nethttps://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b101-20091013151051/recipient/b094-20091001132401%40test.shehas.net1970-01-01T00:00:00.000Zb094-20091001132401@test.shehas.nethttps://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b101-20091013151051/recipient/b095-20091001132401%40test.shehas.net1970-01-01T00:00:00.000Zb095-20091001132401@test.shehas.nethttps://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b101-20091013151051/recipient/b096-20091001132401%40test.shehas.net1970-01-01T00:00:00.000Zb096-20091001132401@test.shehas.nethttps://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b101-20091013151051/recipient/b097-20091001132401%40test.shehas.net1970-01-01T00:00:00.000Zb097-20091001132401@test.shehas.nethttps://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b101-20091013151051/recipient/john.amin%40test.shehas.net1970-01-01T00:00:00.000Zjohn.amin@test.shehas.nethttps://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b101-20091013151051/recipient/john.doe%40test.shehas.net1970-01-01T00:00:00.000Zjohn.doe@test.shehas.net" p60 sg35 I200 sg36 S'OK' p61 sg38 (dp62 sbtp63 a(g1 (g2 g3 Ntp64 Rp65 (dp66 g7 g1 (g8 g3 Ntp67 Rp68 (dp69 g12 S'/a/feeds/test.shehas.net/emailList/2.0/b101-20091013151051/recipient' p70 sg14 S'apps-apis.google.com' p71 sg16 S'https' p72 sg18 Nsg19 (dp73 S'startRecipient' p74 S'john@test.shehas.net' p75 ssbsg21 (dp76 g52 g53 sg54 g55 ssg25 g56 sg27 Nsbg1 (g29 g3 Ntp77 Rp78 (dp79 g33 S"https://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b101-20091013151051/recipient1970-01-01T00:00:00.000ZRecipients for email list b101-200910131510511https://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b101-20091013151051/recipient/john%40test.shehas.net1970-01-01T00:00:00.000Zjohn@test.shehas.nethttps://apps-apis.google.com/a/feeds/test.shehas.net/emailList/2.0/b101-20091013151051/recipient/zzz%40test.shehas.net1970-01-01T00:00:00.000Zzzz@test.shehas.net" p80 sg35 I200 sg36 S'OK' p81 sg38 (dp82 sbtp83 a.gdata-2.0.18/tests/gdata_tests/apps/AppsServiceTestForGetGeneratorForAllNicknames.pickle0000640036466300116100000021472512156622363031544 0ustar afshareng00000000000000(lp0 (ccopy_reg _reconstructor p1 (catom.mock_http MockRequest p2 c__builtin__ object p3 Ntp4 Rp5 (dp6 S'url' p7 g1 (catom.url Url p8 g3 Ntp9 Rp10 (dp11 S'path' p12 S'/accounts/ClientLogin' p13 sS'host' p14 S'www.google.com' p15 sS'protocol' p16 S'https' p17 sS'port' p18 NsS'params' p19 (dp20 sbsS'headers' p21 (dp22 S'Content-Type' p23 S'application/x-www-form-urlencoded' p24 ssS'operation' p25 S'POST' p26 sS'data' p27 S'Passwd=hogehoge&source=AppsClient+%22Unit%22+Tests&service=apps&Email=zzz%40test.shehas.net&accountType=HOSTED_OR_GOOGLE' p28 sbg1 (catom.mock_http MockResponse p29 g3 Ntp30 Rp31 (dp32 S'body' p33 S'SID=hogehoge\nLSID=hogehoge\nAuth=hogehoge\n' p34 sS'status' p35 I200 sS'reason' p36 S'OK' p37 sS'_headers' p38 (dp39 sbtp40 a(g1 (g2 g3 Ntp41 Rp42 (dp43 g7 g1 (g8 g3 Ntp44 Rp45 (dp46 g12 S'/a/feeds/test.shehas.net/nickname/2.0' p47 sg14 S'apps-apis.google.com' p48 sg16 S'https' p49 sg18 Nsg19 (dp50 sbsg21 (dp51 S'Authorization' p52 S'hogehoge' p53 sS'User-Agent' p54 S'AppsClient "Unit" Tests GData-Python/2.0.2' p55 ssg25 S'GET' p56 sg27 Nsbg1 (g29 g3 Ntp57 Rp58 (dp59 g33 S"https://apps-apis.google.com/a/feeds/test.shehas.net/nickname/2.01970-01-01T00:00:00.000ZNicknames1https://apps-apis.google.com/a/feeds/test.shehas.net/nickname/2.0/b000-200910131453361970-01-01T00:00:00.000Zb000-20091013145336https://apps-apis.google.com/a/feeds/test.shehas.net/nickname/2.0/b001-200910131453361970-01-01T00:00:00.000Zb001-20091013145336https://apps-apis.google.com/a/feeds/test.shehas.net/nickname/2.0/b002-200910131453361970-01-01T00:00:00.000Zb002-20091013145336https://apps-apis.google.com/a/feeds/test.shehas.net/nickname/2.0/b003-200910131453361970-01-01T00:00:00.000Zb003-20091013145336https://apps-apis.google.com/a/feeds/test.shehas.net/nickname/2.0/b004-200910131453361970-01-01T00:00:00.000Zb004-20091013145336https://apps-apis.google.com/a/feeds/test.shehas.net/nickname/2.0/b005-200910131453361970-01-01T00:00:00.000Zb005-20091013145336https://apps-apis.google.com/a/feeds/test.shehas.net/nickname/2.0/b006-200910131453361970-01-01T00:00:00.000Zb006-20091013145336https://apps-apis.google.com/a/feeds/test.shehas.net/nickname/2.0/b007-200910131453361970-01-01T00:00:00.000Zb007-20091013145336https://apps-apis.google.com/a/feeds/test.shehas.net/nickname/2.0/b008-200910131453361970-01-01T00:00:00.000Zb008-20091013145336https://apps-apis.google.com/a/feeds/test.shehas.net/nickname/2.0/b009-200910131453361970-01-01T00:00:00.000Zb009-20091013145336https://apps-apis.google.com/a/feeds/test.shehas.net/nickname/2.0/b010-200910131453361970-01-01T00:00:00.000Zb010-20091013145336https://apps-apis.google.com/a/feeds/test.shehas.net/nickname/2.0/b011-200910131453361970-01-01T00:00:00.000Zb011-20091013145336https://apps-apis.google.com/a/feeds/test.shehas.net/nickname/2.0/b012-200910131453361970-01-01T00:00:00.000Zb012-20091013145336https://apps-apis.google.com/a/feeds/test.shehas.net/nickname/2.0/b013-200910131453361970-01-01T00:00:00.000Zb013-20091013145336https://apps-apis.google.com/a/feeds/test.shehas.net/nickname/2.0/b014-200910131453361970-01-01T00:00:00.000Zb014-20091013145336https://apps-apis.google.com/a/feeds/test.shehas.net/nickname/2.0/b015-200910131453361970-01-01T00:00:00.000Zb015-20091013145336https://apps-apis.google.com/a/feeds/test.shehas.net/nickname/2.0/b016-200910131453361970-01-01T00:00:00.000Zb016-20091013145336https://apps-apis.google.com/a/feeds/test.shehas.net/nickname/2.0/b017-200910131453361970-01-01T00:00:00.000Zb017-20091013145336https://apps-apis.google.com/a/feeds/test.shehas.net/nickname/2.0/b018-200910131453361970-01-01T00:00:00.000Zb018-20091013145336https://apps-apis.google.com/a/feeds/test.shehas.net/nickname/2.0/b019-200910131453361970-01-01T00:00:00.000Zb019-20091013145336https://apps-apis.google.com/a/feeds/test.shehas.net/nickname/2.0/b020-200910131453361970-01-01T00:00:00.000Zb020-20091013145336https://apps-apis.google.com/a/feeds/test.shehas.net/nickname/2.0/b021-200910131453361970-01-01T00:00:00.000Zb021-20091013145336https://apps-apis.google.com/a/feeds/test.shehas.net/nickname/2.0/b022-200910131453361970-01-01T00:00:00.000Zb022-20091013145336https://apps-apis.google.com/a/feeds/test.shehas.net/nickname/2.0/b023-200910131453361970-01-01T00:00:00.000Zb023-20091013145336https://apps-apis.google.com/a/feeds/test.shehas.net/nickname/2.0/b024-200910131453361970-01-01T00:00:00.000Zb024-20091013145336https://apps-apis.google.com/a/feeds/test.shehas.net/nickname/2.0/b025-200910131453361970-01-01T00:00:00.000Zb025-20091013145336https://apps-apis.google.com/a/feeds/test.shehas.net/nickname/2.0/b026-200910131453361970-01-01T00:00:00.000Zb026-20091013145336https://apps-apis.google.com/a/feeds/test.shehas.net/nickname/2.0/b027-200910131453361970-01-01T00:00:00.000Zb027-20091013145336https://apps-apis.google.com/a/feeds/test.shehas.net/nickname/2.0/b028-200910131453361970-01-01T00:00:00.000Zb028-20091013145336https://apps-apis.google.com/a/feeds/test.shehas.net/nickname/2.0/b029-200910131453361970-01-01T00:00:00.000Zb029-20091013145336https://apps-apis.google.com/a/feeds/test.shehas.net/nickname/2.0/b030-200910131453361970-01-01T00:00:00.000Zb030-20091013145336https://apps-apis.google.com/a/feeds/test.shehas.net/nickname/2.0/b031-200910131453361970-01-01T00:00:00.000Zb031-20091013145336https://apps-apis.google.com/a/feeds/test.shehas.net/nickname/2.0/b032-200910131453361970-01-01T00:00:00.000Zb032-20091013145336https://apps-apis.google.com/a/feeds/test.shehas.net/nickname/2.0/b033-200910131453361970-01-01T00:00:00.000Zb033-20091013145336https://apps-apis.google.com/a/feeds/test.shehas.net/nickname/2.0/b034-200910131453361970-01-01T00:00:00.000Zb034-20091013145336https://apps-apis.google.com/a/feeds/test.shehas.net/nickname/2.0/b035-200910131453361970-01-01T00:00:00.000Zb035-20091013145336https://apps-apis.google.com/a/feeds/test.shehas.net/nickname/2.0/b036-200910131453361970-01-01T00:00:00.000Zb036-20091013145336https://apps-apis.google.com/a/feeds/test.shehas.net/nickname/2.0/b037-200910131453361970-01-01T00:00:00.000Zb037-20091013145336https://apps-apis.google.com/a/feeds/test.shehas.net/nickname/2.0/b038-200910131453361970-01-01T00:00:00.000Zb038-20091013145336https://apps-apis.google.com/a/feeds/test.shehas.net/nickname/2.0/b039-200910131453361970-01-01T00:00:00.000Zb039-20091013145336https://apps-apis.google.com/a/feeds/test.shehas.net/nickname/2.0/b040-200910131453361970-01-01T00:00:00.000Zb040-20091013145336https://apps-apis.google.com/a/feeds/test.shehas.net/nickname/2.0/b041-200910131453361970-01-01T00:00:00.000Zb041-20091013145336https://apps-apis.google.com/a/feeds/test.shehas.net/nickname/2.0/b042-200910131453361970-01-01T00:00:00.000Zb042-20091013145336https://apps-apis.google.com/a/feeds/test.shehas.net/nickname/2.0/b043-200910131453361970-01-01T00:00:00.000Zb043-20091013145336https://apps-apis.google.com/a/feeds/test.shehas.net/nickname/2.0/b044-200910131453361970-01-01T00:00:00.000Zb044-20091013145336https://apps-apis.google.com/a/feeds/test.shehas.net/nickname/2.0/b045-200910131453361970-01-01T00:00:00.000Zb045-20091013145336https://apps-apis.google.com/a/feeds/test.shehas.net/nickname/2.0/b046-200910131453361970-01-01T00:00:00.000Zb046-20091013145336https://apps-apis.google.com/a/feeds/test.shehas.net/nickname/2.0/b047-200910131453361970-01-01T00:00:00.000Zb047-20091013145336https://apps-apis.google.com/a/feeds/test.shehas.net/nickname/2.0/b048-200910131453361970-01-01T00:00:00.000Zb048-20091013145336https://apps-apis.google.com/a/feeds/test.shehas.net/nickname/2.0/b049-200910131453361970-01-01T00:00:00.000Zb049-20091013145336https://apps-apis.google.com/a/feeds/test.shehas.net/nickname/2.0/b050-200910131453361970-01-01T00:00:00.000Zb050-20091013145336https://apps-apis.google.com/a/feeds/test.shehas.net/nickname/2.0/b051-200910131453361970-01-01T00:00:00.000Zb051-20091013145336https://apps-apis.google.com/a/feeds/test.shehas.net/nickname/2.0/b052-200910131453361970-01-01T00:00:00.000Zb052-20091013145336https://apps-apis.google.com/a/feeds/test.shehas.net/nickname/2.0/b053-200910131453361970-01-01T00:00:00.000Zb053-20091013145336https://apps-apis.google.com/a/feeds/test.shehas.net/nickname/2.0/b054-200910131453361970-01-01T00:00:00.000Zb054-20091013145336https://apps-apis.google.com/a/feeds/test.shehas.net/nickname/2.0/b055-200910131453361970-01-01T00:00:00.000Zb055-20091013145336https://apps-apis.google.com/a/feeds/test.shehas.net/nickname/2.0/b056-200910131453361970-01-01T00:00:00.000Zb056-20091013145336https://apps-apis.google.com/a/feeds/test.shehas.net/nickname/2.0/b057-200910131453361970-01-01T00:00:00.000Zb057-20091013145336https://apps-apis.google.com/a/feeds/test.shehas.net/nickname/2.0/b058-200910131453361970-01-01T00:00:00.000Zb058-20091013145336https://apps-apis.google.com/a/feeds/test.shehas.net/nickname/2.0/b059-200910131453361970-01-01T00:00:00.000Zb059-20091013145336https://apps-apis.google.com/a/feeds/test.shehas.net/nickname/2.0/b060-200910131453361970-01-01T00:00:00.000Zb060-20091013145336https://apps-apis.google.com/a/feeds/test.shehas.net/nickname/2.0/b061-200910131453361970-01-01T00:00:00.000Zb061-20091013145336https://apps-apis.google.com/a/feeds/test.shehas.net/nickname/2.0/b062-200910131453361970-01-01T00:00:00.000Zb062-20091013145336https://apps-apis.google.com/a/feeds/test.shehas.net/nickname/2.0/b063-200910131453361970-01-01T00:00:00.000Zb063-20091013145336https://apps-apis.google.com/a/feeds/test.shehas.net/nickname/2.0/b064-200910131453361970-01-01T00:00:00.000Zb064-20091013145336https://apps-apis.google.com/a/feeds/test.shehas.net/nickname/2.0/b065-200910131453361970-01-01T00:00:00.000Zb065-20091013145336https://apps-apis.google.com/a/feeds/test.shehas.net/nickname/2.0/b066-200910131453361970-01-01T00:00:00.000Zb066-20091013145336https://apps-apis.google.com/a/feeds/test.shehas.net/nickname/2.0/b067-200910131453361970-01-01T00:00:00.000Zb067-20091013145336https://apps-apis.google.com/a/feeds/test.shehas.net/nickname/2.0/b068-200910131453361970-01-01T00:00:00.000Zb068-20091013145336https://apps-apis.google.com/a/feeds/test.shehas.net/nickname/2.0/b069-200910131453361970-01-01T00:00:00.000Zb069-20091013145336https://apps-apis.google.com/a/feeds/test.shehas.net/nickname/2.0/b070-200910131453361970-01-01T00:00:00.000Zb070-20091013145336https://apps-apis.google.com/a/feeds/test.shehas.net/nickname/2.0/b071-200910131453361970-01-01T00:00:00.000Zb071-20091013145336https://apps-apis.google.com/a/feeds/test.shehas.net/nickname/2.0/b072-200910131453361970-01-01T00:00:00.000Zb072-20091013145336https://apps-apis.google.com/a/feeds/test.shehas.net/nickname/2.0/b073-200910131453361970-01-01T00:00:00.000Zb073-20091013145336https://apps-apis.google.com/a/feeds/test.shehas.net/nickname/2.0/b074-200910131453361970-01-01T00:00:00.000Zb074-20091013145336https://apps-apis.google.com/a/feeds/test.shehas.net/nickname/2.0/b075-200910131453361970-01-01T00:00:00.000Zb075-20091013145336https://apps-apis.google.com/a/feeds/test.shehas.net/nickname/2.0/b076-200910131453361970-01-01T00:00:00.000Zb076-20091013145336https://apps-apis.google.com/a/feeds/test.shehas.net/nickname/2.0/b077-200910131453361970-01-01T00:00:00.000Zb077-20091013145336https://apps-apis.google.com/a/feeds/test.shehas.net/nickname/2.0/b078-200910131453361970-01-01T00:00:00.000Zb078-20091013145336https://apps-apis.google.com/a/feeds/test.shehas.net/nickname/2.0/b079-200910131453361970-01-01T00:00:00.000Zb079-20091013145336https://apps-apis.google.com/a/feeds/test.shehas.net/nickname/2.0/b080-200910131453361970-01-01T00:00:00.000Zb080-20091013145336https://apps-apis.google.com/a/feeds/test.shehas.net/nickname/2.0/b081-200910131453361970-01-01T00:00:00.000Zb081-20091013145336https://apps-apis.google.com/a/feeds/test.shehas.net/nickname/2.0/b082-200910131453361970-01-01T00:00:00.000Zb082-20091013145336https://apps-apis.google.com/a/feeds/test.shehas.net/nickname/2.0/b083-200910131453361970-01-01T00:00:00.000Zb083-20091013145336https://apps-apis.google.com/a/feeds/test.shehas.net/nickname/2.0/b084-200910131453361970-01-01T00:00:00.000Zb084-20091013145336https://apps-apis.google.com/a/feeds/test.shehas.net/nickname/2.0/b085-200910131453361970-01-01T00:00:00.000Zb085-20091013145336https://apps-apis.google.com/a/feeds/test.shehas.net/nickname/2.0/b086-200910131453361970-01-01T00:00:00.000Zb086-20091013145336https://apps-apis.google.com/a/feeds/test.shehas.net/nickname/2.0/b087-200910131453361970-01-01T00:00:00.000Zb087-20091013145336https://apps-apis.google.com/a/feeds/test.shehas.net/nickname/2.0/b088-200910131453361970-01-01T00:00:00.000Zb088-20091013145336https://apps-apis.google.com/a/feeds/test.shehas.net/nickname/2.0/b089-200910131453361970-01-01T00:00:00.000Zb089-20091013145336https://apps-apis.google.com/a/feeds/test.shehas.net/nickname/2.0/b090-200910131453361970-01-01T00:00:00.000Zb090-20091013145336https://apps-apis.google.com/a/feeds/test.shehas.net/nickname/2.0/b091-200910131453361970-01-01T00:00:00.000Zb091-20091013145336https://apps-apis.google.com/a/feeds/test.shehas.net/nickname/2.0/b092-200910131453361970-01-01T00:00:00.000Zb092-20091013145336https://apps-apis.google.com/a/feeds/test.shehas.net/nickname/2.0/b093-200910131453361970-01-01T00:00:00.000Zb093-20091013145336https://apps-apis.google.com/a/feeds/test.shehas.net/nickname/2.0/b094-200910131453361970-01-01T00:00:00.000Zb094-20091013145336https://apps-apis.google.com/a/feeds/test.shehas.net/nickname/2.0/b095-200910131453361970-01-01T00:00:00.000Zb095-20091013145336https://apps-apis.google.com/a/feeds/test.shehas.net/nickname/2.0/b096-200910131453361970-01-01T00:00:00.000Zb096-20091013145336https://apps-apis.google.com/a/feeds/test.shehas.net/nickname/2.0/b097-200910131453361970-01-01T00:00:00.000Zb097-20091013145336https://apps-apis.google.com/a/feeds/test.shehas.net/nickname/2.0/b098-200910131453361970-01-01T00:00:00.000Zb098-20091013145336https://apps-apis.google.com/a/feeds/test.shehas.net/nickname/2.0/b099-200910131453361970-01-01T00:00:00.000Zb099-20091013145336" p60 sg35 I200 sg36 S'OK' p61 sg38 (dp62 sbtp63 a(g1 (g2 g3 Ntp64 Rp65 (dp66 g7 g1 (g8 g3 Ntp67 Rp68 (dp69 g12 S'/a/feeds/test.shehas.net/nickname/2.0' p70 sg14 S'apps-apis.google.com' p71 sg16 S'https' p72 sg18 Nsg19 (dp73 S'startNickname' p74 S'b100-20091013145336' p75 ssbsg21 (dp76 g52 g53 sg54 g55 ssg25 g56 sg27 Nsbg1 (g29 g3 Ntp77 Rp78 (dp79 g33 S"https://apps-apis.google.com/a/feeds/test.shehas.net/nickname/2.01970-01-01T00:00:00.000ZNicknames1https://apps-apis.google.com/a/feeds/test.shehas.net/nickname/2.0/b100-200910131453361970-01-01T00:00:00.000Zb100-20091013145336https://apps-apis.google.com/a/feeds/test.shehas.net/nickname/2.0/b101-200910131453361970-01-01T00:00:00.000Zb101-20091013145336" p80 sg35 I200 sg36 S'OK' p81 sg38 (dp82 sbtp83 a.gdata-2.0.18/tests/gdata_tests/blogger_test.py0000750036466300116100000000741712156622363021447 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright (C) 2008 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. __author__ = 'api.jscudder (Jeff Scudder)' import unittest from gdata import test_data import gdata.blogger import atom class BlogEntryTest(unittest.TestCase): def testBlogEntryFromString(self): entry = gdata.blogger.BlogEntryFromString(test_data.BLOG_ENTRY) self.assertEquals(entry.GetBlogName(), 'blogName') self.assertEquals(entry.GetBlogId(), 'blogID') self.assertEquals(entry.title.text, 'Lizzy\'s Diary') def testBlogPostFeedFromString(self): feed = gdata.blogger.BlogPostFeedFromString(test_data.BLOG_POSTS_FEED) self.assertEquals(len(feed.entry), 1) self.assert_(isinstance(feed, gdata.blogger.BlogPostFeed)) self.assert_(isinstance(feed.entry[0], gdata.blogger.BlogPostEntry)) self.assertEquals(feed.entry[0].GetPostId(), 'postID') self.assertEquals(feed.entry[0].GetBlogId(), 'blogID') self.assertEquals(feed.entry[0].title.text, 'Quite disagreeable') def testCommentFeedFromString(self): feed = gdata.blogger.CommentFeedFromString(test_data.BLOG_COMMENTS_FEED) self.assertEquals(len(feed.entry), 1) self.assert_(isinstance(feed, gdata.blogger.CommentFeed)) self.assert_(isinstance(feed.entry[0], gdata.blogger.CommentEntry)) self.assertEquals(feed.entry[0].GetBlogId(), 'blogID') self.assertEquals(feed.entry[0].GetCommentId(), 'commentID') self.assertEquals(feed.entry[0].title.text, 'This is my first comment') self.assertEquals(feed.entry[0].in_reply_to.source, 'http://blogName.blogspot.com/feeds/posts/default/postID') self.assertEquals(feed.entry[0].in_reply_to.ref, 'tag:blogger.com,1999:blog-blogID.post-postID') self.assertEquals(feed.entry[0].in_reply_to.href, 'http://blogName.blogspot.com/2007/04/first-post.html') self.assertEquals(feed.entry[0].in_reply_to.type, 'text/html') def testIdParsing(self): entry = gdata.blogger.BlogEntry() entry.id = atom.Id( text='tag:blogger.com,1999:user-146606542.blog-4023408167658848') self.assertEquals(entry.GetBlogId(), '4023408167658848') entry.id = atom.Id(text='tag:blogger.com,1999:blog-4023408167658848') self.assertEquals(entry.GetBlogId(), '4023408167658848') class InReplyToTest(unittest.TestCase): def testToAndFromString(self): in_reply_to = gdata.blogger.InReplyTo(href='http://example.com/href', ref='http://example.com/ref', source='http://example.com/my_post', type='text/html') xml_string = str(in_reply_to) parsed = gdata.blogger.InReplyToFromString(xml_string) self.assertEquals(parsed.source, in_reply_to.source) self.assertEquals(parsed.href, in_reply_to.href) self.assertEquals(parsed.ref, in_reply_to.ref) self.assertEquals(parsed.type, in_reply_to.type) class CommentEntryTest(unittest.TestCase): def testToAndFromString(self): comment = gdata.blogger.CommentEntry(content=atom.Content(text='Nifty!'), in_reply_to=gdata.blogger.InReplyTo( source='http://example.com/my_post')) parsed = gdata.blogger.CommentEntryFromString(str(comment)) self.assertEquals(parsed.in_reply_to.source, comment.in_reply_to.source) self.assertEquals(parsed.content.text, comment.content.text) if __name__ == '__main__': unittest.main() gdata-2.0.18/tests/gdata_tests/codesearch_test.py0000750036466300116100000000361212156622363022117 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright (C) 2007 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. __author__ = 'api.jscudder (Jeffrey Scudder)' import unittest import gdata.codesearch import gdata.test_data class CodeSearchDataTest(unittest.TestCase): def setUp(self): self.feed = gdata.codesearch.CodesearchFeedFromString( gdata.test_data.CODE_SEARCH_FEED) def testCorrectXmlConversion(self): self.assert_(self.feed.id.text == 'http://www.google.com/codesearch/feeds/search?q=malloc') self.assert_(len(self.feed.entry) == 10) for entry in self.feed.entry: if entry.id.text == ('http://www.google.com/codesearch?hl=en&q=+ma' 'lloc+show:LDjwp-Iqc7U:84hEYaYsZk8:xDGReDhvNi0&sa=N&ct=rx&cd=1' '&cs_p=http://www.gnu.org&cs_f=software/autoconf/manual/autoco' 'nf-2.60/autoconf.html-002&cs_p=http://www.gnu.org&cs_f=softwa' 're/autoconf/manual/autoconf-2.60/autoconf.html-002#first'): self.assert_(len(entry.match) == 4) for match in entry.match: if match.line_number == '4': self.assert_(match.type == 'text/html') self.assert_(entry.file.name == 'software/autoconf/manual/autoconf-2.60/autoconf.html-002') self.assert_(entry.package.name == 'http://www.gnu.org') self.assert_(entry.package.uri == 'http://www.gnu.org') if __name__ == '__main__': unittest.main() gdata-2.0.18/tests/gdata_tests/docs_test.py0000750036466300116100000001533612156622363020755 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright (C) 2006 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. __author__ = ('api.jfisher (Jeff Fisher), ' 'api.eric@google.com (Eric Bidelman)') import unittest from gdata import test_data import gdata.docs class DocumentListEntryTest(unittest.TestCase): def setUp(self): self.dl_entry = gdata.docs.DocumentListEntryFromString( test_data.DOCUMENT_LIST_ENTRY) def testToAndFromStringWithData(self): entry = gdata.docs.DocumentListEntryFromString(str(self.dl_entry)) self.assertEqual(entry.author[0].name.text, 'test.user') self.assertEqual(entry.author[0].email.text, 'test.user@gmail.com') self.assertEqual(entry.GetDocumentType(), 'spreadsheet') self.assertEqual(entry.id.text, 'https://docs.google.com/feeds/documents/private/full/' +\ 'spreadsheet%3Asupercalifragilisticexpealidocious') self.assertEqual(entry.title.text,'Test Spreadsheet') self.assertEqual(entry.resourceId.text, 'spreadsheet:supercalifragilisticexpealidocious') self.assertEqual(entry.lastModifiedBy.name.text,'test.user') self.assertEqual(entry.lastModifiedBy.email.text,'test.user@gmail.com') self.assertEqual(entry.lastViewed.text,'2009-03-05T07:48:21.493Z') self.assertEqual(entry.writersCanInvite.value, 'true') class DocumentListFeedTest(unittest.TestCase): def setUp(self): self.dl_feed = gdata.docs.DocumentListFeedFromString( test_data.DOCUMENT_LIST_FEED) def testToAndFromString(self): self.assert_(len(self.dl_feed.entry) == 2) for an_entry in self.dl_feed.entry: self.assert_(isinstance(an_entry, gdata.docs.DocumentListEntry)) new_dl_feed = gdata.docs.DocumentListFeedFromString(str(self.dl_feed)) for an_entry in new_dl_feed.entry: self.assert_(isinstance(an_entry, gdata.docs.DocumentListEntry)) def testConvertActualData(self): for an_entry in self.dl_feed.entry: self.assertEqual(an_entry.author[0].name.text, 'test.user') self.assertEqual(an_entry.author[0].email.text, 'test.user@gmail.com') self.assertEqual(an_entry.lastModifiedBy.name.text, 'test.user') self.assertEqual(an_entry.lastModifiedBy.email.text, 'test.user@gmail.com') self.assertEqual(an_entry.lastViewed.text,'2009-03-05T07:48:21.493Z') if(an_entry.GetDocumentType() == 'spreadsheet'): self.assertEqual(an_entry.title.text, 'Test Spreadsheet') self.assertEqual(an_entry.writersCanInvite.value, 'true') elif(an_entry.GetDocumentType() == 'document'): self.assertEqual(an_entry.title.text, 'Test Document') self.assertEqual(an_entry.writersCanInvite.value, 'false') def testLinkFinderFindsLinks(self): for entry in self.dl_feed.entry: # All Document List entries should have a self link self.assert_(entry.GetSelfLink() is not None) # All Document List entries should have an HTML link self.assert_(entry.GetHtmlLink() is not None) self.assert_(entry.feedLink.href is not None) class DocumentListAclEntryTest(unittest.TestCase): def setUp(self): self.acl_entry = gdata.docs.DocumentListAclEntryFromString( test_data.DOCUMENT_LIST_ACL_ENTRY) def testToAndFromString(self): self.assert_(isinstance(self.acl_entry, gdata.docs.DocumentListAclEntry)) self.assert_(isinstance(self.acl_entry.role, gdata.docs.Role)) self.assert_(isinstance(self.acl_entry.scope, gdata.docs.Scope)) self.assertEqual(self.acl_entry.scope.value, 'user@gmail.com') self.assertEqual(self.acl_entry.scope.type, 'user') self.assertEqual(self.acl_entry.role.value, 'writer') acl_entry_str = str(self.acl_entry) new_acl_entry = gdata.docs.DocumentListAclEntryFromString(acl_entry_str) self.assert_(isinstance(new_acl_entry, gdata.docs.DocumentListAclEntry)) self.assert_(isinstance(new_acl_entry.role, gdata.docs.Role)) self.assert_(isinstance(new_acl_entry.scope, gdata.docs.Scope)) self.assertEqual(new_acl_entry.scope.value, self.acl_entry.scope.value) self.assertEqual(new_acl_entry.scope.type, self.acl_entry.scope.type) self.assertEqual(new_acl_entry.role.value, self.acl_entry.role.value) def testCreateNewAclEntry(self): cat = gdata.atom.Category( term='http://schemas.google.com/acl/2007#accessRule', scheme='http://schemas.google.com/g/2005#kind') acl_entry = gdata.docs.DocumentListAclEntry(category=[cat]) acl_entry.scope = gdata.docs.Scope(value='user@gmail.com', type='user') acl_entry.role = gdata.docs.Role(value='writer') self.assert_(isinstance(acl_entry, gdata.docs.DocumentListAclEntry)) self.assert_(isinstance(acl_entry.role, gdata.docs.Role)) self.assert_(isinstance(acl_entry.scope, gdata.docs.Scope)) self.assertEqual(acl_entry.scope.value, 'user@gmail.com') self.assertEqual(acl_entry.scope.type, 'user') self.assertEqual(acl_entry.role.value, 'writer') class DocumentListAclFeedTest(unittest.TestCase): def setUp(self): self.feed = gdata.docs.DocumentListAclFeedFromString( test_data.DOCUMENT_LIST_ACL_FEED) def testToAndFromString(self): for entry in self.feed.entry: self.assert_(isinstance(entry, gdata.docs.DocumentListAclEntry)) feed = gdata.docs.DocumentListAclFeedFromString(str(self.feed)) for entry in feed.entry: self.assert_(isinstance(entry, gdata.docs.DocumentListAclEntry)) def testConvertActualData(self): entries = self.feed.entry self.assert_(len(entries) == 2) self.assertEqual(entries[0].title.text, 'Document Permission - user@gmail.com') self.assertEqual(entries[0].role.value, 'owner') self.assertEqual(entries[0].scope.type, 'user') self.assertEqual(entries[0].scope.value, 'user@gmail.com') self.assert_(entries[0].GetSelfLink() is not None) self.assert_(entries[0].GetEditLink() is not None) self.assertEqual(entries[1].title.text, 'Document Permission - user2@google.com') self.assertEqual(entries[1].role.value, 'writer') self.assertEqual(entries[1].scope.type, 'domain') self.assertEqual(entries[1].scope.value, 'google.com') self.assert_(entries[1].GetSelfLink() is not None) self.assert_(entries[1].GetEditLink() is not None) if __name__ == '__main__': unittest.main() gdata-2.0.18/tests/gdata_tests/spreadsheet_test.py0000750036466300116100000004211512156622363022327 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright (C) 2007 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. __author__ = 'api.laurabeth@gmail.com (Laura Beth Lincoln)' import unittest try: from xml.etree import ElementTree except ImportError: from elementtree import ElementTree import gdata import gdata.spreadsheet SPREADSHEETS_FEED = """ http://spreadsheets.google.com/feeds/spreadsheets/private/full 2006-11-17T18:23:45.173Z Available Spreadsheets Fitzwilliam Darcy fitz@gmail.com 1 1 1 http://spreadsheets.google.com/feeds/spreadsheets/private/full/key 2006-11-17T18:24:18.231Z Groceries R Us Groceries R Us Fitzwilliam Darcy fitz@gmail.com """ WORKSHEETS_FEED = """ http://spreadsheets.google.com/feeds/worksheets/key/private/full 2006-11-17T18:23:45.173Z Groceries R Us Fitzwilliam Darcy fitz@gmail.com 1 1 1 http://spreadsheets.google.com/feeds/worksheets/key/private/full/od6 2006-11-17T18:23:45.173Z Sheet1 Sheet1 100 20 """ CELLS_FEED = """ http://spreadsheets.google.com/feeds/cells/key/od6/private/full 2006-11-17T18:27:32.543Z Sheet1 Fitzwilliam Darcy fitz@gmail.com 1 1 100 20 http://spreadsheets.google.com/feeds/cells/key/od6/private/full/R1C1 2006-11-17T18:27:32.543Z A1 Name Name http://spreadsheets.google.com/feeds/cells/key/od6/private/full/R1C2 2006-11-17T18:27:32.543Z B1 Hours Hours """ LIST_FEED = """ http://spreadsheets.google.com/feeds/list/key/od6/private/full 2006-11-17T18:23:45.173Z Sheet1 Fitzwilliam Darcy fitz@gmail.com 2 1 2 http://spreadsheets.google.com/feeds/list/key/od6/private/full/cokwr 2006-11-17T18:23:45.173Z Bingley Hours: 10, Items: 2, IPM: 0.0033 Bingley 10 2 0.0033 http://spreadsheets.google.com/feeds/list/key/od6/private/full/cyevm 2006-11-17T18:23:45.173Z Charlotte Hours: 60, Items: 18000, IPM: 5 Charlotte 60 18000 5 """ class ColCountTest(unittest.TestCase): def setUp(self): self.col_count = gdata.spreadsheet.ColCount() def testToAndFromString(self): self.col_count.text = '20' self.assert_(self.col_count.text == '20') new_col_count = gdata.spreadsheet.ColCountFromString(self.col_count.ToString()) self.assert_(self.col_count.text == new_col_count.text) class RowCountTest(unittest.TestCase): def setUp(self): self.row_count = gdata.spreadsheet.RowCount() def testToAndFromString(self): self.row_count.text = '100' self.assert_(self.row_count.text == '100') new_row_count = gdata.spreadsheet.RowCountFromString(self.row_count.ToString()) self.assert_(self.row_count.text == new_row_count.text) class CellTest(unittest.TestCase): def setUp(self): self.cell = gdata.spreadsheet.Cell() def testToAndFromString(self): self.cell.text = 'test cell' self.assert_(self.cell.text == 'test cell') self.cell.row = '1' self.assert_(self.cell.row == '1') self.cell.col = '2' self.assert_(self.cell.col == '2') self.cell.inputValue = 'test input value' self.assert_(self.cell.inputValue == 'test input value') self.cell.numericValue = 'test numeric value' self.assert_(self.cell.numericValue == 'test numeric value') new_cell = gdata.spreadsheet.CellFromString(self.cell.ToString()) self.assert_(self.cell.text == new_cell.text) self.assert_(self.cell.row == new_cell.row) self.assert_(self.cell.col == new_cell.col) self.assert_(self.cell.inputValue == new_cell.inputValue) self.assert_(self.cell.numericValue == new_cell.numericValue) class CustomTest(unittest.TestCase): def setUp(self): self.custom = gdata.spreadsheet.Custom() def testToAndFromString(self): self.custom.text = 'value' self.custom.column = 'column_name' self.assert_(self.custom.text == 'value') self.assert_(self.custom.column == 'column_name') new_custom = gdata.spreadsheet.CustomFromString(self.custom.ToString()) self.assert_(self.custom.text == new_custom.text) self.assert_(self.custom.column == new_custom.column) class SpreadsheetsWorksheetTest(unittest.TestCase): def setUp(self): self.worksheet = gdata.spreadsheet.SpreadsheetsWorksheet() def testToAndFromString(self): self.worksheet.row_count = gdata.spreadsheet.RowCount(text='100') self.assert_(self.worksheet.row_count.text == '100') self.worksheet.col_count = gdata.spreadsheet.ColCount(text='20') self.assert_(self.worksheet.col_count.text == '20') new_worksheet = gdata.spreadsheet.SpreadsheetsWorksheetFromString( self.worksheet.ToString()) self.assert_(self.worksheet.row_count.text == new_worksheet.row_count.text) self.assert_(self.worksheet.col_count.text == new_worksheet.col_count.text) class SpreadsheetsCellTest(unittest.TestCase): def setUp(self): self.entry = gdata.spreadsheet.SpreadsheetsCell() def testToAndFromString(self): self.entry.cell = gdata.spreadsheet.Cell(text='my cell', row='1', col='2', inputValue='my input value', numericValue='my numeric value') self.assert_(self.entry.cell.text == 'my cell') self.assert_(self.entry.cell.row == '1') self.assert_(self.entry.cell.col == '2') self.assert_(self.entry.cell.inputValue == 'my input value') self.assert_(self.entry.cell.numericValue == 'my numeric value') new_cell = gdata.spreadsheet.SpreadsheetsCellFromString(self.entry.ToString()) self.assert_(self.entry.cell.text == new_cell.cell.text) self.assert_(self.entry.cell.row == new_cell.cell.row) self.assert_(self.entry.cell.col == new_cell.cell.col) self.assert_(self.entry.cell.inputValue == new_cell.cell.inputValue) self.assert_(self.entry.cell.numericValue == new_cell.cell.numericValue) class SpreadsheetsListTest(unittest.TestCase): def setUp(self): self.row = gdata.spreadsheet.SpreadsheetsList() def testToAndFromString(self): self.row.custom['column_1'] = gdata.spreadsheet.Custom(column='column_1', text='my first column') self.row.custom['column_2'] = gdata.spreadsheet.Custom(column='column_2', text='my second column') self.assert_(self.row.custom['column_1'].column == 'column_1') self.assert_(self.row.custom['column_1'].text == 'my first column') self.assert_(self.row.custom['column_2'].column == 'column_2') self.assert_(self.row.custom['column_2'].text == 'my second column') new_row = gdata.spreadsheet.SpreadsheetsListFromString(self.row.ToString()) self.assert_(self.row.custom['column_1'].column == new_row.custom['column_1'].column) self.assert_(self.row.custom['column_1'].text == new_row.custom['column_1'].text) self.assert_(self.row.custom['column_2'].column == new_row.custom['column_2'].column) self.assert_(self.row.custom['column_2'].text == new_row.custom['column_2'].text) class SpreadsheetsSpreadsheetsFeedTest(unittest.TestCase): def setUp(self): #self.item_feed = gdata.spreadsheet.SpreadsheetSpreadsheetsFeed() self.feed = gdata.spreadsheet.SpreadsheetsSpreadsheetsFeedFromString( SPREADSHEETS_FEED) def testToAndFromString(self): self.assert_(len(self.feed.entry) == 1) for an_entry in self.feed.entry: self.assert_(isinstance(an_entry, gdata.spreadsheet.SpreadsheetsSpreadsheet)) new_feed = gdata.spreadsheet.SpreadsheetsSpreadsheetsFeedFromString( str(self.feed)) for an_entry in new_feed.entry: self.assert_(isinstance(an_entry, gdata.spreadsheet.SpreadsheetsSpreadsheet)) class SpreadsheetsWorksheetsFeedTest(unittest.TestCase): def setUp(self): #self.item_feed = gdata.spreadsheet.SpreadsheetWorksheetsFeed() self.feed = gdata.spreadsheet.SpreadsheetsWorksheetsFeedFromString( WORKSHEETS_FEED) def testToAndFromString(self): self.assert_(len(self.feed.entry) == 1) for an_entry in self.feed.entry: self.assert_(isinstance(an_entry, gdata.spreadsheet.SpreadsheetsWorksheet)) new_feed = gdata.spreadsheet.SpreadsheetsWorksheetsFeedFromString( str(self.feed)) for an_entry in new_feed.entry: self.assert_(isinstance(an_entry, gdata.spreadsheet.SpreadsheetsWorksheet)) class SpreadsheetsCellsFeedTest(unittest.TestCase): def setUp(self): #self.item_feed = gdata.spreadsheet.SpreadsheetCellsFeed() self.feed = gdata.spreadsheet.SpreadsheetsCellsFeedFromString( CELLS_FEED) def testToAndFromString(self): self.assert_(len(self.feed.entry) == 2) for an_entry in self.feed.entry: self.assert_(isinstance(an_entry, gdata.spreadsheet.SpreadsheetsCell)) new_feed = gdata.spreadsheet.SpreadsheetsCellsFeedFromString(str(self.feed)) self.assert_(isinstance(new_feed.row_count, gdata.spreadsheet.RowCount)) self.assert_(new_feed.row_count.text == '100') self.assert_(isinstance(new_feed.col_count, gdata.spreadsheet.ColCount)) self.assert_(new_feed.col_count.text == '20') for an_entry in new_feed.entry: self.assert_(isinstance(an_entry, gdata.spreadsheet.SpreadsheetsCell)) class SpreadsheetsListFeedTest(unittest.TestCase): def setUp(self): #self.item_feed = gdata.spreadsheet.SpreadsheetListFeed() self.feed = gdata.spreadsheet.SpreadsheetsListFeedFromString( LIST_FEED) def testToAndFromString(self): self.assert_(len(self.feed.entry) == 2) for an_entry in self.feed.entry: self.assert_(isinstance(an_entry, gdata.spreadsheet.SpreadsheetsList)) new_feed = gdata.spreadsheet.SpreadsheetsListFeedFromString(str(self.feed)) for an_entry in new_feed.entry: self.assert_(isinstance(an_entry, gdata.spreadsheet.SpreadsheetsList)) if __name__ == '__main__': unittest.main() gdata-2.0.18/tests/gdata_tests/client_test.py0000750036466300116100000004104512156622363021277 0ustar afshareng00000000000000#!/usr/bin/env python # # Copyright (C) 2008, 2009 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. # This module is used for version 2 of the Google Data APIs. __author__ = 'j.s@google.com (Jeff Scudder)' import unittest import gdata.client import gdata.gauth import gdata.data import atom.mock_http_core import StringIO class ClientLoginTest(unittest.TestCase): def test_token_request(self): client = gdata.client.GDClient() client.http_client = atom.mock_http_core.SettableHttpClient(200, 'OK', 'SID=DQAAAGgA...7Zg8CTN\n' 'LSID=DQAAAGsA...lk8BBbG\n' 'Auth=DQAAAGgA...dk3fA5N', {'Content-Type': 'text/plain'}) token = client.request_client_login_token('email', 'pw', 'cp', 'test') self.assert_(isinstance(token, gdata.gauth.ClientLoginToken)) self.assertEqual(token.token_string, 'DQAAAGgA...dk3fA5N') # Test a server response without a ClientLogin token.` client.http_client.set_response(200, 'OK', 'SID=12345\nLSID=34567', {}) self.assertRaises(gdata.client.ClientLoginTokenMissing, client.request_client_login_token, 'email', 'pw', '', '') # Test a 302 redirect from the server on a login request. client.http_client.set_response(302, 'ignored', '', {}) # TODO: change the exception class to one in gdata.client. self.assertRaises(gdata.client.BadAuthenticationServiceURL, client.request_client_login_token, 'email', 'pw', '', '') # Test a CAPTCHA challenge from the server client.http_client.set_response(403, 'Access Forbidden', 'Url=http://www.google.com/login/captcha\n' 'Error=CaptchaRequired\n' 'CaptchaToken=DQAAAGgA...dkI1LK9\n' # TODO: verify this sample CAPTCHA URL matches an # actual challenge from the server. 'CaptchaUrl=Captcha?ctoken=HiteT4bVoP6-yFkHPibe7O9EqxeiI7lUSN', {}) try: token = client.request_client_login_token('email', 'pw', '', '') self.fail('should raise a CaptchaChallenge on a 403 with a ' 'CaptchRequired error.') except gdata.client.CaptchaChallenge, challenge: self.assertEquals(challenge.captcha_url, 'http://www.google.com/accounts/' 'Captcha?ctoken=HiteT4bVoP6-yFkHPibe7O9EqxeiI7lUSN') self.assertEquals(challenge.captcha_token, 'DQAAAGgA...dkI1LK9') # Test an unexpected response, a 404 for example. client.http_client.set_response(404, 'ignored', '', {}) self.assertRaises(gdata.client.ClientLoginFailed, client.request_client_login_token, 'email', 'pw', '', '') def test_client_login(self): client = gdata.client.GDClient() client.http_client = atom.mock_http_core.SettableHttpClient(200, 'OK', 'SID=DQAAAGgA...7Zg8CTN\n' 'LSID=DQAAAGsA...lk8BBbG\n' 'Auth=DQAAAGgA...dk3fA5N', {'Content-Type': 'text/plain'}) client.client_login('me@example.com', 'password', 'wise', 'unit test') self.assert_(isinstance(client.auth_token, gdata.gauth.ClientLoginToken)) self.assertEqual(client.auth_token.token_string, 'DQAAAGgA...dk3fA5N') class AuthSubTest(unittest.TestCase): def test_get_and_upgrade_token(self): client = gdata.client.GDClient() client.http_client = atom.mock_http_core.SettableHttpClient(200, 'OK', 'Token=UpgradedTokenVal\n' 'Extra data', {'Content-Type': 'text/plain'}) page_url = 'http://example.com/showcalendar.html?token=CKF50YzIHxCTKMAg' client.auth_token = gdata.gauth.AuthSubToken.from_url(page_url) self.assert_(isinstance(client.auth_token, gdata.gauth.AuthSubToken)) self.assertEqual(client.auth_token.token_string, 'CKF50YzIHxCTKMAg') upgraded = client.upgrade_token() self.assert_(isinstance(client.auth_token, gdata.gauth.AuthSubToken)) self.assertEqual(client.auth_token.token_string, 'UpgradedTokenVal') self.assertEqual(client.auth_token, upgraded) # Ensure passing in a token returns without modifying client's auth_token. client.http_client.set_response(200, 'OK', 'Token=4567', {}) upgraded = client.upgrade_token( gdata.gauth.AuthSubToken.from_url('?token=1234')) self.assertEqual(upgraded.token_string, '4567') self.assertEqual(client.auth_token.token_string, 'UpgradedTokenVal') self.assertNotEqual(client.auth_token, upgraded) # Test exception cases client.auth_token = None self.assertRaises(gdata.client.UnableToUpgradeToken, client.upgrade_token, None) self.assertRaises(gdata.client.UnableToUpgradeToken, client.upgrade_token) def test_revoke_token(self): client = gdata.client.GDClient() client.http_client = atom.mock_http_core.SettableHttpClient( 200, 'OK', '', {}) page_url = 'http://example.com/showcalendar.html?token=CKF50YzIHxCTKMAg' client.auth_token = gdata.gauth.AuthSubToken.from_url(page_url) deleted = client.revoke_token() self.assert_(deleted) self.assertEqual( client.http_client.last_request.headers['Authorization'], 'AuthSub token=CKF50YzIHxCTKMAg') class OAuthTest(unittest.TestCase): def test_hmac_flow(self): client = gdata.client.GDClient() client.http_client = atom.mock_http_core.SettableHttpClient( 200, 'OK', 'oauth_token=ab3cd9j4ks7&oauth_token_secret=ZXhhbXBsZS', {}) request_token = client.get_oauth_token( ['http://example.com/service'], 'http://example.net/myapp', 'consumer', consumer_secret='secret') # Check that the response was correctly parsed. self.assertEqual(request_token.token, 'ab3cd9j4ks7') self.assertEqual(request_token.token_secret, 'ZXhhbXBsZS') self.assertEqual(request_token.auth_state, gdata.gauth.REQUEST_TOKEN) # Also check the Authorization header which was sent in the request. auth_header = client.http_client.last_request.headers['Authorization'] self.assert_('OAuth' in auth_header) self.assert_( 'oauth_callback="http%3A%2F%2Fexample.net%2Fmyapp"' in auth_header) self.assert_('oauth_version="1.0"' in auth_header) self.assert_('oauth_signature_method="HMAC-SHA1"' in auth_header) self.assert_('oauth_consumer_key="consumer"' in auth_header) # Check generation of the authorization URL. authorize_url = request_token.generate_authorization_url() self.assert_(str(authorize_url).startswith( 'https://www.google.com/accounts/OAuthAuthorizeToken')) self.assert_('oauth_token=ab3cd9j4ks7' in str(authorize_url)) # Check that the token information from the browser's URL is parsed. redirected_url = ( 'http://example.net/myapp?oauth_token=CKF5zz&oauth_verifier=Xhhbas') gdata.gauth.authorize_request_token(request_token, redirected_url) self.assertEqual(request_token.token, 'CKF5zz') self.assertEqual(request_token.verifier, 'Xhhbas') self.assertEqual(request_token.auth_state, gdata.gauth.AUTHORIZED_REQUEST_TOKEN) # Check that the token upgrade response was correctly parsed. client.http_client.set_response( 200, 'OK', 'oauth_token=3cd9Fj417&oauth_token_secret=Xhrh6bXBs', {}) access_token = client.get_access_token(request_token) self.assertEqual(request_token.token, '3cd9Fj417') self.assertEqual(request_token.token_secret, 'Xhrh6bXBs') self.assert_(request_token.verifier is None) self.assertEqual(request_token.auth_state, gdata.gauth.ACCESS_TOKEN) self.assertEqual(request_token.token, access_token.token) self.assertEqual(request_token.token_secret, access_token.token_secret) self.assert_(access_token.verifier is None) self.assertEqual(request_token.auth_state, access_token.auth_state) # Also check the Authorization header which was sent in the request. auth_header = client.http_client.last_request.headers['Authorization'] self.assert_('OAuth' in auth_header) self.assert_('oauth_callback="' not in auth_header) self.assert_('oauth_version="1.0"' in auth_header) self.assert_('oauth_verifier="Xhhbas"' in auth_header) self.assert_('oauth_signature_method="HMAC-SHA1"' in auth_header) self.assert_('oauth_consumer_key="consumer"' in auth_header) class RequestTest(unittest.TestCase): def test_simple_request(self): client = gdata.client.GDClient() client.http_client = atom.mock_http_core.EchoHttpClient() response = client.request('GET', 'https://example.com/test') self.assertEqual(response.getheader('Echo-Host'), 'example.com:None') self.assertEqual(response.getheader('Echo-Uri'), '/test') self.assertEqual(response.getheader('Echo-Scheme'), 'https') self.assertEqual(response.getheader('Echo-Method'), 'GET') http_request = atom.http_core.HttpRequest( uri=atom.http_core.Uri(scheme='http', host='example.net', port=8080), method='POST', headers={'X': 1}) http_request.add_body_part('test', 'text/plain') response = client.request(http_request=http_request) self.assertEqual(response.getheader('Echo-Host'), 'example.net:8080') # A Uri with path set to None should default to /. self.assertEqual(response.getheader('Echo-Uri'), '/') self.assertEqual(response.getheader('Echo-Scheme'), 'http') self.assertEqual(response.getheader('Echo-Method'), 'POST') self.assertEqual(response.getheader('Content-Type'), 'text/plain') self.assertEqual(response.getheader('X'), '1') self.assertEqual(response.read(), 'test') # Use the same request object from above, but overwrite the request path # by passing in a URI. response = client.request(uri='/new/path?p=1', http_request=http_request) self.assertEqual(response.getheader('Echo-Host'), 'example.net:8080') self.assertEqual(response.getheader('Echo-Uri'), '/new/path?p=1') self.assertEqual(response.getheader('Echo-Scheme'), 'http') self.assertEqual(response.read(), 'test') def test_gdata_version_header(self): client = gdata.client.GDClient() client.http_client = atom.mock_http_core.EchoHttpClient() response = client.request('GET', 'http://example.com') self.assertEqual(response.getheader('GData-Version'), None) client.api_version = '2' response = client.request('GET', 'http://example.com') self.assertEqual(response.getheader('GData-Version'), '2') def test_redirects(self): client = gdata.client.GDClient() client.http_client = atom.mock_http_core.MockHttpClient() # Add the redirect response for the initial request. first_request = atom.http_core.HttpRequest('http://example.com/1', 'POST') client.http_client.add_response(first_request, 302, None, {'Location': 'http://example.com/1?gsessionid=12'}) second_request = atom.http_core.HttpRequest( 'http://example.com/1?gsessionid=12', 'POST') client.http_client.AddResponse(second_request, 200, 'OK', body='Done') response = client.Request('POST', 'http://example.com/1') self.assertEqual(response.status, 200) self.assertEqual(response.reason, 'OK') self.assertEqual(response.read(), 'Done') redirect_loop_request = atom.http_core.HttpRequest( 'http://example.com/2?gsessionid=loop', 'PUT') client.http_client.add_response(redirect_loop_request, 302, None, {'Location': 'http://example.com/2?gsessionid=loop'}) try: response = client.request(method='PUT', uri='http://example.com/2?gsessionid=loop') self.fail('Loop URL should have redirected forever.') except gdata.client.RedirectError, err: self.assert_(str(err).startswith('Too many redirects from server')) def test_lowercase_location(self): client = gdata.client.GDClient() client.http_client = atom.mock_http_core.MockHttpClient() # Add the redirect response for the initial request. first_request = atom.http_core.HttpRequest('http://example.com/1', 'POST') # In some environments, notably App Engine, the HTTP headers which come # back from a server will be normalized to all lowercase. client.http_client.add_response(first_request, 302, None, {'location': 'http://example.com/1?gsessionid=12'}) second_request = atom.http_core.HttpRequest( 'http://example.com/1?gsessionid=12', 'POST') client.http_client.AddResponse(second_request, 200, 'OK', body='Done') response = client.Request('POST', 'http://example.com/1') self.assertEqual(response.status, 200) self.assertEqual(response.reason, 'OK') self.assertEqual(response.read(), 'Done') def test_exercise_exceptions(self): # TODO pass def test_converter_vs_desired_class(self): def bad_converter(string): return 1 class TestClass(atom.core.XmlElement): _qname = '{http://www.w3.org/2005/Atom}entry' client = gdata.client.GDClient() client.http_client = atom.mock_http_core.EchoHttpClient() test_entry = gdata.data.GDEntry() result = client.post(test_entry, 'http://example.com') self.assert_(isinstance(result, gdata.data.GDEntry)) result = client.post(test_entry, 'http://example.com', converter=bad_converter) self.assertEquals(result, 1) result = client.post(test_entry, 'http://example.com', desired_class=TestClass) self.assert_(isinstance(result, TestClass)) class QueryTest(unittest.TestCase): def test_query_modifies_request(self): request = atom.http_core.HttpRequest() gdata.client.Query( text_query='foo', categories=['a', 'b']).modify_request(request) # categories param from above is named category in URL self.assertEqual(request.uri.query, {'q': 'foo', 'category': 'a,b'}) def test_client_uses_query_modification(self): """If the Query is passed as an unexpected param it should apply""" client = gdata.client.GDClient() client.http_client = atom.mock_http_core.EchoHttpClient() query = gdata.client.Query(max_results=7) client.http_client = atom.mock_http_core.SettableHttpClient( 201, 'CREATED', gdata.data.GDEntry().ToString(), {}) response = client.get('https://example.com/foo', a_random_param=query) self.assertEqual( client.http_client.last_request.uri.query['max-results'], '7') class VersionConversionTest(unittest.TestCase): def test_use_default_version(self): self.assertEquals(gdata.client.get_xml_version(None), 1) def test_str_to_int_version(self): self.assertEquals(gdata.client.get_xml_version('1'), 1) self.assertEquals(gdata.client.get_xml_version('2'), 2) self.assertEquals(gdata.client.get_xml_version('2.1.2'), 2) self.assertEquals(gdata.client.get_xml_version('10.4'), 10) class UpdateTest(unittest.TestCase): """Test Update/PUT""" def test_update_uri_editlink(self): """Test that the PUT uri is grabbed from the entry's edit link""" client = gdata.client.GDClient() client.http_client = atom.mock_http_core.SettableHttpClient( 200, 'OK', gdata.data.GDEntry().ToString(), {}) entry = gdata.data.GDEntry() entry.link.append(atom.data.Link(rel='edit', href='https://example.com/edit')) response = client.update(entry) request = client.http_client.last_request self.assertEqual(str(client.http_client.last_request.uri), 'https://example.com/edit') def test_update_uri(self): """Test that when passed, a uri overrides the entry's edit link""" client = gdata.client.GDClient() client.http_client = atom.mock_http_core.SettableHttpClient( 200, 'OK', gdata.data.GDEntry().ToString(), {}) entry = gdata.data.GDEntry() entry.link.append(atom.data.Link(rel='edit', href='https://example.com/edit')) response = client.update(entry, uri='https://example.com/test') self.assertEqual(str(client.http_client.last_request.uri), 'https://example.com/test') def suite(): return unittest.TestSuite((unittest.makeSuite(ClientLoginTest, 'test'), unittest.makeSuite(AuthSubTest, 'test'), unittest.makeSuite(OAuthTest, 'test'), unittest.makeSuite(RequestTest, 'test'), unittest.makeSuite(VersionConversionTest, 'test'), unittest.makeSuite(QueryTest, 'test'), unittest.makeSuite(UpdateTest, 'test'))) if __name__ == '__main__': unittest.main() gdata-2.0.18/tests/gdata_tests/calendar/0000750036466300116100000000000012156625015020147 5ustar afshareng00000000000000gdata-2.0.18/tests/gdata_tests/calendar/service_test.py0000750036466300116100000004374112156622363023237 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright (C) 2006 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. __author__ = 'api.rboyd@google.com (Ryan Boyd)' import unittest try: from xml.etree import ElementTree except ImportError: from elementtree import ElementTree import atom import atom.mock_http import gdata.calendar import gdata.calendar.service import random import getpass username = '' password = '' class CalendarServiceUnitTest(unittest.TestCase): def setUp(self): self.cal_client = gdata.calendar.service.CalendarService() self.cal_client.email = username self.cal_client.password = password self.cal_client.source = 'GCalendarClient "Unit" Tests' def tearDown(self): # No teardown needed pass def testUrlScrubbing(self): self.assertEquals(self.cal_client._RemoveStandardUrlPrefix( '/test'), '/test') self.assertEquals(self.cal_client._RemoveStandardUrlPrefix( 'http://www.google.com/calendar/test'), '/calendar/test') self.assertEquals(self.cal_client._RemoveStandardUrlPrefix( 'https://www.google.com/calendar/test'), 'https://www.google.com/calendar/test') def testPostUpdateAndDeleteSubscription(self): """Test posting a new subscription, updating it, deleting it""" self.cal_client.ProgrammaticLogin() subscription_id = 'c4o4i7m2lbamc4k26sc2vokh5g%40group.calendar.google.com' subscription_url = '%s%s' % ( 'http://www.google.com/calendar/feeds/default/allcalendars/full/', subscription_id) # Subscribe to Google Doodles calendar calendar = gdata.calendar.CalendarListEntry() calendar.id = atom.Id(text=subscription_id) returned_calendar = self.cal_client.InsertCalendarSubscription(calendar) self.assertEquals(subscription_url, returned_calendar.id.text) self.assertEquals('Google Doodles', returned_calendar.title.text) # Update subscription calendar_to_update = self.cal_client.GetCalendarListEntry(subscription_url) self.assertEquals('Google Doodles', calendar_to_update.title.text) self.assertEquals('true', calendar_to_update.selected.value) calendar_to_update.selected.value = 'false' self.assertEquals('false', calendar_to_update.selected.value) updated_calendar = self.cal_client.UpdateCalendar(calendar_to_update) self.assertEquals('false', updated_calendar.selected.value) # Delete subscription response = self.cal_client.DeleteCalendarEntry( returned_calendar.GetEditLink().href) self.assertEquals(True, response) def testPostUpdateAndDeleteCalendar(self): """Test posting a new calendar, updating it, deleting it""" self.cal_client.ProgrammaticLogin() # New calendar to create title='Little League Schedule' description='This calendar contains practice and game times' time_zone='America/Los_Angeles' hidden=False location='Oakland' color='#2952A3' # Calendar object calendar = gdata.calendar.CalendarListEntry() calendar.title = atom.Title(text=title) calendar.summary = atom.Summary(text=description) calendar.where = gdata.calendar.Where(value_string=location) calendar.color = gdata.calendar.Color(value=color) calendar.timezone = gdata.calendar.Timezone(value=time_zone) if hidden: calendar.hidden = gdata.calendar.Hidden(value='true') else: calendar.hidden = gdata.calendar.Hidden(value='false') # Create calendar new_calendar = self.cal_client.InsertCalendar(new_calendar=calendar) self.assertEquals(title, new_calendar.title.text) self.assertEquals(description, new_calendar.summary.text) self.assertEquals(location, new_calendar.where.value_string) self.assertEquals(color, new_calendar.color.value) self.assertEquals(time_zone, new_calendar.timezone.value) if hidden: self.assertEquals('true', new_calendar.hidden.value) else: self.assertEquals('false', new_calendar.hidden.value) # Update calendar calendar_to_update = self.cal_client.GetCalendarListEntry( new_calendar.id.text) updated_title = 'This is the updated title' calendar_to_update.title.text = updated_title updated_calendar = self.cal_client.UpdateCalendar(calendar_to_update) self.assertEquals(updated_title, updated_calendar.title.text) # Delete calendar calendar_to_delete = self.cal_client.GetCalendarListEntry( new_calendar.id.text) self.cal_client.Delete(calendar_to_delete.GetEditLink().href) return new_calendar def testPostAndDeleteExtendedPropertyEvent(self): """Test posting a new entry with an extended property, deleting it""" # Get random data for creating event r = random.Random() r.seed() random_event_number = str(r.randint(100000,1000000)) random_event_title = 'My Random Extended Property Test Event %s' % ( random_event_number) # Set event data event = gdata.calendar.CalendarEventEntry() event.author.append(atom.Author(name=atom.Name(text='GData Test user'))) event.title = atom.Title(text=random_event_title) event.content = atom.Content(text='Picnic with some lunch') event.extended_property.append(gdata.calendar.ExtendedProperty( name='prop test name', value='prop test value')) # Insert event self.cal_client.ProgrammaticLogin() new_event = self.cal_client.InsertEvent(event, '/calendar/feeds/default/private/full') self.assertEquals(event.extended_property[0].value, new_event.extended_property[0].value) # Delete the event self.cal_client.DeleteEvent(new_event.GetEditLink().href) # WARNING: Due to server-side issues, this test takes a while (~60seconds) def testPostEntryWithCommentAndDelete(self): """Test posting a new entry with an extended property, deleting it""" # Get random data for creating event r = random.Random() r.seed() random_event_number = str(r.randint(100000,1000000)) random_event_title = 'My Random Comments Test Event %s' % ( random_event_number) # Set event data event = gdata.calendar.CalendarEventEntry() event.author.append(atom.Author(name=atom.Name(text='GData Test user'))) event.title = atom.Title(text=random_event_title) event.content = atom.Content(text='Picnic with some lunch') # Insert event self.cal_client.ProgrammaticLogin() new_event = self.cal_client.InsertEvent(event, '/calendar/feeds/default/private/full') # Get comments feed comments_url = new_event.comments.feed_link.href comments_query = gdata.calendar.service.CalendarEventCommentQuery(comments_url) comments_feed = self.cal_client.CalendarQuery(comments_query) # Add comment comments_entry = gdata.calendar.CalendarEventCommentEntry() comments_entry.content = atom.Content(text='Comments content') comments_entry.author.append( atom.Author(name=atom.Name(text='GData Test user'), email=atom.Email(text=username))) new_comments_entry = self.cal_client.InsertEventComment(comments_entry, comments_feed.GetPostLink().href) # Delete the event event_to_delete = self.cal_client.GetCalendarEventEntry(new_event.id.text) self.cal_client.DeleteEvent(event_to_delete.GetEditLink().href) def testPostQueryUpdateAndDeleteEvents(self): """Test posting a new entry, updating it, deleting it, querying for it""" # Get random data for creating event r = random.Random() r.seed() random_event_number = str(r.randint(100000,1000000)) random_event_title = 'My Random Test Event %s' % random_event_number random_start_hour = (r.randint(1,1000000) % 23) random_end_hour = random_start_hour + 1 non_random_start_minute = 0 non_random_end_minute = 0 random_month = (r.randint(1,1000000) % 12 + 1) random_day_of_month = (r.randint(1,1000000) % 28 + 1) non_random_year = 2008 start_time = '%04d-%02d-%02dT%02d:%02d:00.000-05:00' % ( non_random_year, random_month, random_day_of_month, random_start_hour, non_random_start_minute,) end_time = '%04d-%02d-%02dT%02d:%02d:00.000-05:00' % ( non_random_year, random_month, random_day_of_month, random_end_hour, non_random_end_minute,) # Set event data event = gdata.calendar.CalendarEventEntry() event.author.append(atom.Author(name=atom.Name(text='GData Test user'))) event.title = atom.Title(text=random_event_title) event.content = atom.Content(text='Picnic with some lunch') event.where.append(gdata.calendar.Where(value_string='Down by the river')) event.when.append(gdata.calendar.When(start_time=start_time,end_time=end_time)) # Insert event self.cal_client.ProgrammaticLogin() new_event = self.cal_client.InsertEvent(event, '/calendar/feeds/default/private/full') # Ensure that atom data returned from calendar server equals atom data sent self.assertEquals(event.title.text, new_event.title.text) self.assertEquals(event.content.text, new_event.content.text) # Ensure that gd:where data returned from calendar equals value sent self.assertEquals(event.where[0].value_string, new_event.where[0].value_string) # Commented out as dateutil is not in this repository # Ensure that dates returned from calendar server equals dates sent #start_time_py = parse(event.when[0].start_time) #start_time_py_new = parse(new_event.when[0].start_time) #self.assertEquals(start_time_py, start_time_py_new) #end_time_py = parse(event.when[0].end_time) #end_time_py_new = parse(new_event.when[0].end_time) #self.assertEquals(end_time_py, end_time_py_new) # Update event event_to_update = new_event updated_title_text = event_to_update.title.text + ' - UPDATED' event_to_update.title = atom.Title(text=updated_title_text) updated_event = self.cal_client.UpdateEvent( event_to_update.GetEditLink().href, event_to_update) # Ensure that updated title was set in the updated event self.assertEquals(event_to_update.title.text, updated_event.title.text) # Delete the event self.cal_client.DeleteEvent(updated_event.GetEditLink().href) # Ensure deleted event is marked as canceled in the feed after_delete_query = gdata.calendar.service.CalendarEventQuery() after_delete_query.updated_min = '2007-01-01' after_delete_query.text_query = str(random_event_number) after_delete_query.max_results = '1' after_delete_query_result = self.cal_client.CalendarQuery( after_delete_query) # Ensure feed returned at max after_delete_query.max_results events self.assert_( len(after_delete_query_result.entry) <= after_delete_query.max_results) # Ensure status of returned event is canceled self.assertEquals(after_delete_query_result.entry[0].event_status.value, 'CANCELED') def testEventWithSyncEventAndUID(self): """Test posting a new entry (with syncEvent and a UID) and deleting it.""" # Get random data for creating event r = random.Random() r.seed() random_event_number = str(r.randint(100000,1000000)) random_event_title = 'My Random Test Event %s' % random_event_number random_start_hour = (r.randint(1,1000000) % 23) random_end_hour = random_start_hour + 1 non_random_start_minute = 0 non_random_end_minute = 0 random_month = (r.randint(1,1000000) % 12 + 1) random_day_of_month = (r.randint(1,1000000) % 28 + 1) non_random_year = 2008 start_time = '%04d-%02d-%02dT%02d:%02d:00.000-05:00' % ( non_random_year, random_month, random_day_of_month, random_start_hour, non_random_start_minute,) end_time = '%04d-%02d-%02dT%02d:%02d:00.000-05:00' % ( non_random_year, random_month, random_day_of_month, random_end_hour, non_random_end_minute,) # create a random event ID. I'm mimicing an example from outlook here, # the format doesn't seem to be important per the RFC except for being # globally unique. uid_string = '' for i in xrange(121): uid_string += "%X" % r.randint(0, 0xf) # Set event data event = gdata.calendar.CalendarEventEntry() event.author.append(atom.Author(name=atom.Name(text='GData Test user'))) event.title = atom.Title(text=random_event_title) event.content = atom.Content(text='Picnic with some lunch') event.where.append(gdata.calendar.Where(value_string='Down by the river')) event.when.append(gdata.calendar.When( start_time=start_time,end_time=end_time)) event.sync_event = gdata.calendar.SyncEvent('true') event.uid = gdata.calendar.UID(value=uid_string) # Insert event self.cal_client.ProgrammaticLogin() new_event = self.cal_client.InsertEvent(event, '/calendar/feeds/default/private/full') # Inserting it a second time should fail, as it'll have the same UID try: bad_event = self.cal_client.InsertEvent(event, '/calendar/feeds/default/private/full') self.fail('Was able to insert an event with a duplicate UID') except gdata.service.RequestError, error: # for the current problem with redirects, just re-raise so the # failure doesn't seem to be because of the duplicate UIDs. status = error[0]['status'] if status == 302: raise # otherwise, make sure it was the right error self.assertEquals(error[0]['status'], 409) self.assertEquals(error[0]['reason'], 'Conflict') # Ensure that atom data returned from calendar server equals atom data # sent self.assertEquals(event.title.text, new_event.title.text) self.assertEquals(event.content.text, new_event.content.text) # Ensure that gd:where data returned from calendar equals value sent self.assertEquals(event.where[0].value_string, new_event.where[0].value_string) # Delete the event self.cal_client.DeleteEvent(new_event.GetEditLink().href) def testCreateAndDeleteEventUsingBatch(self): # Get random data for creating event r = random.Random() r.seed() random_event_number = str(r.randint(100000,1000000)) random_event_title = 'My Random Comments Test Event %s' % ( random_event_number) # Set event data event = gdata.calendar.CalendarEventEntry() event.author.append(atom.Author(name=atom.Name(text='GData Test user'))) event.title = atom.Title(text=random_event_title) event.content = atom.Content(text='Picnic with some lunch') # Form a batch request batch_request = gdata.calendar.CalendarEventFeed() batch_request.AddInsert(entry=event) # Execute the batch request to insert the event. self.cal_client.ProgrammaticLogin() batch_result = self.cal_client.ExecuteBatch(batch_request, gdata.calendar.service.DEFAULT_BATCH_URL) self.assertEquals(len(batch_result.entry), 1) self.assertEquals(batch_result.entry[0].title.text, random_event_title) self.assertEquals(batch_result.entry[0].batch_operation.type, gdata.BATCH_INSERT) self.assertEquals(batch_result.GetBatchLink().href, gdata.calendar.service.DEFAULT_BATCH_URL) # Create a batch request to delete the newly created entry. batch_delete_request = gdata.calendar.CalendarEventFeed() batch_delete_request.AddDelete(entry=batch_result.entry[0]) batch_delete_result = self.cal_client.ExecuteBatch(batch_delete_request, batch_result.GetBatchLink().href) self.assertEquals(len(batch_delete_result.entry), 1) self.assertEquals(batch_delete_result.entry[0].batch_operation.type, gdata.BATCH_DELETE) def testCorrectReturnTypesForGetMethods(self): self.cal_client.ProgrammaticLogin() result = self.cal_client.GetCalendarEventFeed() self.assertEquals(isinstance(result, gdata.calendar.CalendarEventFeed), True) def testValidHostName(self): mock_http = atom.mock_http.MockHttpClient() response = atom.mock_http.MockResponse(body='', status=200, reason='OK') mock_http.add_response(response, 'GET', 'https://www.google.com/calendar/feeds/default/allcalendars/full') self.cal_client.ssl = True self.cal_client.http_client = mock_http self.cal_client.SetAuthSubToken('foo') self.assertEquals(str(self.cal_client.token_store.find_token( 'https://www.google.com/calendar/feeds/default/allcalendars/full')), 'AuthSub token=foo') resp = self.cal_client.Get('/calendar/feeds/default/allcalendars/full') self.assert_(resp is not None) class CalendarEventQueryUnitTest(unittest.TestCase): def setUp(self): self.query = gdata.calendar.service.CalendarEventQuery() def testOrderByValidatesValues(self): self.query.orderby = 'lastmodified' self.assertEquals(self.query.orderby, 'lastmodified') try: self.query.orderby = 'illegal input' self.fail() except gdata.calendar.service.Error: self.assertEquals(self.query.orderby, 'lastmodified') def testSortOrderValidatesValues(self): self.query.sortorder = 'a' self.assertEquals(self.query.sortorder, 'a') try: self.query.sortorder = 'illegal input' self.fail() except gdata.calendar.service.Error: self.assertEquals(self.query.sortorder, 'a') def testTimezoneParameter(self): self.query.ctz = 'America/Los_Angeles' self.assertEquals(self.query['ctz'], 'America/Los_Angeles') self.assert_(self.query.ToUri().find('America%2FLos_Angeles') > -1) if __name__ == '__main__': print ('Google Calendar Test\nNOTE: Please run these tests only with a ' 'test account. The tests may delete or update your data.') username = raw_input('Please enter your username: ') password = getpass.getpass() unittest.main() gdata-2.0.18/tests/gdata_tests/calendar/calendar_acl_test.py0000640036466300116100000002036112156622363024156 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright (C) 2007 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. __author__ = 'api.lliabraa@google.com (Lane LiaBraaten)' import unittest try: from xml.etree import ElementTree except ImportError: from elementtree import ElementTree import atom import gdata.calendar import gdata.calendar.service import gdata.service import random import getpass from gdata import test_data username = '' password = '' class CalendarServiceAclUnitTest(unittest.TestCase): _aclFeedUri = "/calendar/feeds/default/acl/full" _aclEntryUri = "%s/user:%s" % (_aclFeedUri, "user@gmail.com",) def setUp(self): self.cal_client = gdata.calendar.service.CalendarService() self.cal_client.email = username self.cal_client.password = password self.cal_client.source = 'GCalendarClient ACL "Unit" Tests' def tearDown(self): # No teardown needed pass def _getRandomNumber(self): """Return a random number as a string for testing""" r = random.Random() r.seed() return str(r.randint(100000,1000000)) def _generateAclEntry(self, role="owner", scope_type="user", scope_value=None): """Generates a ACL rule from parameters or makes a random user an owner by default""" if (scope_type=="user" and scope_value is None): scope_value = "user%s@gmail.com" % (self._getRandomNumber()) rule = gdata.calendar.CalendarAclEntry() rule.title = atom.Title(text=role) rule.scope = gdata.calendar.Scope(value=scope_value, type="user") rule.role = gdata.calendar.Role(value="http://schemas.google.com/gCal/2005#%s" % (role)) return rule def assertEqualAclEntry(self, expected, actual): """Compares the values of two ACL entries""" self.assertEqual(expected.role.value, actual.role.value) self.assertEqual(expected.scope.value, actual.scope.value) self.assertEqual(expected.scope.type, actual.scope.type) def testGetAclFeedUnauthenticated(self): """Fiendishly try to get an ACL feed without authenticating""" try: self.cal_client.GetCalendarAclFeed(self._aclFeedUri) self.fail("Unauthenticated request should fail") except gdata.service.RequestError, error: self.assertEqual(error[0]['status'], 401) self.assertEqual(error[0]['reason'], "Authorization required") def testGetAclFeed(self): """Get an ACL feed""" self.cal_client.ProgrammaticLogin() feed = self.cal_client.GetCalendarAclFeed(self._aclFeedUri) self.assertNotEqual(0,len(feed.entry)) def testGetAclEntryUnauthenticated(self): """Fiendishly try to get an ACL entry without authenticating""" try: self.cal_client.GetCalendarAclEntry(self._aclEntryUri) self.fail("Unauthenticated request should fail"); except gdata.service.RequestError, error: self.assertEqual(error[0]['status'], 401) self.assertEqual(error[0]['reason'], "Authorization required") def testGetAclEntry(self): """Get an ACL entry""" self.cal_client.ProgrammaticLogin() self.cal_client.GetCalendarAclEntry(self._aclEntryUri) def testCalendarAclFeedFromString(self): """Create an ACL feed from a hard-coded string""" aclFeed = gdata.calendar.CalendarAclFeedFromString(test_data.ACL_FEED) self.assertEqual("Elizabeth Bennet's access control list", aclFeed.title.text) self.assertEqual(2,len(aclFeed.entry)) def testCalendarAclEntryFromString(self): """Create an ACL entry from a hard-coded string""" aclEntry = gdata.calendar.CalendarAclEntryFromString(test_data.ACL_ENTRY) self.assertEqual("owner", aclEntry.title.text) self.assertEqual("user", aclEntry.scope.type) self.assertEqual("liz@gmail.com", aclEntry.scope.value) self.assertEqual("http://schemas.google.com/gCal/2005#owner", aclEntry.role.value) def testCreateAndDeleteAclEntry(self): """Add an ACL rule and verify that is it returned in the ACL feed. Then delete the rule and verify that the rule is no longer included in the ACL feed.""" # Get the current number of ACL rules self.cal_client.ProgrammaticLogin() aclFeed = self.cal_client.GetCalendarAclFeed(self._aclFeedUri) original_rule_count = len(aclFeed.entry) # Insert entry rule = self._generateAclEntry() returned_rule = self.cal_client.InsertAclEntry(rule, self._aclFeedUri) # Verify rule was added with correct ACL values aclFeed = self.cal_client.GetCalendarAclFeed(self._aclFeedUri) self.assertEqual(original_rule_count+1, len(aclFeed.entry)) self.assertEqualAclEntry(rule, returned_rule) # Delete the event self.cal_client.DeleteAclEntry(returned_rule.GetEditLink().href) aclFeed = self.cal_client.GetCalendarAclFeed(self._aclFeedUri) self.assertEquals(original_rule_count, len(aclFeed.entry)) def testUpdateAclChangeScopeValue(self): """Fiendishly try to insert a test ACL rule and attempt to change the scope value (i.e. username). Verify that an exception is thrown, then delete the test rule.""" # Insert a user-scoped owner role ot random user aclEntry = self._generateAclEntry("owner","user"); self.cal_client.ProgrammaticLogin() rule = self._generateAclEntry() returned_rule = self.cal_client.InsertAclEntry(rule, self._aclFeedUri) # Change the scope value (i.e. what user is the owner) and update the entry updated_rule = returned_rule updated_rule.scope.value = "user_%s@gmail.com" % (self._getRandomNumber()) try: returned_rule = self.cal_client.UpdateAclEntry(returned_rule.GetEditLink().href, updated_rule) except gdata.service.RequestError, error: self.assertEqual(error[0]['status'], 403) self.assertEqual(error[0]['reason'], "Forbidden") self.cal_client.DeleteAclEntry(updated_rule.GetEditLink().href) def testUpdateAclChangeScopeType(self): """Fiendishly try to insert a test ACL rule and attempt to change the scope type (i.e. from 'user' to 'domain'). Verify that an exception is thrown, then delete the test rule.""" # Insert a user-scoped owner role ot random user aclEntry = self._generateAclEntry("owner","user"); self.cal_client.ProgrammaticLogin() rule = self._generateAclEntry() returned_rule = self.cal_client.InsertAclEntry(rule, self._aclFeedUri) # Change the scope value (i.e. what user is the owner) and update the entry updated_rule = returned_rule updated_rule.scope.type = "domain" try: returned_rule = self.cal_client.UpdateAclEntry(returned_rule.GetEditLink().href, updated_rule) except gdata.service.RequestError, error: self.assertEqual(error[0]['status'], 403) self.assertEqual(error[0]['reason'], "Forbidden") self.cal_client.DeleteAclEntry(updated_rule.GetEditLink().href) def testUpdateAclChangeRoleValue(self): """Insert a test ACL rule and attempt to change the scope type (i.e. from 'owner' to 'editor'). Verify that an exception is thrown, then delete the test rule.""" # Insert a user-scoped owner role ot random user aclEntry = self._generateAclEntry("owner","user"); self.cal_client.ProgrammaticLogin() rule = self._generateAclEntry() returned_rule = self.cal_client.InsertAclEntry(rule, self._aclFeedUri) # Change the scope value (i.e. what user is the owner) and update the entry updated_rule = returned_rule updated_rule.role.value = "http://schemas.google.com/gCal/2005#editor" returned_rule = self.cal_client.UpdateAclEntry(returned_rule.GetEditLink().href, updated_rule) self.assertEqualAclEntry(updated_rule, returned_rule) self.cal_client.DeleteAclEntry(updated_rule.GetEditLink().href) if __name__ == '__main__': print ('NOTE: Please run these tests only with a test account. ' + 'The tests may delete or update your data.') username = raw_input('Please enter your username: ') password = getpass.getpass() unittest.main() gdata-2.0.18/tests/gdata_tests/calendar/__init__.py0000640036466300116100000000000012156622363022252 0ustar afshareng00000000000000gdata-2.0.18/tests/gdata_tests/books/0000750036466300116100000000000012156625015017513 5ustar afshareng00000000000000gdata-2.0.18/tests/gdata_tests/books/service_test.py0000750036466300116100000000406012156622363022572 0ustar afshareng00000000000000#!/usr/bin/python __author__ = "James Sams " import unittest import getpass import atom import gdata.books import gdata.books.service from gdata import test_data username = "" password = "" class BookCRUDTests(unittest.TestCase): def setUp(self): self.service = gdata.books.service.BookService(email=username, password=password, source="Google-PythonGdataTest-1") if username and password: self.authenticated = True self.service.ProgrammaticLogin() else: self.authenticated = False def testPublicSearch(self): entry = self.service.get_by_google_id("b7GZr5Btp30C") self.assertEquals((entry.creator[0].text, entry.dc_title[0].text), ('John Rawls', 'A theory of justice')) feed = self.service.search_by_keyword(isbn="9780198250548") feed1 = self.service.search("9780198250548") self.assertEquals(len(feed.entry), 1) self.assertEquals(len(feed1.entry), 1) def testLibraryCrd(self): """ the success of the create operations assumes the book was not already in the library. if it was, there will not be a failure, but a successful add will not actually be tested. """ if not self.authenticated: return entry = self.service.get_by_google_id("b7GZr5Btp30C") entry = self.service.add_item_to_library(entry) lib = list(self.service.get_library()) self.assert_(entry.to_dict()['title'] in [x.to_dict()['title'] for x in lib]) self.service.remove_item_from_library(entry) lib = list(self.service.get_library()) self.assert_(entry.to_dict()['title'] not in [x.to_dict()['title'] for x in lib]) def testAnnotations(self): "annotations do not behave as expected" pass if __name__ == "__main__": print "Please use a test account. May cause data loss." username = raw_input("Google Username: ").strip() password = getpass.getpass() unittest.main() gdata-2.0.18/tests/gdata_tests/books/__init__.py0000640036466300116100000000000012156622363021616 0ustar afshareng00000000000000gdata-2.0.18/tests/gdata_tests/contacts_test.py0000750036466300116100000002034212156622363021634 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright (C) 2008 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. __author__ = 'api.jscudder (Jeffrey Scudder)' import unittest from gdata import test_data import atom import gdata.contacts class ContactEntryTest(unittest.TestCase): def setUp(self): self.entry = gdata.contacts.ContactEntryFromString(test_data.NEW_CONTACT) def testParsingTestEntry(self): self.assertEquals(self.entry.title.text, 'Fitzgerald') self.assertEquals(len(self.entry.email), 2) for email in self.entry.email: if email.rel == 'http://schemas.google.com/g/2005#work': self.assertEquals(email.address, 'liz@gmail.com') elif email.rel == 'http://schemas.google.com/g/2005#home': self.assertEquals(email.address, 'liz@example.org') self.assertEquals(len(self.entry.phone_number), 3) self.assertEquals(len(self.entry.postal_address), 1) self.assertEquals(self.entry.postal_address[0].primary, 'true') self.assertEquals(self.entry.postal_address[0].text, '1600 Amphitheatre Pkwy Mountain View') self.assertEquals(len(self.entry.im), 1) self.assertEquals(len(self.entry.group_membership_info), 1) self.assertEquals(self.entry.group_membership_info[0].href, 'http://google.com/m8/feeds/groups/liz%40gmail.com/base/270f') self.assertEquals(self.entry.group_membership_info[0].deleted, 'false') self.assertEquals(len(self.entry.extended_property), 2) self.assertEquals(self.entry.extended_property[0].name, 'pet') self.assertEquals(self.entry.extended_property[0].value, 'hamster') self.assertEquals(self.entry.extended_property[1].name, 'cousine') self.assertEquals( self.entry.extended_property[1].GetXmlBlobExtensionElement().tag, 'italian') def testToAndFromString(self): copied_entry = gdata.contacts.ContactEntryFromString(str(self.entry)) self.assertEquals(copied_entry.title.text, 'Fitzgerald') self.assertEquals(len(copied_entry.email), 2) for email in copied_entry.email: if email.rel == 'http://schemas.google.com/g/2005#work': self.assertEquals(email.address, 'liz@gmail.com') elif email.rel == 'http://schemas.google.com/g/2005#home': self.assertEquals(email.address, 'liz@example.org') self.assertEquals(len(copied_entry.phone_number), 3) self.assertEquals(len(copied_entry.postal_address), 1) self.assertEquals(copied_entry.postal_address[0].primary, 'true') self.assertEquals(copied_entry.postal_address[0].text, '1600 Amphitheatre Pkwy Mountain View') self.assertEquals(len(copied_entry.im), 1) self.assertEquals(len(copied_entry.group_membership_info), 1) self.assertEquals(copied_entry.group_membership_info[0].href, 'http://google.com/m8/feeds/groups/liz%40gmail.com/base/270f') self.assertEquals(copied_entry.group_membership_info[0].deleted, 'false') self.assertEquals(len(copied_entry.extended_property), 2) self.assertEquals(copied_entry.extended_property[0].name, 'pet') self.assertEquals(copied_entry.extended_property[0].value, 'hamster') self.assertEquals(copied_entry.extended_property[1].name, 'cousine') self.assertEquals( copied_entry.extended_property[1].GetXmlBlobExtensionElement().tag, 'italian') def testCreateContactFromScratch(self): # Create a new entry new_entry = gdata.contacts.ContactEntry() new_entry.title = atom.Title(text='Elizabeth Bennet') new_entry.content = atom.Content(text='Test Notes') new_entry.email.append(gdata.contacts.Email( rel='http://schemas.google.com/g/2005#work', address='liz@gmail.com')) new_entry.phone_number.append(gdata.contacts.PhoneNumber( rel='http://schemas.google.com/g/2005#work', text='(206)555-1212')) new_entry.organization = gdata.contacts.Organization( org_name=gdata.contacts.OrgName(text='TestCo.')) new_entry.extended_property.append(gdata.ExtendedProperty(name='test', value='1234')) new_entry.birthday = gdata.contacts.Birthday(when='2009-7-23') sports_property = gdata.ExtendedProperty(name='sports') sports_property.SetXmlBlob('') new_entry.extended_property.append(sports_property) # Generate and parse the XML for the new entry. entry_copy = gdata.contacts.ContactEntryFromString(str(new_entry)) self.assertEquals(entry_copy.title.text, new_entry.title.text) self.assertEquals(entry_copy.content.text, 'Test Notes') self.assertEquals(len(entry_copy.email), 1) self.assertEquals(entry_copy.email[0].rel, new_entry.email[0].rel) self.assertEquals(entry_copy.email[0].address, 'liz@gmail.com') self.assertEquals(len(entry_copy.phone_number), 1) self.assertEquals(entry_copy.phone_number[0].rel, new_entry.phone_number[0].rel) self.assertEquals(entry_copy.birthday.when, '2009-7-23') self.assertEquals(entry_copy.phone_number[0].text, '(206)555-1212') self.assertEquals(entry_copy.organization.org_name.text, 'TestCo.') self.assertEquals(len(entry_copy.extended_property), 2) self.assertEquals(entry_copy.extended_property[0].name, 'test') self.assertEquals(entry_copy.extended_property[0].value, '1234') class ContactsFeedTest(unittest.TestCase): def setUp(self): self.feed = gdata.contacts.ContactsFeedFromString(test_data.CONTACTS_FEED) def testParsingTestFeed(self): self.assertEquals(self.feed.id.text, 'http://www.google.com/m8/feeds/contacts/liz%40gmail.com/base') self.assertEquals(self.feed.title.text, 'Contacts') self.assertEquals(self.feed.total_results.text, '1') self.assertEquals(len(self.feed.entry), 1) self.assert_(isinstance(self.feed.entry[0], gdata.contacts.ContactEntry)) self.assertEquals(self.feed.entry[0].GetPhotoLink().href, 'http://google.com/m8/feeds/photos/media/liz%40gmail.com/c9012de') self.assertEquals(self.feed.entry[0].GetPhotoEditLink().href, 'http://www.google.com/m8/feeds/photos/media/liz%40gmail.com/' 'c9012de/photo4524') def testToAndFromString(self): copied_feed = gdata.contacts.ContactsFeedFromString(str(self.feed)) self.assertEquals(copied_feed.id.text, 'http://www.google.com/m8/feeds/contacts/liz%40gmail.com/base') self.assertEquals(copied_feed.title.text, 'Contacts') self.assertEquals(copied_feed.total_results.text, '1') self.assertEquals(len(copied_feed.entry), 1) self.assert_(isinstance(copied_feed.entry[0], gdata.contacts.ContactEntry)) class GroupsFeedTest(unittest.TestCase): def setUp(self): self.feed = gdata.contacts.GroupsFeedFromString( test_data.CONTACT_GROUPS_FEED) def testParsingGroupsFeed(self): self.assertEquals(self.feed.id.text, 'jo@gmail.com') self.assertEquals(self.feed.title.text, 'Jo\'s Contact Groups') self.assertEquals(self.feed.total_results.text, '3') self.assertEquals(len(self.feed.entry), 1) self.assert_(isinstance(self.feed.entry[0], gdata.contacts.GroupEntry)) class GroupEntryTest(unittest.TestCase): def setUp(self): self.entry = gdata.contacts.GroupEntryFromString( test_data.CONTACT_GROUP_ENTRY) def testParsingTestEntry(self): self.assertEquals(self.entry.title.text, 'Salsa group') self.assertEquals(len(self.entry.extended_property), 1) self.assertEquals(self.entry.extended_property[0].name, 'more info about the group') self.assertEquals( self.entry.extended_property[0].GetXmlBlobExtensionElement().namespace, atom.ATOM_NAMESPACE) self.assertEquals( self.entry.extended_property[0].GetXmlBlobExtensionElement().tag, 'info') self.assertEquals( self.entry.extended_property[0].GetXmlBlobExtensionElement().text, 'Very nice people.') if __name__ == '__main__': unittest.main() gdata-2.0.18/tests/gdata_tests/calendar_resource/0000750036466300116100000000000012156625015022056 5ustar afshareng00000000000000gdata-2.0.18/tests/gdata_tests/calendar_resource/data_test.py0000750036466300116100000000633412156622363024414 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright (C) 2008 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. __author__ = 'Vic Fryzel ' import unittest import atom.core from gdata import test_data import gdata.calendar_resource.data import gdata.test_config as conf class CalendarResourceEntryTest(unittest.TestCase): def setUp(self): self.entry = atom.core.parse(test_data.CALENDAR_RESOURCE_ENTRY, gdata.calendar_resource.data.CalendarResourceEntry) self.feed = atom.core.parse(test_data.CALENDAR_RESOURCES_FEED, gdata.calendar_resource.data.CalendarResourceFeed) def testCalendarResourceEntryFromString(self): self.assert_(isinstance(self.entry, gdata.calendar_resource.data.CalendarResourceEntry)) self.assertEquals(self.entry.resource_id, 'CR-NYC-14-12-BR') self.assertEquals(self.entry.resource_common_name, 'Boardroom') self.assertEquals(self.entry.resource_description, ('This conference room is in New York City, building 14, floor 12, ' 'Boardroom')) self.assertEquals(self.entry.resource_type, 'CR') def testCalendarResourceFeedFromString(self): self.assertEquals(len(self.feed.entry), 2) self.assert_(isinstance(self.feed, gdata.calendar_resource.data.CalendarResourceFeed)) self.assert_(isinstance(self.feed.entry[0], gdata.calendar_resource.data.CalendarResourceEntry)) self.assert_(isinstance(self.feed.entry[1], gdata.calendar_resource.data.CalendarResourceEntry)) self.assertEquals( self.feed.entry[0].find_edit_link(), 'https://apps-apis.google.com/feeds/calendar/resource/2.0/yourdomain.com/CR-NYC-14-12-BR') self.assertEquals(self.feed.entry[0].resource_id, 'CR-NYC-14-12-BR') self.assertEquals(self.feed.entry[0].resource_common_name, 'Boardroom') self.assertEquals(self.feed.entry[0].resource_description, ('This conference room is in New York City, building 14, floor 12, ' 'Boardroom')) self.assertEquals(self.feed.entry[0].resource_type, 'CR') self.assertEquals(self.feed.entry[1].resource_id, '(Bike)-London-43-Lobby-Bike-1') self.assertEquals(self.feed.entry[1].resource_common_name, 'London bike-1') self.assertEquals(self.feed.entry[1].resource_description, 'Bike is in London at building 43\'s lobby.') self.assertEquals(self.feed.entry[1].resource_type, '(Bike)') self.assertEquals( self.feed.entry[1].find_edit_link(), 'https://apps-apis.google.com/a/feeds/calendar/resource/2.0/yourdomain.com/(Bike)-London-43-Lobby-Bike-1') def suite(): return conf.build_suite([CalendarResourceEntryTest]) if __name__ == '__main__': unittest.main() gdata-2.0.18/tests/gdata_tests/calendar_resource/__init__.py0000640036466300116100000000000012156622363024161 0ustar afshareng00000000000000gdata-2.0.18/tests/gdata_tests/calendar_resource/live_client_test.py0000750036466300116100000001257112156622363026000 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright 2009 Google Inc. All Rights Reserved. # # 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. # This module is used for version 2 of the Google Data APIs. # These tests attempt to connect to Google servers. __author__ = 'Vic Fryzel ' import unittest import gdata.client import gdata.data import gdata.gauth import gdata.calendar_resource.client import gdata.calendar_resource.data import gdata.test_config as conf conf.options.register_option(conf.APPS_DOMAIN_OPTION) class CalendarResourceClientTest(unittest.TestCase): def setUp(self): self.client = gdata.calendar_resource.client.CalendarResourceClient( domain='example.com') if conf.options.get_value('runlive') == 'true': self.client = gdata.calendar_resource.client.CalendarResourceClient( domain=conf.options.get_value('appsdomain')) if conf.options.get_value('ssl') == 'true': self.client.ssl = True conf.configure_client(self.client, 'CalendarResourceClientTest', self.client.auth_service, True) def tearDown(self): conf.close_client(self.client) def testClientConfiguration(self): self.assertEqual('apps-apis.google.com', self.client.host) self.assertEqual('2.0', self.client.api_version) self.assertEqual('apps', self.client.auth_service) self.assertEqual(gdata.gauth.AUTH_SCOPES['apps'], self.client.auth_scopes) if conf.options.get_value('runlive') == 'true': self.assertEqual(self.client.domain, conf.options.get_value('appsdomain')) else: self.assertEqual(self.client.domain, 'example.com') def testMakeResourceFeedUri(self): self.assertEqual('/a/feeds/calendar/resource/2.0/%s/' % self.client.domain, self.client.MakeResourceFeedUri()) self.assertEqual('/a/feeds/calendar/resource/2.0/%s/CR-NYC-14-12-BR' % self.client.domain, self.client.MakeResourceFeedUri(resource_id='CR-NYC-14-12-BR')) self.assertEqual('/a/feeds/calendar/resource/2.0/%s/?test=1' % self.client.domain, self.client.MakeResourceFeedUri(params={'test': 1})) self.assertEqual('/a/feeds/calendar/resource/2.0/%s/CR-NYC-14-12-BR?test=1' % self.client.domain, self.client.MakeResourceFeedUri(resource_id='CR-NYC-14-12-BR', params={'test': 1})) def testCreateRetrieveUpdateDelete(self): if not conf.options.get_value('runlive') == 'true': return # Either load the recording or prepare to make a live request. conf.configure_cache(self.client, 'testCreateUpdateDelete') try: new_entry = self.client.CreateResource( 'CR-NYC-14-12-BR', 'Boardroom', ('This conference room is in New York City, building 14, floor 12, ' 'Boardroom'), 'CR') except Exception, e: print e self.client.delete_resource('CR-NYC-14-12-BR') # If the test failed to run to completion # the resource may already exist new_entry = self.client.CreateResource( 'CR-NYC-14-12-BR', 'Boardroom', ('This conference room is in New York City, building 14, floor 12, ' 'Boardroom'), 'CR') self.assert_(isinstance(new_entry, gdata.calendar_resource.data.CalendarResourceEntry)) self.assertEqual(new_entry.resource_id, 'CR-NYC-14-12-BR') self.assertEqual(new_entry.resource_common_name, 'Boardroom') self.assertEqual(new_entry.resource_description, ('This conference room is in New York City, building 14, floor 12, ' 'Boardroom')) self.assertEqual(new_entry.resource_type, 'CR') fetched_entry = self.client.get_resource(resource_id='CR-NYC-14-12-BR') self.assert_(isinstance(fetched_entry, gdata.calendar_resource.data.CalendarResourceEntry)) self.assertEqual(fetched_entry.resource_id, 'CR-NYC-14-12-BR') self.assertEqual(fetched_entry.resource_common_name, 'Boardroom') self.assertEqual(fetched_entry.resource_description, ('This conference room is in New York City, building 14, floor 12, ' 'Boardroom')) self.assertEqual(fetched_entry.resource_type, 'CR') new_entry.resource_id = 'CR-MTV-14-12-BR' new_entry.resource_common_name = 'Executive Boardroom' new_entry.resource_description = 'This conference room is in Mountain View' new_entry.resource_type = 'BR' updated_entry = self.client.update(new_entry) self.assert_(isinstance(updated_entry, gdata.calendar_resource.data.CalendarResourceEntry)) self.assertEqual(updated_entry.resource_id, 'CR-MTV-14-12-BR') self.assertEqual(updated_entry.resource_common_name, 'Executive Boardroom') self.assertEqual(updated_entry.resource_description, 'This conference room is in Mountain View') self.assertEqual(updated_entry.resource_type, 'BR') self.client.delete_resource('CR-NYC-14-12-BR') def suite(): return conf.build_suite([CalendarResourceClientTest]) if __name__ == '__main__': unittest.TextTestRunner().run(suite()) gdata-2.0.18/tests/gdata_tests/projecthosting/0000750036466300116100000000000012156625015021440 5ustar afshareng00000000000000gdata-2.0.18/tests/gdata_tests/projecthosting/data_test.py0000750036466300116100000001457612156622363024005 0ustar afshareng00000000000000#!/usr/bin/env python # # Copyright (C) 2009 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. # This module is used for version 2 of the Google Data APIs. __author__ = 'jlapenna@google.com (Joe LaPenna)' import unittest from gdata import test_data import gdata.projecthosting.data import atom.core import gdata.test_config as conf ISSUE_ENTRY = """\ http://code.google.com/feeds/issues/p/PROJECT_NAME/issues/full/1 2009-09-09T20:34:35.365Z This is updated issue summary This is issue description elizabeth.bennet /u/elizabeth.bennet/ /u/@UBhTQl1UARRAVga7/ mar...@domain.com /u/fitzwilliam.darcy/ fitzwilliam.darcy Type-Enhancement Priority-Low /u/charlotte.lucas/ charlotte.lucas 0 open Started """ ISSUES_FEED = """\ http://code.google.com/feeds/issues/p/android-test2/issues/full 2009-09-22T04:06:32.794Z %s """ % ISSUE_ENTRY COMMENT_ENTRY = """\ This is comment - update issue elizabeth.bennet This is updated issue summary Started charlotte.lucas -Type-Defect Type-Enhancement -Milestone-2009 -Priority-Medium Priority-Low -fitzwilliam.darcy marialucas@domain.com """ class CommentEntryTest(unittest.TestCase): def testParsing(self): entry = atom.core.parse(COMMENT_ENTRY, gdata.projecthosting.data.CommentEntry) updates = entry.updates self.assertEquals(updates.summary.text, 'This is updated issue summary') self.assertEquals(updates.status.text, 'Started') self.assertEquals(updates.ownerUpdate.text, 'charlotte.lucas') self.assertEquals(len(updates.label), 5) self.assertEquals(updates.label[0].text, '-Type-Defect') self.assertEquals(updates.label[1].text, 'Type-Enhancement') self.assertEquals(updates.label[2].text, '-Milestone-2009') self.assertEquals(updates.label[3].text, '-Priority-Medium') self.assertEquals(updates.label[4].text, 'Priority-Low') self.assertEquals(len(updates.ccUpdate), 2) self.assertEquals(updates.ccUpdate[0].text, '-fitzwilliam.darcy') self.assertEquals(updates.ccUpdate[1].text, 'marialucas@domain.com') class IssueEntryTest(unittest.TestCase): def testParsing(self): entry = atom.core.parse(ISSUE_ENTRY, gdata.projecthosting.data.IssueEntry) self.assertEquals(entry.owner.uri.text, '/u/charlotte.lucas/') self.assertEqual(entry.owner.username.text, 'charlotte.lucas') self.assertEquals(len(entry.cc), 2) cc_0 = entry.cc[0] self.assertEquals(cc_0.uri.text, '/u/@UBhTQl1UARRAVga7/') self.assertEquals(cc_0.username.text, 'mar...@domain.com') cc_1 = entry.cc[1] self.assertEquals(cc_1.uri.text, '/u/fitzwilliam.darcy/') self.assertEquals(cc_1.username.text, 'fitzwilliam.darcy') self.assertEquals(len(entry.label), 2) self.assertEquals(entry.label[0].text, 'Type-Enhancement') self.assertEquals(entry.label[1].text, 'Priority-Low') self.assertEquals(entry.stars.text, '0') self.assertEquals(entry.state.text, 'open') self.assertEquals(entry.status.text, 'Started') class DataClassSanityTest(unittest.TestCase): def test_basic_element_structure(self): conf.check_data_classes(self, [ gdata.projecthosting.data.Uri, gdata.projecthosting.data.Username, gdata.projecthosting.data.Cc, gdata.projecthosting.data.Label, gdata.projecthosting.data.Owner, gdata.projecthosting.data.Stars, gdata.projecthosting.data.State, gdata.projecthosting.data.Status, gdata.projecthosting.data.Summary, gdata.projecthosting.data.Updates, gdata.projecthosting.data.IssueEntry, gdata.projecthosting.data.IssuesFeed, gdata.projecthosting.data.CommentEntry, gdata.projecthosting.data.CommentsFeed]) def suite(): return conf.build_suite([IssueEntryTest, DataClassSanityTest]) if __name__ == '__main__': unittest.main() gdata-2.0.18/tests/gdata_tests/projecthosting/__init__.py0000750036466300116100000000000012156622363023545 0ustar afshareng00000000000000gdata-2.0.18/tests/gdata_tests/projecthosting/live_client_test.py0000750036466300116100000002050512156622363025356 0ustar afshareng00000000000000#!/usr/bin/env python # # Copyright (C) 2009 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. # This module is used for version 2 of the Google Data APIs. # These tests attempt to connect to Google servers. __author__ = 'jlapenna@google.com (Joe LaPenna)' import unittest import gdata.projecthosting.client import gdata.projecthosting.data import gdata.gauth import gdata.client import atom.http_core import atom.mock_http_core import atom.core import gdata.data import gdata.test_config as conf conf.options.register_option(conf.PROJECT_NAME_OPTION) conf.options.register_option(conf.ISSUE_ASSIGNEE_OPTION) class ProjectHostingClientTest(unittest.TestCase): def setUp(self): self.client = None if conf.options.get_value('runlive') == 'true': self.client = gdata.projecthosting.client.ProjectHostingClient() conf.configure_client(self.client, 'ProjectHostingClientTest', 'code') self.project_name = conf.options.get_value('project_name') self.assignee = conf.options.get_value('issue_assignee') self.owner = conf.options.get_value('username') def tearDown(self): conf.close_client(self.client) def create_issue(self): # Add an issue created = self.client.add_issue( self.project_name, 'my title', 'my summary', self.owner, labels=['label0']) self.assertEqual(created.title.text, 'my title') self.assertEqual(created.content.text, 'my summary') self.assertEqual(len(created.label), 1) self.assertEqual(created.label[0].text, 'label0') return created def test_create_update_close(self): if not conf.options.get_value('runlive') == 'true': return # Either load the recording or prepare to make a live request. conf.configure_cache(self.client, 'test_create_update_delete') # Create the issue: created = self.create_issue() # Change the issue we just added. issue_id = created.id.text.split('/')[-1] update_response = self.client.update_issue( self.project_name, issue_id, self.owner, comment='My comment here.', summary='New Summary', status='Accepted', owner=self.assignee, labels=['-label0', 'label1'], ccs=[self.owner]) updates = update_response.updates # Make sure it changed our status, summary, and added the comment. self.assertEqual(update_response.content.text, 'My comment here.') self.assertEqual(updates.summary.text, 'New Summary') self.assertEqual(updates.status.text, 'Accepted') # Make sure it got all our label change requests. self.assertEquals(len(updates.label), 2) self.assertEquals(updates.label[0].text, '-label0') self.assertEquals(updates.label[1].text, 'label1') # Be sure it saw our CC change. We can't check the specific values (yet) # because ccUpdate and ownerUpdate responses are mungled. self.assertEquals(len(updates.ccUpdate), 1) self.assert_(updates.ownerUpdate.text) def test_get_issues(self): if not conf.options.get_value('runlive') == 'true': return # Either load the recording or prepare to make a live request. conf.configure_cache(self.client, 'test_create_update_delete') # Create an issue so we have something to look up. created = self.create_issue() # The fully qualified id is a url, we just want the number. issue_id = created.id.text.split('/')[-1] # Get the specific issue in our issues feed. You could use label, # canned_query and others just the same. query = gdata.projecthosting.client.Query(label='label0') feed = self.client.get_issues(self.project_name, query=query) # Make sure we at least find the entry we created with that label. self.assert_(len(feed.entry) > 0) for issue in feed.entry: label_texts = [label.text for label in issue.label] self.assert_('label0' in label_texts, 'Issue does not have label label0') def test_get_comments(self): if not conf.options.get_value('runlive') == 'true': return # Either load the recording or prepare to make a live request. conf.configure_cache(self.client, 'test_create_update_delete') # Create an issue so we have something to look up. created = self.create_issue() # The fully qualified id is a url, we just want the number. issue_id = created.id.text.split('/')[-1] # Now lets add two comments to that issue. for i in range(2): update_response = self.client.update_issue( self.project_name, issue_id, self.owner, comment='My comment here %s' % i) # We have an issue that has several comments. Lets get them. comments_feed = self.client.get_comments(self.project_name, issue_id) # It has 2 comments. self.assertEqual(2, len(comments_feed.entry)) class ProjectHostingDocExamplesTest(unittest.TestCase): def setUp(self): self.project_name = conf.options.get_value('project_name') self.assignee = conf.options.get_value('issue_assignee') self.owner = conf.options.get_value('username') self.password = conf.options.get_value('password') def test_doc_examples(self): if not conf.options.get_value('runlive') == 'true': return issues_client = gdata.projecthosting.client.ProjectHostingClient() self.authenticating_client(issues_client, self.owner, self.password) issue = self.creating_issues(issues_client, self.project_name, self.owner) issue_id = issue.id.text.split('/')[-1] self.retrieving_all_issues(issues_client, self.project_name) self.retrieving_issues_using_query_parameters( issues_client, self.project_name) self.modifying_an_issue_or_creating_issue_comments( issues_client, self.project_name, issue_id, self.owner, self.assignee) self.retrieving_issues_comments_for_an_issue( issues_client, self.project_name, issue_id) def authenticating_client(self, client, username, password): return client.client_login( username, password, source='your-client-name', service='code') def creating_issues(self, client, project_name, owner): """Create an issue.""" return client.add_issue( project_name, 'my title', 'my summary', owner, labels=['label0']) def retrieving_all_issues(self, client, project_name): """Retrieve all the issues in a project.""" feed = client.get_issues(project_name) for issue in feed.entry: self.assert_(issue.title.text is not None) def retrieving_issues_using_query_parameters(self, client, project_name): """Retrieve a set of issues in a project.""" query = gdata.projecthosting.client.Query(label='label0', max_results=1000) feed = client.get_issues(project_name, query=query) for issue in feed.entry: self.assert_(issue.title.text is not None) return feed def retrieving_issues_comments_for_an_issue(self, client, project_name, issue_id): """Retrieve all issue comments for an issue.""" comments_feed = client.get_comments(project_name, issue_id) for comment in comments_feed.entry: self.assert_(comment.content is not None) return comments_feed def modifying_an_issue_or_creating_issue_comments(self, client, project_name, issue_id, owner, assignee): """Add a comment and update metadata in an issue.""" return client.update_issue( project_name, issue_id, owner, comment='My comment here.', summary='New Summary', status='Accepted', owner=assignee, labels=['-label0', 'label1'], ccs=[owner]) def suite(): return conf.build_suite([ProjectHostingClientTest, ProjectHostingDocExamplesTest]) if __name__ == '__main__': unittest.TextTestRunner().run(suite()) gdata-2.0.18/tests/gdata_tests/auth_test.py0000750036466300116100000006435312156622363020771 0ustar afshareng00000000000000#!/usr/bin/env python # # Copyright (C) 2007, 2008 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. __author__ = 'api.jscudder (Jeff Scudder)' import re import unittest import urllib import gdata.auth CONSUMER_KEY = 'www.yourwebapp.com' CONSUMER_SECRET = 'qB1P2kCFDpRjF+/Iww4' RSA_KEY = """-----BEGIN RSA PRIVATE KEY----- MIICXAIBAAKBgQDVbOaFW+KXecfFJn1PIzYHnNXFxhaQ36QM0K5uSb0Y8NeQUlD2 6t8aKgnm6mcb4vaopHjjdIGWgAzM5Dt0oPIiDXo+jSQbvCIXRduuAt+0cFGb2d+L hALk4AwB8IVIkDJWwgo5Z2OLsP2r/wQlUYKm/tnvQaevK24jNYMLWVJl2QIDAQAB AoGAU93ERBlUVEPFjaJPUX67p4gotNvfWDSZiXOjZ7FQPnG9s3e1WyH2Y5irZXMs 61dnp+NhobfRiGtvHEB/YJgyLRk/CJDnMKslo95e7o65IE9VkcyY6Yvt7YTslsRX Eu7T0xLEA7ON46ypCwNLeWxpJ9SWisEKu2yZJnWauCXEsgUCQQD7b2ZuhGx3msoP YEnwvucp0UxneCvb68otfERZ1J6NfNP47QJw6OwD3r1sWCJ27QZmpvtQH1f8sCk9 t22anGG7AkEA2UzXdtQ8H1uLAN/XXX2qoLuvJK5jRswHS4GeOg4pnnDSiHg3Vbva AxmMIL93ufvIy/xdoENwDPfcI4CbYlrDewJAGWy7W+OSIEoLsqBW+bwkHetnIXNa ZAOkzxKoyrigS8hamupEe+xhqUaFuwXyfjobkpfCA+kXeZrKoM4CjEbR7wJAHMbf Vd4/ZAu0edYq6DenLAgO5rWtcge9A5PTx25utovMZcQ917273mM4unGAwoGEkvcF 0x57LUx5u73hVgIdFwJBAKWGuHRwGPgTWYvhpHM0qveH+8KdU9BUt/kV4ONxIVDB ftetEmJirqOGLECbImoLcUwQrgfMW4ZCxOioJMz/gY0= -----END RSA PRIVATE KEY----- """ class AuthModuleUtilitiesTest(unittest.TestCase): def testGenerateClientLoginRequestBody(self): body = gdata.auth.GenerateClientLoginRequestBody('jo@gmail.com', 'password', 'test service', 'gdata.auth test') expected_parameters = {'Email':r'jo%40gmail.com', 'Passwd':'password', 'service':'test+service', 'source':'gdata.auth+test', 'accountType':'HOSTED_OR_GOOGLE'} self.__matchBody(body, expected_parameters) body = gdata.auth.GenerateClientLoginRequestBody('jo@gmail.com', 'password', 'test service', 'gdata.auth test', account_type='A TEST', captcha_token='12345', captcha_response='test') expected_parameters['accountType'] = 'A+TEST' expected_parameters['logintoken'] = '12345' expected_parameters['logincaptcha'] = 'test' self.__matchBody(body, expected_parameters) def __matchBody(self, body, expected_name_value_pairs): parameters = body.split('&') for param in parameters: (name, value) = param.split('=') self.assert_(expected_name_value_pairs[name] == value) def testGenerateClientLoginAuthToken(self): http_body = ('SID=DQAAAGgA7Zg8CTN\r\n' 'LSID=DQAAAGsAlk8BBbG\r\n' 'Auth=DQAAAGgAdk3fA5N') self.assert_(gdata.auth.GenerateClientLoginAuthToken(http_body) == 'GoogleLogin auth=DQAAAGgAdk3fA5N') class GenerateClientLoginRequestBodyTest(unittest.TestCase): def testPostBodyShouldMatchShortExample(self): auth_body = gdata.auth.GenerateClientLoginRequestBody('johndoe@gmail.com', 'north23AZ', 'cl', 'Gulp-CalGulp-1.05') self.assert_(-1 < auth_body.find('Email=johndoe%40gmail.com')) self.assert_(-1 < auth_body.find('Passwd=north23AZ')) self.assert_(-1 < auth_body.find('service=cl')) self.assert_(-1 < auth_body.find('source=Gulp-CalGulp-1.05')) def testPostBodyShouldMatchLongExample(self): auth_body = gdata.auth.GenerateClientLoginRequestBody('johndoe@gmail.com', 'north23AZ', 'cl', 'Gulp-CalGulp-1.05', captcha_token='DQAAAGgA...dkI1', captcha_response='brinmar') self.assert_(-1 < auth_body.find('logintoken=DQAAAGgA...dkI1')) self.assert_(-1 < auth_body.find('logincaptcha=brinmar')) def testEquivalenceWithOldLogic(self): email = 'jo@gmail.com' password = 'password' account_type = 'HOSTED' service = 'test' source = 'auth test' old_request_body = urllib.urlencode({'Email': email, 'Passwd': password, 'accountType': account_type, 'service': service, 'source': source}) new_request_body = gdata.auth.GenerateClientLoginRequestBody(email, password, service, source, account_type=account_type) for parameter in old_request_body.split('&'): self.assert_(-1 < new_request_body.find(parameter)) class GenerateAuthSubUrlTest(unittest.TestCase): def testDefaultParameters(self): url = gdata.auth.GenerateAuthSubUrl('http://example.com/xyz?x=5', 'http://www.google.com/test/feeds') self.assert_(-1 < url.find( r'scope=http%3A%2F%2Fwww.google.com%2Ftest%2Ffeeds')) self.assert_(-1 < url.find( r'next=http%3A%2F%2Fexample.com%2Fxyz%3Fx%3D5')) self.assert_(-1 < url.find('secure=0')) self.assert_(-1 < url.find('session=1')) def testAllParameters(self): url = gdata.auth.GenerateAuthSubUrl('http://example.com/xyz?x=5', 'http://www.google.com/test/feeds', secure=True, session=False, request_url='https://example.com/auth') self.assert_(-1 < url.find( r'scope=http%3A%2F%2Fwww.google.com%2Ftest%2Ffeeds')) self.assert_(-1 < url.find( r'next=http%3A%2F%2Fexample.com%2Fxyz%3Fx%3D5')) self.assert_(-1 < url.find('secure=1')) self.assert_(-1 < url.find('session=0')) self.assert_(url.startswith('https://example.com/auth')) class GenerateOAuthRequestTokenUrlTest(unittest.TestCase): def testDefaultParameters(self): oauth_input_params = gdata.auth.OAuthInputParams( gdata.auth.OAuthSignatureMethod.RSA_SHA1, CONSUMER_KEY, rsa_key=RSA_KEY) scopes = [ 'http://abcd.example.com/feeds', 'http://www.example.com/abcd/feeds' ] url = gdata.auth.GenerateOAuthRequestTokenUrl( oauth_input_params, scopes=scopes) self.assertEquals('https', url.protocol) self.assertEquals('www.google.com', url.host) self.assertEquals('/accounts/OAuthGetRequestToken', url.path) self.assertEquals('1.0', url.params['oauth_version']) self.assertEquals('RSA-SHA1', url.params['oauth_signature_method']) self.assert_(url.params['oauth_nonce']) self.assert_(url.params['oauth_timestamp']) actual_scopes = url.params['scope'].split(' ') self.assertEquals(2, len(actual_scopes)) for scope in actual_scopes: self.assert_(scope in scopes) self.assertEquals(CONSUMER_KEY, url.params['oauth_consumer_key']) self.assert_(url.params['oauth_signature']) def testAllParameters(self): oauth_input_params = gdata.auth.OAuthInputParams( gdata.auth.OAuthSignatureMethod.HMAC_SHA1, CONSUMER_KEY, consumer_secret=CONSUMER_SECRET) scopes = ['http://abcd.example.com/feeds'] url = gdata.auth.GenerateOAuthRequestTokenUrl( oauth_input_params, scopes=scopes, request_token_url='https://www.example.com/accounts/OAuthRequestToken', extra_parameters={'oauth_version': '2.0', 'my_param': 'my_value'}) self.assertEquals('https', url.protocol) self.assertEquals('www.example.com', url.host) self.assertEquals('/accounts/OAuthRequestToken', url.path) self.assertEquals('2.0', url.params['oauth_version']) self.assertEquals('HMAC-SHA1', url.params['oauth_signature_method']) self.assert_(url.params['oauth_nonce']) self.assert_(url.params['oauth_timestamp']) actual_scopes = url.params['scope'].split(' ') self.assertEquals(1, len(actual_scopes)) for scope in actual_scopes: self.assert_(scope in scopes) self.assertEquals(CONSUMER_KEY, url.params['oauth_consumer_key']) self.assert_(url.params['oauth_signature']) self.assertEquals('my_value', url.params['my_param']) class GenerateOAuthAuthorizationUrlTest(unittest.TestCase): def testDefaultParameters(self): token_key = 'ABCDDSFFDSG' token_secret = 'SDFDSGSDADADSAF' request_token = gdata.auth.OAuthToken(key=token_key, secret=token_secret) url = gdata.auth.GenerateOAuthAuthorizationUrl(request_token) self.assertEquals('https', url.protocol) self.assertEquals('www.google.com', url.host) self.assertEquals('/accounts/OAuthAuthorizeToken', url.path) self.assertEquals(token_key, url.params['oauth_token']) def testAllParameters(self): token_key = 'ABCDDSFFDSG' token_secret = 'SDFDSGSDADADSAF' scopes = [ 'http://abcd.example.com/feeds', 'http://www.example.com/abcd/feeds' ] request_token = gdata.auth.OAuthToken(key=token_key, secret=token_secret, scopes=scopes) url = gdata.auth.GenerateOAuthAuthorizationUrl( request_token, authorization_url='https://www.example.com/accounts/OAuthAuthToken', callback_url='http://www.yourwebapp.com/print', extra_params={'permission': '1'}, include_scopes_in_callback=True, scopes_param_prefix='token_scope') self.assertEquals('https', url.protocol) self.assertEquals('www.example.com', url.host) self.assertEquals('/accounts/OAuthAuthToken', url.path) self.assertEquals(token_key, url.params['oauth_token']) expected_callback_url = ('http://www.yourwebapp.com/print?' 'token_scope=http%3A%2F%2Fabcd.example.com%2Ffeeds' '+http%3A%2F%2Fwww.example.com%2Fabcd%2Ffeeds') self.assertEquals(expected_callback_url, url.params['oauth_callback']) class GenerateOAuthAccessTokenUrlTest(unittest.TestCase): def testDefaultParameters(self): token_key = 'ABCDDSFFDSG' token_secret = 'SDFDSGSDADADSAF' authorized_request_token = gdata.auth.OAuthToken(key=token_key, secret=token_secret) oauth_input_params = gdata.auth.OAuthInputParams( gdata.auth.OAuthSignatureMethod.HMAC_SHA1, CONSUMER_KEY, consumer_secret=CONSUMER_SECRET) url = gdata.auth.GenerateOAuthAccessTokenUrl(authorized_request_token, oauth_input_params) self.assertEquals('https', url.protocol) self.assertEquals('www.google.com', url.host) self.assertEquals('/accounts/OAuthGetAccessToken', url.path) self.assertEquals(token_key, url.params['oauth_token']) self.assertEquals('1.0', url.params['oauth_version']) self.assertEquals('HMAC-SHA1', url.params['oauth_signature_method']) self.assert_(url.params['oauth_nonce']) self.assert_(url.params['oauth_timestamp']) self.assertEquals(CONSUMER_KEY, url.params['oauth_consumer_key']) self.assert_(url.params['oauth_signature']) def testAllParameters(self): token_key = 'ABCDDSFFDSG' authorized_request_token = gdata.auth.OAuthToken(key=token_key) oauth_input_params = gdata.auth.OAuthInputParams( gdata.auth.OAuthSignatureMethod.RSA_SHA1, CONSUMER_KEY, rsa_key=RSA_KEY) url = gdata.auth.GenerateOAuthAccessTokenUrl( authorized_request_token, oauth_input_params, access_token_url='https://www.example.com/accounts/OAuthGetAccessToken', oauth_version= '2.0') self.assertEquals('https', url.protocol) self.assertEquals('www.example.com', url.host) self.assertEquals('/accounts/OAuthGetAccessToken', url.path) self.assertEquals(token_key, url.params['oauth_token']) self.assertEquals('2.0', url.params['oauth_version']) self.assertEquals('RSA-SHA1', url.params['oauth_signature_method']) self.assert_(url.params['oauth_nonce']) self.assert_(url.params['oauth_timestamp']) self.assertEquals(CONSUMER_KEY, url.params['oauth_consumer_key']) self.assert_(url.params['oauth_signature']) class ExtractAuthSubTokensTest(unittest.TestCase): def testGetTokenFromUrl(self): url = 'http://www.yourwebapp.com/showcalendar.html?token=CKF50YzIH' self.assert_(gdata.auth.AuthSubTokenFromUrl(url) == 'AuthSub token=CKF50YzIH') self.assert_(gdata.auth.TokenFromUrl(url) == 'CKF50YzIH') url = 'http://www.yourwebapp.com/showcalendar.html?token==tokenCKF50YzIH=' self.assert_(gdata.auth.AuthSubTokenFromUrl(url) == 'AuthSub token==tokenCKF50YzIH=') self.assert_(gdata.auth.TokenFromUrl(url) == '=tokenCKF50YzIH=') def testGetTokenFromHttpResponse(self): response_body = ('Token=DQAA...7DCTN\r\n' 'Expiration=20061004T123456Z') self.assert_(gdata.auth.AuthSubTokenFromHttpBody(response_body) == 'AuthSub token=DQAA...7DCTN') class CreateAuthSubTokenFlowTest(unittest.TestCase): def testGenerateRequest(self): request_url = gdata.auth.generate_auth_sub_url(next='http://example.com', scopes=['http://www.blogger.com/feeds/', 'http://www.google.com/base/feeds/']) self.assertEquals(request_url.protocol, 'https') self.assertEquals(request_url.host, 'www.google.com') self.assertEquals(request_url.params['scope'], 'http://www.blogger.com/feeds/ http://www.google.com/base/feeds/') self.assertEquals(request_url.params['hd'], 'default') self.assert_(request_url.params['next'].find('auth_sub_scopes') > -1) self.assert_(request_url.params['next'].startswith('http://example.com')) # Use a more complicated 'next' URL. request_url = gdata.auth.generate_auth_sub_url( next='http://example.com/?token_scope=http://www.blogger.com/feeds/', scopes=['http://www.blogger.com/feeds/', 'http://www.google.com/base/feeds/']) self.assert_(request_url.params['next'].find('auth_sub_scopes') > -1) self.assert_(request_url.params['next'].find('token_scope') > -1) self.assert_(request_url.params['next'].startswith('http://example.com/')) def testParseNextUrl(self): url = ('http://example.com/?auth_sub_scopes=http%3A%2F%2Fwww.blogger.com' '%2Ffeeds%2F+http%3A%2F%2Fwww.google.com%2Fbase%2Ffeeds%2F&' 'token=my_nifty_token') token = gdata.auth.extract_auth_sub_token_from_url(url) self.assertEquals(token.get_token_string(), 'my_nifty_token') self.assert_(isinstance(token, gdata.auth.AuthSubToken)) self.assert_(token.valid_for_scope('http://www.blogger.com/feeds/')) self.assert_(token.valid_for_scope('http://www.google.com/base/feeds/')) self.assert_( not token.valid_for_scope('http://www.google.com/calendar/feeds/')) # Parse a more complicated response. url = ('http://example.com/?auth_sub_scopes=http%3A%2F%2Fwww.blogger.com' '%2Ffeeds%2F+http%3A%2F%2Fwww.google.com%2Fbase%2Ffeeds%2F&' 'token_scope=http%3A%2F%2Fwww.blogger.com%2Ffeeds%2F&' 'token=second_token') token = gdata.auth.extract_auth_sub_token_from_url(url) self.assertEquals(token.get_token_string(), 'second_token') self.assert_(isinstance(token, gdata.auth.AuthSubToken)) self.assert_(token.valid_for_scope('http://www.blogger.com/feeds/')) self.assert_(token.valid_for_scope('http://www.google.com/base/feeds/')) self.assert_( not token.valid_for_scope('http://www.google.com/calendar/feeds/')) def testParseNextWithNoToken(self): token = gdata.auth.extract_auth_sub_token_from_url('http://example.com/') self.assert_(token is None) token = gdata.auth.extract_auth_sub_token_from_url( 'http://example.com/?no_token=foo&other=1') self.assert_(token is None) class ExtractClientLoginTokenTest(unittest.TestCase): def testExtractFromBodyWithScopes(self): http_body_string = ('SID=DQAAAGgA7Zg8CTN\r\n' 'LSID=DQAAAGsAlk8BBbG\r\n' 'Auth=DQAAAGgAdk3fA5N') token = gdata.auth.extract_client_login_token(http_body_string, ['http://docs.google.com/feeds/']) self.assertEquals(token.get_token_string(), 'DQAAAGgAdk3fA5N') self.assert_(isinstance(token, gdata.auth.ClientLoginToken)) self.assert_(token.valid_for_scope('http://docs.google.com/feeds/')) self.assert_(not token.valid_for_scope('http://www.blogger.com/feeds')) class ExtractOAuthTokensTest(unittest.TestCase): def testOAuthTokenFromUrl(self): scope_1 = 'http://docs.google.com/feeds/' scope_2 = 'http://www.blogger.com/feeds/' # Case 1: token and scopes both are present. url = ('http://dummy.com/?oauth_token_scope=http%3A%2F%2Fwww.blogger.com' '%2Ffeeds%2F+http%3A%2F%2Fdocs.google.com%2Ffeeds%2F&' 'oauth_token=CMns6t7MCxDz__8B') token = gdata.auth.OAuthTokenFromUrl(url) self.assertEquals('CMns6t7MCxDz__8B', token.key) self.assertEquals(2, len(token.scopes)) self.assert_(scope_1 in token.scopes) self.assert_(scope_2 in token.scopes) # Case 2: token and scopes both are present but scope_param_prefix # passed does not match the one present in the URL. url = ('http://dummy.com/?oauth_token_scope=http%3A%2F%2Fwww.blogger.com' '%2Ffeeds%2F+http%3A%2F%2Fdocs.google.com%2Ffeeds%2F&' 'oauth_token=CMns6t7MCxDz__8B') token = gdata.auth.OAuthTokenFromUrl(url, scopes_param_prefix='token_scope') self.assertEquals('CMns6t7MCxDz__8B', token.key) self.assert_(not token.scopes) # Case 3: None present. url = ('http://dummy.com/?no_oauth_token_scope=http%3A%2F%2Fwww.blogger.com' '%2Ffeeds%2F+http%3A%2F%2Fdocs.google.com%2Ffeeds%2F&' 'no_oauth_token=CMns6t7MCxDz__8B') token = gdata.auth.OAuthTokenFromUrl(url) self.assert_(token is None) def testOAuthTokenFromHttpBody(self): token_key = 'ABCD' token_secret = 'XYZ' # Case 1: token key and secret both present single time. http_body = 'oauth_token=%s&oauth_token_secret=%s' % (token_key, token_secret) token = gdata.auth.OAuthTokenFromHttpBody(http_body) self.assertEquals(token_key, token.key) self.assertEquals(token_secret, token.secret) class OAuthInputParametersTest(unittest.TestCase): def setUp(self): self.oauth_input_parameters_hmac = gdata.auth.OAuthInputParams( gdata.auth.OAuthSignatureMethod.HMAC_SHA1, CONSUMER_KEY, consumer_secret=CONSUMER_SECRET) self.oauth_input_parameters_rsa = gdata.auth.OAuthInputParams( gdata.auth.OAuthSignatureMethod.RSA_SHA1, CONSUMER_KEY, rsa_key=RSA_KEY) def testGetSignatureMethod(self): self.assertEquals( 'HMAC-SHA1', self.oauth_input_parameters_hmac.GetSignatureMethod().get_name()) rsa_signature_method = self.oauth_input_parameters_rsa.GetSignatureMethod() self.assertEquals('RSA-SHA1', rsa_signature_method.get_name()) self.assertEquals(RSA_KEY, rsa_signature_method._fetch_private_cert(None)) def testGetConsumer(self): self.assertEquals(CONSUMER_KEY, self.oauth_input_parameters_hmac.GetConsumer().key) self.assertEquals(CONSUMER_KEY, self.oauth_input_parameters_rsa.GetConsumer().key) self.assertEquals(CONSUMER_SECRET, self.oauth_input_parameters_hmac.GetConsumer().secret) self.assert_(self.oauth_input_parameters_rsa.GetConsumer().secret is None) class TokenClassesTest(unittest.TestCase): def testClientLoginToAndFromString(self): token = gdata.auth.ClientLoginToken() token.set_token_string('foo') self.assertEquals(token.get_token_string(), 'foo') self.assertEquals(token.auth_header, '%s%s' % ( gdata.auth.PROGRAMMATIC_AUTH_LABEL, 'foo')) token.set_token_string(token.get_token_string()) self.assertEquals(token.get_token_string(), 'foo') def testAuthSubToAndFromString(self): token = gdata.auth.AuthSubToken() token.set_token_string('foo') self.assertEquals(token.get_token_string(), 'foo') self.assertEquals(token.auth_header, '%s%s' % ( gdata.auth.AUTHSUB_AUTH_LABEL, 'foo')) token.set_token_string(token.get_token_string()) self.assertEquals(token.get_token_string(), 'foo') def testSecureAuthSubToAndFromString(self): # Case 1: no token. token = gdata.auth.SecureAuthSubToken(RSA_KEY) token.set_token_string('foo') self.assertEquals(token.get_token_string(), 'foo') token.set_token_string(token.get_token_string()) self.assertEquals(token.get_token_string(), 'foo') self.assertEquals(str(token), 'foo') # Case 2: token is a string token = gdata.auth.SecureAuthSubToken(RSA_KEY, token_string='foo') self.assertEquals(token.get_token_string(), 'foo') token.set_token_string(token.get_token_string()) self.assertEquals(token.get_token_string(), 'foo') self.assertEquals(str(token), 'foo') def testOAuthToAndFromString(self): token_key = 'ABCD' token_secret = 'XYZ' # Case 1: token key and secret both present single time. token_string = 'oauth_token=%s&oauth_token_secret=%s' % (token_key, token_secret) token = gdata.auth.OAuthToken() token.set_token_string(token_string) self.assert_(-1 < token.get_token_string().find(token_string.split('&')[0])) self.assert_(-1 < token.get_token_string().find(token_string.split('&')[1])) self.assertEquals(token_key, token.key) self.assertEquals(token_secret, token.secret) # Case 2: token key and secret both present multiple times with unwanted # parameters. token_string = ('oauth_token=%s&oauth_token_secret=%s&' 'oauth_token=%s&ExtraParams=GarbageString' % (token_key, token_secret, 'LMNO')) token = gdata.auth.OAuthToken() token.set_token_string(token_string) self.assert_(-1 < token.get_token_string().find(token_string.split('&')[0])) self.assert_(-1 < token.get_token_string().find(token_string.split('&')[1])) self.assertEquals(token_key, token.key) self.assertEquals(token_secret, token.secret) # Case 3: Only token key present. token_string = 'oauth_token=%s' % (token_key,) token = gdata.auth.OAuthToken() token.set_token_string(token_string) self.assertEquals(token_string, token.get_token_string()) self.assertEquals(token_key, token.key) self.assert_(not token.secret) # Case 4: Only token key present. token_string = 'oauth_token_secret=%s' % (token_secret,) token = gdata.auth.OAuthToken() token.set_token_string(token_string) self.assertEquals(token_string, token.get_token_string()) self.assertEquals(token_secret, token.secret) self.assert_(not token.key) # Case 5: None present. token_string = '' token = gdata.auth.OAuthToken() token.set_token_string(token_string) self.assert_(token.get_token_string() is None) self.assert_(not token.key) self.assert_(not token.secret) def testSecureAuthSubGetAuthHeader(self): # Case 1: Presence of OAuth token (in case of 3-legged OAuth) url = 'http://dummy.com/?q=notebook&s=true' token = gdata.auth.SecureAuthSubToken(RSA_KEY, token_string='foo') auth_header = token.GetAuthHeader('GET', url) self.assert_('Authorization' in auth_header) header_value = auth_header['Authorization'] self.assert_(header_value.startswith(r'AuthSub token="foo"')) self.assert_(-1 < header_value.find(r'sigalg="rsa-sha1"')) self.assert_(-1 < header_value.find(r'data="')) self.assert_(-1 < header_value.find(r'sig="')) m = re.search(r'data="(.*?)"', header_value) self.assert_(m is not None) data = m.group(1) self.assert_(data.startswith('GET')) self.assert_(-1 < data.find(url)) def testOAuthGetAuthHeader(self): # Case 1: Presence of OAuth token (in case of 3-legged OAuth) oauth_input_params = gdata.auth.OAuthInputParams( gdata.auth.OAuthSignatureMethod.RSA_SHA1, CONSUMER_KEY, rsa_key=RSA_KEY) token = gdata.auth.OAuthToken(key='ABCDDSFFDSG', oauth_input_params=oauth_input_params) auth_header = token.GetAuthHeader('GET', 'http://dummy.com/?q=notebook&s=true', realm='http://dummy.com') self.assert_('Authorization' in auth_header) header_value = auth_header['Authorization'] self.assert_(-1 < header_value.find(r'OAuth realm="http://dummy.com"')) self.assert_(-1 < header_value.find(r'oauth_version="1.0"')) self.assert_(-1 < header_value.find(r'oauth_token="ABCDDSFFDSG"')) self.assert_(-1 < header_value.find(r'oauth_nonce="')) self.assert_(-1 < header_value.find(r'oauth_timestamp="')) self.assert_(-1 < header_value.find(r'oauth_signature="')) self.assert_(-1 < header_value.find( r'oauth_consumer_key="%s"' % CONSUMER_KEY)) self.assert_(-1 < header_value.find(r'oauth_signature_method="RSA-SHA1"')) # Case 2: Absence of OAuth token (in case of 2-legged OAuth) oauth_input_params = gdata.auth.OAuthInputParams( gdata.auth.OAuthSignatureMethod.HMAC_SHA1, CONSUMER_KEY, consumer_secret=CONSUMER_SECRET) token = gdata.auth.OAuthToken(oauth_input_params=oauth_input_params) auth_header = token.GetAuthHeader( 'GET', 'http://dummy.com/?xoauth_requestor_id=user@gmail.com&q=book') self.assert_('Authorization' in auth_header) header_value = auth_header['Authorization'] self.assert_(-1 < header_value.find(r'OAuth realm=""')) self.assert_(-1 < header_value.find(r'oauth_version="1.0"')) self.assertEquals(-1, header_value.find(r'oauth_token=')) self.assert_(-1 < header_value.find(r'oauth_nonce="')) self.assert_(-1 < header_value.find(r'oauth_timestamp="')) self.assert_(-1 < header_value.find(r'oauth_signature="')) self.assert_(-1 < header_value.find( r'oauth_consumer_key="%s"' % CONSUMER_KEY)) self.assert_(-1 < header_value.find(r'oauth_signature_method="HMAC-SHA1"')) if __name__ == '__main__': unittest.main() gdata-2.0.18/tests/gdata_tests/photos/0000750036466300116100000000000012156625015017712 5ustar afshareng00000000000000gdata-2.0.18/tests/gdata_tests/photos/service_test.py0000750036466300116100000000556012156622363022777 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright (C) 2007 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. __author__ = 'api.jscudder (Jeffrey Scudder)' import getpass import time import unittest import StringIO import gdata.photos.service import gdata.photos import atom username = '' password = '' test_image_location = '../../testimage.jpg' test_image_name = 'testimage.jpg' class PhotosServiceTest(unittest.TestCase): def setUp(self): # Initialize the client and create a new album for testing. self.client = gdata.photos.service.PhotosService() self.client.email = username self.client.password = password self.client.source = 'Photos Client Unit Tests' self.client.ProgrammaticLogin() # Give the album a unique title by appending the current time. self.test_album = self.client.InsertAlbum( 'Python library test' + str(time.time()), 'A temporary test album.') def testUploadGetAndDeletePhoto(self): image_entry = self.client.InsertPhotoSimple(self.test_album, 'test', 'a pretty testing picture', test_image_location) self.assert_(image_entry.title.text == 'test') results_feed = self.client.SearchUserPhotos('test') self.assert_(len(results_feed.entry) > 0) self.client.Delete(image_entry) def testInsertPhotoUpdateBlobAndDelete(self): new_entry = gdata.photos.PhotoEntry() new_entry.title = atom.Title(text='a_test_image') new_entry.summary = atom.Summary(text='Just a test.') new_entry.category.append(atom.Category( scheme='http://schemas.google.com/g/2005#kind', term='http://schemas.google.com/photos/2007#photo')) entry = self.client.InsertPhoto(self.test_album, new_entry, test_image_location, content_type='image/jpeg') self.assert_(entry.id.text) updated_entry = self.client.UpdatePhotoBlob(entry, test_image_location) self.assert_(entry.GetEditLink().href != updated_entry.GetEditLink().href) self.client.Delete(updated_entry) def tearDown(self): # Delete the test album. test_album = self.client.GetEntry(self.test_album.GetSelfLink().href) self.client.Delete(test_album) if __name__ == '__main__': print ('Google Photos test\nNOTE: Please run these tests only with a test ' 'account. The tests may delete or update your data.') username = raw_input('Please enter your username: ') password = getpass.getpass() unittest.main() gdata-2.0.18/tests/gdata_tests/photos/__init__.py0000640036466300116100000000000012156622363022015 0ustar afshareng00000000000000gdata-2.0.18/tests/gdata_tests/health/0000750036466300116100000000000012156625015017643 5ustar afshareng00000000000000gdata-2.0.18/tests/gdata_tests/health/service_test.py0000750036466300116100000001765412156622363022737 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright 2009 Google Inc. All Rights Reserved. # # 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. __author__ = 'api.eric@google.com (Eric Bidelman)' import getpass import unittest from gdata import test_data import gdata.health import gdata.health.service username = '' password = '' class HealthQueryProfileListTest(unittest.TestCase): def setUp(self): self.health = gdata.health.service.HealthService() self.health.ClientLogin(username, password, source='Health Client Unit Tests') self.profile_list_feed = self.health.GetProfileListFeed() def testGetProfileListFeed(self): self.assert_(isinstance(self.profile_list_feed, gdata.health.ProfileListFeed)) self.assertEqual(self.profile_list_feed.id.text, 'https://www.google.com/health/feeds/profile/list') first_entry = self.profile_list_feed.entry[0] self.assert_(isinstance(first_entry, gdata.health.ProfileListEntry)) self.assert_(first_entry.GetProfileId() is not None) self.assert_(first_entry.GetProfileName() is not None) query = gdata.health.service.HealthProfileListQuery() profile_list = self.health.GetProfileListFeed(query) self.assertEqual(first_entry.GetProfileId(), profile_list.entry[0].GetProfileId()) self.assertEqual(profile_list.id.text, 'https://www.google.com/health/feeds/profile/list') class H9QueryProfileListTest(unittest.TestCase): def setUp(self): self.h9 = gdata.health.service.HealthService(use_h9_sandbox=True) self.h9.ClientLogin(username, password, source='H9 Client Unit Tests') self.profile_list_feed = self.h9.GetProfileListFeed() def testGetProfileListFeed(self): self.assert_(isinstance(self.profile_list_feed, gdata.health.ProfileListFeed)) self.assertEqual(self.profile_list_feed.id.text, 'https://www.google.com/h9/feeds/profile/list') first_entry = self.profile_list_feed.entry[0] self.assert_(isinstance(first_entry, gdata.health.ProfileListEntry)) self.assert_(first_entry.GetProfileId() is not None) self.assert_(first_entry.GetProfileName() is not None) query = gdata.health.service.HealthProfileListQuery() profile_list = self.h9.GetProfileListFeed(query) self.assertEqual(first_entry.GetProfileId(), profile_list.entry[0].GetProfileId()) self.assertEqual(profile_list.id.text, 'https://www.google.com/h9/feeds/profile/list') class HealthQueryProfileTest(unittest.TestCase): def setUp(self): self.health = gdata.health.service.HealthService() self.health.ClientLogin(username, password, source='Health Client Unit Tests') self.profile_list_feed = self.health.GetProfileListFeed() self.profile_id = self.profile_list_feed.entry[0].GetProfileId() def testGetProfileFeed(self): feed = self.health.GetProfileFeed(profile_id=self.profile_id) self.assert_(isinstance(feed, gdata.health.ProfileFeed)) self.assert_(isinstance(feed.entry[0].ccr, gdata.health.Ccr)) def testGetProfileFeedByQuery(self): query = gdata.health.service.HealthProfileQuery( projection='ui', profile_id=self.profile_id) feed = self.health.GetProfileFeed(query=query) self.assert_(isinstance(feed, gdata.health.ProfileFeed)) self.assert_(feed.entry[0].ccr is not None) def testGetProfileDigestFeed(self): query = gdata.health.service.HealthProfileQuery( projection='ui', profile_id=self.profile_id, params={'digest': 'true'}) feed = self.health.GetProfileFeed(query=query) self.assertEqual(len(feed.entry), 1) def testGetMedicationsAndConditions(self): query = gdata.health.service.HealthProfileQuery( projection='ui', profile_id=self.profile_id, params={'digest': 'true'}, categories=['medication|condition']) feed = self.health.GetProfileFeed(query=query) self.assertEqual(len(feed.entry), 1) if feed.entry[0].ccr.GetMedications() is not None: self.assert_(feed.entry[0].ccr.GetMedications()[0] is not None) self.assert_(feed.entry[0].ccr.GetConditions()[0] is not None) self.assert_(feed.entry[0].ccr.GetAllergies() is None) self.assert_(feed.entry[0].ccr.GetAlerts() is None) self.assert_(feed.entry[0].ccr.GetResults() is None) class H9QueryProfileTest(unittest.TestCase): def setUp(self): self.h9 = gdata.health.service.HealthService(use_h9_sandbox=True) self.h9.ClientLogin(username, password, source='H9 Client Unit Tests') self.profile_list_feed = self.h9.GetProfileListFeed() self.profile_id = self.profile_list_feed.entry[0].GetProfileId() def testGetProfileFeed(self): feed = self.h9.GetProfileFeed(profile_id=self.profile_id) self.assert_(isinstance(feed, gdata.health.ProfileFeed)) self.assert_(feed.entry[0].ccr is not None) def testGetProfileFeedByQuery(self): query = gdata.health.service.HealthProfileQuery( service='h9', projection='ui', profile_id=self.profile_id) feed = self.h9.GetProfileFeed(query=query) self.assert_(isinstance(feed, gdata.health.ProfileFeed)) self.assert_(feed.entry[0].ccr is not None) class HealthNoticeTest(unittest.TestCase): def setUp(self): self.health = gdata.health.service.HealthService() self.health.ClientLogin(username, password, source='Health Client Unit Tests') self.profile_list_feed = self.health.GetProfileListFeed() self.profile_id = self.profile_list_feed.entry[0].GetProfileId() def testSendNotice(self): subject_line = 'subject line' body = 'Notice body.' ccr_xml = test_data.HEALTH_CCR_NOTICE_PAYLOAD created_entry = self.health.SendNotice(subject_line, body, ccr=ccr_xml, profile_id=self.profile_id) self.assertEqual(created_entry.title.text, subject_line) self.assertEqual(created_entry.content.text, body) self.assertEqual(created_entry.content.type, 'html') problem = created_entry.ccr.GetProblems()[0] problem_desc = problem.FindChildren('Description')[0] name = problem_desc.FindChildren('Text')[0] self.assertEqual(name.text, 'Aortic valve disorders') class H9NoticeTest(unittest.TestCase): def setUp(self): self.h9 = gdata.health.service.HealthService(use_h9_sandbox=True) self.h9.ClientLogin(username, password, source='H9 Client Unit Tests') self.profile_list_feed = self.h9.GetProfileListFeed() self.profile_id = self.profile_list_feed.entry[0].GetProfileId() def testSendNotice(self): subject_line = 'subject line' body = 'Notice body.' ccr_xml = test_data.HEALTH_CCR_NOTICE_PAYLOAD created_entry = self.h9.SendNotice(subject_line, body, ccr=ccr_xml, profile_id=self.profile_id) self.assertEqual(created_entry.title.text, subject_line) self.assertEqual(created_entry.content.text, body) self.assertEqual(created_entry.content.type, 'html') problem = created_entry.ccr.GetProblems()[0] problem_desc = problem.FindChildren('Description')[0] name = problem_desc.FindChildren('Text')[0] self.assertEqual(name.text, 'Aortic valve disorders') if __name__ == '__main__': print ('Health API Tests\nNOTE: Please run these tests only with a test ' 'account. The tests may delete or update your data.') username = raw_input('Please enter your username: ') password = getpass.getpass() unittest.main() gdata-2.0.18/tests/gdata_tests/health/__init__.py0000750036466300116100000000000012156622363021750 0ustar afshareng00000000000000gdata-2.0.18/tests/gdata_tests/docs/0000750036466300116100000000000012156625015017326 5ustar afshareng00000000000000gdata-2.0.18/tests/gdata_tests/docs/service_test.py0000750036466300116100000004176712156622363022424 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright 2009 Google Inc. All Rights Reserved. # # 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. __author__ = ('api.jfisher (Jeff Fisher), ' 'api.eric@google.com (Eric Bidelman)') import getpass import os import re import StringIO import time import unittest import gdata.docs.service import gdata.spreadsheet.service username = '' password = '' client = gdata.docs.service.DocsService() editClient = gdata.docs.service.DocsService() spreadsheets = gdata.spreadsheet.service.SpreadsheetsService() class DocumentsListServiceTest(unittest.TestCase): def setUp(self): self.client = client self.editClient = editClient self.editClient.SetClientLoginToken(client.GetClientLoginToken()) self.editClient.additional_headers = {'If-Match': '*'} self.spreadsheets = spreadsheets self.DOCUMENT_CATEGORY = client._MakeKindCategory(gdata.docs.service.DOCUMENT_LABEL) self.SPREADSHEET_CATEGORY = client._MakeKindCategory(gdata.docs.service.SPREADSHEET_LABEL) self.PRESENTATION_CATEGORY = client._MakeKindCategory(gdata.docs.service.PRESENTATION_LABEL) class DocumentListQueryTest(DocumentsListServiceTest): def setUp(self): DocumentsListServiceTest.setUp(self) self.feed = self.client.GetDocumentListFeed() def testGetDocumentsListFeed(self): self.assert_(isinstance(self.feed, gdata.docs.DocumentListFeed)) uri = 'http://docs.google.com/feeds/documents/private/full/?max-results=1' # Query using GetDocumentListFeed() feed = self.client.GetDocumentListFeed(uri) self.assert_(isinstance(feed, gdata.docs.DocumentListFeed)) self.assertEqual(len(feed.entry), 1) self.assertEqual(self.feed.entry[0].id.text, feed.entry[0].id.text) self.assertEqual(self.feed.entry[0].title.text, feed.entry[0].title.text) # Query using QueryDocumentListFeed() feed2 = self.client.QueryDocumentListFeed(uri) self.assertEqual(len(feed2.entry), 1) self.assertEqual(self.feed.entry[0].id.text, feed2.entry[0].id.text) self.assertEqual(self.feed.entry[0].title.text, feed2.entry[0].title.text) def testGetDocumentsListEntry(self): self_link = self.feed.entry[0].GetSelfLink().href entry = self.client.GetDocumentListEntry(self_link) self.assert_(isinstance(entry, gdata.docs.DocumentListEntry)) self.assertEqual(self.feed.entry[0].id.text, entry.id.text) self.assertEqual(self.feed.entry[0].title.text, entry.title.text) self.assert_(self.feed.entry[0].resourceId.text is not None) self.assert_(self.feed.entry[0].lastModifiedBy is not None) self.assert_(self.feed.entry[0].lastViewed is not None) def testGetDocumentsListAclFeed(self): uri = ('http://docs.google.com/feeds/documents/private/full/' '-/mine?max-results=1') feed = self.client.GetDocumentListFeed(uri) feed_link = feed.entry[0].GetAclLink().href acl_feed = self.client.GetDocumentListAclFeed(feed_link) self.assert_(isinstance(acl_feed, gdata.docs.DocumentListAclFeed)) self.assert_(isinstance(acl_feed.entry[0], gdata.docs.DocumentListAclEntry)) self.assert_(acl_feed.entry[0].scope is not None) self.assert_(acl_feed.entry[0].role is not None) class DocumentListAclTest(DocumentsListServiceTest): def setUp(self): DocumentsListServiceTest.setUp(self) uri = ('http://docs.google.com/feeds/documents/private/full' '/-/mine?max-results=1') self.feed = self.client.GetDocumentListFeed(uri) self.EMAIL = 'x@example.com' self.SCOPE_TYPE = 'user' self.ROLE_VALUE = 'reader' def testCreateAndUpdateAndDeleteAcl(self): # Add new ACL scope = gdata.docs.Scope(value=self.EMAIL, type=self.SCOPE_TYPE) role = gdata.docs.Role(value=self.ROLE_VALUE) acl_entry = self.client.Post( gdata.docs.DocumentListAclEntry(scope=scope, role=role), self.feed.entry[0].GetAclLink().href, converter=gdata.docs.DocumentListAclEntryFromString) self.assert_(isinstance(acl_entry, gdata.docs.DocumentListAclEntry)) self.assertEqual(acl_entry.scope.value, self.EMAIL) self.assertEqual(acl_entry.scope.type, self.SCOPE_TYPE) self.assertEqual(acl_entry.role.value, self.ROLE_VALUE) # Update the user's role ROLE_VALUE = 'writer' acl_entry.role.value = ROLE_VALUE updated_acl_entry = self.editClient.Put( acl_entry, acl_entry.GetEditLink().href, converter=gdata.docs.DocumentListAclEntryFromString) self.assertEqual(updated_acl_entry.scope.value, self.EMAIL) self.assertEqual(updated_acl_entry.scope.type, self.SCOPE_TYPE) self.assertEqual(updated_acl_entry.role.value, ROLE_VALUE) # Delete the ACL self.editClient.Delete(updated_acl_entry.GetEditLink().href) # Make sure entry was actually deleted acl_feed = self.client.GetDocumentListAclFeed( self.feed.entry[0].GetAclLink().href) for acl_entry in acl_feed.entry: self.assert_(acl_entry.scope.value != self.EMAIL) class DocumentListCreateAndDeleteTest(DocumentsListServiceTest): def setUp(self): DocumentsListServiceTest.setUp(self) self.BLANK_TITLE = "blank.txt" self.TITLE = 'Test title' self.new_entry = gdata.docs.DocumentListEntry() self.new_entry.category.append(self.DOCUMENT_CATEGORY) def testCreateAndDeleteEmptyDocumentSlugHeaderTitle(self): created_entry = self.client.Post(self.new_entry, '/feeds/documents/private/full', extra_headers={'Slug': self.BLANK_TITLE}) self.editClient.Delete(created_entry.GetEditLink().href) self.assertEqual(created_entry.title.text, self.BLANK_TITLE) self.assertEqual(created_entry.category[0].label, 'document') def testCreateAndDeleteEmptyDocumentAtomTitle(self): self.new_entry.title = gdata.atom.Title(text=self.TITLE) created_entry = self.client.Post(self.new_entry, '/feeds/documents/private/full') self.editClient.Delete(created_entry.GetEditLink().href) self.assertEqual(created_entry.title.text, self.TITLE) self.assertEqual(created_entry.category[0].label, 'document') def testCreateAndDeleteEmptySpreadsheet(self): self.new_entry.title = gdata.atom.Title(text=self.TITLE) self.new_entry.category[0] = self.SPREADSHEET_CATEGORY created_entry = self.client.Post(self.new_entry, '/feeds/documents/private/full') self.editClient.Delete(created_entry.GetEditLink().href) self.assertEqual(created_entry.title.text, self.TITLE) self.assertEqual(created_entry.category[0].label, 'viewed') self.assertEqual(created_entry.category[1].label, 'spreadsheet') def testCreateAndDeleteEmptyPresentation(self): self.new_entry.title = gdata.atom.Title(text=self.TITLE) self.new_entry.category[0] = self.PRESENTATION_CATEGORY created_entry = self.client.Post(self.new_entry, '/feeds/documents/private/full') self.editClient.Delete(created_entry.GetEditLink().href) self.assertEqual(created_entry.title.text, self.TITLE) self.assertEqual(created_entry.category[0].label, 'viewed') self.assertEqual(created_entry.category[1].label, 'presentation') def testCreateAndDeleteFolder(self): folder_name = 'TestFolder' folder = self.client.CreateFolder(folder_name) self.assertEqual(folder.title.text, folder_name) self.editClient.Delete(folder.GetEditLink().href) def testCreateAndDeleteFolderInFolder(self): DEST_FOLDER_NAME = 'TestFolder' dest_folder = self.client.CreateFolder(DEST_FOLDER_NAME) CREATED_FOLDER_NAME = 'TestFolder2' new_folder = self.client.CreateFolder(CREATED_FOLDER_NAME, dest_folder) for category in new_folder.category: if category.scheme.startswith(gdata.docs.service.FOLDERS_SCHEME_PREFIX): self.assertEqual(new_folder.category[0].label, DEST_FOLDER_NAME) break # delete the folders we created, this will also delete the child folder dest_folder = self.client.Get(dest_folder.GetSelfLink().href) self.editClient.Delete(dest_folder.GetEditLink().href) class DocumentListMoveInAndOutOfFolderTest(DocumentsListServiceTest): def setUp(self): DocumentsListServiceTest.setUp(self) self.folder_name = 'TestFolder' self.folder = self.client.CreateFolder(self.folder_name) self.doc_title = 'TestDoc' self.ms = gdata.MediaSource(file_path='test.doc', content_type='application/msword') def tearDown(self): folder = self.client.Get(self.folder.GetSelfLink().href) self.editClient.Delete(folder.GetEditLink().href) def testUploadDocumentToFolder(self): created_entry = self.client.Upload(self.ms, self.doc_title, self.folder) for category in created_entry.category: if category.scheme.startswith(gdata.docs.service.FOLDERS_SCHEME_PREFIX): self.assertEqual(category.label, self.folder_name) break # delete the doc we created created_entry = self.client.Get(created_entry.GetSelfLink().href) match = re.search('\/(document%3A[^\/]*)\/?.*?\/(.*)$', created_entry.GetEditLink().href) edit_uri = 'http://docs.google.com/feeds/documents/private/full/' edit_uri += '%s/%s' % (match.group(1), match.group(2)) self.editClient.Delete(edit_uri) def testMoveDocumentInAndOutOfFolder(self): created_entry = self.client.Upload(self.ms, self.doc_title) moved_entry = self.client.MoveIntoFolder(created_entry, self.folder) for category in moved_entry.category: if category.scheme.startswith(gdata.docs.service.FOLDERS_SCHEME_PREFIX): self.assertEqual(category.label, self.folder_name) break self.editClient.MoveOutOfFolder(moved_entry) moved_entry = self.client.Get(moved_entry.GetSelfLink().href) for category in moved_entry.category: starts_with_folder__prefix = category.scheme.startswith( gdata.docs.service.FOLDERS_SCHEME_PREFIX) self.assert_(not starts_with_folder__prefix) created_entry = self.client.Get(created_entry.GetSelfLink().href) self.editClient.Delete(created_entry.GetEditLink().href) def testMoveFolderIntoFolder(self): dest_folder_name = 'DestFolderName' dest_folder = self.client.CreateFolder(dest_folder_name) self.client.MoveIntoFolder(self.folder, dest_folder) self.folder = self.client.Get(self.folder.GetSelfLink().href) folder_was_moved = False for category in self.folder.category: if category.term == dest_folder_name: folder_was_moved = True break self.assert_(folder_was_moved) #cleanup dest_folder = self.client.Get(dest_folder.GetSelfLink().href) self.editClient.Delete(dest_folder.GetEditLink().href) class DocumentListUploadTest(DocumentsListServiceTest): def testUploadAndDeleteDocument(self): ms = gdata.MediaSource(file_path='test.doc', content_type='application/msword') entry = self.client.Upload(ms, 'test doc') self.assertEqual(entry.title.text, 'test doc') self.assertEqual(entry.category[0].label, 'document') self.assert_(isinstance(entry, gdata.docs.DocumentListEntry)) self.editClient.Delete(entry.GetEditLink().href) def testUploadAndDeletePresentation(self): ms = gdata.MediaSource(file_path='test.ppt', content_type='application/vnd.ms-powerpoint') entry = self.client.Upload(ms, 'test preso') self.assertEqual(entry.title.text, 'test preso') self.assertEqual(entry.category[0].label, 'viewed') self.assertEqual(entry.category[1].label, 'presentation') self.assert_(isinstance(entry, gdata.docs.DocumentListEntry)) self.editClient.Delete(entry.GetEditLink().href) def testUploadAndDeleteSpreadsheet(self): ms = gdata.MediaSource(file_path='test.csv', content_type='text/csv') entry = self.client.Upload(ms, 'test spreadsheet') self.assert_(entry.title.text == 'test spreadsheet') self.assertEqual(entry.category[0].label, 'viewed') self.assertEqual(entry.category[1].label, 'spreadsheet') self.assert_(isinstance(entry, gdata.docs.DocumentListEntry)) self.editClient.Delete(entry.GetEditLink().href) class DocumentListUpdateTest(DocumentsListServiceTest): def setUp(self): DocumentsListServiceTest.setUp(self) self.TITLE = 'CreatedTestDoc' new_entry = gdata.docs.DocumentListEntry() new_entry.title = gdata.atom.Title(text=self.TITLE) new_entry.category.append(self.DOCUMENT_CATEGORY) self.created_entry = self.client.Post(new_entry, '/feeds/documents/private/full') def tearDown(self): # Delete the test doc we created self_link = self.created_entry.GetSelfLink().href entry = self.client.GetDocumentListEntry(self_link) self.editClient.Delete(entry.GetEditLink().href) def testUpdateDocumentMetadataAndContent(self): title = 'UpdatedTestDoc' # Update metadata self.created_entry.title.text = title updated_entry = self.editClient.Put(self.created_entry, self.created_entry.GetEditLink().href) self.assertEqual(updated_entry.title.text, title) # Update document's content ms = gdata.MediaSource(file_path='test.doc', content_type='application/msword') uri = updated_entry.GetEditMediaLink().href updated_entry = self.editClient.Put(ms, uri) self.assertEqual(updated_entry.title.text, title) # Append content to document data = 'data to append' ms = gdata.MediaSource(file_handle=StringIO.StringIO(data), content_type='text/plain', content_length=len(data)) uri = updated_entry.GetEditMediaLink().href + '?append=true' updated_entry = self.editClient.Put(ms, uri) class DocumentListExportTest(DocumentsListServiceTest): def testExportDocument(self): query = ('https://docs.google.com/feeds/documents/private/full' '/-/document?max-results=1') feed = self.client.QueryDocumentListFeed(query) file_paths = ['./downloadedTest.doc', './downloadedTest.html', './downloadedTest.odt', './downloadedTest.pdf', './downloadedTest.png', './downloadedTest.rtf', './downloadedTest.txt', './downloadedTest.zip'] for path in file_paths: self.client.Export(feed.entry[0], path) self.assert_(os.path.exists(path)) self.assert_(os.path.getsize(path)) os.remove(path) def testExportPresentation(self): query = ('https://docs.google.com/feeds/documents/private/full' '/-/presentation?max-results=1') feed = self.client.QueryDocumentListFeed(query) file_paths = ['./downloadedTest.pdf', './downloadedTest.ppt', './downloadedTest.swf', './downloadedTest.txt'] for path in file_paths: self.client.Export(feed.entry[0].resourceId.text, path) self.assert_(os.path.exists(path)) self.assert_(os.path.getsize(path)) os.remove(path) def testExportSpreadsheet(self): query = ('https://docs.google.com/feeds/documents/private/full' '/-/spreadsheet?max-results=1') feed = self.client.QueryDocumentListFeed(query) file_paths = ['./downloadedTest.xls', './downloadedTest.csv', './downloadedTest.pdf', './downloadedTest.ods', './downloadedTest.tsv', './downloadedTest.html'] docs_token = self.client.GetClientLoginToken() self.client.SetClientLoginToken(self.spreadsheets.GetClientLoginToken()) for path in file_paths: self.client.Export(feed.entry[0], path) self.assert_(os.path.exists(path)) self.assert_(os.path.getsize(path) > 0) os.remove(path) self.client.SetClientLoginToken(docs_token) def testExportNonExistentDocument(self): path = './ned.txt' exception_raised = False try: self.client.Export('non_existent_doc', path) except Exception, e: # expected exception_raised = True self.assert_(exception_raised) self.assert_(not os.path.exists(path)) if __name__ == '__main__': print ('DocList API Tests\nNOTE: Please run these tests only with a test ' 'account. The tests may delete or update your data.') username = raw_input('Please enter your username: ') password = getpass.getpass() if client.GetClientLoginToken() is None: client.ClientLogin(username, password, source='Document List Client Unit Tests') if spreadsheets.GetClientLoginToken() is None: spreadsheets.ClientLogin(username, password, source='Document List Client Unit Tests') unittest.main() gdata-2.0.18/tests/gdata_tests/docs/data_test.py0000750036466300116100000003576512156622363021676 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright 2009 Google Inc. All Rights Reserved. # # 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. __author__ = 'e.bidelman (Eric Bidelman)' import unittest import atom from gdata import test_data import gdata.acl.data import gdata.data import gdata.docs.data import gdata.test_config as conf class DocsEntryTest(unittest.TestCase): def setUp(self): self.entry = atom.core.parse(test_data.DOCUMENT_LIST_ENTRY_V3, gdata.docs.data.Resource) def testToAndFromStringDocsEntry(self): self.assert_(isinstance(self.entry, gdata.docs.data.Resource)) self.assertEqual(self.entry.GetResourceType(), 'spreadsheet') self.assert_(isinstance(self.entry.last_viewed, gdata.docs.data.LastViewed)) self.assertEqual(self.entry.last_viewed.text, '2009-03-05T07:48:21.493Z') self.assert_( isinstance(self.entry.last_modified_by, gdata.docs.data.LastModifiedBy)) self.assertEqual( self.entry.last_modified_by.email.text, 'test.user@gmail.com') self.assertEqual(self.entry.last_modified_by.name.text, 'test.user') self.assert_(isinstance(self.entry.resource_id, gdata.docs.data.ResourceId)) self.assertEqual(self.entry.resource_id.text, 'spreadsheet:supercalifragilisticexpealidocious') self.assert_(isinstance(self.entry.writers_can_invite, gdata.docs.data.WritersCanInvite)) self.assertEqual(self.entry.writers_can_invite.value, 'true') self.assert_(isinstance(self.entry.quota_bytes_used, gdata.docs.data.QuotaBytesUsed)) self.assertEqual(self.entry.quota_bytes_used.text, '1000') self.assertEqual(len(self.entry.feed_link), 2) self.assert_(isinstance(self.entry.feed_link[0], gdata.data.FeedLink)) self.assertEqual( self.entry.GetAclFeedLink().href, ('https://docs.google.com/feeds/default/private/full/' 'spreadsheet%3Asupercalifragilisticexpealidocious/acl')) self.assertEqual( self.entry.GetRevisionsFeedLink().href, ('https://docs.google.com/feeds/default/private/full/' 'spreadsheet%3Asupercalifragilisticexpealidocious/revisions')) self.assertEqual(len(self.entry.InCollections()), 1) self.assertEqual(self.entry.InCollections()[0].title, 'AFolderName') class AclTest(unittest.TestCase): def setUp(self): self.acl_entry = atom.core.parse(test_data.DOCUMENT_LIST_ACL_ENTRY, gdata.docs.data.AclEntry) self.acl_entry_withkey = atom.core.parse( test_data.DOCUMENT_LIST_ACL_WITHKEY_ENTRY, gdata.docs.data.AclEntry) self.acl_entry_additional_role = atom.core.parse( test_data.DOCUMENT_LIST_ACL_ADDITIONAL_ROLE_ENTRY, gdata.docs.data.AclEntry) def testToAndFromString(self): self.assert_(isinstance(self.acl_entry, gdata.docs.data.AclEntry)) self.assert_(isinstance(self.acl_entry.role, gdata.acl.data.AclRole)) self.assert_(isinstance(self.acl_entry.scope, gdata.acl.data.AclScope)) self.assertEqual(self.acl_entry.scope.value, 'user@gmail.com') self.assertEqual(self.acl_entry.scope.type, 'user') self.assertEqual(self.acl_entry.role.value, 'writer') acl_entry_str = str(self.acl_entry) new_acl_entry = atom.core.parse(acl_entry_str, gdata.docs.data.AclEntry) self.assert_(isinstance(new_acl_entry, gdata.docs.data.AclEntry)) self.assert_(isinstance(new_acl_entry.role, gdata.acl.data.AclRole)) self.assert_(isinstance(new_acl_entry.scope, gdata.acl.data.AclScope)) self.assertEqual(new_acl_entry.scope.value, self.acl_entry.scope.value) self.assertEqual(new_acl_entry.scope.type, self.acl_entry.scope.type) self.assertEqual(new_acl_entry.role.value, self.acl_entry.role.value) def testToAndFromStringWithKey(self): self.assert_(isinstance(self.acl_entry_withkey, gdata.docs.data.AclEntry)) self.assert_(self.acl_entry_withkey.role is None) self.assert_(isinstance(self.acl_entry_withkey.with_key, gdata.acl.data.AclWithKey)) self.assert_(isinstance(self.acl_entry_withkey.with_key.role, gdata.acl.data.AclRole)) self.assert_(isinstance(self.acl_entry_withkey.scope, gdata.acl.data.AclScope)) self.assertEqual(self.acl_entry_withkey.with_key.key, 'somekey') self.assertEqual(self.acl_entry_withkey.with_key.role.value, 'writer') self.assertEqual(self.acl_entry_withkey.scope.value, 'example.com') self.assertEqual(self.acl_entry_withkey.scope.type, 'domain') acl_entry_withkey_str = str(self.acl_entry_withkey) new_acl_entry_withkey = atom.core.parse(acl_entry_withkey_str, gdata.docs.data.AclEntry) self.assert_(isinstance(new_acl_entry_withkey, gdata.docs.data.AclEntry)) self.assert_(new_acl_entry_withkey.role is None) self.assert_(isinstance(new_acl_entry_withkey.with_key, gdata.acl.data.AclWithKey)) self.assert_(isinstance(new_acl_entry_withkey.with_key.role, gdata.acl.data.AclRole)) self.assert_(isinstance(new_acl_entry_withkey.scope, gdata.acl.data.AclScope)) self.assertEqual(new_acl_entry_withkey.with_key.key, self.acl_entry_withkey.with_key.key) self.assertEqual(new_acl_entry_withkey.with_key.role.value, self.acl_entry_withkey.with_key.role.value) self.assertEqual(new_acl_entry_withkey.scope.value, self.acl_entry_withkey.scope.value) self.assertEqual(new_acl_entry_withkey.scope.type, self.acl_entry_withkey.scope.type) def testCreateNewAclEntry(self): cat = gdata.atom.Category( term='http://schemas.google.com/acl/2007#accessRule', scheme='http://schemas.google.com/g/2005#kind') acl_entry = gdata.docs.DocumentListAclEntry(category=[cat]) acl_entry.scope = gdata.docs.Scope(value='user@gmail.com', type='user') acl_entry.role = gdata.docs.Role(value='writer') self.assert_(isinstance(acl_entry, gdata.docs.DocumentListAclEntry)) self.assert_(isinstance(acl_entry.role, gdata.docs.Role)) self.assert_(isinstance(acl_entry.scope, gdata.docs.Scope)) self.assertEqual(acl_entry.scope.value, 'user@gmail.com') self.assertEqual(acl_entry.scope.type, 'user') self.assertEqual(acl_entry.role.value, 'writer') def testAdditionalRole(self): self.assertEqual( self.acl_entry_additional_role.additional_role.value, 'commenter') self.assertEqual( self.acl_entry_additional_role.with_key.additional_role.value, 'commenter') class AclFeedTest(unittest.TestCase): def setUp(self): self.feed = atom.core.parse(test_data.DOCUMENT_LIST_ACL_FEED, gdata.docs.data.AclFeed) def testToAndFromString(self): for entry in self.feed.entry: self.assert_(isinstance(entry, gdata.docs.data.AclEntry)) feed = atom.core.parse(str(self.feed), gdata.docs.data.AclFeed) for entry in feed.entry: self.assert_(isinstance(entry, gdata.docs.data.AclEntry)) def testConvertActualData(self): entries = self.feed.entry self.assert_(len(entries) == 2) self.assertEqual(entries[0].title.text, 'Document Permission - user@gmail.com') self.assertEqual(entries[0].role.value, 'owner') self.assertEqual(entries[0].scope.type, 'user') self.assertEqual(entries[0].scope.value, 'user@gmail.com') self.assert_(entries[0].GetSelfLink() is not None) self.assert_(entries[0].GetEditLink() is not None) self.assertEqual(entries[1].title.text, 'Document Permission - user2@google.com') self.assertEqual(entries[1].role.value, 'writer') self.assertEqual(entries[1].scope.type, 'domain') self.assertEqual(entries[1].scope.value, 'google.com') self.assert_(entries[1].GetSelfLink() is not None) self.assert_(entries[1].GetEditLink() is not None) class RevisionFeedTest(unittest.TestCase): def setUp(self): self.feed = atom.core.parse(test_data.DOCUMENT_LIST_REVISION_FEED, gdata.docs.data.RevisionFeed) def testToAndFromString(self): for entry in self.feed.entry: self.assert_(isinstance(entry, gdata.docs.data.Revision)) feed = atom.core.parse(str(self.feed), gdata.docs.data.RevisionFeed) for entry in feed.entry: self.assert_(isinstance(entry, gdata.docs.data.Revision)) def testConvertActualData(self): entries = self.feed.entry self.assert_(len(entries) == 1) self.assertEqual(entries[0].title.text, 'Revision 2') self.assertEqual(entries[0].publish.value, 'true') self.assertEqual(entries[0].publish_auto.value, 'true') self.assertEqual(entries[0].publish_outside_domain.value, 'false') self.assertEqual( entries[0].GetPublishLink().href, 'https://docs.google.com/View?docid=dfr4&pageview=1&hgd=1') self.assertEqual( entries[0].FindPublishLink(), 'https://docs.google.com/View?docid=dfr4&pageview=1&hgd=1') class DataClassSanityTest(unittest.TestCase): def test_basic_element_structure(self): conf.check_data_classes(self, [ gdata.docs.data.ResourceId, gdata.docs.data.LastModifiedBy, gdata.docs.data.LastViewed, gdata.docs.data.WritersCanInvite, gdata.docs.data.QuotaBytesUsed, gdata.docs.data.Publish, gdata.docs.data.PublishAuto, gdata.docs.data.PublishOutsideDomain, gdata.docs.data.Resource, gdata.docs.data.AclEntry, gdata.docs.data.AclFeed, gdata.docs.data.ResourceFeed, gdata.docs.data.Revision, gdata.docs.data.RevisionFeed]) class CategoryTest(unittest.TestCase): def setUp(self): self.entry = atom.core.parse(test_data.DOCUMENT_LIST_ENTRY_V3, gdata.docs.data.Resource) def testAddCategory(self): entry = gdata.docs.data.Resource() entry.AddCategory('test_scheme', 'test_term', 'test_label') self.assertEqual(entry.GetFirstCategory('test_scheme').scheme, 'test_scheme') self.assertEqual(entry.GetFirstCategory('test_scheme').term, 'test_term') self.assertEqual(entry.GetFirstCategory('test_scheme').label, 'test_label') def testGetFirstCategory(self): entry = gdata.docs.data.Resource() cat1 = entry.AddCategory('test_scheme', 'test_term1', 'test_label1') cat2 = entry.AddCategory('test_scheme', 'test_term2', 'test_label2') self.assertEqual(entry.GetFirstCategory('test_scheme'), cat1) def testGetCategories(self): cat1 = self.entry.AddCategory('test_scheme', 'test_term1', 'test_label1') cat2 = self.entry.AddCategory('test_scheme', 'test_term2', 'test_label2') cats = list(self.entry.GetCategories('test_scheme')) self.assertTrue(cat1 in cats) self.assertTrue(cat2 in cats) def testRemoveCategories(self): self.entry.RemoveCategories(gdata.docs.data.LABELS_SCHEME) self.assertEqual(self.entry.GetLabels(), set()) def testResourceType(self): entry = gdata.docs.data.Resource('spreadsheet') self.assertEqual(self.entry.GetResourceType(), 'spreadsheet') def testGetResourceType(self): self.assertEqual(self.entry.GetResourceType(), 'spreadsheet') def testSetResourceType(self): self.assertEqual(self.entry.GetResourceType(), 'spreadsheet') self.entry.SetResourceType('drawing') self.assertEqual(self.entry.GetResourceType(), 'drawing') def testGetLabels(self): self.assertEqual(self.entry.GetLabels(), set(['mine', 'private', 'restricted-download', 'shared-with-domain', 'viewed', 'starred', 'hidden', 'trashed'])) def testAddLabel(self): entry = gdata.docs.data.Resource() entry.AddLabel('banana') self.assertTrue('banana' in entry.GetLabels()) def testRemoveLabel(self): entry = gdata.docs.data.Resource() entry.AddLabel('banana') entry.AddLabel('orange') self.assertTrue('banana' in entry.GetLabels()) self.assertTrue('orange' in entry.GetLabels()) entry.RemoveLabel('orange') self.assertFalse('orange' in entry.GetLabels()) def testIsHidden(self): self.assertTrue(self.entry.IsHidden()) def testIsNotHidden(self): self.entry.remove_categories(gdata.docs.data.LABELS_SCHEME) self.assertFalse(self.entry.IsHidden()) def testIsViewed(self): self.assertTrue(self.entry.IsViewed()) def testIsNotViewed(self): self.entry.remove_categories(gdata.docs.data.LABELS_SCHEME) self.assertFalse(self.entry.IsViewed()) def testIsStarred(self): self.assertTrue(self.entry.IsStarred()) def testIsNotStarred(self): self.entry.remove_categories(gdata.docs.data.LABELS_SCHEME) self.assertFalse(self.entry.IsStarred()) def testIsTrashed(self): self.assertTrue(self.entry.IsTrashed()) def testIsNotTrashed(self): self.entry.remove_categories(gdata.docs.data.LABELS_SCHEME) self.assertFalse(self.entry.IsTrashed()) def testIsPrivate(self): self.assertTrue(self.entry.IsPrivate()) def testIsNotPrivate(self): self.entry.remove_categories(gdata.docs.data.LABELS_SCHEME) self.assertFalse(self.entry.IsPrivate()) def testIsMine(self): self.assertTrue(self.entry.IsMine()) def testIsNotMine(self): self.entry.remove_categories(gdata.docs.data.LABELS_SCHEME) self.assertFalse(self.entry.IsMine()) def testIsSharedWithDomain(self): self.assertTrue(self.entry.IsSharedWithDomain()) def testIsNotSharedWithDomain(self): self.entry.remove_categories(gdata.docs.data.LABELS_SCHEME) self.assertFalse(self.entry.IsSharedWithDomain()) def testIsRestrictedDownload(self): self.assertTrue(self.entry.IsRestrictedDownload()) def testIsNotRestrictedDownload(self): self.entry.remove_categories(gdata.docs.data.LABELS_SCHEME) self.assertFalse(self.entry.IsRestrictedDownload()) class MetadataTest(unittest.TestCase): def setUp(self): self.entry = atom.core.parse(test_data.DOCUMENT_LIST_METADATA, gdata.docs.data.Metadata) def testAdditionalRoleInfo(self): self.assertEqual(self.entry.additional_role_info[0].kind, 'document') def testAdditionalRoleSet(self): self.assertEqual( self.entry.additional_role_info[0].additional_role_set[0].primaryRole, 'reader') def testAdditionalRole(self): self.assertEqual( self.entry.additional_role_info[0].additional_role_set[0].\ additional_role[0].value, 'commenter') def suite(): return conf.build_suite( [DataClassSanityTest, CategoryTest, DocsHelperTest, DocsEntryTest, AclTest, AclFeed, MetadataTest]) if __name__ == '__main__': unittest.main() gdata-2.0.18/tests/gdata_tests/docs/__init__.py0000640036466300116100000000000012156622363021431 0ustar afshareng00000000000000gdata-2.0.18/tests/gdata_tests/docs/live_client_test.py0000750036466300116100000005417712156622363023260 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright 2011 Google Inc. All Rights Reserved. # # 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. # This module is used for version 2 of the Google Data APIs. # These tests attempt to connect to Google servers. """Live integration tests of the Google Documents List API. RESOURCES: Dict of test resource data, keyed on resource type. """ __author__ = 'vicfryzel@google.com (Vic Fryzel)' import os import os.path import tempfile import time import unittest import gdata.client import gdata.data import gdata.gauth import gdata.docs.client import gdata.docs.data import gdata.test_config as conf RESOURCES = { 'document': (gdata.docs.data.DOCUMENT_LABEL, 'Text Document', 'data/test.0.doc', 'data/test.1.doc', 'application/msword', 'doc'), 'empty_document': (gdata.docs.data.DOCUMENT_LABEL, 'Empty Text Document', None, 'data/test.1.doc', 'application/msword', 'txt'), 'spreadsheet': (gdata.docs.data.SPREADSHEET_LABEL, 'Spreadsheet', 'data/test.0.csv', 'data/test.1.csv', 'text/csv', 'csv'), 'presentation': (gdata.docs.data.PRESENTATION_LABEL, 'Presentation', 'data/test.0.ppt', 'data/test.1.ppt', 'application/vnd.ms-powerpoint', 'ppt'), 'drawing': (gdata.docs.data.DRAWING_LABEL, 'Drawing', 'data/test.0.wmf', 'data/test.1.wmf', 'application/x-msmetafile', 'png'), 'pdf': (gdata.docs.data.PDF_LABEL, 'PDF', 'data/test.0.pdf', 'data/test.1.pdf', 'application/pdf', None), 'file': (gdata.docs.data.FILE_LABEL, 'File', 'data/test.0.bin', 'data/test.1.bin', 'application/octet-stream', None), 'collection': (gdata.docs.data.COLLECTION_LABEL, 'Collection A', None, None, None, None) } class DocsTestCase(unittest.TestCase): def shortDescription(self): if hasattr(self, 'resource_type'): return '%s for %s' % (self.__class__.__name__, self.resource_type) else: return self.__class__.__name__ def _delete(self, resource): try: self.client.DeleteResource(resource, permanent=True, force=True) except: pass def _delete_all(self): resources = self.client.GetAllResources( '/feeds/default/private/full?showfolders=true&showdeleted=true') for resource in resources: self._delete(resource) def _create(self): ms = None if self.resource_path is not None and self.resource_mime is not None: ms = gdata.data.MediaSource( file_path=os.path.join(os.path.dirname(__file__), self.resource_path), content_type=self.resource_mime) entry = gdata.docs.data.Resource(type=self.resource_label, title=self.resource_title) self.resource = self.client.CreateResource(entry, media=ms) def _update(self): ms = None if self.resource_alt_path is not None and self.resource_mime is not None: ms = gdata.data.MediaSource( file_path=os.path.join(os.path.dirname(__file__), self.resource_alt_path), content_type=self.resource_mime) self.resource.title.text = '%s Updated' % self.resource_title return self.client.UpdateResource(self.resource, media=ms, force=True, new_revision=True) def setUp(self): if conf.options.get_value('runlive') != 'true': raise RuntimeError('Live tests require --runlive true') self.client = gdata.docs.client.DocsClient() if conf.options.get_value('ssl') == 'false': self.client.ssl = False conf.configure_client(self.client, 'DocsTest', self.client.auth_service) conf.configure_cache(self.client, str(self.__class__)) if conf.options.get_value('clean') == 'true': self._delete_all() tries = 0 while tries < 3: try: tries += 1 self._create() break except gdata.client.RequestError: if tries >= 2: self.tearDown() raise def tearDown(self): if conf.options.get_value('runlive') == 'true': if conf.options.get_value('clean') == 'true': self._delete_all() else: try: self._delete(self.resource) except: pass conf.close_client(self.client) class ResourcesTest(DocsTestCase): def testGetAllResources(self): results = self.client.GetAllResources( '/feeds/default/private/full?showfolders=true') self.assert_(all(isinstance(item, gdata.docs.data.Resource) \ for item in results)) self.assertEqual(len(results), 1) def testGetResources(self): feed = self.client.GetResources( '/feeds/default/private/full?showfolders=true', limit=1) self.assert_(isinstance(feed, gdata.docs.data.ResourceFeed)) self.assertEqual(len(feed.entry), 1) def testGetResource(self): entry = self.client.GetResource(self.resource) self.assert_(isinstance(entry, gdata.docs.data.Resource)) self.assert_(entry.id.text is not None) self.assert_(entry.title.text is not None) self.assert_(entry.resource_id.text is not None) self.assert_(entry.title.text is not None) entry = self.client.GetResourceById(self.resource.resource_id.text) self.assert_(isinstance(entry, gdata.docs.data.Resource)) self.assert_(entry.id.text is not None) self.assert_(entry.title.text is not None) self.assert_(entry.resource_id.text is not None) self.assert_(entry.title.text is not None) entry = self.client.GetResourceById( self.resource.resource_id.text.split(':')[1]) self.assert_(isinstance(entry, gdata.docs.data.Resource)) self.assert_(entry.id.text is not None) self.assert_(entry.title.text is not None) self.assert_(entry.resource_id.text is not None) self.assert_(entry.title.text is not None) entry = self.client.GetResourceBySelfLink( self.resource.GetSelfLink().href) self.assert_(isinstance(entry, gdata.docs.data.Resource)) self.assert_(entry.id.text is not None) self.assert_(entry.title.text is not None) self.assert_(entry.resource_id.text is not None) self.assert_(entry.title.text is not None) def testMoveResource(self): entry = gdata.docs.data.Resource( type=gdata.docs.data.COLLECTION_LABEL, title='Collection B') collection = self.client.CreateResource(entry) # Start off in 0 collections self.assertEqual(len(self.resource.InCollections()), 0) # Move resource into collection entry = self.client.MoveResource(self.resource, collection) self.assertEqual(len(entry.InCollections()), 1) self.assertEqual(entry.InCollections()[0].title, collection.title.text) self.client.DeleteResource(collection, permanent=True, force=True) def testCopyResource(self): copy_title = '%s Copy' % self.resource_title # Copy only supported for document, spreadsheet, presentation types if self.resource_type in ['document', 'empty_document', 'spreadsheet', 'presentation']: copy = self.client.CopyResource(self.resource, copy_title) self.assertEqual(copy.title.text, copy_title) self.client.DeleteResource(copy, permanent=True, force=True) # TODO(vicfryzel): Expect appropriate error for drawings. elif self.resource_type != 'drawing': self.assertRaises(gdata.client.NotImplemented, self.client.CopyResource, self.resource, copy_title) def testDownloadResource(self): tmp = tempfile.mkstemp() if self.resource_type != 'collection': if self.resource_export is not None: extra_params = {'exportFormat': self.resource_export, 'format': self.resource_export} self.client.DownloadResource(self.resource, tmp[1], extra_params=extra_params) else: self.client.DownloadResource(self.resource, tmp[1]) else: # Cannot download collections self.assertRaises(ValueError, self.client.DownloadResource, self.resource, tmp[1]) # Should get a 404 entry = gdata.docs.data.Resource(type=gdata.docs.data.DOCUMENT_LABEL, title='Does Not Exist') self.assertRaises(AttributeError, self.client.DownloadResource, entry, tmp[1]) os.close(tmp[0]) os.remove(tmp[1]) def testDownloadResourceToMemory(self): if self.resource_type != 'collection': data = None if self.resource_export is not None: extra_params = {'exportFormat': self.resource_export, 'format': self.resource_export} data = self.client.DownloadResourceToMemory( self.resource, extra_params=extra_params) else: data = self.client.DownloadResourceToMemory(self.resource) if self.resource_type == 'empty_document': self.assertEqual(len(data), 3) else: self.assertNotEqual(len(data), 0) else: # Cannot download collections self.assertRaises(ValueError, self.client.DownloadResourceToMemory, self.resource) def testDelete(self): self.assertEqual(self.resource.deleted, None) self.client.DeleteResource(self.resource, force=True) self.resource = self.client.GetResource(self.resource) self.assertNotEqual(self.resource.deleted, None) self.client.DeleteResource(self.resource, permanent=True, force=True) self.assertRaises(gdata.client.RequestError, self.client.GetResource, self.resource) class AclTest(DocsTestCase): def testGetAcl(self): acl_feed = self.client.GetResourceAcl(self.resource) self.assert_(isinstance(acl_feed, gdata.docs.data.AclFeed)) self.assertEqual(len(acl_feed.entry), 1) self.assert_(isinstance(acl_feed.entry[0], gdata.docs.data.AclEntry)) self.assert_(acl_feed.entry[0].scope is not None) self.assert_(acl_feed.entry[0].role is not None) def testGetAclEntry(self): acl_feed = self.client.GetResourceAcl(self.resource) acl_entry = acl_feed.entry[0] same_acl_entry = self.client.GetAclEntry(acl_entry) self.assert_(isinstance(same_acl_entry, gdata.docs.data.AclEntry)) self.assertEqual(acl_entry.GetSelfLink().href, same_acl_entry.GetSelfLink().href) self.assertEqual(acl_entry.title.text, same_acl_entry.title.text) def testAddAclEntry(self): acl_entry_to_add = gdata.docs.data.AclEntry.GetInstance( role='writer', scope_type='default', key=True) new_acl_entry = self.client.AddAclEntry(self.resource, acl_entry_to_add) self.assertEqual(acl_entry_to_add.scope.type, new_acl_entry.scope.type) self.assertEqual(new_acl_entry.scope.value, None) # Key will always be overridden on add self.assertEqual(acl_entry_to_add.with_key.role.value, new_acl_entry.with_key.role.value) acl_feed = self.client.GetResourceAcl(self.resource) self.assert_(isinstance(acl_feed, gdata.docs.data.AclFeed)) self.assert_(isinstance(acl_feed.entry[0], gdata.docs.data.AclEntry)) self.assert_(isinstance(acl_feed.entry[1], gdata.docs.data.AclEntry)) def testUpdateAclEntry(self): acl_entry_to_add = gdata.docs.data.AclEntry.GetInstance( role='reader', scope_type='user', scope_value='jeff@example.com', key=True) other_acl_entry = gdata.docs.data.AclEntry.GetInstance( role='writer', scope_type='user', scope_value='jeff@example.com') new_acl_entry = self.client.AddAclEntry(self.resource, acl_entry_to_add) new_acl_entry.with_key = None new_acl_entry.scope = other_acl_entry.scope new_acl_entry.role = other_acl_entry.role updated_acl_entry = self.client.UpdateAclEntry(new_acl_entry) self.assertEqual(updated_acl_entry.GetSelfLink().href, new_acl_entry.GetSelfLink().href) self.assertEqual(updated_acl_entry.title.text, new_acl_entry.title.text) self.assertEqual(updated_acl_entry.scope.type, other_acl_entry.scope.type) self.assertEqual(updated_acl_entry.scope.value, other_acl_entry.scope.value) self.assertEqual(updated_acl_entry.role.value, other_acl_entry.role.value) self.assertEqual(updated_acl_entry.with_key, None) def testDeleteAclEntry(self): acl_entry_to_add = gdata.docs.data.AclEntry.GetInstance( role='writer', scope_type='user', scope_value='joe@example.com', key=True) acl_feed = self.client.GetResourceAcl(self.resource) new_acl_entry = self.client.AddAclEntry(self.resource, acl_entry_to_add) acl_feed = self.client.GetResourceAcl(self.resource) self.assert_(isinstance(acl_feed, gdata.docs.data.AclFeed)) self.assertEqual(len(acl_feed.entry), 2) self.assert_(isinstance(acl_feed.entry[0], gdata.docs.data.AclEntry)) self.assert_(isinstance(acl_feed.entry[1], gdata.docs.data.AclEntry)) self.client.DeleteAclEntry(new_acl_entry) acl_feed = self.client.GetResourceAcl(self.resource) self.assert_(isinstance(acl_feed, gdata.docs.data.AclFeed)) self.assertEqual(len(acl_feed.entry), 1) self.assert_(isinstance(acl_feed.entry[0], gdata.docs.data.AclEntry)) class RevisionsTest(DocsTestCase): def testGetRevisions(self): # There are no revisions of collections if self.resource_type != 'collection': revisions = self.client.GetRevisions(self.resource) self.assert_(isinstance(revisions, gdata.docs.data.RevisionFeed)) self.assert_(isinstance(revisions.entry[0], gdata.docs.data.Revision)) # Currently, there is a bug where new presentations have 2 revisions. if self.resource_type != 'presentation': self.assertEqual(len(revisions.entry), 1) def testGetRevision(self): # There are no revisions of collections if self.resource_type != 'collection': revisions = self.client.GetRevisions(self.resource) entry = revisions.entry[0] new_entry = self.client.GetRevision(entry) self.assertEqual(entry.GetSelfLink().href, new_entry.GetSelfLink().href) self.assertEqual(entry.title.text, new_entry.title.text) def testGetRevisionBySelfLink(self): # There are no revisions of collections if self.resource_type != 'collection': revisions = self.client.GetRevisions(self.resource) entry = revisions.entry[0] new_entry = self.client.GetRevisionBySelfLink(entry.GetSelfLink().href) self.assertEqual(entry.GetSelfLink().href, new_entry.GetSelfLink().href) self.assertEqual(entry.title.text, new_entry.title.text) def testMultipleRevisionsAndUpdateResource(self): if self.resource_type not in ['collection', 'presentation']: revisions = self.client.GetRevisions(self.resource) self.assertEqual(len(revisions.entry), 1) # Currently, there is a bug where uploaded presentations have 2 revisions. elif self.resource_type == 'presentation': revisions = self.client.GetRevisions(self.resource) self.assertEqual(len(revisions.entry), 2) # Drawings do not currently support update, thus the rest of these # tests do not yet work as expected. if self.resource_type == 'drawing': return entry = self._update() self.assertEqual(entry.title.text, '%s Updated' % self.resource_title) if self.resource_type != 'collection': revisions = self.client.GetRevisions(entry) self.assert_(isinstance(revisions, gdata.docs.data.RevisionFeed)) if self.resource_type == 'presentation': self.assertEqual(len(revisions.entry), 3) self.assert_(isinstance(revisions.entry[2], gdata.docs.data.Revision)) else: self.assertEqual(len(revisions.entry), 2) self.assert_(isinstance(revisions.entry[0], gdata.docs.data.Revision)) self.assert_(isinstance(revisions.entry[1], gdata.docs.data.Revision)) def testPublishRevision(self): if self.resource_type in ['file', 'pdf', 'collection']: return # Drawings do not currently support update, thus this test would fail. if self.resource_type == 'drawing': return entry = self._update() revisions = self.client.GetRevisions(entry) revision = self.client.PublishRevision(revisions.entry[1]) # Currently, there is a bug where uploaded presentations have 2 revisions. if self.resource_type == 'presentation': revisions = self.client.GetRevisions(entry) revision = revisions.entry[2] self.assert_(isinstance(revision, gdata.docs.data.Revision)) self.assertEqual(revision.publish.value, 'true') self.assertEqual(revision.publish_auto.value, 'false') # The rest of the tests require an Apps domain if 'gmail' in conf.options.get_value('username'): return self.assertEqual(revision.publish_outside_domain.value, 'false') # Empty documents won't have further revisions b/c content didn't change if self.resource_type == 'empty_document': return revisions = self.client.GetRevisions(entry) if self.resource_type == 'presentation': revision = self.client.PublishRevision( revisions.entry[2], publish_auto=True, publish_outside_domain=True) else: revision = self.client.PublishRevision( revisions.entry[1], publish_auto=True, publish_outside_domain=True) if self.resource_type == 'spreadsheet': revision = client.GetRevisions(entry).entry[1] self.assert_(isinstance(revision, gdata.docs.data.Revision)) self.assertEqual(revision.publish.value, 'true') self.assertEqual(revision.publish_auto.value, 'true') self.assertEqual(revision.publish_outside_domain.value, 'true') revision = self.client.GetRevision(revisions.entry[0]) self.assertEqual(revision.publish, None) self.assertEqual(revision.publish_auto, None) self.assertEqual(revision.publish_outside_domain, None) def testDownloadRevision(self): if self.resource_type == 'collection': return revisions = self.client.GetRevisions(self.resource) tmp = tempfile.mkstemp() self.client.DownloadRevision(revisions.entry[0], tmp[1]) os.close(tmp[0]) os.remove(tmp[1]) def testDeleteRevision(self): # API can only delete file revisions if self.resource_type != 'file': return entry = self._update() revisions = self.client.GetRevisions(entry) self.assertEqual(len(revisions.entry), 2) self.client.DeleteRevision(revisions.entry[1]) self.assert_(isinstance(revisions, gdata.docs.data.RevisionFeed)) self.assert_(isinstance(revisions.entry[0], gdata.docs.data.Revision)) revisions = self.client.GetRevisions(entry) self.assertEqual(len(revisions.entry), 1) class ChangesTest(DocsTestCase): def testGetChanges(self): # This test assumes that by the time this test is run, the account # being used already has a number of changes changes = self.client.GetChanges(max_results=5) self.assert_(isinstance(changes, gdata.docs.data.ChangeFeed)) self.assert_(len(changes.entry) <= 5) self.assert_(isinstance(changes.entry[0], gdata.docs.data.Change)) self._update() changes = self.client.GetChanges(changes.entry[0].changestamp.value, 5) self.assert_(isinstance(changes, gdata.docs.data.ChangeFeed)) self.assert_(len(changes.entry) <= 5) self.assert_(isinstance(changes.entry[0], gdata.docs.data.Change)) def testDeleteResourceCreatesNewChange(self): """Ensure that deleting a resource causes a new change entry.""" self._update() changes = self.client.GetChanges(max_results=1) latest = changes.entry[0].changestamp.value self._delete(self.resource) time.sleep(10) changes = self.client.GetChanges(max_results=1) self.assert_(latest < changes.entry[0].changestamp.value) class MetadataTest(DocsTestCase): def setUp(self): if conf.options.get_value('runlive') != 'true': raise RuntimeError('Live tests require --runlive true') else: self.client = gdata.docs.client.DocsClient() if conf.options.get_value('ssl') == 'true': self.client.ssl = True conf.configure_client(self.client, 'DocsTest', self.client.auth_service) conf.configure_cache(self.client, str(self.__class__)) if conf.options.get_value('clean') == 'true': self._delete_all() def tearDown(self): conf.close_client(self.client) def testMetadata(self): metadata = self.client.GetMetadata() self.assert_(isinstance(metadata, gdata.docs.data.Metadata)) self.assertNotEqual(int(metadata.quota_bytes_total.text), 0) self.assertEqual(int(metadata.quota_bytes_used.text), 0) self.assertEqual(int(metadata.quota_bytes_used_in_trash.text), 0) self.assertNotEqual(len(metadata.import_formats), 0) self.assertNotEqual(len(metadata.export_formats), 0) self.assertNotEqual(len(metadata.features), 0) self.assertNotEqual(len(metadata.max_upload_sizes), 0) def suite(): suite = unittest.TestSuite() for key, value in RESOURCES.iteritems(): for case in [ResourcesTest, AclTest, RevisionsTest, ChangesTest]: tests = unittest.TestLoader().loadTestsFromTestCase(case) for test in tests: test.resource_type = key test.resource_label = value[0] test.resource_title = value[1] test.resource_path = value[2] test.resource_alt_path = value[3] test.resource_mime = value[4] test.resource_export = value[5] suite.addTest(test) suite.addTests(unittest.TestLoader().loadTestsFromTestCase(MetadataTest)) return suite if __name__ == '__main__': unittest.TextTestRunner().run(suite()) gdata-2.0.18/tests/gdata_tests/docs/data/0000750036466300116100000000000012156625015020237 5ustar afshareng00000000000000gdata-2.0.18/tests/gdata_tests/docs/data/test.1.bin0000640036466300116100000000002212156622363022045 0ustar afshareng00000000000000lorem ipsum dolar gdata-2.0.18/tests/gdata_tests/docs/data/test.1.csv0000750036466300116100000000250212156622363022077 0ustar afshareng00000000000000UPDATED Invoice,Job/Memo,Date,DueWk,Stat,Amount,"HB/Pay/ Adjs",UPDATED Amount Due 2,2,2,2,2,2,2,2 2,2,2,2,2,2,2,2 3,3,3,3,3,3,3,3 4,4,4,4,4,4,4,4 5,5,5,5,5,5,5,5 6,6,6,6,6,6,6,6 7,7,7,7,7,7,7,7 8,8,8,8,8,8,8,8 9,9,9,9,9,9,9,9 20,20,20,20,20,20,20,20 22,22,22,22,22,22,22,22 22,22,22,22,22,22,22,22 23,23,23,23,23,23,23,23 24,24,24,24,24,24,24,24 25,25,25,25,25,25,25,25 26,26,26,26,26,26,26,26 27,27,27,27,27,27,27,27 28,28,28,28,28,28,28,28 29,29,29,29,29,29,29,29 20,20,20,20,20,20,20,20 22,22,22,22,22,22,22,22 22,22,22,22,22,22,22,22 23,23,23,23,23,23,23,23 ,,,,,,, ,,,,,,, ,,,,,,, ,,,,,,, ,,,,,,, ,,,,,,, ,,,,,,, ,,,,,,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, gdata-2.0.18/tests/gdata_tests/docs/data/test.0.pdf0000750036466300116100000001145312156622363022061 0ustar afshareng00000000000000%PDF-1.4 %âãÏÓ 1 0 obj <>>> endobj 2 0 obj <> endobj 5 0 obj <> stream xœ3Ô3…t.s# F›˜Y@f&PFQ*Wš—SŠrC=##K….}7C$’Æ¥’Z\¢à⦩’Åå€ 6 endstream endobj 6 0 obj 79 endobj 3 0 obj <> /ProcSet [/PDF /Text]>>>> endobj 4 0 obj <> endobj 7 0 obj <> endobj 8 0 obj <> stream xœ½WkpSÇ>»weI¶dɶüÀŠ}¯Ø`dëe Ø`ˆ c<8Ä`a lð Éa€bÌÃ!<Ê+¡)¥i“ $)B¦(ˆ%$¡iš™tÒ´!M&:%´j«ç^ËtGb(±š/~|Qjòó †„áØ´ú®M·Ä'´‰—¾‰da\£xÈ€±®ìÂÄ©‰õ´NÍRl .Õ&W,¢I†V^Ûy_óT¼!S{ójµXÁyÕy5?T•Ç#F±°Ô”D¹-+{41‘«ÅQPLdáCÛ޺ط´~DHÕ³Þ]¹bNOGw÷–ªš›éúºæ:ÇÈ.9 æ÷ýãÁënKö­§r 'ÿM|%!C¶*ÁæÊ”Ù)•»‚Ã÷ WG9|qš8BeH+V{³×R­½a1#1çÕÄB³È 9%Œ ¶ºWC×÷®èáŽÈ }…z­èUÊÛ…H‹;´ÒU4VW”áÑy2fÄWhüùȵr*—+Ól±œR¡1ðš,@¸ ߀ÍÁi Cä:eƒ%P}u€@u4;1˜L²°¤¤f’ûòÃv¹‹§ÿ~_OÙ±®{ÊìêwyOÌ;ûòò]fs6ÓÔî/ž>½÷Ù¥Qy…ÖO ãõ½¯™Ìy‹û_Øì²—C¶+0{ Ç)4J^Y¦ä`.ѱ¹e2"3Wßèý«H©×"%iØ ìZï×=½_ãhÃwŸÈ !q7,Ž\a5l¤a.u¹²˜IÇÆ©ÇgNbµ'óQõŒ”ÅêšÔåê•™ñd<Ïk*Jfqqr§F©R I‡-1?NÇ}bû@:®Z͸Q¬æ%©&˜qòÖdÜ6$hÁjILƤ‡ÆÈYÍ¿Ï;l[*_äÇÎïiìûªï1ÝøìÛ#dû®Ý*ª_ô\~^ÞÜÜ‹#dI&‰dbß¿þ‘³ã…ðZÌISäsîC¶¬°Ýµ4†*õÉtˆ>K™3Ì¢?l¢rÚ°y²ê”Y†Js…¥YÖR#øÌ~‹n¥luB«°bD«©ƒlP·§¯±ƒìÑÇA|ÚH–ɵ %C‡fg2¹<®˜SFêÓâõ`H“Ãf»ÆÎÛöùöÕv™|M{¥Z{%¡°P{Õ|u`ÞVsuBbj¡8uq3‡feÛ­Rì¶lq+àßn·‚÷G²QJC².UÌ6)܇½ZõnilÕG¾U›²²F¬±o¢pì˜×–ø.–ÄNy§vÑfSÎ<ÛÓS“'“‰Ïg|RٌʉC‡¦)Óâ³w7¹Wæ™ òçíSËq)ª´ØÌ©c®Š#_Ñ^Ù^ÐÃW®J–.3ɸ8­¼X+ÓëSœ²,cu‡M µ6=V!w©µò ioš,–^‹Y{Ó‚; Ójŧ8Wth–=Áh·&X¥Ãwgµi¯ýÉüW«W'kûV)ÒR¦—ö¥ÄÆÆ'½@Ë÷’ }'÷öqsjM#†ë•âæ&ýuGÑ ã|ÍøoAߟ8𣒑â3ÜøÙ+‘=}UqÛ¢¥÷pAI×WŽWŸÜÈž[ˆÛþÀåF©@}ï9pŽ=‡õ}¬×ð<…ÅÌM\6àÝ O}DHÙHyÚ ý‘à™[‚׊o3ŒÁq;‰ßÂbïråv¼I0p±"8bRSÄG1‡ãgF1C}[ËðÞ¶>ŠcP?09Úï#1ý¨à¼„û™| áIÿ¹„å’þïVˆ˜ ‹é]G¢˜@<ÙÅñ‹QÌÁ<ÒÅ â逽 Ò(Å1¨/ˆb9Ì£n ÇŠè/%'Æ¥%¬’ô'%/á ÖŠqé‡NBœHûùë$›J8Yôƒï|§ˆz.IÂCı\?½d3JÂ’M‘„y O•ð0É~¶„GI¸VÄ ‰3·TÂýþŸ±ª_¿QÂngÅŠÿBo­_ð6ùŸ·Õ+ìò ÂôúÚ@s°ya«0©9ÐÒð¶Ö77&44åõ‹êZƒB¹?è,óû*ýŸ·É[î_ôxƒ7p{ÜØ¨^ˆvˆr}ùˆÛF#£F}–ÑVû•4¼>(x…Ö€×çoô–Í §àå.à…D `Qƒ_º~‰õNß,¼âyQãÃ6>nwˆ;ÁÄzŒëæ^Åëã <(~Xˆýµø¢Ö‚4B+àõW€|(ÄŸÑt¨GËÆb]ˆ61b‹ÔŠ£ê5Áh와ðYŽºEP‡}AIòãÓÖ˰õáÕVÄ>)¶Wê]ã8‘óƒñÆÞg/Ü7b ?å!²`Ä žFÞçéþqœƒìƒZ݉^/ÍID­Òü}Øß(1Y‚:1Òÿ’µ»Wö¥úAû>½Ç®cß½æÒªÏ´Yq·Ì2Y>ó°É¬ÛÂ{"4¡ßïóò¶Ë¤y‹;oê¸M‹ï38øPL¿‘,øàÊ ´8´a!ì Ï·„ÛÂû¡ð{áËáØSáëa*š´ü&5ÍÁ—M%_IËfÏŸM›+ÈÏ+VЙ³RYù¬6«<™=<µœ•N-`“§ZجSí…l¼ÓŠœE¬Øi`“œl¢³œMÀêÂê´[˜ÅêcV»ÙmÌfÏdïÙ.Û®Û¸£‘k‡‡Oq\î<¬5âóšK}X©qNŸÂ–u®ëDZ×;;%‹ï\‘Nå0G§n Û¸!‰µ4´,§šç?ÙK]?Kâp=Ÿ¢w¸v§"Ú•ªw¬kOâ5k5íšÍš-š­üZ~3¿Å¼¹­½mÖm[Û·®ßºAãú±RëÐøu-UªšF"œ#Â[ÄÙóMÞt½IaÚÔåÝ祚¹d”.åê†3“®åè’ØH]2ãu™Ì Lb‚n<;ŸîféúÉLŸ>ž¥ë,,í’n¢.%`mÑ—nÂ$‡&>‡‡¢>ãáU§=|ì)¯Ä*;îáÙ Ïóð´ÛÓ.G<ü™Ó9ü©“9ü Wåqß}ÌÀé2ð§ÏœUŸ<õ†úø‰ßªŽu¿®ê:rT¥=ÞvœºŽµ£š.gWY×ê.¦é2#lFx²ëÝ®H—"VYÀTjŠŸžÅ/*:CFŽ’Hû3Ïd„vá']¨-£ê¨<ø‘HBdsUHá™…`K°54 RBœ;ã®ó†bŒ%AQˆ…xühŒw‡4"ÖKL$¤s×…tˆp(¦`´³?ÔÀãƒÅ¹´bkú |¨ endstream endobj 9 0 obj 3128 endobj 10 0 obj <> endobj xref 0 11 0000000000 65535 f 0000000016 00000 n 0000000107 00000 n 0000000334 00000 n 0000000477 00000 n 0000000163 00000 n 0000000315 00000 n 0000001106 00000 n 0000001289 00000 n 0000004504 00000 n 0000004525 00000 n trailer <> startxref 4609 %%EOF gdata-2.0.18/tests/gdata_tests/docs/data/test.1.pdf0000640036466300116100000000263212156622363022057 0ustar afshareng00000000000000%PDF-1.4 % âãÏÓ 4 0 obj << /Type /Catalog /Names << /JavaScript 3 0 R >> /PageLabels << /Nums [ 0 << /S /D /St 1 >> ] >> /Outlines 2 0 R /Pages 1 0 R >> endobj 5 0 obj << /Creator (þÿGoogle) >> endobj 6 0 obj << /Type /Page /Parent 1 0 R /MediaBox [ 0 0 720 540 ] /Contents 7 0 R /Resources 8 0 R /Annots 10 0 R /Group << /S /Transparency /CS /DeviceRGB >> >> endobj 7 0 obj << /Filter /FlateDecode /Length 9 0 R >> stream xœÅS½nA ìý.,뻤BBJ ¢€ åúRðúÌ^È—»ðÑêîì½±g<Ú½'“Özó¸M|¯oéžLî(Ÿ¶!^æ­Ï-ixÆÜÊÏω¤©¬àL/’õ>ÑgºÉGÍuhÌ­#÷^Ï|‰)k T˜ºdéF6p¼¤p¦°’Rà¾À.…¢¸ÒÛ•§À¯²GÁ­®}«ÿ ¯ÿ¿Ö_JÚÒJ endstream endobj 9 0 obj 390 endobj 10 0 obj [ ] endobj 11 0 obj << /CA 1.0 /ca 1.0 >> endobj 8 0 obj << /Font << >> /Pattern << >> /XObject << >> /ExtGState << /Alpha0 11 0 R >> /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] >> endobj 1 0 obj << /Type /Pages /Kids [ 6 0 R ] /Count 1 >> endobj xref 0 12 0000000002 65535 f 0000001057 00000 n 0000000003 00000 f 0000000000 00000 f 0000000016 00000 n 0000000160 00000 n 0000000207 00000 n 0000000373 00000 n 0000000914 00000 n 0000000837 00000 n 0000000856 00000 n 0000000876 00000 n trailer << /Size 12 /Root 4 0 R /Info 5 0 R >> startxref 1116 %%EOF gdata-2.0.18/tests/gdata_tests/docs/data/test.1.doc0000640036466300116100000004100012156622363022043 0ustar afshareng00000000000000ÐÏࡱá!þÿ þÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿì¥Á!` ø¿ØAWJ4030 .&>Ç>Çì ÿÿÿÿÿÿ¤¦¦âââ⦺ºººº ƺùîââââââââñhY *äââââââââââ⃠ââââäç ™ âââââäââââââääääääääääääääääääääääääääää Updated document text Pastrami shankle strip steak, ribeye boudin short loin spare ribs swine biltong flank tail corned beef meatball turkey fatback. Pork ground round biltong, tenderloin drumstick jowl bacon ham shoulder pork chop meatball beef strip steak brisket. Bresaola spare ribs bacon t-bone andouille beef ribs tri-tip. T-bone drumstick pork, shank spare ribs ball tip chuck pastrami rump sausage boudin. Tail strip steak bresaola, jerky hamburger cow chuck brisket pork chop headcheese bacon pastrami. T-bone meatball headcheese beef pig. Flank shankle pig, boudin shoulder tri-tip sirloin shank cow tongue pork loin brisket bresaola venison. Hamburger tri-tip chicken, rump headcheese jowl pastrami salami. Salami beef ribs jowl shankle biltong. Chicken t-bone ball tip, turkey pastrami shankle flank tri-tip ribeye tongue biltong pancetta jerky ham pork belly. Tenderloin rump pork loin beef ribs tongue, shank short loin boudin beef fatback strip steak. Sirloin strip steak boudin turkey drumstick, chicken bresaola pastrami shank. Headcheese salami flank, spare ribs drumstick biltong tenderloin. Pastrami biltong spare ribs pig shankle. Swine ham hock jowl, spare ribs tri-tip short loin flank hamburger tenderloin salami sausage brisket pig. Short loin pastrami brisket, chuck pig turkey tenderloin beef hamburger short ribs headcheese ham hock jowl. Hamburger shankle short ribs, chuck ribeye beef ribs strip steak. Short ribs flank pork belly, biltong tail t-bone strip steak ball tip salami shankle swine boudin drumstick. Meatloaf venison meatball, drumstick pork loin t-bone short loin salami ribeye swine pastrami. Rump shoulder ground round tri-tip pork loin. Ham hock ham ribeye turkey swine tenderloin. Andouille ham tenderloin, jerky ground round tri-tip hamburger chuck short loin pig pastrami. Flank pork belly sirloin salami. Ham ham hock shank cow, brisket pork corned beef meatloaf. Bacon short ribs pig, salami t-bone chicken swine tongue tri-tip shankle pancetta. Pancetta beef chuck fatback salami boudin, bresaola swine drumstick shoulder. Boudin pork drumstick tail, pork loin flank ground round salami chuck shank venison sirloin t-bone spare ribs pork belly. Chuck tri-tip pancetta sausage, t-bone strip steak brisket. T-bone pork loin bacon chuck pork ball tip. Corned beef ham hock short loin ham meatball sausage, tail tongue drumstick. Shankle short ribs venison strip steak, jerky ribeye andouille sausage meatball t-bone headcheese flank swine pork drumstick. Sirloin ham hock sausage, swine drumstick jerky rump pork fatback beef ribs short loin. Boudin beef pork belly, tongue cow rump bacon sirloin. Ribeye chicken short loin jerky flank pancetta. Chuck turkey ribeye, fatback headcheese short ribs shankle ham spare ribs boudin brisket pancetta. *.  €‚¢¤ÔÖØýùæÒ¿«˜„q]J62h>{§'h>{§B*CJ OJPJQJ^JaJ ph333$B*CJ OJPJQJZ^JaJ ph333'h>{§B*CJ OJPJQJ^JaJ ph333$B*CJ OJPJQJZ^JaJ ph333'h>{§B*CJ OJPJQJ^JaJ ph333$B*CJ OJPJQJZ^JaJ ph333'h>{§B*CJ OJPJQJ^JaJ ph333$B*CJ OJPJQJZ^JaJ ph333'h>{§B*CJ OJPJQJ^JaJ ph333$B*CJ OJPJQJZ^JaJ ph333h>{§Z ,. ÆT8$ & Fdð$d%d&d'd(d)fNÆÿOÆÿPÆÿQÆÿRÆÿA$8$ & Fdð$d%d&d'd(d)fNÆÿOÆÿPÆÿQÆÿRÆÿA$8$ & Fdð$d%d&d'd(d)fNÆÿOÆÿPÆÿQÆÿRÆÿA$ ‚¤ÆT8$ & Fdð$d%d&d'd(d)fNÆÿOÆÿPÆÿQÆÿRÆÿA$8$ & Fdð$d%d&d'd(d)fNÆÿOÆÿPÆÿQÆÿRÆÿA$8$ & Fdð$d%d&d'd(d)fNÆÿOÆÿPÆÿQÆÿRÆÿA$¤ÖØÆ8$ & Fdð$d%d&d'd(d)fNÆÿOÆÿPÆÿQÆÿRÆÿA$8$ & Fdð$d%d&d'd(d)fNÆÿOÆÿPÆÿQÆÿRÆÿA$,1h°Ð/ °à=!° "° # $ %°°Ä°Ä ÄZ 2ÀÐàð 0@P`p€ÀÐàð2(Øè 0@P`p€ÀÐàð 0@P`p€ÀÐàð 0@P`p€ÀÐàð 0@P`p€ÀÐàð 0@P`p€ÀÐàð 0@P`p€8XøV~OJPJQJ^JŽ@ñÿŽ Normal4$$„„„d¤¤A$]„^„`„a$3567>*B*CJOJPJQJ\]^JaJphP@P –{ï Heading 1$dð¤à¤xA$5CJ0\aJ0P@P –{ï Heading 2$dð¤h¤PA$5CJ$\aJ$P@P –{ï Heading 3$dð¤¤PA$5CJ\aJP@P –{ï Heading 4$dð¤ð¤(A$5CJ\aJP@P –{ï Heading 5$dð¤Ü¤(A$5CJ\aJP@P –{ï Heading 6$dð¤È¤(A$5CJ\aJDAòÿ¡D Default Paragraph FontRióÿ³R  Table Normalö4Ö l4Öaö (kôÿÁ(No Listì &ÿÿÿÿØ ¤Øÿ@ì Ġz €ÿTimes New Roman5€Symbol3&Ì ‡z €ÿArial7Georgia@"ñŒðÐh!ð ´´r4d2ƒðÿýXðÿ?>{§ÿÿÿÿUnknownþÿ ÿÿÿÿ ÀFMicrosoft Office Word Document MSWordDocWord.Document.8ô9²qþÿà…ŸòùOh«‘+'³Ù0”˜¤ ¬¸ ÄÐ Üè, 4@HTt €Œ°@@@Aspose.Words for Java 4.0.3.01 Normal.dot@þÿÕÍÕœ.“—+,ù®0Ð X`h t˜ ¨ ° ¸È° Title  æ   !"#$%&'()*+,-.þÿÿÿ0þÿÿÿ2345678þÿÿÿ:;<þÿÿÿRoot Entryÿÿÿÿÿÿÿÿ ÀF@1Tableÿÿÿÿÿÿÿÿ™ CompObjÿÿÿÿÿÿÿÿ/qWordDocumentÿÿÿÿÿÿÿÿ.&SummaryInformation(ÿÿÿÿÿÿÿÿ1ÄDocumentSummaryInformation8ÿÿÿÿÿÿÿÿÿÿÿÿ9ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ þÿÿÿþÿÿÿþÿÿÿþÿÿÿýÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿgdata-2.0.18/tests/gdata_tests/docs/data/test.0.doc0000750036466300116100000021100012156622363022043 0ustar afshareng00000000000000ÐÏࡱá;þÿ †þÿÿÿ€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿýÿÿÿÿÿÿÿþÿÿÿ  !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~þÿÿÿRoot Entryÿÿÿÿÿÿÿÿÿÿÿÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþÿÿÿþÿÿÿþÿÿÿ  !"#$%&'()*+,-./01þÿÿÿ3456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[þÿÿÿ]þÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþÿ ÿÿÿÿ ÀFMicrosoft Word-Dokument MSWordDocWord.Document.8ô9²q [xñÿxDefault(„V^„V„V]„V„`„¤V¤V1$*$/OJQJCJPJ^JaJB*mH sH nHÿ_HÿtHÿV!2V Heading 1@& & F & FOJQJCJ05PJ^JaJ0\BA@òÿ¡BAbsatz-Standardschriftart4þòÿñ4Endnote Characters6þòÿ6Footnote Characters6Uòÿ6 Internet Link B* ph€>*Fþ2FHeading ¤ð¤$OJQJCJPJ^JaJFB2F Text body"„^„„]„„`„¤¤/1BList<þR<Caption ¤x¤x $CJ6aJ]"þb"Index $Vþ2VHorizontal Line"¤¤$d%d&d'dCJ ,%‚,Sender ¤¤60þ1’0Table Contents. ¢.Footer Æ(û% $.².Header Æ(û% $VþÂVPreformatted Text ¤¤OJQJCJPJ^JaJ  ÿÿÿÿÿÿÿÿÿÿÿÿÿÿ„„Æÿ„„Æÿ„„Æÿ„„Æÿ„„Æÿ„„Æÿ„„Æÿ„„Æÿ„„Æÿÿÿÿÿÿ  P GTimes New Roman5Symbol3&Arial7Verdana[ ThorndaleTimes New Roman]HG Mincho Light JmsminchoIArial Unicode MSA&AlbanyArial?Courier NewBÅhUµÒQ-Q-ƒ'0€ì¥ÂM ¿0Caolan80 D ÿÿÿÿÿÿlhhh|4° ¼ Åt¤þÿà…ŸòùOh«‘+'³Ù0@@ H T ` l x„éý1@@@#C@€j&‹v•É@@#CG°êÿÿÿÿ(| €èÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ*¦ÐˆZÆ¥H8)ƒPƒÕ]ñÜ÷o²Ýl}_¬Lý+¶°òéìðÛ—KËK3“#}ý]î;ÜÈtŠ™À‚Ããþé÷H Ž”fV(“„Ú˜úú«…¯ ŠNäœÊÍ9“&&öBxä•;ç|®ùuUzÕk?¥WÐ &$<”Ycƒ YÐð#úd¨·X"AéØÚÛÞ¢Š’G“£VÑCô °£!%ÂB˜Ë &(´M h`z•«D]½­—}.AÞrÍ-y|G†¡ÇÈÉÄØÅ’çàbî°—o}ÖÉÞ‹†;MѹˆGnb⯃øé#7qˆ/Q÷*åFÎè"gõ úHÕÞ9¶VÅ6ü*Eƒ\F#Џ´z*®Ý‚«äRfölë±â½þøßdµ)8|ýÏOýæ)HW€Øú¸xm ´IVþð~uzrhvvðNÏŒÖÖUšZðŒ-x7ü®ýðî›À¶1³DœTZœ_t9µÐ;#勸¸cÑQÇÃOÝ ½x7èò­KÇÏö~øïß¿þµRÖ¥‹"Cc@cZbˆFh2Ñ!èX$–ŽLÇ©(]=¶‘!À\X_ÏH†zKcÑvM}U«VÙÜ$Ò(e­*¹F.oW©@Ãýƒ¾ú:xc#ÈUÝšMð(Óz»©Ã)'7wìyë–)3Ôˆýÿÿÿ‚ƒ„…þÿÿÿ‡þÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ9 X°È„œ¤ Ùdocument ÷CJPJ^JaJýB°Ð/ °à=!°n"°7°7#7°7$7+p,p-p.p3P(20þÿÕÍÕœ.“—+,ù®0éýRoot Entryÿÿÿÿÿÿÿÿ ÀF€CompObjÿÿÿÿjOle ÿÿÿÿÿÿÿÿ1Tableÿÿÿÿÿÿÿÿÿÿÿÿ‘ SummaryInformation(ÿÿÿÿ lëWordDocumentÿÿÿÿÿÿÿÿÿÿÿÿ2D DocumentSummaryInformation8ÿÿÿÿÿÿÿÿÿÿÿÿ\Hÿÿÿÿÿÿÿÿÿÿÿÿþÿÿÿgdata-2.0.18/tests/gdata_tests/docs/data/test.1.ppt0000640036466300116100000002300012156622363022101 0ustar afshareng00000000000000ÐÏࡱá>þÿ þÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿýÿÿÿþÿÿÿþÿÿÿ þÿÿÿþÿÿÿþÿÿÿ þÿÿÿþÿÿÿRoot Entryÿÿÿÿÿÿÿÿd›Oφꪹ)è+Âa¢3ÄCurrent Userÿÿÿÿÿÿÿÿ&SummaryInformation(ÿÿÿÿÿÿÿÿPowerPoint Document(ÿÿÿÿÿÿÿÿ/DocumentSummaryInformation8ÿÿÿÿÿÿÿÿÿÿÿÿdè­é(Àà€ òb/È 0ÒÕ˜·DTimes New Roman·DArial"¤ €`ÿÿÿÿ¥ .©  @£nÿý?" dd@ÿÿïÿÿÿÿÿÿ  @@``€€ |ðtð(  c ð$ƒ¿Àÿ@ñ÷ðó€ÐdÅÝÊš;2NÍÉÊš;úgþý4OdOdvÇ 0Ú ÿÿÿ¬ÿÿÿpûppû@ <ý4!d!d`ó 0LÞÀ>º<ý4dddd`ó 0LÞÀ>ºð8óóêøœï `ð ÿÿÿÿÿÿÿ™ÿÿÿ–––`ð ÿÿÿ€€€Ì™33ÌÌÌÿ²²²`ð ÿÿÿ333ÝÝÝ€€€MMMêêê`ð ÿÿÌff3€€3™3€3ÌÿÌf`ð ÿÿÿ€€€ÿÌfÿÌÌÀÀÀ`ð ÿÿÿ€€€ÀÀÀfÿÿ™`ð ÿÿÿ€€€3™ÿ™ÿÌÌ̲²²£>ÿý?" dd@ÿÿïÿÿÿÿÿÿ,£|ÿý?" ddØ@ÿÿïÿÿÿÿÿÿ € Ô €" Ð@€ ð`€»€ £nÿý?" dd@ÿÿïÿÿÿÿÿÿ   @@``€€P£R    @ ` €`£ p£>€£> ÖðÎððfð( ð ððÒ ð “ ð6€¼jº‡ƒ¿Àÿ ðªà Ëðà º ðTŸ¨ Click to edit Master title style¢!ª !ð ð ƒ ð0€è<ºƒ¿Àÿ ðjà «ðà º 𞟨RClick to edit Master text styles Second level Third level Fourth level Fifth level¢!    ª Sð¶ ð ƒ ð0€X—ºƒ¿Àÿ ðàVðà º ð>Ÿ *¡øð¸ ð ƒ ð0€à–ºƒ¿Àÿ ðŠvVðà  º ð@Ÿ *¡úð¸ ð ƒ ð0€$§ºƒ¿Àÿ ðê!Vðà º ð@Ÿ *¡ØðH ð ƒ ð0ƒ“ŽŸ‹”Þ½h¿ÿ ?ð ÿÿÿ€€€Ì™33ÌÌÌÿ²²² ºDefault Designî¬ï€ \ðT ððøð( ð ððò ð “ ð6‚ƒ„‡¿ÿð€ä€ ðà º ðtŸ UPDATED PREZO¡_0þ¦ù@ Hhˆ8¨ðÆ ð c ð$‚ƒ„ð@ \¤€ ðà º ðZŸ ¡0_þ¦ù@ Hhˆ8¨ð< ð c ð$ƒ¿ÿ ?ð ÿÿÿ€€€Ì™33ÌÌÌÿ²²²îÎï € ~ðv0ð ðð( ð ð ðî ð “ ð6‚ƒ„‡¿ÿðÀœdðà  º ðpŸ Speak lots¡ _ +þ¦ù@ Hhˆ8¨ðì ð ƒ ð0‚ƒ„¿ÿð€œdðà º ðtŸ About stuff¡ 0_ þ¦ù@ Hhˆ8¨ð< ð c ð$ƒ¿ÿ ?ð ÿÿÿ€€€Ì™33ÌÌÌÿ²²²r µY  õœ ãtö_À‘ã ôËGoogleþÿà…ŸòùOh«‘+'³Ù0Ø `h ¸ Ð Ü è ô¤°PowerPoint PresentationGoogleGoogle1@0BwèU@‘,p&÷áÑ•®XWÕöQ¥Ýet.û­Ï5¯Òš‰RylQ]JÒ*2þ©Ï”tVÛøc˜ïJjìä2ÐxÁÀ¼¼IĘÕäê# g3¦dã˜ÜIoj’¸¬¾€Æà¾@¬—’æo+IiãË­÷j(.Þ:BJB>Ö8$öü Æ-ð\$,œ½aáÛ¡#x,X<@V3s/•/¸5ÕCèVûp ´Ü'Rõ~Û¨¿Õ™ÿq$FFbéy¶‰š/£Çšg‰nÁLzU¸^þ^CU‰CØ$ñkȘú-üÿÿÿ-ðMicrosoft PowerPointþÿÕÍÕœ.“—+,ù®04ˆ¨°¸À È Ð Ø à l$,°Custom/  Fonts UsedDesign Template Slide TitlesArialTimes New RomanDefault DesignUPDATED PREZO Speak lots   gdata-2.0.18/tests/gdata_tests/docs/data/test.1.wmf0000640036466300116100000000532612156622363022102 0ustar afshareng00000000000000×ÍÆšNý‚ü*@­S Z²&ÿÿÿÿNý‚ü* & ÿÿÿÿ&$ÿÿÿÿTNPPMicrosoft PowerPoint & TNPPf & ÿÿÿÿ&TNPP ‚üNý ¨Ì/÷€€€€€€€€€ÀÀÀÀÜÀ¦Êðÿûð  ¤€€€ÿÿÿÿÿÿÿÿÿÿÿÿ45&ÿÿÿÿPý€ü,&ÿÿÿÿPý¿ý,,&ÿÿÿÿPý‹ÿ,,ü-úÿÿÿ-²$WôþÉëþ‹ÿÀþ’ÿmþ§ÿ;þÁÿ*þçÿGþ:JþRþnÞý’¸ýº†ýîeý'TýlPý¤PýõcýH‚ýu»ý®þçUþ»þ'ÿ,Vÿ$ˆÿÁÿÙàÿ§g +,å$žYîÿºÿÏÿ¥`ÿÿuòþlòþˆ$ÿ—Oÿ uÿµŸÿàÁÿÖÿ.îÿY— Ï üÿ5ìÿgÊÿ—«ÿ¼ŠÿÝ`ÿó;ÿÿÝþ •þPþçþÊãý§¸ýœý]ý3sýüqý³sýu‚ýA•ý »ýÝæýµþ™@þcþn€þg€þ]\þóÿ\þàÿtþÒÿ•þ¼ÿÀþ³ÿÑþ³ÿÖþúÿÊþ]»þ Öþº & ÿÿÿÿ&ÿÿÿÿ/þªþªþÄÿ"$ þ˜ÿ þpÿ“þ.ÿzþÿWþ¿þHþªþ/þ¸þ/þÉþAþæþ\þÿrþKÿuþtÿ}þµÿ“þÄÿªþ°ÿ & ÿÿÿÿ&ÿÿÿÿ“þ¯þÿöþ$ ÿñþÿÞþæþÁþ¸þ¯þŸþ¯þ“þ¾þšþÖþÅþæþÿöþ & ÿÿÿÿ&ÿÿÿÿxþñýØþlþ$ ÓþñýØþþ¼þ?þ¥þ]þ”þlþþlþxþ]þ|þFþ²þþ & ÿÿÿÿ&ÿÿÿÿþ¿ýTþGþ$ %þ¿ý=þæýKþþTþ6þFþGþ3þGþ+þ8þþþþëý & ÿÿÿÿ&ÿÿÿÿwý(þþýpþ$wý(þ¸ý0þçý=þþýOþöýgþçýpþÁýeþ & ÿÿÿÿ&ÿÿÿÿjý•þþýÿ$ qýáþýÀþ¾ý¢þèý•þùý•þþý«þñý»þÑýÃþ´ýÃþ‘ýÙþsýöþjýÿ & ÿÿÿÿ&ÿÿÿÿþéþ-þ8ÿ$þ8ÿþÿþöþþéþ-þøþ!þÿ & ÿÿÿÿ & ÿÿÿÿ&ÿÿÿÿ €ü&ÿÿÿÿ ‚þÅR$'\×þ³þ²þÖ‚þ„þ¥þìÂþÃ×þ^ÿý<ÿÕIÿÆ_ÿÕuÿ)ÆÿPàÿ‰ Ã8ÁM•TUT+j¡½ Åñ«ö}\?@s1x"^ï¾ÿ΢ÿ®|ÿ Qÿ©7ÿá&ÿ$ ÿPíþ & ÿÿÿÿ&ÿÿÿÿÕhþΓ,$ ’þ oþ;hþ`hþŽ‚þ«»þÁÿÎQÿηÿ³'ŒjX‹(“}æcÞ8Õçÿ܃ÿòÿÐþ & ÿÿÿÿ&ÿÿÿÿE­<$_ÄK•KagE‡Sž…ªÚ­E¥¡––~ª¯£Ç€ÏZðI ÷&ÝZÇ€·Œ©}€q/nÎqŽu7nè & ÿÿÿÿ&ÿÿÿÿRE5D$ ݇õZE5S0~œîéÑ2ÀÂÍÝ3ó•¿×õã¸ìŒvRôYÞ}Ð¬ÉØÉߺأ³F›ëŽ©Œk–0¬òΡ & ÿÿÿÿ&ÿÿÿÿ}ÐüžKþ0$æ þü<þ/KþZFþ~'þ›èýžžý”]ýkýNôü/ÞüÐüßÔüÅý·-ý·uýûýÑáýþ}0þ‰<þßûý & ÿÿÿÿ&ÿÿÿÿâ€üöþ`$.\3þ@Oþsþ͈þ¨žþƒºþìþ£öþýÁþ@ƒþŽ5þÎþÝýÊý²ýú‘ýš]ý@-ýÒûüªÞü¹üTŽü.€üâ´üææüõùüòüÜüÕüÿ»ü)ŸüM»üMÕü?ôüJ ý’ý¯ý<ý\_ý‡tý²ŠýÓ§ýéÃýÕÙý¨õýyþ & ÿÿÿÿ & ÿÿÿÿ & ÿÿÿÿ&TNPP & ÿÿÿÿ-ü-ðëÿéÿ×1 gdata-2.0.18/tests/gdata_tests/docs/data/test.0.ppt0000750036466300116100000003100012156622363022101 0ustar afshareng00000000000000ÐÏࡱá>þÿ þÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿýÿÿÿ þÿÿÿþÿÿÿþÿÿÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿRoot Entryÿÿÿÿÿÿÿÿd›Oφꪹ)è<ÑWƛɀ PowerPoint Document(ÿÿÿÿSummaryInformation(ÿÿÿÿÿÿÿÿ4 DocumentSummaryInformation8ÿÿÿÿÿÿÿÿÿÿÿÿ)ØèÕé(Àà€ ò`/È 0ÒÕ˜·DTimes New Romanl–l–ÐJŒôƒïF0ôƒÕ·DArialNew Romanl–l–ÐJŒôƒïF0ôƒÕ¤€@ÿÿ¥ .©  @£nÿý?" dd@ÿÿïÿÿÿÿÿÿ  @@``€€ ðˆð0ƒ ð0ƒ†A¿ÀÅAÿ@ñ÷ðó€ÐwdÅÝÊš;2NÍÉÊš;úgþý4pdpd„WŒ „ïF0üýÿÿ¦ÿÿÿpûppû@ <ý4!d!d8„@0l–¬JŒ<ý4dddd8„@0l–¬JŒÿ ˆ8Š0º___PPT10‹ ÀÀðÎóŸ¨test¡_0þª Ÿ¨test¡0_þª /ðóêøœï 0`ð ÿÿÿÿÿÿÿ™ÿÿÿ–––`ð ÿÿÿ€€€Ì™33ÌÌÌÿ²²²`ð ÿÿÿ333ÝÝÝ€€€MMMêêê`ð ÿÿÌff3€€3™3€3ÌÿÌf`ð ÿÿÿ€€€ÿÌfÿÌÌÀÀÀ`ð ÿÿÿ€€€ÀÀÀfÿÿ™`ð ÿÿÿ€€€3™ÿ™ÿÌÌ̲²²£>ÿý?" dd@ÿÿïÿÿÿÿÿÿ,£|ÿý?" ddØ@ÿÿïÿÿÿÿÿÿ € Ô €" Ð@€ ð`€»€ £nÿý?" dd@ÿÿïÿÿÿÿÿÿ   @@``€€P£R    @ ` €`£ p£>€£> ÖðÎððfð( ð ððÒ ð “ ð6€´”Œ‡ƒ¿Àÿ ðªà Ëðà Œ ðTŸ¨ Click to edit Master title style¢!ª !ð ð ƒ ð0€ —Œƒ¿Àÿ ðjà «ðà Œ 𞟨RClick to edit Master text styles Second level Third level Fourth level Fifth level¢!    ª Sð¶ ð ƒ ð0€lžŒƒ¿Àÿ ðàVðà Œ ð>Ÿ *¡øð¸ ð ƒ ð0€¼£Œƒ¿Àÿ ðŠvVðà  Œ ð@Ÿ *¡úð¸ ð ƒ ð0€l¨Œƒ¿Àÿ ðê!Vðà Œ ð@Ÿ *¡ØðH ð ƒ ð0ƒ“N ›”n^t¿ÿ ?ð ÿÿÿ€€€Ì™33ÌÌÌÿ²²² ºDefault Designðúñ€0 zðr0ð ð ð( ð ð ðÈ ð ƒ ð0€ÄFŒƒ¿Àÿ ðP ðà    ðPŸ *¡ ùª ðÊ ð ƒ ð0€H ƒ¿Àÿ ð ß ðà   ðRŸ *¡ øª ðd ð c ð$‡¿ÿ ?ð°Ð ðà  ð ð ƒ ð0€@N ƒ¿Àÿ ð° °0Ððà   𞟨RClick to edit Master text styles Second level Third level Fourth level Fifth level¢!    ª SðÎ ð “ ð6€T ‡ƒ¿Àÿ ð_Pðà    ðPŸ *¡ úª ðÐ ð “ ð6€[ ‡ƒ¿Àÿ ð_ ßðà   ðRŸ *¡ ت ðH ð ƒ ð0ƒ“Þ½h”ŽŸ‹¿ÿ ?ð ÿÿÿ€€€»àã33™™™™Ìˆ8Š0º___PPT10‹ë.Æ›É@èÙHî4ï€0 äðÜ ððtð( ð ðð ð £ ð<€$ߌ‚ƒ„‡¿ÿð€ä€ ðà Œ ð žð¤ ð “ ð6€ èŒ‚ƒ„¿ÿð@ \¤€ ðà Œ ð&ž¦àHhˆ8¨ðH ð ƒ ð0ƒ“N ›”n^t¿ÿ ?ð ÿÿÿ€€€Ì™33ÌÌÌÿ²²²ð ñ0 Œð„@ððð( ð ððX ð C ð¿ÿ ð°Ð ðà  ð„ ð S ð€XS ¿ÿ ð° °0Ððà   🪠ðH ð ƒ ð0ƒ“Þ½h”ŽŸ‹¿ÿ ?ð ÿÿÿ€€€»àã33™™™™Ìˆ8Š0º___PPT10‹ë.Æ›É0áHrP݃ ¿õ# ÓÅ1  !"#$%&'(þÿÿÿ*+,-./0þÿÿÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþÿà…ŸòùOh«‘+'³Ù0 `hˆ˜ ¨´ Ô à ìøäPowerPoint PresentationGoogleGoogle2Microsoft PowerPoint@à|øU@‘©Ci¼“Ø“\”ÉþÆC¡|D— ¤Å©¼¯¶¸³Ã³ÌJÒdÑpΆú¡¤®Œ²u®b¡Lv*Z!àРÿþ¸õ¯Ú­·›gq7½ší{”Füÿÿÿ---f$1²³p¾=×ñ¿4EÓa ¬Š – v ’, „ Ä1 Õ .%x©ÂÞîúS(r1¹<R3”!ËúÝTnƒÿ ’« [ Š5 ~ Ò» ÅŠ ¼+ Âú ÔÈ b 1õ L‚ P >º-Ž"{ö>Ѓæþ¸²³---â$oØYx~Žj¢[¶OÏ5 30 5? 9Q Kw €· Îù $ =¦ Á ù; Bš ~ Ëè ÝC ហؽ ÒÊ ÅÞ µï ¬ö ¡ý sj c[V0VB[Qc\qe€j‰j’g¦\ä)óþï î +ë :î `v©DºZÈn×âœñ£¥¡&˜-y&SB;ø,éý ¡ö  ï ¡ë ¯â ÃÞ ÌÞ ×à âå ýø 8 D#N Zdm vï ‚ç }Ü rÐ ^à 4± ­Ž ‚ rv dk TY EA ( × ä –J OÉ % ò[ 8¹  M q Vû&xfYÿUíUØY---Ê$c/Îðál/¯Ø>^ µ § Y / W ô  » b ³ Ï ¸ -Ú $ç cç sä Ú ›¦ Ð Þz +f kd }d  t Þx é~ ò… ùŽ ý¤ ÿ¸ ûÈ òÌ ìÎ ãÒ ·Õ «Ú ¥à  ç ž œ “ ) S+ 2) $"  ë% ä8 ßV ä„ ñ˜ ú¡ · "Ä +é 2þ -+ $øÞÐż÷ ³ ‹ S TD 7= $/ Ô/ î Q ö  I Ó i <© jf ¼ !® ‹c Î? , v ‘ ¥ ´ûÀëÉÙÏÈØfããàóÚáÏ׽ΌÇpÇ/Î---l$4:ï3ÌGj‹Kª.Õ÷SÍì¶² ˽ÜÿnI²_ÆŽåÅý CbžÜû0ÑZ®iœr+ÓyeWH5ëñàáÓÅ̦·×eàSû3.ýH×pŠ~b„8Þl›Vx%IÚ´b:---¶$Yµ\ÀMÇ9É0Ç%ü”ކƂ»‚²€¢€—­¥¦˜\fÑT«#`¡Xl :ç.¦%—%‡(z.dM[c[|m¸™ø©.Z®ç7¯KîXzK2>\1r†Ü®½Î²Þ­í°ý¶ ÂÈÏØã ÜÜìå7áBÜSÜaáiêrðw €‚#€){.u5O;DTíZé_çcégðlûo w+y;~J‚S‹a˜h¤i­eµ\---¾$]“dþ¡ùaþùò‚ÉQï/-;Ht)°Ut='î@ U5UGSPOW@d.mqqoÞbÕ`Î`Äd¸m¯ƒ®¯ »´Ķζô±0¤H¤L¨L¯J¶F¿.à"ë û '%-22A2`'p±ԉÜmáRáEÞ:Ú1Ô(§íU¡ÕÞ¿¢»ˆ»pÄWÕ<í$7ñ᪛K”ª›ìªIÙoä߆ۜ¿»jÁ5½$´¥ “---z$;~­n•Qv)T<í5Þ3Î8´K“«zÃqûoÈ©—«üåv‡ŠÉ‡Ö}óW/N:IEC^HgPoYtbz™Œ´Ìù‡zg![!RI?/ ÿ öóôæöÖûÅ2PA#HùD„4 7øQ›l&”¥”6†Æ~­---ˆ$B|™c×!ì®ûvÍ„µ›¬Å“æ„õn=«Œq g&b-b8dCmP}^‡d“mÑŠãŽòŽŠQgb`t[ž[ kh!d*^,W.N.)*óèÛÍGyQjp‡½˜$›¡Ü¦ÎÒLgQ^)o dëWÙTÅW±]ŸjŽ|™---v$9Ù€ äŸ ì¬ ó± ú³ µ /¾ EË NØ Pá Pí E" /f )’ -å 3 LU^wt“ެÚÑ!áSá¬Ñµ9š…cÖ óÜ v / í ï‚ Ûe ÀK ¡5 H çû ¹û _ "% . ¿a ¦j “j Še vV `< Y7 R5 C3 15 9 I äc Ûq Ù€ ---~$=˜Ý˜Ç”œˆuh0;÷ÿʸ©œd•“Зm²áû˜>Z‹S–F·DÂ/ ö++B½_èqûµ.åK/dxhòRq®ìÞ¹é°ÿ¥ ¥'²OÓhãòõªò·ìÀãÉØÍÉÏ·É‹Ày°p‚gn^\QMAB/@%@B yŒ˜Ý---t$8ûAã=•?Š;ƒ70z"yv R»hâ@Ó6±&j÷Ϭ*l[O}7¢í  }ß BX[zw–š¯¯ºöÒ[àÃ×õdz-¦<”J€ ŠÒɕž½«»åÀòÀû½¹ ³¡{\ MûA---|$<r×x°‹—ß)»Xžk J 6{NFc’ÙÍWþŒ7¹~ÞÏýJ m ­ è ô6ÕJÆk †v˜G¢§ê¥¼˜zƒKp3C +ûÙѤ«Ž“…†yxWx2Œâ•Ñ¢¿à|úV+ù<ÇC˜<j/;æÈ¢Žz}Ntr---ž$M½vÎpÚn@rJp\il`rWrQpMlFZ82%!""ÿ%í+`ê'„ Ž÷œß´ØÂÔÏÒÜÔôØß ö#5&;KBbD’BÇ9ú'Z帎ź`Kæ÷èåå ê#ñ# $)9*[>d@k>q9v0}(þ“ù—÷©´ » ¿ ×ÞòàìàåÜÜÕÓ¯¥©œ§”©†¯}½v---Ø$j9)0 'ù÷ùýæÕ0Î>ÌKÎXÓeÜuæ‚ø¡ü±ü¾òÚæèÜø…>AbúuXŒŠðÆo Yy:d&ô†‘i÷TëAç,çëØÅµ0ª?¡Qše–|””™ÂÖ¦ê°ü¾ •“ ÌÓö4ýð/¶Õ_ëPÿII LR¢­¶$¼=È`ÑkÑÌ“Á™º ²¥©¥ qtPY&UW ^kÄÑ!Ü!åìôùêùÙôÍçÄÈ»ŠºdµQ±A¨:£6›2€4p4]999)---¢$O5ãã÷éɵ£*‹X‚q{¬‰¦x©æÕø( Y ‘ + ÑÑ/ÓYçmûx z z' x5 i\ a{ [— Tº NÉ EÕ ÿ  - H ` i r y +} Gƒ L• ½© à® í® ÿ© § ¢ • ÿi öJ ã Ü Ñ ½ ô Ðý Êø Çô Æè ÆÙ ÊÈ å— ë… ?Í N“ Q# Hë6º+¤ö`¯-;„°þ[è5ã---v$9ºÿ,à¯Ý=øƒ\EŠ¿¶6 oÉ 1ƒ U ö‚ / ,à iFÏÖ LD™j0£› ™`‹ƒ~Ã]$JÑYµx^ƒ'†ø }Ì Yv ?N ô —n Tß KÀ EŠ GZ T+ ä •Î V ` —² ²y ½D ¿ ¶Ü¯Ã™—}oULöºÿ---¾$]˜„ Z¡ B¸ ÷ ãP ı ¿à Á Æ#åa'µî‹ù_ñlw~&~<yTnqM­xàC#öaÜn¨€[A‘°~‡€w‡lcš\§XµXÅZÖaål÷Ø[ís «5ž8Â+X+w/”:¨Kµd¾ƒÃ¢Ã⺨'9ŽWf`Pk#gQÞà`ÐH«ò’N™(Ÿ« ºþÌ÷4èRßÁ¦¯Ekx'žæ«Êº’¾`±è¤ÅxwocÈ˜Ç f¡ N’ ~ úz Ûz ˜„ --- $NlÒ V ;¹ ¹ åË «ï v [: ­ ×0¿Š«P¨ÞíÃàÆí¨7$>í¥îlæÔÝÉ1¹HmD¢.¸*Á%Ñ%ã0<I"[0n7‡;`!›"´'Öy‚@{‚X¦;Ö ßÿåôêéêàäÕÖÌÎÈ€¯²b•Á­ ­ˆ¦d—WŽNJoJbKU]®®J⑳ÀŠäÛ ·o ” ‚í wÝ lÒ ---¬$Tܾ Àk ·½gl¾ÜÉ^Ʉť³Þ~FF Ü&© ˜ùÔìï®3†[{mvvtt{¢ƒ²¡Ñ²ÜÉäßéùéä~ÅŸ丿̅!¸'í)%!W‚ý“òÀÄʲ܃ßkß^ÚRÓI»8§3H1Î<¬<‰3{1q/j*e$aahëYq­2Ì„ýäöýÙð—¤ o- Pê <É óh ¼0 ‡ n X E 0 0 I ܾ ---¸$ZªÐ Œß rö W z ðÁ ÛŠQz&~ƒx¸dåC1ÐC’O³ Xë w:  }Ô r¾ g– Fa R @ ÿ  ì ß 'Ï CÍ SÓ wì ¶ù ç& Æ# û zñ «ñ Á÷ Õ ä# n + 9Ò Aï : -$ 7 ü< ï? à< Ð- «ü ]ì <ã /Ð úÍ èÍ cÝ +ì õ   ! I J pã yOrèS@0³ì×Ά\#·Y0šxÉ#¼¢ [ e #è × àË ÑÉ ÄÉ ·Ë ªÐ -üšƒV--R$'ë!9 é!. æ!% â! à! ×! Å! ¯! Ÿ! ! `!. R!9 B!T A!^ A!j B!y B!‹ E!¡ B!Ñ ¡ ¶ˆ‡Y¦SãSíUöXýbir}„ˆš ò²©!Ð!Y ë!9 -ðüµ¢{-->$é!›Ì!‘­!ŠŽ!Šo!‘P! 3!¶ !Þ!ëû  û ' !F !a !w *!‹ A!š T!¥ h!ª Ü!¥ "š " :"e C"O J"' H" ?"û"Æ"­é!›-ð--ð$vÓ!° Å!¡ ´!• Ÿ! ‰! T!š >!¢ "!¹ !Ï !Ø ® * Z X u Ê‚ Ý { ás \I í ¢Ú ² k‚ ^H [ÿ þ ™ ˆ´qŒUk6P=´+Œ-j4PA5Wxóàê ßF êÀ N ‘ Ð øB N~ ¢¡ §ä ó Ÿ÷ –í Óá  Ý 4 ß F æ V ï n  & ‹ 4 Ö m !ˆ !Œ /!‘ I!Ž `!ˆ q! |!r €!b €!P !A v!$ j! X! 9!ü ú è Ø Û Í Ô É Î Æ Ç É À Í ¹ Ü ¯ î ª ú ¬ !± N!Î ‡!Û ±!× ¾!Î Ç!À Ê!¬ Ê!™ Á!w º!h ­![ ¦!V ›!R Ž!O h!O >!T !T þ O ó F ñ = ÷ 5 !, !* "!% X! ­! º! Ã! Ê! Ð!þ Ù!ç Ý!Ñ Ü!À Ó!° -gdata-2.0.18/tests/gdata_tests/contentforshopping_test.py0000640036466300116100000000707412156622363023754 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright 2009 Google Inc. All Rights Reserved. # # 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. """Content API for Shopping tests""" __author__ = 'afshar (Ali Afshar)' import unittest from gdata.contentforshopping import client, data class CFSClientTest(unittest.TestCase): def test_uri_missing_account_id(self): c = client.ContentForShoppingClient() self.assertRaises(ValueError, c._create_uri, account_id=None, projection=None, resource='a/b') def test_uri_bad_projection(self): c = client.ContentForShoppingClient() self.assertRaises(ValueError, c._create_uri, account_id='123', projection='banana', resource='a/b') def test_good_default_account_id(self): c = client.ContentForShoppingClient(account_id='123') uri = c._create_uri(account_id=None, projection=None, resource='a/b') self.assertEqual(uri, 'https://content.googleapis.com/content/v1/123/a/b/generic') def test_override_request_account_id(self): c = client.ContentForShoppingClient(account_id='123') uri = c._create_uri(account_id='321', projection=None, resource='a/b') self.assertEqual(uri, 'https://content.googleapis.com/content/v1/321/a/b/generic') def test_default_projection(self): c = client.ContentForShoppingClient(account_id='123') uri = c._create_uri(account_id=None, projection=None, resource='a/b') self.assertEqual(c.cfs_projection, 'generic') self.assertEqual(uri, 'https://content.googleapis.com/content/v1/123/a/b/generic') def test_default_projection_change(self): c = client.ContentForShoppingClient(account_id='123', projection='schema') uri = c._create_uri(account_id=None, projection=None, resource='a/b') self.assertEqual(c.cfs_projection, 'schema') self.assertEqual(uri, 'https://content.googleapis.com/content/v1/123/a/b/schema') def test_request_projection(self): c = client.ContentForShoppingClient(account_id='123') uri = c._create_uri(account_id=None, projection='schema', resource='a/b') self.assertEqual(c.cfs_projection, 'generic') self.assertEqual(uri, 'https://content.googleapis.com/content/v1/123/a/b/schema') def test_request_resource(self): c = client.ContentForShoppingClient(account_id='123') uri = c._create_uri(account_id=None, projection=None, resource='x/y/z') self.assertEqual(uri, 'https://content.googleapis.com/content/v1/123/x/y/z/generic') def test_path_single(self): c = client.ContentForShoppingClient(account_id='123') uri = c._create_uri(account_id=None, projection=None, resource='r', path=['1']) self.assertEqual(uri, 'https://content.googleapis.com/content/v1/123/r/generic/1') def test_path_multiple(self): c = client.ContentForShoppingClient(account_id='123') uri = c._create_uri(account_id=None, projection=None, resource='r', path=['1', '2']) self.assertEqual(uri, 'https://content.googleapis.com/content/v1/123/r/generic/1/2') if __name__ == '__main__': unittest.main() gdata-2.0.18/tests/gdata_tests/youtube_test.py0000750036466300116100000006164312156622363021523 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright (C) 2006 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. __author__ = 'api.jhartmann@gmail.com (Jochen Hartmann)' import unittest from gdata import test_data import gdata.youtube import gdata.youtube.service import atom YOUTUBE_TEMPLATE = '{http://gdata.youtube.com/schemas/2007}%s' YT_FORMAT = YOUTUBE_TEMPLATE % ('format') class VideoEntryTest(unittest.TestCase): def setUp(self): self.video_feed = gdata.youtube.YouTubeVideoFeedFromString( test_data.YOUTUBE_VIDEO_FEED) def testCorrectXmlParsing(self): self.assertEquals(self.video_feed.id.text, 'http://gdata.youtube.com/feeds/api/standardfeeds/top_rated') self.assertEquals(len(self.video_feed.entry), 2) for entry in self.video_feed.entry: if (entry.id.text == 'http://gdata.youtube.com/feeds/api/videos/C71ypXYGho8'): self.assertEquals(entry.published.text, '2008-03-20T10:17:27.000-07:00') self.assertEquals(entry.updated.text, '2008-05-14T04:26:37.000-07:00') self.assertEquals(entry.category[0].scheme, 'http://gdata.youtube.com/schemas/2007/keywords.cat') self.assertEquals(entry.category[0].term, 'karyn') self.assertEquals(entry.category[1].scheme, 'http://gdata.youtube.com/schemas/2007/keywords.cat') self.assertEquals(entry.category[1].term, 'garcia') self.assertEquals(entry.category[2].scheme, 'http://gdata.youtube.com/schemas/2007/keywords.cat') self.assertEquals(entry.category[2].term, 'me') self.assertEquals(entry.category[3].scheme, 'http://schemas.google.com/g/2005#kind') self.assertEquals(entry.category[3].term, 'http://gdata.youtube.com/schemas/2007#video') self.assertEquals(entry.title.text, 'Me odeio por te amar - KARYN GARCIA') self.assertEquals(entry.content.text, 'http://www.karyngarcia.com.br') self.assertEquals(entry.link[0].rel, 'alternate') self.assertEquals(entry.link[0].href, 'http://www.youtube.com/watch?v=C71ypXYGho8') self.assertEquals(entry.link[1].rel, 'http://gdata.youtube.com/schemas/2007#video.related') self.assertEquals(entry.link[1].href, 'http://gdata.youtube.com/feeds/api/videos/C71ypXYGho8/related') self.assertEquals(entry.link[2].rel, 'self') self.assertEquals(entry.link[2].href, ('http://gdata.youtube.com/feeds/api/standardfeeds' '/top_rated/C71ypXYGho8')) self.assertEquals(entry.author[0].name.text, 'TvKarynGarcia') self.assertEquals(entry.author[0].uri.text, 'http://gdata.youtube.com/feeds/api/users/tvkaryngarcia') self.assertEquals(entry.media.title.text, 'Me odeio por te amar - KARYN GARCIA') self.assertEquals(entry.media.description.text, 'http://www.karyngarcia.com.br') self.assertEquals(entry.media.keywords.text, 'amar, boyfriend, garcia, karyn, me, odeio, por, te') self.assertEquals(entry.media.duration.seconds, '203') self.assertEquals(entry.media.category[0].label, 'Music') self.assertEquals(entry.media.category[0].scheme, 'http://gdata.youtube.com/schemas/2007/categories.cat') self.assertEquals(entry.media.category[0].text, 'Music') self.assertEquals(entry.media.category[1].label, 'test111') self.assertEquals(entry.media.category[1].scheme, 'http://gdata.youtube.com/schemas/2007/developertags.cat') self.assertEquals(entry.media.category[1].text, 'test111') self.assertEquals(entry.media.category[2].label, 'test222') self.assertEquals(entry.media.category[2].scheme, 'http://gdata.youtube.com/schemas/2007/developertags.cat') self.assertEquals(entry.media.category[2].text, 'test222') self.assertEquals(entry.media.content[0].url, 'http://www.youtube.com/v/C71ypXYGho8') self.assertEquals(entry.media.content[0].type, 'application/x-shockwave-flash') self.assertEquals(entry.media.content[0].medium, 'video') self.assertEquals( entry.media.content[0].extension_attributes['isDefault'], 'true') self.assertEquals( entry.media.content[0].extension_attributes['expression'], 'full') self.assertEquals( entry.media.content[0].extension_attributes['duration'], '203') self.assertEquals( entry.media.content[0].extension_attributes[YT_FORMAT], '5') self.assertEquals(entry.media.content[1].url, ('rtsp://rtsp2.youtube.com/ChoLENy73wIaEQmPhgZ2pXK9CxMYDSANFEgGDA' '==/0/0/0/video.3gp')) self.assertEquals(entry.media.content[1].type, 'video/3gpp') self.assertEquals(entry.media.content[1].medium, 'video') self.assertEquals( entry.media.content[1].extension_attributes['expression'], 'full') self.assertEquals( entry.media.content[1].extension_attributes['duration'], '203') self.assertEquals( entry.media.content[1].extension_attributes[YT_FORMAT], '1') self.assertEquals(entry.media.content[2].url, ('rtsp://rtsp2.youtube.com/ChoLENy73wIaEQmPhgZ2pXK9CxMYESARFEgGDA==' '/0/0/0/video.3gp')) self.assertEquals(entry.media.content[2].type, 'video/3gpp') self.assertEquals(entry.media.content[2].medium, 'video') self.assertEquals( entry.media.content[2].extension_attributes['expression'], 'full') self.assertEquals( entry.media.content[2].extension_attributes['duration'], '203') self.assertEquals( entry.media.content[2].extension_attributes[YT_FORMAT], '6') self.assertEquals(entry.media.player.url, 'http://www.youtube.com/watch?v=C71ypXYGho8') self.assertEquals(entry.media.thumbnail[0].url, 'http://img.youtube.com/vi/C71ypXYGho8/2.jpg') self.assertEquals(entry.media.thumbnail[0].height, '97') self.assertEquals(entry.media.thumbnail[0].width, '130') self.assertEquals(entry.media.thumbnail[0].extension_attributes['time'], '00:01:41.500') self.assertEquals(entry.media.thumbnail[1].url, 'http://img.youtube.com/vi/C71ypXYGho8/1.jpg') self.assertEquals(entry.media.thumbnail[1].height, '97') self.assertEquals(entry.media.thumbnail[1].width, '130') self.assertEquals(entry.media.thumbnail[1].extension_attributes['time'], '00:00:50.750') self.assertEquals(entry.media.thumbnail[2].url, 'http://img.youtube.com/vi/C71ypXYGho8/3.jpg') self.assertEquals(entry.media.thumbnail[2].height, '97') self.assertEquals(entry.media.thumbnail[2].width, '130') self.assertEquals(entry.media.thumbnail[2].extension_attributes['time'], '00:02:32.250') self.assertEquals(entry.media.thumbnail[3].url, 'http://img.youtube.com/vi/C71ypXYGho8/0.jpg') self.assertEquals(entry.media.thumbnail[3].height, '240') self.assertEquals(entry.media.thumbnail[3].width, '320') self.assertEquals(entry.media.thumbnail[3].extension_attributes['time'], '00:01:41.500') self.assertEquals(entry.statistics.view_count, '138864') self.assertEquals(entry.statistics.favorite_count, '2474') self.assertEquals(entry.rating.min, '1') self.assertEquals(entry.rating.max, '5') self.assertEquals(entry.rating.num_raters, '4626') self.assertEquals(entry.rating.average, '4.95') self.assertEquals(entry.comments.feed_link[0].href, ('http://gdata.youtube.com/feeds/api/videos/' 'C71ypXYGho8/comments')) self.assertEquals(entry.comments.feed_link[0].count_hint, '27') self.assertEquals(entry.GetSwfUrl(), 'http://www.youtube.com/v/C71ypXYGho8') self.assertEquals(entry.GetYouTubeCategoryAsString(), 'Music') class VideoEntryPrivateTest(unittest.TestCase): def setUp(self): self.entry = gdata.youtube.YouTubeVideoEntryFromString( test_data.YOUTUBE_ENTRY_PRIVATE) def testCorrectXmlParsing(self): self.assert_(isinstance(self.entry, gdata.youtube.YouTubeVideoEntry)) self.assert_(self.entry.media.private) class VideoFeedTest(unittest.TestCase): def setUp(self): self.feed = gdata.youtube.YouTubeVideoFeedFromString( test_data.YOUTUBE_VIDEO_FEED) def testCorrectXmlParsing(self): self.assertEquals(self.feed.id.text, 'http://gdata.youtube.com/feeds/api/standardfeeds/top_rated') self.assertEquals(self.feed.generator.text, 'YouTube data API') self.assertEquals(self.feed.generator.uri, 'http://gdata.youtube.com/') self.assertEquals(len(self.feed.author), 1) self.assertEquals(self.feed.author[0].name.text, 'YouTube') self.assertEquals(len(self.feed.category), 1) self.assertEquals(self.feed.category[0].scheme, 'http://schemas.google.com/g/2005#kind') self.assertEquals(self.feed.category[0].term, 'http://gdata.youtube.com/schemas/2007#video') self.assertEquals(self.feed.items_per_page.text, '25') self.assertEquals(len(self.feed.link), 4) self.assertEquals(self.feed.link[0].href, 'http://www.youtube.com/browse?s=tr') self.assertEquals(self.feed.link[0].rel, 'alternate') self.assertEquals(self.feed.link[1].href, 'http://gdata.youtube.com/feeds/api/standardfeeds/top_rated') self.assertEquals(self.feed.link[1].rel, 'http://schemas.google.com/g/2005#feed') self.assertEquals(self.feed.link[2].href, ('http://gdata.youtube.com/feeds/api/standardfeeds/top_rated?' 'start-index=1&max-results=25')) self.assertEquals(self.feed.link[2].rel, 'self') self.assertEquals(self.feed.link[3].href, ('http://gdata.youtube.com/feeds/api/standardfeeds/top_rated?' 'start-index=26&max-results=25')) self.assertEquals(self.feed.link[3].rel, 'next') self.assertEquals(self.feed.start_index.text, '1') self.assertEquals(self.feed.title.text, 'Top Rated') self.assertEquals(self.feed.total_results.text, '100') self.assertEquals(self.feed.updated.text, '2008-05-14T02:24:07.000-07:00') self.assertEquals(len(self.feed.entry), 2) class YouTubePlaylistFeedTest(unittest.TestCase): def setUp(self): self.feed = gdata.youtube.YouTubePlaylistFeedFromString( test_data.YOUTUBE_PLAYLIST_FEED) def testCorrectXmlParsing(self): self.assertEquals(len(self.feed.entry), 1) self.assertEquals( self.feed.category[0].scheme, 'http://schemas.google.com/g/2005#kind') self.assertEquals(self.feed.category[0].term, 'http://gdata.youtube.com/schemas/2007#playlistLink') class YouTubePlaylistEntryTest(unittest.TestCase): def setUp(self): self.feed = gdata.youtube.YouTubePlaylistFeedFromString( test_data.YOUTUBE_PLAYLIST_FEED) def testCorrectXmlParsing(self): for entry in self.feed.entry: self.assertEquals(entry.category[0].scheme, 'http://schemas.google.com/g/2005#kind') self.assertEquals(entry.category[0].term, 'http://gdata.youtube.com/schemas/2007#playlistLink') self.assertEquals(entry.description.text, 'My new playlist Description') self.assertEquals(entry.feed_link[0].href, 'http://gdata.youtube.com/feeds/playlists/8BCDD04DE8F771B2') self.assertEquals(entry.feed_link[0].rel, 'http://gdata.youtube.com/schemas/2007#playlist') class YouTubePlaylistVideoFeedTest(unittest.TestCase): def setUp(self): self.feed = gdata.youtube.YouTubePlaylistVideoFeedFromString( test_data.YOUTUBE_PLAYLIST_VIDEO_FEED) def testCorrectXmlParsing(self): self.assertEquals(len(self.feed.entry), 1) self.assertEquals(self.feed.category[0].scheme, 'http://schemas.google.com/g/2005#kind') self.assertEquals(self.feed.category[0].term, 'http://gdata.youtube.com/schemas/2007#playlist') self.assertEquals(self.feed.category[1].scheme, 'http://gdata.youtube.com/schemas/2007/tags.cat') self.assertEquals(self.feed.category[1].term, 'videos') self.assertEquals(self.feed.category[2].scheme, 'http://gdata.youtube.com/schemas/2007/tags.cat') self.assertEquals(self.feed.category[2].term, 'python') class YouTubePlaylistVideoEntryTest(unittest.TestCase): def setUp(self): self.feed = gdata.youtube.YouTubePlaylistVideoFeedFromString( test_data.YOUTUBE_PLAYLIST_VIDEO_FEED) def testCorrectXmlParsing(self): self.assertEquals(len(self.feed.entry), 1) for entry in self.feed.entry: self.assertEquals(entry.position.text, '1') class YouTubeVideoCommentFeedTest(unittest.TestCase): def setUp(self): self.feed = gdata.youtube.YouTubeVideoCommentFeedFromString( test_data.YOUTUBE_COMMENT_FEED) def testCorrectXmlParsing(self): self.assertEquals(len(self.feed.category), 1) self.assertEquals(self.feed.category[0].scheme, 'http://schemas.google.com/g/2005#kind') self.assertEquals(self.feed.category[0].term, 'http://gdata.youtube.com/schemas/2007#comment') self.assertEquals(len(self.feed.link), 4) self.assertEquals(self.feed.link[0].rel, 'related') self.assertEquals(self.feed.link[0].href, 'http://gdata.youtube.com/feeds/videos/2Idhz9ef5oU') self.assertEquals(self.feed.link[1].rel, 'alternate') self.assertEquals(self.feed.link[1].href, 'http://www.youtube.com/watch?v=2Idhz9ef5oU') self.assertEquals(self.feed.link[2].rel, 'http://schemas.google.com/g/2005#feed') self.assertEquals(self.feed.link[2].href, 'http://gdata.youtube.com/feeds/videos/2Idhz9ef5oU/comments') self.assertEquals(self.feed.link[3].rel, 'self') self.assertEquals(self.feed.link[3].href, ('http://gdata.youtube.com/feeds/videos/2Idhz9ef5oU/comments?' 'start-index=1&max-results=25')) self.assertEquals(len(self.feed.entry), 3) class YouTubeVideoCommentEntryTest(unittest.TestCase): def setUp(self): self.feed = gdata.youtube.YouTubeVideoCommentFeedFromString( test_data.YOUTUBE_COMMENT_FEED) def testCorrectXmlParsing(self): self.assertEquals(len(self.feed.entry), 3) self.assert_(isinstance(self.feed.entry[0], gdata.youtube.YouTubeVideoCommentEntry)) for entry in self.feed.entry: if (entry.id.text == ('http://gdata.youtube.com/feeds/videos/' '2Idhz9ef5oU/comments/91F809A3DE2EB81B')): self.assertEquals(entry.category[0].scheme, 'http://schemas.google.com/g/2005#kind') self.assertEquals(entry.category[0].term, 'http://gdata.youtube.com/schemas/2007#comment') self.assertEquals(entry.link[0].href, 'http://gdata.youtube.com/feeds/videos/2Idhz9ef5oU') self.assertEquals(entry.link[0].rel, 'related') self.assertEquals(entry.content.text, 'test66') class YouTubeVideoSubscriptionFeedTest(unittest.TestCase): def setUp(self): self.feed = gdata.youtube.YouTubeSubscriptionFeedFromString( test_data.YOUTUBE_SUBSCRIPTION_FEED) def testCorrectXmlParsing(self): self.assertEquals(len(self.feed.category), 1) self.assertEquals(self.feed.category[0].scheme, 'http://schemas.google.com/g/2005#kind') self.assertEquals(self.feed.category[0].term, 'http://gdata.youtube.com/schemas/2007#subscription') self.assertEquals(len(self.feed.link), 4) self.assertEquals(self.feed.link[0].rel, 'related') self.assertEquals(self.feed.link[0].href, 'http://gdata.youtube.com/feeds/users/andyland74') self.assertEquals(self.feed.link[1].rel, 'alternate') self.assertEquals(self.feed.link[1].href, 'http://www.youtube.com/profile_subscriptions?user=andyland74') self.assertEquals(self.feed.link[2].rel, 'http://schemas.google.com/g/2005#feed') self.assertEquals(self.feed.link[2].href, 'http://gdata.youtube.com/feeds/users/andyland74/subscriptions') self.assertEquals(self.feed.link[3].rel, 'self') self.assertEquals(self.feed.link[3].href, ('http://gdata.youtube.com/feeds/users/andyland74/subscriptions?' 'start-index=1&max-results=25')) self.assertEquals(len(self.feed.entry), 1) class YouTubeVideoSubscriptionEntryTest(unittest.TestCase): def setUp(self): self.feed = gdata.youtube.YouTubeSubscriptionFeedFromString( test_data.YOUTUBE_SUBSCRIPTION_FEED) def testCorrectXmlParsing(self): for entry in self.feed.entry: self.assertEquals(len(entry.category), 2) self.assertEquals(entry.category[0].scheme, 'http://gdata.youtube.com/schemas/2007/subscriptiontypes.cat') self.assertEquals(entry.category[0].term, 'channel') self.assertEquals(entry.category[1].scheme, 'http://schemas.google.com/g/2005#kind') self.assertEquals(entry.category[1].term, 'http://gdata.youtube.com/schemas/2007#subscription') self.assertEquals(len(entry.link), 3) self.assertEquals(entry.link[0].href, 'http://gdata.youtube.com/feeds/users/andyland74') self.assertEquals(entry.link[0].rel, 'related') self.assertEquals(entry.link[1].href, 'http://www.youtube.com/profile_videos?user=NBC') self.assertEquals(entry.link[1].rel, 'alternate') self.assertEquals(entry.link[2].href, ('http://gdata.youtube.com/feeds/users/andyland74/subscriptions/' 'd411759045e2ad8c')) self.assertEquals(entry.link[2].rel, 'self') self.assertEquals(len(entry.feed_link), 1) self.assertEquals(entry.feed_link[0].href, 'http://gdata.youtube.com/feeds/api/users/nbc/uploads') self.assertEquals(entry.feed_link[0].rel, 'http://gdata.youtube.com/schemas/2007#user.uploads') self.assertEquals(entry.username.text, 'NBC') class YouTubeVideoResponseFeedTest(unittest.TestCase): def setUp(self): self.feed = gdata.youtube.YouTubeVideoFeedFromString( test_data.YOUTUBE_VIDEO_RESPONSE_FEED) def testCorrectXmlParsing(self): self.assertEquals(len(self.feed.category), 1) self.assertEquals(self.feed.category[0].scheme, 'http://schemas.google.com/g/2005#kind') self.assertEquals(self.feed.category[0].term, 'http://gdata.youtube.com/schemas/2007#video') self.assertEquals(len(self.feed.link), 4) self.assertEquals(self.feed.link[0].href, 'http://gdata.youtube.com/feeds/videos/2c3q9K4cHzY') self.assertEquals(self.feed.link[0].rel, 'related') self.assertEquals(self.feed.link[1].href, 'http://www.youtube.com/video_response_view_all?v=2c3q9K4cHzY') self.assertEquals(self.feed.link[1].rel, 'alternate') self.assertEquals(self.feed.link[2].href, 'http://gdata.youtube.com/feeds/videos/2c3q9K4cHzY/responses') self.assertEquals(self.feed.link[2].rel, 'http://schemas.google.com/g/2005#feed') self.assertEquals(self.feed.link[3].href, ('http://gdata.youtube.com/feeds/videos/2c3q9K4cHzY/responses?' 'start-index=1&max-results=25')) self.assertEquals(self.feed.link[3].rel, 'self') self.assertEquals(len(self.feed.entry), 1) class YouTubeVideoResponseEntryTest(unittest.TestCase): def setUp(self): self.feed = gdata.youtube.YouTubeVideoFeedFromString( test_data.YOUTUBE_VIDEO_RESPONSE_FEED) def testCorrectXmlParsing(self): for entry in self.feed.entry: self.assert_(isinstance(entry, gdata.youtube.YouTubeVideoEntry)) class YouTubeContactFeed(unittest.TestCase): def setUp(self): self.feed = gdata.youtube.YouTubeContactFeedFromString( test_data.YOUTUBE_CONTACTS_FEED) def testCorrectXmlParsing(self): self.assertEquals(len(self.feed.entry), 2) self.assertEquals(self.feed.category[0].scheme, 'http://schemas.google.com/g/2005#kind') self.assertEquals(self.feed.category[0].term, 'http://gdata.youtube.com/schemas/2007#friend') class YouTubeContactEntry(unittest.TestCase): def setUp(self): self.feed= gdata.youtube.YouTubeContactFeedFromString( test_data.YOUTUBE_CONTACTS_FEED) def testCorrectXmlParsing(self): for entry in self.feed.entry: if (entry.id.text == ('http://gdata.youtube.com/feeds/users/' 'apitestjhartmann/contacts/testjfisher')): self.assertEquals(entry.username.text, 'testjfisher') self.assertEquals(entry.status.text, 'pending') class YouTubeUserEntry(unittest.TestCase): def setUp(self): self.feed = gdata.youtube.YouTubeUserEntryFromString( test_data.YOUTUBE_PROFILE) def testCorrectXmlParsing(self): self.assertEquals(self.feed.author[0].name.text, 'andyland74') self.assertEquals(self.feed.books.text, 'Catch-22') self.assertEquals(self.feed.category[0].scheme, 'http://gdata.youtube.com/schemas/2007/channeltypes.cat') self.assertEquals(self.feed.category[0].term, 'Standard') self.assertEquals(self.feed.category[1].scheme, 'http://schemas.google.com/g/2005#kind') self.assertEquals(self.feed.category[1].term, 'http://gdata.youtube.com/schemas/2007#userProfile') self.assertEquals(self.feed.company.text, 'Google') self.assertEquals(self.feed.gender.text, 'm') self.assertEquals(self.feed.hobbies.text, 'Testing YouTube APIs') self.assertEquals(self.feed.hometown.text, 'Somewhere') self.assertEquals(len(self.feed.feed_link), 6) self.assertEquals(self.feed.feed_link[0].count_hint, '4') self.assertEquals(self.feed.feed_link[0].href, 'http://gdata.youtube.com/feeds/users/andyland74/favorites') self.assertEquals(self.feed.feed_link[0].rel, 'http://gdata.youtube.com/schemas/2007#user.favorites') self.assertEquals(self.feed.feed_link[1].count_hint, '1') self.assertEquals(self.feed.feed_link[1].href, 'http://gdata.youtube.com/feeds/users/andyland74/contacts') self.assertEquals(self.feed.feed_link[1].rel, 'http://gdata.youtube.com/schemas/2007#user.contacts') self.assertEquals(self.feed.feed_link[2].count_hint, '0') self.assertEquals(self.feed.feed_link[2].href, 'http://gdata.youtube.com/feeds/users/andyland74/inbox') self.assertEquals(self.feed.feed_link[2].rel, 'http://gdata.youtube.com/schemas/2007#user.inbox') self.assertEquals(self.feed.feed_link[3].count_hint, None) self.assertEquals(self.feed.feed_link[3].href, 'http://gdata.youtube.com/feeds/users/andyland74/playlists') self.assertEquals(self.feed.feed_link[3].rel, 'http://gdata.youtube.com/schemas/2007#user.playlists') self.assertEquals(self.feed.feed_link[4].count_hint, '4') self.assertEquals(self.feed.feed_link[4].href, 'http://gdata.youtube.com/feeds/users/andyland74/subscriptions') self.assertEquals(self.feed.feed_link[4].rel, 'http://gdata.youtube.com/schemas/2007#user.subscriptions') self.assertEquals(self.feed.feed_link[5].count_hint, '1') self.assertEquals(self.feed.feed_link[5].href, 'http://gdata.youtube.com/feeds/users/andyland74/uploads') self.assertEquals(self.feed.feed_link[5].rel, 'http://gdata.youtube.com/schemas/2007#user.uploads') self.assertEquals(self.feed.first_name.text, 'andy') self.assertEquals(self.feed.last_name.text, 'example') self.assertEquals(self.feed.link[0].href, 'http://www.youtube.com/profile?user=andyland74') self.assertEquals(self.feed.link[0].rel, 'alternate') self.assertEquals(self.feed.link[1].href, 'http://gdata.youtube.com/feeds/users/andyland74') self.assertEquals(self.feed.link[1].rel, 'self') self.assertEquals(self.feed.location.text, 'US') self.assertEquals(self.feed.movies.text, 'Aqua Teen Hungerforce') self.assertEquals(self.feed.music.text, 'Elliott Smith') self.assertEquals(self.feed.occupation.text, 'Technical Writer') self.assertEquals(self.feed.published.text, '2006-10-16T00:09:45.000-07:00') self.assertEquals(self.feed.school.text, 'University of North Carolina') self.assertEquals(self.feed.statistics.last_web_access, '2008-02-25T16:03:38.000-08:00') self.assertEquals(self.feed.statistics.subscriber_count, '1') self.assertEquals(self.feed.statistics.video_watch_count, '21') self.assertEquals(self.feed.statistics.view_count, '9') self.assertEquals(self.feed.thumbnail.url, 'http://i.ytimg.com/vi/YFbSxcdOL-w/default.jpg') self.assertEquals(self.feed.title.text, 'andyland74 Channel') self.assertEquals(self.feed.updated.text, '2008-02-26T11:48:21.000-08:00') self.assertEquals(self.feed.username.text, 'andyland74') if __name__ == '__main__': unittest.main() gdata-2.0.18/tests/gdata_tests/apps_test.py0000750036466300116100000005303012156622363020761 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright (C) 2007 SIOS Technology, 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. __author__ = 'tmatsuo@sios.com (Takashi MATSUO)' import unittest try: from xml.etree import ElementTree except ImportError: from elementtree import ElementTree import atom import gdata from gdata import test_data import gdata.apps class AppsEmailListRecipientFeedTest(unittest.TestCase): def setUp(self): self.rcpt_feed = gdata.apps.EmailListRecipientFeedFromString( test_data.EMAIL_LIST_RECIPIENT_FEED) def testEmailListRecipientEntryCount(self): """Count EmailListRecipient entries in EmailListRecipientFeed""" self.assertEquals(len(self.rcpt_feed.entry), 2) def testLinkFinderFindsHtmlLink(self): """Tests the return value of GetXXXLink() methods""" self.assert_(self.rcpt_feed.GetSelfLink() is not None) self.assert_(self.rcpt_feed.GetNextLink() is not None) self.assert_(self.rcpt_feed.GetEditLink() is None) self.assert_(self.rcpt_feed.GetHtmlLink() is None) def testStartItem(self): """Tests the existence of in EmailListRecipientFeed and verifies the value""" self.assert_(isinstance(self.rcpt_feed.start_index, gdata.StartIndex), "EmailListRecipient feed element must be " + "an instance of gdata.OpenSearch: %s" % self.rcpt_feed.start_index) self.assertEquals(self.rcpt_feed.start_index.text, "1") def testEmailListRecipientEntries(self): """Tests the existence of in EmailListRecipientFeed and simply verifies the value""" for a_entry in self.rcpt_feed.entry: self.assert_(isinstance(a_entry, gdata.apps.EmailListRecipientEntry), "EmailListRecipient Feed must be an instance of " + "apps.EmailListRecipientEntry: %s" % a_entry) self.assertEquals(self.rcpt_feed.entry[0].who.email, "joe@example.com") self.assertEquals(self.rcpt_feed.entry[1].who.email, "susan@example.com") class AppsEmailListFeedTest(unittest.TestCase): def setUp(self): self.list_feed = gdata.apps.EmailListFeedFromString( test_data.EMAIL_LIST_FEED) def testEmailListEntryCount(self): """Count EmailList entries in EmailListFeed""" self.assertEquals(len(self.list_feed.entry), 2) def testLinkFinderFindsHtmlLink(self): """Tests the return value of GetXXXLink() methods""" self.assert_(self.list_feed.GetSelfLink() is not None) self.assert_(self.list_feed.GetNextLink() is not None) self.assert_(self.list_feed.GetEditLink() is None) self.assert_(self.list_feed.GetHtmlLink() is None) def testStartItem(self): """Tests the existence of in EmailListFeed and verifies the value""" self.assert_(isinstance(self.list_feed.start_index, gdata.StartIndex), "EmailList feed element must be an instance " + "of gdata.OpenSearch: %s" % self.list_feed.start_index) self.assertEquals(self.list_feed.start_index.text, "1") def testUserEntries(self): """Tests the existence of in EmailListFeed and simply verifies the value""" for a_entry in self.list_feed.entry: self.assert_(isinstance(a_entry, gdata.apps.EmailListEntry), "EmailList Feed must be an instance of " + "apps.EmailListEntry: %s" % a_entry) self.assertEquals(self.list_feed.entry[0].email_list.name, "us-sales") self.assertEquals(self.list_feed.entry[1].email_list.name, "us-eng") class AppsUserFeedTest(unittest.TestCase): def setUp(self): self.user_feed = gdata.apps.UserFeedFromString(test_data.USER_FEED) def testUserEntryCount(self): """Count User entries in UserFeed""" self.assertEquals(len(self.user_feed.entry), 2) def testLinkFinderFindsHtmlLink(self): """Tests the return value of GetXXXLink() methods""" self.assert_(self.user_feed.GetSelfLink() is not None) self.assert_(self.user_feed.GetNextLink() is not None) self.assert_(self.user_feed.GetEditLink() is None) self.assert_(self.user_feed.GetHtmlLink() is None) def testStartItem(self): """Tests the existence of in UserFeed and verifies the value""" self.assert_(isinstance(self.user_feed.start_index, gdata.StartIndex), "User feed element must be an instance " + "of gdata.OpenSearch: %s" % self.user_feed.start_index) self.assertEquals(self.user_feed.start_index.text, "1") def testUserEntries(self): """Tests the existence of in UserFeed and simply verifies the value""" for a_entry in self.user_feed.entry: self.assert_(isinstance(a_entry, gdata.apps.UserEntry), "User Feed must be an instance of " + "apps.UserEntry: %s" % a_entry) self.assertEquals(self.user_feed.entry[0].login.user_name, "TestUser") self.assertEquals(self.user_feed.entry[0].who.email, "TestUser@example.com") self.assertEquals(self.user_feed.entry[1].login.user_name, "JohnSmith") self.assertEquals(self.user_feed.entry[1].who.email, "JohnSmith@example.com") class AppsNicknameFeedTest(unittest.TestCase): def setUp(self): self.nick_feed = gdata.apps.NicknameFeedFromString(test_data.NICK_FEED) def testNicknameEntryCount(self): """Count Nickname entries in NicknameFeed""" self.assertEquals(len(self.nick_feed.entry), 2) def testId(self): """Tests the existence of in NicknameFeed and verifies the value""" self.assert_(isinstance(self.nick_feed.id, atom.Id), "Nickname feed element must be an instance of " + "atom.Id: %s" % self.nick_feed.id) self.assertEquals(self.nick_feed.id.text, "http://apps-apis.google.com/a/feeds/example.com/nickname/2.0") def testStartItem(self): """Tests the existence of in NicknameFeed and verifies the value""" self.assert_(isinstance(self.nick_feed.start_index, gdata.StartIndex), "Nickname feed element must be an instance " + "of gdata.OpenSearch: %s" % self.nick_feed.start_index) self.assertEquals(self.nick_feed.start_index.text, "1") def testItemsPerPage(self): """Tests the existence of in NicknameFeed and verifies the value""" self.assert_(isinstance(self.nick_feed.items_per_page, gdata.ItemsPerPage), "Nickname feed element must be an " + "instance of gdata.ItemsPerPage: %s" % self.nick_feed.items_per_page) self.assertEquals(self.nick_feed.items_per_page.text, "2") def testLinkFinderFindsHtmlLink(self): """Tests the return value of GetXXXLink() methods""" self.assert_(self.nick_feed.GetSelfLink() is not None) self.assert_(self.nick_feed.GetEditLink() is None) self.assert_(self.nick_feed.GetHtmlLink() is None) def testNicknameEntries(self): """Tests the existence of in NicknameFeed and simply verifies the value""" for a_entry in self.nick_feed.entry: self.assert_(isinstance(a_entry, gdata.apps.NicknameEntry), "Nickname Feed must be an instance of " + "apps.NicknameEntry: %s" % a_entry) self.assertEquals(self.nick_feed.entry[0].nickname.name, "Foo") self.assertEquals(self.nick_feed.entry[1].nickname.name, "Bar") class AppsEmailListRecipientEntryTest(unittest.TestCase): def setUp(self): self.rcpt_entry = gdata.apps.EmailListRecipientEntryFromString( test_data.EMAIL_LIST_RECIPIENT_ENTRY) def testId(self): """Tests the existence of in EmailListRecipientEntry and verifies the value""" self.assert_( isinstance(self.rcpt_entry.id, atom.Id), "EmailListRecipient entry element must be an instance of " + "atom.Id: %s" % self.rcpt_entry.id) self.assertEquals( self.rcpt_entry.id.text, 'https://apps-apis.google.com/a/feeds/example.com/emailList/2.0/us-sales/' + 'recipient/TestUser%40example.com') def testUpdated(self): """Tests the existence of in EmailListRecipientEntry and verifies the value""" self.assert_( isinstance(self.rcpt_entry.updated, atom.Updated), "EmailListRecipient entry element must be an instance " + "of atom.Updated: %s" % self.rcpt_entry.updated) self.assertEquals(self.rcpt_entry.updated.text, '1970-01-01T00:00:00.000Z') def testCategory(self): """Tests the existence of in EmailListRecipientEntry and verifies the value""" for a_category in self.rcpt_entry.category: self.assert_( isinstance(a_category, atom.Category), "EmailListRecipient entry element must be an " + "instance of atom.Category: %s" % a_category) self.assertEquals(a_category.scheme, "http://schemas.google.com/g/2005#kind") self.assertEquals(a_category.term, "http://schemas.google.com/apps/2006#" + "emailList.recipient") def testTitle(self): """Tests the existence of in EmailListRecipientEntry and verifies the value""" self.assert_( isinstance(self.rcpt_entry.title, atom.Title), "EmailListRecipient entry element must be an instance of " + "atom.Title: %s" % self.rcpt_entry.title) self.assertEquals(self.rcpt_entry.title.text, 'TestUser') def testLinkFinderFindsHtmlLink(self): """Tests the return value of GetXXXLink() methods""" self.assert_(self.rcpt_entry.GetSelfLink() is not None) self.assert_(self.rcpt_entry.GetEditLink() is not None) self.assert_(self.rcpt_entry.GetHtmlLink() is None) def testWho(self): """Tests the existence of a in EmailListRecipientEntry and verifies the value""" self.assert_(isinstance(self.rcpt_entry.who, gdata.apps.Who), "EmailListRecipient entry must be an instance of " + "apps.Who: %s" % self.rcpt_entry.who) self.assertEquals(self.rcpt_entry.who.email, 'TestUser@example.com') class AppsEmailListEntryTest(unittest.TestCase): def setUp(self): self.list_entry = gdata.apps.EmailListEntryFromString( test_data.EMAIL_LIST_ENTRY) def testId(self): """Tests the existence of in EmailListEntry and verifies the value""" self.assert_( isinstance(self.list_entry.id, atom.Id), "EmailList entry element must be an instance of atom.Id: %s" % self.list_entry.id) self.assertEquals( self.list_entry.id.text, 'https://apps-apis.google.com/a/feeds/example.com/emailList/2.0/testlist') def testUpdated(self): """Tests the existence of in EmailListEntry and verifies the value""" self.assert_( isinstance(self.list_entry.updated, atom.Updated), "EmailList entry element must be an instance of " + "atom.Updated: %s" % self.list_entry.updated) self.assertEquals(self.list_entry.updated.text, '1970-01-01T00:00:00.000Z') def testCategory(self): """Tests the existence of in EmailListEntry and verifies the value""" for a_category in self.list_entry.category: self.assert_( isinstance(a_category, atom.Category), "EmailList entry element must be an instance " + "of atom.Category: %s" % a_category) self.assertEquals(a_category.scheme, "http://schemas.google.com/g/2005#kind") self.assertEquals(a_category.term, "http://schemas.google.com/apps/2006#emailList") def testTitle(self): """Tests the existence of in EmailListEntry and verifies the value""" self.assert_( isinstance(self.list_entry.title, atom.Title), "EmailList entry element must be an instance of " + "atom.Title: %s" % self.list_entry.title) self.assertEquals(self.list_entry.title.text, 'testlist') def testLinkFinderFindsHtmlLink(self): """Tests the return value of GetXXXLink() methods""" self.assert_(self.list_entry.GetSelfLink() is not None) self.assert_(self.list_entry.GetEditLink() is not None) self.assert_(self.list_entry.GetHtmlLink() is None) def testEmailList(self): """Tests the existence of a in EmailListEntry and verifies the value""" self.assert_(isinstance(self.list_entry.email_list, gdata.apps.EmailList), "EmailList entry must be an instance of " + "apps.EmailList: %s" % self.list_entry.email_list) self.assertEquals(self.list_entry.email_list.name, 'testlist') def testFeedLink(self): """Test the existence of a in EmailListEntry and verifies the value""" for an_feed_link in self.list_entry.feed_link: self.assert_(isinstance(an_feed_link, gdata.FeedLink), "EmailList entry must be an instance of " + "gdata.FeedLink: %s" % an_feed_link) self.assertEquals(self.list_entry.feed_link[0].rel, 'http://schemas.google.com/apps/2006#' + 'emailList.recipients') self.assertEquals(self.list_entry.feed_link[0].href, 'http://apps-apis.google.com/a/feeds/example.com/emailList/' + '2.0/testlist/recipient/') class AppsNicknameEntryTest(unittest.TestCase): def setUp(self): self.nick_entry = gdata.apps.NicknameEntryFromString(test_data.NICK_ENTRY) def testId(self): """Tests the existence of in NicknameEntry and verifies the value""" self.assert_( isinstance(self.nick_entry.id, atom.Id), "Nickname entry element must be an instance of atom.Id: %s" % self.nick_entry.id) self.assertEquals( self.nick_entry.id.text, 'https://apps-apis.google.com/a/feeds/example.com/nickname/2.0/Foo') def testCategory(self): """Tests the existence of in NicknameEntry and verifies the value""" for a_category in self.nick_entry.category: self.assert_( isinstance(a_category, atom.Category), "Nickname entry element must be an instance " + "of atom.Category: %s" % a_category) self.assertEquals(a_category.scheme, "http://schemas.google.com/g/2005#kind") self.assertEquals(a_category.term, "http://schemas.google.com/apps/2006#nickname") def testTitle(self): """Tests the existence of in NicknameEntry and verifies the value""" self.assert_(isinstance(self.nick_entry.title, atom.Title), "Nickname entry element must be an instance " + "of atom.Title: %s" % self.nick_entry.title) self.assertEquals(self.nick_entry.title.text, "Foo") def testLogin(self): """Tests the existence of in NicknameEntry and verifies the value""" self.assert_(isinstance(self.nick_entry.login, gdata.apps.Login), "Nickname entry element must be an instance " + "of apps.Login: %s" % self.nick_entry.login) self.assertEquals(self.nick_entry.login.user_name, "TestUser") def testNickname(self): """Tests the existence of in NicknameEntry and verifies the value""" self.assert_(isinstance(self.nick_entry.nickname, gdata.apps.Nickname), "Nickname entry element must be an instance " + "of apps.Nickname: %s" % self.nick_entry.nickname) self.assertEquals(self.nick_entry.nickname.name, "Foo") def testLinkFinderFindsHtmlLink(self): """Tests the return value of GetXXXLink() methods""" self.assert_(self.nick_entry.GetSelfLink() is not None) self.assert_(self.nick_entry.GetEditLink() is not None) self.assert_(self.nick_entry.GetHtmlLink() is None) class AppsUserEntryTest(unittest.TestCase): def setUp(self): self.user_entry = gdata.apps.UserEntryFromString(test_data.USER_ENTRY) def testId(self): """Tests the existence of in UserEntry and verifies the value""" self.assert_( isinstance(self.user_entry.id, atom.Id), "User entry element must be an instance of atom.Id: %s" % self.user_entry.id) self.assertEquals( self.user_entry.id.text, 'https://apps-apis.google.com/a/feeds/example.com/user/2.0/TestUser') def testUpdated(self): """Tests the existence of in UserEntry and verifies the value""" self.assert_( isinstance(self.user_entry.updated, atom.Updated), "User entry element must be an instance of " + "atom.Updated: %s" % self.user_entry.updated) self.assertEquals(self.user_entry.updated.text, '1970-01-01T00:00:00.000Z') def testCategory(self): """Tests the existence of in UserEntry and verifies the value""" for a_category in self.user_entry.category: self.assert_( isinstance(a_category, atom.Category), "User entry element must be an instance " + "of atom.Category: %s" % a_category) self.assertEquals(a_category.scheme, "http://schemas.google.com/g/2005#kind") self.assertEquals(a_category.term, "http://schemas.google.com/apps/2006#user") def testTitle(self): """Tests the existence of in UserEntry and verifies the value""" self.assert_( isinstance(self.user_entry.title, atom.Title), "User entry element must be an instance of atom.Title: %s" % self.user_entry.title) self.assertEquals(self.user_entry.title.text, 'TestUser') def testLinkFinderFindsHtmlLink(self): """Tests the return value of GetXXXLink() methods""" self.assert_(self.user_entry.GetSelfLink() is not None) self.assert_(self.user_entry.GetEditLink() is not None) self.assert_(self.user_entry.GetHtmlLink() is None) def testLogin(self): """Tests the existence of in UserEntry and verifies the value""" self.assert_(isinstance(self.user_entry.login, gdata.apps.Login), "User entry element must be an instance of apps.Login: %s" % self.user_entry.login) self.assertEquals(self.user_entry.login.user_name, 'TestUser') self.assertEquals(self.user_entry.login.password, 'password') self.assertEquals(self.user_entry.login.suspended, 'false') self.assertEquals(self.user_entry.login.ip_whitelisted, 'false') self.assertEquals(self.user_entry.login.hash_function_name, 'SHA-1') def testName(self): """Tests the existence of in UserEntry and verifies the value""" self.assert_(isinstance(self.user_entry.name, gdata.apps.Name), "User entry element must be an instance of apps.Name: %s" % self.user_entry.name) self.assertEquals(self.user_entry.name.family_name, 'Test') self.assertEquals(self.user_entry.name.given_name, 'User') def testQuota(self): """Tests the existence of in UserEntry and verifies the value""" self.assert_(isinstance(self.user_entry.quota, gdata.apps.Quota), "User entry element must be an instance of apps.Quota: %s" % self.user_entry.quota) self.assertEquals(self.user_entry.quota.limit, '1024') def testFeedLink(self): """Test the existence of a in UserEntry and verifies the value""" for an_feed_link in self.user_entry.feed_link: self.assert_(isinstance(an_feed_link, gdata.FeedLink), "User entry must be an instance of gdata.FeedLink" + ": %s" % an_feed_link) self.assertEquals(self.user_entry.feed_link[0].rel, 'http://schemas.google.com/apps/2006#user.nicknames') self.assertEquals(self.user_entry.feed_link[0].href, 'https://apps-apis.google.com/a/feeds/example.com/nickname/' + '2.0?username=Test-3121') self.assertEquals(self.user_entry.feed_link[1].rel, 'http://schemas.google.com/apps/2006#user.emailLists') self.assertEquals(self.user_entry.feed_link[1].href, 'https://apps-apis.google.com/a/feeds/example.com/emailList/' + '2.0?recipient=testlist@example.com') def testUpdate(self): """Tests for modifing attributes of UserEntry""" self.user_entry.name.family_name = 'ModifiedFamilyName' self.user_entry.name.given_name = 'ModifiedGivenName' self.user_entry.quota.limit = '2048' self.user_entry.login.password = 'ModifiedPassword' self.user_entry.login.suspended = 'true' modified = gdata.apps.UserEntryFromString(self.user_entry.ToString()) self.assertEquals(modified.name.family_name, 'ModifiedFamilyName') self.assertEquals(modified.name.given_name, 'ModifiedGivenName') self.assertEquals(modified.quota.limit, '2048') self.assertEquals(modified.login.password, 'ModifiedPassword') self.assertEquals(modified.login.suspended, 'true') if __name__ == '__main__': unittest.main() gdata-2.0.18/tests/gdata_tests/marketplace/0000750036466300116100000000000012156625015020666 5ustar afshareng00000000000000gdata-2.0.18/tests/gdata_tests/marketplace/__init__.py0000640036466300116100000000000012156622363022771 0ustar afshareng00000000000000gdata-2.0.18/tests/gdata_tests/marketplace/live_client_test.py0000640036466300116100000001171012156622363024600 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright 2009 Google Inc. All Rights Reserved. # # 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. # This module is used for version 2 of the Google Data APIs. # These tests attempt to connect to Google servers. __author__ = 'Alexandre Vivien ' import unittest import gdata.client import gdata.data import gdata.gauth import gdata.marketplace.client import gdata.marketplace.data import gdata.test_config as conf conf.options.register_option(conf.APPS_DOMAIN_OPTION) class LicensingClientTest(unittest.TestCase): def __init__(self, *args, **kwargs): unittest.TestCase.__init__(self, *args, **kwargs) gdata.test_config.options.register( 'appsid', 'Enter the Application ID of your Marketplace application', description='The Application ID of your Marketplace application') gdata.test_config.options.register( 'appsconsumerkey', 'Enter the Consumer Key of your Marketplace application', description='The Consumer Key of your Marketplace application') gdata.test_config.options.register( 'appsconsumersecret', 'Enter the Consumer Secret of your Marketplace application', description='The Consumer Secret of your Marketplace application') def setUp(self): self.client = gdata.marketplace.client.LicensingClient(domain='example.com') if conf.options.get_value('runlive') == 'true': self.client = gdata.marketplace.client.LicensingClient(domain=conf.options.get_value('appsdomain')) conf.configure_client(self.client, 'LicensingClientTest', self.client.auth_service, True) self.client.auth_token = gdata.gauth.TwoLeggedOAuthHmacToken(conf.options.get_value('appsconsumerkey'), conf.options.get_value('appsconsumersecret'), '') self.client.source = 'GData-Python-Client-Test' self.client.account_type='HOSTED' self.client.http_client.debug = True self.app_id = conf.options.get_value('appsid') def tearDown(self): conf.close_client(self.client) def testGetLicense(self): if not conf.options.get_value('runlive') == 'true': return # Either load the recording or prepare to make a live request. conf.configure_cache(self.client, 'testGetLicense') fetched_feed = self.client.GetLicense(app_id=self.app_id) self.assertTrue(isinstance(fetched_feed, gdata.marketplace.data.LicenseFeed)) self.assertTrue(isinstance(fetched_feed.entry[0], gdata.marketplace.data.LicenseEntry)) entity = fetched_feed.entry[0].content.entity self.assertTrue(entity is not None) self.assertNotEqual(entity.id, '') self.assertNotEqual(entity.enabled, '') self.assertNotEqual(entity.customer_id, '') self.assertNotEqual(entity.state, '') def testGetLicenseNotifications(self): if not conf.options.get_value('runlive') == 'true': return # Either load the recording or prepare to make a live request. conf.configure_cache(self.client, 'testGetLicenseNotifications') fetched_feed = self.client.GetLicenseNotifications(app_id=self.app_id, max_results=2) self.assertTrue(isinstance(fetched_feed, gdata.marketplace.data.LicenseFeed)) self.assertEqual(len(fetched_feed.entry), 2) for entry in fetched_feed.entry: entity = entry.content.entity self.assertTrue(entity is not None) self.assertNotEqual(entity.id, '') self.assertNotEqual(entity.domain_name, '') self.assertNotEqual(entity.installer_email, '') self.assertNotEqual(entity.tos_acceptance_time, '') self.assertNotEqual(entity.last_change_time, '') self.assertNotEqual(entity.product_config_id, '') self.assertNotEqual(entity.state, '') next_uri = fetched_feed.find_next_link() fetched_feed_next = self.client.GetLicenseNotifications(uri=next_uri) self.assertTrue(isinstance(fetched_feed_next, gdata.marketplace.data.LicenseFeed)) self.assertTrue(len(fetched_feed_next.entry) <= 2) for entry in fetched_feed_next.entry: entity = entry.content.entity self.assertTrue(entity is not None) self.assertNotEqual(entity.id, '') self.assertNotEqual(entity.domain_name, '') self.assertNotEqual(entity.installer_email, '') self.assertNotEqual(entity.tos_acceptance_time, '') self.assertNotEqual(entity.last_change_time, '') self.assertNotEqual(entity.product_config_id, '') self.assertNotEqual(entity.state, '') def suite(): return conf.build_suite([LicensingClientTest]) if __name__ == '__main__': unittest.TextTestRunner().run(suite()) gdata-2.0.18/tests/gdata_test.py0000750036466300116100000003444712156622363016607 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright (C) 2006 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. __author__ = 'j.s@google.com (Jeff Scudder)' import unittest try: from xml.etree import ElementTree except ImportError: from elementtree import ElementTree import gdata import atom from gdata import test_data import gdata.test_config as conf class StartIndexTest(unittest.TestCase): def setUp(self): self.start_index = gdata.StartIndex() def testToAndFromString(self): self.start_index.text = '1' self.assert_(self.start_index.text == '1') new_start_index = gdata.StartIndexFromString(self.start_index.ToString()) self.assert_(self.start_index.text == new_start_index.text) class ItemsPerPageTest(unittest.TestCase): def setUp(self): self.items_per_page = gdata.ItemsPerPage() def testToAndFromString(self): self.items_per_page.text = '10' self.assert_(self.items_per_page.text == '10') new_items_per_page = gdata.ItemsPerPageFromString( self.items_per_page.ToString()) self.assert_(self.items_per_page.text == new_items_per_page.text) class GDataEntryTest(unittest.TestCase): def testIdShouldBeCleaned(self): entry = gdata.GDataEntryFromString(test_data.XML_ENTRY_1) element_tree = ElementTree.fromstring(test_data.XML_ENTRY_1) self.assert_(element_tree.findall( '{http://www.w3.org/2005/Atom}id')[0].text != entry.id.text) self.assert_(entry.id.text == 'http://www.google.com/test/id/url') def testGeneratorShouldBeCleaned(self): feed = gdata.GDataFeedFromString(test_data.GBASE_FEED) element_tree = ElementTree.fromstring(test_data.GBASE_FEED) self.assert_(element_tree.findall('{http://www.w3.org/2005/Atom}generator' )[0].text != feed.generator.text) self.assert_(feed.generator.text == 'GoogleBase') def testAllowsEmptyId(self): entry = gdata.GDataEntry() try: entry.id = atom.Id() except AttributeError: self.fail('Empty id should not raise an attribute error.') class LinkFinderTest(unittest.TestCase): def setUp(self): self.entry = gdata.GDataEntryFromString(test_data.XML_ENTRY_1) def testLinkFinderGetsLicenseLink(self): self.assertEquals(isinstance(self.entry.GetLicenseLink(), atom.Link), True) self.assertEquals(self.entry.GetLicenseLink().href, 'http://creativecommons.org/licenses/by-nc/2.5/rdf') self.assertEquals(self.entry.GetLicenseLink().rel, 'license') def testLinkFinderGetsAlternateLink(self): self.assertEquals(isinstance(self.entry.GetAlternateLink(), atom.Link), True) self.assertEquals(self.entry.GetAlternateLink().href, 'http://www.provider-host.com/123456789') self.assertEquals(self.entry.GetAlternateLink().rel, 'alternate') class GDataFeedTest(unittest.TestCase): def testCorrectConversionToElementTree(self): test_feed = gdata.GDataFeedFromString(test_data.GBASE_FEED) self.assert_(test_feed.total_results is not None) element_tree = test_feed._ToElementTree() feed = element_tree.find('{http://www.w3.org/2005/Atom}feed') self.assert_(element_tree.find( '{http://a9.com/-/spec/opensearchrss/1.0/}totalResults') is not None) def testAllowsEmptyId(self): feed = gdata.GDataFeed() try: feed.id = atom.Id() except AttributeError: self.fail('Empty id should not raise an attribute error.') class BatchEntryTest(unittest.TestCase): def testCorrectConversionFromAndToString(self): batch_entry = gdata.BatchEntryFromString(test_data.BATCH_ENTRY) self.assertEquals(batch_entry.batch_id.text, 'itemB') self.assertEquals(batch_entry.id.text, 'http://www.google.com/base/feeds/items/' '2173859253842813008') self.assertEquals(batch_entry.batch_operation.type, 'insert') self.assertEquals(batch_entry.batch_status.code, '201') self.assertEquals(batch_entry.batch_status.reason, 'Created') new_entry = gdata.BatchEntryFromString(str(batch_entry)) self.assertEquals(batch_entry.batch_id.text, new_entry.batch_id.text) self.assertEquals(batch_entry.id.text, new_entry.id.text) self.assertEquals(batch_entry.batch_operation.type, new_entry.batch_operation.type) self.assertEquals(batch_entry.batch_status.code, new_entry.batch_status.code) self.assertEquals(batch_entry.batch_status.reason, new_entry.batch_status.reason) class BatchFeedTest(unittest.TestCase): def setUp(self): self.batch_feed = gdata.BatchFeed() self.example_entry = gdata.BatchEntry( atom_id=atom.Id(text='http://example.com/1'), text='This is a test') def testConvertRequestFeed(self): batch_feed = gdata.BatchFeedFromString(test_data.BATCH_FEED_REQUEST) self.assertEquals(len(batch_feed.entry), 4) for entry in batch_feed.entry: self.assert_(isinstance(entry, gdata.BatchEntry)) self.assertEquals(batch_feed.title.text, 'My Batch Feed') new_feed = gdata.BatchFeedFromString(str(batch_feed)) self.assertEquals(len(new_feed.entry), 4) for entry in new_feed.entry: self.assert_(isinstance(entry, gdata.BatchEntry)) self.assertEquals(new_feed.title.text, 'My Batch Feed') def testConvertResultFeed(self): batch_feed = gdata.BatchFeedFromString(test_data.BATCH_FEED_RESULT) self.assertEquals(len(batch_feed.entry), 4) for entry in batch_feed.entry: self.assert_(isinstance(entry, gdata.BatchEntry)) if entry.id.text == ('http://www.google.com/base/feeds/items/' '2173859253842813008'): self.assertEquals(entry.batch_operation.type, 'insert') self.assertEquals(entry.batch_id.text, 'itemB') self.assertEquals(entry.batch_status.code, '201') self.assertEquals(entry.batch_status.reason, 'Created') self.assertEquals(batch_feed.title.text, 'My Batch') new_feed = gdata.BatchFeedFromString(str(batch_feed)) self.assertEquals(len(new_feed.entry), 4) for entry in new_feed.entry: self.assert_(isinstance(entry, gdata.BatchEntry)) if entry.id.text == ('http://www.google.com/base/feeds/items/' '2173859253842813008'): self.assertEquals(entry.batch_operation.type, 'insert') self.assertEquals(entry.batch_id.text, 'itemB') self.assertEquals(entry.batch_status.code, '201') self.assertEquals(entry.batch_status.reason, 'Created') self.assertEquals(new_feed.title.text, 'My Batch') def testAddBatchEntry(self): try: self.batch_feed.AddBatchEntry(batch_id_string='a') self.fail('AddBatchEntry with neither entry or URL should raise Error') except gdata.MissingRequiredParameters: pass new_entry = self.batch_feed.AddBatchEntry( id_url_string='http://example.com/1') self.assertEquals(len(self.batch_feed.entry), 1) self.assertEquals(self.batch_feed.entry[0].id.text, 'http://example.com/1') self.assertEquals(self.batch_feed.entry[0].batch_id.text, '0') self.assertEquals(new_entry.id.text, 'http://example.com/1') self.assertEquals(new_entry.batch_id.text, '0') to_add = gdata.BatchEntry(atom_id=atom.Id(text='originalId')) new_entry = self.batch_feed.AddBatchEntry(entry=to_add, batch_id_string='foo') self.assertEquals(new_entry.batch_id.text, 'foo') self.assertEquals(new_entry.id.text, 'originalId') to_add = gdata.BatchEntry(atom_id=atom.Id(text='originalId'), batch_id=gdata.BatchId(text='bar')) new_entry = self.batch_feed.AddBatchEntry(entry=to_add, id_url_string='newId', batch_id_string='foo') self.assertEquals(new_entry.batch_id.text, 'foo') self.assertEquals(new_entry.id.text, 'originalId') to_add = gdata.BatchEntry(atom_id=atom.Id(text='originalId'), batch_id=gdata.BatchId(text='bar')) new_entry = self.batch_feed.AddBatchEntry(entry=to_add, id_url_string='newId') self.assertEquals(new_entry.batch_id.text, 'bar') self.assertEquals(new_entry.id.text, 'originalId') to_add = gdata.BatchEntry(atom_id=atom.Id(text='originalId'), batch_id=gdata.BatchId(text='bar'), batch_operation=gdata.BatchOperation( op_type=gdata.BATCH_INSERT)) self.assertEquals(to_add.batch_operation.type, gdata.BATCH_INSERT) new_entry = self.batch_feed.AddBatchEntry(entry=to_add, id_url_string='newId', batch_id_string='foo', operation_string=gdata.BATCH_UPDATE) self.assertEquals(new_entry.batch_operation.type, gdata.BATCH_UPDATE) def testAddInsert(self): first_entry = gdata.BatchEntry( atom_id=atom.Id(text='http://example.com/1'), text='This is a test1') self.batch_feed.AddInsert(first_entry) self.assertEquals(self.batch_feed.entry[0].batch_operation.type, gdata.BATCH_INSERT) self.assertEquals(self.batch_feed.entry[0].batch_id.text, '0') second_entry = gdata.BatchEntry( atom_id=atom.Id(text='http://example.com/2'), text='This is a test2') self.batch_feed.AddInsert(second_entry, batch_id_string='foo') self.assertEquals(self.batch_feed.entry[1].batch_operation.type, gdata.BATCH_INSERT) self.assertEquals(self.batch_feed.entry[1].batch_id.text, 'foo') third_entry = gdata.BatchEntry( atom_id=atom.Id(text='http://example.com/3'), text='This is a test3') third_entry.batch_operation = gdata.BatchOperation( op_type=gdata.BATCH_DELETE) # Add an entry with a delete operation already assigned. self.batch_feed.AddInsert(third_entry) # The batch entry should not have the original operation, it should # have been changed to an insert. self.assertEquals(self.batch_feed.entry[2].batch_operation.type, gdata.BATCH_INSERT) self.assertEquals(self.batch_feed.entry[2].batch_id.text, '2') def testAddDelete(self): # Try deleting an entry delete_entry = gdata.BatchEntry( atom_id=atom.Id(text='http://example.com/1'), text='This is a test') self.batch_feed.AddDelete(entry=delete_entry) self.assertEquals(self.batch_feed.entry[0].batch_operation.type, gdata.BATCH_DELETE) self.assertEquals(self.batch_feed.entry[0].id.text, 'http://example.com/1') self.assertEquals(self.batch_feed.entry[0].text, 'This is a test') # Try deleting a URL self.batch_feed.AddDelete(url_string='http://example.com/2') self.assertEquals(self.batch_feed.entry[0].batch_operation.type, gdata.BATCH_DELETE) self.assertEquals(self.batch_feed.entry[1].id.text, 'http://example.com/2') self.assert_(self.batch_feed.entry[1].text is None) def testAddQuery(self): # Try querying with an existing batch entry delete_entry = gdata.BatchEntry( atom_id=atom.Id(text='http://example.com/1')) self.batch_feed.AddQuery(entry=delete_entry) self.assertEquals(self.batch_feed.entry[0].batch_operation.type, gdata.BATCH_QUERY) self.assertEquals(self.batch_feed.entry[0].id.text, 'http://example.com/1') # Try querying a URL self.batch_feed.AddQuery(url_string='http://example.com/2') self.assertEquals(self.batch_feed.entry[0].batch_operation.type, gdata.BATCH_QUERY) self.assertEquals(self.batch_feed.entry[1].id.text, 'http://example.com/2') def testAddUpdate(self): # Try updating an entry delete_entry = gdata.BatchEntry( atom_id=atom.Id(text='http://example.com/1'), text='This is a test') self.batch_feed.AddUpdate(entry=delete_entry) self.assertEquals(self.batch_feed.entry[0].batch_operation.type, gdata.BATCH_UPDATE) self.assertEquals(self.batch_feed.entry[0].id.text, 'http://example.com/1') self.assertEquals(self.batch_feed.entry[0].text, 'This is a test') class ExtendedPropertyTest(unittest.TestCase): def testXmlBlobRoundTrip(self): ep = gdata.ExtendedProperty(name='blobby') ep.SetXmlBlob('') extension = ep.GetXmlBlobExtensionElement() self.assertEquals(extension.tag, 'some_xml') self.assert_(extension.namespace is None) self.assertEquals(extension.attributes['attr'], 'test') ep2 = gdata.ExtendedPropertyFromString(ep.ToString()) extension = ep2.GetXmlBlobExtensionElement() self.assertEquals(extension.tag, 'some_xml') self.assert_(extension.namespace is None) self.assertEquals(extension.attributes['attr'], 'test') def testGettersShouldReturnNoneWithNoBlob(self): ep = gdata.ExtendedProperty(name='no blob') self.assert_(ep.GetXmlBlobExtensionElement() is None) self.assert_(ep.GetXmlBlobString() is None) def testGettersReturnCorrectTypes(self): ep = gdata.ExtendedProperty(name='has blob') ep.SetXmlBlob('') self.assert_(isinstance(ep.GetXmlBlobExtensionElement(), atom.ExtensionElement)) self.assert_(isinstance(ep.GetXmlBlobString(), str)) class FeedLinkTest(unittest.TestCase): def testCorrectFromStringType(self): link = gdata.FeedLinkFromString( '') self.assert_(isinstance(link, gdata.FeedLink)) self.assertEqual(link.count_hint, '5') def suite(): return conf.build_suite([StartIndexTest, StartIndexTest, GDataEntryTest, LinkFinderTest, GDataFeedTest, BatchEntryTest, BatchFeedTest, ExtendedPropertyTest, FeedLinkTest]) if __name__ == '__main__': unittest.main() gdata-2.0.18/tests/__init__.py0000640036466300116100000000000012156622363016177 0ustar afshareng00000000000000gdata-2.0.18/tests/run_all_tests.py0000750036466300116100000000161312156622363017333 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright (C) 2006 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. __author__ = 'api.jscudder@gmail.com (Jeff Scudder)' import sys import unittest import getopt import getpass import module_test_runner import run_data_tests import run_service_tests if __name__ == '__main__': run_data_tests.RunAllTests() run_service_tests.GetValuesForTestSettingsAndRunAllTests() gdata-2.0.18/tests/coverage.py0000640036466300116100000005246112156622363016255 0ustar afshareng00000000000000#!/usr/bin/env python # # Perforce Defect Tracking Integration Project # # # COVERAGE.PY -- COVERAGE TESTING # # Gareth Rees, Ravenbrook Limited, 2001-12-04 # # # 1. INTRODUCTION # # This module provides coverage testing for Python code. # # The intended readership is all Python developers. # # This document is not confidential. # # See [GDR 2001-12-04a] for the command-line interface, programmatic # interface and limitations. See [GDR 2001-12-04b] for requirements and # design. """Usage: coverage.py -x MODULE.py [ARG1 ARG2 ...] Execute module, passing the given command-line arguments, collecting coverage data. coverage.py -e Erase collected coverage data. coverage.py -r [-m] FILE1 FILE2 ... Report on the statement coverage for the given files. With the -m option, show line numbers of the statements that weren't executed. coverage.py -a [-d dir] FILE1 FILE2 ... Make annotated copies of the given files, marking statements that are executed with > and statements that are missed with !. With the -d option, make the copies in that directory. Without the -d option, make each copy in the same directory as the original. Coverage data is saved in the file .coverage by default. Set the COVERAGE_FILE environment variable to save it somewhere else.""" import os import re import string import sys import types # 2. IMPLEMENTATION # # This uses the "singleton" pattern. # # The word "morf" means a module object (from which the source file can # be deduced by suitable manipulation of the __file__ attribute) or a # filename. # # When we generate a coverage report we have to canonicalize every # filename in the coverage dictionary just in case it refers to the # module we are reporting on. It seems a shame to throw away this # information so the data in the coverage dictionary is transferred to # the 'cexecuted' dictionary under the canonical filenames. # # The coverage dictionary is called "c" and the trace function "t". The # reason for these short names is that Python looks up variables by name # at runtime and so execution time depends on the length of variables! # In the bottleneck of this application it's appropriate to abbreviate # names to increase speed. # A dictionary with an entry for (Python source file name, line number # in that file) if that line has been executed. c = {} # t(f, x, y). This method is passed to sys.settrace as a trace # function. See [van Rossum 2001-07-20b, 9.2] for an explanation of # sys.settrace and the arguments and return value of the trace function. # See [van Rossum 2001-07-20a, 3.2] for a description of frame and code # objects. def t(f, x, y): c[(f.f_code.co_filename, f.f_lineno)] = 1 return t the_coverage = None class coverage: error = "coverage error" # Name of the cache file (unless environment variable is set). cache_default = ".coverage" # Environment variable naming the cache file. cache_env = "COVERAGE_FILE" # A map from canonical Python source file name to a dictionary in # which there's an entry for each line number that has been # executed. cexecuted = {} # Cache of results of calling the analysis() method, so that you can # specify both -r and -a without doing double work. analysis_cache = {} # Cache of results of calling the canonical_filename() method, to # avoid duplicating work. canonical_filename_cache = {} def __init__(self): global the_coverage if the_coverage: raise self.error, "Only one coverage object allowed." self.cache = os.environ.get(self.cache_env, self.cache_default) self.restore() self.analysis_cache = {} def help(self, error=None): if error: print error print print __doc__ sys.exit(1) def command_line(self): import getopt settings = {} optmap = { '-a': 'annotate', '-d:': 'directory=', '-e': 'erase', '-h': 'help', '-i': 'ignore-errors', '-m': 'show-missing', '-r': 'report', '-x': 'execute', } short_opts = string.join(map(lambda o: o[1:], optmap.keys()), '') long_opts = optmap.values() options, args = getopt.getopt(sys.argv[1:], short_opts, long_opts) for o, a in options: if optmap.has_key(o): settings[optmap[o]] = 1 elif optmap.has_key(o + ':'): settings[optmap[o + ':']] = a elif o[2:] in long_opts: settings[o[2:]] = 1 elif o[2:] + '=' in long_opts: settings[o[2:]] = a else: self.help("Unknown option: '%s'." % o) if settings.get('help'): self.help() for i in ['erase', 'execute']: for j in ['annotate', 'report']: if settings.get(i) and settings.get(j): self.help("You can't specify the '%s' and '%s' " "options at the same time." % (i, j)) args_needed = (settings.get('execute') or settings.get('annotate') or settings.get('report')) action = settings.get('erase') or args_needed if not action: self.help("You must specify at least one of -e, -x, -r, " "or -a.") if not args_needed and args: self.help("Unexpected arguments %s." % args) if settings.get('erase'): self.erase() if settings.get('execute'): if not args: self.help("Nothing to do.") sys.argv = args self.start() import __main__ # When Python starts a script, sys.path[0] is the directory # in which the Python script was found. So when we run a # script, change sys.path so that it matches what the script # would have found if it had been run normally. sys.path[0] = os.path.dirname(sys.argv[0]) execfile(sys.argv[0], __main__.__dict__) if not args: args = self.cexecuted.keys() ignore_errors = settings.get('ignore-errors') show_missing = settings.get('show-missing') directory = settings.get('directory=') if settings.get('report'): self.report(args, show_missing, ignore_errors) if settings.get('annotate'): self.annotate(args, directory, ignore_errors) def start(self): sys.settrace(t) def stop(self): sys.settrace(None) def erase(self): global c c = {} self.analysis_cache = {} self.cexecuted = {} if os.path.exists(self.cache): os.remove(self.cache) # save(). Save coverage data to the coverage cache. def save(self): self.canonicalize_filenames() cache = open(self.cache, 'wb') import marshal marshal.dump(self.cexecuted, cache) cache.close() # restore(). Restore coverage data from the coverage cache (if it # exists). def restore(self): global c c = {} self.cexecuted = {} if not os.path.exists(self.cache): return try: cache = open(self.cache, 'rb') import marshal cexecuted = marshal.load(cache) cache.close() if isinstance(cexecuted, types.DictType): self.cexecuted = cexecuted except: pass # canonical_filename(filename). Return a canonical filename for the # file (that is, an absolute path with no redundant components and # normalized case). See [GDR 2001-12-04b, 3.3]. def canonical_filename(self, filename): if not self.canonical_filename_cache.has_key(filename): f = filename if os.path.isabs(f) and not os.path.exists(f): f = os.path.basename(f) if not os.path.isabs(f): for path in [os.curdir] + sys.path: g = os.path.join(path, f) if os.path.exists(g): f = g break cf = os.path.normcase(os.path.abspath(f)) self.canonical_filename_cache[filename] = cf return self.canonical_filename_cache[filename] # canonicalize_filenames(). Copy results from "executed" to # "cexecuted", canonicalizing filenames on the way. Clear the # "executed" map. def canonicalize_filenames(self): global c for filename, lineno in c.keys(): f = self.canonical_filename(filename) if not self.cexecuted.has_key(f): self.cexecuted[f] = {} self.cexecuted[f][lineno] = 1 c = {} # morf_filename(morf). Return the filename for a module or file. def morf_filename(self, morf): if isinstance(morf, types.ModuleType): if not hasattr(morf, '__file__'): raise self.error, "Module has no __file__ attribute." file = morf.__file__ else: file = morf return self.canonical_filename(file) # analyze_morf(morf). Analyze the module or filename passed as # the argument. If the source code can't be found, raise an error. # Otherwise, return a pair of (1) the canonical filename of the # source code for the module, and (2) a list of lines of statements # in the source code. def analyze_morf(self, morf): if self.analysis_cache.has_key(morf): return self.analysis_cache[morf] filename = self.morf_filename(morf) ext = os.path.splitext(filename)[1] if ext == '.pyc': if not os.path.exists(filename[0:-1]): raise self.error, ("No source for compiled code '%s'." % filename) filename = filename[0:-1] elif ext != '.py': raise self.error, "File '%s' not Python source." % filename source = open(filename, 'r') import parser tree = parser.suite(source.read()).totuple(1) source.close() statements = {} self.find_statements(tree, statements) lines = statements.keys() lines.sort() result = filename, lines self.analysis_cache[morf] = result return result # find_statements(tree, dict). Find each statement in the parse # tree and record the line on which the statement starts in the # dictionary (by assigning it to 1). # # It works by walking the whole tree depth-first. Every time it # comes across a statement (symbol.stmt -- this includes compound # statements like 'if' and 'while') it calls find_statement, which # descends the tree below the statement to find the first terminal # token in that statement and record the lines on which that token # was found. # # This algorithm may find some lines several times (because of the # grammar production statement -> compound statement -> statement), # but that doesn't matter because we record lines as the keys of the # dictionary. # # See also [GDR 2001-12-04b, 3.2]. def find_statements(self, tree, dict): import symbol, token if token.ISNONTERMINAL(tree[0]): for t in tree[1:]: self.find_statements(t, dict) if tree[0] == symbol.stmt: self.find_statement(tree[1], dict) elif (tree[0] == token.NAME and tree[1] in ['elif', 'except', 'finally']): dict[tree[2]] = 1 def find_statement(self, tree, dict): import token while token.ISNONTERMINAL(tree[0]): tree = tree[1] dict[tree[2]] = 1 # format_lines(statements, lines). Format a list of line numbers # for printing by coalescing groups of lines as long as the lines # represent consecutive statements. This will coalesce even if # there are gaps between statements, so if statements = # [1,2,3,4,5,10,11,12,13,14] and lines = [1,2,5,10,11,13,14] then # format_lines will return "1-2, 5-11, 13-14". def format_lines(self, statements, lines): pairs = [] i = 0 j = 0 start = None pairs = [] while i < len(statements) and j < len(lines): if statements[i] == lines[j]: if start == None: start = lines[j] end = lines[j] j = j + 1 elif start: pairs.append((start, end)) start = None i = i + 1 if start: pairs.append((start, end)) def stringify(pair): start, end = pair if start == end: return "%d" % start else: return "%d-%d" % (start, end) import string return string.join(map(stringify, pairs), ", ") def analysis(self, morf): filename, statements = self.analyze_morf(morf) self.canonicalize_filenames() if not self.cexecuted.has_key(filename): self.cexecuted[filename] = {} missing = [] for line in statements: if not self.cexecuted[filename].has_key(line): missing.append(line) return (filename, statements, missing, self.format_lines(statements, missing)) def morf_name(self, morf): if isinstance(morf, types.ModuleType): return morf.__name__ else: return os.path.splitext(os.path.basename(morf))[0] def report(self, morfs, show_missing=1, ignore_errors=0): if not isinstance(morfs, types.ListType): morfs = [morfs] max_name = max([5,] + map(len, map(self.morf_name, morfs))) fmt_name = "%%- %ds " % max_name fmt_err = fmt_name + "%s: %s" header = fmt_name % "Name" + " Stmts Exec Cover" fmt_coverage = fmt_name + "% 6d % 6d % 5d%%" if show_missing: header = header + " Missing" fmt_coverage = fmt_coverage + " %s" print header print "-" * len(header) total_statements = 0 total_executed = 0 for morf in morfs: name = self.morf_name(morf) try: _, statements, missing, readable = self.analysis(morf) n = len(statements) m = n - len(missing) if n > 0: pc = 100.0 * m / n else: pc = 100.0 args = (name, n, m, pc) if show_missing: args = args + (readable,) print fmt_coverage % args total_statements = total_statements + n total_executed = total_executed + m except KeyboardInterrupt: raise except: if not ignore_errors: type, msg = sys.exc_info()[0:2] print fmt_err % (name, type, msg) if len(morfs) > 1: print "-" * len(header) if total_statements > 0: pc = 100.0 * total_executed / total_statements else: pc = 100.0 args = ("TOTAL", total_statements, total_executed, pc) if show_missing: args = args + ("",) print fmt_coverage % args # annotate(morfs, ignore_errors). blank_re = re.compile("\\s*(#|$)") else_re = re.compile("\\s*else\\s*:\\s*(#|$)") def annotate(self, morfs, directory=None, ignore_errors=0): for morf in morfs: try: filename, statements, missing, _ = self.analysis(morf) source = open(filename, 'r') if directory: dest_file = os.path.join(directory, os.path.basename(filename) + ',cover') else: dest_file = filename + ',cover' dest = open(dest_file, 'w') lineno = 0 i = 0 j = 0 covered = 1 while 1: line = source.readline() if line == '': break lineno = lineno + 1 while i < len(statements) and statements[i] < lineno: i = i + 1 while j < len(missing) and missing[j] < lineno: j = j + 1 if i < len(statements) and statements[i] == lineno: covered = j >= len(missing) or missing[j] > lineno if self.blank_re.match(line): dest.write(' ') elif self.else_re.match(line): # Special logic for lines containing only # 'else:'. See [GDR 2001-12-04b, 3.2]. if i >= len(statements) and j >= len(missing): dest.write('! ') elif i >= len(statements) or j >= len(missing): dest.write('> ') elif statements[i] == missing[j]: dest.write('! ') else: dest.write('> ') elif covered: dest.write('> ') else: dest.write('! ') dest.write(line) source.close() dest.close() except KeyboardInterrupt: raise except: if not ignore_errors: raise # Singleton object. the_coverage = coverage() # Module functions call methods in the singleton object. def start(*args, **kw): return apply(the_coverage.start, args, kw) def stop(*args, **kw): return apply(the_coverage.stop, args, kw) def erase(*args, **kw): return apply(the_coverage.erase, args, kw) def analysis(*args, **kw): return apply(the_coverage.analysis, args, kw) def report(*args, **kw): return apply(the_coverage.report, args, kw) # Save coverage data when Python exits. (The atexit module wasn't # introduced until Python 2.0, so use sys.exitfunc when it's not # available.) try: import atexit atexit.register(the_coverage.save) except ImportError: sys.exitfunc = the_coverage.save # Command-line interface. if __name__ == '__main__': the_coverage.command_line() # A. REFERENCES # # [GDR 2001-12-04a] "Statement coverage for Python"; Gareth Rees; # Ravenbrook Limited; 2001-12-04; # . # # [GDR 2001-12-04b] "Statement coverage for Python: design and # analysis"; Gareth Rees; Ravenbrook Limited; 2001-12-04; # . # # [van Rossum 2001-07-20a] "Python Reference Manual (releae 2.1.1)"; # Guide van Rossum; 2001-07-20; # . # # [van Rossum 2001-07-20b] "Python Library Reference"; Guido van Rossum; # 2001-07-20; . # # # B. DOCUMENT HISTORY # # 2001-12-04 GDR Created. # # 2001-12-06 GDR Added command-line interface and source code # annotation. # # 2001-12-09 GDR Moved design and interface to separate documents. # # 2001-12-10 GDR Open cache file as binary on Windows. Allow # simultaneous -e and -x, or -a and -r. # # 2001-12-12 GDR Added command-line help. Cache analysis so that it # only needs to be done once when you specify -a and -r. # # 2001-12-13 GDR Improved speed while recording. Portable between # Python 1.5.2 and 2.1.1. # # 2002-01-03 GDR Module-level functions work correctly. # # 2002-01-07 GDR Update sys.path when running a file with the -x option, # so that it matches the value the program would get if it were run on # its own. # # # C. COPYRIGHT AND LICENCE # # Copyright 2001 Gareth Rees. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are # met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the # distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # HOLDERS AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS # OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR # TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE # USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH # DAMAGE. # # # # $Id: //info.ravenbrook.com/user/gdr/www.garethrees.org/2001/12/04/python-coverage/coverage.py#9 $ gdata-2.0.18/tests/testimage.jpg0000750036466300116100000000126112156622363016566 0ustar afshareng00000000000000ÿØÿàJFIF``ÿá6ExifII*&ÿÿÿ †±ÿÛC    $.' ",#(7),01444'9=82<.342ÿÛC  2!!22222222222222222222222222222222222222222222222222ÿÀ"ÿÄ ÿĵ}!1AQa"q2‘¡#B±ÁRÑð$3br‚ %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyzƒ„…†‡ˆ‰Š’“”•–—˜™š¢£¤¥¦§¨©ª²³´µ¶·¸¹ºÂÃÄÅÆÇÈÉÊÒÓÔÕÖרÙÚáâãäåæçèéêñòóôõö÷øùúÿÄ ÿĵw!1AQaq"2B‘¡±Á #3RðbrÑ $4á%ñ&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz‚ƒ„…†‡ˆ‰Š’“”•–—˜™š¢£¤¥¦§¨©ª²³´µ¶·¸¹ºÂÃÄÅÆÇÈÉÊÒÓÔÕÖרÙÚâãäåæçèéêòóôõö÷øùúÿÚ ?ö*(¢¿ =3ÿÙgdata-2.0.18/tests/all_tests_cached.py0000750036466300116100000000210312156622363017731 0ustar afshareng00000000000000#!/usr/bin/env python # # Copyright (C) 2009 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. # This module is used for version 2 of the Google Data APIs. __author__ = 'j.s@google.com (Jeff Scudder)' import unittest import all_tests import gdata.test_config as conf conf.options.set_value('runlive', 'true') conf.options.set_value('savecache', 'true') conf.options.set_value('clearcache', 'false') def suite(): return unittest.TestSuite((atom_tests.core_test.suite(),)) if __name__ == '__main__': unittest.TextTestRunner().run(all_tests.suite()) gdata-2.0.18/tests/all_tests_local.py0000750036466300116100000000175312156622363017626 0ustar afshareng00000000000000#!/usr/bin/env python # # Copyright (C) 2009 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. # This module is used for version 2 of the Google Data APIs. __author__ = 'j.s@google.com (Jeff Scudder)' import unittest import all_tests import gdata.test_config as conf conf.options.set_value('runlive', 'false') def suite(): return unittest.TestSuite((atom_tests.core_test.suite(),)) if __name__ == '__main__': unittest.TextTestRunner().run(all_tests.suite()) gdata-2.0.18/tests/module_test_runner.py0000750036466300116100000000414212156622363020372 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright (C) 2006 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. __author__ = 'api.jscudder@gmail.com (Jeff Scudder)' import unittest class ModuleTestRunner(object): def __init__(self, module_list=None, module_settings=None): """Constructor for a runner to run tests in the modules listed. Args: module_list: list (optional) The modules whose test cases will be run. module_settings: dict (optional) A dictionary of module level varables which should be set in the modules if they are present. An example is the username and password which is a module variable in most service_test modules. """ self.modules = module_list or [] self.settings = module_settings or {} def RunAllTests(self): """Executes all tests in this objects modules list. It also sets any module variables which match the settings keys to the corresponding values in the settings member. """ runner = unittest.TextTestRunner() for module in self.modules: # Set any module variables according to the contents in the settings for setting, value in self.settings.iteritems(): try: setattr(module, setting, value) except AttributeError: # This module did not have a variable for the current setting, so # we skip it and try the next setting. pass # We have set all of the applicable settings for the module, now # run the tests. print '\nRunning all tests in module', module.__name__ runner.run(unittest.defaultTestLoader.loadTestsFromModule(module)) gdata-2.0.18/tests/all_tests_clear_cache.py0000750036466300116100000000204112156622363020734 0ustar afshareng00000000000000#!/usr/bin/env python # # Copyright (C) 2009 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. # This module is used for version 2 of the Google Data APIs. __author__ = 'j.s@google.com (Jeff Scudder)' import unittest import all_tests from gdata.test_config import settings settings.RUN_LIVE_TESTS = True settings.CACHE_RESPONSES = True settings.CLEAR_CACHE = True def suite(): return unittest.TestSuite((atom_tests.core_test.suite(),)) if __name__ == '__main__': unittest.TextTestRunner().run(all_tests.suite()) gdata-2.0.18/tests/all_tests.py0000640036466300116100000000722212156622363016447 0ustar afshareng00000000000000#!/usr/bin/env python # # Copyright (C) 2009 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. # This module is used for version 2 of the Google Data APIs. __author__ = 'j.s@google.com (Jeff Scudder)' import unittest # Tests for v2 features. import atom_tests.core_test import atom_tests.data_test import atom_tests.http_core_test import atom_tests.auth_test import atom_tests.mock_http_core_test import atom_tests.client_test import gdata_tests.client_test import gdata_tests.core_test import gdata_tests.data_test import gdata_tests.data_smoke_test import gdata_tests.client_smoke_test import gdata_tests.live_client_test import gdata_tests.gauth_test import gdata_tests.blogger.data_test import gdata_tests.blogger.live_client_test import gdata_tests.spreadsheets.data_test import gdata_tests.spreadsheets.live_client_test import gdata_tests.projecthosting.data_test import gdata_tests.projecthosting.live_client_test import gdata_tests.sites.data_test import gdata_tests.sites.live_client_test import gdata_tests.analytics.data_test import gdata_tests.analytics.live_client_test import gdata_tests.contacts.live_client_test import gdata_tests.contacts.profiles.live_client_test import gdata_tests.calendar_resource.live_client_test import gdata_tests.calendar_resource.data_test import gdata_tests.apps.emailsettings.data_test import gdata_tests.apps.emailsettings.live_client_test import gdata_tests.apps.multidomain.data_test import gdata_tests.apps.multidomain.live_client_test import gdata_tests.youtube.live_client_test def suite(): return unittest.TestSuite(( gdata_tests.contacts.profiles.live_client_test.suite(), atom_tests.core_test.suite(), atom_tests.data_test.suite(), atom_tests.http_core_test.suite(), atom_tests.auth_test.suite(), atom_tests.mock_http_core_test.suite(), atom_tests.client_test.suite(), gdata_tests.client_test.suite(), gdata_tests.core_test.suite(), gdata_tests.data_test.suite(), gdata_tests.data_smoke_test.suite(), gdata_tests.client_smoke_test.suite(), gdata_tests.live_client_test.suite(), gdata_tests.gauth_test.suite(), gdata_tests.blogger.data_test.suite(), gdata_tests.blogger.live_client_test.suite(), gdata_tests.spreadsheets.data_test.suite(), gdata_tests.spreadsheets.live_client_test.suite(), gdata_tests.projecthosting.data_test.suite(), gdata_tests.projecthosting.live_client_test.suite(), gdata_tests.sites.data_test.suite(), gdata_tests.sites.live_client_test.suite(), gdata_tests.analytics.data_test.suite(), gdata_tests.analytics.live_client_test.suite(), gdata_tests.contacts.live_client_test.suite(), gdata_tests.calendar_resource.live_client_test.suite(), gdata_tests.calendar_resource.data_test.suite(), gdata_tests.apps.emailsettings.live_client_test.suite(), gdata_tests.apps.emailsettings.data_test.suite(), gdata_tests.apps.multidomain.live_client_test.suite(), gdata_tests.apps.multidomain.data_test.suite(), gdata_tests.youtube.live_client_test.suite(), )) if __name__ == '__main__': unittest.TextTestRunner().run(suite()) gdata-2.0.18/tests/all_tests_coverage.py0000750036466300116100000000327112156622363020324 0ustar afshareng00000000000000#!/usr/bin/env python # # Copyright (C) 2009 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. # This module is used for version 2 of the Google Data APIs. __author__ = 'j.s@google.com (Jeff Scudder)' import unittest import coverage import all_tests import atom.core import atom.http_core import atom.mock_http_core import atom.auth import atom.client import gdata.gauth import gdata.client import gdata.data import gdata.blogger.data import gdata.blogger.client import gdata.spreadsheets.data from gdata.test_config import settings # Ensure that coverage tests execute the live requests to the servers, but # allow use of cached server responses to speed up repeated runs. settings.RUN_LIVE_TESTS = True settings.CLEAR_CACHE = False def suite(): return unittest.TestSuite((atom_tests.core_test.suite(),)) if __name__ == '__main__': coverage.erase() coverage.start() unittest.TextTestRunner().run(all_tests.suite()) coverage.stop() coverage.report([atom.core, atom.http_core, atom.auth, atom.data, atom.mock_http_core, atom.client, gdata.gauth, gdata.client, gdata.core, gdata.data, gdata.blogger.data, gdata.blogger.client, gdata.spreadsheets.data]) gdata-2.0.18/tests/atom_tests/0000750036466300116100000000000012156625015016256 5ustar afshareng00000000000000gdata-2.0.18/tests/atom_tests/core_test.py0000750036466300116100000004167012156622363020635 0ustar afshareng00000000000000#!/usr/bin/env python # # Copyright (C) 2008 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. # This module is used for version 2 of the Google Data APIs. __author__ = 'j.s@google.com (Jeff Scudder)' import unittest try: from xml.etree import cElementTree as ElementTree except ImportError: try: import cElementTree as ElementTree except ImportError: try: from xml.etree import ElementTree except ImportError: from elementtree import ElementTree import atom.core import gdata.test_config as conf SAMPLE_XML = ('' '' '' '' 'Some Test' 'Different Namespace' '' '' '') NO_NAMESPACE_XML = ('Baz Text!') V1_XML = ('' '' 'Greetings!' '' '') V2_XML = ('' '' 'Greetings!' '' '') class Child(atom.core.XmlElement): _qname = ('{http://example.com/1}child', '{http://example.com/2}child') class Foo(atom.core.XmlElement): _qname = 'foo' class Example(atom.core.XmlElement): _qname = '{http://example.com}foo' child = Child foos = [Foo] tag = 'tag' versioned_attr = ('attr', '{http://new_ns}attr') # Example XmlElement subclass declarations. class Inner(atom.core.XmlElement): _qname = '{http://example.com/xml/1}inner' my_x = 'x' class Outer(atom.core.XmlElement): _qname = '{http://example.com/xml/1}outer' innards = [Inner] class XmlElementTest(unittest.TestCase): def testGetQName(self): class Unversioned(atom.core.XmlElement): _qname = '{http://example.com}foo' class Versioned(atom.core.XmlElement): _qname = ('{http://example.com/1}foo', '{http://example.com/2}foo') self.assert_( atom.core._get_qname(Unversioned, 1) == '{http://example.com}foo') self.assert_( atom.core._get_qname(Unversioned, 2) == '{http://example.com}foo') self.assert_( atom.core._get_qname(Versioned, 1) == '{http://example.com/1}foo') self.assert_( atom.core._get_qname(Versioned, 2) == '{http://example.com/2}foo') def testConstructor(self): e = Example() self.assert_(e.child is None) self.assert_(e.tag is None) self.assert_(e.versioned_attr is None) self.assert_(e.foos == []) self.assert_(e.text is None) def testGetRules(self): rules1 = Example._get_rules(1) self.assert_(rules1[0] == '{http://example.com}foo') self.assert_(rules1[1]['{http://example.com/1}child'] == ('child', Child, False)) self.assert_(rules1[1]['foo'] == ('foos', Foo, True)) self.assert_(rules1[2]['tag'] == 'tag') self.assert_(rules1[2]['attr'] == 'versioned_attr') # Check to make sure we don't recalculate the rules. self.assert_(rules1 == Example._get_rules(1)) rules2 = Example._get_rules(2) self.assert_(rules2[0] == '{http://example.com}foo') self.assert_(rules2[1]['{http://example.com/2}child'] == ('child', Child, False)) self.assert_(rules2[1]['foo'] == ('foos', Foo, True)) self.assert_(rules2[2]['tag'] == 'tag') self.assert_(rules2[2]['{http://new_ns}attr'] == 'versioned_attr') def testGetElements(self): e = Example() e.child = Child() e.child.text = 'child text' e.foos.append(Foo()) e.foos[0].text = 'foo1' e.foos.append(Foo()) e.foos[1].text = 'foo2' e._other_elements.append(atom.core.XmlElement()) e._other_elements[0]._qname = 'bar' e._other_elements[0].text = 'other1' e._other_elements.append(atom.core.XmlElement()) e._other_elements[1]._qname = 'child' e._other_elements[1].text = 'other2' self.contains_expected_elements(e.get_elements(), ['foo1', 'foo2', 'child text', 'other1', 'other2']) self.contains_expected_elements(e.get_elements('child'), ['child text', 'other2']) self.contains_expected_elements( e.get_elements('child', 'http://example.com/1'), ['child text']) self.contains_expected_elements( e.get_elements('child', 'http://example.com/2'), []) self.contains_expected_elements( e.get_elements('child', 'http://example.com/2', 2), ['child text']) self.contains_expected_elements( e.get_elements('child', 'http://example.com/1', 2), []) self.contains_expected_elements( e.get_elements('child', 'http://example.com/2', 3), ['child text']) self.contains_expected_elements(e.get_elements('bar'), ['other1']) self.contains_expected_elements(e.get_elements('bar', version=2), ['other1']) self.contains_expected_elements(e.get_elements('bar', version=3), ['other1']) def contains_expected_elements(self, elements, expected_texts): self.assert_(len(elements) == len(expected_texts)) for element in elements: self.assert_(element.text in expected_texts) def testConstructorKwargs(self): e = Example('hello', child=Child('world'), versioned_attr='1') self.assert_(e.text == 'hello') self.assert_(e.child.text == 'world') self.assert_(e.versioned_attr == '1') self.assert_(e.foos == []) self.assert_(e.tag is None) e = Example(foos=[Foo('1', ignored=1), Foo(text='2')], tag='ok') self.assert_(e.text is None) self.assert_(e.child is None) self.assert_(e.versioned_attr is None) self.assert_(len(e.foos) == 2) self.assert_(e.foos[0].text == '1') self.assert_(e.foos[1].text == '2') self.assert_('ignored' not in e.foos[0].__dict__) self.assert_(e.tag == 'ok') def testParseBasicXmlElement(self): element = atom.core.xml_element_from_string(SAMPLE_XML, atom.core.XmlElement) inners = element.get_elements('inner') self.assert_(len(inners) == 3) self.assert_(inners[0].get_attributes('x')[0].value == '123') self.assert_(inners[0].get_attributes('y') == []) self.assert_(inners[1].get_attributes('x')[0].value == '234') self.assert_(inners[1].get_attributes('y')[0].value == 'abc') self.assert_(inners[2].get_attributes('x') == []) inners = element.get_elements('inner', 'http://example.com/xml/1') self.assert_(len(inners) == 3) inners = element.get_elements(None, 'http://example.com/xml/1') self.assert_(len(inners) == 4) inners = element.get_elements() self.assert_(len(inners) == 4) inners = element.get_elements('other') self.assert_(len(inners) == 1) self.assert_(inners[0].get_attributes( 'z', 'http://example.com/xml/2')[0].value == 'true') inners = element.get_elements('missing') self.assert_(len(inners) == 0) def testBasicXmlElementPreservesMarkup(self): element = atom.core.xml_element_from_string(SAMPLE_XML, atom.core.XmlElement) tree1 = ElementTree.fromstring(SAMPLE_XML) tree2 = ElementTree.fromstring(element.to_string()) self.assert_trees_similar(tree1, tree2) def testSchemaParse(self): outer = atom.core.xml_element_from_string(SAMPLE_XML, Outer) self.assert_(isinstance(outer.innards, list)) self.assert_(len(outer.innards) == 3) self.assert_(outer.innards[0].my_x == '123') def testSchemaParsePreservesMarkup(self): outer = atom.core.xml_element_from_string(SAMPLE_XML, Outer) tree1 = ElementTree.fromstring(SAMPLE_XML) tree2 = ElementTree.fromstring(outer.to_string()) self.assert_trees_similar(tree1, tree2) found_x_and_y = False found_x_123 = False child = tree1.find('{http://example.com/xml/1}inner') matching_children = tree2.findall(child.tag) for match in matching_children: if 'y' in match.attrib and match.attrib['y'] == 'abc': if match.attrib['x'] == '234': found_x_and_y = True self.assert_(match.attrib['x'] == '234') if 'x' in match.attrib and match.attrib['x'] == '123': self.assert_('y' not in match.attrib) found_x_123 = True self.assert_(found_x_and_y) self.assert_(found_x_123) def testGenericTagAndNamespace(self): element = atom.core.XmlElement(text='content') # Try setting tag then namespace. element.tag = 'foo' self.assert_(element._qname == 'foo') element.namespace = 'http://example.com/ns' self.assert_(element._qname == '{http://example.com/ns}foo') element = atom.core.XmlElement() # Try setting namespace then tag. element.namespace = 'http://example.com/ns' self.assert_(element._qname == '{http://example.com/ns}') element.tag = 'foo' self.assert_(element._qname == '{http://example.com/ns}foo') def assert_trees_similar(self, a, b): """Compares two XML trees for approximate matching.""" for child in a: self.assert_(len(a.findall(child.tag)) == len(b.findall(child.tag))) for child in b: self.assert_(len(a.findall(child.tag)) == len(b.findall(child.tag))) self.assert_(len(a) == len(b)) self.assert_(a.text == b.text) self.assert_(a.attrib == b.attrib) class UtilityFunctionTest(unittest.TestCase): def testMatchQnames(self): self.assert_(atom.core._qname_matches( 'foo', 'http://example.com', '{http://example.com}foo')) self.assert_(atom.core._qname_matches( None, None, '{http://example.com}foo')) self.assert_(atom.core._qname_matches( None, None, 'foo')) self.assert_(atom.core._qname_matches( None, None, None)) self.assert_(atom.core._qname_matches( None, None, '{http://example.com}')) self.assert_(atom.core._qname_matches( 'foo', None, '{http://example.com}foo')) self.assert_(atom.core._qname_matches( None, 'http://example.com', '{http://example.com}foo')) self.assert_(atom.core._qname_matches( None, '', 'foo')) self.assert_(atom.core._qname_matches( 'foo', '', 'foo')) self.assert_(atom.core._qname_matches( 'foo', '', 'foo')) self.assert_(atom.core._qname_matches( 'foo', 'http://google.com', '{http://example.com}foo') == False) self.assert_(atom.core._qname_matches( 'foo', 'http://example.com', '{http://example.com}bar') == False) self.assert_(atom.core._qname_matches( 'foo', 'http://example.com', '{http://google.com}foo') == False) self.assert_(atom.core._qname_matches( 'bar', 'http://example.com', '{http://google.com}foo') == False) self.assert_(atom.core._qname_matches( 'foo', None, '{http://example.com}bar') == False) self.assert_(atom.core._qname_matches( None, 'http://google.com', '{http://example.com}foo') == False) self.assert_(atom.core._qname_matches( None, '', '{http://example.com}foo') == False) self.assert_(atom.core._qname_matches( 'foo', '', 'bar') == False) class Chars(atom.core.XmlElement): _qname = u'{http://example.com/}chars' y = 'y' alpha = 'a' class Strs(atom.core.XmlElement): _qname = '{http://example.com/}strs' chars = [Chars] delta = u'd' def parse(string): return atom.core.xml_element_from_string(string, atom.core.XmlElement) def create(tag, string): element = atom.core.XmlElement(text=string) element._qname = tag return element class CharacterEncodingTest(unittest.TestCase): def testUnicodeInputString(self): # Test parsing the inner text. self.assertEqual(parse(u'δ').text, u'\u03b4') self.assertEqual(parse(u'\u03b4').text, u'\u03b4') # Test output valid XML. self.assertEqual(parse(u'δ').to_string(), 'δ') self.assertEqual(parse(u'\u03b4').to_string(), 'δ') # Test setting the inner text and output valid XML. e = create(u'x', u'\u03b4') self.assertEqual(e.to_string(), 'δ') self.assertEqual(e.text, u'\u03b4') self.assert_(isinstance(e.text, unicode)) self.assertEqual(create(u'x', '\xce\xb4'.decode('utf-8')).to_string(), 'δ') def testUnicodeTagsAndAttributes(self): # Begin with test to show underlying ElementTree behavior. t = ElementTree.fromstring(u'test'.encode('utf-8')) self.assertEqual(t.tag, u'del\u03b4ta') self.assertEqual(parse(u'<\u03b4elta>test')._qname, u'\u03b4elta') # Test unicode attribute names and values. t = ElementTree.fromstring(u''.encode('utf-8')) self.assertEqual(t.attrib, {u'\u03b4a': u'\u03b4b'}) self.assertEqual(parse(u'').get_attributes( u'\u03b4a')[0].value, u'\u03b4b') x = create('x', None) x._other_attributes[u'a'] = u'\u03b4elta' self.assert_(x.to_string().startswith('δ').text, u'\u03b4') self.assertEqual(parse(u'\u03b4'.encode('utf-8')).text, u'\u03b4') self.assertEqual(parse('\xce\xb4').text, u'\u03b4') # Test output valid XML. self.assertEqual(parse('δ').to_string(), 'δ') self.assertEqual(parse(u'\u03b4'.encode('utf-8')).to_string(), 'δ') self.assertEqual(parse('\xce\xb4').to_string(), 'δ') # Test setting the inner text and output valid XML. e = create('x', '\xce\xb4') self.assertEqual(e.to_string(), 'δ') # Don't change the encoding until the we convert to an XML string. self.assertEqual(e.text, '\xce\xb4') self.assert_(isinstance(e.text, str)) self.assert_(isinstance(e.to_string(), str)) self.assertEqual(create('x', u'\u03b4'.encode('utf-8')).to_string(), 'δ') # Test attributes and values with UTF-8 inputs. self.assertEqual(parse('').get_attributes( u'\u03b4a')[0].value, u'\u03b4b') def testUtf8TagsAndAttributes(self): self.assertEqual( parse(u'<\u03b4elta>test'.encode('utf-8'))._qname, u'\u03b4elta') self.assertEqual(parse('<\xce\xb4elta>test')._qname, u'\u03b4elta') # Test an element with UTF-8 in the attribute value. x = create('x', None) x._other_attributes[u'a'] = '\xce\xb4' self.assert_(x.to_string(encoding='UTF-8').startswith('\u03b4'.encode('utf-16')).text, u'\u03b4') # Test output valid XML. self.assertEqual(parse(u'\u03b4'.encode('utf-16')).to_string(), 'δ') # Test setting the inner text and output valid XML. e = create('x', u'\u03b4'.encode('utf-16')) self.assertEqual(e.to_string(encoding='utf-16'), 'δ') # Don't change the encoding until the we convert to an XML string. # Allow either little-endian or big-endian byte orderings. self.assert_(e.text in ['\xff\xfe\xb4\x03', '\xfe\xff\x03\xb4']) endianness = LITTLE_ENDIAN if e.text == '\xfe\xff\x03\xb4': endianness = BIG_ENDIAN self.assert_(isinstance(e.text, str)) self.assert_(isinstance(e.to_string(encoding='utf-16'), str)) if endianness == LITTLE_ENDIAN: self.assertEqual( create('x', '\xff\xfe\xb4\x03').to_string(encoding='utf-16'), 'δ') else: self.assertEqual( create('x', '\xfe\xff\x03\xb4').to_string(encoding='utf-16'), 'δ') def testOtherEncodingInTagsAndAttributes(self): self.assertEqual( parse(u'<\u03b4elta>test'.encode('utf-16'))._qname, u'\u03b4elta') # Test an element with UTF-16 in the attribute value. x = create('x', None) x._other_attributes[u'a'] = u'\u03b4'.encode('utf-16') self.assert_(x.to_string(encoding='UTF-16').startswith('= len('/service/subservice?')) self.assert_(path.find('newname=newvalue') >= len('/service/subservice?')) def testParseHttpsUrl(self): atom_service = atom.service.AtomService('code.google.com') self.assertEquals(atom_service.server, 'code.google.com') (host, port, ssl, path) = atom.service.ProcessUrl(atom_service, 'https://www.google.com/service/subservice?name=value&newname=newvalue') self.assertEquals(ssl, True) self.assertEquals(host, 'www.google.com') self.assertEquals(port, 443) self.assert_(path.startswith('/service/subservice?')) self.assert_(path.find('name=value') >= len('/service/subservice?')) self.assert_(path.find('newname=newvalue') >= len('/service/subservice?')) def testParseHttpsUrlWithPort(self): atom_service = atom.service.AtomService('code.google.com') self.assertEquals(atom_service.server, 'code.google.com') (host, port, ssl, path) = atom.service.ProcessUrl(atom_service, 'https://www.google.com:13981/service/subservice?name=value&newname=newvalue') self.assertEquals(ssl, True) self.assertEquals(host, 'www.google.com') self.assertEquals(port, 13981) self.assert_(path.startswith('/service/subservice?')) self.assert_(path.find('name=value') >= len('/service/subservice?')) self.assert_(path.find('newname=newvalue') >= len('/service/subservice?')) def testSetBasicAuth(self): client = atom.service.AtomService() client.UseBasicAuth('foo', 'bar') token = client.token_store.find_token('http://') self.assert_(isinstance(token, atom.service.BasicAuthToken)) self.assertEquals(token.auth_header, 'Basic Zm9vOmJhcg==') client.UseBasicAuth('','') token = client.token_store.find_token('http://') self.assert_(isinstance(token, atom.service.BasicAuthToken)) self.assertEquals(token.auth_header, 'Basic Og==') def testProcessUrlWithStringForService(self): (server, port, ssl, uri) = atom.service.ProcessUrl( service='www.google.com', url='/base/feeds/items') self.assertEquals(server, 'www.google.com') self.assertEquals(port, 80) self.assertEquals(ssl, False) self.assert_(uri.startswith('/base/feeds/items')) client = atom.service.AtomService() client.server = 'www.google.com' client.ssl = True (server, port, ssl, uri) = atom.service.ProcessUrl( service=client, url='/base/feeds/items') self.assertEquals(server, 'www.google.com') self.assertEquals(ssl, True) self.assert_(uri.startswith('/base/feeds/items')) (server, port, ssl, uri) = atom.service.ProcessUrl(service=None, url='https://www.google.com/base/feeds/items') self.assertEquals(server, 'www.google.com') self.assertEquals(port, 443) self.assertEquals(ssl, True) self.assert_(uri.startswith('/base/feeds/items')) def testHostHeaderContainsNonDefaultPort(self): client = atom.service.AtomService() client.http_client.v2_http_client = atom.mock_http_core.EchoHttpClient() response = client.Get('http://example.com') self.assertEqual(response.getheader('Echo-Host'), 'example.com:None') response = client.Get('https://example.com') self.assertEqual(response.getheader('Echo-Host'), 'example.com:None') response = client.Get('https://example.com:8080') self.assertEqual(response.getheader('Echo-Host'), 'example.com:8080') response = client.Get('http://example.com:1234') self.assertEqual(response.getheader('Echo-Host'), 'example.com:1234') def testBadHttpsProxyRaisesRealException(self): """Test that real exceptions are raised when there is an error connecting to a host with an https proxy """ client = atom.service.AtomService(server='example.com') client.server = 'example.com' os.environ['https_proxy'] = 'http://example.com' self.assertRaises(atom.http.ProxyError, atom.service.PrepareConnection, client, 'https://example.com') def suite(): return conf.build_suite([AtomServiceUnitTest]) if __name__ == '__main__': unittest.main() gdata-2.0.18/tests/atom_tests/data_test.py0000750036466300116100000011237312156622363020615 0ustar afshareng00000000000000#!/usr/bin/python # -*-*- encoding: utf-8 -*-*- # # Copyright (C) 2006 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. __author__ = 'api.jscudder@gmail.com (Jeff Scudder)' import sys import unittest try: from xml.etree import ElementTree except ImportError: from elementtree import ElementTree import atom.data import atom.core import gdata.test_config as conf XML_ENTRY_1 = """ http://www.google.com/test/id/url Testing 2000 series laptop
    A Testing Laptop
    Computer Laptop testing laptop products
    """ class AuthorTest(unittest.TestCase): def setUp(self): self.author = atom.data.Author() def testEmptyAuthorShouldHaveEmptyExtensionLists(self): self.assert_(isinstance(self.author._other_elements, list)) self.assertEqual(len(self.author._other_elements), 0) self.assert_(isinstance(self.author._other_attributes, dict)) self.assertEqual(len(self.author._other_attributes), 0) def testNormalAuthorShouldHaveNoExtensionElements(self): self.author.name = atom.data.Name(text='Jeff Scudder') self.assertEqual(self.author.name.text, 'Jeff Scudder') self.assertEqual(len(self.author._other_elements), 0) new_author = atom.core.XmlElementFromString(self.author.ToString(), atom.data.Author) self.assertEqual(len(new_author._other_elements), 0) self.assertEqual(new_author.name.text, 'Jeff Scudder') self.author.extension_elements.append(atom.data.ExtensionElement( 'foo', text='bar')) self.assertEqual(len(self.author.extension_elements), 1) self.assertEqual(self.author.name.text, 'Jeff Scudder') new_author = atom.core.parse(self.author.ToString(), atom.data.Author) self.assertEqual(len(self.author.extension_elements), 1) self.assertEqual(new_author.name.text, 'Jeff Scudder') def testEmptyAuthorToAndFromStringShouldMatch(self): string_from_author = self.author.ToString() new_author = atom.core.XmlElementFromString(string_from_author, atom.data.Author) string_from_new_author = new_author.ToString() self.assertEqual(string_from_author, string_from_new_author) def testAuthorWithNameToAndFromStringShouldMatch(self): self.author.name = atom.data.Name() self.author.name.text = 'Jeff Scudder' string_from_author = self.author.ToString() new_author = atom.core.XmlElementFromString(string_from_author, atom.data.Author) string_from_new_author = new_author.ToString() self.assertEqual(string_from_author, string_from_new_author) self.assertEqual(self.author.name.text, new_author.name.text) def testExtensionElements(self): self.author.extension_attributes['foo1'] = 'bar' self.author.extension_attributes['foo2'] = 'rab' self.assertEqual(self.author.extension_attributes['foo1'], 'bar') self.assertEqual(self.author.extension_attributes['foo2'], 'rab') new_author = atom.core.parse(str(self.author), atom.data.Author) self.assertEqual(new_author.extension_attributes['foo1'], 'bar') self.assertEqual(new_author.extension_attributes['foo2'], 'rab') def testConvertFullAuthorToAndFromString(self): TEST_AUTHOR = """ John Doe john@example.com http://www.google.com """ author = atom.core.parse(TEST_AUTHOR, atom.data.Author) self.assertEqual(author.name.text, 'John Doe') self.assertEqual(author.email.text, 'john@example.com') self.assertEqual(author.uri.text, 'http://www.google.com') class EmailTest(unittest.TestCase): def setUp(self): self.email = atom.data.Email() def testEmailToAndFromString(self): self.email.text = 'This is a test' new_email = atom.core.parse(self.email.to_string(), atom.data.Email) self.assertEqual(self.email.text, new_email.text) self.assertEqual(self.email.extension_elements, new_email.extension_elements) class NameTest(unittest.TestCase): def setUp(self): self.name = atom.data.Name() def testEmptyNameToAndFromStringShouldMatch(self): string_from_name = self.name.ToString() new_name = atom.core.XmlElementFromString(string_from_name, atom.data.Name) string_from_new_name = new_name.ToString() self.assertEqual(string_from_name, string_from_new_name) def testText(self): self.assert_(self.name.text is None) self.name.text = 'Jeff Scudder' self.assertEqual(self.name.text, 'Jeff Scudder') new_name = atom.core.parse(self.name.to_string(), atom.data.Name) self.assertEqual(new_name.text, self.name.text) def testExtensionElements(self): self.name.extension_attributes['foo'] = 'bar' self.assertEqual(self.name.extension_attributes['foo'], 'bar') new_name = atom.core.parse(self.name.ToString(), atom.data.Name) self.assertEqual(new_name.extension_attributes['foo'], 'bar') class ExtensionElementTest(unittest.TestCase): def setUp(self): self.ee = atom.data.ExtensionElement('foo') self.EXTENSION_TREE = """ John Doe Bar """ def testEmptyEEShouldProduceEmptyString(self): pass def testEEParsesTreeCorrectly(self): deep_tree = atom.core.xml_element_from_string(self.EXTENSION_TREE, atom.data.ExtensionElement) self.assertEqual(deep_tree.tag, 'feed') self.assertEqual(deep_tree.namespace, 'http://www.w3.org/2005/Atom') self.assert_(deep_tree.children[0].tag == 'author') self.assert_(deep_tree.children[0].namespace == 'http://www.google.com') self.assert_(deep_tree.children[0].children[0].tag == 'name') self.assert_(deep_tree.children[0].children[0].namespace == 'http://www.google.com') self.assert_(deep_tree.children[0].children[0].text.strip() == 'John Doe') self.assert_(deep_tree.children[0].children[0].children[0].text.strip() == 'Bar') foo = deep_tree.children[0].children[0].children[0] self.assert_(foo.tag == 'foo') self.assert_(foo.namespace == 'http://www.google.com') self.assert_(foo.attributes['up'] == 'down') self.assert_(foo.attributes['yes'] == 'no') self.assert_(foo.children == []) def testEEToAndFromStringShouldMatch(self): string_from_ee = self.ee.ToString() new_ee = atom.core.xml_element_from_string(string_from_ee, atom.data.ExtensionElement) string_from_new_ee = new_ee.ToString() self.assert_(string_from_ee == string_from_new_ee) deep_tree = atom.core.xml_element_from_string(self.EXTENSION_TREE, atom.data.ExtensionElement) string_from_deep_tree = deep_tree.ToString() new_deep_tree = atom.core.xml_element_from_string(string_from_deep_tree, atom.data.ExtensionElement) string_from_new_deep_tree = new_deep_tree.ToString() self.assert_(string_from_deep_tree == string_from_new_deep_tree) class LinkTest(unittest.TestCase): def setUp(self): self.link = atom.data.Link() def testLinkToAndFromString(self): self.link.href = 'test href' self.link.hreflang = 'english' self.link.type = 'text/html' self.link.extension_attributes['foo'] = 'bar' self.assert_(self.link.href == 'test href') self.assert_(self.link.hreflang == 'english') self.assert_(self.link.type == 'text/html') self.assert_(self.link.extension_attributes['foo'] == 'bar') new_link = atom.core.parse(self.link.ToString(), atom.data.Link) self.assert_(self.link.href == new_link.href) self.assert_(self.link.type == new_link.type) self.assert_(self.link.hreflang == new_link.hreflang) self.assert_(self.link.extension_attributes['foo'] == new_link.extension_attributes['foo']) def testLinkType(self): test_link = atom.data.Link(type='text/html') self.assertEqual(test_link.type, 'text/html') class GeneratorTest(unittest.TestCase): def setUp(self): self.generator = atom.data.Generator() def testGeneratorToAndFromString(self): self.generator.uri = 'www.google.com' self.generator.version = '1.0' self.generator.extension_attributes['foo'] = 'bar' self.assert_(self.generator.uri == 'www.google.com') self.assert_(self.generator.version == '1.0') self.assert_(self.generator.extension_attributes['foo'] == 'bar') new_generator = atom.core.parse(self.generator.ToString(), atom.data.Generator) self.assert_(self.generator.uri == new_generator.uri) self.assert_(self.generator.version == new_generator.version) self.assert_(self.generator.extension_attributes['foo'] == new_generator.extension_attributes['foo']) class TitleTest(unittest.TestCase): def setUp(self): self.title = atom.data.Title() def testTitleToAndFromString(self): self.title.type = 'text' self.title.text = 'Less: <' self.assert_(self.title.type == 'text') self.assert_(self.title.text == 'Less: <') new_title = atom.core.parse(str(self.title), atom.data.Title) self.assert_(self.title.type == new_title.type) self.assert_(self.title.text == new_title.text) class SubtitleTest(unittest.TestCase): def setUp(self): self.subtitle = atom.data.Subtitle() def testTitleToAndFromString(self): self.subtitle.type = 'text' self.subtitle.text = 'sub & title' self.assert_(self.subtitle.type == 'text') self.assert_(self.subtitle.text == 'sub & title') new_subtitle = atom.core.parse(self.subtitle.ToString(), atom.data.Subtitle) self.assert_(self.subtitle.type == new_subtitle.type) self.assert_(self.subtitle.text == new_subtitle.text) class SummaryTest(unittest.TestCase): def setUp(self): self.summary = atom.data.Summary() def testTitleToAndFromString(self): self.summary.type = 'text' self.summary.text = 'Less: <' self.assert_(self.summary.type == 'text') self.assert_(self.summary.text == 'Less: <') new_summary = atom.core.parse(self.summary.ToString(), atom.data.Summary) self.assert_(self.summary.type == new_summary.type) self.assert_(self.summary.text == new_summary.text) class CategoryTest(unittest.TestCase): def setUp(self): self.category = atom.data.Category() def testCategoryToAndFromString(self): self.category.term = 'x' self.category.scheme = 'y' self.category.label = 'z' self.assert_(self.category.term == 'x') self.assert_(self.category.scheme == 'y') self.assert_(self.category.label == 'z') new_category = atom.core.parse(self.category.to_string(), atom.data.Category) self.assert_(self.category.term == new_category.term) self.assert_(self.category.scheme == new_category.scheme) self.assert_(self.category.label == new_category.label) class ContributorTest(unittest.TestCase): def setUp(self): self.contributor = atom.data.Contributor() def testContributorToAndFromString(self): self.contributor.name = atom.data.Name(text='J Scud') self.contributor.email = atom.data.Email(text='nobody@nowhere') self.contributor.uri = atom.data.Uri(text='http://www.google.com') self.assert_(self.contributor.name.text == 'J Scud') self.assert_(self.contributor.email.text == 'nobody@nowhere') self.assert_(self.contributor.uri.text == 'http://www.google.com') new_contributor = atom.core.parse(self.contributor.ToString(), atom.data.Contributor) self.assert_(self.contributor.name.text == new_contributor.name.text) self.assert_(self.contributor.email.text == new_contributor.email.text) self.assert_(self.contributor.uri.text == new_contributor.uri.text) class IdTest(unittest.TestCase): def setUp(self): self.my_id = atom.data.Id() def testIdToAndFromString(self): self.my_id.text = 'my nifty id' self.assert_(self.my_id.text == 'my nifty id') new_id = atom.core.parse(self.my_id.ToString(), atom.data.Id) self.assert_(self.my_id.text == new_id.text) class IconTest(unittest.TestCase): def setUp(self): self.icon = atom.data.Icon() def testIconToAndFromString(self): self.icon.text = 'my picture' self.assert_(self.icon.text == 'my picture') new_icon = atom.core.parse(str(self.icon), atom.data.Icon) self.assert_(self.icon.text == new_icon.text) class LogoTest(unittest.TestCase): def setUp(self): self.logo = atom.data.Logo() def testLogoToAndFromString(self): self.logo.text = 'my logo' self.assert_(self.logo.text == 'my logo') new_logo = atom.core.parse(self.logo.ToString(), atom.data.Logo) self.assert_(self.logo.text == new_logo.text) class RightsTest(unittest.TestCase): def setUp(self): self.rights = atom.data.Rights() def testContributorToAndFromString(self): self.rights.text = 'you have the right to remain silent' self.rights.type = 'text' self.assert_(self.rights.text == 'you have the right to remain silent') self.assert_(self.rights.type == 'text') new_rights = atom.core.parse(self.rights.ToString(), atom.data.Rights) self.assert_(self.rights.text == new_rights.text) self.assert_(self.rights.type == new_rights.type) class UpdatedTest(unittest.TestCase): def setUp(self): self.updated = atom.data.Updated() def testUpdatedToAndFromString(self): self.updated.text = 'my time' self.assert_(self.updated.text == 'my time') new_updated = atom.core.parse(self.updated.ToString(), atom.data.Updated) self.assert_(self.updated.text == new_updated.text) class PublishedTest(unittest.TestCase): def setUp(self): self.published = atom.data.Published() def testPublishedToAndFromString(self): self.published.text = 'pub time' self.assert_(self.published.text == 'pub time') new_published = atom.core.parse(self.published.ToString(), atom.data.Published) self.assert_(self.published.text == new_published.text) class FeedEntryParentTest(unittest.TestCase): """The test accesses hidden methods in atom.FeedEntryParent""" def testConvertToAndFromElementTree(self): # Use entry because FeedEntryParent doesn't have a tag or namespace. original = atom.data.Entry() copy = atom.data.FeedEntryParent() original.author.append(atom.data.Author(name=atom.data.Name( text='J Scud'))) self.assert_(original.author[0].name.text == 'J Scud') self.assert_(copy.author == []) original.id = atom.data.Id(text='test id') self.assert_(original.id.text == 'test id') self.assert_(copy.id is None) copy._harvest_tree(original._to_tree()) self.assert_(original.author[0].name.text == copy.author[0].name.text) self.assert_(original.id.text == copy.id.text) class EntryTest(unittest.TestCase): def testConvertToAndFromString(self): entry = atom.data.Entry() entry.author.append(atom.data.Author(name=atom.data.Name(text='js'))) entry.title = atom.data.Title(text='my test entry') self.assert_(entry.author[0].name.text == 'js') self.assert_(entry.title.text == 'my test entry') new_entry = atom.core.parse(entry.ToString(), atom.data.Entry) self.assert_(new_entry.author[0].name.text == 'js') self.assert_(new_entry.title.text == 'my test entry') def testEntryCorrectlyConvertsActualData(self): entry = atom.core.parse(XML_ENTRY_1, atom.data.Entry) self.assert_(entry.category[0].scheme == 'http://base.google.com/categories/itemtypes') self.assert_(entry.category[0].term == 'products') self.assert_(entry.id.text == ' http://www.google.com/test/id/url ') self.assert_(entry.title.text == 'Testing 2000 series laptop') self.assert_(entry.title.type == 'text') self.assert_(entry.content.type == 'xhtml') #TODO check all other values for the test entry def testEntryWithFindElementAndFindAttribute(self): entry = atom.data.Entry() entry.link.append(atom.data.Link(rel='self', href='x')) entry.link.append(atom.data.Link(rel='foo', href='y')) entry.link.append(atom.data.Link(rel='edit',href='z')) self_link = None edit_link = None for link in entry.get_elements('link', 'http://www.w3.org/2005/Atom'): ignored1, ignored2, attributes = link.__class__._get_rules(2) if link.get_attributes('rel')[0].value == 'self': self_link = link.get_attributes('href')[0].value elif link.get_attributes('rel')[0].value == 'edit': edit_link = link.get_attributes('href')[0].value self.assertEqual(self_link, 'x') self.assertEqual(edit_link, 'z') def testAppControl(self): TEST_BASE_ENTRY = """ Testing 2000 series laptop
    A Testing Laptop
    yes Computer Laptop testing laptop products
    """ entry = atom.core.parse(TEST_BASE_ENTRY, atom.data.Entry) self.assertEquals(entry.control.draft.text, 'yes') self.assertEquals(len(entry.control.extension_elements), 1) self.assertEquals(entry.control.extension_elements[0].tag, 'disapproved') class ControlTest(unittest.TestCase): def testVersionRuleGeneration(self): self.assertEqual(atom.core._get_qname(atom.data.Control, 1), '{http://purl.org/atom/app#}control') self.assertEqual(atom.data.Control._get_rules(1)[0], '{http://purl.org/atom/app#}control') def testVersionedControlFromString(self): xml_v1 = """ no""" xml_v2 = """ no""" control_v1 = atom.core.parse(xml_v1, atom.data.Control, 1) control_v2 = atom.core.parse(xml_v2, atom.data.Control, 2) self.assert_(control_v1 is not None) self.assert_(control_v2 is not None) # Parsing with mismatched version numbers should return None. self.assert_(atom.core.parse(xml_v1, atom.data.Control, 2) is None) self.assert_(atom.core.parse(xml_v2, atom.data.Control, 1) is None) def testConvertToAndFromString(self): control = atom.data.Control() control.text = 'some text' control.draft = atom.data.Draft(text='yes') self.assertEquals(control.draft.text, 'yes') self.assertEquals(control.text, 'some text') self.assert_(isinstance(control.draft, atom.data.Draft)) new_control = atom.core.parse(str(control), atom.data.Control) self.assertEquals(control.draft.text, new_control.draft.text) self.assertEquals(control.text, new_control.text) self.assert_(isinstance(new_control.draft, atom.data.Draft)) class DraftTest(unittest.TestCase): def testConvertToAndFromString(self): draft = atom.data.Draft() draft.text = 'maybe' draft.extension_attributes['foo'] = 'bar' self.assertEquals(draft.text, 'maybe') self.assertEquals(draft.extension_attributes['foo'], 'bar') new_draft = atom.core.parse(str(draft), atom.data.Draft) self.assertEquals(draft.text, new_draft.text) self.assertEquals(draft.extension_attributes['foo'], new_draft.extension_attributes['foo']) class SourceTest(unittest.TestCase): def testConvertToAndFromString(self): source = atom.data.Source() source.author.append(atom.data.Author(name=atom.data.Name(text='js'))) source.title = atom.data.Title(text='my test source') source.generator = atom.data.Generator(text='gen') self.assert_(source.author[0].name.text == 'js') self.assert_(source.title.text == 'my test source') self.assert_(source.generator.text == 'gen') new_source = atom.core.parse(source.ToString(), atom.data.Source) self.assert_(new_source.author[0].name.text == 'js') self.assert_(new_source.title.text == 'my test source') self.assert_(new_source.generator.text == 'gen') class FeedTest(unittest.TestCase): def testConvertToAndFromString(self): feed = atom.data.Feed() feed.author.append(atom.data.Author(name=atom.data.Name(text='js'))) feed.title = atom.data.Title(text='my test source') feed.generator = atom.data.Generator(text='gen') feed.entry.append(atom.data.Entry(author=[atom.data.Author( name=atom.data.Name(text='entry author'))])) self.assert_(feed.author[0].name.text == 'js') self.assert_(feed.title.text == 'my test source') self.assert_(feed.generator.text == 'gen') self.assert_(feed.entry[0].author[0].name.text == 'entry author') new_feed = atom.core.parse(feed.ToString(), atom.data.Feed) self.assert_(new_feed.author[0].name.text == 'js') self.assert_(new_feed.title.text == 'my test source') self.assert_(new_feed.generator.text == 'gen') self.assert_(new_feed.entry[0].author[0].name.text == 'entry author') def testPreserveEntryOrder(self): test_xml = ( '' '0' '1' 'Testing Order' '2' '3' '4' '5' '6' '7' '' '8' 'feed_id' '9' '') feed = atom.core.parse(test_xml, atom.data.Feed) for i in xrange(10): self.assertEqual(feed.entry[i].id.text, str(i)) feed = atom.core.parse(feed.ToString(), atom.data.Feed) for i in xrange(10): self.assertEqual(feed.entry[i].id.text, str(i)) temp = feed.entry[3] feed.entry[3] = feed.entry[4] feed.entry[4] = temp self.assert_(feed.entry[2].id.text == '2') self.assert_(feed.entry[3].id.text == '4') self.assert_(feed.entry[4].id.text == '3') self.assert_(feed.entry[5].id.text == '5') feed = atom.core.parse(feed.to_string(), atom.data.Feed) self.assertEqual(feed.entry[2].id.text, '2') self.assertEqual(feed.entry[3].id.text, '4') self.assertEqual(feed.entry[4].id.text, '3') self.assertEqual(feed.entry[5].id.text, '5') class ContentEntryParentTest(unittest.TestCase): """The test accesses hidden methods in atom.FeedEntryParent""" def setUp(self): self.content = atom.data.Content() def testConvertToAndFromElementTree(self): self.content.text = 'my content' self.content.type = 'text' self.content.src = 'my source' self.assert_(self.content.text == 'my content') self.assert_(self.content.type == 'text') self.assert_(self.content.src == 'my source') new_content = atom.core.parse(self.content.ToString(), atom.data.Content) self.assert_(self.content.text == new_content.text) self.assert_(self.content.type == new_content.type) self.assert_(self.content.src == new_content.src) def testContentConstructorSetsSrc(self): new_content = atom.data.Content(src='abcd') self.assertEquals(new_content.src, 'abcd') def testContentFromString(self): content_xml = '' content = atom.core.parse(content_xml, atom.data.Content) self.assert_(isinstance(content, atom.data.Content)) self.assertEqual(content.type, 'test') class PreserveUnkownElementTest(unittest.TestCase): """Tests correct preservation of XML elements which are non Atom""" def setUp(self): GBASE_ATTRIBUTE_FEED = """ http://www.google.com/base/feeds/attributes 2006-11-01T20:35:59.578Z histogram for query: [item type:jobs] GoogleBase 16 1 16 http://www.google.com/base/feeds/attributes/job+industy 2006-11-01T20:36:00.100Z job industry(text) Attribute"job industry" of type text. it internet healthcare information technology accounting clerical and administrative other sales and sales management information systems engineering and architecture sales """ self.feed = atom.core.parse(GBASE_ATTRIBUTE_FEED, atom.data.Feed) def testCaptureOpenSearchElements(self): self.assertEquals(self.feed.FindExtensions('totalResults')[0].tag, 'totalResults') self.assertEquals(self.feed.FindExtensions('totalResults')[0].namespace, 'http://a9.com/-/spec/opensearchrss/1.0/') open_search_extensions = self.feed.FindExtensions( namespace='http://a9.com/-/spec/opensearchrss/1.0/') self.assertEquals(len(open_search_extensions), 3) for element in open_search_extensions: self.assertEquals(element.namespace, 'http://a9.com/-/spec/opensearchrss/1.0/') def testCaptureMetaElements(self): meta_elements = self.feed.entry[0].FindExtensions( namespace='http://base.google.com/ns-metadata/1.0') self.assertEquals(len(meta_elements), 1) self.assertEquals(meta_elements[0].attributes['count'], '4416629') self.assertEquals(len(meta_elements[0].children), 10) def testCaptureMetaChildElements(self): meta_elements = self.feed.entry[0].FindExtensions( namespace='http://base.google.com/ns-metadata/1.0') meta_children = meta_elements[0].FindChildren( namespace='http://base.google.com/ns-metadata/1.0') self.assertEquals(len(meta_children), 10) for child in meta_children: self.assertEquals(child.tag, 'value') class LinkFinderTest(unittest.TestCase): def setUp(self): self.entry = atom.core.parse(XML_ENTRY_1, atom.data.Entry) def testLinkFinderGetsLicenseLink(self): self.assert_(isinstance(self.entry.GetLink('license'), atom.data.Link)) self.assert_(isinstance(self.entry.GetLicenseLink(), atom.data.Link)) self.assertEquals(self.entry.GetLink('license').href, 'http://creativecommons.org/licenses/by-nc/2.5/rdf') self.assertEquals(self.entry.get_license_link().href, 'http://creativecommons.org/licenses/by-nc/2.5/rdf') self.assertEquals(self.entry.GetLink('license').rel, 'license') self.assertEquals(self.entry.FindLicenseLink(), 'http://creativecommons.org/licenses/by-nc/2.5/rdf') def testLinkFinderGetsAlternateLink(self): self.assert_(isinstance(self.entry.GetLink('alternate'), atom.data.Link)) self.assertEquals(self.entry.GetLink('alternate').href, 'http://www.provider-host.com/123456789') self.assertEquals(self.entry.FindAlternateLink(), 'http://www.provider-host.com/123456789') self.assertEquals(self.entry.GetLink('alternate').rel, 'alternate') class AtomBaseTest(unittest.TestCase): def testAtomBaseConvertsExtensions(self): # Using Id because it adds no additional members. atom_base = atom.data.Id() extension_child = atom.data.ExtensionElement('foo', namespace='http://ns0.com') extension_grandchild = atom.data.ExtensionElement('bar', namespace='http://ns0.com') extension_child.children.append(extension_grandchild) atom_base.extension_elements.append(extension_child) self.assertEquals(len(atom_base.extension_elements), 1) self.assertEquals(len(atom_base.extension_elements[0].children), 1) self.assertEquals(atom_base.extension_elements[0].tag, 'foo') self.assertEquals(atom_base.extension_elements[0].children[0].tag, 'bar') element_tree = atom_base._to_tree() self.assert_(element_tree.find('{http://ns0.com}foo') is not None) self.assert_(element_tree.find('{http://ns0.com}foo').find( '{http://ns0.com}bar') is not None) class UtfParsingTest(unittest.TestCase): def setUp(self): self.test_xml = u""" http://www.google.com/test/id/url αλφα """ def testMemberStringEncoding(self): atom_entry = atom.core.parse(self.test_xml, atom.data.Entry) self.assert_(isinstance(atom_entry.title.type, unicode)) self.assertEqual(atom_entry.title.type, u'\u03B1\u03BB\u03C6\u03B1') self.assertEqual(atom_entry.title.text, u'\u03B1\u03BB\u03C6\u03B1') # Setting object members to unicode strings is supported. atom_entry.title.type = u'\u03B1\u03BB\u03C6\u03B1' xml = atom_entry.ToString() # The unicode code points should be converted to XML escaped sequences. self.assert_('αλφα' in xml) # Make sure that we can use plain text when MEMBER_STRING_ENCODING is utf8 atom_entry.title.type = "plain text" atom_entry.title.text = "more text" xml = atom_entry.ToString() self.assert_("plain text" in xml) self.assert_("more text" in xml) # Test something else than utf-8 atom.core.STRING_ENCODING = 'iso8859_7' atom_entry = atom.core.parse(self.test_xml, atom.data.Entry) self.assert_(atom_entry.title.type == u'\u03B1\u03BB\u03C6\u03B1') self.assert_(atom_entry.title.text == u'\u03B1\u03BB\u03C6\u03B1') # Test using unicode strings directly for object members atom_entry = atom.core.parse(self.test_xml, atom.data.Entry) self.assert_(atom_entry.title.type == u'\u03B1\u03BB\u03C6\u03B1') self.assert_(atom_entry.title.text == u'\u03B1\u03BB\u03C6\u03B1') # Make sure that we can use plain text when MEMBER_STRING_ENCODING is # unicode atom_entry.title.type = "plain text" atom_entry.title.text = "more text" xml = atom_entry.ToString() self.assert_("plain text" in xml) self.assert_("more text" in xml) def testConvertExampleXML(self): GBASE_STRING_ENCODING_ENTRY = """ http://www.google.com/base/feeds/snippets/1749 2007-12-09T03:13:07.000Z 2008-01-07T03:26:46.000Z Digital Camera Cord Fits DSC-R1 S40 SONY \xC2\xB7 Cybershot Digital Camera Usb Cable DESCRIPTION This is a 2.5 USB 2.0 A to Mini B (5 Pin) high quality digital camera cable used for connecting your Sony Digital Cameras and Camcoders. Backward Compatible with USB 2.0, 1.0 and 1.1. Fully ... eBay Products EN US 0.99 usd http://www.example.com/pict/27_1.jpg Cameras & Photo>Digital Camera Accessories>Cables Cords & USB Cables 11729 270195049057 2008-02-06T03:26:46Z """ try: entry = atom.core.parse(GBASE_STRING_ENCODING_ENTRY, atom.data.Entry) except UnicodeDecodeError: self.fail('Error when converting XML') class VersionedXmlTest(unittest.TestCase): def test_monoversioned_parent_with_multiversioned_child(self): v2_rules = atom.data.Entry._get_rules(2) self.assert_('{http://www.w3.org/2007/app}control' in v2_rules[1]) entry_xml = """ yes """ entry = e = atom.core.parse(entry_xml, atom.data.Entry, version=2) self.assert_(entry is not None) self.assert_(entry.control is not None) self.assert_(entry.control.draft is not None) self.assertEqual(entry.control.draft.text, 'yes') # v1 rules should not parse v2 XML. entry = e = atom.core.parse(entry_xml, atom.data.Entry, version=1) self.assert_(entry is not None) self.assert_(entry.control is None) # The default version should be v1. entry = e = atom.core.parse(entry_xml, atom.data.Entry) self.assert_(entry is not None) self.assert_(entry.control is None) class DataModelSanityTest(unittest.TestCase): def test_xml_elements(self): conf.check_data_classes(self, [ atom.data.Feed, atom.data.Source, atom.data.Logo, atom.data.Control, atom.data.Draft, atom.data.Generator]) def suite(): return conf.build_suite([AuthorTest, EmailTest, NameTest, ExtensionElementTest, LinkTest, GeneratorTest, TitleTest, SubtitleTest, SummaryTest, IdTest, IconTest, LogoTest, RightsTest, UpdatedTest, PublishedTest, FeedEntryParentTest, EntryTest, ContentEntryParentTest, PreserveUnkownElementTest, FeedTest, LinkFinderTest, AtomBaseTest, UtfParsingTest, VersionedXmlTest, DataModelSanityTest]) if __name__ == '__main__': unittest.main() gdata-2.0.18/tests/atom_tests/mock_server_test.py0000750036466300116100000000730012156622363022214 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright (C) 2008 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. __author__ = 'api.jscudder (Jeff Scudder)' import unittest import gdata.service import atom.mock_service gdata.service.http_request_handler = atom.mock_service class MockRequestTest(unittest.TestCase): def setUp(self): self.request_thumbprint = atom.mock_service.MockRequest('GET', 'http://www.google.com', extra_headers={'Header1':'a', 'Header2':'b'}) def testIsMatch(self): matching_request = atom.mock_service.MockRequest('GET', 'http://www.google.com', extra_headers={'Header1':'a', 'Header2':'b', 'Header3':'c'}) bad_url = atom.mock_service.MockRequest('GET', 'http://example.com', extra_headers={'Header1':'a', 'Header2':'b', 'Header3':'c'}) # Should match because we don't check headers at the moment. bad_header = atom.mock_service.MockRequest('GET', 'http://www.google.com', extra_headers={'Header1':'a', 'Header2':'1', 'Header3':'c'}) bad_verb = atom.mock_service.MockRequest('POST', 'http://www.google.com', data='post data', extra_headers={'Header1':'a', 'Header2':'b'}) self.assertEquals(self.request_thumbprint.IsMatch(matching_request), True) self.assertEquals(self.request_thumbprint.IsMatch(bad_url), False) self.assertEquals(self.request_thumbprint.IsMatch(bad_header), True) self.assertEquals(self.request_thumbprint.IsMatch(bad_verb), False) class HttpRequestTest(unittest.TestCase): def setUp(self): atom.mock_service.recordings = [] self.client = gdata.service.GDataService() def testSimpleRecordedGet(self): recorded_request = atom.mock_service.MockRequest('GET', 'http://example.com/') recorded_response = atom.mock_service.MockHttpResponse('Got it', 200, 'OK') # Add a tuple mapping the mock request to the mock response atom.mock_service.recordings.append((recorded_request, recorded_response)) # Try a couple of GET requests which should match the recorded request. response = self.client.Get('http://example.com/', converter=str) self.assertEquals(response, 'Got it') self.client.server = 'example.com' raw_response = self.client.handler.HttpRequest(self.client, 'GET', None, '/') self.assertEquals(raw_response.read(), 'Got it') self.assertEquals(raw_response.status, 200) self.assertEquals(raw_response.reason, 'OK') class RecordRealHttpRequestsTest(unittest.TestCase): def testRecordAndReuseResponse(self): client = gdata.service.GDataService() client.server = 'www.google.com' atom.mock_service.recordings = [] atom.mock_service.real_request_handler = atom.service # Record a response real_response = atom.mock_service.HttpRequest(client, 'GET', None, 'http://www.google.com/') # Enter 'replay' mode atom.mock_service.real_request_handler = None mock_response = atom.mock_service.HttpRequest(client, 'GET', None, 'http://www.google.com/') self.assertEquals(real_response.reason, mock_response.reason) self.assertEquals(real_response.status, mock_response.status) self.assertEquals(real_response.read(), mock_response.read()) if __name__ == '__main__': unittest.main() gdata-2.0.18/tests/atom_tests/http_core_test.py0000640036466300116100000001441412156622363021666 0ustar afshareng00000000000000#!/usr/bin/env python # # Copyright (C) 2009 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. # This module is used for version 2 of the Google Data APIs. __author__ = 'j.s@google.com (Jeff Scudder)' import unittest import atom.http_core import StringIO class UriTest(unittest.TestCase): def test_parse_uri(self): uri = atom.http_core.parse_uri('http://www.google.com/test?q=foo&z=bar') self.assert_(uri.scheme == 'http') self.assert_(uri.host == 'www.google.com') self.assert_(uri.port is None) self.assert_(uri.path == '/test') self.assert_(uri.query == {'z':'bar', 'q':'foo'}) def test_static_parse_uri(self): uri = atom.http_core.Uri.parse_uri('http://test.com/?token=foo&x=1') self.assertEqual(uri.scheme, 'http') self.assertEqual(uri.host, 'test.com') self.assert_(uri.port is None) self.assertEqual(uri.query, {'token':'foo', 'x':'1'}) def test_modify_request_no_request(self): uri = atom.http_core.parse_uri('http://www.google.com/test?q=foo&z=bar') request = uri.modify_request() self.assert_(request.uri.scheme == 'http') self.assert_(request.uri.host == 'www.google.com') # If no port was provided, the HttpClient is responsible for determining # the default. self.assert_(request.uri.port is None) self.assert_(request.uri.path.startswith('/test')) self.assertEqual(request.uri.query, {'z': 'bar', 'q': 'foo'}) self.assert_(request.method is None) self.assert_(request.headers == {}) self.assert_(request._body_parts == []) def test_modify_request_http_with_set_port(self): request = atom.http_core.HttpRequest(uri=atom.http_core.Uri(port=8080), method='POST') request.add_body_part('hello', 'text/plain') uri = atom.http_core.parse_uri('//example.com/greet') self.assert_(uri.query == {}) self.assert_(uri._get_relative_path() == '/greet') self.assert_(uri.host == 'example.com') self.assert_(uri.port is None) uri.ModifyRequest(request) self.assert_(request.uri.host == 'example.com') # If no scheme was provided, the URI will not add one, but the HttpClient # should assume the request is HTTP. self.assert_(request.uri.scheme is None) self.assert_(request.uri.port == 8080) self.assert_(request.uri.path == '/greet') self.assert_(request.method == 'POST') self.assert_(request.headers['Content-Type'] == 'text/plain') def test_modify_request_use_default_ssl_port(self): request = atom.http_core.HttpRequest( uri=atom.http_core.Uri(scheme='https'), method='PUT') request.add_body_part('hello', 'text/plain') uri = atom.http_core.parse_uri('/greet') uri.modify_request(request) self.assert_(request.uri.host is None) self.assert_(request.uri.scheme == 'https') # If no port was provided, leave the port as None, it is up to the # HttpClient to set the correct default port. self.assert_(request.uri.port is None) self.assert_(request.uri.path == '/greet') self.assert_(request.method == 'PUT') self.assert_(request.headers['Content-Type'] == 'text/plain') self.assert_(len(request._body_parts) == 1) self.assert_(request._body_parts[0] == 'hello') def test_to_string(self): uri = atom.http_core.Uri(host='www.google.com', query={'q':'sippycode'}) uri_string = uri._to_string() self.assert_(uri_string == 'http://www.google.com/?q=sippycode') class HttpRequestTest(unittest.TestCase): def test_request_with_one_body_part(self): request = atom.http_core.HttpRequest() self.assert_(len(request._body_parts) == 0) self.assert_('Content-Length' not in request.headers) self.assert_(not 'Content-Type' in request.headers) self.assert_(not 'Content-Length' in request.headers) request.add_body_part('this is a test', 'text/plain') self.assert_(len(request._body_parts) == 1) self.assert_(request.headers['Content-Type'] == 'text/plain') self.assert_(request._body_parts[0] == 'this is a test') self.assert_(request.headers['Content-Length'] == str(len( 'this is a test'))) def test_add_file_without_size(self): virtual_file = StringIO.StringIO('this is a test') request = atom.http_core.HttpRequest() try: request.add_body_part(virtual_file, 'text/plain') self.fail('We should have gotten an UnknownSize error.') except atom.http_core.UnknownSize: pass request.add_body_part(virtual_file, 'text/plain', len('this is a test')) self.assert_(len(request._body_parts) == 1) self.assert_(request.headers['Content-Type'] == 'text/plain') self.assert_(request._body_parts[0].read() == 'this is a test') self.assert_(request.headers['Content-Length'] == str(len( 'this is a test'))) def test_copy(self): request = atom.http_core.HttpRequest( uri=atom.http_core.Uri(scheme='https', host='www.google.com'), method='POST', headers={'test':'1', 'ok':'yes'}) request.add_body_part('body1', 'text/plain') request.add_body_part('body2', 'text/html') copied = request._copy() self.assert_(request.uri.scheme == copied.uri.scheme) self.assert_(request.uri.host == copied.uri.host) self.assert_(request.method == copied.method) self.assert_(request.uri.path == copied.uri.path) self.assert_(request.headers == copied.headers) self.assert_(request._body_parts == copied._body_parts) copied.headers['test'] = '2' copied._body_parts[1] = 'body3' self.assert_(request.headers != copied.headers) self.assert_(request._body_parts != copied._body_parts) def suite(): return unittest.TestSuite((unittest.makeSuite(UriTest,'test'), unittest.makeSuite(HttpRequestTest,'test'))) if __name__ == '__main__': unittest.main() gdata-2.0.18/tests/atom_tests/url_test.py0000750036466300116100000000646212156622363020507 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright (C) 2008 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. __author__ = 'j.s@google.com (Jeff Scudder)' import unittest import atom.url import gdata.test_config as conf class UrlTest(unittest.TestCase): def testParseUrl(self): url = atom.url.parse_url('http://www.google.com/calendar/feeds') self.assert_(url.protocol == 'http') self.assert_(url.port is None) self.assert_(url.host == 'www.google.com') self.assert_(url.path == '/calendar/feeds') self.assert_(url.params == {}) url = atom.url.parse_url('http://example.com:6091/calendar/feeds') self.assert_(url.protocol == 'http') self.assert_(url.host == 'example.com') self.assert_(url.port == '6091') self.assert_(url.path == '/calendar/feeds') self.assert_(url.params == {}) url = atom.url.parse_url('/calendar/feeds?foo=bar') self.assert_(url.protocol is None) self.assert_(url.host is None) self.assert_(url.path == '/calendar/feeds') self.assert_(len(url.params.keys()) == 1) self.assert_('foo' in url.params) self.assert_(url.params['foo'] == 'bar') url = atom.url.parse_url('/calendar/feeds?my+foo=bar%3Dx') self.assert_(len(url.params.keys()) == 1) self.assert_('my foo' in url.params) self.assert_(url.params['my foo'] == 'bar=x') def testUrlToString(self): url = atom.url.Url(port=80) url.host = 'example.com' self.assert_(str(url), '//example.com:80') url = atom.url.Url(protocol='http', host='example.com', path='/feed') url.params['has spaces'] = 'sneaky=values?&!' self.assert_(url.to_string() == ( 'http://example.com/feed?has+spaces=sneaky%3Dvalues%3F%26%21')) def testGetRequestUri(self): url = atom.url.Url(protocol='http', host='example.com', path='/feed') url.params['has spaces'] = 'sneaky=values?&!' self.assert_(url.get_request_uri() == ( '/feed?has+spaces=sneaky%3Dvalues%3F%26%21')) self.assert_(url.get_param_string() == ( 'has+spaces=sneaky%3Dvalues%3F%26%21')) def testComparistons(self): url1 = atom.url.Url(protocol='http', host='example.com', path='/feed', params={'x':'1', 'y':'2'}) url2 = atom.url.Url(host='example.com', port=80, path='/feed', params={'y':'2', 'x':'1'}) self.assertEquals(url1, url2) url3 = atom.url.Url(host='example.com', port=81, path='/feed', params={'x':'1', 'y':'2'}) self.assert_(url1 != url3) self.assert_(url2 != url3) url4 = atom.url.Url(protocol='ftp', host='example.com', path='/feed', params={'x':'1', 'y':'2'}) self.assert_(url1 != url4) self.assert_(url2 != url4) self.assert_(url3 != url4) def suite(): return conf.build_suite([UrlTest]) if __name__ == '__main__': unittest.main() gdata-2.0.18/tests/atom_tests/__init__.py0000640036466300116100000000000012156622363020361 0ustar afshareng00000000000000gdata-2.0.18/tests/atom_tests/mock_http_test.py0000750036466300116100000000456312156622363021675 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright (C) 2008 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. __author__ = 'j.s@google.com (Jeff Scudder)' import unittest import atom.mock_http import atom.http class MockHttpClientUnitTest(unittest.TestCase): def setUp(self): self.client = atom.mock_http.MockHttpClient() def testRepondToGet(self): mock_response = atom.http_interface.HttpResponse(body='Hooray!', status=200, reason='OK') self.client.add_response(mock_response, 'GET', 'http://example.com/hooray') response = self.client.request('GET', 'http://example.com/hooray') self.assertEquals(len(self.client.recordings), 1) self.assertEquals(response.status, 200) self.assertEquals(response.read(), 'Hooray!') def testRecordResponse(self): # Turn on pass-through record mode. self.client.real_client = atom.http.ProxiedHttpClient() live_response = self.client.request('GET', 'http://www.google.com/base/feeds/snippets?max-results=1') live_response_body = live_response.read() self.assertEquals(live_response.status, 200) self.assertEquals(live_response_body.startswith('test' client = atom.client.AtomPubClient(atom.mock_http_core.EchoHttpClient()) class TestData(object): def modify_request(self, http_request): http_request.add_body_part(body_text, 'application/xml') response = client.put('http://example.org', TestData()) self.assert_(response.getheader('Echo-Host') == 'example.org:None') self.assert_(response.getheader('Echo-Uri') == '/') self.assert_(response.getheader('Echo-Method') == 'PUT') self.assert_(response.getheader('Content-Length') == str(len(body_text))) self.assert_(response.getheader('Content-Type') == 'application/xml') response = client.put(uri='http://example.org', data=TestData()) self.assert_(response.getheader('Content-Length') == str(len(body_text))) self.assert_(response.getheader('Content-Type') == 'application/xml') def test_delete(self): client = atom.client.AtomPubClient(atom.mock_http_core.EchoHttpClient(), source='my new app') response = client.Delete('http://example.com/simple') self.assertEqual(response.getheader('Echo-Host'), 'example.com:None') self.assertEqual(response.getheader('Echo-Uri'), '/simple') self.assertEqual(response.getheader('Echo-Method'), 'DELETE') response = client.delete(uri='http://example.com/d') self.assertEqual(response.getheader('Echo-Uri'), '/d') self.assertEqual(response.getheader('Echo-Method'), 'DELETE') self.assert_( response.getheader('User-Agent').startswith('my new app gdata-py/')) def suite(): return unittest.TestSuite((unittest.makeSuite(AtomPubClientEchoTest, 'test'), )) if __name__ == '__main__': unittest.main() gdata-2.0.18/tests/atom_tests/mock_http_core_test.py0000750036466300116100000002213212156622363022675 0ustar afshareng00000000000000#!/usr/bin/env python # # Copyright (C) 2009 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. # This module is used for version 2 of the Google Data APIs. # This test may make an actual HTTP request. __author__ = 'j.s@google.com (Jeff Scudder)' import unittest import StringIO import os.path import atom.mock_http_core import atom.http_core class EchoClientTest(unittest.TestCase): def test_echo_response(self): client = atom.mock_http_core.EchoHttpClient() # Send a bare-bones POST request. request = atom.http_core.HttpRequest(method='POST', uri=atom.http_core.Uri(host='www.jeffscudder.com', path='/')) request.add_body_part('hello world!', 'text/plain') response = client.request(request) self.assert_(response.getheader('Echo-Host') == 'www.jeffscudder.com:None') self.assert_(response.getheader('Echo-Uri') == '/') self.assert_(response.getheader('Echo-Scheme') is None) self.assert_(response.getheader('Echo-Method') == 'POST') self.assert_(response.getheader('Content-Length') == str(len( 'hello world!'))) self.assert_(response.getheader('Content-Type') == 'text/plain') self.assert_(response.read() == 'hello world!') # Test a path of None should default to / request = atom.http_core.HttpRequest(method='POST', uri=atom.http_core.Uri(host='www.jeffscudder.com', path=None)) response = client.request(request) self.assert_(response.getheader('Echo-Host') == 'www.jeffscudder.com:None') self.assert_(response.getheader('Echo-Method') == 'POST') self.assert_(response.getheader('Echo-Uri') == '/') # Send a multipart request. request = atom.http_core.HttpRequest(method='POST', uri=atom.http_core.Uri(scheme='https', host='www.jeffscudder.com', port=8080, path='/multipart', query={'test': 'true', 'happy': 'yes'}), headers={'Authorization':'Test xyzzy', 'Testing':'True'}) request.add_body_part('start', 'text/plain') request.add_body_part(StringIO.StringIO('hi'), 'text/html', len('hi')) request.add_body_part('alert("Greetings!")', 'text/javascript') response = client.request(request) self.assert_(response.getheader('Echo-Host') == 'www.jeffscudder.com:8080') self.assert_( response.getheader('Echo-Uri') == '/multipart?test=true&happy=yes') self.assert_(response.getheader('Echo-Scheme') == 'https') self.assert_(response.getheader('Echo-Method') == 'POST') self.assert_(response.getheader('Content-Type') == ( 'multipart/related; boundary="%s"' % (atom.http_core.MIME_BOUNDARY,))) expected_body = ('Media multipart posting' '\r\n--%s\r\n' 'Content-Type: text/plain\r\n\r\n' 'start' '\r\n--%s\r\n' 'Content-Type: text/html\r\n\r\n' 'hi' '\r\n--%s\r\n' 'Content-Type: text/javascript\r\n\r\n' 'alert("Greetings!")' '\r\n--%s--') % (atom.http_core.MIME_BOUNDARY, atom.http_core.MIME_BOUNDARY, atom.http_core.MIME_BOUNDARY, atom.http_core.MIME_BOUNDARY,) self.assert_(response.read() == expected_body) self.assert_(response.getheader('Content-Length') == str( len(expected_body))) class MockHttpClientTest(unittest.TestCase): def setUp(self): self.client = atom.mock_http_core.MockHttpClient() def test_respond_with_recording(self): request = atom.http_core.HttpRequest(method='GET') atom.http_core.parse_uri('http://www.google.com/').modify_request(request) self.client.add_response(request, 200, 'OK', body='Testing') response = self.client.request(request) self.assert_(response.status == 200) self.assert_(response.reason == 'OK') self.assert_(response.read() == 'Testing') def test_save_and_load_recordings(self): request = atom.http_core.HttpRequest(method='GET') atom.http_core.parse_uri('http://www.google.com/').modify_request(request) self.client.add_response(request, 200, 'OK', body='Testing') response = self.client.request(request) self.client._save_recordings('test_save_and_load_recordings') self.client._recordings = [] try: response = self.client.request(request) self.fail('There should be no recording for this request.') except atom.mock_http_core.NoRecordingFound: pass self.client._load_recordings('test_save_and_load_recordings') response = self.client.request(request) self.assert_(response.status == 200) self.assert_(response.reason == 'OK') self.assert_(response.read() == 'Testing') def test_use_recordings(self): request = atom.http_core.HttpRequest(method='GET') atom.http_core.parse_uri('http://www.google.com/').modify_request(request) self.client._load_or_use_client('test_use_recordings', atom.http_core.HttpClient()) response = self.client.request(request) if self.client.real_client: self.client._save_recordings('test_use_recordings') self.assert_(response.status in (200, 302)) self.assert_(response.reason in ('OK', 'Found')) self.assert_(response.getheader('server') == 'gws') body = response.read() self.assert_(body.startswith('') or body.startswith('')) def test_match_request(self): x = atom.http_core.HttpRequest('http://example.com/', 'GET') y = atom.http_core.HttpRequest('http://example.com/', 'GET') self.assert_(atom.mock_http_core._match_request(x, y)) y = atom.http_core.HttpRequest('http://example.com/', 'POST') self.assert_(not atom.mock_http_core._match_request(x, y)) y = atom.http_core.HttpRequest('http://example.com/1', 'GET') self.assert_(not atom.mock_http_core._match_request(x, y)) y = atom.http_core.HttpRequest('http://example.com/?gsessionid=1', 'GET') self.assert_(not atom.mock_http_core._match_request(x, y)) y = atom.http_core.HttpRequest('http://example.com/?start_index=1', 'GET') self.assert_(atom.mock_http_core._match_request(x, y)) x = atom.http_core.HttpRequest('http://example.com/?gsessionid=1', 'GET') y = atom.http_core.HttpRequest('http://example.com/?gsessionid=1', 'GET') self.assert_(atom.mock_http_core._match_request(x, y)) y = atom.http_core.HttpRequest('http://example.com/?gsessionid=2', 'GET') self.assert_(not atom.mock_http_core._match_request(x, y)) y = atom.http_core.HttpRequest('http://example.com/', 'GET') self.assert_(not atom.mock_http_core._match_request(x, y)) def test_use_named_sessions(self): self.client._delete_recordings('mock_http_test.test_use_named_sessions') self.client.use_cached_session('mock_http_test.test_use_named_sessions', atom.mock_http_core.EchoHttpClient()) request = atom.http_core.HttpRequest('http://example.com', 'GET') response = self.client.request(request) self.assertEqual(response.getheader('Echo-Method'), 'GET') self.assertEqual(response.getheader('Echo-Host'), 'example.com:None') # We will insert a Cache-Marker header to indicate that this is a # recorded response, but initially it should not be present. self.assertEqual(response.getheader('Cache-Marker'), None) # Modify the recorded response to allow us to identify a cached result # from an echoed result. We need to be able to check to see if this # came from a recording. self.assert_('Cache-Marker' not in self.client._recordings[0][1]._headers) self.client._recordings[0][1]._headers['Cache-Marker'] = '1' self.assert_('Cache-Marker' in self.client._recordings[0][1]._headers) # Save the recorded responses. self.client.close_session() # Create a new client, and have it use the recorded session. client = atom.mock_http_core.MockHttpClient() client.use_cached_session('mock_http_test.test_use_named_sessions', atom.mock_http_core.EchoHttpClient()) # Make the same request, which should use the recorded result. response = client.request(request) self.assertEqual(response.getheader('Echo-Method'), 'GET') self.assertEqual(response.getheader('Echo-Host'), 'example.com:None') # We should now see the cache marker since the response is replayed. self.assertEqual(response.getheader('Cache-Marker'), '1') def suite(): return unittest.TestSuite((unittest.makeSuite(MockHttpClientTest, 'test'), unittest.makeSuite(EchoClientTest, 'test'))) if __name__ == '__main__': unittest.main() gdata-2.0.18/tests/atom_tests/http_interface_test.py0000750036466300116100000000272212156622363022677 0ustar afshareng00000000000000#!/usr/bin/python # # Copyright (C) 2008 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. __author__ = 'api.jscudder (Jeff Scudder)' import unittest import atom.http_interface import StringIO class HttpResponseTest(unittest.TestCase): def testConstructorWithStrings(self): resp = atom.http_interface.HttpResponse(body='Hi there!', status=200, reason='OK', headers={'Content-Length':'9'}) self.assertEqual(resp.read(amt=1), 'H') self.assertEqual(resp.read(amt=2), 'i ') self.assertEqual(resp.read(), 'there!') self.assertEqual(resp.read(), '') self.assertEqual(resp.reason, 'OK') self.assertEqual(resp.status, 200) self.assertEqual(resp.getheader('Content-Length'), '9') self.assert_(resp.getheader('Missing') is None) self.assertEqual(resp.getheader('Missing', default='yes'), 'yes') def suite(): return unittest.TestSuite((unittest.makeSuite(HttpResponseTest,'test'),)) if __name__ == '__main__': unittest.main() gdata-2.0.18/tests/atom_tests/auth_test.py0000640036466300116100000000247612156622363020645 0ustar afshareng00000000000000#!/usr/bin/env python # # Copyright (C) 2009 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. # This module is used for version 2 of the Google Data APIs. __author__ = 'j.s@google.com (Jeff Scudder)' import unittest import atom.auth import atom.http_core class BasicAuthTest(unittest.TestCase): def test_modify_request(self): http_request = atom.http_core.HttpRequest() credentials = atom.auth.BasicAuth('Aladdin', 'open sesame') self.assert_(credentials.basic_cookie == 'QWxhZGRpbjpvcGVuIHNlc2FtZQ==') credentials.modify_request(http_request) self.assert_(http_request.headers[ 'Authorization'] == 'Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==') def suite(): return unittest.TestSuite((unittest.makeSuite(BasicAuthTest,'test'),)) if __name__ == '__main__': unittest.main() gdata-2.0.18/tests/run_service_tests.py0000750036466300116100000001024312156622363020222 0ustar afshareng00000000000000#!/usr/bin/python import sys import unittest import module_test_runner import getopt import getpass # Modules whose tests we will run. import atom_tests.service_test import gdata_tests.service_test import gdata_tests.apps.service_test import gdata_tests.books.service_test import gdata_tests.calendar.service_test import gdata_tests.docs.service_test import gdata_tests.health.service_test import gdata_tests.spreadsheet.service_test import gdata_tests.spreadsheet.text_db_test import gdata_tests.photos.service_test import gdata_tests.contacts.service_test import gdata_tests.blogger.service_test import gdata_tests.youtube.service_test import gdata_tests.health.service_test import gdata_tests.contacts.profiles.service_test def RunAllTests(username, password, spreadsheet_key, worksheet_key, apps_username, apps_password, apps_domain): test_runner = module_test_runner.ModuleTestRunner() test_runner.modules = [atom_tests.service_test, gdata_tests.service_test, gdata_tests.apps.service_test, gdata_tests.base.service_test, gdata_tests.books.service_test, gdata_tests.calendar.service_test, gdata_tests.docs.service_test, gdata_tests.health.service_test, gdata_tests.spreadsheet.service_test, gdata_tests.spreadsheet.text_db_test, gdata_tests.contacts.service_test, gdata_tests.youtube.service_test, gdata_tests.photos.service_test, gdata_tests.contacts.profiles.service_test,] test_runner.settings = {'username':username, 'password':password, 'test_image_location':'testimage.jpg', 'ss_key':spreadsheet_key, 'ws_key':worksheet_key, 'apps_username':apps_username, 'apps_password':apps_password, 'apps_domain':apps_domain} test_runner.RunAllTests() def GetValuesForTestSettingsAndRunAllTests(): username = '' password = '' spreadsheet_key = '' worksheet_key = '' apps_domain = '' apps_username = '' apps_password = '' print ('NOTE: Please run these tests only with a test account. ' 'The tests may delete or update your data.') try: opts, args = getopt.getopt(sys.argv[1:], '', ['username=', 'password=', 'ss_key=', 'ws_key=', 'apps_username=', 'apps_password=', 'apps_domain=']) for o, a in opts: if o == '--username': username = a elif o == '--password': password = a elif o == '--ss_key': spreadsheet_key = a elif o == '--ws_key': worksheet_key = a elif o == '--apps_username': apps_username = a elif o == '--apps_password': apps_password = a elif o == '--apps_domain': apps_domain = a except getopt.GetoptError: pass if username == '' and password == '': print ('Missing --user and --pw command line arguments, ' 'prompting for credentials.') if username == '': username = raw_input('Please enter your username: ') if password == '': password = getpass.getpass() if spreadsheet_key == '': spreadsheet_key = raw_input( 'Please enter the key for the test spreadsheet: ') if worksheet_key == '': worksheet_key = raw_input( 'Please enter the id for the worksheet to be edited: ') if apps_username == '': apps_username = raw_input('Please enter your Google Apps admin username: ') if apps_password == '': apps_password = getpass.getpass() if apps_domain == '': apps_domain = raw_input('Please enter your Google Apps domain: ') RunAllTests(username, password, spreadsheet_key, worksheet_key, apps_username, apps_password, apps_domain) if __name__ == '__main__': GetValuesForTestSettingsAndRunAllTests() gdata-2.0.18/tests/atom_test.py0000750036466300116100000006422712156622363016466 0ustar afshareng00000000000000#!/usr/bin/python # -*-*- encoding: utf-8 -*-*- # # Copyright (C) 2006 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. __author__ = 'j.s@google.com (Jeff Scudder)' import sys import unittest try: from xml.etree import ElementTree except ImportError: from elementtree import ElementTree import atom from gdata import test_data import gdata.test_config as conf class AuthorTest(unittest.TestCase): def setUp(self): self.author = atom.Author() def testEmptyAuthorShouldHaveEmptyExtensionsList(self): self.assert_(isinstance(self.author.extension_elements, list)) self.assert_(len(self.author.extension_elements) == 0) def testNormalAuthorShouldHaveNoExtensionElements(self): self.author.name = atom.Name(text='Jeff Scudder') self.assert_(self.author.name.text == 'Jeff Scudder') self.assert_(len(self.author.extension_elements) == 0) new_author = atom.AuthorFromString(self.author.ToString()) self.assert_(len(self.author.extension_elements) == 0) self.author.extension_elements.append(atom.ExtensionElement( 'foo', text='bar')) self.assert_(len(self.author.extension_elements) == 1) self.assert_(self.author.name.text == 'Jeff Scudder') new_author = atom.AuthorFromString(self.author.ToString()) self.assert_(len(self.author.extension_elements) == 1) self.assert_(new_author.name.text == 'Jeff Scudder') def testEmptyAuthorToAndFromStringShouldMatch(self): string_from_author = self.author.ToString() new_author = atom.AuthorFromString(string_from_author) string_from_new_author = new_author.ToString() self.assert_(string_from_author == string_from_new_author) def testAuthorWithNameToAndFromStringShouldMatch(self): self.author.name = atom.Name() self.author.name.text = 'Jeff Scudder' string_from_author = self.author.ToString() new_author = atom.AuthorFromString(string_from_author) string_from_new_author = new_author.ToString() self.assert_(string_from_author == string_from_new_author) self.assert_(self.author.name.text == new_author.name.text) def testExtensionElements(self): self.author.extension_attributes['foo1'] = 'bar' self.author.extension_attributes['foo2'] = 'rab' self.assert_(self.author.extension_attributes['foo1'] == 'bar') self.assert_(self.author.extension_attributes['foo2'] == 'rab') new_author = atom.AuthorFromString(self.author.ToString()) self.assert_(new_author.extension_attributes['foo1'] == 'bar') self.assert_(new_author.extension_attributes['foo2'] == 'rab') def testConvertFullAuthorToAndFromString(self): author = atom.AuthorFromString(test_data.TEST_AUTHOR) self.assert_(author.name.text == 'John Doe') self.assert_(author.email.text == 'johndoes@someemailadress.com') self.assert_(author.uri.text == 'http://www.google.com') class EmailTest(unittest.TestCase): def setUp(self): self.email = atom.Email() def testEmailToAndFromString(self): self.email.text = 'This is a test' new_email = atom.EmailFromString(self.email.ToString()) self.assert_(self.email.text == new_email.text) self.assert_(self.email.extension_elements == new_email.extension_elements) class NameTest(unittest.TestCase): def setUp(self): self.name = atom.Name() def testEmptyNameToAndFromStringShouldMatch(self): string_from_name = self.name.ToString() new_name = atom.NameFromString(string_from_name) string_from_new_name = new_name.ToString() self.assert_(string_from_name == string_from_new_name) def testText(self): self.assert_(self.name.text is None) self.name.text = 'Jeff Scudder' self.assert_(self.name.text == 'Jeff Scudder') new_name = atom.NameFromString(self.name.ToString()) self.assert_(new_name.text == self.name.text) def testExtensionElements(self): self.name.extension_attributes['foo'] = 'bar' self.assert_(self.name.extension_attributes['foo'] == 'bar') new_name = atom.NameFromString(self.name.ToString()) self.assert_(new_name.extension_attributes['foo'] == 'bar') class ExtensionElementTest(unittest.TestCase): def setUp(self): self.ee = atom.ExtensionElement('foo') def testEmptyEEShouldProduceEmptyString(self): pass def testEEParsesTreeCorrectly(self): deep_tree = atom.ExtensionElementFromString(test_data.EXTENSION_TREE) self.assert_(deep_tree.tag == 'feed') self.assert_(deep_tree.namespace == 'http://www.w3.org/2005/Atom') self.assert_(deep_tree.children[0].tag == 'author') self.assert_(deep_tree.children[0].namespace == 'http://www.google.com') self.assert_(deep_tree.children[0].children[0].tag == 'name') self.assert_(deep_tree.children[0].children[0].namespace == 'http://www.google.com') self.assert_(deep_tree.children[0].children[0].text.strip() == 'John Doe') self.assert_(deep_tree.children[0].children[0].children[0].text.strip() == 'Bar') foo = deep_tree.children[0].children[0].children[0] self.assert_(foo.tag == 'foo') self.assert_(foo.namespace == 'http://www.google.com') self.assert_(foo.attributes['up'] == 'down') self.assert_(foo.attributes['yes'] == 'no') self.assert_(foo.children == []) def testEEToAndFromStringShouldMatch(self): string_from_ee = self.ee.ToString() new_ee = atom.ExtensionElementFromString(string_from_ee) string_from_new_ee = new_ee.ToString() self.assert_(string_from_ee == string_from_new_ee) deep_tree = atom.ExtensionElementFromString(test_data.EXTENSION_TREE) string_from_deep_tree = deep_tree.ToString() new_deep_tree = atom.ExtensionElementFromString(string_from_deep_tree) string_from_new_deep_tree = new_deep_tree.ToString() self.assert_(string_from_deep_tree == string_from_new_deep_tree) class LinkTest(unittest.TestCase): def setUp(self): self.link = atom.Link() def testLinkToAndFromString(self): self.link.href = 'test href' self.link.hreflang = 'english' self.link.type = 'text/html' self.link.extension_attributes['foo'] = 'bar' self.assert_(self.link.href == 'test href') self.assert_(self.link.hreflang == 'english') self.assert_(self.link.type == 'text/html') self.assert_(self.link.extension_attributes['foo'] == 'bar') new_link = atom.LinkFromString(self.link.ToString()) self.assert_(self.link.href == new_link.href) self.assert_(self.link.type == new_link.type) self.assert_(self.link.hreflang == new_link.hreflang) self.assert_(self.link.extension_attributes['foo'] == new_link.extension_attributes['foo']) def testLinkType(self): test_link = atom.Link(link_type='text/html') self.assert_(test_link.type == 'text/html') class GeneratorTest(unittest.TestCase): def setUp(self): self.generator = atom.Generator() def testGeneratorToAndFromString(self): self.generator.uri = 'www.google.com' self.generator.version = '1.0' self.generator.extension_attributes['foo'] = 'bar' self.assert_(self.generator.uri == 'www.google.com') self.assert_(self.generator.version == '1.0') self.assert_(self.generator.extension_attributes['foo'] == 'bar') new_generator = atom.GeneratorFromString(self.generator.ToString()) self.assert_(self.generator.uri == new_generator.uri) self.assert_(self.generator.version == new_generator.version) self.assert_(self.generator.extension_attributes['foo'] == new_generator.extension_attributes['foo']) class TitleTest(unittest.TestCase): def setUp(self): self.title = atom.Title() def testTitleToAndFromString(self): self.title.type = 'text' self.title.text = 'Less: <' self.assert_(self.title.type == 'text') self.assert_(self.title.text == 'Less: <') new_title = atom.TitleFromString(self.title.ToString()) self.assert_(self.title.type == new_title.type) self.assert_(self.title.text == new_title.text) class SubtitleTest(unittest.TestCase): def setUp(self): self.subtitle = atom.Subtitle() def testTitleToAndFromString(self): self.subtitle.type = 'text' self.subtitle.text = 'sub & title' self.assert_(self.subtitle.type == 'text') self.assert_(self.subtitle.text == 'sub & title') new_subtitle = atom.SubtitleFromString(self.subtitle.ToString()) self.assert_(self.subtitle.type == new_subtitle.type) self.assert_(self.subtitle.text == new_subtitle.text) class SummaryTest(unittest.TestCase): def setUp(self): self.summary = atom.Summary() def testTitleToAndFromString(self): self.summary.type = 'text' self.summary.text = 'Less: <' self.assert_(self.summary.type == 'text') self.assert_(self.summary.text == 'Less: <') new_summary = atom.SummaryFromString(self.summary.ToString()) self.assert_(self.summary.type == new_summary.type) self.assert_(self.summary.text == new_summary.text) class CategoryTest(unittest.TestCase): def setUp(self): self.category = atom.Category() def testCategoryToAndFromString(self): self.category.term = 'x' self.category.scheme = 'y' self.category.label = 'z' self.assert_(self.category.term == 'x') self.assert_(self.category.scheme == 'y') self.assert_(self.category.label == 'z') new_category = atom.CategoryFromString(self.category.ToString()) self.assert_(self.category.term == new_category.term) self.assert_(self.category.scheme == new_category.scheme) self.assert_(self.category.label == new_category.label) class ContributorTest(unittest.TestCase): def setUp(self): self.contributor = atom.Contributor() def testContributorToAndFromString(self): self.contributor.name = atom.Name(text='J Scud') self.contributor.email = atom.Email(text='nobody@nowhere') self.contributor.uri = atom.Uri(text='http://www.google.com') self.assert_(self.contributor.name.text == 'J Scud') self.assert_(self.contributor.email.text == 'nobody@nowhere') self.assert_(self.contributor.uri.text == 'http://www.google.com') new_contributor = atom.ContributorFromString(self.contributor.ToString()) self.assert_(self.contributor.name.text == new_contributor.name.text) self.assert_(self.contributor.email.text == new_contributor.email.text) self.assert_(self.contributor.uri.text == new_contributor.uri.text) class IdTest(unittest.TestCase): def setUp(self): self.my_id = atom.Id() def testIdToAndFromString(self): self.my_id.text = 'my nifty id' self.assert_(self.my_id.text == 'my nifty id') new_id = atom.IdFromString(self.my_id.ToString()) self.assert_(self.my_id.text == new_id.text) class IconTest(unittest.TestCase): def setUp(self): self.icon = atom.Icon() def testIconToAndFromString(self): self.icon.text = 'my picture' self.assert_(self.icon.text == 'my picture') new_icon = atom.IconFromString(str(self.icon)) self.assert_(self.icon.text == new_icon.text) class LogoTest(unittest.TestCase): def setUp(self): self.logo = atom.Logo() def testLogoToAndFromString(self): self.logo.text = 'my logo' self.assert_(self.logo.text == 'my logo') new_logo = atom.LogoFromString(self.logo.ToString()) self.assert_(self.logo.text == new_logo.text) class RightsTest(unittest.TestCase): def setUp(self): self.rights = atom.Rights() def testContributorToAndFromString(self): self.rights.text = 'you have the right to remain silent' self.rights.type = 'text' self.assert_(self.rights.text == 'you have the right to remain silent') self.assert_(self.rights.type == 'text') new_rights = atom.RightsFromString(self.rights.ToString()) self.assert_(self.rights.text == new_rights.text) self.assert_(self.rights.type == new_rights.type) class UpdatedTest(unittest.TestCase): def setUp(self): self.updated = atom.Updated() def testUpdatedToAndFromString(self): self.updated.text = 'my time' self.assert_(self.updated.text == 'my time') new_updated = atom.UpdatedFromString(self.updated.ToString()) self.assert_(self.updated.text == new_updated.text) class PublishedTest(unittest.TestCase): def setUp(self): self.published = atom.Published() def testPublishedToAndFromString(self): self.published.text = 'pub time' self.assert_(self.published.text == 'pub time') new_published = atom.PublishedFromString(self.published.ToString()) self.assert_(self.published.text == new_published.text) class FeedEntryParentTest(unittest.TestCase): """The test accesses hidden methods in atom.FeedEntryParent""" def testConvertToAndFromElementTree(self): # Use entry because FeedEntryParent doesn't have a tag or namespace. original = atom.Entry() copy = atom.FeedEntryParent() original.author.append(atom.Author(name=atom.Name(text='J Scud'))) self.assert_(original.author[0].name.text == 'J Scud') self.assert_(copy.author == []) original.id = atom.Id(text='test id') self.assert_(original.id.text == 'test id') self.assert_(copy.id is None) copy._HarvestElementTree(original._ToElementTree()) self.assert_(original.author[0].name.text == copy.author[0].name.text) self.assert_(original.id.text == copy.id.text) class EntryTest(unittest.TestCase): def testConvertToAndFromString(self): entry = atom.Entry() entry.author.append(atom.Author(name=atom.Name(text='js'))) entry.title = atom.Title(text='my test entry') self.assert_(entry.author[0].name.text == 'js') self.assert_(entry.title.text == 'my test entry') new_entry = atom.EntryFromString(entry.ToString()) self.assert_(new_entry.author[0].name.text == 'js') self.assert_(new_entry.title.text == 'my test entry') def testEntryCorrectlyConvertsActualData(self): entry = atom.EntryFromString(test_data.XML_ENTRY_1) self.assert_(entry.category[0].scheme == 'http://base.google.com/categories/itemtypes') self.assert_(entry.category[0].term == 'products') self.assert_(entry.id.text == ' http://www.google.com/test/id/url ') self.assert_(entry.title.text == 'Testing 2000 series laptop') self.assert_(entry.title.type == 'text') self.assert_(entry.content.type == 'xhtml') #TODO check all other values for the test entry def testAppControl(self): entry = atom.EntryFromString(test_data.TEST_BASE_ENTRY) self.assertEquals(entry.control.draft.text, 'yes') self.assertEquals(len(entry.control.extension_elements), 1) self.assertEquals(entry.control.extension_elements[0].tag, 'disapproved') class ControlTest(unittest.TestCase): def testConvertToAndFromString(self): control = atom.Control() control.text = 'some text' control.draft = atom.Draft(text='yes') self.assertEquals(control.draft.text, 'yes') self.assertEquals(control.text, 'some text') self.assertEquals(isinstance(control.draft, atom.Draft), True) new_control = atom.ControlFromString(str(control)) self.assertEquals(control.draft.text, new_control.draft.text) self.assertEquals(control.text, new_control.text) self.assertEquals(isinstance(new_control.draft, atom.Draft), True) class DraftTest(unittest.TestCase): def testConvertToAndFromString(self): draft = atom.Draft() draft.text = 'maybe' draft.extension_attributes['foo'] = 'bar' self.assertEquals(draft.text, 'maybe') self.assertEquals(draft.extension_attributes['foo'], 'bar') new_draft = atom.DraftFromString(str(draft)) self.assertEquals(draft.text, new_draft.text) self.assertEquals(draft.extension_attributes['foo'], new_draft.extension_attributes['foo']) class SourceTest(unittest.TestCase): def testConvertToAndFromString(self): source = atom.Source() source.author.append(atom.Author(name=atom.Name(text='js'))) source.title = atom.Title(text='my test source') source.generator = atom.Generator(text='gen') self.assert_(source.author[0].name.text == 'js') self.assert_(source.title.text == 'my test source') self.assert_(source.generator.text == 'gen') new_source = atom.SourceFromString(source.ToString()) self.assert_(new_source.author[0].name.text == 'js') self.assert_(new_source.title.text == 'my test source') self.assert_(new_source.generator.text == 'gen') class FeedTest(unittest.TestCase): def testConvertToAndFromString(self): feed = atom.Feed() feed.author.append(atom.Author(name=atom.Name(text='js'))) feed.title = atom.Title(text='my test source') feed.generator = atom.Generator(text='gen') feed.entry.append(atom.Entry(author=[atom.Author(name=atom.Name( text='entry author'))])) self.assert_(feed.author[0].name.text == 'js') self.assert_(feed.title.text == 'my test source') self.assert_(feed.generator.text == 'gen') self.assert_(feed.entry[0].author[0].name.text == 'entry author') new_feed = atom.FeedFromString(feed.ToString()) self.assert_(new_feed.author[0].name.text == 'js') self.assert_(new_feed.title.text == 'my test source') self.assert_(new_feed.generator.text == 'gen') self.assert_(new_feed.entry[0].author[0].name.text == 'entry author') def testPreserveEntryOrder(self): test_xml = ( '' '0' '1' 'Testing Order' '2' '3' '4' '5' '6' '7' '' '8' 'feed_id' '9' '') feed = atom.FeedFromString(test_xml) for i in xrange(10): self.assert_(feed.entry[i].id.text == str(i)) feed = atom.FeedFromString(feed.ToString()) for i in xrange(10): self.assert_(feed.entry[i].id.text == str(i)) temp = feed.entry[3] feed.entry[3] = feed.entry[4] feed.entry[4] = temp self.assert_(feed.entry[2].id.text == '2') self.assert_(feed.entry[3].id.text == '4') self.assert_(feed.entry[4].id.text == '3') self.assert_(feed.entry[5].id.text == '5') feed = atom.FeedFromString(feed.ToString()) self.assert_(feed.entry[2].id.text == '2') self.assert_(feed.entry[3].id.text == '4') self.assert_(feed.entry[4].id.text == '3') self.assert_(feed.entry[5].id.text == '5') class ContentEntryParentTest(unittest.TestCase): """The test accesses hidden methods in atom.FeedEntryParent""" def setUp(self): self.content = atom.Content() def testConvertToAndFromElementTree(self): self.content.text = 'my content' self.content.type = 'text' self.content.src = 'my source' self.assert_(self.content.text == 'my content') self.assert_(self.content.type == 'text') self.assert_(self.content.src == 'my source') new_content = atom.ContentFromString(self.content.ToString()) self.assert_(self.content.text == new_content.text) self.assert_(self.content.type == new_content.type) self.assert_(self.content.src == new_content.src) def testContentConstructorSetsSrc(self): new_content = atom.Content(src='abcd') self.assertEquals(new_content.src, 'abcd') class PreserveUnkownElementTest(unittest.TestCase): """Tests correct preservation of XML elements which are non Atom""" def setUp(self): self.feed = atom.FeedFromString(test_data.GBASE_ATTRIBUTE_FEED) def testCaptureOpenSearchElements(self): self.assertEquals(self.feed.FindExtensions('totalResults')[0].tag, 'totalResults') self.assertEquals(self.feed.FindExtensions('totalResults')[0].namespace, 'http://a9.com/-/spec/opensearchrss/1.0/') open_search_extensions = self.feed.FindExtensions( namespace='http://a9.com/-/spec/opensearchrss/1.0/') self.assertEquals(len(open_search_extensions), 3) for element in open_search_extensions: self.assertEquals(element.namespace, 'http://a9.com/-/spec/opensearchrss/1.0/') def testCaptureMetaElements(self): meta_elements = self.feed.entry[0].FindExtensions( namespace='http://base.google.com/ns-metadata/1.0') self.assertEquals(len(meta_elements), 1) self.assertEquals(meta_elements[0].attributes['count'], '4416629') self.assertEquals(len(meta_elements[0].children), 10) def testCaptureMetaChildElements(self): meta_elements = self.feed.entry[0].FindExtensions( namespace='http://base.google.com/ns-metadata/1.0') meta_children = meta_elements[0].FindChildren( namespace='http://base.google.com/ns-metadata/1.0') self.assertEquals(len(meta_children), 10) for child in meta_children: self.assertEquals(child.tag, 'value') class LinkFinderTest(unittest.TestCase): def setUp(self): self.entry = atom.EntryFromString(test_data.XML_ENTRY_1) def testLinkFinderGetsLicenseLink(self): self.assertEquals(isinstance(self.entry.GetLicenseLink(), atom.Link), True) self.assertEquals(self.entry.GetLicenseLink().href, 'http://creativecommons.org/licenses/by-nc/2.5/rdf') self.assertEquals(self.entry.GetLicenseLink().rel, 'license') def testLinkFinderGetsAlternateLink(self): self.assertEquals(isinstance(self.entry.GetAlternateLink(), atom.Link), True) self.assertEquals(self.entry.GetAlternateLink().href, 'http://www.provider-host.com/123456789') self.assertEquals(self.entry.GetAlternateLink().rel, 'alternate') class AtomBaseTest(unittest.TestCase): def testAtomBaseConvertsExtensions(self): # Using Id because it adds no additional members. atom_base = atom.Id() extension_child = atom.ExtensionElement('foo', namespace='http://ns0.com') extension_grandchild = atom.ExtensionElement('bar', namespace='http://ns0.com') extension_child.children.append(extension_grandchild) atom_base.extension_elements.append(extension_child) self.assertEquals(len(atom_base.extension_elements), 1) self.assertEquals(len(atom_base.extension_elements[0].children), 1) self.assertEquals(atom_base.extension_elements[0].tag, 'foo') self.assertEquals(atom_base.extension_elements[0].children[0].tag, 'bar') element_tree = atom_base._ToElementTree() self.assert_(element_tree.find('{http://ns0.com}foo') is not None) self.assert_(element_tree.find('{http://ns0.com}foo').find( '{http://ns0.com}bar') is not None) class UtfParsingTest(unittest.TestCase): def setUp(self): self.test_xml = u""" http://www.google.com/test/id/url \u03B1\u03BB\u03C6\u03B1 """ def testMemberStringEncoding(self): atom_entry = atom.EntryFromString(self.test_xml) #self.assertEqual(atom_entry.title.type.encode('utf-8'), # u'\u03B1\u03BB\u03C6\u03B1'.encode('utf-8')) #self.assertEqual(atom_entry.title.text.encode('utf-8'), # u'\u03B1\u03BB\u03C6\u03B1'.encode('utf-8')) # Setting object members to unicode strings is supported even if # MEMBER_STRING_ENCODING is set 'utf-8' (should it be?) atom_entry.title.type = u'\u03B1\u03BB\u03C6\u03B1' xml = atom_entry.ToString() self.assert_(u'\u03B1\u03BB\u03C6\u03B1'.encode('utf-8') in xml) # Make sure that we can use plain text when MEMBER_STRING_ENCODING is utf8 atom_entry.title.type = "plain text" atom_entry.title.text = "more text" xml = atom_entry.ToString() self.assert_("plain text" in xml) self.assert_("more text" in xml) # Test something else than utf-8 atom.MEMBER_STRING_ENCODING = 'iso8859_7' atom_entry = atom.EntryFromString(self.test_xml) self.assert_(atom_entry.title.type == u'\u03B1\u03BB\u03C6\u03B1'.encode( 'iso8859_7')) self.assert_(atom_entry.title.text == u'\u03B1\u03BB\u03C6\u03B1'.encode( 'iso8859_7')) # Test using unicode strings directly for object members atom.MEMBER_STRING_ENCODING = unicode atom_entry = atom.EntryFromString(self.test_xml) self.assert_(atom_entry.title.type == u'\u03B1\u03BB\u03C6\u03B1') self.assert_(atom_entry.title.text == u'\u03B1\u03BB\u03C6\u03B1') # Make sure that we can use plain text when MEMBER_STRING_ENCODING is # unicode atom_entry.title.type = "plain text" atom_entry.title.text = "more text" xml = atom_entry.ToString() self.assert_("plain text" in xml) self.assert_("more text" in xml) def testConvertExampleXML(self): try: entry = atom.CreateClassFromXMLString(atom.Entry, test_data.GBASE_STRING_ENCODING_ENTRY) except UnicodeDecodeError: self.fail('Error when converting XML') class DeprecationDecoratorTest(unittest.TestCase): def testDeprecationWarning(self): def to_deprecate(): return 5 self.assertEqual(to_deprecate.func_name, 'to_deprecate') deprecated = atom.deprecated('test')(to_deprecate) self.assertNotEqual(to_deprecate, deprecated) # After decorating a function as deprecated, the function name should # still be the name of the original function. self.assertEqual(deprecated.func_name, 'to_deprecate') #@atom.deprecated() def also_deprecated(): return 6 also_deprecated = atom.deprecated()(also_deprecated) self.assertEqual(also_deprecated.func_name, 'also_deprecated') def suite(): return conf.build_suite([AuthorTest, EmailTest, NameTest, ExtensionElementTest, LinkTest, GeneratorTest, TitleTest, SubtitleTest, SummaryTest, IdTest, IconTest, LogoTest, RightsTest, UpdatedTest, PublishedTest, FeedEntryParentTest, EntryTest, ContentEntryParentTest, PreserveUnkownElementTest, FeedTest, LinkFinderTest, AtomBaseTest, UtfParsingTest, DeprecationDecoratorTest]) if __name__ == '__main__': unittest.main() gdata-2.0.18/tests/run_data_tests.py0000750036466300116100000000432312156622363017475 0ustar afshareng00000000000000#!/usr/bin/python import sys import unittest import module_test_runner import getopt import getpass # Modules whose tests we will run. import gdata_test import atom_test import atom_tests.http_interface_test import atom_tests.mock_http_test import atom_tests.token_store_test import atom_tests.url_test import atom_tests.core_test import gdata_tests.apps.emailsettings.data_test import gdata_tests.apps.multidomain.data_test import gdata_tests.apps_test import gdata_tests.auth_test import gdata_tests.books_test import gdata_tests.blogger_test import gdata_tests.calendar_test import gdata_tests.calendar_resource.data_test import gdata_tests.client_test import gdata_tests.codesearch_test import gdata_tests.contacts_test import gdata_tests.docs_test import gdata_tests.health_test import gdata_tests.photos_test import gdata_tests.spreadsheet_test import gdata_tests.youtube_test import gdata_tests.webmastertools_test import gdata_tests.oauth.data_test def RunAllTests(): test_runner = module_test_runner.ModuleTestRunner() test_runner.modules = [gdata_test, atom_test, atom_tests.url_test, atom_tests.http_interface_test, atom_tests.mock_http_test, atom_tests.core_test, atom_tests.token_store_test, gdata_tests.client_test, gdata_tests.apps_test, gdata_tests.apps.emailsettings.data_test, gdata_tests.apps.multidomain.data_test, gdata_tests.auth_test, gdata_tests.books_test, gdata_tests.calendar_test, gdata_tests.docs_test, gdata_tests.health_test, gdata_tests.spreadsheet_test, gdata_tests.photos_test, gdata_tests.codesearch_test, gdata_tests.contacts_test, gdata_tests.youtube_test, gdata_tests.blogger_test, gdata_tests.webmastertools_test, gdata_tests.calendar_resource.data_test, gdata_tests.oauth.data_test] test_runner.RunAllTests() if __name__ == '__main__': RunAllTests() gdata-2.0.18/INSTALL.txt0000640036466300116100000002530312156622362014607 0ustar afshareng00000000000000To get started using the library, you need to make sure that the library and dependencies can be imported. Short instructions: sudo python setup.py install or python setup.py install --home=~ and set your PYTHONPATH to include your home directory. Long instructions copied from the following article from Aug. 2007: Getting Started with the Google Data Python Library http://code.google.com/support/bin/answer.py?answer=75582 ==Introduction== So you've decided to use the Google data Python client library to write an application using one of the many Google data services. Excellent choice! My aim with this short tutorial is to quickly get you started in using the client library to develop your application. You probably want to jump in and start creating your application right away. First though, you may need to configure your development environment and set up the tools you'll need to run the modules included in the client library. Follow the steps below and you'll be running code in no time. ==Installing Python== If you're going to be developing with the Python client library, you'll need a working version of Python 2.2 or higher. Many operating systems come with a version of Python included, so you may be able to skip the installation step. To see which version of Python you have, run python -V in a command line terminal. (Note: the V is uppercase.) This should result in something like: Python 2.4.3 If you see version 2.2 or higher, then you can start installing dependencies. Otherwise, look below to find installation/upgrade instructions for your operating system. --Installing Python on Windows-- There are quite a few implementations of Python to choose from in Windows, but for purposes of this guide, I'll be using the .msi installer found on python.org. 1. Begin by downloading the installer from the Python download page. http://www.python.org/download/ 2. Run the installer ? you can accept all the default settings 3. To see if your install is working as expected, open a command prompt and run python -V. --Installing Python on Mac OS X-- The list of downloads on python.org has .dmg installers for the Mac users out there. Here are the steps to install one of them: 1. Navigate to http://www.python.org/download/mac/ 2. From this page, download the installer for the appropriate version of Mac OS X. Note: The Python installation page for Mac OS X 10.3.8 and below is different than newer versions of Mac OS X. To find your OS X version, choose About This Mac from the Apple menu in the top-left corner of your screen. 3. After the download finishes, double-click the new disk image file (ex. python-2.5-macosx.dmg) to mount it. If you're running Safari, this has already been done for you. 4. Open the mounted image and double-click the installer package inside. 5. Follow the installation instructions and read the information and license agreements as they're presented to you. Again, the default settings will work fine here. 6. Verify the installation by opening Terminal.app (in /Applications/Utilities) and running python -V. The installation's version should appear. --Installing Python on Linux-- To install on Linux and other *nix style operating systems, I prefer to download the source code and compile it. However, you may be able to use your favorite package manager to install Python. (For example, on Ubuntu this can be as easy as running sudo apt-get install python on the command line.) To install from source, follow these steps: 1. Download the source tarball from the Python download page. http://python.org/download/ 2. Once you've downloaded the package, unpack it using the command line. You can use the following tar zxvf Python-2..tgz 3. Next, you'll need to compile and install the source code for the Python interpreter. In the decompressed directory, run ./configure to generate a makefile. 4. Then, run make. This will create a working Python executable file in the local directory. If you don't have root permission or you just want to use Python from your home directory, you can stop here. You'll be able to run Python from this directory, so you might want to add it to your PATH environment variable. 5. I prefer to have Python installed in /usr/bin/ where most Python scripts look for the interpreter. If you have root access, then run make install as root. This will install Python in the default location and it will be usable by everyone on your machine. 6. Check to see if your install is working as expected by opening a terminal and running python -V. ==Installing Dependencies== Currently, the only external dependency is an XML library named ElementTree. If you are using Python version 2.5 or higher, you won't need to install ElementTree since it comes with the Python package. To see if ElementTree is already present on your system, do the following: 1. Run the Python interpreter. I usually do this by executing python on the command line. 2. Try importing the ElementTree module. If you are using Python 2.5 or higher, enter the following in the interpreter: from xml.etree import ElementTree For older versions, enter: from elementtree import ElementTree 3. If the import fails, then you will need to continue reading this section. If it works, then you can skip to Installing the Google data library. 4. Download a version which is appropriate for your operating system. For example, if you are using Windows, download elementtree-1.2.6-20050316.win32.exe. For other operating systems, I recommend downloading a compressed version. 5. If you are using a .tar.gz or .zip version of the library, first unpack, then install it by running ./setup.py install. Running ./setup.py install attempts to compile the library and place it in the system directory for your Python modules. If you do not have root access, you can install the modules in your home directory or an alternate location by running ./setup.py install --home=~. This will place the code in your home directory. There is another option which avoids installing altogether. Once you decompress the download, you will find a directory named elementtree. This directory contains the modules which you will need to import. When you call import from within Python, it looks for a module with the desired name in several places. The first place it looks is in the current directory, so if you are always running your code from one directory, you could just put the elementtree directory there. Python will also look at the directories listed in your PYTHONPATH environment variable. For instructions on editing your PYTHONPATH, see the Appendix at the end of this article. I recommend using ./setup.py install for elementtree. ==Installing the Google Data Library== Download the Google data Python library if you haven't done so. Look for the latest version on the Python project's downloads page. After downloading the library, unpack it using unzip or tar zxvf depending on the type of download you chose. Now you are ready to install the library modules so that they can be imported into Python. There are several ways you can do this: * If you have the ability to install packages for all users to access, you can run ./setup.py install from the unpacked archive's main directory. * If you want to install these modules for use in your home directory, you can run ./setup.py install --home=. In some cases, you want to avoid installing the modules altogether. To do that, modify your PYTHONPATH environment variable to include a directory which contains the gdata and atom directories for the Google data Python client library. For instructions on modifying your PYTHONPATH, see the Appendix at the end of this article. * One final option that I'll mention, is copying the gdata and atom directories from the src directory into whatever directory you are in when you execute python. Python will look in the current directory when you do an import, but I don't recommend this method unless you are creating something quick and simple. Once you've installed the Google data library, you're ready to take the library for a test drive. ==Running Tests and Samples== The Google data Python client library distributions include some test cases which are used in the development of the library. They can also serve as a quick check to make sure that your dependencies and library installation are working. From the top level directory where you've unpacked your copy of the library, try running: ./tests/run_data_tests.py If this script runs correctly, you should see output on the command line like this: Running all tests in module gdata_test ....... ---------------------------------------------------------------------- Ran 7 tests in 0.025s OK Running all tests in module atom_test .......................................... ---------------------------------------------------------------------- Ran 42 tests in 0.016s OK ... If you did not see any errors as the tests execute, then you have probably set up your environment correctly. Congratulations! For further information see the original article: http://code.google.com/support/bin/answer.py?answer=75582 ==Appendix: Modifying the PYTHONPATH== When you import a package or module in Python, the interpreter looks for the file in a series of locations including all of the directories listed in the PYTHONPATH environment variable. I often modify my PYTHONPATH to point to modules where I have copied the source code for a library I am using. This prevents the need to install a module each time it is modified because Python will load the module directly from directory which contains the modified source code. I recommend the PYTHONPATH approach if you are making changes to the client library code, or if you do not have admin rights on your system. By editing the PYTHONPATH, you can put the required modules anywhere you like. I modified my PYTHONPATH on a *nix and Mac OS X system by setting it in my .bashrc shell configuration file. If you are using the bash shell, you can set the variable by adding the following line to your ~/.bashrc file. export PYTHONPATH=$PYTHONPATH:/home//svn/gdata-python-client/src You can then apply these changes to your current shell session by executing source ~/.bashrc. For Windows XP, pull up the Environment Variables for your profile: Control Panel > System Properties > Advanced > Environment Variables. From there, you can either create or edit the PYTHONPATH variable and add the location of your local library copy. gdata-2.0.18/MANIFEST0000640036466300116100000010425312156625015014071 0ustar afshareng00000000000000# file GENERATED by distutils, do NOT edit INSTALL.txt MANIFEST README.txt RELEASE_NOTES.txt setup.py upload-diffs.py pydocs/atom.auth.html pydocs/atom.client.html pydocs/atom.core.html pydocs/atom.data.html pydocs/atom.html pydocs/atom.http.html pydocs/atom.http_core.html pydocs/atom.http_interface.html pydocs/atom.mock_http.html pydocs/atom.mock_http_core.html pydocs/atom.mock_service.html pydocs/atom.service.html pydocs/atom.token_store.html pydocs/atom.url.html pydocs/gdata.Crypto.Cipher.html pydocs/gdata.Crypto.Hash.HMAC.html pydocs/gdata.Crypto.Hash.MD5.html pydocs/gdata.Crypto.Hash.SHA.html pydocs/gdata.Crypto.Hash.html pydocs/gdata.Crypto.Protocol.AllOrNothing.html pydocs/gdata.Crypto.Protocol.Chaffing.html pydocs/gdata.Crypto.Protocol.html pydocs/gdata.Crypto.PublicKey.DSA.html pydocs/gdata.Crypto.PublicKey.ElGamal.html pydocs/gdata.Crypto.PublicKey.RSA.html pydocs/gdata.Crypto.PublicKey.html pydocs/gdata.Crypto.PublicKey.pubkey.html pydocs/gdata.Crypto.PublicKey.qNEW.html pydocs/gdata.Crypto.Util.RFC1751.html pydocs/gdata.Crypto.Util.html pydocs/gdata.Crypto.Util.number.html pydocs/gdata.Crypto.Util.randpool.html pydocs/gdata.Crypto.html pydocs/gdata.acl.data.html pydocs/gdata.acl.html pydocs/gdata.alt.app_engine.html pydocs/gdata.alt.appengine.html pydocs/gdata.alt.html pydocs/gdata.analytics.client.html pydocs/gdata.analytics.data.html pydocs/gdata.analytics.html pydocs/gdata.analytics.service.html pydocs/gdata.apps.adminsettings.html pydocs/gdata.apps.adminsettings.service.html pydocs/gdata.apps.apps_property_entry.html pydocs/gdata.apps.audit.html pydocs/gdata.apps.audit.service.html pydocs/gdata.apps.client.html pydocs/gdata.apps.data.html pydocs/gdata.apps.emailsettings.client.html pydocs/gdata.apps.emailsettings.data.html pydocs/gdata.apps.emailsettings.html pydocs/gdata.apps.emailsettings.service.html pydocs/gdata.apps.groups.client.html pydocs/gdata.apps.groups.data.html pydocs/gdata.apps.groups.html pydocs/gdata.apps.groups.service.html pydocs/gdata.apps.html pydocs/gdata.apps.migration.html pydocs/gdata.apps.migration.service.html pydocs/gdata.apps.multidomain.client.html pydocs/gdata.apps.multidomain.data.html pydocs/gdata.apps.multidomain.html pydocs/gdata.apps.organization.client.html pydocs/gdata.apps.organization.data.html pydocs/gdata.apps.organization.html pydocs/gdata.apps.organization.service.html pydocs/gdata.apps.service.html pydocs/gdata.apps_property.html pydocs/gdata.auth.html pydocs/gdata.blogger.client.html pydocs/gdata.blogger.data.html pydocs/gdata.blogger.html pydocs/gdata.blogger.service.html pydocs/gdata.books.data.html pydocs/gdata.books.html pydocs/gdata.books.service.html pydocs/gdata.calendar.client.html pydocs/gdata.calendar.data.html pydocs/gdata.calendar.html pydocs/gdata.calendar.service.html pydocs/gdata.calendar_resource.client.html pydocs/gdata.calendar_resource.data.html pydocs/gdata.calendar_resource.html pydocs/gdata.client.html pydocs/gdata.codesearch.html pydocs/gdata.codesearch.service.html pydocs/gdata.contacts.client.html pydocs/gdata.contacts.data.html pydocs/gdata.contacts.html pydocs/gdata.contacts.service.html pydocs/gdata.contentforshopping.client.html pydocs/gdata.contentforshopping.data.html pydocs/gdata.contentforshopping.html pydocs/gdata.core.html pydocs/gdata.data.html pydocs/gdata.docs.client.html pydocs/gdata.docs.data.html pydocs/gdata.docs.html pydocs/gdata.docs.service.html pydocs/gdata.dublincore.data.html pydocs/gdata.dublincore.html pydocs/gdata.exif.html pydocs/gdata.finance.data.html pydocs/gdata.finance.html pydocs/gdata.finance.service.html pydocs/gdata.gauth.html pydocs/gdata.geo.data.html pydocs/gdata.geo.html pydocs/gdata.health.html pydocs/gdata.health.service.html pydocs/gdata.html pydocs/gdata.maps.client.html pydocs/gdata.maps.html pydocs/gdata.marketplace.client.html pydocs/gdata.marketplace.data.html pydocs/gdata.marketplace.html pydocs/gdata.media.data.html pydocs/gdata.media.html pydocs/gdata.notebook.data.html pydocs/gdata.notebook.html pydocs/gdata.oauth.html pydocs/gdata.oauth.rsa.html pydocs/gdata.opensearch.data.html pydocs/gdata.opensearch.html pydocs/gdata.photos.html pydocs/gdata.photos.service.html pydocs/gdata.projecthosting.client.html pydocs/gdata.projecthosting.data.html pydocs/gdata.projecthosting.html pydocs/gdata.sample_util.html pydocs/gdata.service.html pydocs/gdata.sites.client.html pydocs/gdata.sites.data.html pydocs/gdata.sites.html pydocs/gdata.spreadsheet.html pydocs/gdata.spreadsheet.service.html pydocs/gdata.spreadsheet.text_db.html pydocs/gdata.spreadsheets.client.html pydocs/gdata.spreadsheets.data.html pydocs/gdata.spreadsheets.html pydocs/gdata.test_config.html pydocs/gdata.test_config_template.html pydocs/gdata.test_data.html pydocs/gdata.tlslite.BaseDB.html pydocs/gdata.tlslite.Checker.html pydocs/gdata.tlslite.FileObject.html pydocs/gdata.tlslite.HandshakeSettings.html pydocs/gdata.tlslite.Session.html pydocs/gdata.tlslite.SessionCache.html pydocs/gdata.tlslite.SharedKeyDB.html pydocs/gdata.tlslite.TLSConnection.html pydocs/gdata.tlslite.TLSRecordLayer.html pydocs/gdata.tlslite.VerifierDB.html pydocs/gdata.tlslite.X509.html pydocs/gdata.tlslite.X509CertChain.html pydocs/gdata.tlslite.api.html pydocs/gdata.tlslite.constants.html pydocs/gdata.tlslite.errors.html pydocs/gdata.tlslite.html pydocs/gdata.tlslite.integration.AsyncStateMachine.html pydocs/gdata.tlslite.integration.ClientHelper.html pydocs/gdata.tlslite.integration.HTTPTLSConnection.html pydocs/gdata.tlslite.integration.IMAP4_TLS.html pydocs/gdata.tlslite.integration.IntegrationHelper.html pydocs/gdata.tlslite.integration.POP3_TLS.html pydocs/gdata.tlslite.integration.SMTP_TLS.html pydocs/gdata.tlslite.integration.TLSAsyncDispatcherMixIn.html pydocs/gdata.tlslite.integration.TLSSocketServerMixIn.html pydocs/gdata.tlslite.integration.TLSTwistedProtocolWrapper.html pydocs/gdata.tlslite.integration.XMLRPCTransport.html pydocs/gdata.tlslite.integration.html pydocs/gdata.tlslite.mathtls.html pydocs/gdata.tlslite.messages.html pydocs/gdata.tlslite.utils.AES.html pydocs/gdata.tlslite.utils.ASN1Parser.html pydocs/gdata.tlslite.utils.Cryptlib_AES.html pydocs/gdata.tlslite.utils.Cryptlib_RC4.html pydocs/gdata.tlslite.utils.Cryptlib_TripleDES.html pydocs/gdata.tlslite.utils.OpenSSL_AES.html pydocs/gdata.tlslite.utils.OpenSSL_RC4.html pydocs/gdata.tlslite.utils.OpenSSL_RSAKey.html pydocs/gdata.tlslite.utils.OpenSSL_TripleDES.html pydocs/gdata.tlslite.utils.PyCrypto_AES.html pydocs/gdata.tlslite.utils.PyCrypto_RC4.html pydocs/gdata.tlslite.utils.PyCrypto_RSAKey.html pydocs/gdata.tlslite.utils.PyCrypto_TripleDES.html pydocs/gdata.tlslite.utils.Python_AES.html pydocs/gdata.tlslite.utils.Python_RC4.html pydocs/gdata.tlslite.utils.Python_RSAKey.html pydocs/gdata.tlslite.utils.RC4.html pydocs/gdata.tlslite.utils.RSAKey.html pydocs/gdata.tlslite.utils.TripleDES.html pydocs/gdata.tlslite.utils.cipherfactory.html pydocs/gdata.tlslite.utils.codec.html pydocs/gdata.tlslite.utils.compat.html pydocs/gdata.tlslite.utils.cryptomath.html pydocs/gdata.tlslite.utils.dateFuncs.html pydocs/gdata.tlslite.utils.hmac.html pydocs/gdata.tlslite.utils.html pydocs/gdata.tlslite.utils.jython_compat.html pydocs/gdata.tlslite.utils.keyfactory.html pydocs/gdata.tlslite.utils.rijndael.html pydocs/gdata.tlslite.utils.xmltools.html pydocs/gdata.urlfetch.html pydocs/gdata.webmastertools.data.html pydocs/gdata.webmastertools.html pydocs/gdata.webmastertools.service.html pydocs/gdata.youtube.client.html pydocs/gdata.youtube.data.html pydocs/gdata.youtube.html pydocs/gdata.youtube.service.html pydocs/generate_docs samples/analytics/account_feed_demo.py samples/analytics/data_feed_demo.py samples/analytics/mgmt_feed_demo.py samples/apps/adminsettings_example.py samples/apps/client_secrets.json samples/apps/create_update_group.py samples/apps/email_audit_email_monitoring.py samples/apps/email_settings_labels_filters.py samples/apps/emailsettings_example.py samples/apps/emailsettings_pop_settings.py samples/apps/groups_provisioning_quick_start_example.py samples/apps/list_group_members.py samples/apps/migration_example.py samples/apps/multidomain_quick_start_example.py samples/apps/org_unit_sites.py samples/apps/orgunit_quick_start_example.py samples/apps/provisioning_oauth2_example.py samples/apps/search_organize_users.py samples/apps/suspended_users_cleanup.py samples/apps/user_profile_contacts_2lo.py samples/apps/userprovisioning_quick_start_example.py samples/apps/marketplace_sample/app.yaml samples/apps/marketplace_sample/domain_mgmt_app.py samples/apps/marketplace_sample/appengine_utilities/__init__.py samples/apps/marketplace_sample/appengine_utilities/cache.py samples/apps/marketplace_sample/appengine_utilities/event.py samples/apps/marketplace_sample/appengine_utilities/flash.py samples/apps/marketplace_sample/appengine_utilities/rotmodel.py samples/apps/marketplace_sample/appengine_utilities/sessions.py samples/apps/marketplace_sample/appengine_utilities/settings_default.py samples/apps/marketplace_sample/appengine_utilities/django-middleware/__init__.py samples/apps/marketplace_sample/appengine_utilities/django-middleware/middleware.py samples/apps/marketplace_sample/appengine_utilities/interface/__init__.py samples/apps/marketplace_sample/appengine_utilities/interface/main.py samples/apps/marketplace_sample/appengine_utilities/interface/css/main.css samples/apps/marketplace_sample/appengine_utilities/interface/css/cssbase/base-context-min.css samples/apps/marketplace_sample/appengine_utilities/interface/css/cssbase/base-context.css samples/apps/marketplace_sample/appengine_utilities/interface/css/cssbase/base-min.css samples/apps/marketplace_sample/appengine_utilities/interface/css/cssbase/base.css samples/apps/marketplace_sample/appengine_utilities/interface/css/cssfonts/fonts-context-min.css samples/apps/marketplace_sample/appengine_utilities/interface/css/cssfonts/fonts-context.css samples/apps/marketplace_sample/appengine_utilities/interface/css/cssfonts/fonts-min.css samples/apps/marketplace_sample/appengine_utilities/interface/css/cssfonts/fonts.css samples/apps/marketplace_sample/appengine_utilities/interface/css/cssgrids/grids-context-min.css samples/apps/marketplace_sample/appengine_utilities/interface/css/cssgrids/grids-context.css samples/apps/marketplace_sample/appengine_utilities/interface/css/cssgrids/grids-min.css samples/apps/marketplace_sample/appengine_utilities/interface/css/cssgrids/grids.css samples/apps/marketplace_sample/appengine_utilities/interface/css/cssreset/reset-context-min.css samples/apps/marketplace_sample/appengine_utilities/interface/css/cssreset/reset-context.css samples/apps/marketplace_sample/appengine_utilities/interface/css/cssreset/reset-min.css samples/apps/marketplace_sample/appengine_utilities/interface/css/cssreset/reset.css samples/apps/marketplace_sample/appengine_utilities/interface/templates/404.html samples/apps/marketplace_sample/appengine_utilities/interface/templates/base.html samples/apps/marketplace_sample/appengine_utilities/interface/templates/scheduler_form.html samples/apps/marketplace_sample/atom/__init__.py samples/apps/marketplace_sample/atom/auth.py samples/apps/marketplace_sample/atom/client.py samples/apps/marketplace_sample/atom/core.py samples/apps/marketplace_sample/atom/data.py samples/apps/marketplace_sample/atom/http.py samples/apps/marketplace_sample/atom/http_core.py samples/apps/marketplace_sample/atom/http_interface.py samples/apps/marketplace_sample/atom/mock_http.py samples/apps/marketplace_sample/atom/mock_http_core.py samples/apps/marketplace_sample/atom/mock_service.py samples/apps/marketplace_sample/atom/service.py samples/apps/marketplace_sample/atom/token_store.py samples/apps/marketplace_sample/atom/url.py samples/apps/marketplace_sample/css/style.css samples/apps/marketplace_sample/gdata/__init__.py samples/apps/marketplace_sample/gdata/apps_property.py samples/apps/marketplace_sample/gdata/auth.py samples/apps/marketplace_sample/gdata/client.py samples/apps/marketplace_sample/gdata/core.py samples/apps/marketplace_sample/gdata/data.py samples/apps/marketplace_sample/gdata/gauth.py samples/apps/marketplace_sample/gdata/sample_util.py samples/apps/marketplace_sample/gdata/service.py samples/apps/marketplace_sample/gdata/test_config.py samples/apps/marketplace_sample/gdata/test_data.py samples/apps/marketplace_sample/gdata/urlfetch.py samples/apps/marketplace_sample/gdata/apps/__init__.py samples/apps/marketplace_sample/gdata/apps/apps_property_entry.py samples/apps/marketplace_sample/gdata/apps/client.py samples/apps/marketplace_sample/gdata/apps/data.py samples/apps/marketplace_sample/gdata/apps/service.py samples/apps/marketplace_sample/gdata/apps/adminsettings/__init__.py samples/apps/marketplace_sample/gdata/apps/adminsettings/service.py samples/apps/marketplace_sample/gdata/apps/audit/__init__.py samples/apps/marketplace_sample/gdata/apps/audit/service.py samples/apps/marketplace_sample/gdata/apps/emailsettings/__init__.py samples/apps/marketplace_sample/gdata/apps/emailsettings/client.py samples/apps/marketplace_sample/gdata/apps/emailsettings/data.py samples/apps/marketplace_sample/gdata/apps/emailsettings/service.py samples/apps/marketplace_sample/gdata/apps/groups/__init__.py samples/apps/marketplace_sample/gdata/apps/groups/client.py samples/apps/marketplace_sample/gdata/apps/groups/data.py samples/apps/marketplace_sample/gdata/apps/groups/service.py samples/apps/marketplace_sample/gdata/apps/migration/__init__.py samples/apps/marketplace_sample/gdata/apps/migration/service.py samples/apps/marketplace_sample/gdata/apps/multidomain/__init__.py samples/apps/marketplace_sample/gdata/apps/multidomain/client.py samples/apps/marketplace_sample/gdata/apps/multidomain/data.py samples/apps/marketplace_sample/gdata/apps/organization/__init__.py samples/apps/marketplace_sample/gdata/apps/organization/client.py samples/apps/marketplace_sample/gdata/apps/organization/data.py samples/apps/marketplace_sample/gdata/apps/organization/service.py samples/apps/marketplace_sample/gdata/calendar/__init__.py samples/apps/marketplace_sample/gdata/calendar/client.py samples/apps/marketplace_sample/gdata/calendar/data.py samples/apps/marketplace_sample/gdata/calendar/service.py samples/apps/marketplace_sample/gdata/oauth/CHANGES.txt samples/apps/marketplace_sample/gdata/oauth/__init__.py samples/apps/marketplace_sample/gdata/oauth/rsa.py samples/apps/marketplace_sample/gdata/tlslite/BaseDB.py samples/apps/marketplace_sample/gdata/tlslite/Checker.py samples/apps/marketplace_sample/gdata/tlslite/FileObject.py samples/apps/marketplace_sample/gdata/tlslite/HandshakeSettings.py samples/apps/marketplace_sample/gdata/tlslite/Session.py samples/apps/marketplace_sample/gdata/tlslite/SessionCache.py samples/apps/marketplace_sample/gdata/tlslite/SharedKeyDB.py samples/apps/marketplace_sample/gdata/tlslite/TLSConnection.py samples/apps/marketplace_sample/gdata/tlslite/TLSRecordLayer.py samples/apps/marketplace_sample/gdata/tlslite/VerifierDB.py samples/apps/marketplace_sample/gdata/tlslite/X509.py samples/apps/marketplace_sample/gdata/tlslite/X509CertChain.py samples/apps/marketplace_sample/gdata/tlslite/__init__.py samples/apps/marketplace_sample/gdata/tlslite/api.py samples/apps/marketplace_sample/gdata/tlslite/constants.py samples/apps/marketplace_sample/gdata/tlslite/errors.py samples/apps/marketplace_sample/gdata/tlslite/mathtls.py samples/apps/marketplace_sample/gdata/tlslite/messages.py samples/apps/marketplace_sample/gdata/tlslite/integration/AsyncStateMachine.py samples/apps/marketplace_sample/gdata/tlslite/integration/ClientHelper.py samples/apps/marketplace_sample/gdata/tlslite/integration/HTTPTLSConnection.py samples/apps/marketplace_sample/gdata/tlslite/integration/IMAP4_TLS.py samples/apps/marketplace_sample/gdata/tlslite/integration/IntegrationHelper.py samples/apps/marketplace_sample/gdata/tlslite/integration/POP3_TLS.py samples/apps/marketplace_sample/gdata/tlslite/integration/SMTP_TLS.py samples/apps/marketplace_sample/gdata/tlslite/integration/TLSAsyncDispatcherMixIn.py samples/apps/marketplace_sample/gdata/tlslite/integration/TLSSocketServerMixIn.py samples/apps/marketplace_sample/gdata/tlslite/integration/TLSTwistedProtocolWrapper.py samples/apps/marketplace_sample/gdata/tlslite/integration/XMLRPCTransport.py samples/apps/marketplace_sample/gdata/tlslite/integration/__init__.py samples/apps/marketplace_sample/gdata/tlslite/utils/AES.py samples/apps/marketplace_sample/gdata/tlslite/utils/ASN1Parser.py samples/apps/marketplace_sample/gdata/tlslite/utils/Cryptlib_AES.py samples/apps/marketplace_sample/gdata/tlslite/utils/Cryptlib_RC4.py samples/apps/marketplace_sample/gdata/tlslite/utils/Cryptlib_TripleDES.py samples/apps/marketplace_sample/gdata/tlslite/utils/OpenSSL_AES.py samples/apps/marketplace_sample/gdata/tlslite/utils/OpenSSL_RC4.py samples/apps/marketplace_sample/gdata/tlslite/utils/OpenSSL_RSAKey.py samples/apps/marketplace_sample/gdata/tlslite/utils/OpenSSL_TripleDES.py samples/apps/marketplace_sample/gdata/tlslite/utils/PyCrypto_AES.py samples/apps/marketplace_sample/gdata/tlslite/utils/PyCrypto_RC4.py samples/apps/marketplace_sample/gdata/tlslite/utils/PyCrypto_RSAKey.py samples/apps/marketplace_sample/gdata/tlslite/utils/PyCrypto_TripleDES.py samples/apps/marketplace_sample/gdata/tlslite/utils/Python_AES.py samples/apps/marketplace_sample/gdata/tlslite/utils/Python_RC4.py samples/apps/marketplace_sample/gdata/tlslite/utils/Python_RSAKey.py samples/apps/marketplace_sample/gdata/tlslite/utils/RC4.py samples/apps/marketplace_sample/gdata/tlslite/utils/RSAKey.py samples/apps/marketplace_sample/gdata/tlslite/utils/TripleDES.py samples/apps/marketplace_sample/gdata/tlslite/utils/__init__.py samples/apps/marketplace_sample/gdata/tlslite/utils/cipherfactory.py samples/apps/marketplace_sample/gdata/tlslite/utils/codec.py samples/apps/marketplace_sample/gdata/tlslite/utils/compat.py samples/apps/marketplace_sample/gdata/tlslite/utils/cryptomath.py samples/apps/marketplace_sample/gdata/tlslite/utils/dateFuncs.py samples/apps/marketplace_sample/gdata/tlslite/utils/entropy.c samples/apps/marketplace_sample/gdata/tlslite/utils/hmac.py samples/apps/marketplace_sample/gdata/tlslite/utils/jython_compat.py samples/apps/marketplace_sample/gdata/tlslite/utils/keyfactory.py samples/apps/marketplace_sample/gdata/tlslite/utils/rijndael.py samples/apps/marketplace_sample/gdata/tlslite/utils/win32prng.c samples/apps/marketplace_sample/gdata/tlslite/utils/xmltools.py samples/apps/marketplace_sample/images/google.png samples/apps/marketplace_sample/js/json2.js samples/apps/marketplace_sample/js/main.js samples/apps/marketplace_sample/templates/index.html samples/apps/provisioning_oauth_example/app.yaml samples/apps/provisioning_oauth_example/footer.html samples/apps/provisioning_oauth_example/header.html samples/apps/provisioning_oauth_example/index.html samples/apps/provisioning_oauth_example/login.html samples/apps/provisioning_oauth_example/main.py samples/apps/provisioning_oauth_example/css/index.css samples/apps/provisioning_oauth_example/images/google-small.png samples/apps/provisioning_oauth_example/js/index.js samples/authsub/secure_authsub.py samples/blogger/BloggerExample.py samples/blogger/BloggerExampleV1.py samples/blogger/app/app.yaml samples/blogger/app/auth_required.html samples/blogger/app/blogapp.py samples/blogger/app/list_blogs.html samples/blogger/app/post_editor.html samples/blogger/app/welcome.html samples/blogger/oauth-appengine/app.yaml samples/blogger/oauth-appengine/index.html samples/blogger/oauth-appengine/index.yaml samples/blogger/oauth-appengine/main.py samples/blogger/oauth-appengine/oauth.py samples/blogger/oauth-appengine/css/index.css samples/calendar/calendarExample.py samples/calendar_resource/calendar_resource_example.py samples/codesearch/CodesearchExample.py samples/contacts/contacts_example.py samples/contacts/profiles_example.py samples/contacts/unshare_profiles.py samples/contentforshopping/add_batch_products.py samples/contentforshopping/add_product.py samples/contentforshopping/ca_insert.py samples/contentforshopping/ca_list.py samples/contentforshopping/list_products.py samples/docs/docs_example.py samples/docs/docs_v3_example.py samples/docs/samplerunner.py samples/finance/test_finance.py samples/mashups/birthdaySample.py samples/oauth/2_legged_oauth.py samples/oauth/TwoLeggedOAuthExample.py samples/oauth/oauth_example.py samples/oauth/oauth_on_appengine/README.txt samples/oauth/oauth_on_appengine/app.yaml samples/oauth/oauth_on_appengine/index.html samples/oauth/oauth_on_appengine/index.yaml samples/oauth/oauth_on_appengine/main_hmac.py samples/oauth/oauth_on_appengine/main_rsa.py samples/oauth/oauth_on_appengine/appengine_utilities/__init__.py samples/oauth/oauth_on_appengine/appengine_utilities/cache.py samples/oauth/oauth_on_appengine/appengine_utilities/cron.py samples/oauth/oauth_on_appengine/appengine_utilities/event.py samples/oauth/oauth_on_appengine/appengine_utilities/flash.py samples/oauth/oauth_on_appengine/appengine_utilities/paginator.py samples/oauth/oauth_on_appengine/appengine_utilities/rotmodel.py samples/oauth/oauth_on_appengine/appengine_utilities/sessions.py samples/oauth/oauth_on_appengine/css/index.css samples/oauth/oauth_on_appengine/images/icon_document.gif samples/oauth/oauth_on_appengine/images/icon_folder.gif samples/oauth/oauth_on_appengine/images/icon_pdf.gif samples/oauth/oauth_on_appengine/images/icon_presentation.gif samples/oauth/oauth_on_appengine/images/icon_spreadsheet.gif samples/oauth/oauth_on_appengine/images/icon_starred.png samples/oauth/oauth_on_appengine/images/icon_trashed.png samples/oauth/oauth_on_appengine/js/jquery.corners.min.js samples/sites/sites_example.py samples/spreadsheets/spreadsheetExample.py samples/webmastertools/AddDeleteExampleDotCom.py samples/webmastertools/SitemapsFeedSummary.py samples/webmastertools/SitesFeedSummary.py src/atom/__init__.py src/atom/auth.py src/atom/client.py src/atom/core.py src/atom/data.py src/atom/http.py src/atom/http_core.py src/atom/http_interface.py src/atom/mock_http.py src/atom/mock_http_core.py src/atom/mock_service.py src/atom/service.py src/atom/token_store.py src/atom/url.py src/gdata/__init__.py src/gdata/apps_property.py src/gdata/auth.py src/gdata/client.py src/gdata/core.py src/gdata/data.py src/gdata/gauth.py src/gdata/sample_util.py src/gdata/service.py src/gdata/test_config.py src/gdata/test_data.py src/gdata/urlfetch.py src/gdata/Crypto/__init__.py src/gdata/Crypto/test.py src/gdata/Crypto/Cipher/AES.pyd src/gdata/Crypto/Cipher/ARC2.pyd src/gdata/Crypto/Cipher/ARC4.pyd src/gdata/Crypto/Cipher/Blowfish.pyd src/gdata/Crypto/Cipher/CAST.pyd src/gdata/Crypto/Cipher/DES.pyd src/gdata/Crypto/Cipher/DES3.pyd src/gdata/Crypto/Cipher/IDEA.pyd src/gdata/Crypto/Cipher/RC5.pyd src/gdata/Crypto/Cipher/XOR.pyd src/gdata/Crypto/Cipher/__init__.py src/gdata/Crypto/Hash/HMAC.py src/gdata/Crypto/Hash/MD2.pyd src/gdata/Crypto/Hash/MD4.pyd src/gdata/Crypto/Hash/MD5.py src/gdata/Crypto/Hash/RIPEMD.pyd src/gdata/Crypto/Hash/SHA.py src/gdata/Crypto/Hash/SHA256.pyd src/gdata/Crypto/Hash/__init__.py src/gdata/Crypto/Protocol/AllOrNothing.py src/gdata/Crypto/Protocol/Chaffing.py src/gdata/Crypto/Protocol/__init__.py src/gdata/Crypto/PublicKey/DSA.py src/gdata/Crypto/PublicKey/ElGamal.py src/gdata/Crypto/PublicKey/RSA.py src/gdata/Crypto/PublicKey/__init__.py src/gdata/Crypto/PublicKey/pubkey.py src/gdata/Crypto/PublicKey/qNEW.py src/gdata/Crypto/Util/RFC1751.py src/gdata/Crypto/Util/__init__.py src/gdata/Crypto/Util/number.py src/gdata/Crypto/Util/randpool.py src/gdata/Crypto/Util/test.py src/gdata/acl/__init__.py src/gdata/acl/data.py src/gdata/alt/__init__.py src/gdata/alt/app_engine.py src/gdata/alt/appengine.py src/gdata/analytics/__init__.py src/gdata/analytics/client.py src/gdata/analytics/data.py src/gdata/analytics/service.py src/gdata/apps/__init__.py src/gdata/apps/apps_property_entry.py src/gdata/apps/client.py src/gdata/apps/data.py src/gdata/apps/service.py src/gdata/apps/adminsettings/__init__.py src/gdata/apps/adminsettings/service.py src/gdata/apps/audit/__init__.py src/gdata/apps/audit/service.py src/gdata/apps/emailsettings/__init__.py src/gdata/apps/emailsettings/client.py src/gdata/apps/emailsettings/data.py src/gdata/apps/emailsettings/service.py src/gdata/apps/groups/__init__.py src/gdata/apps/groups/client.py src/gdata/apps/groups/data.py src/gdata/apps/groups/service.py src/gdata/apps/migration/__init__.py src/gdata/apps/migration/service.py src/gdata/apps/multidomain/__init__.py src/gdata/apps/multidomain/client.py src/gdata/apps/multidomain/data.py src/gdata/apps/organization/__init__.py src/gdata/apps/organization/client.py src/gdata/apps/organization/data.py src/gdata/apps/organization/service.py src/gdata/blogger/__init__.py src/gdata/blogger/client.py src/gdata/blogger/data.py src/gdata/blogger/service.py src/gdata/books/__init__.py src/gdata/books/data.py src/gdata/books/service.py src/gdata/calendar/__init__.py src/gdata/calendar/client.py src/gdata/calendar/data.py src/gdata/calendar/service.py src/gdata/calendar_resource/__init__.py src/gdata/calendar_resource/client.py src/gdata/calendar_resource/data.py src/gdata/codesearch/__init__.py src/gdata/codesearch/service.py src/gdata/contacts/__init__.py src/gdata/contacts/client.py src/gdata/contacts/data.py src/gdata/contacts/service.py src/gdata/contentforshopping/__init__.py src/gdata/contentforshopping/client.py src/gdata/contentforshopping/data.py src/gdata/docs/__init__.py src/gdata/docs/client.py src/gdata/docs/data.py src/gdata/docs/service.py src/gdata/dublincore/__init__.py src/gdata/dublincore/data.py src/gdata/exif/__init__.py src/gdata/finance/__init__.py src/gdata/finance/data.py src/gdata/finance/service.py src/gdata/geo/__init__.py src/gdata/geo/data.py src/gdata/health/__init__.py src/gdata/health/service.py src/gdata/marketplace/__init__.py src/gdata/marketplace/client.py src/gdata/marketplace/data.py src/gdata/media/__init__.py src/gdata/media/data.py src/gdata/notebook/__init__.py src/gdata/notebook/data.py src/gdata/oauth/CHANGES.txt src/gdata/oauth/__init__.py src/gdata/oauth/rsa.py src/gdata/opensearch/__init__.py src/gdata/opensearch/data.py src/gdata/photos/__init__.py src/gdata/photos/service.py src/gdata/projecthosting/__init__.py src/gdata/projecthosting/client.py src/gdata/projecthosting/data.py src/gdata/sites/__init__.py src/gdata/sites/client.py src/gdata/sites/data.py src/gdata/spreadsheet/__init__.py src/gdata/spreadsheet/service.py src/gdata/spreadsheet/text_db.py src/gdata/spreadsheets/__init__.py src/gdata/spreadsheets/client.py src/gdata/spreadsheets/data.py src/gdata/tlslite/BaseDB.py src/gdata/tlslite/Checker.py src/gdata/tlslite/FileObject.py src/gdata/tlslite/HandshakeSettings.py src/gdata/tlslite/Session.py src/gdata/tlslite/SessionCache.py src/gdata/tlslite/SharedKeyDB.py src/gdata/tlslite/TLSConnection.py src/gdata/tlslite/TLSRecordLayer.py src/gdata/tlslite/VerifierDB.py src/gdata/tlslite/X509.py src/gdata/tlslite/X509CertChain.py src/gdata/tlslite/__init__.py src/gdata/tlslite/api.py src/gdata/tlslite/constants.py src/gdata/tlslite/errors.py src/gdata/tlslite/mathtls.py src/gdata/tlslite/messages.py src/gdata/tlslite/integration/AsyncStateMachine.py src/gdata/tlslite/integration/ClientHelper.py src/gdata/tlslite/integration/HTTPTLSConnection.py src/gdata/tlslite/integration/IMAP4_TLS.py src/gdata/tlslite/integration/IntegrationHelper.py src/gdata/tlslite/integration/POP3_TLS.py src/gdata/tlslite/integration/SMTP_TLS.py src/gdata/tlslite/integration/TLSAsyncDispatcherMixIn.py src/gdata/tlslite/integration/TLSSocketServerMixIn.py src/gdata/tlslite/integration/TLSTwistedProtocolWrapper.py src/gdata/tlslite/integration/XMLRPCTransport.py src/gdata/tlslite/integration/__init__.py src/gdata/tlslite/utils/AES.py src/gdata/tlslite/utils/ASN1Parser.py src/gdata/tlslite/utils/Cryptlib_AES.py src/gdata/tlslite/utils/Cryptlib_RC4.py src/gdata/tlslite/utils/Cryptlib_TripleDES.py src/gdata/tlslite/utils/OpenSSL_AES.py src/gdata/tlslite/utils/OpenSSL_RC4.py src/gdata/tlslite/utils/OpenSSL_RSAKey.py src/gdata/tlslite/utils/OpenSSL_TripleDES.py src/gdata/tlslite/utils/PyCrypto_AES.py src/gdata/tlslite/utils/PyCrypto_RC4.py src/gdata/tlslite/utils/PyCrypto_RSAKey.py src/gdata/tlslite/utils/PyCrypto_TripleDES.py src/gdata/tlslite/utils/Python_AES.py src/gdata/tlslite/utils/Python_RC4.py src/gdata/tlslite/utils/Python_RSAKey.py src/gdata/tlslite/utils/RC4.py src/gdata/tlslite/utils/RSAKey.py src/gdata/tlslite/utils/TripleDES.py src/gdata/tlslite/utils/__init__.py src/gdata/tlslite/utils/cipherfactory.py src/gdata/tlslite/utils/codec.py src/gdata/tlslite/utils/compat.py src/gdata/tlslite/utils/cryptomath.py src/gdata/tlslite/utils/dateFuncs.py src/gdata/tlslite/utils/entropy.c src/gdata/tlslite/utils/hmac.py src/gdata/tlslite/utils/jython_compat.py src/gdata/tlslite/utils/keyfactory.py src/gdata/tlslite/utils/rijndael.py src/gdata/tlslite/utils/win32prng.c src/gdata/tlslite/utils/xmltools.py src/gdata/webmastertools/__init__.py src/gdata/webmastertools/data.py src/gdata/webmastertools/service.py src/gdata/youtube/__init__.py src/gdata/youtube/client.py src/gdata/youtube/data.py src/gdata/youtube/service.py tests/__init__.py tests/all_tests.py tests/all_tests_cached.py tests/all_tests_clear_cache.py tests/all_tests_coverage.py tests/all_tests_local.py tests/atom_test.py tests/coverage.py tests/gdata_test.py tests/module_test_runner.py tests/run_all_tests.py tests/run_data_tests.py tests/run_service_tests.py tests/testimage.jpg tests/atom_tests/__init__.py tests/atom_tests/auth_test.py tests/atom_tests/client_test.py tests/atom_tests/core_test.py tests/atom_tests/data_test.py tests/atom_tests/http_core_test.py tests/atom_tests/http_interface_test.py tests/atom_tests/mock_client_test.py tests/atom_tests/mock_http_core_test.py tests/atom_tests/mock_http_test.py tests/atom_tests/mock_server_test.py tests/atom_tests/service_test.py tests/atom_tests/token_store_test.py tests/atom_tests/url_test.py tests/files/testimage.jpg tests/gdata_tests/__init__.py tests/gdata_tests/apps_test.py tests/gdata_tests/auth_test.py tests/gdata_tests/blogger_test.py tests/gdata_tests/books_test.py tests/gdata_tests/calendar_test.py tests/gdata_tests/client_smoke_test.py tests/gdata_tests/client_test.py tests/gdata_tests/codesearch_test.py tests/gdata_tests/contacts_test.py tests/gdata_tests/contentforshopping_test.py tests/gdata_tests/core_test.py tests/gdata_tests/data_smoke_test.py tests/gdata_tests/data_test.py tests/gdata_tests/docs_test.py tests/gdata_tests/gauth_test.py tests/gdata_tests/health_test.py tests/gdata_tests/live_client_test.py tests/gdata_tests/photos_test.py tests/gdata_tests/resumable_upload_test.py tests/gdata_tests/sample_util_test.py tests/gdata_tests/service_test.py tests/gdata_tests/spreadsheet_test.py tests/gdata_tests/webmastertools_test.py tests/gdata_tests/youtube_test.py tests/gdata_tests/analytics/__init__.py tests/gdata_tests/analytics/data_test.py tests/gdata_tests/analytics/live_client_test.py tests/gdata_tests/analytics/query_test.py tests/gdata_tests/apps/AppsServiceTestForGetGeneratorForAllEmailLists.pickle tests/gdata_tests/apps/AppsServiceTestForGetGeneratorForAllNicknames.pickle tests/gdata_tests/apps/AppsServiceTestForGetGeneratorForAllRecipients.pickle tests/gdata_tests/apps/AppsServiceTestForGetGeneratorForAllUsers.pickle tests/gdata_tests/apps/TestDataForGeneratorTest.p tests/gdata_tests/apps/__init__.py tests/gdata_tests/apps/data_test.py tests/gdata_tests/apps/live_client_test.py tests/gdata_tests/apps/service_test.py tests/gdata_tests/apps/service_test_using_mock.py tests/gdata_tests/apps/emailsettings/__init__.py tests/gdata_tests/apps/emailsettings/data_test.py tests/gdata_tests/apps/emailsettings/live_client_test.py tests/gdata_tests/apps/emailsettings/service_test.py tests/gdata_tests/apps/groups/__init__.py tests/gdata_tests/apps/groups/data_test.py tests/gdata_tests/apps/groups/live_client_test.py tests/gdata_tests/apps/groups/service_test.py tests/gdata_tests/apps/migration/__init__.py tests/gdata_tests/apps/migration/service_test.py tests/gdata_tests/apps/multidomain/__init__.py tests/gdata_tests/apps/multidomain/data_test.py tests/gdata_tests/apps/multidomain/live_client_test.py tests/gdata_tests/apps/organization/__init__.py tests/gdata_tests/apps/organization/data_test.py tests/gdata_tests/apps/organization/live_client_test.py tests/gdata_tests/apps/organization/service_test.py tests/gdata_tests/blogger/__init__.py tests/gdata_tests/blogger/data_test.py tests/gdata_tests/blogger/live_client_test.py tests/gdata_tests/blogger/service_test.py tests/gdata_tests/books/__init__.py tests/gdata_tests/books/service_test.py tests/gdata_tests/calendar/__init__.py tests/gdata_tests/calendar/calendar_acl_test.py tests/gdata_tests/calendar/service_test.py tests/gdata_tests/calendar_resource/__init__.py tests/gdata_tests/calendar_resource/data_test.py tests/gdata_tests/calendar_resource/live_client_test.py tests/gdata_tests/contacts/__init__.py tests/gdata_tests/contacts/live_client_test.py tests/gdata_tests/contacts/service_test.py tests/gdata_tests/contacts/profiles/__init__.py tests/gdata_tests/contacts/profiles/live_client_test.py tests/gdata_tests/contacts/profiles/service_test.py tests/gdata_tests/docs/__init__.py tests/gdata_tests/docs/data_test.py tests/gdata_tests/docs/live_client_test.py tests/gdata_tests/docs/service_test.py tests/gdata_tests/docs/data/test.0.bin tests/gdata_tests/docs/data/test.0.csv tests/gdata_tests/docs/data/test.0.doc tests/gdata_tests/docs/data/test.0.pdf tests/gdata_tests/docs/data/test.0.ppt tests/gdata_tests/docs/data/test.0.wmf tests/gdata_tests/docs/data/test.1.bin tests/gdata_tests/docs/data/test.1.csv tests/gdata_tests/docs/data/test.1.doc tests/gdata_tests/docs/data/test.1.pdf tests/gdata_tests/docs/data/test.1.ppt tests/gdata_tests/docs/data/test.1.wmf tests/gdata_tests/health/__init__.py tests/gdata_tests/health/service_test.py tests/gdata_tests/marketplace/__init__.py tests/gdata_tests/marketplace/live_client_test.py tests/gdata_tests/oauth/__init__.py tests/gdata_tests/oauth/data_test.py tests/gdata_tests/photos/__init__.py tests/gdata_tests/photos/service_test.py tests/gdata_tests/projecthosting/__init__.py tests/gdata_tests/projecthosting/data_test.py tests/gdata_tests/projecthosting/live_client_test.py tests/gdata_tests/sites/__init__.py tests/gdata_tests/sites/data_test.py tests/gdata_tests/sites/live_client_test.py tests/gdata_tests/spreadsheet/__init__.py tests/gdata_tests/spreadsheet/service_test.py tests/gdata_tests/spreadsheet/text_db_test.py tests/gdata_tests/spreadsheets/__init__.py tests/gdata_tests/spreadsheets/data_test.py tests/gdata_tests/spreadsheets/live_client_test.py tests/gdata_tests/youtube/__init__.py tests/gdata_tests/youtube/live_client_test.py tests/gdata_tests/youtube/service_test.py