Merge lp:~jameinel/maas/maasclient-multipart into lp:maas/trunk

Proposed by John A Meinel on 2012-10-05
Status: Merged
Approved by: John A Meinel on 2012-10-05
Approved revision: 1168
Merged at revision: 1180
Proposed branch: lp:~jameinel/maas/maasclient-multipart
Merge into: lp:maas/trunk
Diff against target: 71 lines (+26/-8)
2 files modified
src/apiclient/multipart.py (+15/-8)
src/apiclient/tests/test_multipart.py (+11/-0)
To merge this branch: bzr merge lp:~jameinel/maas/maasclient-multipart
Reviewer Review Type Date Requested Status
Gavin Panella (community) 2012-10-05 Approve on 2012-10-05
Review via email: mp+128176@code.launchpad.net

Commit Message

Support MAASClient.post(..., variable=['list', 'of', 'bytes']) which allows us to send in a list of content.

The multipart code already supported passing the same variable multiple times, but you can't expose that from a **kwargs style function.

Description of the Change

For the Tag updates, we need to pass a list to the API. It turns out that MAASClient didn't actually support it via the .get()/.post() methods.

The fix is pretty straightforward, and the tests assert that Django properly understands the results.

The other way to do this is to have the get/post methods themselves translate a "x=[list]" argument into a series of [(x, item1), (x, item2), (x, item3)...], which is already supported by the multipart code. But this made the most sense to me.

To post a comment you must log in.
John A Meinel (jameinel) wrote :

Note that this doesn't fix the 'MAASClient.get()' case. Where urlencode happily takes a list, str(lst) it, and then encodes the string formatted version.

But at least we can send a list easily via POST.

Jeroen T. Vermeulen (jtv) wrote :

Checking for a list seems dangerously fragile: what of tuples, result sets, generators..? Is there no better way to distinguish this special case?

John A Meinel (jameinel) wrote :

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

On 10/5/2012 12:25 PM, Jeroen T. Vermeulen wrote:
> Checking for a list seems dangerously fragile: what of tuples,
> result sets, generators..? Is there no better way to distinguish
> this special case?
>

Well, list is all I needed. :)

You can do:

try:
 iter(foo)
except TypeError

but then you still need to check for string and unicode first, because
both of them are also iterable,and you don't want to send stuff one
character at a time.

isinstance(obj, (list, tuple, set, ...)) is also a possibility, though
I would probably stick with "if someone needs X, they can add support
for X".

John
=:->

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.12 (Cygwin)
Comment: Using GnuPG with Mozilla - http://www.enigmail.net/

iEYEARECAAYFAlBunIsACgkQJdeBCYSNAAOVcACeLxLab12/sqr8yqsMt7I1Nwra
jeUAoKwoHGu7sB5zZjpt9B6NIOTs5JOY
=tS+u
-----END PGP SIGNATURE-----

Gavin Panella (allenap) wrote :

Remember your abc :)

  from collections import Mapping

  if isinstance(thing, Mapping):
      # Treat it like a dict.
      ...
  else:
      # Assume it's an iterable of (name, value) tuples.
      ...

Also, maascli.utils.urlencode is a less stupid version of
urllib.urlencode. It probably ought to move to apiclient.utils. Or the
standard library ;)

1168. By John A Meinel on 2012-10-05

Switch to make_payloads, since that is a bit cleaner about instance checks.

Gavin Panella (allenap) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/apiclient/multipart.py'
2--- src/apiclient/multipart.py 2012-09-24 11:13:39 +0000
3+++ src/apiclient/multipart.py 2012-10-05 11:15:35 +0000
4@@ -14,7 +14,10 @@
5 'encode_multipart_data',
6 ]
7
8-from collections import Mapping
9+from collections import (
10+ Iterable,
11+ Mapping,
12+ )
13 from email.generator import Generator
14 from email.mime.application import MIMEApplication
15 from email.mime.multipart import MIMEMultipart
16@@ -59,16 +62,20 @@
17 return payload
18
19
20-def make_payload(name, content):
21+def make_payloads(name, content):
22 if isinstance(content, bytes):
23- return make_bytes_payload(name, content)
24+ yield make_bytes_payload(name, content)
25 elif isinstance(content, unicode):
26- return make_string_payload(name, content)
27+ yield make_string_payload(name, content)
28 elif isinstance(content, IOBase):
29- return make_file_payload(name, content)
30+ yield make_file_payload(name, content)
31 elif callable(content):
32 with content() as content:
33- return make_payload(name, content)
34+ yield make_payload(name, content)
35+ elif isinstance(content, Iterable):
36+ for part in content:
37+ for payload in make_payloads(name, part):
38+ yield payload
39 else:
40 raise AssertionError(
41 "%r is unrecognised: %r" % (name, content))
42@@ -77,8 +84,8 @@
43 def build_multipart_message(data):
44 message = MIMEMultipart("form-data")
45 for name, content in data:
46- payload = make_payload(name, content)
47- message.attach(payload)
48+ for payload in make_payloads(name, content):
49+ message.attach(payload)
50 return message
51
52
53
54=== modified file 'src/apiclient/tests/test_multipart.py'
55--- src/apiclient/tests/test_multipart.py 2012-09-22 19:43:13 +0000
56+++ src/apiclient/tests/test_multipart.py 2012-10-05 11:15:35 +0000
57@@ -116,3 +116,14 @@
58 self.assertEqual(
59 files_expected, files_observed,
60 ahem_django_ahem)
61+
62+ def test_encode_multipart_data_list_params(self):
63+ params_in = [
64+ ("one", ["ABC", "XYZ"]),
65+ ("one", "UVW"),
66+ ]
67+ body, headers = encode_multipart_data(params_in, [])
68+ params_out, files_out = (
69+ parse_headers_and_body_with_django(headers, body))
70+ self.assertEqual({'one': ['ABC', 'XYZ', 'UVW']}, params_out)
71+ self.assertSetEqual(set(), set(files_out))