Merge lp:~andrea.corbellini/lazr.restful/fix-375353 into lp:lazr.restful

Proposed by Andrea Corbellini
Status: Merged
Approved by: Francis J. Lacoste
Approved revision: 40
Merged at revision: not available
Proposed branch: lp:~andrea.corbellini/lazr.restful/fix-375353
Merge into: lp:lazr.restful
Diff against target: None lines
To merge this branch: bzr merge lp:~andrea.corbellini/lazr.restful/fix-375353
Reviewer Review Type Date Requested Status
Francis J. Lacoste (community) Approve
LAZR Developers Pending
Review via email: mp+6892@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Andrea Corbellini (andrea.corbellini) wrote :

Here's my attempt to fix bug 375353.

This branch adds a new method annotator (cache_for) that allows to specify how much seconds the response should be cached by the browser. BaseResourceOperationAdapter.call() then sets the correct Cache-control HTTP header.

Note that Cache-control is not supported by HTTP/1.0 clients:
http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9

Revision history for this message
Francis J. Lacoste (flacoste) wrote :

On Thursday 04 June 2009, Andrea Corbellini wrote:
> You have been requested to review the proposed merge of
> lp:~andrea-bs/lazr.restful/fix-375353 into lp:lazr.restful.
>
> Here's my attempt to fix bug 375353.
>
> This branch adds a new method annotator (cache_for) that allows to specify
> how much seconds the response should be cached by the browser.
> BaseResourceOperationAdapter.call() then sets the correct Cache-control
> HTTP header.
>
> Note that Cache-control is not supported by HTTP/1.0 clients:
> http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9

Thank you very much Andrea!

This branch is great and matches the implementation plan we discussed.

I have a few minor comments:

>=== modified file 'src/lazr/restful/declarations.py'

>
>+class cache_for(_method_annotator):
>+ """Decorator specifying how much a response may be cached by a
> client."""

s/how much/how long/

>+
>+ def __init__(self, duration):
>+ """Specify the duration, in seconds, of the resource."""

s/the/the caching/

>+ self.duration = duration

I think it would be a good idea to add error checking here. So that passing in
a strings gives an error message instead of generating invalid headers.

    try:
  self.duration = int(duration)
 except (TypeError, ValueError):
    raise TypeError("Caching duration should be a number of seconds: %s" %
duration)

(And please add a test for that behaviour.)

>+
>+ # For responses to GET requests, tell the client to cache the
> response

Missing a period at the end of the comment.

If you implement the above changes, I'll merge the branch on your behalf.

Again, thanks a lot!

 status approved
    review approve

--
Francis J. Lacoste
<email address hidden>

review: Approve
Revision history for this message
Andrea Corbellini (andrea.corbellini) wrote :

Thanks for your review.

On Thu, 2009-06-04 at 19:12 +0000, Francis J. Lacoste wrote:
> I think it would be a good idea to add error checking here. So that passing in
> a strings gives an error message instead of generating invalid headers.
>
> try:
> self.duration = int(duration)
> except (TypeError, ValueError):
> raise TypeError("Caching duration should be a number of seconds: %s" %
> duration)
>
> (And please add a test for that behaviour.)

I would prefer to do something like this:
  if not isinstance(duration, (int, long)): raise ...

I think that this is a better way to do this because it should be safer:
if, for mistake, the wrong object is passed to @cache_for and if it has
a __int__() method, it may pass the check, causing an incorrect behavior
instead of raising an exception.

Revision history for this message
Francis J. Lacoste (flacoste) wrote :

On Friday 05 June 2009, Andrea Corbellini wrote:
> Thanks for your review.
>
> On Thu, 2009-06-04 at 19:12 +0000, Francis J. Lacoste wrote:
> > I think it would be a good idea to add error checking here. So that
> > passing in a strings gives an error message instead of generating invalid
> > headers.
> >
> > try:
> > self.duration = int(duration)
> > except (TypeError, ValueError):
> > raise TypeError("Caching duration should be a number of seconds:
> > %s" % duration)
> >
> > (And please add a test for that behaviour.)
>
> I would prefer to do something like this:
> if not isinstance(duration, (int, long)): raise ...
>
> I think that this is a better way to do this because it should be safer:
> if, for mistake, the wrong object is passed to @cache_for and if it has
> a __int__() method, it may pass the check, causing an incorrect behavior
> instead of raising an exception.

Ok. Should we check that it's a positive integer?

--
Francis J. Lacoste
<email address hidden>

41. By Andrea Corbellini

Check the value given to @cache_for.

Revision history for this message
Andrea Corbellini (andrea.corbellini) wrote :

On Fri, 2009-06-05 at 13:15 +0000, Francis J. Lacoste wrote:
> Ok. Should we check that it's a positive integer?
Yes: numbers <= 0 should never be given.

I've added all the checks and fixed all the other problems. The branch
should be OK now. Do you need anything else?

Revision history for this message
Francis J. Lacoste (flacoste) wrote :

On Friday 05 June 2009, Andrea Corbellini wrote:
> On Fri, 2009-06-05 at 13:15 +0000, Francis J. Lacoste wrote:
> > Ok. Should we check that it's a positive integer?
>
> Yes: numbers <= 0 should never be given.
>
> I've added all the checks and fixed all the other problems. The branch
> should be OK now. Do you need anything else?

Nope, thanks, that was nice.

I merged it with the following small changes:

- Added cache_for to __all__
- Interpolate the value in the ValueError message when the value isn't
positive.

Thanks a lot for your contribution!

--
Francis J. Lacoste
<email address hidden>

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/lazr/restful/declarations.py'
2--- src/lazr/restful/declarations.py 2009-04-14 06:08:51 +0000
3+++ src/lazr/restful/declarations.py 2009-05-29 15:32:33 +0000
4@@ -591,6 +591,18 @@
5 annotations['return_type'] = ObjectLink(schema=self.interface)
6
7
8+class cache_for(_method_annotator):
9+ """Decorator specifying how much a response may be cached by a client."""
10+
11+ def __init__(self, duration):
12+ """Specify the duration, in seconds, of the resource."""
13+ self.duration = duration
14+
15+ def annotate_method(self, method, annotations):
16+ """See `_method_annotator`."""
17+ annotations['cache_for'] = self.duration
18+
19+
20 class export_read_operation(_export_operation):
21 """Decorator marking a method for export as a read operation."""
22 type = 'read_operation'
23@@ -829,6 +841,14 @@
24 def call(self, **kwargs):
25 """See `ResourceOperation`."""
26 params = self._getMethodParameters(kwargs)
27+
28+ # For responses to GET requests, tell the client to cache the response
29+ if (IResourceGETOperation.providedBy(self)
30+ and 'cache_for' in self._export_info):
31+ self.request.response.setHeader(
32+ 'Cache-control', 'max-age=%i'
33+ % self._export_info['cache_for'])
34+
35 result = getattr(self.context, self._method_name)(**params)
36 return self.encodeResult(result)
37
38
39=== modified file 'src/lazr/restful/docs/webservice-declarations.txt'
40--- src/lazr/restful/docs/webservice-declarations.txt 2009-04-17 17:59:18 +0000
41+++ src/lazr/restful/docs/webservice-declarations.txt 2009-05-29 15:39:08 +0000
42@@ -1406,6 +1406,43 @@
43 TypeError: A field can only have one mutator method; set_value_2
44 makes two.
45
46+Caching
47+=======
48+
49+It is possible to cache a server response in the browser cache using
50+the @cache_for decorator:
51+
52+ >>> from lazr.restful.declarations import cache_for
53+ >>> from lazr.restful.testing.webservice import FakeRequest
54+ >>>
55+ >>> class ICachedBookSet(IBookSet):
56+ ... """IBookSet supporting caching."""
57+ ... export_as_webservice_collection(IBook)
58+ ...
59+ ... @collection_default_content()
60+ ... @export_read_operation()
61+ ... @cache_for(60)
62+ ... def getAllBooks():
63+ ... """Return all books."""
64+ ...
65+ ...
66+ >>> class CachedBookSet(BookSet):
67+ ... """Simple ICachedBookSet implementation."""
68+ ... implements(IBookSet)
69+ ...
70+ ... def getAllBooks(self):
71+ ... return self.books
72+ >>>
73+ >>> request = FakeRequest()
74+ >>> read_method_adapter_factory = generate_operation_adapter(
75+ ... ICachedBookSet['getAllBooks'])
76+ >>> read_method_adapter = read_method_adapter_factory(
77+ ... CachedBookSet(['Cool book']), request)
78+ >>> print read_method_adapter.call()
79+ ["Cool book"]
80+ >>> print request.response.headers
81+ {'Content-Type': 'application/json', 'Cache-control': 'max-age=60'}
82+
83 Security
84 ========
85

Subscribers

People subscribed via source and target branches