Merge lp:~wallyworld/lazr.restful/dict-parameters into lp:lazr.restful
- dict-parameters
- Merge into trunk
Status: | Merged |
---|---|
Merged at revision: | 199 |
Proposed branch: | lp:~wallyworld/lazr.restful/dict-parameters |
Merge into: | lp:lazr.restful |
Diff against target: |
351 lines (+180/-12) 6 files modified
.bzrignore (+1/-0) src/lazr/restful/NEWS.txt (+6/-0) src/lazr/restful/configure.zcml (+7/-0) src/lazr/restful/docs/webservice-marshallers.txt (+83/-6) src/lazr/restful/marshallers.py (+82/-5) src/lazr/restful/version.txt (+1/-1) |
To merge this branch: | bzr merge lp:~wallyworld/lazr.restful/dict-parameters |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Francis J. Lacoste (community) | Approve | ||
Benji York | Pending | ||
Review via email: mp+97128@code.launchpad.net |
Commit message
Add support for marshalling exported method parameters of type dict.
Description of the change
== Implementation ==
Add the zcml registration and implementation class for a dictionary marshaller: DictFieldMarsha
Also fixed some drive-by lint issues.
== Tests ==
Add new tests to the webservice-
== Lint ==
Linting changed files:
src/lazr/
src/lazr/
src/lazr/
src/lazr/
src/lazr/
./src/lazr/
157: E261 at least two spaces before inline comment
Ian Booth (wallyworld) wrote : | # |
Hi
Thanks for the review
>
> [1]
>
> 255 + def __init__(self, field, request):
> 256 + """See `SimpleFieldMar
> 257 +
> 258 + This also looks for the appropriate marshaller for key_type and
> 259 + value_type.
> 260 + """
> 261 + super(DictField
> 262 + field, request)
> 263 + self.key_marshaller = getMultiAdapter(
> 264 + (field.key_type, request), IFieldMarshaller)
> 265 + self.value_
> 266j+ (field.value_type, request), IFieldMarshaller)
>
> I think we should probably raise an error if key_type or value_type aren't defined?
>
There's no need - the default adaptor lookup will simply return a
SimpleFieldMars
used for List and Set also works the same way.
>
> [2]
>
> 307 + @property
> 308 + def _python_
> 309 + """Create the appropriate python collection from a dict."""
> 310 + # For the DictMarshaller, _type contains the type object,
> 311 + # which can be used as a factory.
> 312 + return self.field._type
>
> Why this indirection? Do you really expect users to subclass the marshaller to
> override the default? YAGNI
>
I based the code on what AbstractCollect
this case we know it's a dict so I agree YAGNI, will fix.
> [3]
>
> 300 + value = super(
> 301 + DictFieldMarsha
>
> Why is this needed?
>
> value is already a dict, what do you expect the superclass to handle? Ah,
> I think it's error handling? Maybe add a comment about that? Which actually
> points to the fact that you are missing a test for this. You test that ValueError
> are raised if the key or value are of the improper type, but not if the request/json is
> not actually a dict (or of the proper representation).
>
Yes, it's for error handling. I'll add a comment and yes, I did miss out
adding a test for this (I meant to). Thanks for picking that up.
Ian Booth (wallyworld) wrote : | # |
Argh, poor wording.
>
> Yes, it's for error handling. I'll add a comment and yes, I did miss out
> adding a test for this (I meant to). Thanks for picking that up.
>
I mean to say that I had intended to add a test but somehow didn't.
Preview Diff
1 | === modified file '.bzrignore' | |||
2 | --- .bzrignore 2011-11-18 22:23:44 +0000 | |||
3 | +++ .bzrignore 2012-03-13 23:50:24 +0000 | |||
4 | @@ -12,3 +12,4 @@ | |||
5 | 12 | *.egg | 12 | *.egg |
6 | 13 | dist | 13 | dist |
7 | 14 | .DS_Store | 14 | .DS_Store |
8 | 15 | MANIFEST | ||
9 | 15 | 16 | ||
10 | === modified file 'src/lazr/restful/NEWS.txt' | |||
11 | --- src/lazr/restful/NEWS.txt 2011-10-11 01:45:29 +0000 | |||
12 | +++ src/lazr/restful/NEWS.txt 2012-03-13 23:50:24 +0000 | |||
13 | @@ -2,6 +2,12 @@ | |||
14 | 2 | NEWS for lazr.restful | 2 | NEWS for lazr.restful |
15 | 3 | ===================== | 3 | ===================== |
16 | 4 | 4 | ||
17 | 5 | 0.19.5 (2012-03-13) | ||
18 | 6 | =================== | ||
19 | 7 | |||
20 | 8 | Fixed bug 953587: add a dict marshaller so that exported method parameters | ||
21 | 9 | can be of type dict. | ||
22 | 10 | |||
23 | 5 | 0.19.4 (2011-10-11) | 11 | 0.19.4 (2011-10-11) |
24 | 6 | =================== | 12 | =================== |
25 | 7 | 13 | ||
26 | 8 | 14 | ||
27 | === modified file 'src/lazr/restful/configure.zcml' | |||
28 | --- src/lazr/restful/configure.zcml 2011-03-22 14:42:59 +0000 | |||
29 | +++ src/lazr/restful/configure.zcml 2012-03-13 23:50:24 +0000 | |||
30 | @@ -272,6 +272,13 @@ | |||
31 | 272 | /> | 272 | /> |
32 | 273 | 273 | ||
33 | 274 | <adapter | 274 | <adapter |
34 | 275 | for="zope.schema.interfaces.IDict | ||
35 | 276 | zope.publisher.interfaces.http.IHTTPRequest" | ||
36 | 277 | provides="lazr.restful.interfaces.IFieldMarshaller" | ||
37 | 278 | factory="lazr.restful.marshallers.DictFieldMarshaller" | ||
38 | 279 | /> | ||
39 | 280 | |||
40 | 281 | <adapter | ||
41 | 275 | for="lazr.restful.interfaces.ICollectionField | 282 | for="lazr.restful.interfaces.ICollectionField |
42 | 276 | zope.publisher.interfaces.http.IHTTPRequest" | 283 | zope.publisher.interfaces.http.IHTTPRequest" |
43 | 277 | provides="lazr.restful.interfaces.IFieldMarshaller" | 284 | provides="lazr.restful.interfaces.IFieldMarshaller" |
44 | 278 | 285 | ||
45 | === modified file 'src/lazr/restful/docs/webservice-marshallers.txt' | |||
46 | --- src/lazr/restful/docs/webservice-marshallers.txt 2010-10-05 15:49:55 +0000 | |||
47 | +++ src/lazr/restful/docs/webservice-marshallers.txt 2012-03-13 23:50:24 +0000 | |||
48 | @@ -700,9 +700,10 @@ | |||
49 | 700 | collection of objects associated with some other object. The generic | 700 | collection of objects associated with some other object. The generic |
50 | 701 | collection marshaller will take care of marshalling to the proper | 701 | collection marshaller will take care of marshalling to the proper |
51 | 702 | collection type, and of marshalling the individual items using the | 702 | collection type, and of marshalling the individual items using the |
53 | 703 | marshaller for its value_type. | 703 | marshaller for its value_type. Dictionaries may specify separate |
54 | 704 | marshallers for their keys and values. | ||
55 | 704 | 705 | ||
57 | 705 | >>> from zope.schema import List, Tuple, Set | 706 | >>> from zope.schema import Dict, List, Tuple, Set |
58 | 706 | >>> list_of_strings_field = List(value_type=Text()) | 707 | >>> list_of_strings_field = List(value_type=Text()) |
59 | 707 | >>> from lazr.restful.example.base.interfaces import Cuisine | 708 | >>> from lazr.restful.example.base.interfaces import Cuisine |
60 | 708 | >>> tuple_of_ints_field = Tuple(value_type=Int()) | 709 | >>> tuple_of_ints_field = Tuple(value_type=Int()) |
61 | @@ -710,6 +711,9 @@ | |||
62 | 710 | ... value_type=Choice(vocabulary=Cuisine)) | 711 | ... value_type=Choice(vocabulary=Cuisine)) |
63 | 711 | >>> set_of_choices_field = Set( | 712 | >>> set_of_choices_field = Set( |
64 | 712 | ... value_type=Choice(vocabulary=Cuisine)).bind(None) | 713 | ... value_type=Choice(vocabulary=Cuisine)).bind(None) |
65 | 714 | >>> dict_of_choices_field = Dict( | ||
66 | 715 | ... key_type=Text(), | ||
67 | 716 | ... value_type=Choice(vocabulary=Cuisine)) | ||
68 | 713 | 717 | ||
69 | 714 | >>> list_marshaller = getMultiAdapter( | 718 | >>> list_marshaller = getMultiAdapter( |
70 | 715 | ... (list_of_strings_field, request), IFieldMarshaller) | 719 | ... (list_of_strings_field, request), IFieldMarshaller) |
71 | @@ -731,9 +735,22 @@ | |||
72 | 731 | >>> verifyObject(IFieldMarshaller, set_marshaller) | 735 | >>> verifyObject(IFieldMarshaller, set_marshaller) |
73 | 732 | True | 736 | True |
74 | 733 | 737 | ||
78 | 734 | The only JSON representation for the collection itself is a list, since | 738 | >>> dict_marshaller = getMultiAdapter( |
79 | 735 | that's the only sequence type available in JSON. Anything else will | 739 | ... (dict_of_choices_field, request), IFieldMarshaller) |
80 | 736 | raise a ValueError. | 740 | >>> verifyObject(IFieldMarshaller, dict_marshaller) |
81 | 741 | True | ||
82 | 742 | >>> dict_key_marshaller = getMultiAdapter( | ||
83 | 743 | ... (dict_of_choices_field.key_type, request), IFieldMarshaller) | ||
84 | 744 | >>> verifyObject(IFieldMarshaller, dict_key_marshaller) | ||
85 | 745 | True | ||
86 | 746 | >>> dict_value_marshaller = getMultiAdapter( | ||
87 | 747 | ... (dict_of_choices_field.value_type, request), IFieldMarshaller) | ||
88 | 748 | >>> verifyObject(IFieldMarshaller, dict_value_marshaller) | ||
89 | 749 | True | ||
90 | 750 | |||
91 | 751 | For sequences, the only JSON representation for the collection itself is a | ||
92 | 752 | list, since that's the only sequence type available in JSON. Anything else | ||
93 | 753 | will raise a ValueError. | ||
94 | 737 | 754 | ||
95 | 738 | >>> list_marshaller.marshall_from_json_data([u"Test"]) | 755 | >>> list_marshaller.marshall_from_json_data([u"Test"]) |
96 | 739 | [u'Test'] | 756 | [u'Test'] |
97 | @@ -743,11 +760,40 @@ | |||
98 | 743 | ... | 760 | ... |
99 | 744 | ValueError: got 'unicode', expected list: u'Test' | 761 | ValueError: got 'unicode', expected list: u'Test' |
100 | 745 | 762 | ||
101 | 763 | For dicts, we support marshalling from sequences of (name, value) pairs as | ||
102 | 764 | well as from dicts or even strings which are interpreted as single element | ||
103 | 765 | lists. | ||
104 | 766 | |||
105 | 767 | >>> dict_marshaller.marshall_from_json_data({u"foo": u"Vegetarian"}) | ||
106 | 768 | {u'foo': <Item Cuisine.VEGETARIAN, ...>} | ||
107 | 769 | |||
108 | 770 | >>> dict_marshaller.marshall_from_json_data([(u"foo", u"Vegetarian")]) | ||
109 | 771 | {u'foo': <Item Cuisine.VEGETARIAN, ...>} | ||
110 | 772 | |||
111 | 773 | >>> dict_marshaller.marshall_from_request(u"foo,Vegetarian") | ||
112 | 774 | {u'foo': <Item Cuisine.VEGETARIAN, ...>} | ||
113 | 775 | |||
114 | 776 | If we attempt to marshall something other than one of the above data formats, | ||
115 | 777 | a ValueError will be raised. | ||
116 | 778 | |||
117 | 779 | >>> dict_marshaller.marshall_from_json_data(u"Test") | ||
118 | 780 | Traceback (most recent call last): | ||
119 | 781 | ... | ||
120 | 782 | ValueError: got 'unicode', expected dict: u'Test' | ||
121 | 783 | |||
122 | 784 | >>> dict_marshaller.marshall_from_request(u"Test") | ||
123 | 785 | Traceback (most recent call last): | ||
124 | 786 | ... | ||
125 | 787 | ValueError: got '[u'Test']', list of name,value pairs | ||
126 | 788 | |||
127 | 746 | None is passed through though. | 789 | None is passed through though. |
128 | 747 | 790 | ||
129 | 748 | >>> print list_marshaller.marshall_from_json_data(None) | 791 | >>> print list_marshaller.marshall_from_json_data(None) |
130 | 749 | None | 792 | None |
131 | 750 | 793 | ||
132 | 794 | >>> print dict_marshaller.marshall_from_json_data(None) | ||
133 | 795 | None | ||
134 | 796 | |||
135 | 751 | ValueError is also raised if one of the value in the list doesn't | 797 | ValueError is also raised if one of the value in the list doesn't |
136 | 752 | validate against the more specific marshaller. | 798 | validate against the more specific marshaller. |
137 | 753 | 799 | ||
138 | @@ -760,6 +806,18 @@ | |||
139 | 760 | ... [u'Vegetarian', u'NoSuchChoice']) | 806 | ... [u'Vegetarian', u'NoSuchChoice']) |
140 | 761 | ValueError: Invalid value "NoSuchChoice"... | 807 | ValueError: Invalid value "NoSuchChoice"... |
141 | 762 | 808 | ||
142 | 809 | ValueError is also raised if one of the keys or values in the dict doesn't | ||
143 | 810 | validate against the more specific marshaller. | ||
144 | 811 | |||
145 | 812 | >>> dict_marshaller.marshall_from_json_data({1: u"Vegetarian"}) | ||
146 | 813 | Traceback (most recent call last): | ||
147 | 814 | ... | ||
148 | 815 | ValueError: got 'int', expected unicode: 1 | ||
149 | 816 | |||
150 | 817 | >>> show_ValueError(dict_marshaller.marshall_from_request, | ||
151 | 818 | ... {u'foo': u'NoSuchChoice'}) | ||
152 | 819 | ValueError: Invalid value "NoSuchChoice"... | ||
153 | 820 | |||
154 | 763 | The return type is correctly typed to the concrete collection. | 821 | The return type is correctly typed to the concrete collection. |
155 | 764 | 822 | ||
156 | 765 | >>> tuple_marshaller.marshall_from_json_data([1, 2, 3]) | 823 | >>> tuple_marshaller.marshall_from_json_data([1, 2, 3]) |
157 | @@ -776,16 +834,28 @@ | |||
158 | 776 | >>> [item.title for item in result] | 834 | >>> [item.title for item in result] |
159 | 777 | ['Vegetarian', 'General'] | 835 | ['Vegetarian', 'General'] |
160 | 778 | 836 | ||
161 | 837 | >>> marshalled_dict = dict_marshaller.marshall_from_json_data( | ||
162 | 838 | ... {u'foo': u'Vegetarian', u'bar': u'General'}) | ||
163 | 839 | >>> type(marshalled_dict) | ||
164 | 840 | <type 'dict'> | ||
165 | 841 | >>> sorted( | ||
166 | 842 | ... [(key, value.title) for key, value in marshalled_dict.items()]) | ||
167 | 843 | [(u'bar', 'General'), (u'foo', 'Vegetarian')] | ||
168 | 779 | 844 | ||
169 | 780 | When coming from the request, either a list or a JSON-encoded | 845 | When coming from the request, either a list or a JSON-encoded |
170 | 781 | representation is accepted. The normal request rules for the | 846 | representation is accepted. The normal request rules for the |
172 | 782 | underlying type are then followed. | 847 | underlying type are then followed. When marshalling dicts, the |
173 | 848 | list elements are name,value strings which are pulled apart and | ||
174 | 849 | used to populate the dict. | ||
175 | 783 | 850 | ||
176 | 784 | >>> list_marshaller.marshall_from_request([u'1', u'2']) | 851 | >>> list_marshaller.marshall_from_request([u'1', u'2']) |
177 | 785 | [u'1', u'2'] | 852 | [u'1', u'2'] |
178 | 786 | >>> list_marshaller.marshall_from_request('["1", "2"]') | 853 | >>> list_marshaller.marshall_from_request('["1", "2"]') |
179 | 787 | [u'1', u'2'] | 854 | [u'1', u'2'] |
180 | 788 | 855 | ||
181 | 856 | >>> dict_marshaller.marshall_from_request('["foo,Vegetarian"]') | ||
182 | 857 | {u'foo': <Item Cuisine.VEGETARIAN, ...>} | ||
183 | 858 | |||
184 | 789 | >>> tuple_marshaller.marshall_from_request([u'1', u'2']) | 859 | >>> tuple_marshaller.marshall_from_request([u'1', u'2']) |
185 | 790 | (1, 2) | 860 | (1, 2) |
186 | 791 | 861 | ||
187 | @@ -794,6 +864,9 @@ | |||
188 | 794 | >>> print list_marshaller.marshall_from_request('null') | 864 | >>> print list_marshaller.marshall_from_request('null') |
189 | 795 | None | 865 | None |
190 | 796 | 866 | ||
191 | 867 | >>> print dict_marshaller.marshall_from_request('null') | ||
192 | 868 | None | ||
193 | 869 | |||
194 | 797 | Also, as a convenience for web client, so that they don't have to JSON | 870 | Also, as a convenience for web client, so that they don't have to JSON |
195 | 798 | encode single-element list, non-list value are promoted into a | 871 | encode single-element list, non-list value are promoted into a |
196 | 799 | single-element list. | 872 | single-element list. |
197 | @@ -810,6 +883,10 @@ | |||
198 | 810 | >>> sorted(set_marshaller.unmarshall(None, marshalled_set)) | 883 | >>> sorted(set_marshaller.unmarshall(None, marshalled_set)) |
199 | 811 | ['Dessert', 'Vegetarian'] | 884 | ['Dessert', 'Vegetarian'] |
200 | 812 | 885 | ||
201 | 886 | >>> unmarshalled = dict_marshaller.unmarshall(None, marshalled_dict) | ||
202 | 887 | >>> sorted(unmarshalled.iteritems()) | ||
203 | 888 | [(u'bar', 'General'), (u'foo', 'Vegetarian')] | ||
204 | 889 | |||
205 | 813 | CollectionField | 890 | CollectionField |
206 | 814 | --------------- | 891 | --------------- |
207 | 815 | 892 | ||
208 | 816 | 893 | ||
209 | === modified file 'src/lazr/restful/marshallers.py' | |||
210 | --- src/lazr/restful/marshallers.py 2011-01-21 21:12:17 +0000 | |||
211 | +++ src/lazr/restful/marshallers.py 2012-03-13 23:50:24 +0000 | |||
212 | @@ -9,6 +9,7 @@ | |||
213 | 9 | 'BytesFieldMarshaller', | 9 | 'BytesFieldMarshaller', |
214 | 10 | 'CollectionFieldMarshaller', | 10 | 'CollectionFieldMarshaller', |
215 | 11 | 'DateTimeFieldMarshaller', | 11 | 'DateTimeFieldMarshaller', |
216 | 12 | 'DictFieldMarshaller', | ||
217 | 12 | 'FloatFieldMarshaller', | 13 | 'FloatFieldMarshaller', |
218 | 13 | 'IntFieldMarshaller', | 14 | 'IntFieldMarshaller', |
219 | 14 | 'ObjectLookupFieldMarshaller', | 15 | 'ObjectLookupFieldMarshaller', |
220 | @@ -112,7 +113,7 @@ | |||
221 | 112 | path_parts = [urllib.unquote(part) for part in path.split('/')] | 113 | path_parts = [urllib.unquote(part) for part in path.split('/')] |
222 | 113 | path_parts.pop(0) | 114 | path_parts.pop(0) |
223 | 114 | path_parts.reverse() | 115 | path_parts.reverse() |
225 | 115 | request = config.createRequest(StringIO(), {'PATH_INFO' : path}) | 116 | request = config.createRequest(StringIO(), {'PATH_INFO': path}) |
226 | 116 | request.setTraversalStack(path_parts) | 117 | request.setTraversalStack(path_parts) |
227 | 117 | root = request.publication.getApplication(self.request) | 118 | root = request.publication.getApplication(self.request) |
228 | 118 | return request.traverse(root) | 119 | return request.traverse(root) |
229 | @@ -125,7 +126,6 @@ | |||
230 | 125 | """ | 126 | """ |
231 | 126 | implements(IFieldMarshaller) | 127 | implements(IFieldMarshaller) |
232 | 127 | 128 | ||
233 | 128 | |||
234 | 129 | # Set this to type or tuple of types that the JSON value must be of. | 129 | # Set this to type or tuple of types that the JSON value must be of. |
235 | 130 | _type = None | 130 | _type = None |
236 | 131 | 131 | ||
237 | @@ -153,7 +153,7 @@ | |||
238 | 153 | if value != '': | 153 | if value != '': |
239 | 154 | try: | 154 | try: |
240 | 155 | v = value | 155 | v = value |
242 | 156 | if isinstance (v, str): | 156 | if isinstance(v, str): |
243 | 157 | v = v.decode('utf8') # assume utf8 | 157 | v = v.decode('utf8') # assume utf8 |
244 | 158 | # XXX gary 2009-03-28 | 158 | # XXX gary 2009-03-28 |
245 | 159 | # The use of the enclosing brackets is a hack to work around | 159 | # The use of the enclosing brackets is a hack to work around |
246 | @@ -327,7 +327,7 @@ | |||
247 | 327 | """Describe all values, not just the selected value.""" | 327 | """Describe all values, not just the selected value.""" |
248 | 328 | unmarshalled = [] | 328 | unmarshalled = [] |
249 | 329 | for item in self.field.vocabulary: | 329 | for item in self.field.vocabulary: |
251 | 330 | item_dict = {'token' : item.token, 'title' : item.title} | 330 | item_dict = {'token': item.token, 'title': item.title} |
252 | 331 | if value.title == item.title: | 331 | if value.title == item.title: |
253 | 332 | item_dict['selected'] = True | 332 | item_dict['selected'] = True |
254 | 333 | unmarshalled.append(item_dict) | 333 | unmarshalled.append(item_dict) |
255 | @@ -462,6 +462,84 @@ | |||
256 | 462 | return set | 462 | return set |
257 | 463 | 463 | ||
258 | 464 | 464 | ||
259 | 465 | class DictFieldMarshaller(SimpleFieldMarshaller): | ||
260 | 466 | """A marshaller for Dicts. | ||
261 | 467 | |||
262 | 468 | It looks up the marshallers for its (key-type, value-type), to handle its | ||
263 | 469 | contained name/value pairs. | ||
264 | 470 | """ | ||
265 | 471 | _type = dict | ||
266 | 472 | _type_error_message = 'not a dict: %r' | ||
267 | 473 | |||
268 | 474 | def __init__(self, field, request): | ||
269 | 475 | """See `SimpleFieldMarshaller`. | ||
270 | 476 | |||
271 | 477 | This also looks for the appropriate marshaller for key_type and | ||
272 | 478 | value_type. | ||
273 | 479 | """ | ||
274 | 480 | super(DictFieldMarshaller, self).__init__( | ||
275 | 481 | field, request) | ||
276 | 482 | self.key_marshaller = getMultiAdapter( | ||
277 | 483 | (field.key_type, request), IFieldMarshaller) | ||
278 | 484 | self.value_marshaller = getMultiAdapter( | ||
279 | 485 | (field.value_type, request), IFieldMarshaller) | ||
280 | 486 | |||
281 | 487 | def _marshall_from_request(self, value): | ||
282 | 488 | """See `IFieldMarshaller`. | ||
283 | 489 | |||
284 | 490 | All items in the dict are marshalled using the appropriate | ||
285 | 491 | `IFieldMarshaller` for the key_type, value_type. | ||
286 | 492 | |||
287 | 493 | When coming in via HTML request parameters, the dict is sent as a list | ||
288 | 494 | of comma separated "key,value" strings. | ||
289 | 495 | If the value isn't a list or dict, transform it into a one-element list. | ||
290 | 496 | That allows web client to submit one-element list of strings | ||
291 | 497 | without having to JSON-encode it. | ||
292 | 498 | """ | ||
293 | 499 | if not isinstance(value, list) and not isinstance(value, dict): | ||
294 | 500 | value = [value] | ||
295 | 501 | if isinstance(value, list): | ||
296 | 502 | try: | ||
297 | 503 | value = dict(nv.split(',', 2) for nv in value) | ||
298 | 504 | except ValueError: | ||
299 | 505 | raise ValueError( | ||
300 | 506 | u"got '%s', list of name,value pairs" % value) | ||
301 | 507 | return dict( | ||
302 | 508 | (self.key_marshaller.marshall_from_json_data(key), | ||
303 | 509 | self.value_marshaller.marshall_from_json_data(val)) | ||
304 | 510 | for key, val in value.items()) | ||
305 | 511 | |||
306 | 512 | def _marshall_from_json_data(self, value): | ||
307 | 513 | """See `SimpleFieldMarshaller`. | ||
308 | 514 | |||
309 | 515 | Marshall the items in the dict using the appropriate | ||
310 | 516 | (key,value) marshallers. | ||
311 | 517 | |||
312 | 518 | The incoming value may be a list of name,value pairs in which case | ||
313 | 519 | we convert to a dict first. | ||
314 | 520 | """ | ||
315 | 521 | if isinstance(value, list): | ||
316 | 522 | value = dict(value) | ||
317 | 523 | # Call super to check for errors and raise a ValueError if necessary. | ||
318 | 524 | value = super( | ||
319 | 525 | DictFieldMarshaller, self)._marshall_from_json_data(value) | ||
320 | 526 | return dict( | ||
321 | 527 | (self.key_marshaller.marshall_from_json_data(key), | ||
322 | 528 | self.value_marshaller.marshall_from_json_data(val)) | ||
323 | 529 | for key, val in value.items()) | ||
324 | 530 | |||
325 | 531 | def unmarshall(self, entry, value): | ||
326 | 532 | """See `SimpleFieldMarshaller`. | ||
327 | 533 | |||
328 | 534 | The value is unmarshalled into a dict and all its items are | ||
329 | 535 | unmarshalled using the appropriate FieldMarshaller. | ||
330 | 536 | """ | ||
331 | 537 | return dict( | ||
332 | 538 | (self.key_marshaller.unmarshall(entry, key), | ||
333 | 539 | self.value_marshaller.unmarshall(entry, val)) | ||
334 | 540 | for key, val in value.items()) | ||
335 | 541 | |||
336 | 542 | |||
337 | 465 | class CollectionFieldMarshaller(SimpleFieldMarshaller): | 543 | class CollectionFieldMarshaller(SimpleFieldMarshaller): |
338 | 466 | """A marshaller for collection fields.""" | 544 | """A marshaller for collection fields.""" |
339 | 467 | implements(IUnmarshallingDoesntNeedValue) | 545 | implements(IUnmarshallingDoesntNeedValue) |
340 | @@ -569,4 +647,3 @@ | |||
341 | 569 | # to the object underlying a resource, we need to strip its | 647 | # to the object underlying a resource, we need to strip its |
342 | 570 | # security proxy. | 648 | # security proxy. |
343 | 571 | return removeSecurityProxy(resource).context | 649 | return removeSecurityProxy(resource).context |
344 | 572 | |||
345 | 573 | 650 | ||
346 | === modified file 'src/lazr/restful/version.txt' | |||
347 | --- src/lazr/restful/version.txt 2011-10-11 01:45:29 +0000 | |||
348 | +++ src/lazr/restful/version.txt 2012-03-13 23:50:24 +0000 | |||
349 | @@ -1,1 +1,1 @@ | |||
351 | 1 | 0.19.4 | 1 | 0.19.5 |
Hi Ian,
Thank you very much for this addition to lazr.restful!
I'm approving this on the premise that you'll address my main comment.
[1]
255 + def __init__(self, field, request): shaller` . Marshaller, self).__init__( marshaller = getMultiAdapter(
256 + """See `SimpleFieldMar
257 +
258 + This also looks for the appropriate marshaller for key_type and
259 + value_type.
260 + """
261 + super(DictField
262 + field, request)
263 + self.key_marshaller = getMultiAdapter(
264 + (field.key_type, request), IFieldMarshaller)
265 + self.value_
266j+ (field.value_type, request), IFieldMarshaller)
I think we should probably raise an error if key_type or value_type aren't defined?
[2]
307 + @property collection_ factory( self):
308 + def _python_
309 + """Create the appropriate python collection from a dict."""
310 + # For the DictMarshaller, _type contains the type object,
311 + # which can be used as a factory.
312 + return self.field._type
Why this indirection? Do you really expect users to subclass the marshaller to
override the default? YAGNI
[3]
300 + value = super( ller, self)._ marshall_ from_json_ data(value)
301 + DictFieldMarsha
Why is this needed?
value is already a dict, what do you expect the superclass to handle? Ah,
I think it's error handling? Maybe add a comment about that? Which actually
points to the fact that you are missing a test for this. You test that ValueError
are raised if the key or value are of the improper type, but not if the request/json is
not actually a dict (or of the proper representation).
Thanks for the drive-by formatting fixes.
Cheers