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)
Perfect! I guess I didnt search hard enough before I asked lol
kgamble
February 2, 2022, 10:01pm
5
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
1 Like
it appears isPresent()
is java-specific?
hasattr(pageModel, 'PAGE')
returns True for me as well
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
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.
3 Likes
thanks! at least now I know I can stop trying for a while and just watch the release notes