diff --git a/changelog.d/299.feature.rst b/changelog.d/299.feature.rst new file mode 100644 index 00000000..c80d711c --- /dev/null +++ b/changelog.d/299.feature.rst @@ -0,0 +1 @@ +treq produces a more helpful exception when passed a tuple of the wrong size in the *files* parameter. diff --git a/src/treq/client.py b/src/treq/client.py index ba4f0a1c..509160b0 100644 --- a/src/treq/client.py +++ b/src/treq/client.py @@ -394,6 +394,18 @@ def _convert_files(files): file_name, fobj = val elif len(val) == 3: file_name, content_type, fobj = val + else: + # NB: This is TypeError for backward compatibility. This case + # used to fall through to `IBodyProducer`, below, which raised + # TypeError about being unable to coerce None. + raise TypeError( + ( + "`files` argument must be a sequence of tuples of" + " (file_name, file_obj) or" + " (file_name, content_type, file_obj)," + " but the {!r} tuple has length {}: {!r}" + ).format(param, len(val), val), + ) else: fobj = val if hasattr(fobj, "name"): @@ -402,6 +414,7 @@ def _convert_files(files): if not content_type: content_type = _guess_content_type(file_name) + # XXX: Shouldn't this call self._data_to_body_producer? yield (param, (file_name, content_type, IBodyProducer(fobj))) diff --git a/src/treq/test/test_client.py b/src/treq/test/test_client.py index a4dc9577..571353b5 100644 --- a/src/treq/test/test_client.py +++ b/src/treq/test/test_client.py @@ -398,6 +398,36 @@ def test_request_named_attachment_and_ctype(self): boundary=b'heyDavid'), self.MultiPartProducer.call_args) + def test_request_files_tuple_too_short(self): + """ + The `HTTPClient.request()` *files* argument requires tuples of length + 2 or 3. It raises `TypeError` when the tuple is too short. + """ + with self.assertRaises(TypeError) as c: + self.client.request( + "POST", + b"http://example.com/", + files=[("t1", ("foo.txt",))], + ) + + self.assertIn("'t1' tuple has length 1", str(c.exception)) + + def test_request_files_tuple_too_long(self): + """ + The `HTTPClient.request()` *files* argument requires tuples of length + 2 or 3. It raises `TypeError` when the tuple is too long. + """ + with self.assertRaises(TypeError) as c: + self.client.request( + "POST", + b"http://example.com/", + files=[ + ("t4", ("foo.txt", "text/plain", BytesIO(b"...\n"), "extra!")), + ], + ) + + self.assertIn("'t4' tuple has length 4", str(c.exception)) + @mock.patch('treq.client.uuid.uuid4', mock.Mock(return_value="heyDavid")) def test_request_mixed_params(self):