pydev multithread debugging
Question: I'm trying to debug an application which makes use of the pynetdicom library.
I'm not sure how relevant that specific detail is, however what IS relevant is
that it makes heavy use of multithreading to run background socket listener
tasks without blocking the main thread. The storescp.py example can be used to
reproduce this.

Whenever I place a breakpoint that gets encountered (regardless of what
thread, main or child, it gets encountered in), I get the following traceback:

    
    
    Traceback (most recent call last):
      File "/Applications/Aptana Studio 3/plugins/org.python.pydev_2.7.0.2013012902/pysrc/pydevd.py", line 1397, in <module>
        debugger.run(setup['file'], None, None)
      File "/Applications/Aptana Studio 3/plugins/org.python.pydev_2.7.0.2013012902/pysrc/pydevd.py", line 1090, in run
        pydev_imports.execfile(file, globals, locals) #execute the script
      File "/Users/alexw/Development/Python/kreport2/KReport2/dicomdatascraper.py", line 183, in <module>
        oldDicomList = copy.copy(newData)
      File "/Users/alexw/Development/Python/kreport2/KReport2/dicomdatascraper.py", line 183, in <module>
        oldDicomList = copy.copy(newData)
      File "/Applications/Aptana Studio 3/plugins/org.python.pydev_2.7.0.2013012902/pysrc/pydevd_frame.py", line 135, in trace_dispatch
        self.doWaitSuspend(thread, frame, event, arg)
      File "/Applications/Aptana Studio 3/plugins/org.python.pydev_2.7.0.2013012902/pysrc/pydevd_frame.py", line 25, in doWaitSuspend
        self._args[0].doWaitSuspend(*args, **kwargs)
      File "/Applications/Aptana Studio 3/plugins/org.python.pydev_2.7.0.2013012902/pysrc/pydevd.py", line 832, in doWaitSuspend
        self.processInternalCommands()
      File "/Applications/Aptana Studio 3/plugins/org.python.pydev_2.7.0.2013012902/pysrc/pydevd.py", line 360, in processInternalCommands
        thread_id = GetThreadId(t)
      File "/Applications/Aptana Studio 3/plugins/org.python.pydev_2.7.0.2013012902/pysrc/pydevd_constants.py", line 140, in GetThreadId
        return thread.__pydevd_id__
      File "/Users/alexw/.virtualenvs/kreport2dev/devlibs/pynetdicom/source/netdicom/applicationentity.py", line 73, in __getattr__
        obj = eval(attr)()
      File "<string>", line 1, in <module>
    NameError: name '__pydevd_id__' is not defined
    

My thought is that, perhaps, in order to make things work, PyDev monkey-
patches a `__pydevd_id__` into spawned threads, however fails to patch those
into _these_ threads because they are, in fact, subclasses and not direct
instances of `threading.Thread` (in this case, the worker is an instance of
`class Association(threading.Thread):`).

Of course, I don't know PyDev well enough to confirm this theory, or else fix
it. And it seems neither does the internet.

Is subclassing `Thread` so rarely used a pattern that it's simply not
considered in the PyDev architecture? Without re-architecting the library, how
could this issue be remedied?


Answer: I simply needed to look harder at that traceback.

The pynetdicom library, in its subclassing of `threading.Thread`, overrode
`__getattr__` and somewhat broke it. The problem was:

    
    
    def __getattr__(self, attr):
        #while not self.AssociationEstablished:
        #    time.sleep(0.001)
        obj = eval(attr)
    
        # do some stuff
    
        return obj
    

when a nonexistent attribute is passed, a NameError is raised. This isn't
caught by pydev's monkeypatching routine (`if thread.__pydevd_id__ raises
AttributeError, thread.__pydevd_id__ = stuff`)

The solution was to update that section thusly:

    
    
    def __getattr__(self, attr):
        #while not self.AssociationEstablished:
        #    time.sleep(0.001)
        try:
            obj = eval(attr)
        except NameError:
            raise AttributeError
    
        # do some stuff
    
        return obj
    

This intercepts the NameError and raises an AttributeError instead, as
`__getattr__` should if the queried attribute doesn't exist.

