From 38051ac64efdb05f6c1de001871eeec138695d6f Mon Sep 17 00:00:00 2001 From: "Brian J. Watson" Date: Tue, 25 Oct 2016 07:53:08 -0700 Subject: [PATCH] Add media_mime_type keyword argument Sometimes the Python mimetypes module cannot automatically detect the MIME type of a media upload file, and the user would want to explicitly specify it. An example is audio/x-raw. This commit adds a media_mime_type keyword argument to media upload methods. If the caller does not specify this argument, a warning is logged to teach the user about it in case they need to explicitly specify a MIME type. --- googleapiclient/discovery.py | 16 ++++++++++++++-- tests/data/small-png | Bin 0 -> 190 bytes tests/test_discovery.py | 32 ++++++++++++++++++++++++++++---- 3 files changed, 42 insertions(+), 6 deletions(-) create mode 100644 tests/data/small-png diff --git a/googleapiclient/discovery.py b/googleapiclient/discovery.py index c451b473a4d..4e7b7363863 100644 --- a/googleapiclient/discovery.py +++ b/googleapiclient/discovery.py @@ -108,6 +108,12 @@ 'type': 'string', 'required': False, } +MEDIA_MIME_TYPE_PARAMETER_DEFAULT_VALUE = { + 'description': ('The MIME type 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'. @@ -481,7 +487,7 @@ def _fix_up_parameters(method_desc, root_desc, http_method): def _fix_up_media_upload(method_desc, root_desc, path_url, parameters): - """Updates parameters of API by adding 'media_body' if supported by method. + """Adds 'media_body' and 'media_mime_type' parameters 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 @@ -518,6 +524,7 @@ def _fix_up_media_upload(method_desc, root_desc, path_url, parameters): if media_upload: media_path_url = _media_path_url_from_info(root_desc, path_url) parameters['media_body'] = MEDIA_BODY_PARAMETER_DEFAULT_VALUE.copy() + parameters['media_mime_type'] = MEDIA_MIME_TYPE_PARAMETER_DEFAULT_VALUE.copy() if 'body' in parameters: parameters['body']['required'] = False @@ -751,6 +758,7 @@ def method(self, **kwargs): actual_path_params[parameters.argmap[key]] = cast_value body_value = kwargs.get('body', None) media_filename = kwargs.get('media_body', None) + media_mime_type = kwargs.get('media_mime_type', None) if self._developerKey: actual_query_params['key'] = self._developerKey @@ -774,7 +782,11 @@ def method(self, **kwargs): if media_filename: # Ensure we end up with a valid MediaUpload object. if isinstance(media_filename, six.string_types): - (media_mime_type, encoding) = mimetypes.guess_type(media_filename) + if media_mime_type is None: + logger.warning( + 'media_mime_type argument not specified: trying to auto-detect for %s', + media_filename) + media_mime_type, _ = 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)): diff --git a/tests/data/small-png b/tests/data/small-png new file mode 100644 index 0000000000000000000000000000000000000000..5446bc2535858b1be502aa2e4ccc044080b633b0 GIT binary patch literal 190 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1SJ1Ryj={W7>k44ofy`glX(f`a29w(7Bet# z3xhBt!>lBDvF`Ey)*PTV22WQ%mvv4FO#p;_GMNAX literal 0 HcmV?d00001 diff --git a/tests/test_discovery.py b/tests/test_discovery.py index c43d9e41f40..210717d26ca 100644 --- a/tests/test_discovery.py +++ b/tests/test_discovery.py @@ -50,6 +50,7 @@ from googleapiclient.discovery import DISCOVERY_URI from googleapiclient.discovery import key2param from googleapiclient.discovery import MEDIA_BODY_PARAMETER_DEFAULT_VALUE +from googleapiclient.discovery import MEDIA_MIME_TYPE_PARAMETER_DEFAULT_VALUE from googleapiclient.discovery import ResourceMethodParameters from googleapiclient.discovery import STACK_QUERY_PARAMETERS from googleapiclient.discovery import STACK_QUERY_PARAMETER_DEFAULT_VALUE @@ -61,6 +62,7 @@ from googleapiclient.errors import ResumableUploadError from googleapiclient.errors import UnacceptableMimeTypeError from googleapiclient.errors import UnknownApiNameOrVersion +from googleapiclient.errors import UnknownFileType from googleapiclient.http import BatchHttpRequest from googleapiclient.http import HttpMock from googleapiclient.http import HttpMockSequence @@ -212,14 +214,16 @@ def test_fix_up_media_upload_no_initial_invalid(self): def test_fix_up_media_upload_no_initial_valid_minimal(self): valid_method_desc = {'mediaUpload': {'accept': []}} - final_parameters = {'media_body': MEDIA_BODY_PARAMETER_DEFAULT_VALUE} + final_parameters = {'media_body': MEDIA_BODY_PARAMETER_DEFAULT_VALUE, + 'media_mime_type': MEDIA_MIME_TYPE_PARAMETER_DEFAULT_VALUE} self._base_fix_up_method_description_test( valid_method_desc, {}, final_parameters, [], 0, 'http://root/upload/fake/fake-path/') def test_fix_up_media_upload_no_initial_valid_full(self): valid_method_desc = {'mediaUpload': {'accept': ['*/*'], 'maxSize': '10GB'}} - final_parameters = {'media_body': MEDIA_BODY_PARAMETER_DEFAULT_VALUE} + final_parameters = {'media_body': MEDIA_BODY_PARAMETER_DEFAULT_VALUE, + 'media_mime_type': MEDIA_MIME_TYPE_PARAMETER_DEFAULT_VALUE} ten_gb = 10 * 2**30 self._base_fix_up_method_description_test( valid_method_desc, {}, final_parameters, ['*/*'], @@ -236,7 +240,8 @@ def test_fix_up_media_upload_with_initial_valid_minimal(self): valid_method_desc = {'mediaUpload': {'accept': []}} initial_parameters = {'body': {}} final_parameters = {'body': {'required': False}, - 'media_body': MEDIA_BODY_PARAMETER_DEFAULT_VALUE} + 'media_body': MEDIA_BODY_PARAMETER_DEFAULT_VALUE, + 'media_mime_type': MEDIA_MIME_TYPE_PARAMETER_DEFAULT_VALUE} self._base_fix_up_method_description_test( valid_method_desc, initial_parameters, final_parameters, [], 0, 'http://root/upload/fake/fake-path/') @@ -245,7 +250,8 @@ def test_fix_up_media_upload_with_initial_valid_full(self): valid_method_desc = {'mediaUpload': {'accept': ['*/*'], 'maxSize': '10GB'}} initial_parameters = {'body': {}} final_parameters = {'body': {'required': False}, - 'media_body': MEDIA_BODY_PARAMETER_DEFAULT_VALUE} + 'media_body': MEDIA_BODY_PARAMETER_DEFAULT_VALUE, + 'media_mime_type': MEDIA_MIME_TYPE_PARAMETER_DEFAULT_VALUE} ten_gb = 10 * 2**30 self._base_fix_up_method_description_test( valid_method_desc, initial_parameters, final_parameters, ['*/*'], @@ -775,6 +781,24 @@ def test_simple_media_good_upload(self): 'https://www.googleapis.com/upload/zoo/v1/animals?uploadType=media&alt=json', request.uri) + def test_simple_media_unknown_mimetype(self): + self.http = HttpMock(datafile('zoo.json'), {'status': '200'}) + zoo = build('zoo', 'v1', http=self.http) + + try: + zoo.animals().insert(media_body=datafile('small-png')) + self.fail("should throw exception if mimetype is unknown.") + except UnknownFileType: + pass + + request = zoo.animals().insert(media_body=datafile('small-png'), + media_mime_type='image/png') + self.assertEquals('image/png', request.headers['content-type']) + self.assertEquals(b'PNG', request.body[1:4]) + assertUrisEqual(self, + 'https://www.googleapis.com/upload/zoo/v1/animals?uploadType=media&alt=json', + request.uri) + def test_multipart_media_raise_correct_exceptions(self): self.http = HttpMock(datafile('zoo.json'), {'status': '200'}) zoo = build('zoo', 'v1', http=self.http)