Article
Robert Cemper · Jun 20 7m read

Working with Globals in Embedded PythonContestant

My major interest is  Working with Globals in Embedded Python.
So I checked the available official documentation.

#1 Introduction to Globals
an attempt of a generic description of what a global is. Pointing to

#2 A Closer Look at ObjectScript
But where is Embedded Python ?
Way down you see

#3 Embedded Python

3.1 Embedded Python Overview
3.1.1 Work with Globals

Great if you have never seen a Global before
Otherwise a shocking primitive example
3.2 Using Embedded Python
Last hope: >>> but there is just NOTHING visible.

This is more than just disappointing! Even IRIS Native API for Python is more detailed.
To be clear about what I expect:

SET, GET, KILL of a Global node

Native API: Fundamental Node Operations  and

Navigation with $DATA(), $ORDER(), $QUERY()

Native API: Iteration with nextSubscript() and isDefined()
So I had to investigate, reverse engineer it and experiment myself.

And these are my findings:

All examples are shown in Python Shell as found in IRIS for Windows (x86-64) 2022.1 (Build 209U)
making intensive use of the implicit print() function.

The Global

Whatever you plan to do you need to start with the class iris.gref to create a reference object for the Global.
The Global name is passed as string directly or as variable similar to Indirection in COS/ISOS.
The initial caret (^) is not required as it is clear that we just deal with Globals !

   >>> globalname='rcc'
   >>> nglob=iris.gref(globalname)
   >>> glob=iris.gref('rcc')
   >>> cglob=iris.gref('^rcc')
These are 3 Global references to the same Global.
Just a reference but no indication of this global exists
Interactive doc:  print(glob.__doc__)
InterSystems IRIS global reference object.
Use the iris.gref() method to obtain a reference to a global
 
SUBSCRIPTS

Any global subscript is passed as a Py list [sub1,sub2]. No big difference to COS/ISOS
Just the top-level needs special treatment.
To signal No Subscript it is not an empty list but this [None]

SET

To set a Global we may do it 'directly' as we would in COS/ISOS.

   >>> glob[1,1]=11

or use method gref.set()

   >>> glob.set([1,3],13)

Interactive doc:  print(glob.set.__doc__)
Given the keys of a global, 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

To access the content of a Global node we may do it 'directly' as we would in COS/ISOS.

   >>> glob[1,3]
   13

or use method gref.get()

   >>> glob.get([1,1])
   11

Interactive doc:  print(glob.get.__doc__)
Given the keys of a global, 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.

Attention: This is NOT $GET() as you may know from COS/ISOS

   >>> glob.get([1,99])
   Traceback (most recent call last):
   File "<input>", line 1, in <module>
   KeyError: 'Global Undefined'
   >>>

But using it directly it acts as $GET() in COS/ISOS

   >>> x=glob[1,99]
   >>> print(x)
   None
   >>>

This None signals what SQL expresses as NULL. It will show up later again.

KILL

There is only the method gref.kill() to achieve the expected result.

   >>> glob.kill([1,3])
   >>> y=glob[1,3]
   >>> print(y)
   None
   >>>

Interactive doc: print(glob.kill.__doc__)
Given the keys of a global, 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.

$DATA()

The related method is gref.data()
Interactive doc: print(glob.data.__doc__)
Given the keys of a global, 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

It works as expected.

   >>> glob.data()
   10
   >>> glob.data([None])
   10
   >>> glob[None]=9
   >>> glob.data([None])
   11
   >>> glob.data([1,1])
   1
   >>> glob.data([1,3])
   0
   >>>

$ORDER()

For this example I have added a few nodes to the Global ^rcc:

   >zw ^rcc
   ^rcc=9
   ^rcc(1,1)=11
   ^rcc(1,2)=12
   ^rcc(2,3,4)=234
   ^rcc(2,3,5)=235
   ^rcc(2,4,4)=244
   ^rcc(7)=7

The related method is gref.order()
Interactive doc: print(glob.order.__doc__)
Given the keys of a global, returns the next key of the global.
Example:  j = g.order([i,j]) sets j to the next second-level key of global g.

So we see:

   >>> print(glob.order([]))
   1
   >>> print(glob.order([1]))
   2
   >>> print(glob.order([2]))
   7
   >>> print(glob.order([7]))
   None
   >>> print(glob.order([1,'']))
   1
   >>> print(glob.order([1,1]))
   2
   >>> print(glob.order([2,3,]))
   4
   >>> print(glob.order([2,3,""]))
   4
   >>> print(glob.order([2,3,4]))   
   5
   >>> print(glob.order([2,4,4]))
   None
   >>>

Here a missing subscript as reference or an empty string are equivalent.

$QUERY()

The related method is gref.query()
Interactive doc:  print(glob.query.__doc__)
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

The behavior of this method differs from COS/ISOS.

  • It returns ALL nodes after the starting node
  • It includes the stored content
  • It returns also virtual nodes with NO content indicated as None. Our small example looks like this (wrapped for readability):
   >>> print(list(glob.query()))
   [(['1'], None), (['1', '1'], 11), (['1', '2'], 12), (['2'], None), 
        (['2', '3'], None), (['2', '3', '4'], 234), (['2', '3', '5'], 235), 
        (['2', '4'], None), (['2', '4', '4'], 244), (['7'], 7)]
   >>>

or more readable:

   >>> for (key, value) in glob.query():
   ...  print(key,''.ljust(20-len(str(list(key))),'>'),value)
   ...
   ['1'] >>>>>>>>>>>>>>> None
   ['1', '1'] >>>>>>>>>> 11
   ['1', '2'] >>>>>>>>>> 12
   ['2'] >>>>>>>>>>>>>>> None
   ['2', '3'] >>>>>>>>>> None
   ['2', '3', '4'] >>>>> 234
   ['2', '3', '5'] >>>>> 235
   ['2', '4'] >>>>>>>>>> None
   ['2', '4', '4'] >>>>> 244
   ['7'] >>>>>>>>>>>>>>> 7
   >>>

It is definitely not ZWRITE !

Another option is to get the subscripts only using gref.keys()
Interactive doc: print(glob.keys.__doc__)
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. >>>

   >>> list(glob.keys())
   [['1'], ['1', '1'], ['1', '2'], ['2'], ['2', '3'], ['2', '3', '4'], 
                 ['2', '3', '5'], ['2', '4'], ['2', '4', '4'], ['7']]
   >>>

And then I found gref.orderiter() with this
Interactive doc: print(glob.orderiter.__doc__)
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.

It acts like $ORDER() also fetching the content and
providing the next sub-node down with it'S content like $QUERY()
see it:

   >>> list(glob.orderiter([]))
   [(['1'], None), (['1', '1'], 11)]
   >>> list(glob.orderiter([1]))
   [(['2'], None), (['2', '3'], None), (['2', '3', '4'], 234)]
   >>> list(glob.orderiter([2]))
   [(['7'], 7)]
   >>>

Finally, there is a method gref.getAsBytes() 
Interactive doc: print(glob.getAsBytes.__doc__)
Given the keys of a global, 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.

It fails for numeric values. But likes strings:

   >>> glob[5]="robert"
   >>> glob.get([5])
   'robert'
   >>> glob.getAsBytes([5])
   b'robert'

And if I run in COS/ISOS: set ^rcc(9)=$lB(99,"robert") 
I can get this:

   >>> glob[9]
   '\x03\x04c\x08\x01robert'
   >>> glob.getAsBytes([9])
   b'\x03\x04c\x08\x01robert'
   >>>

How did I detect all these methods:

   >>> for meth in glob.__dir__():
   ...  meth
   ...
   '__len__'
   '__getitem__'
   '__setitem__'
   '__delitem__'
   '__new__'
   'data'
   'get'
   'set'
   'kill'
   'getAsBytes'
   'order'
   'query'
   'orderiter'
   'keys'
   '__doc__'
   '__repr__'
   '__hash__'
   '__str__'
   '__getattribute__'
   '__setattr__'
   '__delattr__'
   '__lt__'
   '__le__'
   '__eq__'
   '__ne__'
   '__gt__'
   '__ge__'
   '__init__'
   '__reduce_ex__'
   '__reduce__'
   '__subclasshook__'
   '__init_subclass__'
   '__format__'
   '__sizeof__'
   '__dir__'
   '__class__'
   >>>

I hope this makes life easier if you require direct access to Globals from Embedded Python
My personal learning: There is mostly a documentation . . . . somewhere.
You just have to dig and explore it.

Video Demo

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

Hi Robert, before developing the documentation on Embedded Python, we discussed the ways we expected that developers would be likely use this feature. Direct access to globals was considered lower priority. Undoubtedly there are people who will have the need to do so, and we will be supplementing the documentation with additional material in the future. In the meantime, your post will be valuable for others who share your interest, so thank you for your contribution.

The quickest way to get a summary of the methods that can be used to access globals from Embedded Python is to type help(iris) from the Embedded Python shell.

Thank you Mark!
And thanks for the hint on help(iris).
You hit the point:
The post was mainly meant as an interim filler until some more precise doc is available.
And also as a follow-up to my previous published exercises.

And for the actual contest, I needed a subject nobody else touched on before.