diff options
author | arfy slowy <slowy.arfy@gmail.com> | 2021-07-15 00:16:31 +0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-07-14 17:16:31 +0000 |
commit | d35c912aa62956d68ed3ea01f135e50e2f811ec0 (patch) | |
tree | 0bd3c4483b06c1603451edf08149b492d84eb0a6 | |
parent | 5489f527d789f06cbf60ce858dd9a86948dff2b3 (diff) | |
download | google-api-python-client-d35c912aa62956d68ed3ea01f135e50e2f811ec0.tar.gz |
chore: code clean up (#1442)
Thank you for opening a Pull Request! Before submitting your PR, there are a few things you can do to make sure it goes smoothly:
- [x] Make sure to open an issue as a [bug/issue](https://github.com/googleapis/google-api-python-client/issues/new/choose) before writing your code! That way we can discuss the change, evaluate designs, and agree on the general idea
- [x] Ensure the tests and linter pass
- [x] Code coverage does not decrease (if any source code was changed)
- [x] Appropriate docs were updated (if necessary)
Fixes #1441 🦕
-rwxr-xr-x | describe.py | 152 | ||||
-rw-r--r-- | googleapiclient/http.py | 1103 | ||||
-rw-r--r-- | googleapiclient/schema.py | 131 | ||||
-rw-r--r-- | noxfile.py | 14 |
4 files changed, 719 insertions, 681 deletions
diff --git a/describe.py b/describe.py index b565872bd..49ae27451 100755 --- a/describe.py +++ b/describe.py @@ -24,7 +24,6 @@ from __future__ import print_function __author__ = "jcgregorio@google.com (Joe Gregorio)" -from collections import OrderedDict import argparse import collections import json @@ -36,14 +35,15 @@ import sys from googleapiclient.discovery import DISCOVERY_URI from googleapiclient.discovery import build from googleapiclient.discovery import build_from_document -from googleapiclient.discovery import UnknownApiNameOrVersion from googleapiclient.http import build_http -from googleapiclient.errors import HttpError import uritemplate DISCOVERY_DOC_DIR = ( - pathlib.Path(__file__).parent.resolve() / "googleapiclient" / "discovery_cache" / "documents" + pathlib.Path(__file__).parent.resolve() + / "googleapiclient" + / "discovery_cache" + / "documents" ) CSS = """<style> @@ -171,16 +171,16 @@ parser.add_argument( 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. + 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 '_'. - """ + Args: + version: string, The version string. + Returns: + The string with '.' replaced with '_'. + """ return version.replace(".", "_") @@ -188,14 +188,14 @@ def safe_version(version): def unsafe_version(version): """Undoes what safe_version() does. - See safe_version() for the details. + See safe_version() for the details. - Args: - version: string, The safe version string. - Returns: - The string with '_' replaced with '.'. - """ + Args: + version: string, The safe version string. + Returns: + The string with '_' replaced with '.'. + """ return version.replace("_", ".") @@ -203,12 +203,12 @@ def unsafe_version(version): def method_params(doc): """Document the parameters of a method. - Args: - doc: string, The method's docstring. + Args: + doc: string, The method's docstring. - Returns: - The method signature as a string. - """ + Returns: + The method signature as a string. + """ doclines = doc.splitlines() if "Args:" in doclines: begin = doclines.index("Args:") @@ -253,10 +253,10 @@ def method_params(doc): def method(name, doc): """Documents an individual method. - Args: - name: string, Name of the method. - doc: string, The methods docstring. - """ + Args: + name: string, Name of the method. + doc: string, The methods docstring. + """ import html params = method_params(doc) @@ -269,13 +269,13 @@ def method(name, 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. + 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. - """ + Returns: + HTML with links to each of the parent resources of this resource. + """ parts = path.split(".") crumbs = [] @@ -299,14 +299,14 @@ def breadcrumbs(path, root_discovery): 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. - """ + 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] @@ -357,7 +357,9 @@ def document_collection(resource, path, root_discovery, discovery, css=CSS): return "\n".join(html) -def document_collection_recursive(resource, path, root_discovery, discovery, doc_destination_dir): +def document_collection_recursive( + resource, path, root_discovery, discovery, doc_destination_dir +): html = document_collection(resource, path, root_discovery, discovery) f = open(pathlib.Path(doc_destination_dir).joinpath(path + "html"), "w") @@ -379,7 +381,7 @@ def document_collection_recursive(resource, path, root_discovery, discovery, doc path + name + ".", root_discovery, discovery["resources"].get(dname, {}), - doc_destination_dir + doc_destination_dir, ) @@ -392,10 +394,11 @@ def document_api(name, version, uri, doc_destination_dir): uri (str): URI of the API's discovery document doc_destination_dir (str): relative path where the reference documentation should be saved. - """ + """ http = build_http() resp, content = http.request( - uri or uritemplate.expand( + uri + or uritemplate.expand( FLAGS.discovery_uri_template, {"api": name, "apiVersion": version} ) ) @@ -413,11 +416,11 @@ def document_api(name, version, uri, doc_destination_dir): with open(discovery_file_path, "r+") as f: try: json_data = json.load(f) - revision = json_data['revision'] + revision = json_data["revision"] except json.JSONDecodeError: revision = None - if revision is None or discovery['revision'] >= revision: + if revision is None or discovery["revision"] >= revision: # Reset position to the beginning f.seek(0) # Write the changes to disk @@ -426,25 +429,35 @@ def document_api(name, version, uri, doc_destination_dir): f.truncate() elif resp.status == 404: - print("Warning: {} {} not found. HTTP Code: {}".format(name, version, resp.status)) + print( + "Warning: {} {} not found. HTTP Code: {}".format(name, version, resp.status) + ) return else: - print("Warning: {} {} could not be built. HTTP Code: {}".format(name, version, resp.status)) + print( + "Warning: {} {} could not be built. HTTP Code: {}".format( + name, version, resp.status + ) + ) return document_collection_recursive( - service, "{}_{}.".format(name, safe_version(version)), discovery, discovery, doc_destination_dir + service, + "{}_{}.".format(name, safe_version(version)), + discovery, + discovery, + doc_destination_dir, ) def document_api_from_discovery_document(discovery_url, doc_destination_dir): """Document the given API. - Args: - discovery_url (str): URI of discovery document. - doc_destination_dir (str): relative path where the reference - documentation should be saved. - """ + Args: + discovery_url (str): URI of discovery document. + doc_destination_dir (str): relative path where the reference + documentation should be saved. + """ http = build_http() response, content = http.request(discovery_url) discovery = json.loads(content) @@ -455,11 +468,16 @@ def document_api_from_discovery_document(discovery_url, doc_destination_dir): version = safe_version(discovery["version"]) document_collection_recursive( - service, "{}_{}.".format(name, version), discovery, discovery, doc_destination_dir + service, + "{}_{}.".format(name, version), + discovery, + discovery, + doc_destination_dir, ) + def generate_all_api_documents(directory_uri=DIRECTORY_URI, doc_destination_dir=BASE): - """ Retrieve discovery artifacts and fetch reference documentations + """Retrieve discovery artifacts and fetch reference documentations for all apis listed in the public discovery directory. args: directory_uri (str): uri of the public discovery directory. @@ -472,13 +490,18 @@ def generate_all_api_documents(directory_uri=DIRECTORY_URI, doc_destination_dir= if resp.status == 200: directory = json.loads(content)["items"] for api in directory: - document_api(api["name"], api["version"], api["discoveryRestUrl"], doc_destination_dir) + document_api( + api["name"], + api["version"], + api["discoveryRestUrl"], + doc_destination_dir, + ) api_directory[api["name"]].append(api["version"]) # sort by api name and version number for api in api_directory: api_directory[api] = sorted(api_directory[api]) - api_directory = OrderedDict( + api_directory = collections.OrderedDict( sorted(api_directory.items(), key=lambda x: x[0]) ) @@ -499,9 +522,14 @@ def generate_all_api_documents(directory_uri=DIRECTORY_URI, doc_destination_dir= else: sys.exit("Failed to load the discovery document.") + if __name__ == "__main__": FLAGS = parser.parse_args(sys.argv[1:]) if FLAGS.discovery_uri: - document_api_from_discovery_document(discovery_url=FLAGS.discovery_uri, doc_destination_dir=FLAGS.dest) + document_api_from_discovery_document( + discovery_url=FLAGS.discovery_uri, doc_destination_dir=FLAGS.dest + ) else: - generate_all_api_documents(directory_uri=FLAGS.directory_uri, doc_destination_dir=FLAGS.dest) + generate_all_api_documents( + directory_uri=FLAGS.directory_uri, doc_destination_dir=FLAGS.dest + ) diff --git a/googleapiclient/http.py b/googleapiclient/http.py index 5d4227fb8..0dd9c328d 100644 --- a/googleapiclient/http.py +++ b/googleapiclient/http.py @@ -20,8 +20,6 @@ actual HTTP request. """ from __future__ import absolute_import import six -from six.moves import http_client -from six.moves import range __author__ = "jcgregorio@google.com (Joe Gregorio)" @@ -87,13 +85,13 @@ if six.PY2: def _should_retry_response(resp_status, content): """Determines whether a response should be retried. - Args: - resp_status: The response status received. - content: The response content body. + Args: + resp_status: The response status received. + content: The response content body. - Returns: - True if the response should be retried, otherwise False. - """ + Returns: + True if the response should be retried, otherwise False. + """ reason = None # Retry on 5xx errors. @@ -122,7 +120,14 @@ def _should_retry_response(resp_status, content): # first. # See Issue #1243 # https://github.com/googleapis/google-api-python-client/issues/1243 - error_detail_keyword = next((kw for kw in ["errors", "status", "message"] if kw in data["error"]), "") + error_detail_keyword = next( + ( + kw + for kw in ["errors", "status", "message"] + if kw in data["error"] + ), + "", + ) if error_detail_keyword: reason = data["error"][error_detail_keyword] @@ -152,25 +157,25 @@ def _retry_request( ): """Retries an HTTP request multiple times while handling errors. - If after all retries the request still fails, last error is either returned as - return value (for HTTP 5xx errors) or thrown (for ssl.SSLError). - - Args: - http: Http object to be used to execute request. - num_retries: Maximum number of retries. - req_type: Type of the request (used for logging retries). - sleep, rand: Functions to sleep for random time between retries. - uri: URI to be requested. - method: HTTP method to be used. - args, kwargs: Additional arguments passed to http.request. - - Returns: - resp, content - Response from the http request (may be HTTP 5xx). - """ + If after all retries the request still fails, last error is either returned as + return value (for HTTP 5xx errors) or thrown (for ssl.SSLError). + + Args: + http: Http object to be used to execute request. + num_retries: Maximum number of retries. + req_type: Type of the request (used for logging retries). + sleep, rand: Functions to sleep for random time between retries. + uri: URI to be requested. + method: HTTP method to be used. + args, kwargs: Additional arguments passed to http.request. + + Returns: + resp, content - Response from the http request (may be HTTP 5xx). + """ resp = None content = None exception = None - for retry_num in range(num_retries + 1): + for retry_num in six.moves.range(num_retries + 1): if retry_num > 0: # Sleep before retrying. sleep_time = rand() * 2 ** retry_num @@ -235,21 +240,21 @@ class MediaUploadProgress(object): 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. - """ + 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. - """ + 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 and self.total_size != 0: return float(self.resumable_progress) / float(self.total_size) else: @@ -262,20 +267,20 @@ class MediaDownloadProgress(object): def __init__(self, resumable_progress, total_size): """Constructor. - Args: - resumable_progress: int, bytes received so far. - total_size: int, total bytes in complete download. - """ + 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. - """ + 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 and self.total_size != 0: return float(self.resumable_progress) / float(self.total_size) else: @@ -285,107 +290,107 @@ class MediaDownloadProgress(object): 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. - """ + 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. - """ + Returns: + Chunk size in bytes. + """ raise NotImplementedError() def mimetype(self): """Mime type of the body. - Returns: - Mime type. - """ + 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. - """ + 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. - """ + 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. + 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. - """ + 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. + 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. - """ + 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. - """ + 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. + 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(). - """ + 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: @@ -398,24 +403,24 @@ class MediaUpload(object): 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(). - """ + 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(). + representation produced by to_json(). - Args: - s: string, JSON from to_json(). + Args: + s: string, JSON from to_json(). - Returns: - An instance of the subclass of MediaUpload that was serialized with - 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"] @@ -428,44 +433,44 @@ class MediaUpload(object): 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 = 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. - """ + Note that the Python file object is compatible with io.Base and can be used + with this class also. + + fh = 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. - """ + 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 @@ -480,68 +485,68 @@ class MediaIoBaseUpload(MediaUpload): def chunksize(self): """Chunk size for resumable uploads. - Returns: - Chunk size in bytes. - """ + Returns: + Chunk size in bytes. + """ return self._chunksize def mimetype(self): """Mime type of the body. - Returns: - Mime type. - """ + Returns: + Mime type. + """ return self._mimetype def size(self): """Size of upload. - Returns: - Size of the body, or None of the size is unknown. - """ + 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. - """ + 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. + 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. - """ + 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. + 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. - """ + 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. - """ + Returns: + The returned value is an io.IOBase subclass that supports seek, i.e. + seekable() returns True. + """ return self._fd def to_json(self): @@ -552,24 +557,24 @@ class MediaIoBaseUpload(MediaUpload): 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. - """ + 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__( @@ -577,18 +582,18 @@ class MediaFileUpload(MediaIoBaseUpload): ): """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. - """ + 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._fd = None self._filename = filename self._fd = open(self._filename, "rb") @@ -609,10 +614,10 @@ class MediaFileUpload(MediaIoBaseUpload): 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(). - """ + Returns: + string, a JSON representation of this instance, suitable to pass to + from_json(). + """ return self._to_json(strip=["_fd"]) @staticmethod @@ -629,9 +634,9 @@ class MediaFileUpload(MediaIoBaseUpload): class MediaInMemoryUpload(MediaIoBaseUpload): """MediaUpload for a chunk of bytes. - DEPRECATED: Use MediaIoBaseUpload with either io.TextIOBase or StringIO for - the stream. - """ + DEPRECATED: Use MediaIoBaseUpload with either io.TextIOBase or StringIO for + the stream. + """ @util.positional(2) def __init__( @@ -643,18 +648,18 @@ class MediaInMemoryUpload(MediaIoBaseUpload): ): """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. - """ + 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 = BytesIO(body) super(MediaInMemoryUpload, self).__init__( fd, mimetype, chunksize=chunksize, resumable=resumable @@ -662,36 +667,36 @@ class MediaInMemoryUpload(MediaIoBaseUpload): class MediaIoBaseDownload(object): - """"Download media resources. + """ "Download media resources. - Note that the Python file object is compatible with io.Base and can be used - with this class also. + 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) + 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!" - """ + 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. - """ + 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 @@ -716,21 +721,21 @@ class MediaIoBaseDownload(object): def next_chunk(self, num_retries=0): """Get the next chunk of the download. - Args: - num_retries: Integer, number of times to retry with randomized - exponential backoff. If all retries fail, the raised HttpError - represents the last request. If zero (default), we attempt the - request only once. + Args: + num_retries: Integer, number of times to retry 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): (MediaDownloadProgress, boolean) - The value of 'done' will be True when the media has been fully - downloaded or the total size of the media is unknown. + Returns: + (status, done): (MediaDownloadProgress, boolean) + The value of 'done' will be True when the media has been fully + downloaded or the total size of the media is unknown. - Raises: - googleapiclient.errors.HttpError if the response was not a 2xx. - httplib2.HttpLib2Error if a transport error has occurred. - """ + Raises: + googleapiclient.errors.HttpError if the response was not a 2xx. + httplib2.HttpLib2Error if a transport error has occurred. + """ headers = self._headers.copy() headers["range"] = "bytes=%d-%d" % ( self._progress, @@ -773,28 +778,31 @@ class MediaIoBaseDownload(object): self._total_size = int(length) if self._total_size == 0: self._done = True - return MediaDownloadProgress(self._progress, self._total_size), self._done + return ( + MediaDownloadProgress(self._progress, self._total_size), + self._done, + ) 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. - """ + 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. - """ + 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 @@ -803,12 +811,12 @@ class _StreamSlice(object): def read(self, n=-1): """Read n bytes. - Args: - n, int, the number of bytes to read. + Args: + n, int, the number of bytes to read. - Returns: - A string of length 'n', or less if EOF is reached. - """ + 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 @@ -834,18 +842,18 @@ class HttpRequest(object): ): """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. - """ + 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 @@ -874,22 +882,22 @@ class HttpRequest(object): 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 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 occurred. - """ + 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 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 occurred. + """ if http is None: http = self.http @@ -939,53 +947,53 @@ class HttpRequest(object): def add_response_callback(self, cb): """add_response_headers_callback - Args: - cb: Callback to be called on receiving the response headers, of signature: + Args: + cb: Callback to be called on receiving the response headers, of signature: - def cb(resp): - # Where resp is an instance of httplib2.Response - """ + 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. + 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: + Example: - media = MediaFileUpload('cow.png', mimetype='image/png', - chunksize=1000, resumable=True) - request = farm.animals().insert( - id='cow', - name='cow.png', - media_body=media) + 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) + 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 with randomized - exponential backoff. If all retries fail, the raised HttpError - represents the last request. If zero (default), we attempt the - request only once. + 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 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. + 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 occurred. - """ + Raises: + googleapiclient.errors.HttpError if the response was not a 2xx. + httplib2.HttpLib2Error if a transport error has occurred. + """ if http is None: http = self.http @@ -1063,9 +1071,13 @@ class HttpRequest(object): # sending "bytes 0--1/0" results in an invalid request # Only add header "Content-Range" if chunk_end != -1 if chunk_end != -1: - headers["Content-Range"] = "bytes %d-%d/%s" % (self.resumable_progress, chunk_end, size) + headers["Content-Range"] = "bytes %d-%d/%s" % ( + self.resumable_progress, + chunk_end, + size, + ) - for retry_num in range(num_retries + 1): + for retry_num in six.moves.range(num_retries + 1): if retry_num > 0: self._sleep(self._rand() * 2 ** retry_num) LOGGER.warning( @@ -1088,17 +1100,17 @@ class HttpRequest(object): 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. + 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. + 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. - """ + 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) @@ -1158,48 +1170,48 @@ class HttpRequest(object): 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 + Example: + from googleapiclient.http import BatchHttpRequest - 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 + 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') + service = build('farm', 'v2') - batch = BatchHttpRequest() + batch = BatchHttpRequest() - batch.add(service.animals().list(), list_animals) - batch.add(service.farmers().list(), list_farmers) - batch.execute(http=http) - """ + 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. - """ + 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 = _LEGACY_BATCH_URI @@ -1242,10 +1254,10 @@ class BatchHttpRequest(object): 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. - """ + 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() @@ -1272,14 +1284,14 @@ class BatchHttpRequest(object): def _id_to_header(self, id_): """Convert an id to a Content-ID header value. - Args: - id_: string, identifier of individual request. + 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. - """ + 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() @@ -1291,18 +1303,18 @@ class BatchHttpRequest(object): 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. + Presumes the Content-ID header conforms to the format that _id_to_header() + returns. - Args: - header: string, Content-ID header value. + Args: + header: string, Content-ID header value. - Returns: - The extracted id value. + Returns: + The extracted id value. - Raises: - BatchError if the header is not in the expected format. - """ + 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: @@ -1314,12 +1326,12 @@ class BatchHttpRequest(object): def _serialize_request(self, request): """Convert an HttpRequest object into a string. - Args: - request: HttpRequest, the request to serialize. + Args: + request: HttpRequest, the request to serialize. - Returns: - The request as a string in application/http format. - """ + Returns: + The request as a string in application/http format. + """ # Construct status line parsed = urlparse(request.uri) request_line = urlunparse( @@ -1362,12 +1374,12 @@ class BatchHttpRequest(object): def _deserialize_response(self, payload): """Convert string into httplib2 response and content. - Args: - payload: string, headers and body as a string. + Args: + payload: string, headers and body as a string. - Returns: - A pair (resp, content), such as would be returned from httplib2.request. - """ + 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) @@ -1390,11 +1402,11 @@ class BatchHttpRequest(object): def _new_id(self): """Create a new id. - Auto incrementing number that avoids conflicts with ids already used. + Auto incrementing number that avoids conflicts with ids already used. - Returns: - string, a new unique id. - """ + 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 @@ -1404,31 +1416,31 @@ class BatchHttpRequest(object): 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 never 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. - """ + 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 never 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 len(self._order) >= MAX_BATCH_LIMIT: raise BatchError( @@ -1448,16 +1460,16 @@ class BatchHttpRequest(object): 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. - requests: list, list of request objects to send. - - Raises: - httplib2.HttpLib2Error if a transport error has occurred. - googleapiclient.errors.BatchError if the response is the wrong format. - """ + 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. + requests: list, list of request objects to send. + + Raises: + httplib2.HttpLib2Error if a transport error has occurred. + 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) @@ -1522,18 +1534,18 @@ class BatchHttpRequest(object): 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. + 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 + Returns: + None - Raises: - httplib2.HttpLib2Error if a transport error has occurred. - googleapiclient.errors.BatchError if the response is the wrong format. - """ + Raises: + httplib2.HttpLib2Error if a transport error has occurred. + googleapiclient.errors.BatchError if the response is the wrong format. + """ # If we have no requests return if len(self._order) == 0: return None @@ -1603,18 +1615,18 @@ class BatchHttpRequest(object): class HttpRequestMock(object): """Mock of HttpRequest. - Do not construct directly, instead use RequestMockBuilder. - """ + 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. - """ + 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 @@ -1626,9 +1638,9 @@ class HttpRequestMock(object): 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. - """ + 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) @@ -1657,21 +1669,21 @@ class RequestMockBuilder(object): 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. + 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. - """ + 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 @@ -1687,10 +1699,10 @@ class RequestMockBuilder(object): 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. - """ + 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] @@ -1719,10 +1731,10 @@ class HttpMock(object): def __init__(self, filename=None, headers=None): """ - Args: - filename: string, absolute filename to read response from - headers: dict, header to return with response - """ + Args: + filename: string, absolute filename to read response from + headers: dict, header to return with response + """ if headers is None: headers = {"status": "200"} if filename: @@ -1755,35 +1767,36 @@ class HttpMock(object): def close(self): return None + 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 - """ + 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) - """ + Args: + iterable: iterable, a sequence of pairs of (headers, body) + """ self._iterable = iterable self.follow_redirects = True self.request_sequence = list() @@ -1821,22 +1834,22 @@ class HttpMockSequence(object): 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. + 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. + Returns: + A modified instance of http that was passed in. - Example: + Example: - h = httplib2.Http() - h = set_user_agent(h, "my-app-name/6.0") + 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. - """ + 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'. @@ -1871,22 +1884,22 @@ def set_user_agent(http, user_agent): def tunnel_patch(http): """Tunnel PATCH requests over POST. - Args: - http - An instance of httplib2.Http - or something that acts like it. + Args: + http - An instance of httplib2.Http + or something that acts like it. - Returns: - A modified instance of http that was passed in. + Returns: + A modified instance of http that was passed in. - Example: + Example: - h = httplib2.Http() - h = tunnel_patch(h, "my-app-name/6.0") + 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. - """ + 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'. @@ -1925,14 +1938,14 @@ def tunnel_patch(http): def build_http(): """Builds httplib2.Http object - Returns: - A httplib2.Http object, which is used to make http requests, and which has timeout set by default. - To override default timeout call + Returns: + A httplib2.Http object, which is used to make http requests, and which has timeout set by default. + To override default timeout call - socket.setdefaulttimeout(timeout_in_sec) + socket.setdefaulttimeout(timeout_in_sec) - before interacting with this method. - """ + before interacting with this method. + """ if socket.getdefaulttimeout() is not None: http_timeout = socket.getdefaulttimeout() else: @@ -1943,12 +1956,12 @@ def build_http(): # This asks httplib2 to exclude 308s from the status codes # it treats as redirects try: - http.redirect_codes = http.redirect_codes - {308} + http.redirect_codes = http.redirect_codes - {308} except AttributeError: - # Apache Beam tests depend on this library and cannot - # currently upgrade their httplib2 version - # http.redirect_codes does not exist in previous versions - # of httplib2, so pass - pass + # Apache Beam tests depend on this library and cannot + # currently upgrade their httplib2 version + # http.redirect_codes does not exist in previous versions + # of httplib2, so pass + pass return http diff --git a/googleapiclient/schema.py b/googleapiclient/schema.py index 2d589840c..00f858875 100644 --- a/googleapiclient/schema.py +++ b/googleapiclient/schema.py @@ -63,7 +63,6 @@ import six __author__ = "jcgregorio@google.com (Joe Gregorio)" -import copy from collections import OrderedDict from googleapiclient import _helpers as util @@ -75,10 +74,10 @@ class Schemas(object): def __init__(self, discovery): """Constructor. - Args: - discovery: object, Deserialized discovery document from which we pull - out the named schema. - """ + Args: + discovery: object, Deserialized discovery document from which we pull + out the named schema. + """ self.schemas = discovery.get("schemas", {}) # Cache of pretty printed schemas. @@ -88,15 +87,15 @@ class Schemas(object): 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. + 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. - """ + Returns: + string, A string that contains a prototype object with + comments that conforms to the given schema. + """ if seen is None: seen = [] @@ -117,13 +116,13 @@ class Schemas(object): def prettyPrintByName(self, name): """Get pretty printed object prototype from the schema name. - Args: - name: string, Name of schema in the discovery document. + 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. - """ + 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=0)[:-2] @@ -131,15 +130,15 @@ class Schemas(object): 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. + 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. - """ + Returns: + string, A string that contains a prototype object with + comments that conforms to the given schema. + """ if seen is None: seen = [] @@ -148,23 +147,23 @@ class Schemas(object): def prettyPrintSchema(self, schema): """Get pretty printed object prototype of schema. - Args: - schema: object, Parsed JSON schema. + Args: + schema: object, Parsed JSON schema. - Returns: - string, A string that contains a prototype object with - comments that conforms to the given 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=0)[:-2] def get(self, name, default=None): """Get deserialized JSON schema from the schema name. - Args: - name: string, Schema name. - default: object, return value if name not found. - """ + Args: + name: string, Schema name. + default: object, return value if name not found. + """ return self.schemas.get(name, default) @@ -175,12 +174,12 @@ class _SchemaToStruct(object): 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. - """ + 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 = [] @@ -203,26 +202,26 @@ class _SchemaToStruct(object): def emit(self, text): """Add text as a line to the output. - Args: - text: string, Text to 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. - """ + 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. - """ + Args: + text: string, Text to output. + comment: string, Python comment. + """ if comment: divider = "\n" + " " * (self.dent + 2) + "# " lines = comment.splitlines() @@ -243,12 +242,12 @@ class _SchemaToStruct(object): def _to_str_impl(self, schema): """Prototype object based on the schema, in Python code with comments. - Args: - schema: object, Parsed JSON schema file. + Args: + schema: object, Parsed JSON schema file. - Returns: - Prototype object based on the schema, in Python code with comments. - """ + Returns: + Prototype object based on the schema, in Python code with comments. + """ stype = schema.get("type") if stype == "object": self.emitEnd("{", schema.get("description", "")) @@ -305,14 +304,14 @@ class _SchemaToStruct(object): 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. + 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. - """ + 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/noxfile.py b/noxfile.py index d33f26ecc..6e2cadc3b 100644 --- a/noxfile.py +++ b/noxfile.py @@ -1,4 +1,3 @@ - # Copyright 2020 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,8 +13,6 @@ # limitations under the License. import os -import sys - import nox import shutil @@ -61,21 +58,21 @@ def lint(session): ) def unit(session, oauth2client): # Clean up dist and build folders - shutil.rmtree('dist', ignore_errors=True) - shutil.rmtree('build', ignore_errors=True) + shutil.rmtree("dist", ignore_errors=True) + shutil.rmtree("build", ignore_errors=True) session.install(*test_dependencies) session.install(oauth2client) # Create and install wheels - session.run('python3', 'setup.py', 'bdist_wheel') - session.install(os.path.join('dist', os.listdir('dist').pop())) + session.run("python3", "setup.py", "bdist_wheel") + session.install(os.path.join("dist", os.listdir("dist").pop())) # Run tests from a different directory to test the package artifacts root_dir = os.path.dirname(os.path.realpath(__file__)) temp_dir = session.create_tmp() session.chdir(temp_dir) - shutil.copytree(os.path.join(root_dir, 'tests'), 'tests') + shutil.copytree(os.path.join(root_dir, "tests"), "tests") # Run py.test against the unit tests. session.run( @@ -91,6 +88,7 @@ def unit(session, oauth2client): *session.posargs, ) + @nox.session(python=["3.9"]) def scripts(session): session.install(*test_dependencies) |