Written by

Senior Software Engineer
Article Ashok Kumar T · Sep 13, 2024 7m read

Embedded python in InterSystems IRIS Part-2

In the previous article. Practices of class members and their execution within embedded Python. We will now turn our attention to the process of switching namespaces, accessing global variables , traversing and routine executions within embedded Python.

Before proceeding to the other functions. let us briefly review the execute function within the iris package. This function is exceptionally beneficial for executing the arbitrary ObjectScript functions and class invocation.

>>> b = iris.execute('return $Piece("test^aaaa","^",2)')
>>> b
'aaaa'
>>> b = iris.execute('return $Extract("123456",2,5)')
>>> b
'2345'
>>> b = iris.execute('return $Length(123456)')
>>> iris.execute('write ##Class(%SYSTEM.SYS).NameSpace()')
LEARNING>>>
>>> b = iris.execute('return ##Class(%SYSTEM.SYS).NameSpace()')
>>> b
'LEARNING'

Let us begin!

4. Switch Namespaces

Switching namespaces during execution is often necessary. However, unlike in IRIS, direct switching of namespaces within embedded Python is not feasible. Therefore, it is essential to utilize existing class definitions or to create a wrapper method to facilitate the switching of namespaces.  

ClassMethod SwitchNM() [ Language = python ]
{
    import iris
    print(iris.cls('%SYSTEM.SYS').NameSpace())
    print(iris.system.Process.SetNamespace("USER"))
    try:
        iris.cls('User.EmbeddedPython').pyGetTemplateString()
    except RuntimeError as e:
        print("Wrong NameSpace",e)
}

 

5. Global

To utilize the capabilities of global for data traversal or to retrieve information from legacy global systems directly, rather than through SQL or objects within embedded Python, one can access it directly by employing the gref function from the iris package. To set or get global values, the gref function can be utilized to establish a reference to the global variable and directly assign values within Python.

 

iris.gref

class gref(builtins.object)
 |  InterSystems IRIS global reference object.
 |  Use the iris.gref() method to obtain a reference to a global
 |
 |  Methods defined here:
 |
 |  __delitem__(self, key, /)
 |      Delete self[key].
 |
 |  __getitem__(self, key, /)
 |      Return self[key].
 |
 |  __len__(self, /)
 |      Return len(self).
 |
 |  __repr__(self, /)
 |      Return repr(self).
 |
 |  __setitem__(self, key, value, /)
 |      Set self[key] to value.
 |
 |  __str__(self, /)
 |      Return str(self).
 |
 |  data(self, key)
 |      Given the keys of a global as a list, returns the state of that.
 |      Example: x = g.data([i,j]) sets x to 0,1,10,11 0-if undefined, 1-defined, 10-undefined but has descendants, 11-has value and descendants
 |
 |  get(self, key)
 |      Given the keys of a global as a list, returns the value stored at that node of the global.
 |      Example: x = g.get([i,j]) sets x to the value stored at key i,j of global g.
 |
 |  getAsBytes(self, key)
 |      Given the keys of a global as a list, returns a string stored at that node of the global, as bytes.
 |      Example: x = g.getAsBytes([i,j]) sets x to the value stored at key i,j of global g, as bytes.
 |
 |  keys(self, key)
 |      Traverses a global starting at the specified key, returning each key in the global.
 |      Example: for key in g.keys([i, j]) traverses g from key i,j, returning each key in turn. Optional second argumenent 1 or -1, if -1 reverses the returned order
 |
 |  kill(self, key)
 |      Given the keys of a global as a list, kills that node of the global and its subtree.
 |      Example: g.kill([i,j]) kills the node stored at key i,j of global g and any descendants.
 |
 |  order(self, key)
 |      Given the keys of a global as a list, returns the next key of the global, optional second argument 1 or -1, if -1 returns the previous key.
 |      Example: j = g.order([i,j]) sets j to the next second-level key of global g.
 |
 |  orderiter(self, key)
 |      Traverses a global starting at the specified key, returning the next key and value as a tuple.
 |      Example: for (key, value) in g.orderiter([i,j]) traverses g from key i,j, returning the next key and value. Optional second argumenent 1 or -1, if -1 reverses the returned order.
 |
 |  query(self, key)
 |      Traverses a global starting at the specified key, returning each key and value as a tuple.
 |      Example: for (key, value) in g.query([i,j]) traverses g from key i,j, returning each key and value in turn. Optional second argumenent 1 or -1, if -1 reverses the returned order.
 |
 |  set(self, key, value)
 |      Given the keys of a global as a list, sets the value stored at that key of the global.
 |      Example: g.set([i,j], 10) sets the value of the node at key i,j of global g to 10
 |
 |  ----------------------------------------------------------------------
 |  Static methods defined here:
 |
 |  __new__(*args, **kwargs) from builtins.type
 |      Create and return a new object.  See help(type) for accurate signature.

5.1 Set global values

ClassMethod SetGlobal() [ Language = python ]
{
import iris
#create a global reference
g = iris.gref('^mygbl') 
g[1],g[2]='Mon','Tue'
g["95752455",1]=iris.execute('return $LFS("Ashok,55720,9639639639,test@gmail.com",",")')
g["85752400",1]=iris.execute('return $LB("Test","9517539635","t@gmail.com")')
g["test","c1"]=iris.execute('return ##Class(MyLearn.EmbeddedPython).executeAndGetResult()') # method wil return some listbuild values# declare values by using set function
g.set([3],'Wed')
g.set([3,1,1],'Test multilevel')
}

5.2 get global values
Fetch the global values from python directly by using the subscripts or get method.

ClassMethod GetGlobal() [ Language = python ]
{
    import iris
    #gets a global reference
    g = iris.gref('^mybgl') 
    # get values
    print(g[3,1,1])
    print(g.get([2,1]))
    print(g["95752455",1])
}

5.3 Traversal 

order - Traversing the global is essential for collecting multiple level of data's from global. This embedded Python  order functions similarly to the $Order command, utilizing the order function from the iris.gref. Initially, it is necessary to establish a reference to the global entity that requires traversal.

Single subscript level traversal

ClassMethod DollarOrder() [ Language = python ]
{
    import iris
    g = iris.gref('^MyLearn.EmbeddedPythonD') # I use my persistent class global
    key = ''
    while True:
        key = g.order([key])
        if key == None:
            breakprint(f'{key} {g.get([key])}')
}

Multi subscript level traversal

 

global

zw ^mygbl
^mygbl(1)="Mon"
^mygbl(2)="Tue"
^mygbl(3)="Wed"
^mygbl(3,1,1)="Test multilevel"
^mygbl(85752400,1)=$lb("Test","9517539635","t@gmail.com")
^mygbl(95752455,1)=$lb("Ashok","55720","9639639639","test@gmail.com")
^mygbl("test","c1")=$lb("Test","8527538521","pylearn@gmail.com")
 
ClassMethod DollarOrderMultiLevel() [ Language = python ]
{
 import iris
 g = iris.gref('^mygbl')
 key1= ''whileTrue:
 	key1 = g.order([key1])
 	if key1== None:
 		break
 	key2 = ''whileTrue:
 		key2 = g.order([key1,key2])
 		if key2 == None:
 			break
 		value = g.get([key1,key2])
 		print(key1,key2,value)
}

query - query function from the iris.gref similarly $query. This function is collects all the global values in to tuples. the tuple result contains the id(s) in list and values is the next tuple. You can refer the below tuple sample 

 

tuple

ex: 
zw ^mybgl
^mybgl(1)="Mon"
^mybgl(2)="Tue"
^mybgl(3)="Wed"
^mybgl(3,1,1)="Test multilevel"
^mybgl(95752455,1)=$lb("Ashok","55720","9639639639","test@gmail.com")

Python tuple : ( [ids], data)
(['1'], 'Mon')
(['2'], 'Tue')
(['3'], 'Wed')
(['3', '1', '1'], 'Test multilevel')
(['95752455', '1'], '\x07\x01Ashok\x07\x0155720\x0c\x019639639639\x10\x01test@gmail.com')

ClassMethod DollarQuery() [ Language = python ]
{
 	import iris
 	g = iris.gref('^mygbl')
 	key = g.query()#this will return tuples of all the subscriptsfor x in key:
 		print(x) # output (['3', '1', '1'], 'Test multilevel')
}

data - this data function Check whether the given subscript is exist in the global and return the $data values by using the data function

ClassMethod DollarData() [ Language = python ]
{
    import iris
    g = iris.gref('^mygbl')
    key1= ''
    print(g.data([1]))
}

 

6. Routines

Furthermore, it is essential to implement the class members. We must execute the routines as part of the implementation for legacy codebase systems and other related situations. Consequently, there exists a dedicated function within the iris library package that allows for the invocation of routines from embedded python through the use of the routine function.

 

myirispython.mac

myirispython
 123
 q
ir1
 "running ir1"
 q
add(p1,p2) public{
return p1+p2
}
sub(p1,p2)
 c= p1-p2
ClassMethod RunRoutines() [ Language = python ]
{
    import iris
    iris.routine('^myirispython')
    iris.routine('add^myirispython',1,2) # same aswrite$$add^myirispython(1,2)
}

Additionally, you can execute the routine by using execute function as well. iris.execute('do ^myirispython')

note: If the routine is not found 
>>> iris.routine('^myirispythonT')
Traceback (most recent call last):
  File "<input>", line 1, in <module>
RuntimeError: Routine not found

Will continue the other topics in the next article.

Comments

Guillaume Rongier · Sep 13, 2024

Great articles. Thanks for sharing.

Why are you using Language Tag and not "pure" python script ? It's so much convient to work with native python script than python code in cls classes.

0
Ashok Kumar T  Sep 13, 2024 to Guillaume Rongier

Hello @Guillaume Rongier

Thanks for the feedback! I go over your pretty useful article. I just write python code inside the ObjectScript itself by using language mode because of it's small code snippets. I actually facing some issues while writing IRIS in native python script.

From my pervious community question. First I install this intersystems_irispython-3.2.0-py3-none-any.whl in python and there is no cls, execute, routine, gref, ref or other IRIS script functions available.

As you recommend from the post. I install the official driver  https://github.com/intersystems-community/intersystems-irispython/releases/download/3.8.0/intersystems_iris-3.8.0-py3-none-any.whl file and I could use the IRIS functions for embedded python cls, execute, routine, gref, ref etc...

However, I got this ImportError: DLL load failed while importing pythonint: The specified module could not be found." error while executing the .py scripterror while executing my script

script is nothing but simple class method invocation.

import iris
defExecute_Classmethod():
    print(iris.cls('MyLearn.EmbeddedPython').test1())

Execute_Classmethod()
 
0
Ashok Kumar T  Sep 13, 2024 to Guillaume Rongier

anyway when I keep my native python scripts under "IRISinstalldirectory\mgr\python" and import my code as module and it's working because it's running inside the IRIS not using the python "driver" 

ClassMethod CallPyscripts()
{
    set ap = "mypyap"set pyImport = ##class(%SYS.Python).Import(ap)
    set builtin = $SYSTEM.Python.Builtins()
    do builtin.help(pyImport)
    write pyImport.irisversion,!
    write pyImport."Execute_Classmethod1"()
}
#__init__.pyimport iris
from .irisembdpyth2024 import *

irisversion = iris.execute('return $zv')

# irisembdpyth2024.py fileimport iris

defExecute_Classmethod1():
    print(iris.cls('MyLearn.EmbeddedPython').test1())
0
Pietro Di Leo · Jan 21

Hello Ashok, this is a great article, thanks for sharing it!

However, I would like to ask how to create an oref using a property index. For example, considering the class:

Class User.Test Extends%Persistent
{

Property Code As%String(MAXLEN = 50) [ Required ];
Index CodeIdx On CodeIdx [ Unique ];
}

In ObjectScript I can use the following syntax to open the oref using its ID: 

set obj = ##class(User.Test).CodeIdxOpen(ID)

In the same way I can use the CodeIdxDelete or CodeIdxExists as well.

I was wondering how to obtain the same result in Embedded Python.

0
Ashok Kumar T  Jan 21 to Pietro Di Leo

Hello @Pietro Di Leo

AFAIK, There is no direct execution of the IndexKeyOpen  and other generated methods  in python. We can write a wrapper method to run those methods.

Class pylearn.NewClass2 Extends%Persistent
{

Property Code As%String(MAXLEN = 50) [ Required ];
Index CodeIdx On Code [ Unique ];ClassMethod RunCode() [ Language = python ]
{
    import iris
    iris_obj =  iris.cls(__name__).GeObjByIdx("CodeIdx","A212")
    print(iris_obj.Code)
}

ClassMethod GeObjByIdx(IndexName, ID)
{
	Return$ClassMethod(,IndexName_"Open",ID)
}
}
0
Pietro Di Leo  Jan 21 to Ashok Kumar T

Thanks for the clarification Ashok! I solved in a similar way. It would be good in a future version to have the possibility of executing generated methods too.

0