Next, don't define functions in a message handler or other event scripts. Define all your functions in a project library script, including the message handler logic. Then call your function from the actual message handler event.
Additionally, don't import anything inside of a function, import it at the top of the project library script, outside of any function definition. E.G:
from com.inductiveautomation.ignition.gateway import IgnitionGateway
from com.inductiveautomation.ignition.gateway.auth.security.level import SecurityLevelManager
from com.inductiveautomation.ignition.gateway.auth.security.level import SecurityLevelConfig
from com.google.common.collect import ImmutableList
def myMessageHandler(payload):
""" """
# blah blah blah
You might be running into a known issue with scoping/library availability in the event scripts, defining your functions in the project library script resolves this.
I would also recommend adding
except java.lang.Throwable as t:
logger.warn("Java exception occurred", t)
to your try block to catch any java errors. Your current try block will only catch python exceptions, which do not include Java errors.
It's honestly much more efficient to prompt "I want to do x" and have an LLM generate a code block that already knows the interfaces defined in the documentation that I didn't even know existed prior to looking through the Sources breakdown.
Per the guidance, I reformatted the original logic to be a script within the project library and am now calling the script from the Gateway Message Handler via ScriptName.main():
from com.inductiveautomation.ignition.gateway import IgnitionGateway
from com.inductiveautomation.ignition.gateway.auth.security.level import SecurityLevelManager
from com.inductiveautomation.ignition.gateway.auth.security.level import SecurityLevelConfig
from com.google.common.collect import ImmutableList
logger = system.util.getLogger('UserManagement')
def cloneAndModify(children):
try:
newChildren = []
for child in children:
modifiedChild = SecurityLevelConfig(
child.getName(),
child.getDescription(),
cloneAndModify(child.getChildren())
)
newChildren.append(modifiedChild)
return newChildren
except Exception as e:
logger.info(str(e))
def addTestRole(rootConfigs):
try:
updated = []
for cfg in rootConfigs:
if cfg.getName() == "Authenticated":
rolesNode = None
rolesIndex = None
# Find "Roles"
for i, c in enumerate(cfg.getChildren()):
if c.getName() == "Roles":
rolesNode = c
rolesIndex = i
break
if rolesNode is None:
raise Exception("Roles node not found in Authenticated hierarchy.")
# Build Test Role node
testRole = SecurityLevelConfig(
"Test Role",
None,
[
SecurityLevelConfig(
"SecurityZones",
"Represents the security zones that a user has.",
[SecurityLevelConfig("Default", None, [])]
)
]
)
# Rebuild Roles children
updatedRolesChildren = cloneAndModify(rolesNode.getChildren())
updatedRolesChildren.append(testRole)
updatedRolesNode = SecurityLevelConfig(
"Roles",
rolesNode.getDescription(),
updatedRolesChildren
)
# Rebuild Authenticated node
newAuthenticatedChildren = cloneAndModify(cfg.getChildren())
newAuthenticatedChildren[rolesIndex] = updatedRolesNode
updatedCfg = SecurityLevelConfig(
cfg.getName(),
cfg.getDescription(),
newAuthenticatedChildren
)
else:
updatedCfg = SecurityLevelConfig(
cfg.getName(),
cfg.getDescription(),
cloneAndModify(cfg.getChildren())
)
updated.append(updatedCfg)
return updated
except Exception as e:
logger.info(str(e))
def main():
try:
# Execute update
gw_context = IgnitionGateway.get()
sec_mgr = gw_context.getSecurityLevelManager()
curr_lvls = sec_mgr.getSecurityLevelsConfig()
new_lvls = addTestRole(curr_lvls)
sec_mgr.updateSecurityLevelsConfig(ImmutableList.copyOf(new_lvls))
except Exception as e:
logger.info(str(e))
except java.lang.Throwable as t:
logger.warn("Java exception occurred", t)
And here's the error being thrown:
com.inductiveautomation.ignition.common.script.JythonExecException: Traceback (most recent call last): File "", line 2, in handleMessage ImportError: Error loading module UserManagement: Traceback (most recent call last): File "", line 3, in ImportError: cannot import name SecurityLevelConfig
at org.python.core.Py.ImportError(Py.java:327)
at com.inductiveautomation.ignition.common.script.ScriptModule.loadModule(ScriptModule.java:79)
at com.inductiveautomation.ignition.common.script.ScriptModule.__findattr_ex__(ScriptModule.java:99)
at org.python.core.PyObject.__getattr__(PyObject.java:957)
at org.python.pycode._pyx66.handleMessage$1(:2)
at org.python.pycode._pyx66.call_function()
at org.python.core.PyTableCode.call(PyTableCode.java:173)
at org.python.core.PyBaseCode.call(PyBaseCode.java:306)
at org.python.core.PyFunction.function___call__(PyFunction.java:474)
at org.python.core.PyFunction.__call__(PyFunction.java:469)
at org.python.core.PyFunction.__call__(PyFunction.java:464)
at com.inductiveautomation.ignition.common.script.ScriptManager.runFunction(ScriptManager.java:847)
at com.inductiveautomation.ignition.common.script.ScriptManager.runFunction(ScriptManager.java:829)
at com.inductiveautomation.ignition.gateway.project.ProjectScriptLifecycle$TrackingProjectScriptManager.runFunction(ProjectScriptLifecycle.java:868)
at com.inductiveautomation.ignition.common.script.message.MessageHandlerRunnable.run(MessageHandlerRunnable.java:121)
at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Unknown Source)
at java.base/java.util.concurrent.FutureTask.run(Unknown Source)
at java.base/java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(Unknown Source)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
at java.base/java.lang.Thread.run(Unknown Source)
Caused by: org.python.core.PyException: ImportError: Error loading module UserManagement: Traceback (most recent call last): File "", line 3, in ImportError: cannot import name SecurityLevelConfig
The 2nd point is especially important when using system.net.httpClient, where you want to be reusing the same instance as much as possible instead of creating a new one each time you do something.
There are also some legacy scoping issues that you get around by having functions defined in the project library script and calling them from events.
Regarding imports:
Basically, the trade off between locking once for an import when first initializing the project library script vs locking every time the function is called. Someone else might be able to explain it in further detail.