diff --git a/third_party/google_api_python_client/.gitignore b/third_party/google_api_python_client/.gitignore deleted file mode 100644 index ddb969d34c..0000000000 --- a/third_party/google_api_python_client/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -# Build artifacts -*.py[cod] -google_api_python_client.egg-info/ -build/ -dist/ - -# Test files -.tox/ diff --git a/third_party/google_api_python_client/.gitmodules b/third_party/google_api_python_client/.gitmodules deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/third_party/google_api_python_client/.hgignore b/third_party/google_api_python_client/.hgignore deleted file mode 100644 index dc9cf78267..0000000000 --- a/third_party/google_api_python_client/.hgignore +++ /dev/null @@ -1,24 +0,0 @@ -syntax: glob - -*.pyc -*.pyc-2.4 -*.dat -.*.swp -*/.git/* -*/.cache/* -.gitignore -.tox -samples/buzz/*.dat -samples/moderator/*.dat -htmlcov/* -.coverage -database.sqlite3 -build/* -googlecode_upload.py -google_api_python_client.egg-info/* -dist/* -snapshot/* -MANIFEST -.project -.pydevproject -.settings/* diff --git a/third_party/google_api_python_client/CHANGELOG b/third_party/google_api_python_client/CHANGELOG deleted file mode 100644 index af6d6698e6..0000000000 --- a/third_party/google_api_python_client/CHANGELOG +++ /dev/null @@ -1,167 +0,0 @@ -v1.3.1 - Version 1.3.1 - - Quick release for a fix around aliasing in v1.3. - -v1.3 - Version 1.3 - - Add support for the Google Application Default Credentials. - Require python 2.6 as a minimum version. - Update several API samples. - Finish splitting out oauth2client repo and update tests. - Various doc cleanup and bugfixes. - - Two important notes: - * We've added `googleapiclient` as the primary suggested import - name, and kept `apiclient` as an alias, in order to have a more - appropriate import name. At some point, we will remove `apiclient` - as an alias. - * Due to an issue around in-place upgrades for Python packages, - it's not possible to do an upgrade from version 1.2 to 1.3. Instead, - setup.py attempts to detect this and prevents it. Simply remove - the previous version and reinstall to fix this. - -v1.2 - Version 1.2 - - The use of the gflags library is now deprecated, and is no longer a - dependency. If you are still using the oauth2client.tools.run() function - then include gflags as a dependency of your application or switch to - oauth2client.tools.run_flow. - Samples have been updated to use the new apiclient.sample_tools, and no - longer use gflags. - Added support for the experimental Object Change Notification, as found in - the Cloud Storage API. - The oauth2client App Engine decorators are now threadsafe. - - - Use the following redirects feature of httplib2 where it returns the - ultimate URL after a series of redirects to avoid multiple hops for every - resumable media upload request. - - Updated AdSense Management API samples to V1.3 - - Add option to automatically retry requests. - - Ability to list registered keys in multistore_file. - - User-agent must contain (gzip). - - The 'method' parameter for httplib2 is not positional. This would cause - spurious warnings in the logging. - - Making OAuth2Decorator more extensible. Fixes Issue 256. - - Update AdExchange Buyer API examples to version v1.2. - - -v1.1 - Version 1.1 - - Add PEM support to SignedJWTAssertionCredentials (used to only support - PKCS12 formatted keys). Note that if you use PEM formatted keys you can use - PyCrypto 2.6 or later instead of OpenSSL. - - Allow deserialized discovery docs to be passed to build_from_document(). - - - Make ResumableUploadError derive from HttpError. - - Many changes to move all the closures in apiclient.discovery into real - - classes and objects. - - Make from_json behavior inheritable. - - Expose the full token response in OAuth2Client and OAuth2Decorator. - - Handle reasons that are None. - - Added support for NDB based storing of oauth2client objects. - - Update grant_type for AssertionCredentials. - - Adding a .revoke() to Credentials. Closes issue 98. - - Modify oauth2client.multistore_file to store and retrieve credentials - using an arbitrary key. - - Don't accept 403 challenges by default for auth challenges. - - Set httplib2.RETRIES to 1. - - Consolidate handling of scopes. - - Upgrade to httplib2 version 0.8. - - Allow setting the response_type in OAuth2WebServerFlow. - - Ensure that dataWrapper feature is checked before using the 'data' value. - - HMAC verification does not use a constant time algorithm. - -v1.0 - Version 1.0 - - - Changes to the code for running tests and building releases. - -v1.0c3 - Version 1.0 Release Candidate 3 - - - In samples and oauth2 decorator, escape untrusted content before displaying it. - - Do not allow credentials files to be symlinks. - - Add XSRF protection to oauth2decorator callback 'state'. - - Handle uploading chunked media by stream. - - Handle passing streams directly to httplib2. - - Add support for Google Compute Engine service accounts. - - Flows no longer need to be saved between uses. - - Change GET to POST if URI is too long. Fixes issue #96. - - Add a keyring based Storage. - - More robust picking up JSON error responses. - - Make batch errors align with normal errors. - - Add a Google Compute sample. - - Token refresh to work with 'old' GData API - - Loading of client_secrets JSON file backed by a cache. - - Switch to new discovery path parameters. - - Add support for additionalProperties when printing schema'd objects. - - Fix media upload parameter names. Reviewed in http://codereview.appspot.com/6374062/ - - oauth2client support for URL-encoded format of exchange token response (e.g. Facebook) - - Build cleaner and easier to read docs for dynamic surfaces. - -v1.0c2 - Version 1.0 Release Candidate 2 - - - Parameter values of None should be treated as missing. Fixes issue #144. - - Distribute the samples separately from the library source. Fixes issue #155. - - Move all remaining samples over to client_secrets.json. Fixes issue #156. - - Make locked_file.py understand win32file primitives for better awesomeness. - -v1.0c1 - Version 1.0 Release Candidate 1 - - - Documentation for the library has switched to epydoc: - http://google-api-python-client.googlecode.com/hg/docs/epy/index.html - - Many improvements for media support: - * Added media download support, including resumable downloads. - * Better handling of streams that report their size as 0. - * Update Media Upload to include io.Base and also fix some bugs. - - OAuth bug fixes and improvements. - * Remove OAuth 1.0 support. - * Added credentials_from_code and credentials_from_clientsecrets_and_code. - * Make oauth2client support Windows-friendly locking. - * Fix bug in StorageByKeyName. - * Fix None handling in Django fields. Reviewed in http://codereview.appspot.com/6298084/. Fixes issue #128. - - Add epydoc generated docs. Reviewed in http://codereview.appspot.com/6305043/ - - Move to PEP386 compliant version numbers. - - New and updated samples - * Ad Exchange Buyer API v1 code samples. - * Automatically generate Samples wiki page from README files. - * Update Google Prediction samples. - * Add a Tasks sample that demonstrates Service accounts. - * new analytics api samples. Reviewed here: http://codereview.appspot.com/5494058/ - - Convert all inline samples to the Farm API for consistency. - -v1.0beta8 - - Updated meda upload support. - - Many fixes for batch requests. - - Better handling for requests that don't require a body. - - Fix issues with Google App Engine Python 2.7 runtime. - - Better support for proxies. - - All Storages now have a .delete() method. - - Important changes which might break your code: - * apiclient.anyjson has moved to oauth2client.anyjson. - * Some calls, for example, taskqueue().lease() used to require a parameter - named body. In this new release only methods that really need to send a body - require a body parameter, and so you may get errors about an unknown - 'body' parameter in your call. The solution is to remove the unneeded - body={} parameter. - -v1.0beta7 - - Support for batch requests. http://code.google.com/p/google-api-python-client/wiki/Batch - - Support for media upload. http://code.google.com/p/google-api-python-client/wiki/MediaUpload - - Better handling for APIs that return something other than JSON. - - Major cleanup and consolidation of the samples. - - Bug fixes and other enhancements: - 72 Defect Appengine OAuth2Decorator: Convert redirect address to string - 22 Defect Better error handling for unknown service name or version - 48 Defect StorageByKeyName().get() has side effects - 50 Defect Need sample client code for Admin Audit API - 28 Defect better comments for app engine sample Nov 9 - 63 Enhancement Let OAuth2Decorator take a list of scope - diff --git a/third_party/google_api_python_client/LICENSE b/third_party/google_api_python_client/LICENSE deleted file mode 100644 index 2987b3b95e..0000000000 --- a/third_party/google_api_python_client/LICENSE +++ /dev/null @@ -1,22 +0,0 @@ - Copyright 2014 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. - -Dependent Modules -================= - -This code has the following dependencies -above and beyond the Python standard library: - -uritemplates - Apache License 2.0 -httplib2 - MIT License diff --git a/third_party/google_api_python_client/MANIFEST.in b/third_party/google_api_python_client/MANIFEST.in deleted file mode 100644 index cc692b3b7a..0000000000 --- a/third_party/google_api_python_client/MANIFEST.in +++ /dev/null @@ -1,6 +0,0 @@ -recursive-include apiclient *.json *.py -include CHANGELOG -include LICENSE -include README -include FAQ -include setpath.sh diff --git a/third_party/google_api_python_client/Makefile b/third_party/google_api_python_client/Makefile deleted file mode 100644 index 6366e77dc7..0000000000 --- a/third_party/google_api_python_client/Makefile +++ /dev/null @@ -1,47 +0,0 @@ -pep8: - find googleapiclient samples -name "*.py" | xargs pep8 --ignore=E111,E202 - -APP_ENGINE_PATH=../google_appengine - -test: - tox - -.PHONY: coverage -coverage: - coverage erase - find tests -name "test_*.py" | xargs --max-args=1 coverage run -a runtests.py - coverage report - coverage html - -.PHONY: docs -docs: - cd docs; ./build - mkdir -p docs/dyn - python describe.py - -.PHONY: wiki -wiki: - python samples-index.py > ../google-api-python-client.wiki/SampleApps.wiki - -.PHONY: prerelease -prerelease: - -rm -rf dist/ - -sudo rm -rf dist/ - -rm -rf snapshot/ - -sudo rm -rf snapshot/ - # ./tools/gae-zip-creator.sh - python expandsymlinks.py - cd snapshot; python setup.py clean - cd snapshot; python setup.py sdist --formats=gztar,zip - cd snapshot; tar czf google-api-python-client-samples-$(shell python setup.py --version).tar.gz samples - cd snapshot; zip -r google-api-python-client-samples-$(shell python setup.py --version).zip samples - - -.PHONY: release -release: prerelease - @echo "This target will upload a new release to PyPi and code.google.com hosting." - @echo "Are you sure you want to proceed? (yes/no)" - @read yn; if [ yes -ne $(yn) ]; then exit 1; fi - @echo "Here we go..." - cd snapshot; python setup.py sdist --formats=gztar,zip register upload - \ No newline at end of file diff --git a/third_party/google_api_python_client/README.chromium b/third_party/google_api_python_client/README.chromium deleted file mode 100644 index eb7a00d0d0..0000000000 --- a/third_party/google_api_python_client/README.chromium +++ /dev/null @@ -1,6 +0,0 @@ -URL: https://github.com/google/google-api-python-client -Version: v1.3.1 -Revision: 49d45a6c3318b75e551c3022020f46c78655f365 -License: Apache License, Version 2.0 (the "License") - -No local changes diff --git a/third_party/google_api_python_client/README.md b/third_party/google_api_python_client/README.md deleted file mode 100644 index c22221c072..0000000000 --- a/third_party/google_api_python_client/README.md +++ /dev/null @@ -1,32 +0,0 @@ -# About -This is the Python client library for Google's discovery based APIs. To get started, please see the [full documentation for this library](http://google.github.io/google-api-python-client). Additionally, [dynamically generated documentation](http://api-python-client-doc.appspot.com/) is available for all of the APIs supported by this library. - - -# Installation -To install, simply use `pip` or `easy_install`: - -```bash -$ pip install --upgrade google-api-python-client -``` -or -```bash -$ easy_install --upgrade google-api-python-client -``` - -See the [Developers Guide](https://developers.google.com/api-client-library/python/start/get_started) for more detailed instructions and additional documentation. - -# Python Version -Python 2.6 or 2.7 is required. Python 3.x is not yet supported. - -# Third Party Libraries and Dependencies -The following libraries will be installed when you install the client library: -* [httplib2](https://github.com/jcgregorio/httplib2) -* [uri-templates](https://github.com/uri-templates/uritemplate-py) - -For development you will also need the following libraries: -* [WebTest](http://pythonpaste.org/webtest/) -* [pycrypto](https://pypi.python.org/pypi/pycrypto) -* [pyopenssl](https://pypi.python.org/pypi/pyOpenSSL) - -# Contributing -Please see the [contributing page](http://google.github.io/google-api-python-client/contributing.html) for more information. In particular, we love pull requests - but please make sure to sign the contributor license agreement. diff --git a/third_party/google_api_python_client/__init__.py b/third_party/google_api_python_client/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/third_party/google_api_python_client/apiclient/__init__.py b/third_party/google_api_python_client/apiclient/__init__.py deleted file mode 100644 index 5efb142e01..0000000000 --- a/third_party/google_api_python_client/apiclient/__init__.py +++ /dev/null @@ -1,40 +0,0 @@ -"""Retain apiclient as an alias for googleapiclient.""" - -import googleapiclient - -try: - import oauth2client -except ImportError: - raise RuntimeError( - 'Previous version of google-api-python-client detected; due to a ' - 'packaging issue, we cannot perform an in-place upgrade. To repair, ' - 'remove and reinstall this package, along with oauth2client and ' - 'uritemplate. One can do this with pip via\n' - ' pip install -I google-api-python-client' - ) - -from googleapiclient import channel -from googleapiclient import discovery -from googleapiclient import errors -from googleapiclient import http -from googleapiclient import mimeparse -from googleapiclient import model -from googleapiclient import sample_tools -from googleapiclient import schema - -__version__ = googleapiclient.__version__ - -_SUBMODULES = { - 'channel': channel, - 'discovery': discovery, - 'errors': errors, - 'http': http, - 'mimeparse': mimeparse, - 'model': model, - 'sample_tools': sample_tools, - 'schema': schema, -} - -import sys -for module_name, module in _SUBMODULES.iteritems(): - sys.modules['apiclient.%s' % module_name] = module diff --git a/third_party/google_api_python_client/describe.py b/third_party/google_api_python_client/describe.py deleted file mode 100755 index 5dcac904c6..0000000000 --- a/third_party/google_api_python_client/describe.py +++ /dev/null @@ -1,390 +0,0 @@ -#!/usr/bin/python -# -# Copyright 2014 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 documentation for generate API surfaces. - -Command-line tool that creates documentation for all APIs listed in discovery. -The documentation is generated from a combination of the discovery document and -the generated API surface itself. -""" - -__author__ = 'jcgregorio@google.com (Joe Gregorio)' - -import argparse -import json -import os -import re -import string -import sys - -from googleapiclient.discovery import DISCOVERY_URI -from googleapiclient.discovery import build -from googleapiclient.discovery import build_from_document -import httplib2 -import uritemplate - -CSS = """ -""" - -METHOD_TEMPLATE = """
- $name($params) -
$doc
-
-""" - -COLLECTION_LINK = """

- $name() -

-

Returns the $name Resource.

-""" - -METHOD_LINK = """

- $name($params)

-

$firstline

""" - -BASE = 'docs/dyn' - -DIRECTORY_URI = 'https://www.googleapis.com/discovery/v1/apis?preferred=true' - -parser = argparse.ArgumentParser(description=__doc__) - -parser.add_argument('--discovery_uri_template', default=DISCOVERY_URI, - help='URI Template for discovery.') - -parser.add_argument('--discovery_uri', default='', - help=('URI of discovery document. If supplied then only ' - 'this API will be documented.')) - -parser.add_argument('--directory_uri', default=DIRECTORY_URI, - help=('URI of directory document. Unused if --discovery_uri' - ' is supplied.')) - -parser.add_argument('--dest', default=BASE, - help='Directory name to write documents into.') - - - -def safe_version(version): - """Create a safe version of the verion string. - - Needed so that we can distinguish between versions - and sub-collections in URIs. I.e. we don't want - adsense_v1.1 to refer to the '1' collection in the v1 - version of the adsense api. - - Args: - version: string, The version string. - Returns: - The string with '.' replaced with '_'. - """ - - return version.replace('.', '_') - - -def unsafe_version(version): - """Undoes what safe_version() does. - - See safe_version() for the details. - - - Args: - version: string, The safe version string. - Returns: - The string with '_' replaced with '.'. - """ - - return version.replace('_', '.') - - -def method_params(doc): - """Document the parameters of a method. - - Args: - doc: string, The method's docstring. - - Returns: - The method signature as a string. - """ - doclines = doc.splitlines() - if 'Args:' in doclines: - begin = doclines.index('Args:') - if 'Returns:' in doclines[begin+1:]: - end = doclines.index('Returns:', begin) - args = doclines[begin+1: end] - else: - args = doclines[begin+1:] - - parameters = [] - for line in args: - m = re.search('^\s+([a-zA-Z0-9_]+): (.*)', line) - if m is None: - continue - pname = m.group(1) - desc = m.group(2) - if '(required)' not in desc: - pname = pname + '=None' - parameters.append(pname) - parameters = ', '.join(parameters) - else: - parameters = '' - return parameters - - -def method(name, doc): - """Documents an individual method. - - Args: - name: string, Name of the method. - doc: string, The methods docstring. - """ - - params = method_params(doc) - return string.Template(METHOD_TEMPLATE).substitute( - name=name, params=params, doc=doc) - - -def breadcrumbs(path, root_discovery): - """Create the breadcrumb trail to this page of documentation. - - Args: - path: string, Dot separated name of the resource. - root_discovery: Deserialized discovery document. - - Returns: - HTML with links to each of the parent resources of this resource. - """ - parts = path.split('.') - - crumbs = [] - accumulated = [] - - for i, p in enumerate(parts): - prefix = '.'.join(accumulated) - # The first time through prefix will be [], so we avoid adding in a - # superfluous '.' to prefix. - if prefix: - prefix += '.' - display = p - if i == 0: - display = root_discovery.get('title', display) - crumbs.append('%s' % (prefix + p, display)) - accumulated.append(p) - - return ' . '.join(crumbs) - - -def document_collection(resource, path, root_discovery, discovery, css=CSS): - """Document a single collection in an API. - - Args: - resource: Collection or service being documented. - path: string, Dot separated name of the resource. - root_discovery: Deserialized discovery document. - discovery: Deserialized discovery document, but just the portion that - describes the resource. - css: string, The CSS to include in the generated file. - """ - collections = [] - methods = [] - resource_name = path.split('.')[-2] - html = [ - '', - css, - '

%s

' % breadcrumbs(path[:-1], root_discovery), - '

Instance Methods

' - ] - - # Which methods are for collections. - for name in dir(resource): - if not name.startswith('_') and callable(getattr(resource, name)): - if hasattr(getattr(resource, name), '__is_resource__'): - collections.append(name) - else: - methods.append(name) - - - # TOC - if collections: - for name in collections: - if not name.startswith('_') and callable(getattr(resource, name)): - href = path + name + '.html' - html.append(string.Template(COLLECTION_LINK).substitute( - href=href, name=name)) - - if methods: - for name in methods: - if not name.startswith('_') and callable(getattr(resource, name)): - doc = getattr(resource, name).__doc__ - params = method_params(doc) - firstline = doc.splitlines()[0] - html.append(string.Template(METHOD_LINK).substitute( - name=name, params=params, firstline=firstline)) - - if methods: - html.append('

Method Details

') - for name in methods: - dname = name.rsplit('_')[0] - html.append(method(name, getattr(resource, name).__doc__)) - - html.append('') - - return '\n'.join(html) - - -def document_collection_recursive(resource, path, root_discovery, discovery): - - html = document_collection(resource, path, root_discovery, discovery) - - f = open(os.path.join(FLAGS.dest, path + 'html'), 'w') - f.write(html.encode('utf-8')) - f.close() - - for name in dir(resource): - if (not name.startswith('_') - and callable(getattr(resource, name)) - and hasattr(getattr(resource, name), '__is_resource__')): - dname = name.rsplit('_')[0] - collection = getattr(resource, name)() - document_collection_recursive(collection, path + name + '.', root_discovery, - discovery['resources'].get(dname, {})) - -def document_api(name, version): - """Document the given API. - - Args: - name: string, Name of the API. - version: string, Version of the API. - """ - service = build(name, version) - response, content = http.request( - uritemplate.expand( - FLAGS.discovery_uri_template, { - 'api': name, - 'apiVersion': version}) - ) - discovery = json.loads(content) - - version = safe_version(version) - - document_collection_recursive( - service, '%s_%s.' % (name, version), discovery, discovery) - - -def document_api_from_discovery_document(uri): - """Document the given API. - - Args: - uri: string, URI of discovery document. - """ - http = httplib2.Http() - response, content = http.request(FLAGS.discovery_uri) - discovery = json.loads(content) - - service = build_from_document(discovery) - - name = discovery['version'] - version = safe_version(discovery['version']) - - document_collection_recursive( - service, '%s_%s.' % (name, version), discovery, discovery) - - -if __name__ == '__main__': - FLAGS = parser.parse_args(sys.argv[1:]) - if FLAGS.discovery_uri: - document_api_from_discovery_document(FLAGS.discovery_uri) - else: - http = httplib2.Http() - resp, content = http.request( - FLAGS.directory_uri, - headers={'X-User-IP': '0.0.0.0'}) - if resp.status == 200: - directory = json.loads(content)['items'] - for api in directory: - document_api(api['name'], api['version']) - else: - sys.exit("Failed to load the discovery document.") diff --git a/third_party/google_api_python_client/expandsymlinks.py b/third_party/google_api_python_client/expandsymlinks.py deleted file mode 100644 index 82136221b1..0000000000 --- a/third_party/google_api_python_client/expandsymlinks.py +++ /dev/null @@ -1,58 +0,0 @@ -#!/usr/bin/python2.4 -# -*- coding: utf-8 -*- -# -# Copyright 2014 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. - -"""Copy files from source to dest expanding symlinks along the way. -""" - -from shutil import copytree - -import argparse -import sys - - -# Ignore these files and directories when copying over files into the snapshot. -IGNORE = set(['.hg', 'httplib2', 'oauth2', 'simplejson', 'static']) - -# In addition to the above files also ignore these files and directories when -# copying over samples into the snapshot. -IGNORE_IN_SAMPLES = set(['googleapiclient', 'oauth2client', 'uritemplate']) - -parser = argparse.ArgumentParser(description=__doc__) - -parser.add_argument('--source', default='.', - help='Directory name to copy from.') - -parser.add_argument('--dest', default='snapshot', - help='Directory name to copy to.') - - -def _ignore(path, names): - retval = set() - if path != '.': - retval = retval.union(IGNORE_IN_SAMPLES.intersection(names)) - retval = retval.union(IGNORE.intersection(names)) - return retval - - -def main(): - copytree(FLAGS.source, FLAGS.dest, symlinks=True, - ignore=_ignore) - - -if __name__ == '__main__': - FLAGS = parser.parse_args(sys.argv[1:]) - main() diff --git a/third_party/google_api_python_client/googleapiclient/__init__.py b/third_party/google_api_python_client/googleapiclient/__init__.py deleted file mode 100644 index 1e1a6cf6d7..0000000000 --- a/third_party/google_api_python_client/googleapiclient/__init__.py +++ /dev/null @@ -1,15 +0,0 @@ -# Copyright 2014 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. - -__version__ = "1.3.1" diff --git a/third_party/google_api_python_client/googleapiclient/channel.py b/third_party/google_api_python_client/googleapiclient/channel.py deleted file mode 100644 index 68a3b89191..0000000000 --- a/third_party/google_api_python_client/googleapiclient/channel.py +++ /dev/null @@ -1,285 +0,0 @@ -"""Channel notifications support. - -Classes and functions to support channel subscriptions and notifications -on those channels. - -Notes: - - This code is based on experimental APIs and is subject to change. - - Notification does not do deduplication of notification ids, that's up to - the receiver. - - Storing the Channel between calls is up to the caller. - - -Example setting up a channel: - - # Create a new channel that gets notifications via webhook. - channel = new_webhook_channel("https://example.com/my_web_hook") - - # Store the channel, keyed by 'channel.id'. Store it before calling the - # watch method because notifications may start arriving before the watch - # method returns. - ... - - resp = service.objects().watchAll( - bucket="some_bucket_id", body=channel.body()).execute() - channel.update(resp) - - # Store the channel, keyed by 'channel.id'. Store it after being updated - # since the resource_id value will now be correct, and that's needed to - # stop a subscription. - ... - - -An example Webhook implementation using webapp2. Note that webapp2 puts -headers in a case insensitive dictionary, as headers aren't guaranteed to -always be upper case. - - id = self.request.headers[X_GOOG_CHANNEL_ID] - - # Retrieve the channel by id. - channel = ... - - # Parse notification from the headers, including validating the id. - n = notification_from_headers(channel, self.request.headers) - - # Do app specific stuff with the notification here. - if n.resource_state == 'sync': - # Code to handle sync state. - elif n.resource_state == 'exists': - # Code to handle the exists state. - elif n.resource_state == 'not_exists': - # Code to handle the not exists state. - - -Example of unsubscribing. - - service.channels().stop(channel.body()) -""" - -import datetime -import uuid - -from googleapiclient import errors -from ...oauth2client import util - - -# The unix time epoch starts at midnight 1970. -EPOCH = datetime.datetime.utcfromtimestamp(0) - -# Map the names of the parameters in the JSON channel description to -# the parameter names we use in the Channel class. -CHANNEL_PARAMS = { - 'address': 'address', - 'id': 'id', - 'expiration': 'expiration', - 'params': 'params', - 'resourceId': 'resource_id', - 'resourceUri': 'resource_uri', - 'type': 'type', - 'token': 'token', - } - -X_GOOG_CHANNEL_ID = 'X-GOOG-CHANNEL-ID' -X_GOOG_MESSAGE_NUMBER = 'X-GOOG-MESSAGE-NUMBER' -X_GOOG_RESOURCE_STATE = 'X-GOOG-RESOURCE-STATE' -X_GOOG_RESOURCE_URI = 'X-GOOG-RESOURCE-URI' -X_GOOG_RESOURCE_ID = 'X-GOOG-RESOURCE-ID' - - -def _upper_header_keys(headers): - new_headers = {} - for k, v in headers.iteritems(): - new_headers[k.upper()] = v - return new_headers - - -class Notification(object): - """A Notification from a Channel. - - Notifications are not usually constructed directly, but are returned - from functions like notification_from_headers(). - - Attributes: - message_number: int, The unique id number of this notification. - state: str, The state of the resource being monitored. - uri: str, The address of the resource being monitored. - resource_id: str, The unique identifier of the version of the resource at - this event. - """ - @util.positional(5) - def __init__(self, message_number, state, resource_uri, resource_id): - """Notification constructor. - - Args: - message_number: int, The unique id number of this notification. - state: str, The state of the resource being monitored. Can be one - of "exists", "not_exists", or "sync". - resource_uri: str, The address of the resource being monitored. - resource_id: str, The identifier of the watched resource. - """ - self.message_number = message_number - self.state = state - self.resource_uri = resource_uri - self.resource_id = resource_id - - -class Channel(object): - """A Channel for notifications. - - Usually not constructed directly, instead it is returned from helper - functions like new_webhook_channel(). - - Attributes: - type: str, The type of delivery mechanism used by this channel. For - example, 'web_hook'. - id: str, A UUID for the channel. - token: str, An arbitrary string associated with the channel that - is delivered to the target address with each event delivered - over this channel. - address: str, The address of the receiving entity where events are - delivered. Specific to the channel type. - expiration: int, The time, in milliseconds from the epoch, when this - channel will expire. - params: dict, A dictionary of string to string, with additional parameters - controlling delivery channel behavior. - resource_id: str, An opaque id that identifies the resource that is - being watched. Stable across different API versions. - resource_uri: str, The canonicalized ID of the watched resource. - """ - - @util.positional(5) - def __init__(self, type, id, token, address, expiration=None, - params=None, resource_id="", resource_uri=""): - """Create a new Channel. - - In user code, this Channel constructor will not typically be called - manually since there are functions for creating channels for each specific - type with a more customized set of arguments to pass. - - Args: - type: str, The type of delivery mechanism used by this channel. For - example, 'web_hook'. - id: str, A UUID for the channel. - token: str, An arbitrary string associated with the channel that - is delivered to the target address with each event delivered - over this channel. - address: str, The address of the receiving entity where events are - delivered. Specific to the channel type. - expiration: int, The time, in milliseconds from the epoch, when this - channel will expire. - params: dict, A dictionary of string to string, with additional parameters - controlling delivery channel behavior. - resource_id: str, An opaque id that identifies the resource that is - being watched. Stable across different API versions. - resource_uri: str, The canonicalized ID of the watched resource. - """ - self.type = type - self.id = id - self.token = token - self.address = address - self.expiration = expiration - self.params = params - self.resource_id = resource_id - self.resource_uri = resource_uri - - def body(self): - """Build a body from the Channel. - - Constructs a dictionary that's appropriate for passing into watch() - methods as the value of body argument. - - Returns: - A dictionary representation of the channel. - """ - result = { - 'id': self.id, - 'token': self.token, - 'type': self.type, - 'address': self.address - } - if self.params: - result['params'] = self.params - if self.resource_id: - result['resourceId'] = self.resource_id - if self.resource_uri: - result['resourceUri'] = self.resource_uri - if self.expiration: - result['expiration'] = self.expiration - - return result - - def update(self, resp): - """Update a channel with information from the response of watch(). - - When a request is sent to watch() a resource, the response returned - from the watch() request is a dictionary with updated channel information, - such as the resource_id, which is needed when stopping a subscription. - - Args: - resp: dict, The response from a watch() method. - """ - for json_name, param_name in CHANNEL_PARAMS.iteritems(): - value = resp.get(json_name) - if value is not None: - setattr(self, param_name, value) - - -def notification_from_headers(channel, headers): - """Parse a notification from the webhook request headers, validate - the notification, and return a Notification object. - - Args: - channel: Channel, The channel that the notification is associated with. - headers: dict, A dictionary like object that contains the request headers - from the webhook HTTP request. - - Returns: - A Notification object. - - Raises: - errors.InvalidNotificationError if the notification is invalid. - ValueError if the X-GOOG-MESSAGE-NUMBER can't be converted to an int. - """ - headers = _upper_header_keys(headers) - channel_id = headers[X_GOOG_CHANNEL_ID] - if channel.id != channel_id: - raise errors.InvalidNotificationError( - 'Channel id mismatch: %s != %s' % (channel.id, channel_id)) - else: - message_number = int(headers[X_GOOG_MESSAGE_NUMBER]) - state = headers[X_GOOG_RESOURCE_STATE] - resource_uri = headers[X_GOOG_RESOURCE_URI] - resource_id = headers[X_GOOG_RESOURCE_ID] - return Notification(message_number, state, resource_uri, resource_id) - - -@util.positional(2) -def new_webhook_channel(url, token=None, expiration=None, params=None): - """Create a new webhook Channel. - - Args: - url: str, URL to post notifications to. - token: str, An arbitrary string associated with the channel that - is delivered to the target address with each notification delivered - over this channel. - expiration: datetime.datetime, A time in the future when the channel - should expire. Can also be None if the subscription should use the - default expiration. Note that different services may have different - limits on how long a subscription lasts. Check the response from the - watch() method to see the value the service has set for an expiration - time. - params: dict, Extra parameters to pass on channel creation. Currently - not used for webhook channels. - """ - expiration_ms = 0 - if expiration: - delta = expiration - EPOCH - expiration_ms = delta.microseconds/1000 + ( - delta.seconds + delta.days*24*3600)*1000 - if expiration_ms < 0: - expiration_ms = 0 - - return Channel('web_hook', str(uuid.uuid4()), - token, url, expiration=expiration_ms, - params=params) - diff --git a/third_party/google_api_python_client/googleapiclient/discovery.py b/third_party/google_api_python_client/googleapiclient/discovery.py deleted file mode 100644 index 3ddac5712f..0000000000 --- a/third_party/google_api_python_client/googleapiclient/discovery.py +++ /dev/null @@ -1,995 +0,0 @@ -# Copyright 2014 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. - -"""Client for discovery based APIs. - -A client library for Google's discovery based APIs. -""" - -__author__ = 'jcgregorio@google.com (Joe Gregorio)' -__all__ = [ - 'build', - 'build_from_document', - 'fix_method_name', - 'key2param', - ] - - -# Standard library imports -import StringIO -import copy -from email.generator import Generator -from email.mime.multipart import MIMEMultipart -from email.mime.nonmultipart import MIMENonMultipart -import json -import keyword -import logging -import mimetypes -import os -import re -import urllib -import urlparse - -try: - from urlparse import parse_qsl -except ImportError: - from cgi import parse_qsl - -# Third-party imports -from ... import httplib2 -import mimeparse -from ... import uritemplate - -# Local imports -from googleapiclient.errors import HttpError -from googleapiclient.errors import InvalidJsonError -from googleapiclient.errors import MediaUploadSizeError -from googleapiclient.errors import UnacceptableMimeTypeError -from googleapiclient.errors import UnknownApiNameOrVersion -from googleapiclient.errors import UnknownFileType -from googleapiclient.http import HttpRequest -from googleapiclient.http import MediaFileUpload -from googleapiclient.http import MediaUpload -from googleapiclient.model import JsonModel -from googleapiclient.model import MediaModel -from googleapiclient.model import RawModel -from googleapiclient.schema import Schemas -from oauth2client.client import GoogleCredentials -from oauth2client.util import _add_query_parameter -from oauth2client.util import positional - - -# The client library requires a version of httplib2 that supports RETRIES. -httplib2.RETRIES = 1 - -logger = logging.getLogger(__name__) - -URITEMPLATE = re.compile('{[^}]*}') -VARNAME = re.compile('[a-zA-Z0-9_-]+') -DISCOVERY_URI = ('https://www.googleapis.com/discovery/v1/apis/' - '{api}/{apiVersion}/rest') -DEFAULT_METHOD_DOC = 'A description of how to use this function' -HTTP_PAYLOAD_METHODS = frozenset(['PUT', 'POST', 'PATCH']) -_MEDIA_SIZE_BIT_SHIFTS = {'KB': 10, 'MB': 20, 'GB': 30, 'TB': 40} -BODY_PARAMETER_DEFAULT_VALUE = { - 'description': 'The request body.', - 'type': 'object', - 'required': True, -} -MEDIA_BODY_PARAMETER_DEFAULT_VALUE = { - 'description': ('The filename of the media request body, or an instance ' - 'of a MediaUpload object.'), - 'type': 'string', - 'required': False, -} - -# Parameters accepted by the stack, but not visible via discovery. -# TODO(dhermes): Remove 'userip' in 'v2'. -STACK_QUERY_PARAMETERS = frozenset(['trace', 'pp', 'userip', 'strict']) -STACK_QUERY_PARAMETER_DEFAULT_VALUE = {'type': 'string', 'location': 'query'} - -# Library-specific reserved words beyond Python keywords. -RESERVED_WORDS = frozenset(['body']) - - -def fix_method_name(name): - """Fix method names to avoid reserved word conflicts. - - Args: - name: string, method name. - - Returns: - The name with a '_' prefixed if the name is a reserved word. - """ - if keyword.iskeyword(name) or name in RESERVED_WORDS: - return name + '_' - else: - return name - - -def key2param(key): - """Converts key names into parameter names. - - For example, converting "max-results" -> "max_results" - - Args: - key: string, the method key name. - - Returns: - A safe method name based on the key name. - """ - result = [] - key = list(key) - if not key[0].isalpha(): - result.append('x') - for c in key: - if c.isalnum(): - result.append(c) - else: - result.append('_') - - return ''.join(result) - - -@positional(2) -def build(serviceName, - version, - http=None, - discoveryServiceUrl=DISCOVERY_URI, - developerKey=None, - model=None, - requestBuilder=HttpRequest, - credentials=None): - """Construct a Resource for interacting with an API. - - Construct a Resource object for interacting with an API. The serviceName and - version are the names from the Discovery service. - - Args: - serviceName: string, name of the service. - version: string, the version of the service. - http: httplib2.Http, An instance of httplib2.Http or something that acts - like it that HTTP requests will be made through. - discoveryServiceUrl: string, a URI Template that points to the location of - the discovery service. It should have two parameters {api} and - {apiVersion} that when filled in produce an absolute URI to the discovery - document for that service. - developerKey: string, key obtained from - https://code.google.com/apis/console. - model: googleapiclient.Model, converts to and from the wire format. - requestBuilder: googleapiclient.http.HttpRequest, encapsulator for an HTTP - request. - credentials: oauth2client.Credentials, credentials to be used for - authentication. - - Returns: - A Resource object with methods for interacting with the service. - """ - params = { - 'api': serviceName, - 'apiVersion': version - } - - if http is None: - http = httplib2.Http() - - requested_url = uritemplate.expand(discoveryServiceUrl, params) - - # REMOTE_ADDR is defined by the CGI spec [RFC3875] as the environment - # variable that contains the network address of the client sending the - # request. If it exists then add that to the request for the discovery - # document to avoid exceeding the quota on discovery requests. - if 'REMOTE_ADDR' in os.environ: - requested_url = _add_query_parameter(requested_url, 'userIp', - os.environ['REMOTE_ADDR']) - logger.info('URL being requested: GET %s' % requested_url) - - resp, content = http.request(requested_url) - - if resp.status == 404: - raise UnknownApiNameOrVersion("name: %s version: %s" % (serviceName, - version)) - if resp.status >= 400: - raise HttpError(resp, content, uri=requested_url) - - try: - service = json.loads(content) - except ValueError, e: - logger.error('Failed to parse as JSON: ' + content) - raise InvalidJsonError() - - return build_from_document(content, base=discoveryServiceUrl, http=http, - developerKey=developerKey, model=model, requestBuilder=requestBuilder, - credentials=credentials) - - -@positional(1) -def build_from_document( - service, - base=None, - future=None, - http=None, - developerKey=None, - model=None, - requestBuilder=HttpRequest, - credentials=None): - """Create a Resource for interacting with an API. - - Same as `build()`, but constructs the Resource object from a discovery - document that is it given, as opposed to retrieving one over HTTP. - - Args: - service: string or object, the JSON discovery document describing the API. - The value passed in may either be the JSON string or the deserialized - JSON. - base: string, base URI for all HTTP requests, usually the discovery URI. - This parameter is no longer used as rootUrl and servicePath are included - within the discovery document. (deprecated) - future: string, discovery document with future capabilities (deprecated). - http: httplib2.Http, An instance of httplib2.Http or something that acts - like it that HTTP requests will be made through. - developerKey: string, Key for controlling API usage, generated - from the API Console. - model: Model class instance that serializes and de-serializes requests and - responses. - requestBuilder: Takes an http request and packages it up to be executed. - credentials: object, credentials to be used for authentication. - - Returns: - A Resource object with methods for interacting with the service. - """ - - # future is no longer used. - future = {} - - if isinstance(service, basestring): - service = json.loads(service) - base = urlparse.urljoin(service['rootUrl'], service['servicePath']) - schema = Schemas(service) - - if credentials: - # If credentials were passed in, we could have two cases: - # 1. the scopes were specified, in which case the given credentials - # are used for authorizing the http; - # 2. the scopes were not provided (meaning the Application Default - # Credentials are to be used). In this case, the Application Default - # Credentials are built and used instead of the original credentials. - # If there are no scopes found (meaning the given service requires no - # authentication), there is no authorization of the http. - if (isinstance(credentials, GoogleCredentials) and - credentials.create_scoped_required()): - scopes = service.get('auth', {}).get('oauth2', {}).get('scopes', {}) - if scopes: - credentials = credentials.create_scoped(scopes.keys()) - else: - # No need to authorize the http object - # if the service does not require authentication. - credentials = None - - if credentials: - http = credentials.authorize(http) - - if model is None: - features = service.get('features', []) - model = JsonModel('dataWrapper' in features) - return Resource(http=http, baseUrl=base, model=model, - developerKey=developerKey, requestBuilder=requestBuilder, - resourceDesc=service, rootDesc=service, schema=schema) - - -def _cast(value, schema_type): - """Convert value to a string based on JSON Schema type. - - See http://tools.ietf.org/html/draft-zyp-json-schema-03 for more details on - JSON Schema. - - Args: - value: any, the value to convert - schema_type: string, the type that value should be interpreted as - - Returns: - A string representation of 'value' based on the schema_type. - """ - if schema_type == 'string': - if type(value) == type('') or type(value) == type(u''): - return value - else: - return str(value) - elif schema_type == 'integer': - return str(int(value)) - elif schema_type == 'number': - return str(float(value)) - elif schema_type == 'boolean': - return str(bool(value)).lower() - else: - if type(value) == type('') or type(value) == type(u''): - return value - else: - return str(value) - - -def _media_size_to_long(maxSize): - """Convert a string media size, such as 10GB or 3TB into an integer. - - Args: - maxSize: string, size as a string, such as 2MB or 7GB. - - Returns: - The size as an integer value. - """ - if len(maxSize) < 2: - return 0L - units = maxSize[-2:].upper() - bit_shift = _MEDIA_SIZE_BIT_SHIFTS.get(units) - if bit_shift is not None: - return long(maxSize[:-2]) << bit_shift - else: - return long(maxSize) - - -def _media_path_url_from_info(root_desc, path_url): - """Creates an absolute media path URL. - - Constructed using the API root URI and service path from the discovery - document and the relative path for the API method. - - Args: - root_desc: Dictionary; the entire original deserialized discovery document. - path_url: String; the relative URL for the API method. Relative to the API - root, which is specified in the discovery document. - - Returns: - String; the absolute URI for media upload for the API method. - """ - return '%(root)supload/%(service_path)s%(path)s' % { - 'root': root_desc['rootUrl'], - 'service_path': root_desc['servicePath'], - 'path': path_url, - } - - -def _fix_up_parameters(method_desc, root_desc, http_method): - """Updates parameters of an API method with values specific to this library. - - Specifically, adds whatever global parameters are specified by the API to the - parameters for the individual method. Also adds parameters which don't - appear in the discovery document, but are available to all discovery based - APIs (these are listed in STACK_QUERY_PARAMETERS). - - SIDE EFFECTS: This updates the parameters dictionary object in the method - description. - - Args: - method_desc: Dictionary with metadata describing an API method. Value comes - from the dictionary of methods stored in the 'methods' key in the - deserialized discovery document. - root_desc: Dictionary; the entire original deserialized discovery document. - http_method: String; the HTTP method used to call the API method described - in method_desc. - - Returns: - The updated Dictionary stored in the 'parameters' key of the method - description dictionary. - """ - parameters = method_desc.setdefault('parameters', {}) - - # Add in the parameters common to all methods. - for name, description in root_desc.get('parameters', {}).iteritems(): - parameters[name] = description - - # Add in undocumented query parameters. - for name in STACK_QUERY_PARAMETERS: - parameters[name] = STACK_QUERY_PARAMETER_DEFAULT_VALUE.copy() - - # Add 'body' (our own reserved word) to parameters if the method supports - # a request payload. - if http_method in HTTP_PAYLOAD_METHODS and 'request' in method_desc: - body = BODY_PARAMETER_DEFAULT_VALUE.copy() - body.update(method_desc['request']) - parameters['body'] = body - - return parameters - - -def _fix_up_media_upload(method_desc, root_desc, path_url, parameters): - """Updates parameters of API by adding 'media_body' if supported by method. - - SIDE EFFECTS: If the method supports media upload and has a required body, - sets body to be optional (required=False) instead. Also, if there is a - 'mediaUpload' in the method description, adds 'media_upload' key to - parameters. - - Args: - method_desc: Dictionary with metadata describing an API method. Value comes - from the dictionary of methods stored in the 'methods' key in the - deserialized discovery document. - root_desc: Dictionary; the entire original deserialized discovery document. - path_url: String; the relative URL for the API method. Relative to the API - root, which is specified in the discovery document. - parameters: A dictionary describing method parameters for method described - in method_desc. - - Returns: - Triple (accept, max_size, media_path_url) where: - - accept is a list of strings representing what content types are - accepted for media upload. Defaults to empty list if not in the - discovery document. - - max_size is a long representing the max size in bytes allowed for a - media upload. Defaults to 0L if not in the discovery document. - - media_path_url is a String; the absolute URI for media upload for the - API method. Constructed using the API root URI and service path from - the discovery document and the relative path for the API method. If - media upload is not supported, this is None. - """ - media_upload = method_desc.get('mediaUpload', {}) - accept = media_upload.get('accept', []) - max_size = _media_size_to_long(media_upload.get('maxSize', '')) - media_path_url = None - - if media_upload: - media_path_url = _media_path_url_from_info(root_desc, path_url) - parameters['media_body'] = MEDIA_BODY_PARAMETER_DEFAULT_VALUE.copy() - if 'body' in parameters: - parameters['body']['required'] = False - - return accept, max_size, media_path_url - - -def _fix_up_method_description(method_desc, root_desc): - """Updates a method description in a discovery document. - - SIDE EFFECTS: Changes the parameters dictionary in the method description with - extra parameters which are used locally. - - Args: - method_desc: Dictionary with metadata describing an API method. Value comes - from the dictionary of methods stored in the 'methods' key in the - deserialized discovery document. - root_desc: Dictionary; the entire original deserialized discovery document. - - Returns: - Tuple (path_url, http_method, method_id, accept, max_size, media_path_url) - where: - - path_url is a String; the relative URL for the API method. Relative to - the API root, which is specified in the discovery document. - - http_method is a String; the HTTP method used to call the API method - described in the method description. - - method_id is a String; the name of the RPC method associated with the - API method, and is in the method description in the 'id' key. - - accept is a list of strings representing what content types are - accepted for media upload. Defaults to empty list if not in the - discovery document. - - max_size is a long representing the max size in bytes allowed for a - media upload. Defaults to 0L if not in the discovery document. - - media_path_url is a String; the absolute URI for media upload for the - API method. Constructed using the API root URI and service path from - the discovery document and the relative path for the API method. If - media upload is not supported, this is None. - """ - path_url = method_desc['path'] - http_method = method_desc['httpMethod'] - method_id = method_desc['id'] - - parameters = _fix_up_parameters(method_desc, root_desc, http_method) - # Order is important. `_fix_up_media_upload` needs `method_desc` to have a - # 'parameters' key and needs to know if there is a 'body' parameter because it - # also sets a 'media_body' parameter. - accept, max_size, media_path_url = _fix_up_media_upload( - method_desc, root_desc, path_url, parameters) - - return path_url, http_method, method_id, accept, max_size, media_path_url - - -# TODO(dhermes): Convert this class to ResourceMethod and make it callable -class ResourceMethodParameters(object): - """Represents the parameters associated with a method. - - Attributes: - argmap: Map from method parameter name (string) to query parameter name - (string). - required_params: List of required parameters (represented by parameter - name as string). - repeated_params: List of repeated parameters (represented by parameter - name as string). - pattern_params: Map from method parameter name (string) to regular - expression (as a string). If the pattern is set for a parameter, the - value for that parameter must match the regular expression. - query_params: List of parameters (represented by parameter name as string) - that will be used in the query string. - path_params: Set of parameters (represented by parameter name as string) - that will be used in the base URL path. - param_types: Map from method parameter name (string) to parameter type. Type - can be any valid JSON schema type; valid values are 'any', 'array', - 'boolean', 'integer', 'number', 'object', or 'string'. Reference: - http://tools.ietf.org/html/draft-zyp-json-schema-03#section-5.1 - enum_params: Map from method parameter name (string) to list of strings, - where each list of strings is the list of acceptable enum values. - """ - - def __init__(self, method_desc): - """Constructor for ResourceMethodParameters. - - Sets default values and defers to set_parameters to populate. - - Args: - method_desc: Dictionary with metadata describing an API method. Value - comes from the dictionary of methods stored in the 'methods' key in - the deserialized discovery document. - """ - self.argmap = {} - self.required_params = [] - self.repeated_params = [] - self.pattern_params = {} - self.query_params = [] - # TODO(dhermes): Change path_params to a list if the extra URITEMPLATE - # parsing is gotten rid of. - self.path_params = set() - self.param_types = {} - self.enum_params = {} - - self.set_parameters(method_desc) - - def set_parameters(self, method_desc): - """Populates maps and lists based on method description. - - Iterates through each parameter for the method and parses the values from - the parameter dictionary. - - Args: - method_desc: Dictionary with metadata describing an API method. Value - comes from the dictionary of methods stored in the 'methods' key in - the deserialized discovery document. - """ - for arg, desc in method_desc.get('parameters', {}).iteritems(): - param = key2param(arg) - self.argmap[param] = arg - - if desc.get('pattern'): - self.pattern_params[param] = desc['pattern'] - if desc.get('enum'): - self.enum_params[param] = desc['enum'] - if desc.get('required'): - self.required_params.append(param) - if desc.get('repeated'): - self.repeated_params.append(param) - if desc.get('location') == 'query': - self.query_params.append(param) - if desc.get('location') == 'path': - self.path_params.add(param) - self.param_types[param] = desc.get('type', 'string') - - # TODO(dhermes): Determine if this is still necessary. Discovery based APIs - # should have all path parameters already marked with - # 'location: path'. - for match in URITEMPLATE.finditer(method_desc['path']): - for namematch in VARNAME.finditer(match.group(0)): - name = key2param(namematch.group(0)) - self.path_params.add(name) - if name in self.query_params: - self.query_params.remove(name) - - -def createMethod(methodName, methodDesc, rootDesc, schema): - """Creates a method for attaching to a Resource. - - Args: - methodName: string, name of the method to use. - methodDesc: object, fragment of deserialized discovery document that - describes the method. - rootDesc: object, the entire deserialized discovery document. - schema: object, mapping of schema names to schema descriptions. - """ - methodName = fix_method_name(methodName) - (pathUrl, httpMethod, methodId, accept, - maxSize, mediaPathUrl) = _fix_up_method_description(methodDesc, rootDesc) - - parameters = ResourceMethodParameters(methodDesc) - - def method(self, **kwargs): - # Don't bother with doc string, it will be over-written by createMethod. - - for name in kwargs.iterkeys(): - if name not in parameters.argmap: - raise TypeError('Got an unexpected keyword argument "%s"' % name) - - # Remove args that have a value of None. - keys = kwargs.keys() - for name in keys: - if kwargs[name] is None: - del kwargs[name] - - for name in parameters.required_params: - if name not in kwargs: - raise TypeError('Missing required parameter "%s"' % name) - - for name, regex in parameters.pattern_params.iteritems(): - if name in kwargs: - if isinstance(kwargs[name], basestring): - pvalues = [kwargs[name]] - else: - pvalues = kwargs[name] - for pvalue in pvalues: - if re.match(regex, pvalue) is None: - raise TypeError( - 'Parameter "%s" value "%s" does not match the pattern "%s"' % - (name, pvalue, regex)) - - for name, enums in parameters.enum_params.iteritems(): - if name in kwargs: - # We need to handle the case of a repeated enum - # name differently, since we want to handle both - # arg='value' and arg=['value1', 'value2'] - if (name in parameters.repeated_params and - not isinstance(kwargs[name], basestring)): - values = kwargs[name] - else: - values = [kwargs[name]] - for value in values: - if value not in enums: - raise TypeError( - 'Parameter "%s" value "%s" is not an allowed value in "%s"' % - (name, value, str(enums))) - - actual_query_params = {} - actual_path_params = {} - for key, value in kwargs.iteritems(): - to_type = parameters.param_types.get(key, 'string') - # For repeated parameters we cast each member of the list. - if key in parameters.repeated_params and type(value) == type([]): - cast_value = [_cast(x, to_type) for x in value] - else: - cast_value = _cast(value, to_type) - if key in parameters.query_params: - actual_query_params[parameters.argmap[key]] = cast_value - if key in parameters.path_params: - actual_path_params[parameters.argmap[key]] = cast_value - body_value = kwargs.get('body', None) - media_filename = kwargs.get('media_body', None) - - if self._developerKey: - actual_query_params['key'] = self._developerKey - - model = self._model - if methodName.endswith('_media'): - model = MediaModel() - elif 'response' not in methodDesc: - model = RawModel() - - headers = {} - headers, params, query, body = model.request(headers, - actual_path_params, actual_query_params, body_value) - - expanded_url = uritemplate.expand(pathUrl, params) - url = urlparse.urljoin(self._baseUrl, expanded_url + query) - - resumable = None - multipart_boundary = '' - - if media_filename: - # Ensure we end up with a valid MediaUpload object. - if isinstance(media_filename, basestring): - (media_mime_type, encoding) = mimetypes.guess_type(media_filename) - if media_mime_type is None: - raise UnknownFileType(media_filename) - if not mimeparse.best_match([media_mime_type], ','.join(accept)): - raise UnacceptableMimeTypeError(media_mime_type) - media_upload = MediaFileUpload(media_filename, - mimetype=media_mime_type) - elif isinstance(media_filename, MediaUpload): - media_upload = media_filename - else: - raise TypeError('media_filename must be str or MediaUpload.') - - # Check the maxSize - if maxSize > 0 and media_upload.size() > maxSize: - raise MediaUploadSizeError("Media larger than: %s" % maxSize) - - # Use the media path uri for media uploads - expanded_url = uritemplate.expand(mediaPathUrl, params) - url = urlparse.urljoin(self._baseUrl, expanded_url + query) - if media_upload.resumable(): - url = _add_query_parameter(url, 'uploadType', 'resumable') - - if media_upload.resumable(): - # This is all we need to do for resumable, if the body exists it gets - # sent in the first request, otherwise an empty body is sent. - resumable = media_upload - else: - # A non-resumable upload - if body is None: - # This is a simple media upload - headers['content-type'] = media_upload.mimetype() - body = media_upload.getbytes(0, media_upload.size()) - url = _add_query_parameter(url, 'uploadType', 'media') - else: - # This is a multipart/related upload. - msgRoot = MIMEMultipart('related') - # msgRoot should not write out it's own headers - setattr(msgRoot, '_write_headers', lambda self: None) - - # attach the body as one part - msg = MIMENonMultipart(*headers['content-type'].split('/')) - msg.set_payload(body) - msgRoot.attach(msg) - - # attach the media as the second part - msg = MIMENonMultipart(*media_upload.mimetype().split('/')) - msg['Content-Transfer-Encoding'] = 'binary' - - payload = media_upload.getbytes(0, media_upload.size()) - msg.set_payload(payload) - msgRoot.attach(msg) - # encode the body: note that we can't use `as_string`, because - # it plays games with `From ` lines. - fp = StringIO.StringIO() - g = Generator(fp, mangle_from_=False) - g.flatten(msgRoot, unixfrom=False) - body = fp.getvalue() - - multipart_boundary = msgRoot.get_boundary() - headers['content-type'] = ('multipart/related; ' - 'boundary="%s"') % multipart_boundary - url = _add_query_parameter(url, 'uploadType', 'multipart') - - logger.info('URL being requested: %s %s' % (httpMethod,url)) - return self._requestBuilder(self._http, - model.response, - url, - method=httpMethod, - body=body, - headers=headers, - methodId=methodId, - resumable=resumable) - - docs = [methodDesc.get('description', DEFAULT_METHOD_DOC), '\n\n'] - if len(parameters.argmap) > 0: - docs.append('Args:\n') - - # Skip undocumented params and params common to all methods. - skip_parameters = rootDesc.get('parameters', {}).keys() - skip_parameters.extend(STACK_QUERY_PARAMETERS) - - all_args = parameters.argmap.keys() - args_ordered = [key2param(s) for s in methodDesc.get('parameterOrder', [])] - - # Move body to the front of the line. - if 'body' in all_args: - args_ordered.append('body') - - for name in all_args: - if name not in args_ordered: - args_ordered.append(name) - - for arg in args_ordered: - if arg in skip_parameters: - continue - - repeated = '' - if arg in parameters.repeated_params: - repeated = ' (repeated)' - required = '' - if arg in parameters.required_params: - required = ' (required)' - paramdesc = methodDesc['parameters'][parameters.argmap[arg]] - paramdoc = paramdesc.get('description', 'A parameter') - if '$ref' in paramdesc: - docs.append( - (' %s: object, %s%s%s\n The object takes the' - ' form of:\n\n%s\n\n') % (arg, paramdoc, required, repeated, - schema.prettyPrintByName(paramdesc['$ref']))) - else: - paramtype = paramdesc.get('type', 'string') - docs.append(' %s: %s, %s%s%s\n' % (arg, paramtype, paramdoc, required, - repeated)) - enum = paramdesc.get('enum', []) - enumDesc = paramdesc.get('enumDescriptions', []) - if enum and enumDesc: - docs.append(' Allowed values\n') - for (name, desc) in zip(enum, enumDesc): - docs.append(' %s - %s\n' % (name, desc)) - if 'response' in methodDesc: - if methodName.endswith('_media'): - docs.append('\nReturns:\n The media object as a string.\n\n ') - else: - docs.append('\nReturns:\n An object of the form:\n\n ') - docs.append(schema.prettyPrintSchema(methodDesc['response'])) - - setattr(method, '__doc__', ''.join(docs)) - return (methodName, method) - - -def createNextMethod(methodName): - """Creates any _next methods for attaching to a Resource. - - The _next methods allow for easy iteration through list() responses. - - Args: - methodName: string, name of the method to use. - """ - methodName = fix_method_name(methodName) - - def methodNext(self, previous_request, previous_response): - """Retrieves the next page of results. - -Args: - previous_request: The request for the previous page. (required) - previous_response: The response from the request for the previous page. (required) - -Returns: - A request object that you can call 'execute()' on to request the next - page. Returns None if there are no more items in the collection. - """ - # Retrieve nextPageToken from previous_response - # Use as pageToken in previous_request to create new request. - - if 'nextPageToken' not in previous_response: - return None - - request = copy.copy(previous_request) - - pageToken = previous_response['nextPageToken'] - parsed = list(urlparse.urlparse(request.uri)) - q = parse_qsl(parsed[4]) - - # Find and remove old 'pageToken' value from URI - newq = [(key, value) for (key, value) in q if key != 'pageToken'] - newq.append(('pageToken', pageToken)) - parsed[4] = urllib.urlencode(newq) - uri = urlparse.urlunparse(parsed) - - request.uri = uri - - logger.info('URL being requested: %s %s' % (methodName,uri)) - - return request - - return (methodName, methodNext) - - -class Resource(object): - """A class for interacting with a resource.""" - - def __init__(self, http, baseUrl, model, requestBuilder, developerKey, - resourceDesc, rootDesc, schema): - """Build a Resource from the API description. - - Args: - http: httplib2.Http, Object to make http requests with. - baseUrl: string, base URL for the API. All requests are relative to this - URI. - model: googleapiclient.Model, converts to and from the wire format. - requestBuilder: class or callable that instantiates an - googleapiclient.HttpRequest object. - developerKey: string, key obtained from - https://code.google.com/apis/console - resourceDesc: object, section of deserialized discovery document that - describes a resource. Note that the top level discovery document - is considered a resource. - rootDesc: object, the entire deserialized discovery document. - schema: object, mapping of schema names to schema descriptions. - """ - self._dynamic_attrs = [] - - self._http = http - self._baseUrl = baseUrl - self._model = model - self._developerKey = developerKey - self._requestBuilder = requestBuilder - self._resourceDesc = resourceDesc - self._rootDesc = rootDesc - self._schema = schema - - self._set_service_methods() - - def _set_dynamic_attr(self, attr_name, value): - """Sets an instance attribute and tracks it in a list of dynamic attributes. - - Args: - attr_name: string; The name of the attribute to be set - value: The value being set on the object and tracked in the dynamic cache. - """ - self._dynamic_attrs.append(attr_name) - self.__dict__[attr_name] = value - - def __getstate__(self): - """Trim the state down to something that can be pickled. - - Uses the fact that the instance variable _dynamic_attrs holds attrs that - will be wiped and restored on pickle serialization. - """ - state_dict = copy.copy(self.__dict__) - for dynamic_attr in self._dynamic_attrs: - del state_dict[dynamic_attr] - del state_dict['_dynamic_attrs'] - return state_dict - - def __setstate__(self, state): - """Reconstitute the state of the object from being pickled. - - Uses the fact that the instance variable _dynamic_attrs holds attrs that - will be wiped and restored on pickle serialization. - """ - self.__dict__.update(state) - self._dynamic_attrs = [] - self._set_service_methods() - - def _set_service_methods(self): - self._add_basic_methods(self._resourceDesc, self._rootDesc, self._schema) - self._add_nested_resources(self._resourceDesc, self._rootDesc, self._schema) - self._add_next_methods(self._resourceDesc, self._schema) - - def _add_basic_methods(self, resourceDesc, rootDesc, schema): - # Add basic methods to Resource - if 'methods' in resourceDesc: - for methodName, methodDesc in resourceDesc['methods'].iteritems(): - fixedMethodName, method = createMethod( - methodName, methodDesc, rootDesc, schema) - self._set_dynamic_attr(fixedMethodName, - method.__get__(self, self.__class__)) - # Add in _media methods. The functionality of the attached method will - # change when it sees that the method name ends in _media. - if methodDesc.get('supportsMediaDownload', False): - fixedMethodName, method = createMethod( - methodName + '_media', methodDesc, rootDesc, schema) - self._set_dynamic_attr(fixedMethodName, - method.__get__(self, self.__class__)) - - def _add_nested_resources(self, resourceDesc, rootDesc, schema): - # Add in nested resources - if 'resources' in resourceDesc: - - def createResourceMethod(methodName, methodDesc): - """Create a method on the Resource to access a nested Resource. - - Args: - methodName: string, name of the method to use. - methodDesc: object, fragment of deserialized discovery document that - describes the method. - """ - methodName = fix_method_name(methodName) - - def methodResource(self): - return Resource(http=self._http, baseUrl=self._baseUrl, - model=self._model, developerKey=self._developerKey, - requestBuilder=self._requestBuilder, - resourceDesc=methodDesc, rootDesc=rootDesc, - schema=schema) - - setattr(methodResource, '__doc__', 'A collection resource.') - setattr(methodResource, '__is_resource__', True) - - return (methodName, methodResource) - - for methodName, methodDesc in resourceDesc['resources'].iteritems(): - fixedMethodName, method = createResourceMethod(methodName, methodDesc) - self._set_dynamic_attr(fixedMethodName, - method.__get__(self, self.__class__)) - - def _add_next_methods(self, resourceDesc, schema): - # Add _next() methods - # Look for response bodies in schema that contain nextPageToken, and methods - # that take a pageToken parameter. - if 'methods' in resourceDesc: - for methodName, methodDesc in resourceDesc['methods'].iteritems(): - if 'response' in methodDesc: - responseSchema = methodDesc['response'] - if '$ref' in responseSchema: - responseSchema = schema.get(responseSchema['$ref']) - hasNextPageToken = 'nextPageToken' in responseSchema.get('properties', - {}) - hasPageToken = 'pageToken' in methodDesc.get('parameters', {}) - if hasNextPageToken and hasPageToken: - fixedMethodName, method = createNextMethod(methodName + '_next') - self._set_dynamic_attr(fixedMethodName, - method.__get__(self, self.__class__)) diff --git a/third_party/google_api_python_client/googleapiclient/errors.py b/third_party/google_api_python_client/googleapiclient/errors.py deleted file mode 100644 index a1999fd503..0000000000 --- a/third_party/google_api_python_client/googleapiclient/errors.py +++ /dev/null @@ -1,140 +0,0 @@ -#!/usr/bin/python2.4 -# -# Copyright 2014 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. - -"""Errors for the library. - -All exceptions defined by the library -should be defined in this file. -""" - -__author__ = 'jcgregorio@google.com (Joe Gregorio)' - -import json - -from ...oauth2client import util - - -class Error(Exception): - """Base error for this module.""" - pass - - -class HttpError(Error): - """HTTP data was invalid or unexpected.""" - - @util.positional(3) - def __init__(self, resp, content, uri=None): - self.resp = resp - self.content = content - self.uri = uri - - def _get_reason(self): - """Calculate the reason for the error from the response content.""" - reason = self.resp.reason - try: - data = json.loads(self.content) - reason = data['error']['message'] - except (ValueError, KeyError): - pass - if reason is None: - reason = '' - return reason - - def __repr__(self): - if self.uri: - return '' % ( - self.resp.status, self.uri, self._get_reason().strip()) - else: - return '' % (self.resp.status, self._get_reason()) - - __str__ = __repr__ - - -class InvalidJsonError(Error): - """The JSON returned could not be parsed.""" - pass - - -class UnknownFileType(Error): - """File type unknown or unexpected.""" - pass - - -class UnknownLinkType(Error): - """Link type unknown or unexpected.""" - pass - - -class UnknownApiNameOrVersion(Error): - """No API with that name and version exists.""" - pass - - -class UnacceptableMimeTypeError(Error): - """That is an unacceptable mimetype for this operation.""" - pass - - -class MediaUploadSizeError(Error): - """Media is larger than the method can accept.""" - pass - - -class ResumableUploadError(HttpError): - """Error occured during resumable upload.""" - pass - - -class InvalidChunkSizeError(Error): - """The given chunksize is not valid.""" - pass - -class InvalidNotificationError(Error): - """The channel Notification is invalid.""" - pass - -class BatchError(HttpError): - """Error occured during batch operations.""" - - @util.positional(2) - def __init__(self, reason, resp=None, content=None): - self.resp = resp - self.content = content - self.reason = reason - - def __repr__(self): - return '' % (self.resp.status, self.reason) - - __str__ = __repr__ - - -class UnexpectedMethodError(Error): - """Exception raised by RequestMockBuilder on unexpected calls.""" - - @util.positional(1) - def __init__(self, methodId=None): - """Constructor for an UnexpectedMethodError.""" - super(UnexpectedMethodError, self).__init__( - 'Received unexpected call %s' % methodId) - - -class UnexpectedBodyError(Error): - """Exception raised by RequestMockBuilder on unexpected bodies.""" - - def __init__(self, expected, provided): - """Constructor for an UnexpectedMethodError.""" - super(UnexpectedBodyError, self).__init__( - 'Expected: [%s] - Provided: [%s]' % (expected, provided)) diff --git a/third_party/google_api_python_client/googleapiclient/http.py b/third_party/google_api_python_client/googleapiclient/http.py deleted file mode 100644 index 863827933b..0000000000 --- a/third_party/google_api_python_client/googleapiclient/http.py +++ /dev/null @@ -1,1614 +0,0 @@ -# Copyright 2014 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. - -"""Classes to encapsulate a single HTTP request. - -The classes implement a command pattern, with every -object supporting an execute() method that does the -actuall HTTP request. -""" - -__author__ = 'jcgregorio@google.com (Joe Gregorio)' - -import StringIO -import base64 -import copy -import gzip -import httplib2 -import json -import logging -import mimeparse -import mimetypes -import os -import random -import sys -import time -import urllib -import urlparse -import uuid - -from email.generator import Generator -from email.mime.multipart import MIMEMultipart -from email.mime.nonmultipart import MIMENonMultipart -from email.parser import FeedParser -from errors import BatchError -from errors import HttpError -from errors import InvalidChunkSizeError -from errors import ResumableUploadError -from errors import UnexpectedBodyError -from errors import UnexpectedMethodError -from model import JsonModel -from ...oauth2client import util - - -DEFAULT_CHUNK_SIZE = 512*1024 - -MAX_URI_LENGTH = 2048 - - -class MediaUploadProgress(object): - """Status of a resumable upload.""" - - def __init__(self, resumable_progress, total_size): - """Constructor. - - Args: - resumable_progress: int, bytes sent so far. - total_size: int, total bytes in complete upload, or None if the total - upload size isn't known ahead of time. - """ - self.resumable_progress = resumable_progress - self.total_size = total_size - - def progress(self): - """Percent of upload completed, as a float. - - Returns: - the percentage complete as a float, returning 0.0 if the total size of - the upload is unknown. - """ - if self.total_size is not None: - return float(self.resumable_progress) / float(self.total_size) - else: - return 0.0 - - -class MediaDownloadProgress(object): - """Status of a resumable download.""" - - def __init__(self, resumable_progress, total_size): - """Constructor. - - Args: - resumable_progress: int, bytes received so far. - total_size: int, total bytes in complete download. - """ - self.resumable_progress = resumable_progress - self.total_size = total_size - - def progress(self): - """Percent of download completed, as a float. - - Returns: - the percentage complete as a float, returning 0.0 if the total size of - the download is unknown. - """ - if self.total_size is not None: - return float(self.resumable_progress) / float(self.total_size) - else: - return 0.0 - - -class MediaUpload(object): - """Describes a media object to upload. - - Base class that defines the interface of MediaUpload subclasses. - - Note that subclasses of MediaUpload may allow you to control the chunksize - when uploading a media object. It is important to keep the size of the chunk - as large as possible to keep the upload efficient. Other factors may influence - the size of the chunk you use, particularly if you are working in an - environment where individual HTTP requests may have a hardcoded time limit, - such as under certain classes of requests under Google App Engine. - - Streams are io.Base compatible objects that support seek(). Some MediaUpload - subclasses support using streams directly to upload data. Support for - streaming may be indicated by a MediaUpload sub-class and if appropriate for a - platform that stream will be used for uploading the media object. The support - for streaming is indicated by has_stream() returning True. The stream() method - should return an io.Base object that supports seek(). On platforms where the - underlying httplib module supports streaming, for example Python 2.6 and - later, the stream will be passed into the http library which will result in - less memory being used and possibly faster uploads. - - If you need to upload media that can't be uploaded using any of the existing - MediaUpload sub-class then you can sub-class MediaUpload for your particular - needs. - """ - - def chunksize(self): - """Chunk size for resumable uploads. - - Returns: - Chunk size in bytes. - """ - raise NotImplementedError() - - def mimetype(self): - """Mime type of the body. - - Returns: - Mime type. - """ - return 'application/octet-stream' - - def size(self): - """Size of upload. - - Returns: - Size of the body, or None of the size is unknown. - """ - return None - - def resumable(self): - """Whether this upload is resumable. - - Returns: - True if resumable upload or False. - """ - return False - - def getbytes(self, begin, end): - """Get bytes from the media. - - Args: - begin: int, offset from beginning of file. - length: int, number of bytes to read, starting at begin. - - Returns: - A string of bytes read. May be shorter than length if EOF was reached - first. - """ - raise NotImplementedError() - - def has_stream(self): - """Does the underlying upload support a streaming interface. - - Streaming means it is an io.IOBase subclass that supports seek, i.e. - seekable() returns True. - - Returns: - True if the call to stream() will return an instance of a seekable io.Base - subclass. - """ - return False - - def stream(self): - """A stream interface to the data being uploaded. - - Returns: - The returned value is an io.IOBase subclass that supports seek, i.e. - seekable() returns True. - """ - raise NotImplementedError() - - @util.positional(1) - def _to_json(self, strip=None): - """Utility function for creating a JSON representation of a MediaUpload. - - Args: - strip: array, An array of names of members to not include in the JSON. - - Returns: - string, a JSON representation of this instance, suitable to pass to - from_json(). - """ - t = type(self) - d = copy.copy(self.__dict__) - if strip is not None: - for member in strip: - del d[member] - d['_class'] = t.__name__ - d['_module'] = t.__module__ - return json.dumps(d) - - def to_json(self): - """Create a JSON representation of an instance of MediaUpload. - - Returns: - string, a JSON representation of this instance, suitable to pass to - from_json(). - """ - return self._to_json() - - @classmethod - def new_from_json(cls, s): - """Utility class method to instantiate a MediaUpload subclass from a JSON - representation produced by to_json(). - - Args: - s: string, JSON from to_json(). - - Returns: - An instance of the subclass of MediaUpload that was serialized with - to_json(). - """ - data = json.loads(s) - # Find and call the right classmethod from_json() to restore the object. - module = data['_module'] - m = __import__(module, fromlist=module.split('.')[:-1]) - kls = getattr(m, data['_class']) - from_json = getattr(kls, 'from_json') - return from_json(s) - - -class MediaIoBaseUpload(MediaUpload): - """A MediaUpload for a io.Base objects. - - Note that the Python file object is compatible with io.Base and can be used - with this class also. - - fh = io.BytesIO('...Some data to upload...') - media = MediaIoBaseUpload(fh, mimetype='image/png', - chunksize=1024*1024, resumable=True) - farm.animals().insert( - id='cow', - name='cow.png', - media_body=media).execute() - - Depending on the platform you are working on, you may pass -1 as the - chunksize, which indicates that the entire file should be uploaded in a single - request. If the underlying platform supports streams, such as Python 2.6 or - later, then this can be very efficient as it avoids multiple connections, and - also avoids loading the entire file into memory before sending it. Note that - Google App Engine has a 5MB limit on request size, so you should never set - your chunksize larger than 5MB, or to -1. - """ - - @util.positional(3) - def __init__(self, fd, mimetype, chunksize=DEFAULT_CHUNK_SIZE, - resumable=False): - """Constructor. - - Args: - fd: io.Base or file object, The source of the bytes to upload. MUST be - opened in blocking mode, do not use streams opened in non-blocking mode. - The given stream must be seekable, that is, it must be able to call - seek() on fd. - mimetype: string, Mime-type of the file. - chunksize: int, File will be uploaded in chunks of this many bytes. Only - used if resumable=True. Pass in a value of -1 if the file is to be - uploaded as a single chunk. Note that Google App Engine has a 5MB limit - on request size, so you should never set your chunksize larger than 5MB, - or to -1. - resumable: bool, True if this is a resumable upload. False means upload - in a single request. - """ - super(MediaIoBaseUpload, self).__init__() - self._fd = fd - self._mimetype = mimetype - if not (chunksize == -1 or chunksize > 0): - raise InvalidChunkSizeError() - self._chunksize = chunksize - self._resumable = resumable - - self._fd.seek(0, os.SEEK_END) - self._size = self._fd.tell() - - def chunksize(self): - """Chunk size for resumable uploads. - - Returns: - Chunk size in bytes. - """ - return self._chunksize - - def mimetype(self): - """Mime type of the body. - - Returns: - Mime type. - """ - return self._mimetype - - def size(self): - """Size of upload. - - Returns: - Size of the body, or None of the size is unknown. - """ - return self._size - - def resumable(self): - """Whether this upload is resumable. - - Returns: - True if resumable upload or False. - """ - return self._resumable - - def getbytes(self, begin, length): - """Get bytes from the media. - - Args: - begin: int, offset from beginning of file. - length: int, number of bytes to read, starting at begin. - - Returns: - A string of bytes read. May be shorted than length if EOF was reached - first. - """ - self._fd.seek(begin) - return self._fd.read(length) - - def has_stream(self): - """Does the underlying upload support a streaming interface. - - Streaming means it is an io.IOBase subclass that supports seek, i.e. - seekable() returns True. - - Returns: - True if the call to stream() will return an instance of a seekable io.Base - subclass. - """ - return True - - def stream(self): - """A stream interface to the data being uploaded. - - Returns: - The returned value is an io.IOBase subclass that supports seek, i.e. - seekable() returns True. - """ - return self._fd - - def to_json(self): - """This upload type is not serializable.""" - raise NotImplementedError('MediaIoBaseUpload is not serializable.') - - -class MediaFileUpload(MediaIoBaseUpload): - """A MediaUpload for a file. - - Construct a MediaFileUpload and pass as the media_body parameter of the - method. For example, if we had a service that allowed uploading images: - - - media = MediaFileUpload('cow.png', mimetype='image/png', - chunksize=1024*1024, resumable=True) - farm.animals().insert( - id='cow', - name='cow.png', - media_body=media).execute() - - Depending on the platform you are working on, you may pass -1 as the - chunksize, which indicates that the entire file should be uploaded in a single - request. If the underlying platform supports streams, such as Python 2.6 or - later, then this can be very efficient as it avoids multiple connections, and - also avoids loading the entire file into memory before sending it. Note that - Google App Engine has a 5MB limit on request size, so you should never set - your chunksize larger than 5MB, or to -1. - """ - - @util.positional(2) - def __init__(self, filename, mimetype=None, chunksize=DEFAULT_CHUNK_SIZE, - resumable=False): - """Constructor. - - Args: - filename: string, Name of the file. - mimetype: string, Mime-type of the file. If None then a mime-type will be - guessed from the file extension. - chunksize: int, File will be uploaded in chunks of this many bytes. Only - used if resumable=True. Pass in a value of -1 if the file is to be - uploaded in a single chunk. Note that Google App Engine has a 5MB limit - on request size, so you should never set your chunksize larger than 5MB, - or to -1. - resumable: bool, True if this is a resumable upload. False means upload - in a single request. - """ - self._filename = filename - fd = open(self._filename, 'rb') - if mimetype is None: - (mimetype, encoding) = mimetypes.guess_type(filename) - super(MediaFileUpload, self).__init__(fd, mimetype, chunksize=chunksize, - resumable=resumable) - - def to_json(self): - """Creating a JSON representation of an instance of MediaFileUpload. - - Returns: - string, a JSON representation of this instance, suitable to pass to - from_json(). - """ - return self._to_json(strip=['_fd']) - - @staticmethod - def from_json(s): - d = json.loads(s) - return MediaFileUpload(d['_filename'], mimetype=d['_mimetype'], - chunksize=d['_chunksize'], resumable=d['_resumable']) - - -class MediaInMemoryUpload(MediaIoBaseUpload): - """MediaUpload for a chunk of bytes. - - DEPRECATED: Use MediaIoBaseUpload with either io.TextIOBase or StringIO for - the stream. - """ - - @util.positional(2) - def __init__(self, body, mimetype='application/octet-stream', - chunksize=DEFAULT_CHUNK_SIZE, resumable=False): - """Create a new MediaInMemoryUpload. - - DEPRECATED: Use MediaIoBaseUpload with either io.TextIOBase or StringIO for - the stream. - - Args: - body: string, Bytes of body content. - mimetype: string, Mime-type of the file or default of - 'application/octet-stream'. - chunksize: int, File will be uploaded in chunks of this many bytes. Only - used if resumable=True. - resumable: bool, True if this is a resumable upload. False means upload - in a single request. - """ - fd = StringIO.StringIO(body) - super(MediaInMemoryUpload, self).__init__(fd, mimetype, chunksize=chunksize, - resumable=resumable) - - -class MediaIoBaseDownload(object): - """"Download media resources. - - Note that the Python file object is compatible with io.Base and can be used - with this class also. - - - Example: - request = farms.animals().get_media(id='cow') - fh = io.FileIO('cow.png', mode='wb') - downloader = MediaIoBaseDownload(fh, request, chunksize=1024*1024) - - done = False - while done is False: - status, done = downloader.next_chunk() - if status: - print "Download %d%%." % int(status.progress() * 100) - print "Download Complete!" - """ - - @util.positional(3) - def __init__(self, fd, request, chunksize=DEFAULT_CHUNK_SIZE): - """Constructor. - - Args: - fd: io.Base or file object, The stream in which to write the downloaded - bytes. - request: googleapiclient.http.HttpRequest, the media request to perform in - chunks. - chunksize: int, File will be downloaded in chunks of this many bytes. - """ - self._fd = fd - self._request = request - self._uri = request.uri - self._chunksize = chunksize - self._progress = 0 - self._total_size = None - self._done = False - - # Stubs for testing. - self._sleep = time.sleep - self._rand = random.random - - @util.positional(1) - def next_chunk(self, num_retries=0): - """Get the next chunk of the download. - - Args: - num_retries: Integer, number of times to retry 500's with randomized - exponential backoff. If all retries fail, the raised HttpError - represents the last request. If zero (default), we attempt the - request only once. - - Returns: - (status, done): (MediaDownloadStatus, boolean) - The value of 'done' will be True when the media has been fully - downloaded. - - Raises: - googleapiclient.errors.HttpError if the response was not a 2xx. - httplib2.HttpLib2Error if a transport error has occured. - """ - headers = { - 'range': 'bytes=%d-%d' % ( - self._progress, self._progress + self._chunksize) - } - http = self._request.http - - for retry_num in xrange(num_retries + 1): - if retry_num > 0: - self._sleep(self._rand() * 2**retry_num) - logging.warning( - 'Retry #%d for media download: GET %s, following status: %d' - % (retry_num, self._uri, resp.status)) - - resp, content = http.request(self._uri, headers=headers) - if resp.status < 500: - break - - if resp.status in [200, 206]: - if 'content-location' in resp and resp['content-location'] != self._uri: - self._uri = resp['content-location'] - self._progress += len(content) - self._fd.write(content) - - if 'content-range' in resp: - content_range = resp['content-range'] - length = content_range.rsplit('/', 1)[1] - self._total_size = int(length) - - if self._progress == self._total_size: - self._done = True - return MediaDownloadProgress(self._progress, self._total_size), self._done - else: - raise HttpError(resp, content, uri=self._uri) - - -class _StreamSlice(object): - """Truncated stream. - - Takes a stream and presents a stream that is a slice of the original stream. - This is used when uploading media in chunks. In later versions of Python a - stream can be passed to httplib in place of the string of data to send. The - problem is that httplib just blindly reads to the end of the stream. This - wrapper presents a virtual stream that only reads to the end of the chunk. - """ - - def __init__(self, stream, begin, chunksize): - """Constructor. - - Args: - stream: (io.Base, file object), the stream to wrap. - begin: int, the seek position the chunk begins at. - chunksize: int, the size of the chunk. - """ - self._stream = stream - self._begin = begin - self._chunksize = chunksize - self._stream.seek(begin) - - def read(self, n=-1): - """Read n bytes. - - Args: - n, int, the number of bytes to read. - - Returns: - A string of length 'n', or less if EOF is reached. - """ - # The data left available to read sits in [cur, end) - cur = self._stream.tell() - end = self._begin + self._chunksize - if n == -1 or cur + n > end: - n = end - cur - return self._stream.read(n) - - -class HttpRequest(object): - """Encapsulates a single HTTP request.""" - - @util.positional(4) - def __init__(self, http, postproc, uri, - method='GET', - body=None, - headers=None, - methodId=None, - resumable=None): - """Constructor for an HttpRequest. - - Args: - http: httplib2.Http, the transport object to use to make a request - postproc: callable, called on the HTTP response and content to transform - it into a data object before returning, or raising an exception - on an error. - uri: string, the absolute URI to send the request to - method: string, the HTTP method to use - body: string, the request body of the HTTP request, - headers: dict, the HTTP request headers - methodId: string, a unique identifier for the API method being called. - resumable: MediaUpload, None if this is not a resumbale request. - """ - self.uri = uri - self.method = method - self.body = body - self.headers = headers or {} - self.methodId = methodId - self.http = http - self.postproc = postproc - self.resumable = resumable - self.response_callbacks = [] - self._in_error_state = False - - # Pull the multipart boundary out of the content-type header. - major, minor, params = mimeparse.parse_mime_type( - headers.get('content-type', 'application/json')) - - # The size of the non-media part of the request. - self.body_size = len(self.body or '') - - # The resumable URI to send chunks to. - self.resumable_uri = None - - # The bytes that have been uploaded. - self.resumable_progress = 0 - - # Stubs for testing. - self._rand = random.random - self._sleep = time.sleep - - @util.positional(1) - def execute(self, http=None, num_retries=0): - """Execute the request. - - Args: - http: httplib2.Http, an http object to be used in place of the - one the HttpRequest request object was constructed with. - num_retries: Integer, number of times to retry 500's with randomized - exponential backoff. If all retries fail, the raised HttpError - represents the last request. If zero (default), we attempt the - request only once. - - Returns: - A deserialized object model of the response body as determined - by the postproc. - - Raises: - googleapiclient.errors.HttpError if the response was not a 2xx. - httplib2.HttpLib2Error if a transport error has occured. - """ - if http is None: - http = self.http - - if self.resumable: - body = None - while body is None: - _, body = self.next_chunk(http=http, num_retries=num_retries) - return body - - # Non-resumable case. - - if 'content-length' not in self.headers: - self.headers['content-length'] = str(self.body_size) - # If the request URI is too long then turn it into a POST request. - if len(self.uri) > MAX_URI_LENGTH and self.method == 'GET': - self.method = 'POST' - self.headers['x-http-method-override'] = 'GET' - self.headers['content-type'] = 'application/x-www-form-urlencoded' - parsed = urlparse.urlparse(self.uri) - self.uri = urlparse.urlunparse( - (parsed.scheme, parsed.netloc, parsed.path, parsed.params, None, - None) - ) - self.body = parsed.query - self.headers['content-length'] = str(len(self.body)) - - # Handle retries for server-side errors. - for retry_num in xrange(num_retries + 1): - if retry_num > 0: - self._sleep(self._rand() * 2**retry_num) - logging.warning('Retry #%d for request: %s %s, following status: %d' - % (retry_num, self.method, self.uri, resp.status)) - - resp, content = http.request(str(self.uri), method=str(self.method), - body=self.body, headers=self.headers) - if resp.status < 500: - break - - for callback in self.response_callbacks: - callback(resp) - if resp.status >= 300: - raise HttpError(resp, content, uri=self.uri) - return self.postproc(resp, content) - - @util.positional(2) - def add_response_callback(self, cb): - """add_response_headers_callback - - Args: - cb: Callback to be called on receiving the response headers, of signature: - - def cb(resp): - # Where resp is an instance of httplib2.Response - """ - self.response_callbacks.append(cb) - - @util.positional(1) - def next_chunk(self, http=None, num_retries=0): - """Execute the next step of a resumable upload. - - Can only be used if the method being executed supports media uploads and - the MediaUpload object passed in was flagged as using resumable upload. - - Example: - - media = MediaFileUpload('cow.png', mimetype='image/png', - chunksize=1000, resumable=True) - request = farm.animals().insert( - id='cow', - name='cow.png', - media_body=media) - - response = None - while response is None: - status, response = request.next_chunk() - if status: - print "Upload %d%% complete." % int(status.progress() * 100) - - - Args: - http: httplib2.Http, an http object to be used in place of the - one the HttpRequest request object was constructed with. - num_retries: Integer, number of times to retry 500's with randomized - exponential backoff. If all retries fail, the raised HttpError - represents the last request. If zero (default), we attempt the - request only once. - - Returns: - (status, body): (ResumableMediaStatus, object) - The body will be None until the resumable media is fully uploaded. - - Raises: - googleapiclient.errors.HttpError if the response was not a 2xx. - httplib2.HttpLib2Error if a transport error has occured. - """ - if http is None: - http = self.http - - if self.resumable.size() is None: - size = '*' - else: - size = str(self.resumable.size()) - - if self.resumable_uri is None: - start_headers = copy.copy(self.headers) - start_headers['X-Upload-Content-Type'] = self.resumable.mimetype() - if size != '*': - start_headers['X-Upload-Content-Length'] = size - start_headers['content-length'] = str(self.body_size) - - for retry_num in xrange(num_retries + 1): - if retry_num > 0: - self._sleep(self._rand() * 2**retry_num) - logging.warning( - 'Retry #%d for resumable URI request: %s %s, following status: %d' - % (retry_num, self.method, self.uri, resp.status)) - - resp, content = http.request(self.uri, method=self.method, - body=self.body, - headers=start_headers) - if resp.status < 500: - break - - if resp.status == 200 and 'location' in resp: - self.resumable_uri = resp['location'] - else: - raise ResumableUploadError(resp, content) - elif self._in_error_state: - # If we are in an error state then query the server for current state of - # the upload by sending an empty PUT and reading the 'range' header in - # the response. - headers = { - 'Content-Range': 'bytes */%s' % size, - 'content-length': '0' - } - resp, content = http.request(self.resumable_uri, 'PUT', - headers=headers) - status, body = self._process_response(resp, content) - if body: - # The upload was complete. - return (status, body) - - # The httplib.request method can take streams for the body parameter, but - # only in Python 2.6 or later. If a stream is available under those - # conditions then use it as the body argument. - if self.resumable.has_stream() and sys.version_info[1] >= 6: - data = self.resumable.stream() - if self.resumable.chunksize() == -1: - data.seek(self.resumable_progress) - chunk_end = self.resumable.size() - self.resumable_progress - 1 - else: - # Doing chunking with a stream, so wrap a slice of the stream. - data = _StreamSlice(data, self.resumable_progress, - self.resumable.chunksize()) - chunk_end = min( - self.resumable_progress + self.resumable.chunksize() - 1, - self.resumable.size() - 1) - else: - data = self.resumable.getbytes( - self.resumable_progress, self.resumable.chunksize()) - - # A short read implies that we are at EOF, so finish the upload. - if len(data) < self.resumable.chunksize(): - size = str(self.resumable_progress + len(data)) - - chunk_end = self.resumable_progress + len(data) - 1 - - headers = { - 'Content-Range': 'bytes %d-%d/%s' % ( - self.resumable_progress, chunk_end, size), - # Must set the content-length header here because httplib can't - # calculate the size when working with _StreamSlice. - 'Content-Length': str(chunk_end - self.resumable_progress + 1) - } - - for retry_num in xrange(num_retries + 1): - if retry_num > 0: - self._sleep(self._rand() * 2**retry_num) - logging.warning( - 'Retry #%d for media upload: %s %s, following status: %d' - % (retry_num, self.method, self.uri, resp.status)) - - try: - resp, content = http.request(self.resumable_uri, method='PUT', - body=data, - headers=headers) - except: - self._in_error_state = True - raise - if resp.status < 500: - break - - return self._process_response(resp, content) - - def _process_response(self, resp, content): - """Process the response from a single chunk upload. - - Args: - resp: httplib2.Response, the response object. - content: string, the content of the response. - - Returns: - (status, body): (ResumableMediaStatus, object) - The body will be None until the resumable media is fully uploaded. - - Raises: - googleapiclient.errors.HttpError if the response was not a 2xx or a 308. - """ - if resp.status in [200, 201]: - self._in_error_state = False - return None, self.postproc(resp, content) - elif resp.status == 308: - self._in_error_state = False - # A "308 Resume Incomplete" indicates we are not done. - self.resumable_progress = int(resp['range'].split('-')[1]) + 1 - if 'location' in resp: - self.resumable_uri = resp['location'] - else: - self._in_error_state = True - raise HttpError(resp, content, uri=self.uri) - - return (MediaUploadProgress(self.resumable_progress, self.resumable.size()), - None) - - def to_json(self): - """Returns a JSON representation of the HttpRequest.""" - d = copy.copy(self.__dict__) - if d['resumable'] is not None: - d['resumable'] = self.resumable.to_json() - del d['http'] - del d['postproc'] - del d['_sleep'] - del d['_rand'] - - return json.dumps(d) - - @staticmethod - def from_json(s, http, postproc): - """Returns an HttpRequest populated with info from a JSON object.""" - d = json.loads(s) - if d['resumable'] is not None: - d['resumable'] = MediaUpload.new_from_json(d['resumable']) - return HttpRequest( - http, - postproc, - uri=d['uri'], - method=d['method'], - body=d['body'], - headers=d['headers'], - methodId=d['methodId'], - resumable=d['resumable']) - - -class BatchHttpRequest(object): - """Batches multiple HttpRequest objects into a single HTTP request. - - Example: - from googleapiclient.http import BatchHttpRequest - - def list_animals(request_id, response, exception): - \"\"\"Do something with the animals list response.\"\"\" - if exception is not None: - # Do something with the exception. - pass - else: - # Do something with the response. - pass - - def list_farmers(request_id, response, exception): - \"\"\"Do something with the farmers list response.\"\"\" - if exception is not None: - # Do something with the exception. - pass - else: - # Do something with the response. - pass - - service = build('farm', 'v2') - - batch = BatchHttpRequest() - - batch.add(service.animals().list(), list_animals) - batch.add(service.farmers().list(), list_farmers) - batch.execute(http=http) - """ - - @util.positional(1) - def __init__(self, callback=None, batch_uri=None): - """Constructor for a BatchHttpRequest. - - Args: - callback: callable, A callback to be called for each response, of the - form callback(id, response, exception). The first parameter is the - request id, and the second is the deserialized response object. The - third is an googleapiclient.errors.HttpError exception object if an HTTP error - occurred while processing the request, or None if no error occurred. - batch_uri: string, URI to send batch requests to. - """ - if batch_uri is None: - batch_uri = 'https://www.googleapis.com/batch' - self._batch_uri = batch_uri - - # Global callback to be called for each individual response in the batch. - self._callback = callback - - # A map from id to request. - self._requests = {} - - # A map from id to callback. - self._callbacks = {} - - # List of request ids, in the order in which they were added. - self._order = [] - - # The last auto generated id. - self._last_auto_id = 0 - - # Unique ID on which to base the Content-ID headers. - self._base_id = None - - # A map from request id to (httplib2.Response, content) response pairs - self._responses = {} - - # A map of id(Credentials) that have been refreshed. - self._refreshed_credentials = {} - - def _refresh_and_apply_credentials(self, request, http): - """Refresh the credentials and apply to the request. - - Args: - request: HttpRequest, the request. - http: httplib2.Http, the global http object for the batch. - """ - # For the credentials to refresh, but only once per refresh_token - # If there is no http per the request then refresh the http passed in - # via execute() - creds = None - if request.http is not None and hasattr(request.http.request, - 'credentials'): - creds = request.http.request.credentials - elif http is not None and hasattr(http.request, 'credentials'): - creds = http.request.credentials - if creds is not None: - if id(creds) not in self._refreshed_credentials: - creds.refresh(http) - self._refreshed_credentials[id(creds)] = 1 - - # Only apply the credentials if we are using the http object passed in, - # otherwise apply() will get called during _serialize_request(). - if request.http is None or not hasattr(request.http.request, - 'credentials'): - creds.apply(request.headers) - - def _id_to_header(self, id_): - """Convert an id to a Content-ID header value. - - Args: - id_: string, identifier of individual request. - - Returns: - A Content-ID header with the id_ encoded into it. A UUID is prepended to - the value because Content-ID headers are supposed to be universally - unique. - """ - if self._base_id is None: - self._base_id = uuid.uuid4() - - return '<%s+%s>' % (self._base_id, urllib.quote(id_)) - - def _header_to_id(self, header): - """Convert a Content-ID header value to an id. - - Presumes the Content-ID header conforms to the format that _id_to_header() - returns. - - Args: - header: string, Content-ID header value. - - Returns: - The extracted id value. - - Raises: - BatchError if the header is not in the expected format. - """ - if header[0] != '<' or header[-1] != '>': - raise BatchError("Invalid value for Content-ID: %s" % header) - if '+' not in header: - raise BatchError("Invalid value for Content-ID: %s" % header) - base, id_ = header[1:-1].rsplit('+', 1) - - return urllib.unquote(id_) - - def _serialize_request(self, request): - """Convert an HttpRequest object into a string. - - Args: - request: HttpRequest, the request to serialize. - - Returns: - The request as a string in application/http format. - """ - # Construct status line - parsed = urlparse.urlparse(request.uri) - request_line = urlparse.urlunparse( - (None, None, parsed.path, parsed.params, parsed.query, None) - ) - status_line = request.method + ' ' + request_line + ' HTTP/1.1\n' - major, minor = request.headers.get('content-type', 'application/json').split('/') - msg = MIMENonMultipart(major, minor) - headers = request.headers.copy() - - if request.http is not None and hasattr(request.http.request, - 'credentials'): - request.http.request.credentials.apply(headers) - - # MIMENonMultipart adds its own Content-Type header. - if 'content-type' in headers: - del headers['content-type'] - - for key, value in headers.iteritems(): - msg[key] = value - msg['Host'] = parsed.netloc - msg.set_unixfrom(None) - - if request.body is not None: - msg.set_payload(request.body) - msg['content-length'] = str(len(request.body)) - - # Serialize the mime message. - fp = StringIO.StringIO() - # maxheaderlen=0 means don't line wrap headers. - g = Generator(fp, maxheaderlen=0) - g.flatten(msg, unixfrom=False) - body = fp.getvalue() - - # Strip off the \n\n that the MIME lib tacks onto the end of the payload. - if request.body is None: - body = body[:-2] - - return status_line.encode('utf-8') + body - - def _deserialize_response(self, payload): - """Convert string into httplib2 response and content. - - Args: - payload: string, headers and body as a string. - - Returns: - A pair (resp, content), such as would be returned from httplib2.request. - """ - # Strip off the status line - status_line, payload = payload.split('\n', 1) - protocol, status, reason = status_line.split(' ', 2) - - # Parse the rest of the response - parser = FeedParser() - parser.feed(payload) - msg = parser.close() - msg['status'] = status - - # Create httplib2.Response from the parsed headers. - resp = httplib2.Response(msg) - resp.reason = reason - resp.version = int(protocol.split('/', 1)[1].replace('.', '')) - - content = payload.split('\r\n\r\n', 1)[1] - - return resp, content - - def _new_id(self): - """Create a new id. - - Auto incrementing number that avoids conflicts with ids already used. - - Returns: - string, a new unique id. - """ - self._last_auto_id += 1 - while str(self._last_auto_id) in self._requests: - self._last_auto_id += 1 - return str(self._last_auto_id) - - @util.positional(2) - def add(self, request, callback=None, request_id=None): - """Add a new request. - - Every callback added will be paired with a unique id, the request_id. That - unique id will be passed back to the callback when the response comes back - from the server. The default behavior is to have the library generate it's - own unique id. If the caller passes in a request_id then they must ensure - uniqueness for each request_id, and if they are not an exception is - raised. Callers should either supply all request_ids or nevery supply a - request id, to avoid such an error. - - Args: - request: HttpRequest, Request to add to the batch. - callback: callable, A callback to be called for this response, of the - form callback(id, response, exception). The first parameter is the - request id, and the second is the deserialized response object. The - third is an googleapiclient.errors.HttpError exception object if an HTTP error - occurred while processing the request, or None if no errors occurred. - request_id: string, A unique id for the request. The id will be passed to - the callback with the response. - - Returns: - None - - Raises: - BatchError if a media request is added to a batch. - KeyError is the request_id is not unique. - """ - if request_id is None: - request_id = self._new_id() - if request.resumable is not None: - raise BatchError("Media requests cannot be used in a batch request.") - if request_id in self._requests: - raise KeyError("A request with this ID already exists: %s" % request_id) - self._requests[request_id] = request - self._callbacks[request_id] = callback - self._order.append(request_id) - - def _execute(self, http, order, requests): - """Serialize batch request, send to server, process response. - - Args: - http: httplib2.Http, an http object to be used to make the request with. - order: list, list of request ids in the order they were added to the - batch. - request: list, list of request objects to send. - - Raises: - httplib2.HttpLib2Error if a transport error has occured. - googleapiclient.errors.BatchError if the response is the wrong format. - """ - message = MIMEMultipart('mixed') - # Message should not write out it's own headers. - setattr(message, '_write_headers', lambda self: None) - - # Add all the individual requests. - for request_id in order: - request = requests[request_id] - - msg = MIMENonMultipart('application', 'http') - msg['Content-Transfer-Encoding'] = 'binary' - msg['Content-ID'] = self._id_to_header(request_id) - - body = self._serialize_request(request) - msg.set_payload(body) - message.attach(msg) - - # encode the body: note that we can't use `as_string`, because - # it plays games with `From ` lines. - fp = StringIO.StringIO() - g = Generator(fp, mangle_from_=False) - g.flatten(message, unixfrom=False) - body = fp.getvalue() - - headers = {} - headers['content-type'] = ('multipart/mixed; ' - 'boundary="%s"') % message.get_boundary() - - resp, content = http.request(self._batch_uri, method='POST', body=body, - headers=headers) - - if resp.status >= 300: - raise HttpError(resp, content, uri=self._batch_uri) - - # Now break out the individual responses and store each one. - boundary, _ = content.split(None, 1) - - # Prepend with a content-type header so FeedParser can handle it. - header = 'content-type: %s\r\n\r\n' % resp['content-type'] - for_parser = header + content - - parser = FeedParser() - parser.feed(for_parser) - mime_response = parser.close() - - if not mime_response.is_multipart(): - raise BatchError("Response not in multipart/mixed format.", resp=resp, - content=content) - - for part in mime_response.get_payload(): - request_id = self._header_to_id(part['Content-ID']) - response, content = self._deserialize_response(part.get_payload()) - self._responses[request_id] = (response, content) - - @util.positional(1) - def execute(self, http=None): - """Execute all the requests as a single batched HTTP request. - - Args: - http: httplib2.Http, an http object to be used in place of the one the - HttpRequest request object was constructed with. If one isn't supplied - then use a http object from the requests in this batch. - - Returns: - None - - Raises: - httplib2.HttpLib2Error if a transport error has occured. - googleapiclient.errors.BatchError if the response is the wrong format. - """ - - # If http is not supplied use the first valid one given in the requests. - if http is None: - for request_id in self._order: - request = self._requests[request_id] - if request is not None: - http = request.http - break - - if http is None: - raise ValueError("Missing a valid http object.") - - self._execute(http, self._order, self._requests) - - # Loop over all the requests and check for 401s. For each 401 request the - # credentials should be refreshed and then sent again in a separate batch. - redo_requests = {} - redo_order = [] - - for request_id in self._order: - resp, content = self._responses[request_id] - if resp['status'] == '401': - redo_order.append(request_id) - request = self._requests[request_id] - self._refresh_and_apply_credentials(request, http) - redo_requests[request_id] = request - - if redo_requests: - self._execute(http, redo_order, redo_requests) - - # Now process all callbacks that are erroring, and raise an exception for - # ones that return a non-2xx response? Or add extra parameter to callback - # that contains an HttpError? - - for request_id in self._order: - resp, content = self._responses[request_id] - - request = self._requests[request_id] - callback = self._callbacks[request_id] - - response = None - exception = None - try: - if resp.status >= 300: - raise HttpError(resp, content, uri=request.uri) - response = request.postproc(resp, content) - except HttpError, e: - exception = e - - if callback is not None: - callback(request_id, response, exception) - if self._callback is not None: - self._callback(request_id, response, exception) - - -class HttpRequestMock(object): - """Mock of HttpRequest. - - Do not construct directly, instead use RequestMockBuilder. - """ - - def __init__(self, resp, content, postproc): - """Constructor for HttpRequestMock - - Args: - resp: httplib2.Response, the response to emulate coming from the request - content: string, the response body - postproc: callable, the post processing function usually supplied by - the model class. See model.JsonModel.response() as an example. - """ - self.resp = resp - self.content = content - self.postproc = postproc - if resp is None: - self.resp = httplib2.Response({'status': 200, 'reason': 'OK'}) - if 'reason' in self.resp: - self.resp.reason = self.resp['reason'] - - def execute(self, http=None): - """Execute the request. - - Same behavior as HttpRequest.execute(), but the response is - mocked and not really from an HTTP request/response. - """ - return self.postproc(self.resp, self.content) - - -class RequestMockBuilder(object): - """A simple mock of HttpRequest - - Pass in a dictionary to the constructor that maps request methodIds to - tuples of (httplib2.Response, content, opt_expected_body) that should be - returned when that method is called. None may also be passed in for the - httplib2.Response, in which case a 200 OK response will be generated. - If an opt_expected_body (str or dict) is provided, it will be compared to - the body and UnexpectedBodyError will be raised on inequality. - - Example: - response = '{"data": {"id": "tag:google.c...' - requestBuilder = RequestMockBuilder( - { - 'plus.activities.get': (None, response), - } - ) - googleapiclient.discovery.build("plus", "v1", requestBuilder=requestBuilder) - - Methods that you do not supply a response for will return a - 200 OK with an empty string as the response content or raise an excpetion - if check_unexpected is set to True. The methodId is taken from the rpcName - in the discovery document. - - For more details see the project wiki. - """ - - def __init__(self, responses, check_unexpected=False): - """Constructor for RequestMockBuilder - - The constructed object should be a callable object - that can replace the class HttpResponse. - - responses - A dictionary that maps methodIds into tuples - of (httplib2.Response, content). The methodId - comes from the 'rpcName' field in the discovery - document. - check_unexpected - A boolean setting whether or not UnexpectedMethodError - should be raised on unsupplied method. - """ - self.responses = responses - self.check_unexpected = check_unexpected - - def __call__(self, http, postproc, uri, method='GET', body=None, - headers=None, methodId=None, resumable=None): - """Implements the callable interface that discovery.build() expects - of requestBuilder, which is to build an object compatible with - HttpRequest.execute(). See that method for the description of the - parameters and the expected response. - """ - if methodId in self.responses: - response = self.responses[methodId] - resp, content = response[:2] - if len(response) > 2: - # Test the body against the supplied expected_body. - expected_body = response[2] - if bool(expected_body) != bool(body): - # Not expecting a body and provided one - # or expecting a body and not provided one. - raise UnexpectedBodyError(expected_body, body) - if isinstance(expected_body, str): - expected_body = json.loads(expected_body) - body = json.loads(body) - if body != expected_body: - raise UnexpectedBodyError(expected_body, body) - return HttpRequestMock(resp, content, postproc) - elif self.check_unexpected: - raise UnexpectedMethodError(methodId=methodId) - else: - model = JsonModel(False) - return HttpRequestMock(None, '{}', model.response) - - -class HttpMock(object): - """Mock of httplib2.Http""" - - def __init__(self, filename=None, headers=None): - """ - Args: - filename: string, absolute filename to read response from - headers: dict, header to return with response - """ - if headers is None: - headers = {'status': '200 OK'} - if filename: - f = file(filename, 'r') - self.data = f.read() - f.close() - else: - self.data = None - self.response_headers = headers - self.headers = None - self.uri = None - self.method = None - self.body = None - self.headers = None - - - def request(self, uri, - method='GET', - body=None, - headers=None, - redirections=1, - connection_type=None): - self.uri = uri - self.method = method - self.body = body - self.headers = headers - return httplib2.Response(self.response_headers), self.data - - -class HttpMockSequence(object): - """Mock of httplib2.Http - - Mocks a sequence of calls to request returning different responses for each - call. Create an instance initialized with the desired response headers - and content and then use as if an httplib2.Http instance. - - http = HttpMockSequence([ - ({'status': '401'}, ''), - ({'status': '200'}, '{"access_token":"1/3w","expires_in":3600}'), - ({'status': '200'}, 'echo_request_headers'), - ]) - resp, content = http.request("http://examples.com") - - There are special values you can pass in for content to trigger - behavours that are helpful in testing. - - 'echo_request_headers' means return the request headers in the response body - 'echo_request_headers_as_json' means return the request headers in - the response body - 'echo_request_body' means return the request body in the response body - 'echo_request_uri' means return the request uri in the response body - """ - - def __init__(self, iterable): - """ - Args: - iterable: iterable, a sequence of pairs of (headers, body) - """ - self._iterable = iterable - self.follow_redirects = True - - def request(self, uri, - method='GET', - body=None, - headers=None, - redirections=1, - connection_type=None): - resp, content = self._iterable.pop(0) - if content == 'echo_request_headers': - content = headers - elif content == 'echo_request_headers_as_json': - content = json.dumps(headers) - elif content == 'echo_request_body': - if hasattr(body, 'read'): - content = body.read() - else: - content = body - elif content == 'echo_request_uri': - content = uri - return httplib2.Response(resp), content - - -def set_user_agent(http, user_agent): - """Set the user-agent on every request. - - Args: - http - An instance of httplib2.Http - or something that acts like it. - user_agent: string, the value for the user-agent header. - - Returns: - A modified instance of http that was passed in. - - Example: - - h = httplib2.Http() - h = set_user_agent(h, "my-app-name/6.0") - - Most of the time the user-agent will be set doing auth, this is for the rare - cases where you are accessing an unauthenticated endpoint. - """ - request_orig = http.request - - # The closure that will replace 'httplib2.Http.request'. - def new_request(uri, method='GET', body=None, headers=None, - redirections=httplib2.DEFAULT_MAX_REDIRECTS, - connection_type=None): - """Modify the request headers to add the user-agent.""" - if headers is None: - headers = {} - if 'user-agent' in headers: - headers['user-agent'] = user_agent + ' ' + headers['user-agent'] - else: - headers['user-agent'] = user_agent - resp, content = request_orig(uri, method, body, headers, - redirections, connection_type) - return resp, content - - http.request = new_request - return http - - -def tunnel_patch(http): - """Tunnel PATCH requests over POST. - Args: - http - An instance of httplib2.Http - or something that acts like it. - - Returns: - A modified instance of http that was passed in. - - Example: - - h = httplib2.Http() - h = tunnel_patch(h, "my-app-name/6.0") - - Useful if you are running on a platform that doesn't support PATCH. - Apply this last if you are using OAuth 1.0, as changing the method - will result in a different signature. - """ - request_orig = http.request - - # The closure that will replace 'httplib2.Http.request'. - def new_request(uri, method='GET', body=None, headers=None, - redirections=httplib2.DEFAULT_MAX_REDIRECTS, - connection_type=None): - """Modify the request headers to add the user-agent.""" - if headers is None: - headers = {} - if method == 'PATCH': - if 'oauth_token' in headers.get('authorization', ''): - logging.warning( - 'OAuth 1.0 request made with Credentials after tunnel_patch.') - headers['x-http-method-override'] = "PATCH" - method = 'POST' - resp, content = request_orig(uri, method, body, headers, - redirections, connection_type) - return resp, content - - http.request = new_request - return http diff --git a/third_party/google_api_python_client/googleapiclient/mimeparse.py b/third_party/google_api_python_client/googleapiclient/mimeparse.py deleted file mode 100644 index 8038af18c9..0000000000 --- a/third_party/google_api_python_client/googleapiclient/mimeparse.py +++ /dev/null @@ -1,172 +0,0 @@ -# Copyright 2014 Joe Gregorio -# -# Licensed under the MIT License - -"""MIME-Type Parser - -This module provides basic functions for handling mime-types. It can handle -matching mime-types against a list of media-ranges. See section 14.1 of the -HTTP specification [RFC 2616] for a complete explanation. - - http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1 - -Contents: - - parse_mime_type(): Parses a mime-type into its component parts. - - parse_media_range(): Media-ranges are mime-types with wild-cards and a 'q' - quality parameter. - - quality(): Determines the quality ('q') of a mime-type when - compared against a list of media-ranges. - - quality_parsed(): Just like quality() except the second parameter must be - pre-parsed. - - best_match(): Choose the mime-type with the highest quality ('q') - from a list of candidates. -""" - -__version__ = '0.1.3' -__author__ = 'Joe Gregorio' -__email__ = 'joe@bitworking.org' -__license__ = 'MIT License' -__credits__ = '' - - -def parse_mime_type(mime_type): - """Parses a mime-type into its component parts. - - Carves up a mime-type and returns a tuple of the (type, subtype, params) - where 'params' is a dictionary of all the parameters for the media range. - For example, the media range 'application/xhtml;q=0.5' would get parsed - into: - - ('application', 'xhtml', {'q', '0.5'}) - """ - parts = mime_type.split(';') - params = dict([tuple([s.strip() for s in param.split('=', 1)])\ - for param in parts[1:] - ]) - full_type = parts[0].strip() - # Java URLConnection class sends an Accept header that includes a - # single '*'. Turn it into a legal wildcard. - if full_type == '*': - full_type = '*/*' - (type, subtype) = full_type.split('/') - - return (type.strip(), subtype.strip(), params) - - -def parse_media_range(range): - """Parse a media-range into its component parts. - - Carves up a media range and returns a tuple of the (type, subtype, - params) where 'params' is a dictionary of all the parameters for the media - range. For example, the media range 'application/*;q=0.5' would get parsed - into: - - ('application', '*', {'q', '0.5'}) - - In addition this function also guarantees that there is a value for 'q' - in the params dictionary, filling it in with a proper default if - necessary. - """ - (type, subtype, params) = parse_mime_type(range) - if not params.has_key('q') or not params['q'] or \ - not float(params['q']) or float(params['q']) > 1\ - or float(params['q']) < 0: - params['q'] = '1' - - return (type, subtype, params) - - -def fitness_and_quality_parsed(mime_type, parsed_ranges): - """Find the best match for a mime-type amongst parsed media-ranges. - - Find the best match for a given mime-type against a list of media_ranges - that have already been parsed by parse_media_range(). Returns a tuple of - the fitness value and the value of the 'q' quality parameter of the best - match, or (-1, 0) if no match was found. Just as for quality_parsed(), - 'parsed_ranges' must be a list of parsed media ranges. - """ - best_fitness = -1 - best_fit_q = 0 - (target_type, target_subtype, target_params) =\ - parse_media_range(mime_type) - for (type, subtype, params) in parsed_ranges: - type_match = (type == target_type or\ - type == '*' or\ - target_type == '*') - subtype_match = (subtype == target_subtype or\ - subtype == '*' or\ - target_subtype == '*') - if type_match and subtype_match: - param_matches = reduce(lambda x, y: x + y, [1 for (key, value) in \ - target_params.iteritems() if key != 'q' and \ - params.has_key(key) and value == params[key]], 0) - fitness = (type == target_type) and 100 or 0 - fitness += (subtype == target_subtype) and 10 or 0 - fitness += param_matches - if fitness > best_fitness: - best_fitness = fitness - best_fit_q = params['q'] - - return best_fitness, float(best_fit_q) - - -def quality_parsed(mime_type, parsed_ranges): - """Find the best match for a mime-type amongst parsed media-ranges. - - Find the best match for a given mime-type against a list of media_ranges - that have already been parsed by parse_media_range(). Returns the 'q' - quality parameter of the best match, 0 if no match was found. This function - bahaves the same as quality() except that 'parsed_ranges' must be a list of - parsed media ranges. - """ - - return fitness_and_quality_parsed(mime_type, parsed_ranges)[1] - - -def quality(mime_type, ranges): - """Return the quality ('q') of a mime-type against a list of media-ranges. - - Returns the quality 'q' of a mime-type when compared against the - media-ranges in ranges. For example: - - >>> quality('text/html','text/*;q=0.3, text/html;q=0.7, - text/html;level=1, text/html;level=2;q=0.4, */*;q=0.5') - 0.7 - - """ - parsed_ranges = [parse_media_range(r) for r in ranges.split(',')] - - return quality_parsed(mime_type, parsed_ranges) - - -def best_match(supported, header): - """Return mime-type with the highest quality ('q') from list of candidates. - - Takes a list of supported mime-types and finds the best match for all the - media-ranges listed in header. The value of header must be a string that - conforms to the format of the HTTP Accept: header. The value of 'supported' - is a list of mime-types. The list of supported mime-types should be sorted - in order of increasing desirability, in case of a situation where there is - a tie. - - >>> best_match(['application/xbel+xml', 'text/xml'], - 'text/*;q=0.5,*/*; q=0.1') - 'text/xml' - """ - split_header = _filter_blank(header.split(',')) - parsed_header = [parse_media_range(r) for r in split_header] - weighted_matches = [] - pos = 0 - for mime_type in supported: - weighted_matches.append((fitness_and_quality_parsed(mime_type, - parsed_header), pos, mime_type)) - pos += 1 - weighted_matches.sort() - - return weighted_matches[-1][0][1] and weighted_matches[-1][2] or '' - - -def _filter_blank(i): - for s in i: - if s.strip(): - yield s diff --git a/third_party/google_api_python_client/googleapiclient/model.py b/third_party/google_api_python_client/googleapiclient/model.py deleted file mode 100644 index 0f0172cab5..0000000000 --- a/third_party/google_api_python_client/googleapiclient/model.py +++ /dev/null @@ -1,383 +0,0 @@ -#!/usr/bin/python2.4 -# -# Copyright 2014 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. - -"""Model objects for requests and responses. - -Each API may support one or more serializations, such -as JSON, Atom, etc. The model classes are responsible -for converting between the wire format and the Python -object representation. -""" - -__author__ = 'jcgregorio@google.com (Joe Gregorio)' - -import json -import logging -import urllib - -from googleapiclient import __version__ -from errors import HttpError - - -dump_request_response = False - - -def _abstract(): - raise NotImplementedError('You need to override this function') - - -class Model(object): - """Model base class. - - All Model classes should implement this interface. - The Model serializes and de-serializes between a wire - format such as JSON and a Python object representation. - """ - - def request(self, headers, path_params, query_params, body_value): - """Updates outgoing requests with a serialized body. - - Args: - headers: dict, request headers - path_params: dict, parameters that appear in the request path - query_params: dict, parameters that appear in the query - body_value: object, the request body as a Python object, which must be - serializable. - Returns: - A tuple of (headers, path_params, query, body) - - headers: dict, request headers - path_params: dict, parameters that appear in the request path - query: string, query part of the request URI - body: string, the body serialized in the desired wire format. - """ - _abstract() - - def response(self, resp, content): - """Convert the response wire format into a Python object. - - Args: - resp: httplib2.Response, the HTTP response headers and status - content: string, the body of the HTTP response - - Returns: - The body de-serialized as a Python object. - - Raises: - googleapiclient.errors.HttpError if a non 2xx response is received. - """ - _abstract() - - -class BaseModel(Model): - """Base model class. - - Subclasses should provide implementations for the "serialize" and - "deserialize" methods, as well as values for the following class attributes. - - Attributes: - accept: The value to use for the HTTP Accept header. - content_type: The value to use for the HTTP Content-type header. - no_content_response: The value to return when deserializing a 204 "No - Content" response. - alt_param: The value to supply as the "alt" query parameter for requests. - """ - - accept = None - content_type = None - no_content_response = None - alt_param = None - - def _log_request(self, headers, path_params, query, body): - """Logs debugging information about the request if requested.""" - if dump_request_response: - logging.info('--request-start--') - logging.info('-headers-start-') - for h, v in headers.iteritems(): - logging.info('%s: %s', h, v) - logging.info('-headers-end-') - logging.info('-path-parameters-start-') - for h, v in path_params.iteritems(): - logging.info('%s: %s', h, v) - logging.info('-path-parameters-end-') - logging.info('body: %s', body) - logging.info('query: %s', query) - logging.info('--request-end--') - - def request(self, headers, path_params, query_params, body_value): - """Updates outgoing requests with a serialized body. - - Args: - headers: dict, request headers - path_params: dict, parameters that appear in the request path - query_params: dict, parameters that appear in the query - body_value: object, the request body as a Python object, which must be - serializable by json. - Returns: - A tuple of (headers, path_params, query, body) - - headers: dict, request headers - path_params: dict, parameters that appear in the request path - query: string, query part of the request URI - body: string, the body serialized as JSON - """ - query = self._build_query(query_params) - headers['accept'] = self.accept - headers['accept-encoding'] = 'gzip, deflate' - if 'user-agent' in headers: - headers['user-agent'] += ' ' - else: - headers['user-agent'] = '' - headers['user-agent'] += 'google-api-python-client/%s (gzip)' % __version__ - - if body_value is not None: - headers['content-type'] = self.content_type - body_value = self.serialize(body_value) - self._log_request(headers, path_params, query, body_value) - return (headers, path_params, query, body_value) - - def _build_query(self, params): - """Builds a query string. - - Args: - params: dict, the query parameters - - Returns: - The query parameters properly encoded into an HTTP URI query string. - """ - if self.alt_param is not None: - params.update({'alt': self.alt_param}) - astuples = [] - for key, value in params.iteritems(): - if type(value) == type([]): - for x in value: - x = x.encode('utf-8') - astuples.append((key, x)) - else: - if getattr(value, 'encode', False) and callable(value.encode): - value = value.encode('utf-8') - astuples.append((key, value)) - return '?' + urllib.urlencode(astuples) - - def _log_response(self, resp, content): - """Logs debugging information about the response if requested.""" - if dump_request_response: - logging.info('--response-start--') - for h, v in resp.iteritems(): - logging.info('%s: %s', h, v) - if content: - logging.info(content) - logging.info('--response-end--') - - def response(self, resp, content): - """Convert the response wire format into a Python object. - - Args: - resp: httplib2.Response, the HTTP response headers and status - content: string, the body of the HTTP response - - Returns: - The body de-serialized as a Python object. - - Raises: - googleapiclient.errors.HttpError if a non 2xx response is received. - """ - self._log_response(resp, content) - # Error handling is TBD, for example, do we retry - # for some operation/error combinations? - if resp.status < 300: - if resp.status == 204: - # A 204: No Content response should be treated differently - # to all the other success states - return self.no_content_response - return self.deserialize(content) - else: - logging.debug('Content from bad request was: %s' % content) - raise HttpError(resp, content) - - def serialize(self, body_value): - """Perform the actual Python object serialization. - - Args: - body_value: object, the request body as a Python object. - - Returns: - string, the body in serialized form. - """ - _abstract() - - def deserialize(self, content): - """Perform the actual deserialization from response string to Python - object. - - Args: - content: string, the body of the HTTP response - - Returns: - The body de-serialized as a Python object. - """ - _abstract() - - -class JsonModel(BaseModel): - """Model class for JSON. - - Serializes and de-serializes between JSON and the Python - object representation of HTTP request and response bodies. - """ - accept = 'application/json' - content_type = 'application/json' - alt_param = 'json' - - def __init__(self, data_wrapper=False): - """Construct a JsonModel. - - Args: - data_wrapper: boolean, wrap requests and responses in a data wrapper - """ - self._data_wrapper = data_wrapper - - def serialize(self, body_value): - if (isinstance(body_value, dict) and 'data' not in body_value and - self._data_wrapper): - body_value = {'data': body_value} - return json.dumps(body_value) - - def deserialize(self, content): - content = content.decode('utf-8') - body = json.loads(content) - if self._data_wrapper and isinstance(body, dict) and 'data' in body: - body = body['data'] - return body - - @property - def no_content_response(self): - return {} - - -class RawModel(JsonModel): - """Model class for requests that don't return JSON. - - Serializes and de-serializes between JSON and the Python - object representation of HTTP request, and returns the raw bytes - of the response body. - """ - accept = '*/*' - content_type = 'application/json' - alt_param = None - - def deserialize(self, content): - return content - - @property - def no_content_response(self): - return '' - - -class MediaModel(JsonModel): - """Model class for requests that return Media. - - Serializes and de-serializes between JSON and the Python - object representation of HTTP request, and returns the raw bytes - of the response body. - """ - accept = '*/*' - content_type = 'application/json' - alt_param = 'media' - - def deserialize(self, content): - return content - - @property - def no_content_response(self): - return '' - - -class ProtocolBufferModel(BaseModel): - """Model class for protocol buffers. - - Serializes and de-serializes the binary protocol buffer sent in the HTTP - request and response bodies. - """ - accept = 'application/x-protobuf' - content_type = 'application/x-protobuf' - alt_param = 'proto' - - def __init__(self, protocol_buffer): - """Constructs a ProtocolBufferModel. - - The serialzed protocol buffer returned in an HTTP response will be - de-serialized using the given protocol buffer class. - - Args: - protocol_buffer: The protocol buffer class used to de-serialize a - response from the API. - """ - self._protocol_buffer = protocol_buffer - - def serialize(self, body_value): - return body_value.SerializeToString() - - def deserialize(self, content): - return self._protocol_buffer.FromString(content) - - @property - def no_content_response(self): - return self._protocol_buffer() - - -def makepatch(original, modified): - """Create a patch object. - - Some methods support PATCH, an efficient way to send updates to a resource. - This method allows the easy construction of patch bodies by looking at the - differences between a resource before and after it was modified. - - Args: - original: object, the original deserialized resource - modified: object, the modified deserialized resource - Returns: - An object that contains only the changes from original to modified, in a - form suitable to pass to a PATCH method. - - Example usage: - item = service.activities().get(postid=postid, userid=userid).execute() - original = copy.deepcopy(item) - item['object']['content'] = 'This is updated.' - service.activities.patch(postid=postid, userid=userid, - body=makepatch(original, item)).execute() - """ - patch = {} - for key, original_value in original.iteritems(): - modified_value = modified.get(key, None) - if modified_value is None: - # Use None to signal that the element is deleted - patch[key] = None - elif original_value != modified_value: - if type(original_value) == type({}): - # Recursively descend objects - patch[key] = makepatch(original_value, modified_value) - else: - # In the case of simple types or arrays we just replace - patch[key] = modified_value - else: - # Don't add anything to patch if there's no change - pass - for key in modified: - if key not in original: - patch[key] = modified[key] - - return patch diff --git a/third_party/google_api_python_client/googleapiclient/sample_tools.py b/third_party/google_api_python_client/googleapiclient/sample_tools.py deleted file mode 100644 index cbd6d6f736..0000000000 --- a/third_party/google_api_python_client/googleapiclient/sample_tools.py +++ /dev/null @@ -1,102 +0,0 @@ -# Copyright 2014 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. - -"""Utilities for making samples. - -Consolidates a lot of code commonly repeated in sample applications. -""" - -__author__ = 'jcgregorio@google.com (Joe Gregorio)' -__all__ = ['init'] - - -import argparse -import httplib2 -import os - -from googleapiclient import discovery -from ...oauth2client import client -from ...oauth2client import file -from ...oauth2client import tools - - -def init(argv, name, version, doc, filename, scope=None, parents=[], discovery_filename=None): - """A common initialization routine for samples. - - Many of the sample applications do the same initialization, which has now - been consolidated into this function. This function uses common idioms found - in almost all the samples, i.e. for an API with name 'apiname', the - credentials are stored in a file named apiname.dat, and the - client_secrets.json file is stored in the same directory as the application - main file. - - Args: - argv: list of string, the command-line parameters of the application. - name: string, name of the API. - version: string, version of the API. - doc: string, description of the application. Usually set to __doc__. - file: string, filename of the application. Usually set to __file__. - parents: list of argparse.ArgumentParser, additional command-line flags. - scope: string, The OAuth scope used. - discovery_filename: string, name of local discovery file (JSON). Use when discovery doc not available via URL. - - Returns: - A tuple of (service, flags), where service is the service object and flags - is the parsed command-line flags. - """ - if scope is None: - scope = 'https://www.googleapis.com/auth/' + name - - # Parser command-line arguments. - parent_parsers = [tools.argparser] - parent_parsers.extend(parents) - parser = argparse.ArgumentParser( - description=doc, - formatter_class=argparse.RawDescriptionHelpFormatter, - parents=parent_parsers) - flags = parser.parse_args(argv[1:]) - - # 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 = os.path.join(os.path.dirname(filename), - 'client_secrets.json') - - # Set up a Flow object to be used if we need to authenticate. - flow = client.flow_from_clientsecrets(client_secrets, - scope=scope, - message=tools.message_if_missing(client_secrets)) - - # Prepare credentials, and authorize HTTP object with them. - # If the credentials don't exist or are invalid run through the native client - # flow. The Storage object will ensure that if successful the good - # credentials will get written back to a file. - storage = file.Storage(name + '.dat') - credentials = storage.get() - if credentials is None or credentials.invalid: - credentials = tools.run_flow(flow, storage, flags) - http = credentials.authorize(http = httplib2.Http()) - - if discovery_filename is None: - # Construct a service object via the discovery service. - service = discovery.build(name, version, http=http) - else: - # Construct a service object using a local discovery document file. - with open(discovery_filename) as discovery_file: - service = discovery.build_from_document( - discovery_file.read(), - base='https://www.googleapis.com/', - http=http) - return (service, flags) diff --git a/third_party/google_api_python_client/googleapiclient/schema.py b/third_party/google_api_python_client/googleapiclient/schema.py deleted file mode 100644 index af413177f0..0000000000 --- a/third_party/google_api_python_client/googleapiclient/schema.py +++ /dev/null @@ -1,311 +0,0 @@ -# Copyright 2014 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. - -"""Schema processing for discovery based APIs - -Schemas holds an APIs discovery schemas. It can return those schema as -deserialized JSON objects, or pretty print them as prototype objects that -conform to the schema. - -For example, given the schema: - - schema = \"\"\"{ - "Foo": { - "type": "object", - "properties": { - "etag": { - "type": "string", - "description": "ETag of the collection." - }, - "kind": { - "type": "string", - "description": "Type of the collection ('calendar#acl').", - "default": "calendar#acl" - }, - "nextPageToken": { - "type": "string", - "description": "Token used to access the next - page of this result. Omitted if no further results are available." - } - } - } - }\"\"\" - - s = Schemas(schema) - print s.prettyPrintByName('Foo') - - Produces the following output: - - { - "nextPageToken": "A String", # Token used to access the - # next page of this result. Omitted if no further results are available. - "kind": "A String", # Type of the collection ('calendar#acl'). - "etag": "A String", # ETag of the collection. - }, - -The constructor takes a discovery document in which to look up named schema. -""" - -# TODO(jcgregorio) support format, enum, minimum, maximum - -__author__ = 'jcgregorio@google.com (Joe Gregorio)' - -import copy - -from oauth2client import util - - -class Schemas(object): - """Schemas for an API.""" - - def __init__(self, discovery): - """Constructor. - - Args: - discovery: object, Deserialized discovery document from which we pull - out the named schema. - """ - self.schemas = discovery.get('schemas', {}) - - # Cache of pretty printed schemas. - self.pretty = {} - - @util.positional(2) - def _prettyPrintByName(self, name, seen=None, dent=0): - """Get pretty printed object prototype from the schema name. - - Args: - name: string, Name of schema in the discovery document. - seen: list of string, Names of schema already seen. Used to handle - recursive definitions. - - Returns: - string, A string that contains a prototype object with - comments that conforms to the given schema. - """ - if seen is None: - seen = [] - - if name in seen: - # Do not fall into an infinite loop over recursive definitions. - return '# Object with schema name: %s' % name - seen.append(name) - - if name not in self.pretty: - self.pretty[name] = _SchemaToStruct(self.schemas[name], - seen, dent=dent).to_str(self._prettyPrintByName) - - seen.pop() - - return self.pretty[name] - - def prettyPrintByName(self, name): - """Get pretty printed object prototype from the schema name. - - Args: - name: string, Name of schema in the discovery document. - - Returns: - string, A string that contains a prototype object with - comments that conforms to the given schema. - """ - # Return with trailing comma and newline removed. - return self._prettyPrintByName(name, seen=[], dent=1)[:-2] - - @util.positional(2) - def _prettyPrintSchema(self, schema, seen=None, dent=0): - """Get pretty printed object prototype of schema. - - Args: - schema: object, Parsed JSON schema. - seen: list of string, Names of schema already seen. Used to handle - recursive definitions. - - Returns: - string, A string that contains a prototype object with - comments that conforms to the given schema. - """ - if seen is None: - seen = [] - - return _SchemaToStruct(schema, seen, dent=dent).to_str(self._prettyPrintByName) - - def prettyPrintSchema(self, schema): - """Get pretty printed object prototype of schema. - - Args: - schema: object, Parsed JSON schema. - - Returns: - string, A string that contains a prototype object with - comments that conforms to the given schema. - """ - # Return with trailing comma and newline removed. - return self._prettyPrintSchema(schema, dent=1)[:-2] - - def get(self, name): - """Get deserialized JSON schema from the schema name. - - Args: - name: string, Schema name. - """ - return self.schemas[name] - - -class _SchemaToStruct(object): - """Convert schema to a prototype object.""" - - @util.positional(3) - def __init__(self, schema, seen, dent=0): - """Constructor. - - Args: - schema: object, Parsed JSON schema. - seen: list, List of names of schema already seen while parsing. Used to - handle recursive definitions. - dent: int, Initial indentation depth. - """ - # The result of this parsing kept as list of strings. - self.value = [] - - # The final value of the parsing. - self.string = None - - # The parsed JSON schema. - self.schema = schema - - # Indentation level. - self.dent = dent - - # Method that when called returns a prototype object for the schema with - # the given name. - self.from_cache = None - - # List of names of schema already seen while parsing. - self.seen = seen - - def emit(self, text): - """Add text as a line to the output. - - Args: - text: string, Text to output. - """ - self.value.extend([" " * self.dent, text, '\n']) - - def emitBegin(self, text): - """Add text to the output, but with no line terminator. - - Args: - text: string, Text to output. - """ - self.value.extend([" " * self.dent, text]) - - def emitEnd(self, text, comment): - """Add text and comment to the output with line terminator. - - Args: - text: string, Text to output. - comment: string, Python comment. - """ - if comment: - divider = '\n' + ' ' * (self.dent + 2) + '# ' - lines = comment.splitlines() - lines = [x.rstrip() for x in lines] - comment = divider.join(lines) - self.value.extend([text, ' # ', comment, '\n']) - else: - self.value.extend([text, '\n']) - - def indent(self): - """Increase indentation level.""" - self.dent += 1 - - def undent(self): - """Decrease indentation level.""" - self.dent -= 1 - - def _to_str_impl(self, schema): - """Prototype object based on the schema, in Python code with comments. - - Args: - schema: object, Parsed JSON schema file. - - Returns: - Prototype object based on the schema, in Python code with comments. - """ - stype = schema.get('type') - if stype == 'object': - self.emitEnd('{', schema.get('description', '')) - self.indent() - if 'properties' in schema: - for pname, pschema in schema.get('properties', {}).iteritems(): - self.emitBegin('"%s": ' % pname) - self._to_str_impl(pschema) - elif 'additionalProperties' in schema: - self.emitBegin('"a_key": ') - self._to_str_impl(schema['additionalProperties']) - self.undent() - self.emit('},') - elif '$ref' in schema: - schemaName = schema['$ref'] - description = schema.get('description', '') - s = self.from_cache(schemaName, seen=self.seen) - parts = s.splitlines() - self.emitEnd(parts[0], description) - for line in parts[1:]: - self.emit(line.rstrip()) - elif stype == 'boolean': - value = schema.get('default', 'True or False') - self.emitEnd('%s,' % str(value), schema.get('description', '')) - elif stype == 'string': - value = schema.get('default', 'A String') - self.emitEnd('"%s",' % str(value), schema.get('description', '')) - elif stype == 'integer': - value = schema.get('default', '42') - self.emitEnd('%s,' % str(value), schema.get('description', '')) - elif stype == 'number': - value = schema.get('default', '3.14') - self.emitEnd('%s,' % str(value), schema.get('description', '')) - elif stype == 'null': - self.emitEnd('None,', schema.get('description', '')) - elif stype == 'any': - self.emitEnd('"",', schema.get('description', '')) - elif stype == 'array': - self.emitEnd('[', schema.get('description')) - self.indent() - self.emitBegin('') - self._to_str_impl(schema['items']) - self.undent() - self.emit('],') - else: - self.emit('Unknown type! %s' % stype) - self.emitEnd('', '') - - self.string = ''.join(self.value) - return self.string - - def to_str(self, from_cache): - """Prototype object based on the schema, in Python code with comments. - - Args: - from_cache: callable(name, seen), Callable that retrieves an object - prototype for a schema with the given name. Seen is a list of schema - names already seen as we recursively descend the schema definition. - - Returns: - Prototype object based on the schema, in Python code with comments. - The lines of the code will all be properly indented. - """ - self.from_cache = from_cache - return self._to_str_impl(self.schema) diff --git a/third_party/google_api_python_client/samples-index.py b/third_party/google_api_python_client/samples-index.py deleted file mode 100644 index 712f552c91..0000000000 --- a/third_party/google_api_python_client/samples-index.py +++ /dev/null @@ -1,246 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- -# -# Copyright 2014 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. - -"""Build wiki page with a list of all samples. - -The information for the wiki page is built from data found in all the README -files in the samples. The format of the README file is: - - - Description is everything up to the first blank line. - - api: plus (Used to look up the long name in discovery). - keywords: appengine (such as appengine, oauth2, cmdline) - - The rest of the file is ignored when it comes to building the index. -""" - -import httplib2 -import itertools -import json -import os -import re - -BASE_HG_URI = ('http://code.google.com/p/google-api-python-client/source/' - 'browse/#hg') - -http = httplib2.Http('.cache') -r, c = http.request('https://www.googleapis.com/discovery/v1/apis') -if r.status != 200: - raise ValueError('Received non-200 response when retrieving Discovery.') - -# Dictionary mapping api names to their discovery description. -DIRECTORY = {} -for item in json.loads(c)['items']: - if item['preferred']: - DIRECTORY[item['name']] = item - -# A list of valid keywords. Should not be taken as complete, add to -# this list as needed. -KEYWORDS = { - 'appengine': 'Google App Engine', - 'oauth2': 'OAuth 2.0', - 'cmdline': 'Command-line', - 'django': 'Django', - 'threading': 'Threading', - 'pagination': 'Pagination', - 'media': 'Media Upload and Download' - } - - -def get_lines(name, lines): - """Return lines that begin with name. - - Lines are expected to look like: - - name: space separated values - - Args: - name: string, parameter name. - lines: iterable of string, lines in the file. - - Returns: - List of values in the lines that match. - """ - retval = [] - matches = itertools.ifilter(lambda x: x.startswith(name + ':'), lines) - for line in matches: - retval.extend(line[len(name)+1:].split()) - return retval - - -def wiki_escape(s): - """Detect WikiSyntax (i.e. InterCaps, a.k.a. CamelCase) and escape it.""" - ret = [] - for word in s.split(): - if re.match(r'[A-Z]+[a-z]+[A-Z]', word): - word = '!%s' % word - ret.append(word) - return ' '.join(ret) - - -def context_from_sample(api, keywords, dirname, desc, uri): - """Return info for expanding a sample into a template. - - Args: - api: string, name of api. - keywords: list of string, list of keywords for the given api. - dirname: string, directory name of the sample. - desc: string, long description of the sample. - uri: string, uri of the sample code if provided in the README. - - Returns: - A dictionary of values useful for template expansion. - """ - if uri is None: - uri = BASE_HG_URI + dirname.replace('/', '%2F') - else: - uri = ''.join(uri) - if api is None: - return None - else: - entry = DIRECTORY[api] - context = { - 'api': api, - 'version': entry['version'], - 'api_name': wiki_escape(entry.get('title', entry.get('description'))), - 'api_desc': wiki_escape(entry['description']), - 'api_icon': entry['icons']['x32'], - 'keywords': keywords, - 'dir': dirname, - 'uri': uri, - 'desc': wiki_escape(desc), - } - return context - - -def keyword_context_from_sample(keywords, dirname, desc, uri): - """Return info for expanding a sample into a template. - - Sample may not be about a specific api. - - Args: - keywords: list of string, list of keywords for the given api. - dirname: string, directory name of the sample. - desc: string, long description of the sample. - uri: string, uri of the sample code if provided in the README. - - Returns: - A dictionary of values useful for template expansion. - """ - if uri is None: - uri = BASE_HG_URI + dirname.replace('/', '%2F') - else: - uri = ''.join(uri) - context = { - 'keywords': keywords, - 'dir': dirname, - 'uri': uri, - 'desc': wiki_escape(desc), - } - return context - - -def scan_readme_files(dirname): - """Scans all subdirs of dirname for README files. - - Args: - dirname: string, name of directory to walk. - - Returns: - (samples, keyword_set): list of information about all samples, the union - of all keywords found. - """ - samples = [] - keyword_set = set() - - for root, dirs, files in os.walk(dirname): - if 'README' in files: - filename = os.path.join(root, 'README') - with open(filename, 'r') as f: - content = f.read() - lines = content.splitlines() - desc = ' '.join(itertools.takewhile(lambda x: x, lines)) - api = get_lines('api', lines) - keywords = get_lines('keywords', lines) - uri = get_lines('uri', lines) - if not uri: - uri = None - - for k in keywords: - if k not in KEYWORDS: - raise ValueError( - '%s is not a valid keyword in file %s' % (k, filename)) - keyword_set.update(keywords) - if not api: - api = [None] - samples.append((api[0], keywords, root[1:], desc, uri)) - - samples.sort() - - return samples, keyword_set - - -def main(): - # Get all the information we need out of the README files in the samples. - samples, keyword_set = scan_readme_files('./samples') - - # Now build a wiki page with all that information. Accumulate all the - # information as string to be concatenated when were done. - page = ['\n= Samples By API =\n'] - - # All the samples, grouped by API. - current_api = None - for api, keywords, dirname, desc, uri in samples: - context = context_from_sample(api, keywords, dirname, desc, uri) - if context is None: - continue - if current_api != api: - page.append(""" -=== %(api_icon)s %(api_name)s === - -%(api_desc)s - -Documentation for the %(api_name)s in [https://google-api-client-libraries.appspot.com/documentation/%(api)s/%(version)s/python/latest/ PyDoc] - -""" % context) - current_api = api - - page.append('|| [%(uri)s %(dir)s] || %(desc)s ||\n' % context) - - # Now group the samples by keywords. - for keyword, keyword_name in KEYWORDS.iteritems(): - if keyword not in keyword_set: - continue - page.append('\n= %s Samples =\n\n' % keyword_name) - page.append('\n') - for _, keywords, dirname, desc, uri in samples: - context = keyword_context_from_sample(keywords, dirname, desc, uri) - if keyword not in keywords: - continue - page.append(""" - - - -""" % context) - page.append('
[%(uri)s %(dir)s] %(desc)s
\n') - - print ''.join(page) - - -if __name__ == '__main__': - main() diff --git a/third_party/google_api_python_client/setup.py b/third_party/google_api_python_client/setup.py deleted file mode 100644 index 40dbc0f150..0000000000 --- a/third_party/google_api_python_client/setup.py +++ /dev/null @@ -1,94 +0,0 @@ -# Copyright 2014 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. - -"""Setup script for Google API Python client. - -Also installs included versions of third party libraries, if those libraries -are not already installed. -""" -from __future__ import print_function - -import sys - -if sys.version_info < (2, 6): - print('google-api-python-client requires python version >= 2.6.', - file=sys.stderr) - sys.exit(1) - -from setuptools import setup -import pkg_resources - -def _DetectBadness(): - import os - if 'SKIP_GOOGLEAPICLIENT_COMPAT_CHECK' in os.environ: - return - o2c_pkg = None - try: - o2c_pkg = pkg_resources.get_distribution('oauth2client') - except pkg_resources.DistributionNotFound: - pass - oauth2client = None - try: - import oauth2client - except ImportError: - pass - if o2c_pkg is None and oauth2client is not None: - raise RuntimeError( - 'Previous version of google-api-python-client detected; due to a ' - 'packaging issue, we cannot perform an in-place upgrade. Please remove ' - 'the old version and re-install this package.' - ) - -_DetectBadness() - -packages = [ - 'apiclient', - 'googleapiclient', -] - -install_requires = [ - 'httplib2>=0.8', - 'oauth2client>=1.3', - 'uritemplate>=0.6', -] - -if sys.version_info < (2, 7): - install_requires.append('argparse') - -long_desc = """The Google API Client for Python is a client library for -accessing the Plus, Moderator, and many other Google APIs.""" - -import googleapiclient -version = googleapiclient.__version__ - -setup( - name="google-api-python-client", - version=version, - description="Google API Client Library for Python", - long_description=long_desc, - author="Google Inc.", - url="http://github.com/google/google-api-python-client/", - install_requires=install_requires, - packages=packages, - package_data={}, - license="Apache 2.0", - keywords="google api client", - classifiers=[ - 'Development Status :: 5 - Production/Stable', - 'Intended Audience :: Developers', - 'License :: OSI Approved :: Apache Software License', - 'Operating System :: POSIX', - 'Topic :: Internet :: WWW/HTTP', - ], -) diff --git a/third_party/google_api_python_client/sitecustomize.py b/third_party/google_api_python_client/sitecustomize.py deleted file mode 100644 index ef0f06376a..0000000000 --- a/third_party/google_api_python_client/sitecustomize.py +++ /dev/null @@ -1,12 +0,0 @@ -# Set up the system so that this development -# version of google-api-python-client is run, even if -# an older version is installed on the system. -# -# To make this totally automatic add the following to -# your ~/.bash_profile: -# -# export PYTHONPATH=/path/to/where/you/checked/out/googleapiclient -import sys -import os - -sys.path.insert(0, os.path.dirname(__file__)) diff --git a/third_party/google_api_python_client/static/Credentials.png b/third_party/google_api_python_client/static/Credentials.png deleted file mode 100644 index a5be2c533f..0000000000 Binary files a/third_party/google_api_python_client/static/Credentials.png and /dev/null differ diff --git a/third_party/google_api_python_client/tox.ini b/third_party/google_api_python_client/tox.ini deleted file mode 100644 index 5a5dfbcbd6..0000000000 --- a/third_party/google_api_python_client/tox.ini +++ /dev/null @@ -1,18 +0,0 @@ -[tox] -envlist = py26, py27 - -[testenv] -deps = keyring - mox - pyopenssl - pycrypto==2.6 - django - webtest - nose -setenv = PYTHONPATH=../google_appengine - -[testenv:py26] -commands = nosetests --ignore-files=test_oauth2client_appengine\.py - -[testenv:py27] -commands = nosetests diff --git a/third_party/uritemplate/.gitignore b/third_party/uritemplate/.gitignore deleted file mode 100644 index 5b1b935455..0000000000 --- a/third_party/uritemplate/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -*.pyc -build -dist -MANIFEST - diff --git a/third_party/uritemplate/.gitmodules b/third_party/uritemplate/.gitmodules deleted file mode 100644 index 3258e9499b..0000000000 --- a/third_party/uritemplate/.gitmodules +++ /dev/null @@ -1,3 +0,0 @@ -[submodule "test/cases"] - path = test/cases - url = git://github.com/uri-templates/uritemplate-test.git diff --git a/third_party/uritemplate/.travis.yml b/third_party/uritemplate/.travis.yml deleted file mode 100644 index 520ac1ba28..0000000000 --- a/third_party/uritemplate/.travis.yml +++ /dev/null @@ -1,11 +0,0 @@ -language: python -python: - - "2.5" - - "2.6" - - "2.7" - - "3.3" - - "pypy" -# dependencies -install: pip install simplejson --use-mirrors -# command to run tests -script: "cd test; make" diff --git a/third_party/uritemplate/MAINTAINERS.rst b/third_party/uritemplate/MAINTAINERS.rst deleted file mode 100644 index 4918f841c8..0000000000 --- a/third_party/uritemplate/MAINTAINERS.rst +++ /dev/null @@ -1,14 +0,0 @@ -Instructions for Maintainers -============================ - -Release -------- - -To release a build: - -1. Push all changes, verify CI passes (see link in README.rst). -2. Increment __version__ in __init__.py -3. ``git tag -a uri-template-py-[version]`` -4. ``git push --tags origin master`` -5. ``python setup.py sdist upload`` -6. Brew coffee or tea. diff --git a/third_party/uritemplate/MANIFEST.in b/third_party/uritemplate/MANIFEST.in deleted file mode 100644 index 9561fb1061..0000000000 --- a/third_party/uritemplate/MANIFEST.in +++ /dev/null @@ -1 +0,0 @@ -include README.rst diff --git a/third_party/uritemplate/README.chromium b/third_party/uritemplate/README.chromium deleted file mode 100644 index 0057f9a4e4..0000000000 --- a/third_party/uritemplate/README.chromium +++ /dev/null @@ -1,6 +0,0 @@ -URL: https://github.com/uri-templates/uritemplate-py/ -Version: 0.6 -Revision: 1e780a49412cdbb273e9421974cb91845c124f3f -License: Apache License, Version 2.0 (the "License") - -No local changes diff --git a/third_party/uritemplate/README.rst b/third_party/uritemplate/README.rst deleted file mode 100644 index 80129cbccc..0000000000 --- a/third_party/uritemplate/README.rst +++ /dev/null @@ -1,71 +0,0 @@ -uritemplate -=========== - -.. image:: https://secure.travis-ci.org/uri-templates/uritemplate-py.png?branch=master - :alt: build status - :target: http://travis-ci.org/uri-templates/uritemplate-py - -This is a Python implementation of `RFC6570`_, URI Template, and can -expand templates up to and including Level 4 in that specification. - -It exposes a method, *expand*. For example: - -.. code-block:: python - - >>> from uritemplate import expand - >>> expand("http://www.{domain}/", {"domain": "foo.com"}) - 'http://www.foo.com/' - -It also exposes a method *variables* that returns all variables used in a -uritemplate. For example: - -.. code-block:: python - - >>> from uritemplate import variables - >>> variables('http:www{.domain*}{/top,next}{?q:20}') - >>> set(['domain', 'next', 'q', 'top']) - -This function can be useful to determine what keywords are available to be -expanded. - -.. _RFC6570: http://tools.ietf.org/html/rfc6570 - - -Requirements ------------- - -uritemplate works with Python 2.5+. - -.. note:: You need to install `simplejson`_ module for Python 2.5. - -.. _simplejson: https://pypi.python.org/pypi/simplejson/ - - -Install -------- - -The easiest way to install uritemplate is with pip:: - - $ pip install uritemplate - -See its `Python Package Index entry`_ for more. - -.. _Python Package Index entry: http://pypi.python.org/pypi/uritemplate - - -License -======= - -Copyright 2011-2013 Joe Gregorio - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - -http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. diff --git a/third_party/uritemplate/__init__.py b/third_party/uritemplate/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/third_party/uritemplate/setup.py b/third_party/uritemplate/setup.py deleted file mode 100755 index 9b71aae19e..0000000000 --- a/third_party/uritemplate/setup.py +++ /dev/null @@ -1,37 +0,0 @@ -#!/usr/bin/env python - -from distutils.core import setup -import uritemplate - -base_url = "http://github.com/uri-templates/uritemplate-py/" - -setup( - name = 'uritemplate', - version = uritemplate.__version__, - description = 'URI Templates', - author = 'Joe Gregorio', - author_email = 'joe@bitworking.org', - url = base_url, - download_url = \ - '%starball/uritemplate-py-%s' % (base_url, uritemplate.__version__), - packages = ['uritemplate'], - provides = ['uritemplate'], - long_description=open("README.rst").read(), - install_requires = ['simplejson >= 2.5.0'], - classifiers = [ - 'Development Status :: 4 - Beta', - 'Intended Audience :: Developers', - 'License :: OSI Approved :: Apache Software License', - 'Programming Language :: Python', - 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 2.5', - 'Programming Language :: Python :: 2.6', - 'Programming Language :: Python :: 2.7', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.3', - 'Operating System :: POSIX', - 'Topic :: Internet :: WWW/HTTP', - 'Topic :: Software Development :: Libraries :: Python Modules', - ] -) - diff --git a/third_party/uritemplate/uritemplate/__init__.py b/third_party/uritemplate/uritemplate/__init__.py deleted file mode 100755 index 712405d42d..0000000000 --- a/third_party/uritemplate/uritemplate/__init__.py +++ /dev/null @@ -1,265 +0,0 @@ -#!/usr/bin/env python - -""" -URI Template (RFC6570) Processor -""" - -__copyright__ = """\ -Copyright 2011-2013 Joe Gregorio - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT 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 re -try: - from urllib.parse import quote -except ImportError: - from urllib import quote - - - -__version__ = "0.6" - -RESERVED = ":/?#[]@!$&'()*+,;=" -OPERATOR = "+#./;?&|!@" -MODIFIER = ":^" -TEMPLATE = re.compile("{([^\}]+)}") - - -def variables(template): - '''Returns the set of keywords in a uri template''' - vars = set() - for varlist in TEMPLATE.findall(template): - if varlist[0] in OPERATOR: - varlist = varlist[1:] - varspecs = varlist.split(',') - for var in varspecs: - # handle prefix values - var = var.split(':')[0] - # handle composite values - if var.endswith('*'): - var = var[:-1] - vars.add(var) - return vars - - -def _quote(value, safe, prefix=None): - if prefix is not None: - return quote(str(value)[:prefix], safe) - return quote(str(value), safe) - - -def _tostring(varname, value, explode, prefix, operator, safe=""): - if isinstance(value, list): - return ",".join([_quote(x, safe) for x in value]) - if isinstance(value, dict): - keys = sorted(value.keys()) - if explode: - return ",".join([_quote(key, safe) + "=" + \ - _quote(value[key], safe) for key in keys]) - else: - return ",".join([_quote(key, safe) + "," + \ - _quote(value[key], safe) for key in keys]) - elif value is None: - return - else: - return _quote(value, safe, prefix) - - -def _tostring_path(varname, value, explode, prefix, operator, safe=""): - joiner = operator - if isinstance(value, list): - if explode: - out = [_quote(x, safe) for x in value if value is not None] - else: - joiner = "," - out = [_quote(x, safe) for x in value if value is not None] - if out: - return joiner.join(out) - else: - return - elif isinstance(value, dict): - keys = sorted(value.keys()) - if explode: - out = [_quote(key, safe) + "=" + \ - _quote(value[key], safe) for key in keys \ - if value[key] is not None] - else: - joiner = "," - out = [_quote(key, safe) + "," + \ - _quote(value[key], safe) \ - for key in keys if value[key] is not None] - if out: - return joiner.join(out) - else: - return - elif value is None: - return - else: - return _quote(value, safe, prefix) - - -def _tostring_semi(varname, value, explode, prefix, operator, safe=""): - joiner = operator - if operator == "?": - joiner = "&" - if isinstance(value, list): - if explode: - out = [varname + "=" + _quote(x, safe) \ - for x in value if x is not None] - if out: - return joiner.join(out) - else: - return - else: - return varname + "=" + ",".join([_quote(x, safe) \ - for x in value]) - elif isinstance(value, dict): - keys = sorted(value.keys()) - if explode: - return joiner.join([_quote(key, safe) + "=" + \ - _quote(value[key], safe) \ - for key in keys if key is not None]) - else: - return varname + "=" + ",".join([_quote(key, safe) + "," + \ - _quote(value[key], safe) for key in keys \ - if key is not None]) - else: - if value is None: - return - elif value: - return (varname + "=" + _quote(value, safe, prefix)) - else: - return varname - - -def _tostring_query(varname, value, explode, prefix, operator, safe=""): - joiner = operator - if operator in ["?", "&"]: - joiner = "&" - if isinstance(value, list): - if 0 == len(value): - return None - if explode: - return joiner.join([varname + "=" + _quote(x, safe) \ - for x in value]) - else: - return (varname + "=" + ",".join([_quote(x, safe) \ - for x in value])) - elif isinstance(value, dict): - if 0 == len(value): - return None - keys = sorted(value.keys()) - if explode: - return joiner.join([_quote(key, safe) + "=" + \ - _quote(value[key], safe) \ - for key in keys]) - else: - return varname + "=" + \ - ",".join([_quote(key, safe) + "," + \ - _quote(value[key], safe) for key in keys]) - else: - if value is None: - return - elif value: - return (varname + "=" + _quote(value, safe, prefix)) - else: - return (varname + "=") - - -TOSTRING = { - "" : _tostring, - "+": _tostring, - "#": _tostring, - ";": _tostring_semi, - "?": _tostring_query, - "&": _tostring_query, - "/": _tostring_path, - ".": _tostring_path, - } - - -def expand(template, variables): - """ - Expand template as a URI Template using variables. - """ - def _sub(match): - expression = match.group(1) - operator = "" - if expression[0] in OPERATOR: - operator = expression[0] - varlist = expression[1:] - else: - varlist = expression - - safe = "" - if operator in ["+", "#"]: - safe = RESERVED - varspecs = varlist.split(",") - varnames = [] - defaults = {} - for varspec in varspecs: - default = None - explode = False - prefix = None - if "=" in varspec: - varname, default = tuple(varspec.split("=", 1)) - else: - varname = varspec - if varname[-1] == "*": - explode = True - varname = varname[:-1] - elif ":" in varname: - try: - prefix = int(varname[varname.index(":")+1:]) - except ValueError: - raise ValueError("non-integer prefix '{0}'".format( - varname[varname.index(":")+1:])) - varname = varname[:varname.index(":")] - if default: - defaults[varname] = default - varnames.append((varname, explode, prefix)) - - retval = [] - joiner = operator - start = operator - if operator == "+": - start = "" - joiner = "," - if operator == "#": - joiner = "," - if operator == "?": - joiner = "&" - if operator == "&": - start = "&" - if operator == "": - joiner = "," - for varname, explode, prefix in varnames: - if varname in variables: - value = variables[varname] - if not value and value != "" and varname in defaults: - value = defaults[varname] - elif varname in defaults: - value = defaults[varname] - else: - continue - expanded = TOSTRING[operator]( - varname, value, explode, prefix, operator, safe=safe) - if expanded is not None: - retval.append(expanded) - if len(retval) > 0: - return start + joiner.join(retval) - else: - return "" - - return TEMPLATE.sub(_sub, template)