Overall, excellent and clean work. As mentioned on IRC, in the future, try to submit style/formatting-only stuff in a separate patch.
Design Discussion
-----------------
The one big design/architecture thing that I'd like to suggest a change for is making the responsibility of the Serializer subclasses very specific to transforming the dicts that may compose the body of a Response object. As they currently stand, the Serializer subclasses look to be doing more work that may more appropriately belong in the Controller.
I'll work through an example.
Consider the ImageSerializer class (shortened for brevity):
I think that I would prefer that the ImageSerializer class NOT be responsible for:
a) creating a webob.Response object
b) modifying any headers of the Response object other than Content-Type
I think that all the Serializer subclasses should be doing is transforming the Response object's body attribute to the necessary serialized format, and setting the Content-Type header appropriately.
All the other activities that are happening in ImageSerializer I believe should be in the Controller object, since the Controller is responsible for setting various headers (ETag, Location, etc) and for creating the Response object itself...
So, I think I'd prefer the ImageSerializer to look like this:
class ImageSerializer(wsgi.JSONResponseSerializer):
def _serialize(self, response): response.content_type = 'application/json'
meta = response.results # dict(image=image_meta) response.body = self.to_json(meta)
return response
This allows logic to be centralized in the Controller objects and for the Serializer objects to focus their responsibility only on serializing the contents of the Response objects returned from the Controller methods.
Tiny Nits/Fixes
---------------
Here are some little nits to correct before an approve.
1) docstring stuff
445 + """Base Nova Exception
446 +
447 + To correctly use this class, inherit from it and define
448 + a 'message' property. That message will get printf'd
449 + with the keyword arguments provided to the constructor.
450 +
451 + """
Please use this style instead:
"""
Base Nova Exception
To correctly use this class, inherit from it and define
a 'message' property. That message will get printf'd
with the keyword arguments provided to the constructor.
"""
No need for extra newline at the end, and please put the short description on a new line after the opening """
Some of the new docstrings are correct, others aren't... please do a quick run-through to check they are all consistent like the above style.
Also, we're Glance, not Nova ;P
2)
475 import datetime
476 -
477 import eventlet
eventlet is a 3rd party library. We separate stdlib modules from 3rd party modules with a single newline.
3) few little typos
67 + :retval similiar to 'show' method but without image_data
s/similiar/similar
4)
133 + if 'location' in image_meta and image_meta['location'] is not None:
264 + if 'location' in image_meta and image_meta['location'] is not None:
can be shortened to:
if image_meta.get('location'):
5)
342 + def create(self, request):
343 + result = {}
344 + result['image_meta'] = utils.get_image_meta_from_headers(request)
345 + data = request.body if self.has_body(request) else None
346 + result['image_data'] = data
347 + return result
348 +
349 + def update(self, request):
350 + result = {}
351 + result['image_meta'] = utils.get_image_meta_from_headers(request)
352 + data = request.body if self.has_body(request) else None
353 + result['image_data'] = data
354 + return result
Little bit of DRY cleanup needed there... something like this would do:
def _deserialize(self, request):
result = {}
result['image_meta'] = utils.get_image_meta_from_headers(request)
data = request.body if self.has_body(request) else None
result['image_data'] = data
return result
Could you change the param order to (controller, serializer, deserializer)? You list the params in the docstring that way, and I think it makes more sense that way...
Also, have you thought about how you will deal with multiple serializers? How do you plan to support a resource that can be translated into both JSON and XML?
Hey again Brian, here's a full review...
Overall, excellent and clean work. As mentioned on IRC, in the future, try to submit style/formattin g-only stuff in a separate patch.
Design Discussion
-----------------
The one big design/architecture thing that I'd like to suggest a change for is making the responsibility of the Serializer subclasses very specific to transforming the dicts that may compose the body of a Response object. As they currently stand, the Serializer subclasses look to be doing more work that may more appropriately belong in the Controller.
I'll work through an example.
Consider the ImageSerializer class (shortened for brevity):
357 +class ImageSerializer (wsgi.JSONRespo nseSerializer) : location_ header( self, response, image_meta): image_location( image_meta) headers. add('Location' , location) checksum_ header( self, response, image_meta): headers. add('ETag' , image_meta[ 'checksum' ]) image_meta_ headers( self, response, image_meta): meta_to_ http_headers( image_meta) headers. add(k, v) location( self, image_meta): 'image_ meta'] app_iter= result[ 'image_ iterator' ]) headers. add('Content- Length' , image_meta['size']) headers. add('Content- Type', 'application/ octet-stream' ) image_meta_ headers( response, image_meta) location_ header( response, image_meta) checksum_ header( response, image_meta) 'image_ meta'] headers. add('Content- Type', 'application/json') json(dict( image=image_ meta)) location_ header( response, image_meta) checksum_ header( response, image_meta)
...
360 + def _inject_
361 + location = self._get_
362 + response.
363 +
364 + def _inject_
365 + response.
366 +
367 + def _inject_
...
379 + headers = utils.image_
380 +
381 + for k, v in headers.items():
382 + response.
383 +
384 + def _get_image_
...
386 + return "/v1/images/%s" % image_meta['id']
...
396 + def show(self, result):
397 + image_meta = result[
398 +
399 + response = webob.Response(
400 + # Using app_iter blanks content-length, so we set it here...
401 + response.
402 + response.
403 +
404 + self._inject_
405 + self._inject_
406 + self._inject_
...
419 + def create(self, result):
420 + image_meta = result[
421 + response = webob.Response()
422 + response.status = httplib.CREATED
423 + response.
424 + response.body = self.to_
425 + self._inject_
426 + self._inject_
427 + return response
I think that I would prefer that the ImageSerializer class NOT be responsible for:
a) creating a webob.Response object
b) modifying any headers of the Response object other than Content-Type
I think that all the Serializer subclasses should be doing is transforming the Response object's body attribute to the necessary serialized format, and setting the Content-Type header appropriately.
All the other activities that are happening in ImageSerializer I believe should be in the Controller object, since the Controller is responsible for setting various headers (ETag, Location, etc) and for creating the Response object itself...
So, I think I'd prefer the ImageSerializer to look like this:
class ImageSerializer (wsgi.JSONRespo nseSerializer) :
response. content_ type = 'application/json' image_meta)
response. body = self.to_json(meta)
def _serialize(self, response):
meta = response.results # dict(image=
return response
def create(self, response): (response)
return self._serialize
def update(self, response): (response)
return self._serialize
And leave all the other logic and header-setting in the Controller class...
Note that above, I use:
meta = response.results
In the Controller, you would change this:
return {'image_meta': blah}
to be like what it was before, with a slight change:
response = webob.Response( request= req)
# Add response headers...
response.results = image_meta
return response
This allows logic to be centralized in the Controller objects and for the Serializer objects to focus their responsibility only on serializing the contents of the Response objects returned from the Controller methods.
Tiny Nits/Fixes
---------------
Here are some little nits to correct before an approve.
1) docstring stuff
445 + """Base Nova Exception
446 +
447 + To correctly use this class, inherit from it and define
448 + a 'message' property. That message will get printf'd
449 + with the keyword arguments provided to the constructor.
450 +
451 + """
Please use this style instead:
"""
Base Nova Exception
To correctly use this class, inherit from it and define
a 'message' property. That message will get printf'd
with the keyword arguments provided to the constructor.
"""
No need for extra newline at the end, and please put the short description on a new line after the opening """
Some of the new docstrings are correct, others aren't... please do a quick run-through to check they are all consistent like the above style.
Also, we're Glance, not Nova ;P
2)
475 import datetime
476 -
477 import eventlet
eventlet is a 3rd party library. We separate stdlib modules from 3rd party modules with a single newline.
3) few little typos
67 + :retval similiar to 'show' method but without image_data
s/similiar/similar
4)
133 + if 'location' in image_meta and image_meta[ 'location' ] is not None: 'location' ] is not None:
264 + if 'location' in image_meta and image_meta[
can be shortened to:
if image_meta. get('location' ):
5)
342 + def create(self, request): 'image_ meta'] = utils.get_ image_meta_ from_headers( request) body(request) else None 'image_ data'] = data 'image_ meta'] = utils.get_ image_meta_ from_headers( request) body(request) else None 'image_ data'] = data
343 + result = {}
344 + result[
345 + data = request.body if self.has_
346 + result[
347 + return result
348 +
349 + def update(self, request):
350 + result = {}
351 + result[
352 + data = request.body if self.has_
353 + result[
354 + return result
Little bit of DRY cleanup needed there... something like this would do:
def _deserialize(self, request): 'image_ meta'] = utils.get_ image_meta_ from_headers( request) body(request) else None 'image_ data'] = data
result = {}
result[
data = request.body if self.has_
result[
return result
def create(self, request): ze(request)
return self._deseriali
def update(self, request): ze(request)
return self._deseriali
6) the wsgi.Resource constructor
665 + def __init__(self, deserializer, controller, serializer):
Could you change the param order to (controller, serializer, deserializer)? You list the params in the docstring that way, and I think it makes more sense that way...
Also, have you thought about how you will deal with multiple serializers? How do you plan to support a resource that can be translated into both JSON and XML?