return data
def _encode_files(self, files):
+ """Build the body for a multipart/form-data request.
+ Will successfully encode files when passed as a dict or a list of
+ 2-tuples. Order is retained if data is a list of 2-tuples but abritrary
+ if parameters are supplied as a dict.
+
+ """
if (not files) or isinstance(self.data, str):
return None
- try:
- fields = self.data.copy()
- except AttributeError:
- fields = dict(self.data)
+ def tuples(obj):
+ """Ensure 2-tuples. A dict or a 2-tuples list can be supplied."""
+ if isinstance(obj, dict):
+ return list(obj.items())
+ elif hasattr(obj, '__iter__'):
+ try:
+ dict(obj)
+ except ValueError:
+ pass
+ else:
+ return obj
+ raise ValueError('A dict or a list of 2-tuples required.')
+
+ # 2-tuples containing both file and data fields.
+ fields = []
- for (k, v) in list(files.items()):
+ for k, v in tuples(files):
# support for explicit filename
if isinstance(v, (tuple, list)):
fn, fp = v
fp = v
if isinstance(fp, (bytes, str)):
fp = StringIO(fp)
- fields.update({k: (fn, fp.read())})
-
- for field in fields:
- if isinstance(fields[field], numeric_types):
- fields[field] = str(fields[field])
- if isinstance(fields[field], list):
- newvalue = ', '.join(fields[field])
- fields[field] = newvalue
-
- (body, content_type) = encode_multipart_formdata(fields)
-
- return (body, content_type)
+ fields.append((k, (fn, fp.read())))
+
+ for k, vs in tuples(self.data):
+ if isinstance(vs, list):
+ for v in vs:
+ fields.append((k, str(v)))
+ else:
+ fields.append((k, str(vs)))
+
+ body, content_type = encode_multipart_formdata(fields)
+
+ return body, content_type
@property
def full_url(self):
list for a value in the data argument."""
data = {'field': ['a', 'b']}
- files = {'file': 'Garbled data'}
+ files = {'field': 'Garbled data'}
r = post(httpbin('post'), data=data, files=files)
t = json.loads(r.text)
- self.assertEqual(t.get('form'), {'field': 'a, b'})
+ self.assertEqual(t.get('form'), {'field': ['a', 'b']})
self.assertEqual(t.get('files'), files)
def test_str_data_content_type(self):
r = get(URL())
self.assertEqual(r.status_code, 200)
+ def test_post_fields_with_multiple_values_and_files_as_tuples(self):
+ """Test that it is possible to POST multiple data and file fields
+ with the same name."""
+
+ data = [
+ ('__field__', '__value__'),
+ ('__field__', '__value__'),
+ ]
+ files = [
+ ('__field__', '__value__'),
+ ('__field__', '__value__'),
+ ]
+
+ r = post(httpbin('post'), data=data, files=files)
+ t = json.loads(r.text)
+
+ self.assertEqual(t.get('form'), {
+ '__field__': [
+ '__value__',
+ '__value__',
+ ]
+ })
+
+ # It's not currently possible to test for multiple file fields with
+ # the same name against httpbin so we need to inspect the encoded
+ # body manually.
+ request = r.request
+ body, content_type = request._encode_files(request.files)
+ file_field = ('Content-Disposition: form-data;'
+ ' name="__field__"; filename="__field__"')
+ self.assertEqual(body.count('__value__'), 4)
+ self.assertEqual(body.count(file_field), 2)
+
+
if __name__ == '__main__':
unittest.main()