6

I'm trying to use the property decorator in a Class. While it works well per se, I can't use any code that has to access the REQUEST.

class SomeClass():
   #Zope magic code
   _properties=({'id':'someValue', 'type':'ustring', 'mode':'r'},)

  def get_someValue(self):
    return self.REQUEST

  @property
  def someValue(self):
    return self.REQUEST

Although calling get_someValue gets me the desired result, trying to access someValue raises an AttributeError.

What's the logic behind this behaviour? Is there a way to get around this limitation?

(I'm using Zope 2.13.16, Python 2.7.3)

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
Georg Pfolz
  • 1,416
  • 2
  • 12
  • 12

2 Answers2

8

The property decorator only works with new-style classes; that is to say, classes that inherit from object. Acquisition (which gives you access to the global REQUEST object via attribute access) on the other hand is very much 'old-skool' python and the two do not work well together, as property ignores acquisition wrappers, which are needed to acquire the REQUEST object.

Zope has it's own property-like method that pre-dates new-style classes and the property decorater, called ComputedAttribute, which actually predates the property decorator and new-style classes by many years. A ComputedAttribute-wrapped function does know how to behave with an Acquisition-wrapped object, though.

You can use ComputedAttibute much like the property decorator:

from ComputedAttribute import ComputedAttribute

class SomeClass():   
    @ComputedAttribute
    def someProperty(self):
        return 'somevalue'

The ComputedAttribute wrapper function also can be configured with a level of wrapping, which is what we need when dealing with Acquisition wrappers. You cannot use the ComputedAttribute as a decorator in that case:

class SomeClass():   
    def someValue(self):
        return self.REQUEST
    someValue = ComputedAttribute(someValue, 1)

It is easy enough to define a new function to do the decorating for us though:

from ComputedAttribute import ComputedAttribute

def computed_attribute_decorator(level=0):
    def computed_attribute_wrapper(func):
        return ComputedAttribute(func, level)
    return computed_attribute_wrapper

Stick this in a utility module somewhere, after which you can then use it as a callable decorator to mark something as an Acquisition-aware property:

class SomeClass(): 
    @computed_attribute_decorator(level=1)
    def someValue(self):
        return self.REQUEST

Note that unlike property, ComputedAttribute can only be used for getters; there is no support for setters or deleters.

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • 1
    Thank you so much! I'm using Zope for a couple of years already and never stumbled over the ComputedAttribute method. – Georg Pfolz Sep 22 '12 at 17:26
  • This seems to work for me in one case (Plone/Dexterity content item), but for purposes of clarification: are there any known side-effects of using ComputedAttribute-based decorator like this on new-style classes? What base classes must a class extend to use ComputedAttribute (for example, in a browser view participating in acquisition)? – sdupton Jan 11 '14 at 15:15
  • 1
    You only can use ComputedAttribute on classes derived from`ExtensionClass` (which includes the `Acquisition.Explicit` and `Acquisition.Implicit` classes). Other than that there are no other requirements. – Martijn Pieters Jan 11 '14 at 16:13
  • 1
    I know of no special considerations for new-style classes otherwise. – Martijn Pieters Jan 11 '14 at 16:16
  • It is worth noting that the wrapping level one uses for ComputedAttribute might need to change for ExtensionClass 4.2.1+. In order to use a decorator in the manner described above, I had to change level=1 to level=0. This may be relevant especially to Plone users using ComputedAttribute in their add-ons, when upgrading to Plone 5.1+. – sdupton Aug 02 '17 at 20:34
3

If you want to route around needing acquisition and cannot explicitly set the request from calling code in the constructor of your class, use zope.globalrequest. Otherwise, you may want to consider a browser view (which always multi-adapts some context and a request).

sdupton
  • 1,869
  • 10
  • 9
  • Thanks for pointing me to zope.globalrequest. I don't have the package installed, so I'll give it a try if I ever need to get around acquisition. At the moment I'm perfectly happy with the answer provided by Martijn Pieters. What do you mean by "considering a browser view"? – Georg Pfolz Sep 22 '12 at 23:46