zopefoundation / zope.interface

Interfaces for Python
http://zopeinterface.readthedocs.io/
Other
317 stars 70 forks source link

TypeError: 'type' object is not iterable #288

Closed gelbi123 closed 4 months ago

gelbi123 commented 5 months ago

Dear Zope-Developer Team,

following issue appeared:

    Traceback (most recent call last):
      File "/opt/wu-wien/zope/buildouts/msereini-z5-dev/eggs/waitress-2.1.2-py3.9.egg/waitress/channel.py", line 428, in service
        task.service()
      File "/opt/wu-wien/zope/buildouts/msereini-z5-dev/eggs/waitress-2.1.2-py3.9.egg/waitress/task.py", line 168, in service
        self.execute()
      File "/opt/wu-wien/zope/buildouts/msereini-z5-dev/eggs/waitress-2.1.2-py3.9.egg/waitress/task.py", line 434, in execute
        app_iter = self.channel.server.application(environ, start_response)
      File "/opt/wu-wien/zope/buildouts/msereini-z5-dev/eggs/Paste-3.7.1-py3.9.egg/paste/translogger.py", line 77, in __call__
        return self.application(environ, replacement_start_response)
      File "/opt/wu-wien/zope/buildouts/msereini-z5-dev/eggs/Zope-5.9-py3.9.egg/ZPublisher/httpexceptions.py", line 30, in __call__
        return self.application(environ, start_response)
      File "/opt/wu-wien/zope/buildouts/msereini-z5-dev/eggs/Zope-5.9-py3.9.egg/ZPublisher/WSGIPublisher.py", line 391, in publish_module
        response = _publish(request, new_mod_info)
      File "/opt/wu-wien/zope/buildouts/msereini-z5-dev/eggs/Zope-5.9-py3.9.egg/ZPublisher/WSGIPublisher.py", line 269, in publish
        obj = request.traverse(path, validated_hook=validate_user)
      File "/opt/wu-wien/zope/buildouts/msereini-z5-dev/eggs/Zope-5.9-py3.9.egg/ZPublisher/BaseRequest.py", line 515, in traverse
        subobject = self.traverseName(object, entry_name)
      File "/opt/wu-wien/zope/buildouts/msereini-z5-dev/eggs/Zope-5.9-py3.9.egg/ZPublisher/BaseRequest.py", line 348, in traverseName
        ob2 = adapter.publishTraverse(self, name)
      File "/opt/wu-wien/zope/buildouts/msereini-z5-dev/eggs/Zope-5.9-py3.9.egg/ZPublisher/BaseRequest.py", line 142, in publishTraverse
        doc = getattr(subobject, '__doc__', None)
      File "/opt/wu-wien/zope/buildouts/msereini-z5-dev/eggs/ZODB-5.8.1-py3.9.egg/ZODB/Connection.py", line 791, in setstate
        self._reader.setGhostState(obj, p)
      File "/opt/wu-wien/zope/buildouts/msereini-z5-dev/eggs/ZODB-5.8.1-py3.9.egg/ZODB/serialize.py", line 639, in setGhostState
        state = self.getState(pickle)
      File "/opt/wu-wien/zope/buildouts/msereini-z5-dev/eggs/ZODB-5.8.1-py3.9.egg/ZODB/serialize.py", line 632, in getState
        return unpickler.load()
      File "/opt/wu-wien/zope/buildouts/msereini-z5-dev/develop-sources/zope.interface/src/zope/interface/declarations.py", line 803, in Provides
        spec = ProvidesClass(*interfaces)
      File "/opt/wu-wien/zope/buildouts/msereini-z5-dev/develop-sources/zope.interface/src/zope/interface/declarations.py", line 730, in __init__
        Declaration.__init__(self, *self._add_interfaces_to_cls(interfaces, cls))
      File "/opt/wu-wien/zope/buildouts/msereini-z5-dev/develop-sources/zope.interface/src/zope/interface/declarations.py", line 79, in __init__
        Specification.__init__(self, _normalizeargs(bases))
      File "/opt/wu-wien/zope/buildouts/msereini-z5-dev/develop-sources/zope.interface/src/zope/interface/declarations.py", line 1182, in _normalizeargs
        _normalizeargs(v, output)
      File "/opt/wu-wien/zope/buildouts/msereini-z5-dev/develop-sources/zope.interface/src/zope/interface/declarations.py", line 1181, in _normalizeargs
        for v in sequence:
    TypeError: 'type' object is not iterable

What I did:

I created a MarkerInterface


    class IMyMarkerInterface(...):
        "..."

and marked a ZMI-Folder @root with this Interface (via Interfaces-tab).

Now I removed the source file where the markerinterface is declared and restarted the instance. The restart works fine but EVERY request will lead now to the error described above.

Issue research:

File: declarations.py

    def _normalizeargs(sequence, output = None):
    """Normalize declaration arguments

    Normalization arguments might contain Declarions, tuples, or single
    interfaces.

    Anything but individial interfaces or implements specs will be expanded.
    """
    if output is None:
        output = []

    cls = sequence.__class__
    if InterfaceClass in cls.__mro__ or Implements in cls.__mro__:
        output.append(sequence)
    else:
        for v in sequence:
        ^^^^^^^^^^^^^^^^^^
            _normalizeargs(v, output)

Here some informations about the sequence parameter from debug session

    sequence = <class 'MyMarker.interfaces.IMyMarkerInterface'>
    sequence.__mro__ = (<class 'MyMarker.interfaces.IMyMarker'>,
                            <class 'ZODB.broken.Broken'>, <class 'object'>)
    cls = sequence.__class__ = <class 'type'>

What version of Python and Zope/Addons I am using:

davisagli commented 5 months ago

@gelbi123 This is not really a problem with zope.interface.

You appear to be storing references to the interface in the ZODB. When the code for a class is no longer available, the ZODB loads it as an instance of ZODB.broken.Broken. This error happens because the Broken instance cannot function where an interface is expected.

In general with the ZODB you should remove all persistent references to a class before you remove the class from the code. If it's already too late, you can use https://pypi.org/project/zodbupdate/ to convert those references to a different class.

zope.interface could perhaps be improved to show a clearer error message in this case, where it gets an object that is not an interface where it is expecting an interface.

gelbi123 commented 5 months ago

@davisagli thanks for your reply and sorry for my late reply!

You are right, it is not a problem with zope.interface itself.
Its a class implementing something which is not an interface.

Or in that case a object (folder) which should direclty provide ZODB.broken.Broken which is also not an interface.

But I wanted to mention that this behaviour could be a timebomb in the case of the following szenario:

Running a complex system with multiple zope instances coupled via ZEO. Then a
folder will be marked on InstanceA, all requests on other
instances (with the shared Data.fs) could lead to this error if the traverse
mechanism went trough the marked folder and the source code isn't installed.

This was approximately the case in my(our) setup.

In that case I would expect that the code depending on the marker interface
will not work but not all the other code.

Can you recommend a better place to mention that?

jensens commented 5 months ago

Actually a solution might be to "fake" the existence of the old Interface derived class. If you create somewhere in your code a fakemodule.py containing the missing marker interface and use a technique like here https://github.com/plone/plone.app.upgrade/blob/93157548a8b616b44e8fcc31f215806df7648863/plone/app/upgrade/utils.py#L209 to alias it to the old place, it should be possible to load the code in question. Then you can develop code to find objects with the marker and use zope.interface.noLongerProvides to remove it.

gelbi123 commented 4 months ago

@jensens Thank you for sharing this technique.

In my case it wasn't actually the problem to
get rid of the marker interface. On the Instance
with the source code I could unmark it
via the ZMI Interface tab. Instead of that I simple
installed the source code on all the other instances.

The problem was more that the added marker interface
causes a lot of broken requests(TypeError) on instances without
the source code.

To prevent this I developed a customized InterfaceClass object
which should be used as a base class for persistent marker
interfaces. Now when the marker interface gets unpickled and
the source code isn't found, a placeholder will be generated
(like broken objects).

icemac commented 4 months ago

@gelbi123 As your problem seems to be solved, I am closing this issue now.