Printing the name of the function and who called it?

In the process of converting a lot of spaghetti code to organized script modules and multiple functions. One drawback I’ve noticed to this is that sometimes the error messages are truncated and not as helpful or specific as if the script was on the button onAction script. I will just get errors that show a Key I tried access in a dictionary that doesn’t exist, or a type error, but no line number or even what function this occured in. I am trying to mediate that.

What I am looking for is some function I can call in the beginning of other functions so that it will print out the current function and what function called it if possible.

I tried doing it with using the inspect module per this and doing this in a function called from another function

import inspect
print "running " + str(inspect.stack()[1][3]) + " called by " + str(inspect.stack()[2][3])

which just throws me a error like so
image

I assume this has to do with the odd runtime environment Ignition is, java calling python scripts etc and so I’m not sure if there is a python stack to really access like there would be in a traditional all python application.

Is there another way to do this? I would really like a function I could call as the first line of other functions that will just print “Running function (function name), called by (function name)”

You might find inspiration in the PythonAsJavaException class in my later.py script module.

How I would in practice use this?

You’re talking a bout this snippet right ?

#------------
# Java wrapper for Jython exceptions to preserve the python
# stack trace.
#
class PythonAsJavaException(java.lang.Exception):

    @staticmethod
    def fullClassName(cls):
        if cls.__bases__:
            return fullClassName(cls.__bases__[0])+"."+cls.__name__
        return cls.__name__

    def __init__(self, pyexc, tb=None):
        super(PythonAsJavaException, self).__init__(str(pyexc), None, True, True)
        traceElements = []
        if tb is None:
            tb = sys.exc_info()[2]
        while tb:
            code = tb.tb_frame.f_code
            vnames = code.co_varnames
            if vnames and vnames[0] in ('cls', 'self'):
                ref = tb.tb_frame.f_locals[vnames[0]]
                if vnames[0] == 'self':
                    className = fullClassName(ref.__class__)
                else:
                    className = fullClassName(ref)
            else:
                className = '<global>'
            traceElements.append(StackTraceElement(className, code.co_name, code.co_filename, tb.tb_lineno))
            tb = tb.tb_next
        self.setStackTrace(traceElements)

It’s a bit over my head - how could I use implement say in a situation like this for a module lets call barcodes?

def create(data):
    import barcodes
    # something here to print the module.create is running, called by GUI
    barcodes.foo()

def foo():
    # something here to print barcode.foo is runnig, called by create
    x = 1

Now say I ran barcode.create(data) from my GUI, how would I use your class to have printed to the console

running barcode.create called from ? 
running barcode.foo called from barcode.create

You wouldn’t use my class. I said inspiration. I would use python/jython’s exception machinery to generate a traceback. With raise inside a try block, if not directly. The traceback has the information you want. That class is an example of traversing it.

1 Like

I’m also in need of something like this…
@bkarabinchak.psi Did you ever get it working?

You can use sys._getframe(1).f_code.co_name to get the caller's name.
I'd make a decorator for this, something like

import sys

def get_caller(func):
	def wrapper(*args, **kwargs):
		print("{} called by {}".format(func.__name__, sys._getframe(1).f_code.co_name))
		return func(*args, **kwargs)
	return wrapper
@get_caller
def foo():
	bar()

@get_caller
def bar():
	pass

foo()

foo called by <module>
bar called by foo

1 Like

OK, thank you for that…
But I probably don’t understand all this … sh*t… :innocent:

If I put this in the button actionPerformed script, I get

foo called by <module>
bar called by foo

But from where is foo called…? What does <module> means?
I’d expect to see something like … button’s name.actionPerformed…instead of <module>?

There might be ways to make it more accurate (get the module’s name, things like that…) but I have no idea how all this interacts with Ignition’s components. You might need to call upon Java for this, I don’t know.

OK, maybe uncle @pturmel can share some… samples…? :pray:

No, I gave up and just hand traced how the code was being run, now I just judiciously use a logger where needed, logger.trace(“Running function x”) is the first line of any function I care to know and use logger.info()'s at important junctions. I also restructured my code so it’s not really a mystery who’s calling what anymore so I didn’t need this anymore. Sorry can’t be more helpful

I imagine <module> refers to the scripting module you have your functions in but I am not an expert here.

1 Like

If you create a java exception instance you can get the java call-chain that ran the jython script, but you won’t get a specific component instance. It does generally reveal a component type, which can narrow down your search. Most of Ignition’s event machinery yields <module> when the source is an anonymous script.

You have to address this in your design pattern:

  • events should have single-line scripts that delegate to a project library function,
  • event or event.source should always be an argument passed in,
  • the library function should wrap the entire body in a try catch catch block–java and jython flavors,
  • and the handler should create a new exception that identifies the component, including the original exception as the cause.

There is no other magic.

4 Likes

Phil,

Are there any specific reasons to do this other than organization, code reuse, exception handling etc...? I'm rolling through a large library of components and moving the logic as much as I can into a project library function for basically just organization. I was just curious if there were other benefits on the java/jython/Ignition side of things that we might see as a side benefit.

The biggest reason is to avoid legacy scoping rules. If you search the forum you can find tons of examples of the different ways in which legacy scoping creates issues.

To sum things up succinctly:

3 Likes

Thanks! That makes sense.

We’ve never run into that exact issue so we’ve been running along w/o issues on event scripts. We’ve been going through fixing up our standards and just wanted to have a bit more of an explanation for future users.