Merge lp:~leonardr/lazr.restful/promote-write-operation-to-mutator into lp:lazr.restful

Proposed by Leonard Richardson
Status: Merged
Approved by: Данило Шеган
Approved revision: 174
Merged at revision: 172
Proposed branch: lp:~leonardr/lazr.restful/promote-write-operation-to-mutator
Merge into: lp:lazr.restful
Diff against target: 231 lines (+102/-25)
4 files modified
src/lazr/restful/NEWS.txt (+11/-5)
src/lazr/restful/docs/webservice-declarations.txt (+65/-5)
src/lazr/restful/metazcml.py (+25/-14)
src/lazr/restful/version.txt (+1/-1)
To merge this branch: bzr merge lp:~leonardr/lazr.restful/promote-write-operation-to-mutator
Reviewer Review Type Date Requested Status
Данило Шеган (community) Approve
Review via email: mp+49976@code.launchpad.net

Description of the change

Let me explain this in terms of the class I used to test it.

    >>> class IOperationPromotedToMutator(Interface):
    ... export_as_webservice_entry()
    ...
    ... field = exported(TextLine(readonly=True))
    ...
    ... @mutator_for(field)
    ... @operation_for_version('3.0')
    ... @operation_parameters(text=TextLine())
    ... @export_write_operation()
    ... @operation_for_version('1.0')
    ... def set_value(text):
    ... pass

The example web service has four versions: beta, 1.0, 2.0, and 3.0. In 'beta', mutator methods were also published as named operations (just like in Launchpad 'beta'), but that behavior stopped in 1.0. Call 1.0 the 'cutoff version'.

The 'set_value' method is introduced in 1.0, and it's a normal write operation in 2.0, but in 3.0 it's promoted to a mutator method.

Here's the problem: this method was not available in 1.0 or 2.0. lazr.restful saw that set_value was defined as a mutator, and added a mask registration to stop it from showing up as of the 'cutoff version', 1.0. This would have been the correct behavior if set_value was defined as a mutator *before* the cutoff version, but actually it was defined as a mutator *after* the cutoff version.

lazr.restful was masking the write operation registration in the cutoff version if the method was defined as a mutator in *any* version. This branch changes it so that the operation is only masked in the cutoff version if it was defined as a mutator *before* the cutoff version. If a method is promoted to a mutator after the cutoff version, a lookup for that method will fail starting in the promotion version.

This branch also changes the versioned request marker interfaces in webservice-declarations.txt so that version n+1 is a subclass of version n. This more closely reproduces the behavior of a real lazr.restful web service, and lets me do a more realistic test.

To post a comment you must log in.
Revision history for this message
Данило Шеган (danilo) wrote :

FTR:

<danilos> leonardr, I assume there are tests for mutators working correctly otherwise? (since your test only "proves" that it stops providing a method)
<leonardr> danilos: yes, there are. a mutator is just a write operation that doesn't show up in lookups
<leonardr> i could add a test that the mutator works in 3.0
<danilos> leonardr, right, thanks
 leonardr, if there are already tests for that elsewhere, no need to imho
 leonardr, just one more question: is it still possible to keep a method and make it a mutator? (i.e. have set_value keep working in 3.0)
<leonardr> danilos: no, because there's no point in having a named operation that duplicates the effects of a mutator
<leonardr> the situation we have here is where we made a mistake--this should have been a mutator method all along
 but we can't change an old version for backwards compatibility reasons
<danilos> leonardr, well, I can imagine someone wanting to keep both so you don't have to port client code, no?
 leonardr, maybe that'd be hidden by things like launchpadlib instead, so I am just wondering
<leonardr> danilos: this falls into the category of 'if you don't want to port code, don't use the new version'
<leonardr> it's an easy change to make
<danilos> leonardr, ok, as long as it's recognized as being a use-case (or not), I think it's fine; some explicit documentation might be useful, if you agree
 leonardr, anyway, with that considered, r=me

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'src/lazr/restful/NEWS.txt'
--- src/lazr/restful/NEWS.txt 2011-02-02 17:55:01 +0000
+++ src/lazr/restful/NEWS.txt 2011-02-16 14:39:11 +0000
@@ -2,9 +2,15 @@
2NEWS for lazr.restful2NEWS for lazr.restful
3=====================3=====================
44
50.16.0 (Unreleased)50.16.1 (2011-02-16)
6===================6===================
77
8Fixed a bug that prevented a write operation from being promoted to a
9mutator operation.
10
110.16.0 (No official release)
12============================
13
8If each entry in the web service corresponds to some object on a14If each entry in the web service corresponds to some object on a
9website, and there's a way of converting a web service request into a15website, and there's a way of converting a web service request into a
10website request, the web service will now provide website links for16website request, the web service will now provide website links for
@@ -16,13 +22,13 @@
16Validation errors for named operations will be properly sent to the22Validation errors for named operations will be properly sent to the
17client even if they contain Unicode characters. (Launchpad bug 619180.)23client even if they contain Unicode characters. (Launchpad bug 619180.)
1824
190.15.4 (2010-01-26)250.15.4 (2011-01-26)
20===================26===================
2127
22Fixed inconsistent handling of custom HTML field renderings. An28Fixed inconsistent handling of custom HTML field renderings. An
23IFieldHTMLRenderer can now return either Unicode or UTF-8.29IFieldHTMLRenderer can now return either Unicode or UTF-8.
2430
250.15.3 (2010-01-21)310.15.3 (2011-01-21)
26===================32===================
2733
28lazr.restful will now complain if you try to export an IObject, as34lazr.restful will now complain if you try to export an IObject, as
@@ -32,7 +38,7 @@
32use IObject.38use IObject.
3339
3440
350.15.2 (2010-01-20)410.15.2 (2011-01-20)
36===================42===================
3743
38lazr.restful gives a more helpful error message when a published44lazr.restful gives a more helpful error message when a published
@@ -41,7 +47,7 @@
4147
42lazr.restful's tests now pass in Python 2.7. (Launchpad bug 691841)48lazr.restful's tests now pass in Python 2.7. (Launchpad bug 691841)
4349
440.15.1 (2010-01-19)500.15.1 (2011-01-19)
45===================51===================
4652
47Fixed a redirect bug when a web browser requests a representation53Fixed a redirect bug when a web browser requests a representation
4854
=== modified file 'src/lazr/restful/docs/webservice-declarations.txt'
--- src/lazr/restful/docs/webservice-declarations.txt 2011-01-24 22:00:56 +0000
+++ src/lazr/restful/docs/webservice-declarations.txt 2011-02-16 14:39:11 +0000
@@ -989,15 +989,19 @@
989need a marker interface for every version, registered as a utility989need a marker interface for every version, registered as a utility
990under the name of the version.990under the name of the version.
991991
992Each version interface subclasses the previous version's
993interface. This lets a request use a resource definition for the
994previous version if it hasn't changed since then.
995
992 >>> from zope.component import getSiteManager996 >>> from zope.component import getSiteManager
993 >>> from lazr.restful.interfaces import IWebServiceVersion997 >>> from lazr.restful.interfaces import IWebServiceVersion
994 >>> class ITestServiceRequestBeta(IWebServiceVersion):998 >>> class ITestServiceRequestBeta(IWebServiceVersion):
995 ... pass999 ... pass
996 >>> class ITestServiceRequest10(IWebServiceVersion):1000 >>> class ITestServiceRequest10(ITestServiceRequestBeta):
997 ... pass1001 ... pass
998 >>> class ITestServiceRequest20(IWebServiceVersion):1002 >>> class ITestServiceRequest20(ITestServiceRequest10):
999 ... pass1003 ... pass
1000 >>> class ITestServiceRequest30(IWebServiceVersion):1004 >>> class ITestServiceRequest30(ITestServiceRequest20):
1001 ... pass1005 ... pass
1002 >>> sm = getSiteManager()1006 >>> sm = getSiteManager()
1003 >>> for marker, name in [(ITestServiceRequestBeta, 'beta'),1007 >>> for marker, name in [(ITestServiceRequestBeta, 'beta'),
@@ -2804,6 +2808,62 @@
2804Edge cases2808Edge cases
2805**********2809**********
28062810
2811You can promote a named operation to a mutator operation
2812^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
2813
2814Here's a named operation that was defined in '1.0' and promoted to a
2815mutator in '3.0'.
2816
2817 >>> class IOperationPromotedToMutator(Interface):
2818 ... export_as_webservice_entry()
2819 ...
2820 ... field = exported(TextLine(readonly=True))
2821 ...
2822 ... @mutator_for(field)
2823 ... @operation_for_version('3.0')
2824 ... @operation_parameters(text=TextLine())
2825 ... @export_write_operation()
2826 ... @operation_for_version('1.0')
2827 ... def set_value(text):
2828 ... pass
2829
2830 >>> class OperationPromotedToMutator:
2831 ... implements(IOperationPromotedToMutator)
2832
2833 >>> module = register_test_module(
2834 ... 'mutatorpromotion', IOperationPromotedToMutator,
2835 ... OperationPromotedToMutator)
2836
2837 >>> context = OperationPromotedToMutator()
2838
2839The operation is not available in 'beta', because it hasn't been
2840defined yet.
2841
2842 >>> print operation_for(context, 'beta', 'set_value').__class__.__name__
2843 Traceback (most recent call last):
2844 ...
2845 ComponentLookupError: ...
2846
2847The operation is available in both '1.0', and '2.0', even though
2848mutator operations aren't published as named operations after
28491.0. This is because the operation doesn't become a mutator operation
2850until 3.0.
2851
2852 >>> print operation_for(context, '1.0', 'set_value').__class__.__name__
2853 POST_IOperationPromotedToMutator_set_value_1_0
2854
2855 >>> print operation_for(context, '2.0', 'set_value').__class__.__name__
2856 POST_IOperationPromotedToMutator_set_value_1_0
2857
2858The operation is not available in 3.0, the version in which it becomes
2859a mutator.
2860
2861 >>> operation_for(context, '3.0', 'set_value')
2862 Traceback (most recent call last):
2863 ...
2864 ComponentLookupError: ...
2865
2866
2807You can't immediately reinstate a mutator operation as a named operation2867You can't immediately reinstate a mutator operation as a named operation
2808^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^2868^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
28092869
28102870
=== modified file 'src/lazr/restful/metazcml.py'
--- src/lazr/restful/metazcml.py 2011-01-12 05:30:51 +0000
+++ src/lazr/restful/metazcml.py 2011-02-16 14:39:11 +0000
@@ -336,8 +336,15 @@
336 # version.336 # version.
337 previous_operation_name = None337 previous_operation_name = None
338 previous_operation_provides = None338 previous_operation_provides = None
339 operation_registered_as_mutator = False339 mutator_operation_needs_to_be_blocked = False
340 master_stack = tag
340 for version, tag in tag.stack:341 for version, tag in tag.stack:
342 if version is None:
343 this_version_index = 0
344 else:
345 this_version_index = config.active_versions.index(
346 version)
347
341 if tag['type'] == REMOVED_OPERATION_TYPE:348 if tag['type'] == REMOVED_OPERATION_TYPE:
342 # This operation is not present in this version.349 # This operation is not present in this version.
343 # We'll represent this by setting the operation_name350 # We'll represent this by setting the operation_name
@@ -389,15 +396,9 @@
389 if tag['type'] in ['destructor']:396 if tag['type'] in ['destructor']:
390 operation_name = ''397 operation_name = ''
391398
392 if version is None:
393 this_version_index = 0
394 else:
395 this_version_index = config.active_versions.index(
396 version)
397 if (tag.get('is_mutator', False)399 if (tag.get('is_mutator', False)
398 and (no_mutator_operations_after is None400 and (no_mutator_operations_after is None
399 or no_mutator_operations_after < this_version_index)):401 or no_mutator_operations_after < this_version_index)):
400
401 # This is a mutator method, and in this version,402 # This is a mutator method, and in this version,
402 # mutator methods are not published as named403 # mutator methods are not published as named
403 # operations at all. Block any lookup of the named404 # operations at all. Block any lookup of the named
@@ -406,7 +407,7 @@
406 # This will save us from having to do another407 # This will save us from having to do another
407 # de-registration later.408 # de-registration later.
408 factory = _mask_adapter_registration409 factory = _mask_adapter_registration
409 operation_registered_as_mutator = False410 mutator_operation_needs_to_be_blocked = False
410 else:411 else:
411 factory = generate_operation_adapter(method, version)412 factory = generate_operation_adapter(method, version)
412413
@@ -428,15 +429,25 @@
428 register_adapter_for_version(429 register_adapter_for_version(
429 factory, interface, version, operation_provides,430 factory, interface, version, operation_provides,
430 operation_name, context.info)431 operation_name, context.info)
431 if tag.get('is_mutator'):432 if (tag.get('is_mutator')
432 operation_registered_as_mutator = True433 and block_mutator_operations_as_of_version != None):
434 defined_in = this_version_index
435 blocked_as_of = config.active_versions.index(
436 block_mutator_operations_as_of_version)
437 if defined_in < blocked_as_of:
438 # In this version, this mutator is also a
439 # named operation. But in a later version
440 # mutators stop being named operations, and
441 # the operation registration for this mutator
442 # will need to be blocked.
443 mutator_operation_needs_to_be_blocked = True
433 previous_operation_name = operation_name444 previous_operation_name = operation_name
434 previous_operation_provides = operation_provides445 previous_operation_provides = operation_provides
435446
436 if (operation_registered_as_mutator and447 if mutator_operation_needs_to_be_blocked:
437 block_mutator_operations_as_of_version is not None):448 # The operation was registered as a mutator, back in the
438 # The operation was registered as a mutator, and it never449 # days when mutator operations were also named operations,
439 # got de-registered. De-register it now.450 # and it never got de-registered. De-register it now.
440 register_adapter_for_version(451 register_adapter_for_version(
441 _mask_adapter_registration, interface,452 _mask_adapter_registration, interface,
442 block_mutator_operations_as_of_version,453 block_mutator_operations_as_of_version,
443454
=== modified file 'src/lazr/restful/version.txt'
--- src/lazr/restful/version.txt 2011-01-26 16:50:13 +0000
+++ src/lazr/restful/version.txt 2011-02-16 14:39:11 +0000
@@ -1,1 +1,1 @@
10.16.010.16.1

Subscribers

People subscribed via source and target branches