Article
· Apr 16, 2023 4m read

Tuples ahead

Overview

Cross-Skilling from IRIS objectScript to Python it becomes clear there are some fascinating differences in syntax.

One of these areas was how Python returns Tuples from a method with automatic unpacking.

Effectively this presents as a method that returns multiple values. What an awesome invention :)

out1, out2 = some_function(in1, in2)

ObjectScript has an alternative approach with ByRef and Output parameters.

Do ##class(some_class).SomeMethod(.inAndOut1, in2, .out2)

Where:

  • inAndOut1 is ByRef
  • out2 is Output

The leading dot (".") in front of the variable name passes ByRef and for Output.

The purpose of this article is to describe how the community PyHelper utility has been enhanced to give a pythonic way to take advantage of ByRef and Output parameters. Gives access to %objlasterror and has an approach for Python None type handling.
 

    Example ByRef

    Normal invocation for embedded python would be:

    oHL7=iris.cls("EnsLib.HL7.Message")._OpenId('er12345')

    When this method fails to open, variable "oHL7" is an empty string.
    In the signature of this method there is a status parameter that is available to object script that gives an explanation of the exact problem.
    For example:

    • The record may not exist
    • The record couldn't be opened in default exclusive concurrency mode ("1"), within timeout
    ClassMethod %OpenId(id As %String = "", concurrency As %Integer = -1, ByRef sc As %Status = {$$$OK}) As %ObjectHandle

    The TupleOut method can assist returning the value of argument sc, back to a python context.
     

    > oHL7,tsc=iris.cls("alwo.PyHelper").TupleOut("EnsLib.HL7.Message","%OpenId",['sc'],1,'er145999', 0)
    > oHL7
    ''
    > iris.cls("%SYSTEM.Status").DisplayError(tsc)
    ERROR #5809: Object to Load not found, class 'EnsLib.HL7.Message', ID 'er145999'1
    ```

    The list ['sc'] contains a single item in this case. It can return multiple ByRef values, and in the order specified. Which is useful to automatically unpack to the intended python variables.

    Example Output parameter handling

    Python code:

    > oHL7=iris.cls("EnsLib.HL7.Message")._OpenId('145')
    > oHL7.GetValueAt('<%MSH:9.1')
    ''

    The returned string is empty but is this because the element is actually empty OR because something went wrong.
    In object script there is also an output status parameter (pStatus) that can be accessed to determine this condition.

    Object script code:

    > write oHL7.GetValueAt("<%MSH:9.1",,.pStatus)
    ''
    > Do $System.Status.DisplayError(pStatus)
    ERROR <Ens>ErrGeneral: No segment found at path '<%MSH'

    With TupleOut the equivalent functionality can be attained by returning and unpacking both the method return value AND the status output parameter.

    Python code:

    > hl7=iris.cls("EnsLib.HL7.Message")._OpenId(145,0)
    > val, status = iris.cls("alwo.PyHelper").TupleOut(hl7,"GetValueAt",['pStatus'],1,"<&$BadMSH:9.1")
    > val==''
    True
    > iris.cls("%SYSTEM.Status").IsError(status)
    1
    > iris.cls("%SYSTEM.Status").DisplayError(status)
    ERROR <Ens>ErrGeneral: No segment found at path '<&$BadMSH'1


    Special variable %objlasterror

    In objectscript there is access to percent variables across method scope.
    There are scenarios where detecting or accessing special variable %objlasterror is useful after calling a CORE or third party API
    The TupleOut method allows access to %objlasterror, as though it has been defined as an Output parameter, when invoking methods from Python

    > del _objlasterror
    
    > out,_objlasterror=iris.cls("alwo.PyHelper").TupleOut("EnsLib.HL7.Message","%OpenId",['%objlasterror'],1,'er145999', 0) 
    
    > iris.cls("%SYSTEM.Status").DisplayError(_objlasterror)
    ERROR #5809: Object to Load not found, class 'EnsLib.HL7.Message', ID 'er145999'1

    When None is not a String

    TupleOut handles python None references as objectscript undefined. This allows parameters to default and methods behave consistently.
    This is significant for example with %Persistent::%OnNew where the %OnNew method is not triggered when None is supplied for initvalue, but would be triggered if an empty string was supplied.

    In objectscript the implementation might say:

    do oHL7.myMethod("val1",,,"val2")

    Note the lack of variables between commas.

    TupleOut facilitates the same behavior with:

    Python:

    iris.cls("alwo.PyHelper").TupleOut(oHL7,"myMethod",[],0,"val1",None,None,"val2")

    Another way to consider this, is being able to have one line implementation of invocation code, that behaves flexibly depending on pre-setup of variables:

    Object Script:

    set arg1="val1"
    kill arg2
    kill arg3
    set arg4="val2"
    do oHL7.myMethod(.arg1, .arg2, .arg3, .arg4)

    TupleOut facilitates the same behavior with:

    Python:

    arg1="val1"
    arg2=None
    arg3=None
    arg4="val2"
    iris.cls("alwo.PyHelper").TupleOut(oHL7,"myMethod",[],0,arg1,arg2,arg3,arg4)

    List and Dictionaries

    When handling parameters for input, ByRef and Output, TupleOut utilizes PyHelper automatic mapping between:
    IRIS Lists and Python Lists
    IRIS Arrays and Python Arrays
    Where it takes care to always use strings to represent dictionary keys when moving from IRIS Arrays to Python Dict types.

    Conclusion

    Hope this article helps inspire new ideas and discussion for embedded Python ideas and suggestions.

    Hope also it gives encouragement to explore the flexibility for how IRIS can easily bend to meet new challenges.

    Discussion (0)1
    Log in or sign up to continue