Get current scope in scripting

Is there a way to easily grab the current scope in scripting?

We have a lot of code that will either execute through normal python, tag event scripts, script console, or perspective scripts. And so we need to identify what scope we are in sometimes to verify how we want to handle a task.

I know that I can try to do things like import the gateway context, or do a perspective print, etc, but is there a more rigid test to see Current scope? (Understanding this will fail in normal python, but we can work around that)

3 Likes

Options :slight_smile:

Perfect! I guess I didnt search hard enough before I asked lol

My preferred option is probably this one


try:
    from com.inductiveautomation.ignition.common.model import ApplicationScope
    scope = ApplicationScope.getGlobalScope()


    if (ApplicationScope.isClient(scope)):
	    print("Client Scope")

    if (ApplicationScope.isDesigner(scope)):
	    print("Designer Scope")
	
    if (ApplicationScope.isGateway(scope)):
        try:
            system.perspective.print("Perspective Scope")
        except:
	    print("Gateway Scope")
except:
    print("Python Scope")
1 Like

We’ve got this in a script called env

from com.inductiveautomation.ignition.common.model import ApplicationScope

scope = ApplicationScope.getGlobalScope()


def isDesigner():
	return ApplicationScope.isDesigner(scope)

def isGateway():
	return ApplicationScope.isGateway(scope)
	
def isPerspective():
	return ApplicationScope.isGateway(scope) and hasattr(system,'perspective')

def isVision():
	return ApplicationScope.isClient(scope)

Then call it with env.isGateway() etc…

3 Likes

any suggestions on how to make isPerspective() return false when called from a session property? In designer and otherwise?

Do I need to combine this method with SYSTEM_FLAGS?

e.g. when modifying a session property in designer (with change script):

def valueChanged(self, previousValue, currentValue, origin, missedEvents):
	import inspect
	from com.inductiveautomation.ignition.common.model import ApplicationScope
	
	logger = system.util.getLogger(inspect.currentframe().f_code.co_name)
	logger.info('test Property Change Script scope')
	logger.info('isDesigner | {!s}'.format(env.isDesigner()))
	logger.info('isGateway | {!s}'.format(env.isGateway()))
	logger.info('isPerspective | {!s}'.format(env.isPerspective()))
	logger.info('toCode | {}'.format(ApplicationScope.toCode(ApplicationScope.getGlobalScope())))
	logger.info("hasattr('system,'perspective') | {!s}".format(hasattr(system,'perspective')))
	system.perspective.print('test') # returns 'No perspective page attached to this thread'

test Property Change Script scope
isDesigner | False
isGateway | True
isPerspective | True
toCode | G
hasattr('system,'perspective') | True
ERROR: 'No perspective page attached to this thread'

Theoretically, something like this should work:

from com.inductiveautomation.ignition.gateway import IgnitionGateway

pageModel = IgnitionGateway.get().moduleManager.resolveClass("com.inductiveautomation.perspective.gateway.model.PageModel")

if pageModel.PAGE.get() is not None:
	# in a page

Subject to change between Ignition versions and Perspective module versions; class names should be considered an implementation detail.

What's the ultimate use case for this kind of introspection?

2 Likes

thank you, I'll give it a shot.

for the moment I'm just using it for context-aware debugging (printing where possible/desired, logging where not). comments/suggestions welcome.

FWIW, I'm fully aware logging alone is the preferred method :slight_smile:

1 Like

it appears isPresent() is java-specific?

hasattr(pageModel, 'PAGE') returns True for me as well :confused:

PageModel is the class. The PAGE attribute will always be present because it's a ThreadLocal field:

I steered you wrong with isPresent() because it's available in the JDK source code, which is what I was looking at for the class, but it's a package-private method that won't be accessible. The get() method is public: https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/ThreadLocal.html#get()

So if pageModel.PAGE.get() is not None: would be the equivalent operation and should work.

3 Likes

anyone have success getting custom code/module/component/binding/etc scripting/context tracebacks as well as Ignition does with native logger/console exceptions?

end goal is to never have to specify a logger "name" in system.util.getLogger() - i just want it to know exactly where it is - all the way down to the component/script/function name - from wherever I call it

here's my (super-hacky) variation of @bschroeder's code:

env
# https://forum.inductiveautomation.com/t/get-current-scope-in-scripting/55686/6
from com.inductiveautomation.ignition.common.model import ApplicationScope

scope = ApplicationScope.getGlobalScope()

def isDesigner():
	return ApplicationScope.isDesigner(scope)

def isGateway():
	return ApplicationScope.isGateway(scope)

def isPerspective():
	# https://forum.inductiveautomation.com/t/get-current-scope-in-scripting/55686/8
	# https://forum.inductiveautomation.com/t/get-current-scope-in-scripting/55686/11
	try:
		from com.inductiveautomation.ignition.gateway import IgnitionGateway
		pageModel = IgnitionGateway.get().moduleManager.resolveClass("com.inductiveautomation.perspective.gateway.model.PageModel")
		return ApplicationScope.isGateway(scope) and hasattr(system,'perspective') and pageModel.PAGE.get() is not None
	except: return False

def isVision():
	return ApplicationScope.isClient(scope)

def getScopeCode():
	return ApplicationScope.toCode(scope)

class util:
	class getLogger:
'''
EXAMPLE:

import inspect

logger = env.util.getLogger('{!s}.{!s}.{!s}'.format(self.name, __name__,inspect.currentframe().f_code.co_name))
try:
	#code here
	logger.info('message')
except Exception as e: logger.info(e)

'''
		def __init__(self, name):
			self.name = name

		def __getattr__(self, name):
			def overload(message=''):
				string = "{} {} - {}".format(name.upper(), self.name, message)
				if isPerspective(): system.perspective.print(string)
				elif isGateway():
					logger = system.util.getLogger(self.name)
					eval('logger.{}'.format(name))(message)
				elif isDesigner() or isVision(): print(string)
			return overload

1 Like

Basically, no, there's no great way to do so at the moment.
I filed a ticket a few months ago to make sure that we're adding context where we have it, probably via the __file__ or __name__ or something standard variables.

4 Likes

thanks! at least now I know I can stop trying for a while and just watch the release notes

Will this help distinguish between a proper Perspective session (ie: button on a Perspective View calling a function) and a function being called from a Perspective session but via an asynchronous thread. ?

The asynchronous thread will not have the thread local for the page.

Yep perfect, I've run a few tests and it is working much better with that in now.