Remove apiclient package
BUG=
Review URL: https://codereview.chromium.org/1091163002
git-svn-id: svn://svn.chromium.org/chrome/trunk/tools/depot_tools@294877 0039d316-1c4b-4281-b951-d872f2087c98
diff --git a/third_party/google_api_python_client/.gitignore b/third_party/google_api_python_client/.gitignore
deleted file mode 100644
index ddb969d..0000000
--- 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 e69de29..0000000
--- a/third_party/google_api_python_client/.gitmodules
+++ /dev/null
diff --git a/third_party/google_api_python_client/.hgignore b/third_party/google_api_python_client/.hgignore
deleted file mode 100644
index dc9cf78..0000000
--- 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 af6d669..0000000
--- 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 2987b3b..0000000
--- 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 cc692b3..0000000
--- 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 6366e77..0000000
--- 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 eb7a00d..0000000
--- 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 c22221c..0000000
--- 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 e69de29..0000000
--- a/third_party/google_api_python_client/__init__.py
+++ /dev/null
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 5efb142..0000000
--- 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 5dcac90..0000000
--- 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 = """<style>
-
-body, h1, h2, h3, div, span, p, pre, a {
- margin: 0;
- padding: 0;
- border: 0;
- font-weight: inherit;
- font-style: inherit;
- font-size: 100%;
- font-family: inherit;
- vertical-align: baseline;
-}
-
-body {
- font-size: 13px;
- padding: 1em;
-}
-
-h1 {
- font-size: 26px;
- margin-bottom: 1em;
-}
-
-h2 {
- font-size: 24px;
- margin-bottom: 1em;
-}
-
-h3 {
- font-size: 20px;
- margin-bottom: 1em;
- margin-top: 1em;
-}
-
-pre, code {
- line-height: 1.5;
- font-family: Monaco, 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', 'Lucida Console', monospace;
-}
-
-pre {
- margin-top: 0.5em;
-}
-
-h1, h2, h3, p {
- font-family: Arial, sans serif;
-}
-
-h1, h2, h3 {
- border-bottom: solid #CCC 1px;
-}
-
-.toc_element {
- margin-top: 0.5em;
-}
-
-.firstline {
- margin-left: 2 em;
-}
-
-.method {
- margin-top: 1em;
- border: solid 1px #CCC;
- padding: 1em;
- background: #EEE;
-}
-
-.details {
- font-weight: bold;
- font-size: 14px;
-}
-
-</style>
-"""
-
-METHOD_TEMPLATE = """<div class="method">
- <code class="details" id="$name">$name($params)</code>
- <pre>$doc</pre>
-</div>
-"""
-
-COLLECTION_LINK = """<p class="toc_element">
- <code><a href="$href">$name()</a></code>
-</p>
-<p class="firstline">Returns the $name Resource.</p>
-"""
-
-METHOD_LINK = """<p class="toc_element">
- <code><a href="#$name">$name($params)</a></code></p>
-<p class="firstline">$firstline</p>"""
-
-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('<a href="%s.html">%s</a>' % (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 = [
- '<html><body>',
- css,
- '<h1>%s</h1>' % breadcrumbs(path[:-1], root_discovery),
- '<h2>Instance Methods</h2>'
- ]
-
- # 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('<h3>Method Details</h3>')
- for name in methods:
- dname = name.rsplit('_')[0]
- html.append(method(name, getattr(resource, name).__doc__))
-
- html.append('</body></html>')
-
- 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 8213622..0000000
--- 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 1e1a6cf..0000000
--- 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 68a3b89..0000000
--- 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 3ddac57..0000000
--- 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 a1999fd..0000000
--- 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 '<HttpError %s when requesting %s returned "%s">' % (
- self.resp.status, self.uri, self._get_reason().strip())
- else:
- return '<HttpError %s "%s">' % (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 '<BatchError %s "%s">' % (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 8638279..0000000
--- 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 8038af1..0000000
--- 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 0f0172c..0000000
--- 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 cbd6d6f..0000000
--- 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 <http://code.google.com/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 af41317..0000000
--- 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 712f552..0000000
--- 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 = ['<wiki:toc max_depth="3" />\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('<table border=1 cellspacing=0 cellpadding=8px>\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("""
-<tr>
- <td>[%(uri)s %(dir)s] </td>
- <td> %(desc)s </td>
-</tr>""" % context)
- page.append('</table>\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 40dbc0f..0000000
--- 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 ef0f063..0000000
--- 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 a5be2c5..0000000
--- a/third_party/google_api_python_client/static/Credentials.png
+++ /dev/null
Binary files 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 5a5dfbc..0000000
--- 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 5b1b935..0000000
--- 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 3258e94..0000000
--- 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 520ac1b..0000000
--- 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 4918f84..0000000
--- 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 9561fb1..0000000
--- 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 0057f9a..0000000
--- 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 80129cb..0000000
--- 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 e69de29..0000000
--- a/third_party/uritemplate/__init__.py
+++ /dev/null
diff --git a/third_party/uritemplate/setup.py b/third_party/uritemplate/setup.py
deleted file mode 100755
index 9b71aae..0000000
--- 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 712405d..0000000
--- 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)