Permalink
Browse files

adding support to multi-value keys in body for multipart/form-data co…

…ntent-type
  • Loading branch information...
1 parent c6d2b65 commit 1d31fae24ba933698121c1550f1822c1f5326c50 @igorsobreira igorsobreira committed Dec 6, 2010
Showing with 71 additions and 3 deletions.
  1. +4 −2 bolacha/__init__.py
  2. +14 −1 bolacha/multipart.py
  3. +40 −0 tests/unit/test_bolacha.py
  4. +13 −0 tests/unit/test_multipart.py
View
@@ -29,6 +29,7 @@
from bolacha.multipart import encode_multipart
from bolacha.multipart import urlencode
from bolacha.multipart import is_file
+from bolacha.multipart import expand_items
__version__ = '0.4.0'
__release__ = 'tasty'
@@ -100,8 +101,9 @@ def request(self, url, method, body=None, headers=None):
is_urlencoded = False
- body_has_file = isinstance(body, dict) and any([is_file(fobj)
- for fobj in body.values()])
+ body_has_file = isinstance(body, dict) and any([is_file(value)
+ for key,value
+ in expand_items(body)])
if isinstance(body, dict):
if body_has_file:
View
@@ -60,10 +60,23 @@ def to_str(s, encoding='utf-8', strings_only=False, errors='strict'):
else:
return s
+def expand_items(dictionary):
+ """
+ Given a dict like {'key': ('value1', 'value2')} returns
+ a list like [('key','value1'), ('key', 'value2')]
+ """
+ items = []
+ for key, value in dictionary.items():
+ if isinstance(value, (list, tuple)):
+ items.extend([(key, item) for item in value])
+ else:
+ items.append((key, value))
+ return items
+
def encode_multipart(boundary, data):
lines = []
- for key, value in data.items():
+ for key, value in expand_items(data):
if is_file(value):
lines.extend(encode_file(boundary, key, value))
else:
View
@@ -483,3 +483,43 @@ def read(self):
b.request('http://somewhere.com', 'POST', body=body, headers=request_headers)
mocker.VerifyAll()
+def test_request_with_multiple_files_with_same_name_will_upload_multipart():
+ mocker = Mox()
+
+ klass_mock = mocker.CreateMockAnything()
+ http_mock = mocker.CreateMockAnything()
+ klass_mock().AndReturn(http_mock)
+
+ class FileStub(object):
+ name = '/path/to/file'
+ def __init__(self, num):
+ self.num = num
+ def read(self):
+ return 'FileStubContent-%s' % self.num
+
+ request_headers = {}
+ body = {
+ 'pictures': (FileStub(1), FileStub(2)),
+ }
+
+ expected_body = '--%(boundary)s\r\nContent-Disposition: form-data; ' \
+ 'name="pictures"; filename="file"\r\nContent-Type: ' \
+ 'application/octet-stream\r\n\r\nFileStubContent-1\r\n' \
+ '--%(boundary)s\r\nContent-Disposition: form-data; ' \
+ 'name="pictures"; filename="file"\r\nContent-Type: ' \
+ 'application/octet-stream\r\n\r\nFileStubContent-2\r\n' \
+ '--%(boundary)s--\r\n' \
+ % {'boundary': BOUNDARY}
+ expected_header = {'content-length': '364',
+ 'Content-type': 'multipart/form-data; ' \
+ 'boundary=%s' % BOUNDARY}
+
+ http_mock.request('http://somewhere.com', 'POST',
+ expected_body, expected_header). \
+ AndReturn(({}, ''))
+
+ mocker.ReplayAll()
+
+ b = Bolacha(klass_mock)
+ b.request('http://somewhere.com', 'POST', body=body, headers=request_headers)
+ mocker.VerifyAll()
@@ -155,3 +155,16 @@ class FakeFile(object):
read = 'not a callable'
assert not multipart.is_file(FakeFile())
+
+
+def test_expand_items():
+ d = {
+ 'key1': ('value1-1', 'value1-2'),
+ 'key2': 'value2',
+ }
+ items = multipart.expand_items(d)
+
+ assert ('key1', 'value1-1') in items
+ assert ('key1', 'value1-2') in items
+ assert ('key2', 'value2') in items
+

0 comments on commit 1d31fae

Please sign in to comment.