From 33e6105822ececca8e8d75a0d40e644cb69924c4 Mon Sep 17 00:00:00 2001 From: James Ebentier Date: Tue, 3 Feb 2015 18:03:20 -0800 Subject: [PATCH 1/7] MultipartForm: adding in basic support for multipart form submissions on python bindings --- src/main/resources/python/api.mustache | 13 +++++-- src/main/resources/python/swagger.mustache | 40 +++++++++++++++++++--- 2 files changed, 46 insertions(+), 7 deletions(-) diff --git a/src/main/resources/python/api.mustache b/src/main/resources/python/api.mustache index 1b304e81d96..09a33ec88a9 100644 --- a/src/main/resources/python/api.mustache +++ b/src/main/resources/python/api.mustache @@ -59,8 +59,11 @@ class {{classname}}(object): queryParams = {} headerParams = {} formParams = {} + files = [] bodyParam = None + headerParams['Content-type'] = '{{#consumes}}{{mediaType}}{{#hasMore}},{{/hasMore}}{{/consumes}}' + {{#queryParams}} if ('{{paramName}}' in params): queryParams['{{paramName}}'] = self.apiClient.toPathValue(params['{{paramName}}']) @@ -80,10 +83,14 @@ class {{classname}}(object): {{#formParams}} if ('{{paramName}}' in params): + {{#notFile}} formParams['{{paramName}}'] = params['{{paramName}}'] + {{/notFile}} + {{#isFile}} + files.append(('{{paramName}}', params['{{paramName}}'])) + {{/isFile}} {{/formParams}} - if formParams: - headerParams['Content-type'] = 'application/x-www-form-urlencoded' + {{newline}} {{#bodyParam}} if ('{{paramName}}' in params): @@ -93,7 +100,7 @@ class {{classname}}(object): postData = (formParams if formParams else bodyParam) response = self.apiClient.callAPI(resourcePath, method, queryParams, - postData, headerParams) + postData, headerParams, files=files) {{#returnType}} if not response: diff --git a/src/main/resources/python/swagger.mustache b/src/main/resources/python/swagger.mustache index b4e1fab3595..e87b834d786 100644 --- a/src/main/resources/python/swagger.mustache +++ b/src/main/resources/python/swagger.mustache @@ -12,6 +12,9 @@ import urllib2 import httplib import json import datetime +import mimetools +import mimetypes +import itertools from models import * @@ -26,9 +29,10 @@ class ApiClient: self.apiKey = apiKey self.apiServer = apiServer self.cookie = None + self.boundary = mimetools.choose_boundary() def callAPI(self, resourcePath, method, queryParams, postData, - headerParams=None): + headerParams=None, files=None): url = self.apiServer + resourcePath headers = {} @@ -60,12 +64,15 @@ class ApiClient: elif method in ['POST', 'PUT', 'DELETE']: if postData: - data = self.sanitizeForSerialization(postData) + postData = self.sanitizeForSerialization(postData) if 'Content-type' not in headers: headers['Content-type'] = 'application/json' - data = json.dumps(data) + data = json.dumps(postData) + else if headers['Content-type'] == 'multipart/form': + headers['Content-type'] = 'multipart/form; boundry=%s' % self.boundary + data = self.buildMultipartFormData(postData, files) else: - data = urllib.urlencode(data) + data = urllib.urlencode(postData) else: raise Exception('Method ' + method + ' is not recognized.') @@ -127,6 +134,31 @@ class ApiClient: elif type(postData) not in safeToDump: data = json.dumps(postData.__dict__) + def buildMultipartFormData(self, postData, files): + parts = [] + part_boundary = '--' + self.boundary + parts.extend( + [part_boundary, 'Content-Disposition: form-data; name="%s"' % name, '', value] + for name, value in postData.items() + ) + + file_tuples = [] + for fieldname, file_path in files: + filename = file_path.split('/')[-1] + f = open(file_path, 'r') + mimetype = mimetypes.guess_type(filename)[0] or 'application/octet-stream' + body = f.read() + file_tuples.append((fieldname, filename, mimetype, body)) + parts.extend( + [part_boundary, 'Content-Disposition file; name="%s"; filename="%s"' % (fieldname, filename), 'Content-Type: %s' % content_type, '', body] + for fieldname, filename, content_type, body in file_tuples + ) + + flattened = list(itertools.chain(*parts)) + flattened.append('--' + self.boundary + '--') + flattened.append('') + return '\r\n'.join(flattened) + def deserialize(self, obj, objClass): """Derialize a JSON string into an object. From 55575541d8fd4f19f0f4242f11032e27398f0034 Mon Sep 17 00:00:00 2001 From: James Ebentier Date: Tue, 3 Feb 2015 18:28:56 -0800 Subject: [PATCH 2/7] MultipartForm: better formatting --- src/main/resources/python/api.mustache | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/python/api.mustache b/src/main/resources/python/api.mustache index 09a33ec88a9..cf2c25ee161 100644 --- a/src/main/resources/python/api.mustache +++ b/src/main/resources/python/api.mustache @@ -89,8 +89,8 @@ class {{classname}}(object): {{#isFile}} files.append(('{{paramName}}', params['{{paramName}}'])) {{/isFile}} - {{/formParams}} {{newline}} + {{/formParams}} {{#bodyParam}} if ('{{paramName}}' in params): From 63bb20238bd62a915d2f26900de5cd31ba1b712c Mon Sep 17 00:00:00 2001 From: James Ebentier Date: Tue, 3 Feb 2015 19:01:31 -0800 Subject: [PATCH 3/7] MultipartForm: fixing syntax error --- src/main/resources/python/swagger.mustache | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/python/swagger.mustache b/src/main/resources/python/swagger.mustache index e87b834d786..834be596c4c 100644 --- a/src/main/resources/python/swagger.mustache +++ b/src/main/resources/python/swagger.mustache @@ -68,7 +68,7 @@ class ApiClient: if 'Content-type' not in headers: headers['Content-type'] = 'application/json' data = json.dumps(postData) - else if headers['Content-type'] == 'multipart/form': + elif headers['Content-type'] == 'multipart/form': headers['Content-type'] = 'multipart/form; boundry=%s' % self.boundary data = self.buildMultipartFormData(postData, files) else: From e955c9dcfddfeec32c5a157fa403e3f5850e939c Mon Sep 17 00:00:00 2001 From: James Ebentier Date: Wed, 4 Feb 2015 09:56:10 -0800 Subject: [PATCH 4/7] MultipartForm: getting the file uploading working --- src/main/resources/python/api.mustache | 6 +-- src/main/resources/python/swagger.mustache | 59 ++++++++++++---------- 2 files changed, 36 insertions(+), 29 deletions(-) diff --git a/src/main/resources/python/api.mustache b/src/main/resources/python/api.mustache index cf2c25ee161..16a0ebdebed 100644 --- a/src/main/resources/python/api.mustache +++ b/src/main/resources/python/api.mustache @@ -59,7 +59,7 @@ class {{classname}}(object): queryParams = {} headerParams = {} formParams = {} - files = [] + files = {} bodyParam = None headerParams['Content-type'] = '{{#consumes}}{{mediaType}}{{#hasMore}},{{/hasMore}}{{/consumes}}' @@ -87,9 +87,9 @@ class {{classname}}(object): formParams['{{paramName}}'] = params['{{paramName}}'] {{/notFile}} {{#isFile}} - files.append(('{{paramName}}', params['{{paramName}}'])) + files['{{paramName}}'] = params['{{paramName}}'] {{/isFile}} - {{newline}} + {{#hasMore}}{{newline}}{{/hasMore}} {{/formParams}} {{#bodyParam}} diff --git a/src/main/resources/python/swagger.mustache b/src/main/resources/python/swagger.mustache index 834be596c4c..59b6d38fbd6 100644 --- a/src/main/resources/python/swagger.mustache +++ b/src/main/resources/python/swagger.mustache @@ -12,9 +12,9 @@ import urllib2 import httplib import json import datetime -import mimetools import mimetypes -import itertools +import random +import string from models import * @@ -29,7 +29,7 @@ class ApiClient: self.apiKey = apiKey self.apiServer = apiServer self.cookie = None - self.boundary = mimetools.choose_boundary() + self.boundary = ''.join(random.choice(string.ascii_letters + string.digits) for _ in range(30)) def callAPI(self, resourcePath, method, queryParams, postData, headerParams=None, files=None): @@ -40,7 +40,6 @@ class ApiClient: for param, value in headerParams.iteritems(): headers[param] = value - #headers['Content-type'] = 'application/json' headers['api_key'] = self.apiKey if self.cookie: @@ -68,9 +67,10 @@ class ApiClient: if 'Content-type' not in headers: headers['Content-type'] = 'application/json' data = json.dumps(postData) - elif headers['Content-type'] == 'multipart/form': - headers['Content-type'] = 'multipart/form; boundry=%s' % self.boundary + elif headers['Content-type'] == 'multipart/form-data': data = self.buildMultipartFormData(postData, files) + headers['Content-type'] = 'multipart/form-data; boundary={0}'.format(self.boundary) + headers['Content-length'] = str(len(data)) else: data = urllib.urlencode(postData) @@ -135,29 +135,36 @@ class ApiClient: data = json.dumps(postData.__dict__) def buildMultipartFormData(self, postData, files): - parts = [] - part_boundary = '--' + self.boundary - parts.extend( - [part_boundary, 'Content-Disposition: form-data; name="%s"' % name, '', value] - for name, value in postData.items() - ) + def escape_quotes(s): + return s.replace('"', '\\"') - file_tuples = [] - for fieldname, file_path in files: - filename = file_path.split('/')[-1] - f = open(file_path, 'r') + lines = [] + + for name, value in postData.items(): + lines.extend(( + '--{0}'.format(self.boundary), + 'Content-Disposition: form-data; name="{0}"'.format(escape_quotes(name)), + '', + str(value), + )) + + for name, filepath in files.items(): + f = open(filepath, 'r') + filename = filepath.split('/')[-1] mimetype = mimetypes.guess_type(filename)[0] or 'application/octet-stream' - body = f.read() - file_tuples.append((fieldname, filename, mimetype, body)) - parts.extend( - [part_boundary, 'Content-Disposition file; name="%s"; filename="%s"' % (fieldname, filename), 'Content-Type: %s' % content_type, '', body] - for fieldname, filename, content_type, body in file_tuples - ) + lines.extend(( + '--{0}'.format(self.boundary), + 'Content-Disposition: form-data; name="{0}"; filename="{1}"'.format(escape_quotes(name), escape_quotes(filename)), + 'Content-Type: {0}'.format(mimetype), + '', + f.read() + )) - flattened = list(itertools.chain(*parts)) - flattened.append('--' + self.boundary + '--') - flattened.append('') - return '\r\n'.join(flattened) + lines.extend(( + '--{0}--'.format(self.boundary), + '' + )) + return '\r\n'.join(lines) def deserialize(self, obj, objClass): """Derialize a JSON string into an object. From 257d67346c535247101a5a85936cf3995086f85d Mon Sep 17 00:00:00 2001 From: James Ebentier Date: Wed, 4 Feb 2015 10:41:25 -0800 Subject: [PATCH 5/7] MultipartForm: fixing some spacing issues --- src/main/resources/python/api.mustache | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/main/resources/python/api.mustache b/src/main/resources/python/api.mustache index 16a0ebdebed..2a7bf5d65a2 100644 --- a/src/main/resources/python/api.mustache +++ b/src/main/resources/python/api.mustache @@ -83,13 +83,7 @@ class {{classname}}(object): {{#formParams}} if ('{{paramName}}' in params): - {{#notFile}} - formParams['{{paramName}}'] = params['{{paramName}}'] - {{/notFile}} - {{#isFile}} - files['{{paramName}}'] = params['{{paramName}}'] - {{/isFile}} - {{#hasMore}}{{newline}}{{/hasMore}} + {{#notFile}}formParams['{{paramName}}'] = params['{{paramName}}']{{/notFile}}{{#isFile}}files['{{paramName}}'] = params['{{paramName}}']{{/isFile}} {{/formParams}} {{#bodyParam}} From 6c97a9c6c787b94fce10cd64d1e9471e0f10e5d4 Mon Sep 17 00:00:00 2001 From: James Ebentier Date: Wed, 4 Feb 2015 10:54:55 -0800 Subject: [PATCH 6/7] MultipartForm: some more fixing of formating --- src/main/resources/python/api.mustache | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/resources/python/api.mustache b/src/main/resources/python/api.mustache index 2a7bf5d65a2..30214af6d06 100644 --- a/src/main/resources/python/api.mustache +++ b/src/main/resources/python/api.mustache @@ -84,6 +84,7 @@ class {{classname}}(object): {{#formParams}} if ('{{paramName}}' in params): {{#notFile}}formParams['{{paramName}}'] = params['{{paramName}}']{{/notFile}}{{#isFile}}files['{{paramName}}'] = params['{{paramName}}']{{/isFile}} + {{newline}} {{/formParams}} {{#bodyParam}} From 0f041dae90af5898af55da5a1b25b3644bcc59ac Mon Sep 17 00:00:00 2001 From: James Ebentier Date: Wed, 4 Feb 2015 11:49:28 -0800 Subject: [PATCH 7/7] MultipartForm: adding support for php --- src/main/resources/php/Swagger.mustache | 3 ++- src/main/resources/php/api.mustache | 5 +++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/main/resources/php/Swagger.mustache b/src/main/resources/php/Swagger.mustache index 962cc53455f..a176b68aa0d 100644 --- a/src/main/resources/php/Swagger.mustache +++ b/src/main/resources/php/Swagger.mustache @@ -77,7 +77,8 @@ class APIClient { $headers[] = "api_key: " . $this->apiKey; } - if (is_object($postData) or is_array($postData)) { + + if (strpos($headers['Content-Type'], "multipart/form-data") < 0 and (is_object($postData) or is_array($postData))) { $postData = json_encode($this->sanitizeForSerialization($postData)); } diff --git a/src/main/resources/php/api.mustache b/src/main/resources/php/api.mustache index 30d34f556e7..43ffbaa60a5 100644 --- a/src/main/resources/php/api.mustache +++ b/src/main/resources/php/api.mustache @@ -72,7 +72,12 @@ class {{classname}} { } {{#formParams}} if(${{paramName}} != null) { + {{#notFile}} $body['{{paramName}}'] = ${{paramName}}; + {{/notFile}} + {{#isFile}} + $body['{{paramName}}'] = '@' . ${{paramName}}; + {{/isFile}} } {{/formParams}} if (empty($body)) {