Skip to content

Using Embedded Python with InterSystems IRIS


Python can be used in InterSystems IRIS classes with no significant performance cost.

Embedded Python

Since InterSystems IRIS 2022.1, Python has been fully integrated into the InterSystems IRIS kernel and can be used with similar performance to ObjectScript. This 'Embedded Python' modality means Python runs locally to the data, reducing the latency cost of transferring the data.

Embedded Python differs from other connection methods ( Connecting to InterSystems IRIS from Client-Side Python Applications page for more detail.) because the Python kernel is running on the same machine as the data.

Prerequisites

To maximize learning from this guide, it is recommended to test the code provided on your own InterSystems IRIS instance. The easiest way to run an InterSystems IRIS sandbox is to use InterSystems IRIS Community Edition in a docker container, which can be run with the following command. For more information see running IRIS see Get InterSystems IRIS Community with Docker or Get InterSystems IRIS Community Edition with an Install Kit.

docker run --name my-iris --publish 1972:1972 --publish 52773:52773 -d intersystems/iris-community:latest-em

Throughout this guide, there will be references for IRIS classes, many of which you may wish to import onto your IRIS instance. For information on how to do this, see Intro To InterSystems IRIS Classes or Setting Up your Development Environment in VS Code.

Installing Packages

A major benefit of using Python with InterSystems IRIS is allowing access to the wide ecosystem of Python Libraries available to be installed from PyPI. Packages can be installed with pip, however, they need to be installed to a specific target directory. Standard pip commands will result in an error message: error: externally-managed-environment ...

To install Python libraries for use in InterSystems IRIS classes, specify the install target with the --target flag. The target is the {IRIS-INSTALL-DIR}/mgr/python.

The following shows the install command for the package numpy with the standard install location.

pip install --target /usr/irissys/mgr/python numpy 

If you are calling into IRIS from Python Files, as described in the Calling IRIS from Python Files section below, you can also create and use virtual environments in the standard way, the details for this are included in that section.

Modalities

There are several modalities for using Embedded Python with InterSystems IRIS, the choice of which will depend on the use case. Python methods can be called directly from ObjectScript. Alternatively, methods and class methods within Python classes can be defined in Python, or Python can be accessed in standalone Python files and connect directly to InterSystems IRIS. You can also write custom SQL functions in Python (this won't be covered in this guide here, for more information see the relevant section of documentation).

For Python-first development, it is recommended to use standalone Python files as this is the only Embedded Python modality that allows a native Python developer environment, including using linters, debuggers and complete error messages. However, other modalities allow easy integration of Python methods into existing workflows, including directly into ObjectScript code.

Using Python From ObjectScript

Python can be used from ObjectScript, including importing specific Python modules. This modality is best if you have an ObjectScript method, but require functionality which is easier to implement in Python. This uses the class %SYS.Python to import the Python module. The following example shows the standard Python Library base64 being used to decode an encoded string.

// Import base64 Python Library
set base64 =  ##class(%SYS.Python).Import("base64")

set encodedString = "SGVsbG8sIFdvcmxkIQ=="

// Decode the Base64-encoded string using the imported Python module
set decodedString = base64.b64decode(encodedString)

// Write the decoded string
write decodedString

In this example, a Python module base64 is imported and then the functions from this module can be used as expected.

To use built-in Python classes and functions, including datatypes, you can import builtins, the Python module of built in classes.

// Import the Python built-ins
set builtins = ##class(%SYS.Python).Import(builtins)

// Create a python list
set mylist = builtins.list()

do mylist.append(1)
do mylist.append(2)
do mylist.append("foo")
do mylist.append("bar")

// Use Python Print
do builtins.print(mylist)

// Outputs: [1, 2, 'foo', 'bar']

To use custom Python files, you need to add the path to the file to the Python `sys` path, which is where Python searches for modules. This is shown in detail in the Calling IRIS from Python Files section below.

Python In IRIS Classes

Internal class methods and methods can be written in Python by adding a keyword tag [language=python] after a method definition. This method is useful for short methods or integrating methods into existing workflows.

Class packagename.PythonClass Extends %RegisteredObject
{

Property Name As %String;

Parameter CONSTANTNAME = 2;

ClassMethod PythonClassMethod(a As %Integer, b As %Integer) [ Language = python ]
{
 
	import iris
	
	## Access the constant
	constant = iris.cls(__name__)._GetParameter('CONSTANTNAME')
	
	value = (a+b) * constant
	
	s = f"The result of ' ({a} + {b}) X {constant} ' is {value}"
	print(s)
}

Method PythonMethod(pGreeting As %String) [ Language = python ]
{
    # Access Object properties
    print(pGreeting +" "+ self.Name)
}

}

You could use this class from the ObjectScript terminal like normal:

do ##class(packagename.PythonClass).PythonClassMethod(1, 2) 
// prints "The result of ' (1 + 2) X 2 ' is 6"
set obj = ##class(packagename.PythonClass).%New()

set obj.Name = "John"
do obj.PythonMethod("Hello")
// prints "Hello John"

Calling IRIS From Python Files

Embedded Python can be used in standalone Python files, which can be called from within InterSystems IRIS classes or executed from the terminal in the same way as normal Python files. This method is best for full Python-first development, as it allows standard Python development tools (e.g. linters and debuggers) to be used.

Setup

Calling into IRIS from Embedded Python requires some environmental set-up to allow access to IRIS from Python. This section will show how this can be done from a bash terminal on the machine running InterSystems IRIS.

If you wish to skip the set-up steps, there is an Embedded Python Template available on the developer community which allows you to immediately build and run a docker container with the environment pre-configured, skipping the set up below.

If you are running InterSystems IRIS in a docker container as recommended, use the following command on your standard terminal (or Powershell if you are running Windows) to start a bash terminal within your docker container.

docker exec -it my-iris bash

Environmental Variables

Before using Embedded Python, certain environmental variables need to be set which may not be set by default. Run the following commands in the terminal to ensure the correct Python kernel is being accessed. This assumes InterSystems IRIS is installed in the default location, if this is incorrect, please change the IRISINSTALLDIR variable before running.

# Set the IRIS install location. This is the default for docker containers. 
export IRISINSTALLDIR=/usr/irissys

# Add the binaries to the PATH
export PATH=$PATH:$IRISINSTALLDIR/bin

# Set the default Python to the IRISPYTHON kernel
export PYTHONPATH=$IRISINSTALLDIR/lib/python

At this point, you can use Python, you need to use irispython as the command. If you try to import the iris module, the connection will likely be rejected, because the service is not enabled, and your credentials are not set.

Along with the install locations, to connect to IRIS from Python, we also need to set the credentials to log in to IRIS. If you are using a Docker container sandbox, you can use the following default Namespace and passwords as follows, otherwise change these commands to your user password. You can disable authentication on the embedded python, as detailed in the section below, this is not recommended for production.

# The Namespace to connect to 
export IRISNAMESPACE=USER

# Username
export IRISUSERNAME=SuperUser

# Password
export IRISPASSWORD=SYS

These will need to be set in every new terminal instance or login. To make these accessible by default, these can be added to .bashrc, which is loaded every time a new terminal is opened. A command to add them to your .bashrc is shown below. Alternatively, you can add these environmental variables to a Dockerfile or Docker run command (not shown).

cat >> ~/.bashrc << 'EOF'
export IRISINSTALLDIR=/usr/irissys
export PATH="$PATH:$IRISINSTALLDIR/bin"
export PYTHONPATH="$IRISINSTALLDIR/lib/python"
export IRISNAMESPACE=USER
export IRISUSERNAME=SuperUser
export IRISPASSWORD=SYS
EOF

# Load the .bashrc 
source ~/.bashrc

Enable Service Call In

Finally, the service by which Python files call into InterSystems IRIS is called %Service_CallIn. This is, by default, disabled in a new InterSystems IRIS instance. Before connecting to InterSystems IRIS from Python (with Embedded Python), this service needs to be enabled. This can either be done from the terminal or the Management Portal.

To do this from the terminal, first open an IRIS terminal:

iris session iris

Then run:

// Change namespace to %SYS
set $NAMESPACE = "%SYS"

// Save the current settings to a new array called `prop`
do ##class(Security.Services).Get("%Service_CallIn",.prop)

// Enable the service
set prop("Enabled")=1

// Uncomment to Allow unauthenticated access (removes the need for environmental credentials)
// set prop("AutheEnabled")=48

// Modify the settings using our edited settings array
do ##class(Security.Services).Modify("%Service_CallIn",.prop)

From the Management Portal, you can go to System Administration -> Security -> Services -> Go, then select %Service_CallIn from the list, click the Service Enabled checkbox and press Save.

Using Virtual Environments

As mentioned in the prerequisites section above, global package installs with pip need to include a specific target. When running Python files which call into IRIS, you can create an activate a virtual Python environment, which can be used normally. Virtual environments are effective for separating requirements used for specific projects.

When you've activated a virtual environment (using the correct Python kernel), you can install packages into the environment with standard pip installs. This process is shown below:

# Create a virtual environment called .venv in the current directory
irispython -m venv .venv

# Activate the virtual environment
source .venv/bin/activate

# Install packages as normal
pip install numpy

Running Python Files

After the set-up above, Python can be used as irispython, including creating a Python shell. For example, if we had the following function defined in a file at /home/irisowner/python_files/hello_world.py:

import iris
def main(name):

    res = iris.sql.exec("SELECT 'Hello From IRIS-SQL'")
    print([x for x in res])

    print("Hello World from Python")
    return "Hello World from "+name

if __name__=="__main__":
    print(main("Bash Terminal"))

We could run this with:

irispython /home/irisowner/python_files/hello_world.py

# Outputs: 
# [['Hello From IRIS-SQL']]
# Hello World from Python
# Hello World from Bash Terminal

You can also run Python files from IRIS classes. To do this, you need to use the Python module sys to add the path to the file to the module search path. The following IRIS class shows this using both an ObjectScript class method and a Python class method:

Class sample.RunPythonFile Extends %RegisteredObject
{

ClassMethod RunFromPython() As %Status [ Language = python ]
{
    # Import the Python sys module
    import sys
    
    # Add the path to the file to the module search path  
    sys.path.append("/home/irisowner/python_files")
    
    # Import the file
    import hello_world 

    # Call the main function
    result = hello_world.main("IRIS ClassMethod in Python")
    
    # Print Result
    print(result)
}

ClassMethod RunFromObjectScript() As %Status
{
    // Import the Python sys module
    set sys = ##class(%SYS.Python).Import("sys")
    
    // Add the path to the file to the module search path 
    do sys.path.append("/home/irisowner/python_files")

    // Import the file
    set helloworld = ##class(%SYS.Python).Import("hello_world")

    // Call the main function
    set result = helloworld.main("IRIS ClassMethod in ObjectScript")
    
    // Print Result
    write result,!
    quit $$$OK
}

}

We can then run these class methods from the IRIS terminal with:

do ##class(sample.RunPythonFile).RunFromObjectScript()
// Output: 
// [['Hello From IRIS-SQL']]
// Hello World from Python
// Hello World IRIS ClassMethod in ObjectScript

do ##class(sample.RunPythonFile).RunFromPython()
// Output: 
// [['Hello From IRIS-SQL']]
// Hello World from Python
// Hello World from IRIS ClassMethod in Python

IRIS Module Reference

The sections above show the different ways you can run Python code in InterSystems IRIS with Embedded Python. This section shows how the iris module in Embedded Python can be used to perform tasks in IRIS. The code in this section can be used irrespective of which of the above modalities you are using, assuming they are set up correctly.

This is meant to be a quick guide to the most common usage for the module, demonstrating how InterSystems IRIS can be controlled directly from Python code. For a complete reference to the module, see the InterSystems IRIS Python Module Reference Documentation.

The Embedded Python IRIS module is imported with import iris, without requiring installation. Please note  import iris is different in Server-side (embedded) Python and client-side Python through the DB-API or IRIS-native API.

Accessing Classes and Objects

To access internal classes, you simply refer to it using with iris.package.class, for example:

import iris

# Run a class method with:
# iris.PackageName.ClassName.ClassMethodName()

iris.packagename.PythonClass.PythonClassMethod() # The class defined above

print(iris._SYS.System.GetInstanceName()) # Prints "IRIS" or the current instance name

The main exception to this simple access syntax is that special characters in a class or method call, e.g. % and $ are replaced by an underscore, for example .%New() in ObjectScript becomes _New() in Python.

We can see this when instantiating class objects. If we had a persistent class sample.Person (run the code in the Using SQL section below to create this class) we could access it as follows:

import iris

new_person = iris.sample.Person._New()
new_person = "Jane Doe"
new_person.Age = 28

person._Save()

Running ObjectScript

You can run ObjectScript code directly with iris.execute:

import iris

iris.execute("write 'Hello World from Objectscript'")
iris.execute("set ^demoGlobal(1) = 'foo'")
iris.execute("zwrite ^demoGlobal")

Using SQL

Like in ObjectScript, SQL can be run in "Embedded" style, where the SQL command is executed directly, or Dynamically, where the statement is prepared with placeholders for values, which can be added at run-time.

Embedded:

import iris

# Drop table (so file can be re-run)
iris.sql.exec("DROP TABLE IF EXISTS sample.Person")

# Create table
iris.sql.exec("CREATE TABLE sample.Person (Name VARCHAR(250), Age INTEGER)")

# Insert Value
iris.sql.exec("INSERT INTO sample.Person (Name, Age) VALUES ('Jane', 26)")

# Query
rs = iris.sql.exec("SELECT Name, AGE FROM sample.Person") 

for row in rs:
    print(row)

# Prints:
# ['Jane', 26]

Dynamic:

import iris

# Use ? as a placeholder
query = 'INSERT INTO sample.Person (Name, Age) VALUES (?, ?)'

# Prepare Query
stmt = iris.sql.prepare(query)

# Define data
people = [("John", 34), ("Amy", 27), ("Peter", 57)]

# Iterate over data
for person in people:

    # Execute statement with data as parameters for the ? placeholders
    stmt.execute(person[0], person[1])    

The SQL result set class also allows you to parse the results directly into a pandas DataFrame. This requires installing pandas first:

pip install --target /usr/irissys/mgr/python pandas

Then, you can output a DataFrame from a query results object with ResultSet.dataframe():

import iris

# Prepare statement using ? as a placeholder
stmt = iris.sql.prepare("SELECT Name, Age FROM sample.Person WHERE Age > ?" )

# pass in argument(s) for placeholder
rs = stmt.execute(age)

# Use the resultset.dataframe() function 
df = rs.dataframe()
print(df.head())

# Prints:
#     name  age
# 0   John   34
# 1  Peter   57