Skip to main content

Accessing Global Arrays with Python

This chapter covers the following topics:

Note:
Creating a Database Connection in Python

The examples in this chapter assume that iris.IRIS object irispy already exists and is connected to the server. The following code is used to create and connect irispy:

  import iris
  conn = iris.connect('127.0.0.1', 51773, 'USER', '_SYSTEM', 'SYS')
  irispy = iris.createIRIS(conn)

For more information, see the Quick Reference entries for iris package functions connect() and createIRIS().

Introduction to Global Arrays

A global array, like all sparse arrays, is a tree structure rather than a sequential list. The basic concept behind global arrays can be illustrated by analogy to a file structure. Each directory in the tree is uniquely identified by a path composed of a root directory identifier followed by a series of subdirectory identifiers, and any directory may or may not contain data.

Global arrays work the same way: each node in the tree is uniquely identified by a node address composed of a global name identifier and a series of subscript identifiers, and a node may or may not contain a value. For example, here is a global array consisting of six nodes, two of which contain values:

   root -->|--> foo --> SubFoo='A'
           |--> bar --> lowbar --> UnderBar=123

Values could be stored in the other possible node addresses (for example, root or root->bar), but no resources are wasted if those node addresses are valueless. Unlike a directory structure, all nodes in a global array must have either a value or a subnode with a value. In InterSystems ObjectScript globals notation, the two nodes with values would be:

   root('foo','SubFoo')
   root('bar','lowbar','UnderBar')

In this notation, the global name (root) is followed by a comma-delimited subscript list in parentheses. Together, they specify the entire node address of the node.

This global array could be created by two calls to the Native SDK set() method. The first argument is the value to be assigned, and the rest of the arguments specify the node address:

  irisObject.set('A', 'root', 'foo', 'SubFoo')
  irisObject.set(123, 'root', 'bar', 'lowbar', 'UnderBar')

Global array root is does not exist until the first call assigns value 'A' to node root('foo','SubFoo'). Nodes can be created in any order, and with any set of subscripts. The same global array would be created if we reversed the order of these two calls. The valueless nodes are created automatically, and will be deleted automatically when no longer needed.

The Native SDK code to create this array is demonstrated in the following example. An IRISConnection object establishes a connection to the server. The connection will be used by an instance of iris.IRIS named irispy. Native SDK methods are then used to create a global array, read the resulting persistent values from the database, and delete the global array.

The NativeDemo Program
# Import the Native SDK module
import iris

# Open a connection to the server
args = {'hostname':'127.0.0.1', 'port':52773,
    'namespace':'USER', 'username':'_SYSTEM', 'password':'SYS'
}
conn = iris.connect(**args)
# Create an iris object
irispy = iris.createIRIS(conn)

# Create a global array in the USER namespace on the server
irispy.set('A', 'root', 'foo', 'SubFoo')
irispy.set(123, 'root', 'bar', 'lowbar', 'UnderBar')

# Read the values from the database and print them
subfoo_value = irispy.get('root', 'foo', 'SubFoo')
underbar_value = irispy.get('root', 'bar', 'lowbar', 'UnderBar')
print('Created two values: ')
print('   root("foo","SubFoo")=', subfoo_value)
print('   root("bar","lowbar","UnderBar")=', underbar_value)

# Delete the global array and terminate
irispy.kill('root') # delete global array root
conn.close()

NativeDemo prints the following lines:

  Created two values:
     root('foo','SubFoo')="A"
     root('bar','lowbar','UnderBar')=123

In this example, Native SDK iris package methods are used to connect to the database and to create irispy, which is the instance of iris.IRIS the contains the connection object. Native SDK methods perform the following actions:

  • iris.connect() creates a connection object named conn, connected to the database associated with the USER namespace.

  • iris.createIRIS() creates a new instance of iris.IRIS named irispy, which will access the database through server connection conn.

  • iris.IRIS.set() creates new persistent nodes in database namespace USER.

  • iris.IRIS.get() returns the values of the specified nodes.

  • iris.IRIS.kill() deletes the specified root node and all of its subnodes from the database.

  • iris.IRISConnection.close() closes the connection.

See “iris Package Methods” for details about connecting and creating an instance of iris.IRIS. See “Fundamental Node Operations” for more information about set(), get(), and kill().

This simple example doesn’t cover more advanced topics such as iteration. See “Class iris.IRISGlobalNode” for information on how to create and iterate over complex global arrays.

Glossary of Global Array Terms

See the previous section for an overview of the concepts listed here. Examples in this glossary will refer to the global array structure listed below. The Legs global array has ten nodes and three node levels. Seven of the ten nodes contain values:

  Legs                       # root node, valueless, 3 child nodes
    fish = 0                 # level 1 node, value=0
    mammal                   # level 1 node, valueless
      human = 2              # level 2 node, value=2
      dog = 4                # level 2 node, value=4
    bug                      # level 1 node, valueless, 3 child nodes
      insect = 6             # level 2 node, value=6
      spider = 8             # level 2 node, value=8
      millipede = Diplopoda  # level 2 node, value="Diplopoda", 1 child node
        centipede = 100      # level 3 node, value=100
Child node

The nodes immediately under a given parent node. The address of a child node is specified by adding exactly one subscript to the end of the parent subscript list. For example, parent node Legs('mammal') has child nodes Legs('mammal','human') and Legs('mammal','dog').

Global name

The identifier for the root node is also the name of the entire global array. For example, root node identifier Legs is the global name of global array Legs. Unlike subscripts, global names can only consist of letters, numbers, and periods (see Global Naming Rules).

Node

An element of a global array, uniquely identified by a namespace consisting of a global name and an arbitrary number of subscript identifiers. A node must either contain a value, have child nodes, or both.

Node level

The number of subscripts in the node address. A ‘level 2 node’ is just another way of saying ‘a node with two subscripts’. For example, Legs('mammal','dog') is a level 2 node. It is two levels under root node Legs and one level under Legs('mammal').

Node address

The complete namespace of a node, consisting of the global name and all subscripts. For example, node address Legs('fish') consists of root node identifier Legs plus a list containing one subscript, 'fish'. Depending on context, Legs (with no subscript list) can refer to either the root node address or the entire global array.

Root node

The unsubscripted node at the base of the global array tree. The identifier for a root node is its global name with no subscripts.

Subnode

All descendants of a given node are referred to as subnodes of that node. For example, node Legs('bug') has four different subnodes on two levels. All nine subscripted nodes are subnodes of root node Legs.

Subscript / Subscript list

All nodes under the root node are addressed by specifying the global name and a list of one or more subscript identifiers. (The global name plus the subscript list is the node address). Subscripts can be bool, bytes, bytearray, Decimal, float, int, or str.

Target address

Many Native SDK methods require you to specify a valid node address that does not necessarily point to an existing node. For example, the set() method takes a value argument and a target address, and stores the value at that address. If no node exists at the target address, a new node is created.

Value

A node can contain a value of type bool, bytes, bytearray, Decimal, float, int, str, IRISList, or None (see Typecast Methods and Supported Datatypes). A node that has child nodes can be valueless, but a node with no child nodes must contain a value.

Valueless node

A node must either contain data, have child nodes, or both. A node that has child nodes but does not contain data is called a valueless node. Valueless nodes only exist as pointers to lower level nodes.

Global Naming Rules

Global names and subscripts obey the following rules:

  • The length of a node address (totaling the length of the global name and all subscripts) can be up to 511 characters. (Some typed characters may count as more than one encoded character for this limit. For more information, see “Maximum Length of a Global Reference” in Using Globals).

  • A global name can include letters, numbers, and periods ('.'), and can have a length of up to 31 significant characters. It must begin with a letter, and must not end with a period.

  • A subscript can be bool, bytes, bytearray, Decimal, float, int, or str. String subscripts are case-sensitive, and can contain any character (including non-printing characters). Subscript length is restricted only by the maximum length of a node address.

Fundamental Node Operations

This section demonstrates how to use the set(), get(), and kill() methods to create, access, and delete nodes. These methods have the following signatures:

  set (value, globalName, subscripts)
  get (globalName, subscripts)
  kill (globalName, subscripts)
  • value can be bool, bytes, bytearray, Decimal, float, int, str, IRISList, or None.

  • globalName can only include letters, numbers, and periods ('.'), must begin with a letter, and cannot end with a period.

  • subscripts can be bool, bytes, bytearray, Decimal, float, int, or str. A string subscript is case-sensitive and can include non-printing characters.

All of the examples in this section assume that a connected instance of IRIS named irispy already exists (see “Creating a Connection in Python”).

Setting and changing node values

iris.IRIS.set() takes value, globalname, and *subscripts arguments and stores the value at the specified node address. If no node exists at that address, a new one is created.

In the following example, the first call to set() creates a new node at subnode address myGlobal('A') and sets the value of the node to string 'first'. The second call changes the value of the subnode, replacing it with integer 1.

  irispy.set('first','myGlobal','A')  # create node myGlobal('A') = 'first'
  irispy.set(1,'myGlobal','A')        # change value of myGlobal('A') to 1.
Retrieving node values with get()

iris.IRIS.get() takes globalname and *subscripts arguments and returns the value stored at the specified node address, or None if there is no value at that address.

  irispy.set(23,'myGlobal','A')
  value_of_A = irispy.get('myGlobal','A')

The get() method returns an untyped value. To return a specific datatype, use one of the IRIS.get() typecast methods. The following methods are available: getBoolean(), getBytes(), getDecimal() getFloat() getInteger(), getString(), getIRISList(), and getObject().

Deleting a node or group of nodes

iris.IRIS.kill() — deletes the specified node and all of its subnodes. The entire global array will be deleted if the root node is deleted or if all nodes with values are deleted.

Global array myGlobal initially contains the following nodes:

   myGlobal = <valueless node>
     myGlobal('A') = 0
       myGlobal('A',1) = 0
       myGlobal('A',2) = 0
     myGlobal('B') = <valueless node>
       myGlobal('B',1) = 0

This example will delete the global array by calling kill() on two of its subnodes. The first call will delete node myGlobal('A') and both of its subnodes:

  irispy.kill('myGlobal','A')    # also kills myGlobal('A',1) and myGlobal('A',2)

The second call deletes the last remaining subnode with a value, killing the entire global array:

  irispy.kill('myGlobal','B',1)  # deletes last value in global array myGlobal
  • The parent node, myGlobal('B'), is deleted because it is valueless and now has no subnodes.

  • Root node myGlobal is valueless and now has no subnodes, so the entire global array is deleted from the database.

Iteration with nextSubscript() and isDefined()

In ObjectScript, the standard iteration methods are $ORDER and $DATA. The Native SDK provides corresponding methods nextSubscript() and isDefined() for those who wish to emulate the ObjectScript methods.

The IRIS.nextSubscript() method (corresponds to $ORDER) is a much less powerful iteration method than node(), but it works in much the same way, iterating over a set of nodes under the same parent. Given a node address and direction of iteration, it returns the subscript of the next node under the same parent as the specified node, or None if there are no more nodes in the indicated direction.

The IRIS.isDefined() method (corresponds to $DATA) can be used to determine if a specified node has a value, a subnode, or both. It returns one of the following values:

  • 0 — the specified node does not exist

  • 1 — the node exists and has a value

  • 10 — the node is valueless but has a child node

  • 11 — the node has both a value and a child node

The returned value can be used to determine several useful boolean values:

   exists = (irispy.isDefined(root,subscripts) > 0)
   hasValue = (irispy.isDefined(root,subscripts) in [1,11]) # [value, value+child]
   hasChild = (irispy.isDefined(root,subscripts) in [10,11]) # [child, value+child]
Find sibling nodes and test for children

The following code uses nextSubscript() to iterate over nodes under heroes('dogs'), starting at heroes('dogs',chr(0)) (the first possible subscript). It tests each node with isDefined() to see if it has children.

  direction = 0 # direction of iteration (boolean forward/reverse)
  next_sub = chr(0) # start at first possible subscript
  while next_sub != None:
    if (irispy.isDefined('heroes','dogs',next_sub) in [10,11]): # [child, value+child]
      print('   ', next_sub, 'has children')
    next_sub = irispy.nextSubscript(direction,'heroes','dogs',next_sub)
    print('next subscript = ' + str(next_sub) )

Prints:

next subscript = Balto
next subscript = Hachiko
next subscript = Lassie
    Lassie has children
next subscript = Whitefang
next subscript = None
Feedback