plainbox.impl.censoREd – working around frustrating stuff

This module is the result of an evening of frustration caused by the need to support Python 3.2 and a failing doctest that exercises, unintentionally, the behavior of the compiled regular expression object’s __repr__() method. That should be something we can fix, right? Let’s not get crazy here:

>>> import re
>>> sre_cls = type(re.compile(""))
>>> sre_cls
<class '_sre.SRE_Pattern'>

Aha, we have a nice type. It’s only got a broken __repr__ method that sucks. But this is Python, we can fix that? Right?

>>> sre_cls.__repr__ = (
...     lambda self: "re.compile({!r})".format(self.pattern))
... 
Traceback (most recent call last):
...
TypeError: can't set attributes of built-in/extension
           type '_sre.SRE_Pattern'

Hmm, okay, so let’s try something else:

>>> class Pattern(sre_cls):
...     def __repr__(self):
...         return "re.compile({!r})".format(self.pattern)
Traceback (most recent call last):
...
TypeError: type '_sre.SRE_Pattern' is not an acceptable base type

Sigh, denial, anger, bargaining, depression, acceptance https://twitter.com/zygoon/status/560088469192843264

The last resort, aka, the proxy approach. Let’s use a bit of magic to work around the problem. This way we won’t have to subclass or override anything.

class plainbox.impl.censoREd.PatternProxy(proxy_obj, proxiee)[source]

Bases: padme.proxy

A proxy that overrides the __repr__() to match what Python 3.3+ providers on the internal object representing a compiled regular expression.

>>> import re
>>> sre_cls = type(re.compile(""))
>>> pattern = PatternProxy(re.compile("profanity"))

Can we have a repr() like in Python3.4 please?

>>> pattern
re.compile('profanity')

Does it still work like a normal pattern object?

>>> pattern.match("profanity") is not None
True
>>> pattern.match("love") is not None
False

Yes (gets another drink).

direct(fn)

Mark a method as not-to-be-proxied.

This decorator can be used inside proxy sub-classes. Please consult the documentation of proxy for details.

In practical terms there are two reasons one can use proxy.direct.

  • First, as a way to change the behaviour of a proxy. In this mode a method that already exists on the proxied object is intercepted and custom code is executed. The custom code can still call the original, if desired, by using the proxy.original() function to access the original object
  • Second, as a way to introduce new functionality to an object. In that sense the resulting proxy will be less transparent as all proxy.direct methods are explicitly visible and available to access but this may be exactly what is desired in some situations.

For additional details on how to use this decorator, see the documentation of the padme module.

original(proxy_obj)

Return the proxiee hidden behind the given proxy.

Parameters:proxy – An instance of proxy or its subclass.
Returns:The original object that the proxy is hiding.

This function can be used to access the object hidden behind a proxy. This is useful when access to original object is necessary, for example, to implement an method decorated with @proxy.direct.

In the following example, we cannot use super() to get access to the append method because the proxy does not really subclass the list object. To override the append method in a way that allows us to still call the original we must use the proxy.original() function:

>>> class verbose_list(proxy):
...     @proxy.direct
...     def append(self, item):
...         print("Appending:", item)
...         proxy.original(self).append(item)

Now that we have a verbose_list class, we can use it to see that it works as expected:

>>> l = verbose_list([])
>>> l.append(42)
Appending: 42
>>> l
[42]
state(proxy_obj)

Support function for accessing the state of a proxy object.

The main reason for this function to exist is to facilitate creating stateful proxy objects. This allows you to put state on objects that cannot otherwise hold it (typically built-in classes or classes using __slots__) and to keep the state invisible to the original object so that it cannot interfere with any future APIs.

To use it, just call it on any proxy object and use the return value as a normal object you can get/set attributes on. For example:

>>> life = proxy(42)

We cannot set attributes on integer instances:

>>> life.foo = True
Traceback (most recent call last):
    ...
AttributeError: 'int' object has no attribute 'foo'

But we can do that with a proxy around the integer object.

>>> proxy.state(life).foo = True
>>> proxy.state(life).foo
True
comments powered by Disqus