Skip to content

Commit

Permalink
Fix plone.memoize.view to support unhashable types in function argume…
Browse files Browse the repository at this point in the history
…nts.

Fixes: #36
  • Loading branch information
thet committed Jul 6, 2023
1 parent b52b961 commit 4e9cea7
Show file tree
Hide file tree
Showing 3 changed files with 61 additions and 9 deletions.
3 changes: 3 additions & 0 deletions news/36.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Fix plone.memoize.view to support unhashable types in function arguments.

Fixes: #36
10 changes: 6 additions & 4 deletions plone/memoize/view.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
from zope.annotation.interfaces import IAnnotations
from zope.globalrequest import getRequest

import json


class ViewMemo:
key = "plone.memoize"
Expand Down Expand Up @@ -43,8 +45,8 @@ def memogetter(*args, **kwargs):
context_id,
instance.__class__.__name__,
func.__name__,
args[1:],
frozenset(kwargs.items()),
json.dumps(args[1:]),
json.dumps(kwargs),
)
if key not in cache:
cache[key] = func(*args, **kwargs)
Expand Down Expand Up @@ -72,8 +74,8 @@ def memogetter(*args, **kwargs):
key = (
instance.__class__.__name__,
func.__name__,
args[1:],
frozenset(kwargs.items()),
json.dumps(args[1:]),
json.dumps(kwargs),
)
if key not in cache:
cache[key] = func(*args, **kwargs)
Expand Down
57 changes: 52 additions & 5 deletions plone/memoize/view.rst
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,16 @@ First we set up a dummy view::
... return '%s world' % self.txt1
...
... @view.memoize
... def getMsg(self, to, **instruction):
... lst = ['%s--%s' %t for t in sorted(instruction.items(), reverse=True)]
... def getMsg(self, to, *args, **kwargs):
... lst = ['%s' %t for t in args]
... lst = lst + ['%s--%s' %t for t in sorted(kwargs.items(), reverse=True)]
... instxt = ' '.join(lst)
... return ("%s: %s world%s %s" %(to, self.txt1, self.bang, instxt)).strip()
...
... @view.memoize_contextless
... def getAnotherMsg(self, to, **instruction):
... lst = ['%s--%s' %t for t in instruction.items()]
... def getAnotherMsg(self, to, *args, **kwargs):
... lst = ['%s' %t for t in args]
... lst = lst + ['%s--%s' %t for t in kwargs.items()]
... instxt = ' '.join(lst)
... return ("%s: %s world%s %s" %(to, self.txt1, self.bang, instxt)).strip()
...
Expand Down Expand Up @@ -81,14 +83,31 @@ Even though we've twiddled txt1, txt2 is not recalculated::
>>> msg.txt2
'hello world'

We support memoization of multiple signatures as long as all signature values are hashable::
We support memoization of multiple signatures.
The signature values just need to be able to be converted into a JSON string::

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

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

We also support unhashable types like lists and dicts::

>>> print(msg.getMsg('J.D.', lower=['shields', 'engines']))
J.D.: goodbye cruel world! lower--['shields', 'engines']

>>> print(msg.getMsg('J.D.', actions={'open': ['gates']}))
J.D.: goodbye cruel world! actions--{'open': ['gates']}

Also in non-keyword arguments::

>>> print(msg.getMsg('J.D.', ['shields', 'engines']))
J.D.: goodbye cruel world! ['shields', 'engines']

>>> print(msg.getMsg('J.D.', {'open': ['gates']}))
J.D.: goodbye cruel world! {'open': ['gates']}

We can alter data underneath, but nothing changes::

>>> msg.txt1 = 'sound and fury'
Expand Down Expand Up @@ -173,6 +192,34 @@ based on parameters, but not on context::
>>> print(msg2.getAnotherMsg('J.D.', **{'raise':'roofbeams'}))
J.D.: so long, cruel world& raise--roofbeams

Contextless memoizing also supports unhashable types like lists and dicts::

>>> print(msg3.getAnotherMsg('J.D.', lower=['shields', 'engines']))
J.D.: so long, cruel world& lower--['shields', 'engines']

>>> print(msg2.getAnotherMsg('J.D.', lower=['shields', 'engines']))
J.D.: so long, cruel world& lower--['shields', 'engines']

>>> print(msg3.getAnotherMsg('J.D.', actions={'open': ['gates']}))
J.D.: so long, cruel world& actions--{'open': ['gates']}

>>> print(msg2.getAnotherMsg('J.D.', actions={'open': ['gates']}))
J.D.: so long, cruel world& actions--{'open': ['gates']}

Also in non-keyword arguments::

>>> print(msg3.getAnotherMsg('J.D.', ['shields', 'engines']))
J.D.: so long, cruel world& ['shields', 'engines']

>>> print(msg2.getAnotherMsg('J.D.', ['shields', 'engines']))
J.D.: so long, cruel world& ['shields', 'engines']

>>> print(msg3.getAnotherMsg('J.D.', {'open': ['gates']}))
J.D.: so long, cruel world& {'open': ['gates']}

>>> print(msg2.getAnotherMsg('J.D.', {'open': ['gates']}))
J.D.: so long, cruel world& {'open': ['gates']}

There is also support for using a global request
if zope.globalrequest is available.
With that you can cache also functions.
Expand Down

0 comments on commit 4e9cea7

Please sign in to comment.