Add preliminary support for uploading media.
Reviewed in http://codereview.appspot.com/4515144/
This commit is contained in:
		@@ -29,17 +29,21 @@ import re
 | 
			
		||||
import uritemplate
 | 
			
		||||
import urllib
 | 
			
		||||
import urlparse
 | 
			
		||||
import mimetypes
 | 
			
		||||
 | 
			
		||||
try:
 | 
			
		||||
    from urlparse import parse_qsl
 | 
			
		||||
except ImportError:
 | 
			
		||||
    from cgi import parse_qsl
 | 
			
		||||
 | 
			
		||||
from http import HttpRequest
 | 
			
		||||
from anyjson import simplejson
 | 
			
		||||
from model import JsonModel
 | 
			
		||||
from errors import UnknownLinkType
 | 
			
		||||
from email.mime.multipart import MIMEMultipart
 | 
			
		||||
from email.mime.nonmultipart import MIMENonMultipart
 | 
			
		||||
from errors import HttpError
 | 
			
		||||
from errors import InvalidJsonError
 | 
			
		||||
from errors import UnknownLinkType
 | 
			
		||||
from http import HttpRequest
 | 
			
		||||
from model import JsonModel
 | 
			
		||||
 | 
			
		||||
URITEMPLATE = re.compile('{[^}]*}')
 | 
			
		||||
VARNAME = re.compile('[a-zA-Z0-9_-]+')
 | 
			
		||||
@@ -52,6 +56,11 @@ STACK_QUERY_PARAMETERS = ['trace', 'fields', 'pp', 'prettyPrint', 'userIp',
 | 
			
		||||
  'userip', 'strict']
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _write_headers(self):
 | 
			
		||||
  # Utility no-op method for multipart media handling
 | 
			
		||||
  pass
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def key2param(key):
 | 
			
		||||
  """Converts key names into parameter names.
 | 
			
		||||
 | 
			
		||||
@@ -250,6 +259,11 @@ def createResource(http, baseUrl, model, requestBuilder,
 | 
			
		||||
          'type': 'object',
 | 
			
		||||
          'required': True,
 | 
			
		||||
          }
 | 
			
		||||
      methodDesc['parameters']['media_body'] = {
 | 
			
		||||
          'description': 'The filename of the media request body.',
 | 
			
		||||
          'type': 'string',
 | 
			
		||||
          'required': False,
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
    argmap = {} # Map from method parameter name to query parameter name
 | 
			
		||||
    required_params = [] # Required parameters
 | 
			
		||||
@@ -310,6 +324,7 @@ def createResource(http, baseUrl, model, requestBuilder,
 | 
			
		||||
                'Parameter "%s" value "%s" is not an allowed value in "%s"' %
 | 
			
		||||
                (name, kwargs[name], str(enums)))
 | 
			
		||||
 | 
			
		||||
      media_filename = kwargs.pop('media_body', None)
 | 
			
		||||
      actual_query_params = {}
 | 
			
		||||
      actual_path_params = {}
 | 
			
		||||
      for key, value in kwargs.iteritems():
 | 
			
		||||
@@ -332,16 +347,49 @@ def createResource(http, baseUrl, model, requestBuilder,
 | 
			
		||||
      headers, params, query, body = self._model.request(headers,
 | 
			
		||||
          actual_path_params, actual_query_params, body_value)
 | 
			
		||||
 | 
			
		||||
      # TODO(ade) This exists to fix a bug in V1 of the Buzz discovery
 | 
			
		||||
      # document.  Base URLs should not contain any path elements. If they do
 | 
			
		||||
      # then urlparse.urljoin will strip them out This results in an incorrect
 | 
			
		||||
      # URL which returns a 404
 | 
			
		||||
      url_result = urlparse.urlsplit(self._baseUrl)
 | 
			
		||||
      new_base_url = url_result[0] + '://' + url_result[1]
 | 
			
		||||
 | 
			
		||||
      expanded_url = uritemplate.expand(pathUrl, params)
 | 
			
		||||
      url = urlparse.urljoin(self._baseUrl,
 | 
			
		||||
                             url_result[2] + expanded_url + query)
 | 
			
		||||
      url = urlparse.urljoin(self._baseUrl, expanded_url + query)
 | 
			
		||||
 | 
			
		||||
      if media_filename:
 | 
			
		||||
        (media_mime_type, encoding) = mimetypes.guess_type(media_filename)
 | 
			
		||||
        if media_mime_type is None:
 | 
			
		||||
          raise UnknownFileType(media_filename)
 | 
			
		||||
 | 
			
		||||
        # modify the path to prepend '/upload'
 | 
			
		||||
        parsed = list(urlparse.urlparse(url))
 | 
			
		||||
        parsed[2] = '/upload' + parsed[2]
 | 
			
		||||
        url = urlparse.urlunparse(parsed)
 | 
			
		||||
 | 
			
		||||
        if body is None:
 | 
			
		||||
          headers['content-type'] = media_mime_type
 | 
			
		||||
          # make the body the contents of the file
 | 
			
		||||
          f = file(media_filename, 'rb')
 | 
			
		||||
          body = f.read()
 | 
			
		||||
          f.close()
 | 
			
		||||
        else:
 | 
			
		||||
          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_mime_type.split('/'))
 | 
			
		||||
          msg['Content-Transfer-Encoding'] = 'binary'
 | 
			
		||||
 | 
			
		||||
          f = file(media_filename, 'rb')
 | 
			
		||||
          msg.set_payload(f.read())
 | 
			
		||||
          f.close()
 | 
			
		||||
          msgRoot.attach(msg)
 | 
			
		||||
 | 
			
		||||
          body = msgRoot.as_string()
 | 
			
		||||
 | 
			
		||||
          # must appear after the call to as_string() to get the right boundary
 | 
			
		||||
          headers['content-type'] = ('multipart/related; '
 | 
			
		||||
                                     'boundary="%s"') % msgRoot.get_boundary()
 | 
			
		||||
 | 
			
		||||
      logging.info('URL being requested: %s' % url)
 | 
			
		||||
      return self._requestBuilder(self._http,
 | 
			
		||||
@@ -358,6 +406,8 @@ def createResource(http, baseUrl, model, requestBuilder,
 | 
			
		||||
    for arg in argmap.iterkeys():
 | 
			
		||||
      if arg in STACK_QUERY_PARAMETERS:
 | 
			
		||||
        continue
 | 
			
		||||
      if arg == 'media_body':
 | 
			
		||||
        continue
 | 
			
		||||
      repeated = ''
 | 
			
		||||
      if arg in repeated_params:
 | 
			
		||||
        repeated = ' (repeated)'
 | 
			
		||||
 
 | 
			
		||||
@@ -98,10 +98,12 @@ class DecoratorTests(unittest.TestCase):
 | 
			
		||||
                                         debug=True)
 | 
			
		||||
    self.app = TestApp(application)
 | 
			
		||||
    users.get_current_user = UserMock
 | 
			
		||||
    self.httplib2_orig = httplib2.Http
 | 
			
		||||
    httplib2.Http = Http2Mock
 | 
			
		||||
 | 
			
		||||
  def tearDown(self):
 | 
			
		||||
    self.testbed.deactivate()
 | 
			
		||||
    httplib2.Http = self.httplib2_orig
 | 
			
		||||
 | 
			
		||||
  def test_required(self):
 | 
			
		||||
    # An initial request to an oauth_required decorated path should be a
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user