How to break your Plone site with implementedBy
Sometimes, when you start your plone instance, just like any other day, you get an error message which make you think what on earth is going on here?
When you are developing, you know that error messages will be there sooner or later. Most of the time when you don't know the answer immediately you copy and paste the message in your google searchbar and you will find the answer within a few minutes. If you first had your coffee you would have known immediatly what was going on, but hey, don't blame yourself, today it is a typical monday morning.
But on some very rare occasions you don't find an answer in a few minutes. You even won't find an answer after a few hours, and after spending some time with your very intelligent and smart colleagues you don't find anything which could point you in the right direction. And this always happens when you don't want it to happen offcourse.
An example error message of this is the following, on a Plone 3.3.2 website you are developing on for some weeks :
...
raise TypeError("ImplementedBy called for non-factory", cls)
File "../parts/zope2/lib/python/zope/interface/declarations.py",
line 353, in implementedByFallback
TypeError: (<exceptions.TypeError instance at 0x936cb20>,
<built-in function implementedBy>,
(<RegistrationTool at portal_registration>,))
This happened by just visiting the Plone site, it even happened when visiting the ZMI.
Get back
We had to edit the code of zope/interfaces/declarations.py to something like this to get back in our Plone site:
...
else:
try:
bases = cls.__bases__
except AttributeError:
if not callable(cls):
pass
# raise TypeError("ImplementedBy called for non-factory", cls)
bases = ()
bases = ()
Ok, after this very unwanted change it as time to find out what was going on. What we know now is that is has something to do with implementedBy and that we used in in some code to check if an objects implements an interface.
Check the documentation
What does the documentation say about implementedBy ?
You can ask an interface if a certain class or instance that you hand it implements that interface. For example, say you want to know if instances of the HelloComponent class implement ‘Hello’:
>>> IHello.implementedBy(HelloComponent)
This is a true expression. If you had an instance of HelloComponent, you can also ask the interface if that instance implements the interface:
>>> IHello.implementedBy(my_hello_instance)
Ok, so it's only querying a class or an instance if it's implementing an interface. Nothing evil on that, right?
No clue where to look now so let's try to export some valuable content from the website by making an .zexp file. But while importing the zexp you get another error message :
Module ZODB.ExportImport, line 92, in importFile
Module transaction._transaction, line 256, in savepoint
Module transaction._transaction, line 253, in savepoint
Module transaction._transaction, line 642, in __init__
Module ZODB.Connection, line 1091, in savepoint
Module ZODB.Connection, line 550, in _commit
Module ZODB.ExportImport, line 178, in _importDuringCommit
Module copy_reg, line 70, in _reduce_ex
TypeError: default __new__ takes no parameters
Right. The plone site is broken, we cannot export the content and even when you copy and paste content in the same website you get this error. And nobody on the world seems to have a similar problem.
IRC log FTW
Google is giving us a lot of hits on ZClasses for Zope versions of 2.9 and below. But we use Zope 2.10.9 and offcourse no ZClasses. Fortunately, there is one useful hint from an irc log by dixond :
<dixond> I had some code, which made the mistake of calling
ISiteRoot.implementedBy(context) ,which is (apparently) supposed to
exception if context is not callable. I guess Archetypes objects are
actually callable. Anyhow, it didn't Exception. Instead, it hosed the
object such that import/export of that object no longer worked.
<dixond> thing is that it was a post-hoc thing. IE, once
ISiteRoot.implementedBy(context) had been called once, it was stuffed
from then on in. The object was just bung.
That's nice. When using implementedBy on an Archetypes object it permanently breaks it, according to dixond. Let's investigate the objects using a debug session to find out what is going on. It probably has something to do with implements or implemented, as that is queried a lot in the implementedBy method.
On the broken plone installation (which could be run by patching the interfaces/declarations.py file) :
object = app.myportal.myfolder.myobject
object.__implemented__
<implementedBy mypackage.content.content.project.?>
That looks odd. A question mark. Let's see if we can find something on that. In the docstring of implementedBy :
Note that the name of the spec ends with a '?', because the `Callable`
instance does not have a `__name__` attribute.
Hmm, Ok. we should invest some time by starting a pdb session to see what happens there in implementedBy, but we are running out of time as we want a working website now. Let's try, a wild guess, to remove the implemented attribute :
del object.__implemented__
object.__implemented__
<implementedBy mypackage.content.content.project.Project>
That is looking different. By adding a fresh plone site and some new content we figured out that those new objects do not have question marks in their interfaces either.
Fixing the Plone site
Ok, back to the start. We have a broken Plone site and some weird stuff in our implemented attributes. We want a full working Plone site again, so we tried to remove all the implemented attributes of the objects and committed it to the ZODB. Hopefully we could export our content again, and fortunately we could!
After replacing the implementedBy calls in our code with providedBy (we were testing if instances provide the interface anyway) the problems are now gone.
And yes, zope.interface is probably giving the right answer when to use which method :
def providedBy(object):
"""Test whether the interface is implemented by the object"""
def implementedBy(class_):
"""Test whether the interface is implemented by instances of the class"""
However, a query method should not alter your objects in my opinion. So my conclusion for now, until I fully understand what is going on is :
Think twice before using implementedBy in your Plone project and do not call it on Archetypes objects.
I hope some people with knowledge of this kind of issues have some useful comments on this. Until then you will probably find two useful hits when you search for it on google :)