Unifying Embedded Python and the Native API with `iris-embedded-python-wrapper`

When developing Python applications with InterSystems IRIS, you can quickly end up with several execution contexts:
- Python launched directly by IRIS with Embedded Python;
- a regular
python3process that loads the Embedded Python libraries from a local IRIS installation; - an external Python application that connects to IRIS through the official native driver.
These three cases are useful, but they do not behave exactly the same way for imports, system configuration, object APIs, and SQL access. The iris-embedded-python-wrapper project provides a stable Python facade to reduce these differences and keep a single entry point: import iris.
The Problem
In a Python project built around IRIS, the same code may need to run in several environments:
- in an IRIS terminal with
iris python iris, oriris session irisfollowed by:py; - in a local Python script launched with
python3; - in a remote Python service that connects to an IRIS instance.
Without an abstraction layer, several details often need to be handled separately:
- the Embedded Python
irismodule is only available when the IRIS runtime is correctly loaded; - the native SDK also exposes an
irispackage, which can create collisions or ambiguous imports; iris.cls(...)and DB-API connections do not follow exactly the same path in embedded and remote modes;- on Linux and macOS, dynamic library paths must be prepared before the Python process starts;
- boundary values such as SQL
NULLand empty strings can be represented differently depending on the backend.
The goal of this wrapper is to let the application declare its execution context clearly while keeping business code close between embedded and native modes.
What the Wrapper Provides
The package provides an import iris facade that keeps the most common entry points available:
iris.cls(...)to access IRIS classes;iris.connect(...)to configure or open a connection depending on the context;iris.dbapifor PEP 249-style SQL access;iris.runtimeto inspect and explicitly control the active mode.
Supported modes are:
| Mode | Description | Example |
|---|---|---|
embedded-kernel |
Python is launched by IRIS | iris python iris or :py |
embedded-local |
python3 loads the Embedded Python libraries from a local IRIS installation |
IRISINSTALLDIR, iris.connect(path=...) |
native-remote |
Python connects to a remote IRIS instance | official native driver intersystems-irispython |
unavailable |
no IRIS backend is available yet | configuration required before use |
Installation
The package can be installed with pip:
pip install iris-embedded-python-wrapper
The project depends on the official driver:
intersystems-irispython>=5.0.0
For embedded mode, you also need a local InterSystems IRIS installation. To access embedded mode from an external Python process, the %Service_CallIn service must be enabled.
Example: Using Embedded Python
In an Embedded Python shell launched by IRIS:
iris python iris
or from an IRIS session:
USER>:py
you can import iris as usual:
import iris
print(iris.system.Version.GetVersion())
print(iris.runtime.state)
In this context, iris.runtime.state should report:
'embedded-kernel'
If you want to force the wrapper from a local project directory:
PYTHONPATH=/path/to/iris-embedded-python-wrapper iris python iris
Example: Loading IRIS from a Local python3
The embedded-local mode lets you run a regular Python script while using the Embedded Python libraries from an IRIS installation.
On Linux:
export IRISINSTALLDIR=/opt/iris
export LD_LIBRARY_PATH=$IRISINSTALLDIR/bin:$LD_LIBRARY_PATH
python3 my_script.py
On macOS:
export IRISINSTALLDIR=/opt/iris
export DYLD_LIBRARY_PATH=$IRISINSTALLDIR/bin:$DYLD_LIBRARY_PATH
python3 my_script.py
The wrapper also lets you explicitly declare the IRIS installation path:
import iris
iris.connect(path="/opt/iris")
obj = iris.cls("Ens.StringRequest")._New()
obj.StringValue = "hello from embedded-local"
Important point: on Unix, iris.connect(path=...) can configure Python paths at runtime, but it cannot retroactively fix dynamic library resolution if the Python process has already started without the correct LD_LIBRARY_PATH or DYLD_LIBRARY_PATH.
Example: Keeping iris.cls(...) in Remote Native Mode
With the native SDK, remote code normally uses an explicit IRIS handle:
import iris
conn = iris.connect("localhost", 1972, "USER", "SuperUser", "<password>")
db = iris.createIRIS(conn)
req = db.classMethodValue("Ens.StringRequest", "%New")
db.set(req, "StringValue", "hello")
value = db.get(req, "StringValue")
With the wrapper, you can bind the native connection once and keep a syntax close to Embedded Python:
import iris
conn = iris.connect("localhost", 1972, "USER", "SuperUser", "<password>")
iris.runtime.configure(native_connection=conn)
req = iris.cls("Ens.StringRequest")._New()
req.StringValue = "hello"
value = req.StringValue
The native proxy converts the first _ into %, which lets you write _New() in Python to call %New.
Controlling the Runtime Explicitly
iris.runtime is the wrapper's source of truth for the active context.
import iris
ctx = iris.runtime.get()
print(ctx.mode)
print(ctx.state)
print(ctx.embedded_available)
Useful properties include:
iris.runtime.mode: selected policy, for exampleauto,embedded, ornative;iris.runtime.state: detected state, for exampleembedded-kernel,embedded-local,native-remote, orunavailable;iris.runtime.embedded_available: whether the embedded backend is usable;iris.runtime.iris: native IRIS handle bound to the runtime, if available;iris.runtime.dbapi: explicitly bound DB-API connection, if available.
You can also force a mode:
import iris
iris.runtime.configure(mode="embedded")
or reset to automatic detection:
iris.runtime.reset()
SQL Access with iris.dbapi
The wrapper exposes a DB-API facade compatible with common usage:
connect();cursor();execute();fetchone(),fetchmany(),fetchall();commit(),rollback(),close();- PEP 249 exceptions such as
InterfaceError,OperationalError, etc.
In embedded mode:
import iris
conn = iris.dbapi.connect(mode="embedded")
cur = conn.cursor()
cur.execute("SELECT Name FROM Sample.Person")
rows = cur.fetchall()
cur.close()
conn.close()
In embedded-local mode with an explicit path:
import iris
conn = iris.dbapi.connect(path="/opt/iris", namespace="USER")
cur = conn.cursor()
cur.execute("SELECT 1")
print(cur.fetchone())
In remote native mode:
import iris
conn = iris.dbapi.connect(
mode="native",
hostname="localhost",
port=1972,
namespace="USER",
username="SuperUser",
password="<password>",
)
cur = conn.cursor()
cur.execute("SELECT 1")
print(cur.fetchone())
In auto mode, the wrapper chooses the backend based on the provided arguments and the state of iris.runtime. For example, if you provide hostname, port, namespace, username, and password, the connection is routed to the native driver.
The wrapper also normalizes some embedded cases to bring behavior closer to the native DB-API:
- SQL
NULLbecomes PythonNone; - an empty SQL string becomes
""; Nonepassed as a parameter remains SQLNULL;""passed as a parameter remains an empty SQL string.
Binding a Virtual Environment to Embedded Python
The project provides two practical commands:
bind_iris
unbind_iris
bind_iris looks for the Python library of the current virtual environment, updates the IRIS Embedded Python configuration, and creates a backup of the iris.cpf file.
Example:
python3 -m venv .venv
. .venv/bin/activate
pip install iris-embedded-python-wrapper
bind_iris
Depending on the platform and IRIS configuration, IRIS administrator privileges may be required. On Windows, restarting the IRIS instance may be required after changing the configuration.
When Should You Use This Wrapper?
This wrapper is useful if you want to:
- write Python IRIS code that can run in embedded or remote mode;
- reduce differences between Embedded Python and the Native API;
- make the execution mode observable through
iris.runtime; - use a single DB-API facade in application code;
- simplify virtual environment configuration for Embedded Python;
- test embedded and remote paths more easily.
It is not a replacement for InterSystems IRIS or the official native SDK. It is a convenience layer that builds on top of those components to make application code more portable.
Resources
- GitHub project: https://github.com/grongierisc/iris-embedded-python-wrapper
- Project documentation: https://github.com/grongierisc/iris-embedded-python-wrapper/blob/master/README.md
- Installation:
pip install iris-embedded-python-wrapper - License: MIT
Conclusion
iris-embedded-python-wrapper provides a simple facade around the main Python execution modes of InterSystems IRIS. The main benefit is being able to write:
import iris
and then explicitly decide whether the code runs in Embedded Python, embedded-local mode, or through a remote native connection. For applications that need to move from one mode to another, or for teams that want to share more code between local scripts, embedded jobs, and external Python services, this approach removes a lot of technical noise.