Instance Decorators
===================

Memo decorators for instances.

Originally from Whit Morriss' memojito package, they are used to memoize return values for methods on an instance.
The memoized values are stored on an attribute on the instance and disappear when the instance is destroyed or a cleanup is called.

.. warning::
    The ``instance`` caching decorator writes to ZODB when filling the cache.
    This can lead to database conflict errors caused by write on read.

Let's try it out w/ a dummy class::

    >>> from plone.memoize import instance
    >>> class MyMsg(object):
    ...     bang='!'
    ...
    ...     @property
    ...     @instance.memoize
    ...     def txt2(self):
    ...         #extreme intense calculation
    ...         return '%s world' %self.txt1
    ...
    ...     @instance.memoize
    ...     def getMsg(self, to, **instruction):
    ...         lst = ['%s--%s' %t for t in sorted(instruction.items(), reverse=True)]
    ...         instxt = ' '.join(lst)
    ...         return ("%s: %s%s %s" %(to, self.txt2, self.bang, instxt)).strip()
    ...
    ...     @instance.memoizedproperty
    ...     def recurse(self):
    ...         return "recursive: %s" % self.txt2
    ...
    ...     @instance.clearbefore
    ...     def clearbefore(self):
    ...         return self.txt2
    ...
    ...     @instance.clearafter
    ...     def clearafter(self):
    ...         return self.txt2
    ...
    ...     def __init__(self, txt1):
    ...         self.txt1 = txt1

    >>> msg = MyMsg('hello')
    >>> msg.txt2
    'hello world'

    >>> msg.txt1 = 'nice to visit this'

Even though we've twiddled txt1, txt2 is not recalculated::

    >>> msg.txt2
    'hello world'

The memo is stored by a key made of the method's name, args, and a frozenset of any kwargs.
If those are expected to be big, you should compute your own hash of it::

    >>> key = ('txt2', (msg,), frozenset([]))
    >>> msg._memojito_[key]
    'hello world'

The clear after decorator will clear the memos after returning the methods value::

    >>> msg.clearafter()
    'hello world'

So now the message should have changed::

    >>> msg.txt2
    'nice to visit this world'

We change the text again::

    >>> msg.txt1 = 'goodbye cruel'

The message is still the same of course::

    >>> msg.txt2
    'nice to visit this world'

Now we can test the clear before, which does the opposite from the clear after, allowing new values to be calculated::

    >>> msg.clearbefore()
    'goodbye cruel world'

memojito supports memoization of multiple signatures as long as all signature values are hashable::

    >>> print(msg.getMsg('Ernest'))
    Ernest: goodbye cruel world!

    >>> print(msg.getMsg('J.D.', **{'raise':'roofbeams'}))
    J.D.: goodbye cruel world! raise--roofbeams

We can alter data underneath, but nothing changes::

    >>> msg.txt1 = 'sound and fury'
    >>> print(msg.getMsg('J.D.', **{'raise':'roofbeams'}))
    J.D.: goodbye cruel world! raise--roofbeams

    >>> print(msg.getMsg('Ernest'))
    Ernest: goodbye cruel world!

If we alter the signature, our msg is recalculated, but since mst.txt2 is a memo, only the values passed in change::

    >>> from collections import OrderedDict
    >>> ins = OrderedDict([('tale', 'told by idiot'), ('signify', 'nothing')])
    >>> print(msg.getMsg('Bill F.', **ins))
    Bill F.: goodbye cruel world! tale--told by idiot signify--nothing

    >>> print(msg.getMsg('J.D.', **{'catcher':'rye'}))
    J.D.: goodbye cruel world! catcher--rye

If change the bang, the memo remains the same::

    >>> msg.bang='#!'
    >>> print(msg.getMsg('J.D.', **{'catcher':'rye'}))
    J.D.: goodbye cruel world! catcher--rye

    >>> print(msg.getMsg('Ernest'))
    Ernest: goodbye cruel world!

clearing works the same as for properties::

    >>> print(msg.clearafter())
    goodbye cruel world

Our shebang appears::

    >>> print(msg.getMsg('Ernest'))
    Ernest: sound and fury world#!

Our message to faulkner now is semantically correct::

    >>> ins = OrderedDict([('tale', 'told by idiot'), ('signify', 'nothing')])
    >>> print(msg.getMsg('Bill F.', **ins))
    Bill F.: sound and fury world#! tale--told by idiot signify--nothing

Let's make sure that memoized properties which call OTHER memoized properties do the right thing::

    >>> msg = MyMsg('hello')
    >>> print(msg.recurse)
    recursive: hello world

Now we make sure that both the txt2 and the recurse values are in the cache::

    >>> print(len(msg._memojito_.keys()))
    2

