See also DebuggingZope?.

Interactive Zope debugging: some examples

This is a compendium of good info found on the web and on the list. Not much new, but it covers some uses I often need.

IPython?

For me, this is the cat's pyjamas. IPython is an enhanced interactive Python shell that comes from the Scientific Python community. You'll never want to use the plain interpreter again.

Here's the simplest way to start up the debugger with IPython?:

jean@klippie work $ ./bin/zopectl debug
Starting debugger (the name "app" is bound to the top-level Zope object)
>>> import IPython
>>> IPython.Shell.IPShell().mainloop(sys_exit=1)
In [1]: @autocall # Otherwise IPython tries to *call* app
Automatic calling is: OFF
In [2]: app = __IP.internal_ns['app']

I often add some common defines:

# Common dev stuff I'm using ..
from Acquisition import aq_inner, aq_parent, aq_base, aq_chain, aq_get
from Products.CMFCore.utils import getToolByName
membership_tool = getToolByName(app.fab, 'portal_membership')
# etc ..

Now I can autocomplete to my heart's content:

In [15]: app.acl_users.getU
app.acl_users.getUser                app.acl_users.getUserNames__roles__
app.acl_users.getUserById            app.acl_users.getUser__roles__
app.acl_users.getUserById__roles__   app.acl_users.getUsers
app.acl_users.getUserNames           app.acl_users.getUsers__roles__

In [15]: app.acl_users.getUser?? # Get docs as well as the source
Type:           Python Method
Base Class:     <type 'function'>
String Form:    <bound method UserFolder.getUser of <UserFolder instance at 411297d0>>
Namespace:      Interactive
File:           /home/jean/Zope-2.7.1-0/lib/python/AccessControl/User.py
Definition:     app.acl_users.getUser(self, name)
Source:
def getUser(self, name):
        """Return the named user object or None"""
        return self.data.get(name, None)

Debugging as an authenticated user (failing)

Fetch the user, and create a new security context for it. For our first try, I'm going to make an intentional mistake, and forget to wrap the user we're fetching:

In [17]: from AccessControl.SecurityManagement import newSecurityManager
In [18]: jean = app.acl_users.getUser('jean')
In [19]: newSecurityManager?
Type:           function
Base Class:     <type 'function'>
String Form:    <function newSecurityManager at 0x40bb7c34>
Namespace:      Interactive
File:           /home/jean/Zope-2.7.1-0/lib/python/AccessControl/SecurityManagement.py
Definition:     newSecurityManager(request, user)
Docstring:
    Set up a new security context for a request for a user
In [21]: newSecurityManager(jean)

Now, if I try to create some new content, I'll get an error due to the fact that my user object isn't wrapped in an acquisition context. IPython? shows me a detailed, colored traceback of which I'm quoting only the last few lines:

In [22]: mat = app.fab.materials.invokeFactory('Material', 'test')
AttributeError           Traceback (most recent call last)
...
    282     uid=user.getId()
    283     if uid is None: return uid
--> 284     db=user.aq_inner.aq_parent
    285     path=[absattr(db.id)]
    286     root=db.getPhysicalRoot()

AttributeError: aq_inner

Starting the debugger automatically

OK, we can try fixing that by wrapping the user object so that it can acquire. I'm going to wrap it in the wrong context (the Plone site, instead of the user folder), so that the invokeFactory call still fails. When the exception is raised, we land in a debugger session where we can examine the context to see what should be fixed:

In [23]: jean = jean.__of__(app.fab) # Let's try wrapping (wrongly!!)
In [24]: @pdb # Now IPython will drop us in a debugger upon exception
Automatic pdb calling has been turned ON
In [25]: mat = app.fab.materials.invokeFactory('Material', 'test1')
...
/home/jean/Zope-2.7.1-0/lib/python/AccessControl/Owned.py in getOwner(self, info, aq_get, UnownableOwner, getSecurityManager)
     92             user = SpecialUsers.nobody
     93         else:
---> 94             user = udb.getUserById(oid, None)
     95             if user is None: user = SpecialUsers.nobody
     96         return user

AttributeError: getUserById
> /home/jean/Zope-2.7.1-0/lib/python/AccessControl/Owned.py(94)getOwner()
-> user = udb.getUserById(oid, None)
(Pdb) udb
<PloneSite instance at 41124b30>
(Pdb) udb.getUserById
*** AttributeError: getUserById
(Pdb) udb.acl_users.getUserById
<bound method GroupUserFolder.getUserById of <GroupUserFolder instance at 41be9ad0>>

Debugging as an authenticated user (successfully)

Here I'm getting the user from the 'acl_users' in the root, and wrapping it in Plone's User Folder. Calling 'invokeFactory' works:

In [10]: from AccessControl.SecurityManagement import newSecurityManager, getSecurityManager
In [11]: user = app.acl_users.getUser('jean').__of__(app.portal.acl_users)
In [12]: newSecurityManager(None, user)
In [13]: getSecurityManager().getUser() # just to verify it.
Out[13]: jean
In [14]: app.fab.materials.invokeFactory('Material', 'test11')
In [15]: mat = getattr(app.fab.materials, 'test11')
In [16]: mat
Out[16]: <Material at /fab/materials/test11>

Now the object can be edited and so on:

In [17]: mat.edit(text_format='plain/html',text='dummy body')
In [18]: mat.setTitle('dummy title')

And I can commit the transaction to persist it in the ZODB:

In [19]: app._p_jar.sync()
In [20]: get_transaction().commit()

Connecting to the monitor port

You can connect to a monitor port of the medusa server. First, in 'zope.conf' you need to tell Zope to start a monitor server:

<monitor-server>
  # valid keys are "address"
  address 9082
</monitor-server>

Then, you can connect to it like so, and get a Python prompt:

jean@klippie work $ python ~/Zope-2.7.1-0/lib/python/ZServer/medusa_client.py localhost 9082

Enter Password:
Welcome to <socket._socketobject object at 0x41ab1c84>
>>>

The password is the emergency user's password, defined in the 'access' file in the root of your Zope instance.

To be honest, I have no idea what you can do at the monitor prompt. I haven't used it.

Debugging the results of calling an URL through the web

(This section lifted from mcdonc's Howto )

If a page template raises an exception, you can do this:

In [10]: import Zope
In [11]: Zope.debug('/path/to/object?query_string', u='username:password', pm=1)
.... output will happen
In [12]: import pdb
In [13]: pdb.pm()

You will be placed at the point where the exception occured in the code. You can't step through code from here, but you can examine the values of the code where the error occured. Note the usefulness of the "u=" parameter which lets you set your user to any user id without needing a through-the-web authentication.

To do normal debugging, instead of the "pm=1" argument, provide an argument to Zope.debug of "d=1". This will allow you to step through code ala Michel's HowTo?.

Acknowledgements

Without lots of help from the mailing lists and web, this wouldn't exist. The first examples above come from Jens Vagelpohl, Paul Winkler, and J Cameron Cooper.