Scripting Module, call mycompany.method instead of system.mycompany.method

I would like to add scripting functions to my projects and call my methods by typing mycompany.library.doSomething() instead of system.mycompany.library.doSomething().

I’ve searched the forums, and read the Adding Scripting Functions section on the SDK Documentation, but I haven’t found a clear way to accomplish that.

According to the docs the following code should work, but it doesn’t:

import mycompany
print mycompany.library.doSomething()

How are the experts doing this? :thinking:

Do you need to add scripting functions via the SDK or can you just create project/global scripts?

Or just put your code into a .py file and put it into /user-lib/pylib/, then you can call it with import filename and run whatever is in the file.

I would use shared or project scripts.

# standard implementation of shared script libraries
import shared
shared.mycompany.library.doSomething()

# shortened import
from shared import mycompany
mycompany.library.doSomething()

1 Like

Thank you @Kevin.Herron, @zackscriven, and @PGriffith for your proposed solutions.

In my company we have multiple gateways, so the idea of creating our own module is something that I’ve been contemplating. In the past I have worked with the project/shared, but I would like to create our own utility library, and have a one true source for our scripting needs instead of having multiple versions of the shared scripts spread across all of our gateways.

Other main key factors are version control, maintainability, and testability. Finally we are considering purchasing EAM, which would allow us to deploy updates to our own modules.

While I have no problem with having to call our methods in the form of system.mycompany.library.method(), I am still curious about the possibility of just doing mycompany.library.method().

Any suggestions?

Thanks,
CR

As you can see in the example, the “system” prefix is just a convention. I think you can use whatever prefix you want.

from system import tag
tag.read(“myTag”)

I tried the following:

/*ScriptFunctions.java*/
public static String toUpper(@ScriptArg("text") String text) {
    return text.toUpperCase();
}

/*Client/Designer/GatewayHook.java*/
@Override
public void initializeScriptManager(ScriptManager manager) {
    super.initializeScriptManager(manager);
    manager.addScriptModule(
            "mycompany.util",
            ScriptFunctions.class,
            new ScriptFunctions.ScriptDocumentation());
}

And while I do get code completion, I get the following error when I try to run my code on the Script Console:

Traceback (most recent call last):
  File "<buffer>", line 1, in <module>
NameError: name 'mycompany' is not defined

So apparently one cannot just assign whatever prefix.

With this proposed solution I lost code completion, and in my case that’s also important.

It almost accomplished the expected behavior, but without code completion I just wouldn’t go this route.

Thanks!

Unfortunately, no, it’s not just a convention. Or rather, the developer can use whatever names they want, but only the shared.*, project.*, and system.* module trees will be pre-imported into any script environment (with fpmi.* as a synonym for system.*).
The only other choice would be to insert a suitable python object into the __builtins__ module. Not that I’d recommend that…

Yeah, I think the best course of action is to just deal having the system prefix.

Why wouldn’t you recommend that, @pturmel? Would it impact performance or add instability?

I believe the people from Perfect Abstractions (@nmudge) have figured out a way to accomplish what I’m after on their Power Scripting Module. See here.

It is unnecessary to import pa before it is used because it is automatically imported into Ignition Python scripts and modules.

Easily done or python sorcery?

I try not to recommend anything that I haven’t done unless it’s obvious to me that it would work, and work safely.

I noticed that when that module was first published, and reasoned that it had to be done with the __builtins__ module. It certainly is convenient.

I suspect Nick just executed a static jython script to create the pa object and insert it into the builtins dictionary. (In the initializeScriptManager module hook method). It’s called monkey-patching in the python world, and it does have legitimate uses.

1 Like

For me, the easiest is to create custom python modules in a dedicated python IDE, and copy them to the /user-lib/pylib/ directory on the server via a script (can also be a python script fired from the IDE). The gateway automatically picks up modifications and pushes updates to the clients.

This gives us version control, better search, and easy to import scripts. We can just import it like this:

import atree
atree.myModule.doSomething()

We do need to import this, as opposed to the system, shared or project modules. But it’s a lot easier to maintain than a complete Java module IMO.

The main caveat for us is that you cannot store any state reliably in the scripts as the state would be destroyed on a script update. You always need to write your state to either a database or some Ignition tags. But this does result in crash-safe code.

You also have to watch out for infinite loops (like polling loops), these don’t get terminated when the script updates, causing old code to remain active. You need to do all polling via Ignition (f.e. call it from an Ignition timer script every loop execution).

If one is careful, state can be managed with the dictionary from system.util.getGlobals(), and long-running threads are fine as long as you build in a mechanism to shut them down when new code wants to take over. Long-running threads are the only practical way to read from custom serial or custom TCP streams with reasonable latency.

One other option that was already briefly mentioned;
In are companies we use the EAM module for this. So that we know the local library’s from the ones being source from are main EAM gateway we name it EAM. We have our entire shared library in a folder called EAM, and push these from are EAM gateway. The only current downside is the EAM library list when pushing are still not sorted, so when you push them, finding the ones you want to push is a challenge when you have lots of them.
Works well for us, as long as the ones you pushing are fully tested!

Would it be possible to expound on this approach… possibly a code snippet. We are working on transposing an existing py lib into a scripting rpc module, and this would be immensely helpful. I am new to Ignition, and probably not as well versed on the jython implementation as I should be.