Merge lp:~leonardr/lazr.restful/use-bleedthrough-for-methods into lp:lazr.restful

Proposed by Leonard Richardson
Status: Merged
Merged at revision: not available
Proposed branch: lp:~leonardr/lazr.restful/use-bleedthrough-for-methods
Merge into: lp:lazr.restful
Diff against target: 244 lines (+132/-10)
4 files modified
src/lazr/restful/declarations.py (+36/-2)
src/lazr/restful/docs/utils.txt (+3/-0)
src/lazr/restful/docs/webservice-declarations.txt (+82/-4)
src/lazr/restful/utils.py (+11/-4)
To merge this branch: bzr merge lp:~leonardr/lazr.restful/use-bleedthrough-for-methods
Reviewer Review Type Date Requested Status
Brad Crittenden (community) code Approve
Review via email: mp+17683@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Leonard Richardson (leonardr) wrote :

This branch creates the 'operation_for_version' annotation, used to publish a named operation in different ways for different versions of the web service. Currently 'operation_for_version' lets you define different versions of the methods, but it doesn't really matter, because the bleed-through stack means that only the most recent version is visible.

I made some changes to BleedThroughDict, implementing additional bits of the dict interface, so that the code in declarations.py could treat a BTD like a normal dict.

Not every annotation works with operation_for_version. For instance, the rename_parameters_as annotation directly modifies an object in the bleed-through stack, instead of putting new values in there. This means that using rename_parameters_as will modify every version of the annotation at once. In subsequent branches I'll be changing the BleedThroughDict data structure to work with this kind of annotation.

Revision history for this message
Brad Crittenden (bac) wrote :

Very nice branch Leonard. On IRC I suggested adding a comment about the use of None on the stack and you proposed a nice solution. With that change it looks great.

review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'src/lazr/restful/declarations.py'
--- src/lazr/restful/declarations.py 2010-01-11 18:27:43 +0000
+++ src/lazr/restful/declarations.py 2010-01-19 20:04:14 +0000
@@ -27,6 +27,7 @@
27 'generate_entry_interface',27 'generate_entry_interface',
28 'generate_operation_adapter',28 'generate_operation_adapter',
29 'mutator_for',29 'mutator_for',
30 'operation_for_version',
30 'operation_parameters',31 'operation_parameters',
31 'operation_returns_entry',32 'operation_returns_entry',
32 'operation_returns_collection_of',33 'operation_returns_collection_of',
@@ -56,7 +57,9 @@
56from lazr.restful import (57from lazr.restful import (
57 Collection, Entry, EntryAdapterUtility, ResourceOperation, ObjectLink)58 Collection, Entry, EntryAdapterUtility, ResourceOperation, ObjectLink)
58from lazr.restful.security import protect_schema59from lazr.restful.security import protect_schema
59from lazr.restful.utils import camelcase_to_underscore_separated, get_current_browser_request60from lazr.restful.utils import (
61 BleedThroughDict, camelcase_to_underscore_separated,
62 get_current_browser_request)
6063
61LAZR_WEBSERVICE_EXPORTED = '%s.exported' % LAZR_WEBSERVICE_NS64LAZR_WEBSERVICE_EXPORTED = '%s.exported' % LAZR_WEBSERVICE_NS
62COLLECTION_TYPE = 'collection'65COLLECTION_TYPE = 'collection'
@@ -294,7 +297,17 @@
294 """Annotates the function with the fixed arguments."""297 """Annotates the function with the fixed arguments."""
295 # Everything in the function dictionary ends up as tagged value298 # Everything in the function dictionary ends up as tagged value
296 # in the interface method specification.299 # in the interface method specification.
297 annotations = method.__dict__.setdefault(LAZR_WEBSERVICE_EXPORTED, {})300 annotations = method.__dict__.get(LAZR_WEBSERVICE_EXPORTED, None)
301 if annotations is None:
302 # Create a new bleed-through dict which associates
303 # annotation data with the earliest active version of the
304 # web service. Future @webservice_version annotations will
305 # push later versions onto the BleedThroughDict, allowing
306 # new versions to specify annotation data that conflicts
307 # with old versions.
308 annotations = BleedThroughDict()
309 annotations.push(None)
310 method.__dict__[LAZR_WEBSERVICE_EXPORTED] = annotations
298 self.annotate_method(method, annotations)311 self.annotate_method(method, annotations)
299 return method312 return method
300313
@@ -442,6 +455,27 @@
442 set(annotations.get('call_with', {}).keys()))455 set(annotations.get('call_with', {}).keys()))
443456
444457
458class operation_for_version(_method_annotator):
459 """Decorator specifying which version of the webservice is defined.
460
461 Decorators processed after this one will decorate the given web
462 service version and, by default, subsequent versions will inherit
463 their values. Subsequent versions may provide conflicting values,
464 but those values will not affect this version.
465 """
466 def __init__(self, version):
467 _check_called_from_interface_def('%s()' % self.__class__.__name__)
468 self.version = version
469
470 def annotate_method(self, method, annotations):
471 """See `_method_annotator`."""
472 # The annotations dict is a BleedThroughDict. Push a new dict
473 # onto its stack, labeled with the version number, so that
474 # future annotations can override old annotations without
475 # destroying them.
476 annotations.push(self.version)
477
478
445class export_operation_as(_method_annotator):479class export_operation_as(_method_annotator):
446 """Decorator specifying the name to export the method as."""480 """Decorator specifying the name to export the method as."""
447481
448482
=== modified file 'src/lazr/restful/docs/utils.txt'
--- src/lazr/restful/docs/utils.txt 2010-01-14 10:00:38 +0000
+++ src/lazr/restful/docs/utils.txt 2010-01-19 20:04:14 +0000
@@ -57,6 +57,9 @@
57 >>> print stack.get('key', 'default')57 >>> print stack.get('key', 'default')
58 value58 value
5959
60 >>> 'key' in stack
61 True
62
60 >>> sorted(stack.items())63 >>> sorted(stack.items())
61 [('key', 'value')]64 [('key', 'value')]
6265
6366
=== modified file 'src/lazr/restful/docs/webservice-declarations.txt'
--- src/lazr/restful/docs/webservice-declarations.txt 2010-01-11 18:27:43 +0000
+++ src/lazr/restful/docs/webservice-declarations.txt 2010-01-19 20:04:14 +0000
@@ -267,11 +267,12 @@
267Exporting methods267Exporting methods
268=================268=================
269269
270Entries and collections can support operations on the webservice. The270Entries and collections can publish named operations on the
271operations supported are defined by tagging methods in the content271webservice. Every named operation corresponds to some method defined
272interface with special decorators.272on the content interface. To publish a method as a named operation,
273you tag it with special decorators.
273274
274Three different decorators are used based on the kind of method275Four different decorators are used based on the kind of method
275exported.276exported.
276277
2771. @export_read_operation2781. @export_read_operation
@@ -295,6 +296,11 @@
295 creating and the name of the fields in the schema that are passed as296 creating and the name of the fields in the schema that are passed as
296 parameters.297 parameters.
297298
2994. @export_destructor_operation
300
301 This will mark the method as available as a DELETE operation on the
302 exported resource.
303
298The specification of the web service's acceptable method parameters304The specification of the web service's acceptable method parameters
299should be described using the @operation_parameters decorator, which305should be described using the @operation_parameters decorator, which
300takes normal IField instances.306takes normal IField instances.
@@ -444,6 +450,9 @@
444 return_type: <lazr.restful._operation.ObjectLink object...>450 return_type: <lazr.restful._operation.ObjectLink object...>
445 type: 'factory'451 type: 'factory'
446452
453Default values and required parameters
454--------------------------------------
455
447Parameters default and required attributes are set automatically based456Parameters default and required attributes are set automatically based
448on the method signature.457on the method signature.
449458
@@ -494,6 +503,75 @@
494 >>> param_defs['optional2'].default503 >>> param_defs['optional2'].default
495 u'Default2'504 u'Default2'
496505
506Versioning
507----------
508
509Different versions of the webservice can publish the same interface
510method in totally different ways. Here's a simple example. This method
511appears differently in three versions of the web service: 2.0, 1.0,
512and in an unnamed pre-1.0 version.
513
514 >>> from lazr.restful.declarations import operation_for_version
515 >>> class MultiVersionMethod(Interface):
516 ... export_as_webservice_entry()
517 ...
518 ... @call_with(fixed='2.0 value')
519 ... @operation_for_version('2.0')
520 ...
521 ... @call_with(fixed='1.0 value')
522 ... @export_operation_as('new_name')
523 ... @operation_for_version('1.0')
524 ...
525 ... @call_with(fixed='pre-1.0 value')
526 ... @operation_parameters(
527 ... required=TextLine(),
528 ... fixed=TextLine()
529 ... )
530 ... @export_read_operation()
531 ... def a_method(required, fixed='Fixed value'):
532 ... """Method demonstrating multiversion publication."""
533
534The tagged value containing the annotations looks like a dictionary,
535but it's actually a stack of dictionaries named after the versions.
536
537 >>> dictionary = MultiVersionMethod['a_method'].getTaggedValue(
538 ... 'lazr.restful.exported')
539 >>> dictionary.dict_names
540 [None, '1.0', '2.0']
541
542The dictionary on top of the stack is for the 2.0 version of the web
543service. In 2.0, the method is published as 'new_name' and its 'fixed'
544argument is fixed to the string '2.0 value'.
545
546 >>> print dictionary['as']
547 new_name
548 >>> dictionary['call_with']
549 {'fixed': '2.0 value'}
550
551Let's pop the 2.0 version off the stack. Now we can see how the method
552looks in 1.0. It's still called 'new_name', but its 'fixed' argument
553is fixed to the string '1.0 value'.
554
555 >>> ignored = dictionary.pop()
556 >>> print dictionary['as']
557 new_name
558 >>> dictionary['call_with']
559 {'fixed': '1.0 value'}
560
561Let's pop one more time to see how the method looks in the pre-1.0
562version. It hasn't yet been renamed to 'new_name', and its 'fixed'
563argument is fixed to the string 'pre-1.0 value'.
564
565 >>> ignored = dictionary.pop()
566 >>> print dictionary.get('as')
567 None
568 >>> dictionary['call_with']
569 {'fixed': 'pre-1.0 value'}
570 >>> print dictionary.items()
571
572Error handling
573--------------
574
497All these decorators can only be used from within an interface575All these decorators can only be used from within an interface
498definition:576definition:
499577
500578
=== modified file 'src/lazr/restful/utils.py'
--- src/lazr/restful/utils.py 2010-01-14 17:08:20 +0000
+++ src/lazr/restful/utils.py 2010-01-19 20:04:14 +0000
@@ -41,9 +41,11 @@
41 """41 """
42 missing = object()42 missing = object()
4343
44 def __init__(self):44 def __init__(self, name=None, dictionary=None, opaque=False):
45 """Initialize the bleed-through dictionary."""45 """Initialize the bleed-through dictionary."""
46 self.stack = []46 self.stack = []
47 if name is not None and dictionary is not None:
48 self.push(name, dictionary, opaque)
4749
48 def push(self, name, dictionary=None, opaque=False):50 def push(self, name, dictionary=None, opaque=False):
49 """Pushes a dictionary onto the stack.51 """Pushes a dictionary onto the stack.
@@ -53,9 +55,6 @@
53 :arg opaque: If True, values will not 'bleed through' from55 :arg opaque: If True, values will not 'bleed through' from
54 dictionaries lower on the stack.56 dictionaries lower on the stack.
55 """57 """
56 if name is None:
57 self.stack.append(name)
58 return
59 if dictionary is None:58 if dictionary is None:
60 dictionary = {}59 dictionary = {}
61 self.stack.append((name, dict(dictionary), opaque))60 self.stack.append((name, dict(dictionary), opaque))
@@ -83,6 +82,14 @@
83 """Is the stack empty?"""82 """Is the stack empty?"""
84 return len(self.stack) == 083 return len(self.stack) == 0
8584
85 def setdefault(self, key, value):
86 """Get a from the top of the stack, setting it if not present."""
87 return self.stack[0][1].setdefault(key, value)
88
89 def __contains__(self, key):
90 """Check whether a key is visible in the stack."""
91 return self.get(key, missing) is not missing
92
86 def __getitem__(self, key):93 def __getitem__(self, key):
87 """Look up an item somewhere in the stack."""94 """Look up an item somewhere in the stack."""
88 if self.is_empty:95 if self.is_empty:

Subscribers

People subscribed via source and target branches