Create a scripting function with optional parameters in the SDK

I’m trying to develop a scripting function with the java SDK and I would like to implement optional arguments. Is there an elegant way to achieve this? Typically, how are out of the box scripting functions with optional arguments programmed by IA ?
Thanks in advance.

Here’s a snippet from my Ethernet/IP module that should get you started:

	@KeywordArgs(names = { "name", "slot", "id" }, types = { String.class, Integer.class, Long.class })
	static public PyObject getDevice(PyObject[] args, String[] keywords) {
		ArgParser ap = new ArgParser("getDevice", args, keywords, new String[] { "name", "slot", "id" });
		String name = ap.getString(0, null);
		int slot = ap.getInt(1, -1);
		long id = (Long) Py.tojava(ap.getPyObject(2, new PyLong(0L)), Long.class);

Thanks for your answer. I understand your code and it is exactly what I’m looking for.
That being said, my function’s declaration in the AbstractScriptModule file should then look like:

@ScriptFunction(docBundlePrefix = "AbstractScriptModule")
public String getRawData(PyObject[] args, String[] keywords) {
        return getRawDataImpl(args, keywords);
protected abstract String getRawDataImpl(PyObject[] args, String[] keywords);

So there won’t be any input parameter with the @ScriptArg annotation like shown in the examples right? Is that ok?

I’m not sure how to combine the @KeywordArgs annotation with the @ScriptFunction annotation. I don’t use the latter. I have a custom doc provider with similar functionality.

It’s perfectly fine to have both the @ScriptFunction and @KeywordArgs annotations on the same function. We do this all the time internally.

To answer @mehdi.bouzit1 's question, with an example from our code…

The superclass looks like this:
Dataset queryAgentStatus(PyObject[] pyArgs, String[] keywords);
(no annotations at all)

One of the subclasses looks like this:

    @ScriptFunction(docBundlePrefix = "EAMScriptingFunctions")
    @KeywordArgs(names = {"groupIds", "agentIds", "isConnected"}, types = {String[].class,
        String[].class, Boolean.class})
    public Dataset queryAgentStatus(PyObject[] pyArgs, String[] keywords) {
       // some code here

If you're on newer 8.0.X+ SDK, you could try out PyArgParser in common - it's much more reasonable about automatic casting (imo) than PyArgumentMap.

Also, TypeUtilities.pyToJava is nice if you could have someone pass very large numbers - at some point, Jython switches to BigInteger/BigDecimal as the return from tojava, which the TypeUtilities method will automatically unpack.

As an aside for @mehdi.bouzit1 - while the PyObject[], String[] signature is definitely preferred, Jython also negotiates 'optional' arguments just fine if you do plain Java overloads - sometimes that can be easier for functions that only accept a couple arguments, since you also get automatic Jython type coercion and don't have to worry about unpacking the pyArgs array.

1 Like

Most of my projects are dual 7.9/8.1. I don't actually use your SDK, per se.

Ah, handy. Will probably use next time.

I do this most of the time.

1 Like

Thanks everyone, I’ve been able to successfully create my method with optional arguments with the ArgParser function.

That being said, I tried to use PyArgParser (probably the wrong way) as follows:

@ScriptFunction(docBundlePrefix = "AbstractScriptModule")
@KeywordArgs(names = { "arg1", "arg2", "arg3", ... },
                          types = { String.class, String.class,String.class,String.class,String.class, ...})
    protected String getRawDataImpl(PyObject[] args, String[] keywords) throws IOException{
        PyArgParser pap = new PyArgParser(args, keywords);
        String[] cmdArray = new String[16];
        cmdArray[0] = pap.arg(0,null).toString();
        cmdArray[1] = pap.arg(1,null).toString();
        cmdArray[2] = pap.arg(2,null).toString();

and ended having a NullPointerException if I hit an argument I didn’t provide.

Furthermore, on the designer, if I hit Ctrl+Space to display my function’s tooltip, it’s displaying something like:

getRawData(PyObject[], String[]) → String
Get raw data from the VSE.
  PyObject[] - No description provided.
  String[] - No description provided.
    No return value.

Is there any way to display a tooltip with all my arguments ?
Thank you.

I'm pretty sure you're using the wrong class; there's a PyArgParser in Jython, but the one to use is

Do you have each parameter identified in your .properties file?


1 Like

You are right, I was using com.ziclix.python.sql.util.PyArgParser...
I had to explicitly add:

import com.inductiveautomation.ignition.common.script.PyArgParser;

and import the maven dependancy.

I used this function as follows:

@ScriptFunction(docBundlePrefix = "AbstractScriptModule")
@KeywordArgs(names = { "arg1", "arg2",...},types = { String.class, String.class,String.class,...})
    protected String getRawDataImpl(PyObject[] args, String[] keywords) throws IOException{       
        PyArgParser pap = PyArgParser.parseArgs(args,keywords,new String[] { "arg1", "arg2",...},
                new Class[]{String.class, String.class,...},"getRawDataImpl");
       // Get the parameters
       pap.getString("arg1").orElse("default value 1");
       pap.getString("arg2").orElse("default value 2");

As for the .properties file issue, I used the SDK example philosophy and used a myMethod and myMethodImpl, as used with the multiply method. My .properties file was linked to getRawData, so I just had to add a @KeywordArgs(names = { "arg1", "arg2",...},types = { String.class, String.class,String.class,...}) on top of the getRawData declaration in the AbstractScriptModule file.

Thank you for your support !