Using system.* inside a custom function?

I have a custom function on a component called refreshData. Inside this function I have a call to [tt]system.util.invokeAsynchronous[/tt] and it was giving me the error that it couldn’t find the attribute [tt]invokeAsynchronous[/tt]. I tried it with [tt]invokeLater[/tt] as well and it had the same issue. I added a simple call to [tt]system.db.runQuery(“Select 4”)[/tt] and this time instead of erroring on [tt]runQuery[/tt] it errored on [tt]db[/tt] with a similar message:

Traceback (most recent call last): File "<event:visionWindowOpened>", line 5, in <module> File "<custom-function refreshData>", line 4, in refreshData AttributeError: 'com.inductiveautomation.ignition.common.script.Scr' object has no attribute 'db'
This code is being called from inside [tt]visionWindowOpened[/tt], if that makes any difference. I even tried calling the function with [tt]invokeLater[/tt] from within the [tt]visionWindowOpened[/tt] event thinking maybe there was an issue with system.* not having fully loaded yet or something odd like that, but that made no difference.

Help!

Have you tried adding import system at the top?

Yes I forgot to mention that. Adding [tt]import system[/tt] changes nothing.

I’m not sure why it isn’t finding system
What version of Ignition? Can you post the actual code from the component?

While you are in the code editor, what happens when you type system.db. and then hit control - space?

Here is the code:

def refreshData(self) import system system.db.runQuery("Select 4") #Testing system.* progressBar = self.parent.getComponent(self.progressBar) progressBar.visible = True def runQ(): query = """select distinct concat(r.recipename, " - ", r.tooltypeid) as RecipeName,count(r.id) as Recipes from runrecipes rr left join recipes r on r.recipename = rr.recipename where rr.recipename is not null group by rr.recipename limit 5""" res = system.db.runQuery(query) self.data = res progressBar.visible = False system.util.invokeAsynchronous(runQ) #Why is this failing?
Ignition version is 7.8.4

Control+Space pops up the list of all available commands as usual. This list includes the commands I am trying to execute. Copying/pasting this code into the Script Console (and modifying it to not have invalid references to [tt]self[/tt], [tt]progressBar[/tt] etc.) executes without issue.

I know it’s possible to use these things within custom functions because I have executed queries and invocations in custom functions in other projects.

[quote=“TedNewman”]Here is the code:

def refreshData(self) import system system.db.runQuery("Select 4") #Testing system.* progressBar = self.parent.getComponent(self.progressBar) progressBar.visible = True def runQ(): query = """select distinct concat(r.recipename, " - ", r.tooltypeid) as RecipeName,count(r.id) as Recipes from runrecipes rr left join recipes r on r.recipename = rr.recipename where rr.recipename is not null group by rr.recipename limit 5""" res = system.db.runQuery(query) self.data = res progressBar.visible = False system.util.invokeAsynchronous(runQ) #Why is this failing?
Ignition version is 7.8.4

Control+Space pops up the list of all available commands as usual. This list includes the commands I am trying to execute. Copying/pasting this code into the Script Console (and modifying it to not have invalid references to [tt]self[/tt], [tt]progressBar[/tt] etc.) executes without issue.

I know it’s possible to use these things within custom functions because I have executed queries and invocations in custom functions in other projects.[/quote]

There is no semicolon after the define function statement. Fix that and see if that does it.

[quote=“JGJohnson”]
There is no semicolon after the define function statement. Fix that and see if that does it.[/quote]

That’s because I had to manually type it to paste it here, sorry. It DOES have a colon in my actual code. The first line of custom functions is not editable and therefore blocks all keystrokes including copy/paste. Quite annoying if you ask me.

I was able to solve it, though I still don’t know what the bug was exactly.

It looks like the issue had something to do with calling it from within the [tt]visionWindowOpened[/tt] event handler. Placing the function call in a button and clicking the button worked with no problems. I had tried using [tt]system.util.invokeLater(root.getComponent(“My Component”).myFunction)[/tt] to invoke the function later but that didn’t make a difference. However I found that if I put the call to myFunction() inside another function an invoked that one later, it worked.

root = event.source.getRootContainer() def doLater(): root.getComponent("My Component").myFunction() system.util.invokeLater(doLater)
I don’t understand why the cases of those would be different but clearly something about it changed the way system.* was loaded or something.

Inductive have confirmed that this is a bug (ticket #44974).

Custom functions on windows cannot access project/system or import anything. The workaround is to move the custom function to a component. The best option is to move them to the Root Container.

I would strongly advise against using multiple invokeLater calls to fix this, as this is likely to be unreliable.

Old thread now, but the best I found, so I figured I’d post back. I’ve just accepted that in Vision every single custom method needs to start with a system argument that you pass around like a… well, you know. Throwing in a second argument that’s rootContainer is often helpful, too. It’s so crazy that the components are completely shorn of their context outside of event handlers.

I actually did find a hack so dirty even I won’t use it. It’s way more boilerplate than just passing the system around anyway.

Huh. I’ve never had this problem on components. As noted, this was a bug on custom methods for Vision windows, not components.

Hello @silverbacknet ,
I’m having similar issues (Attribute Error in Component Custom Method). I don’t mind passing a system argument…can you explain how I’d actually do that?
Thank you and regards,
Ted.

It’s literally just passing the object system to the custom method. Say you’ve defined a custom method doStuff on the window, and you have parameters stuffToDo, stuffData.

Change the parameters to system, stuffToDo, stuffData, and invoke it from any component event handler as
doStuff(system, "log", event.data)
instead of just
doStuff("log", event.data).

Now you’ve brought the entire system.* set of internal functions in, and you can use them as usual.

I do it this way, instead of putting it at the end, since typically you either require the functions or not when you’re making the method. But you can make it the last argument with a = Null default and test against that before any use, if you want to be sure you don’t break any legacy call.

2 Likes

Thank you @silverbacknet for your solution. It worked like a charm.

This is still a bug and I am running Ignition 8.1.4.