Search code examples
pythontkintertcltk-toolkit

Implementing Tcl bgerror in python


This is a follow up question from this one where it is suggested to implement the bgerror to actually get a response to what went wrong in the background. However I'm a bit confused, as I understand the documentation it should be sufficient to define a proc named bgerror that will be called by the interpreter, doesn't it ?

My attempt below shows how I try to implement it, with no effect at all.

import tkinter as tk

tcl = tk.Tk()
####work around for puts in python
def puts(inp):
    print(inp)

cmd = tcl.register(puts)
tcl.eval(
    'proc puts {args} {' +
        f'{cmd} [join $args " "]' +
    '}')
tcl.call('puts', 'test') #test puts

#attempt to implement bgerror 
tcl.eval('''
proc bgerror {message} {
    set timestamp [clock format [clock seconds]]
    puts "$timestamp: bgerror in $::argv '$message'"
}''')

#setup a failing proc
tcl.eval('''
after 500 {error "this is an error"}
''')
tcl.mainloop()

In addition, it would be nice to know if this is the right approach at all, since the documentation suggests for newer applications to use interp bgerror. But again I'm confused, cause there seems to be no indicator where I could find the path for these calls or are they for child interpreter exclusively ?


Solution

  • In this case, you'd do better with explicit registration instead of using implicit registration based on naming, as that would let you access the full error information (including the stack trace!) and not just the message.

    import tkinter
    tcl = tkinter.Tcl()
    
    # Fortunately, we can put the complex stuff in a function
    def install_bgerror_handler(tcl, handler):
        def type_thunk(message, options):
            # Call internal command to convert Tcl dict to Python dict
            handler(message, tkinter._splitdict(tcl, options))
        tcl.createcommand(handler.__name__, type_thunk)
        tcl.call("interp", "bgerror", "", handler.__name__)
    
    def bgerr_handler(message, options):
        print(message, "<<", options)
    
    install_bgerror_handler(tcl, bgerr_handler)
    
    # demonstrate with a background failure
    tcl.eval('''
        after 500 {error "this is an error"}
    ''')
    tcl.mainloop()
    

    The message printed out in this case is: this is an error << {'code': '1', 'level': '0', 'errorstack': 'INNER {returnImm {this is an error} {}}', 'errorcode': 'NONE', 'errorinfo': 'this is an error\n while executing\n"error "this is an error""\n ("after" script)', 'errorline': '1'} The stack trace is available in the handler as options["errorinfo"]. The errorcode and errorline may also be interesting. (Conversion to being kwargs-based is left as an exercise, but be aware that the set of keys in that dictionary is not a closed set; more complex errors may include keys not listed above.)

    It's a good idea to take special care to avoid actually throwing exceptions from your handler. Things will work, but it's probably a bad plan.