Écrit par

Sales Engineer at InterSystems
Article Guillaume Rongier · 2 h il y a 8m read

Unifier Embedded Python et Native API avec `iris-embedded-python-wrapper`

 

Quand on développe en Python avec InterSystems IRIS, on peut rapidement se retrouver avec plusieurs contextes d'exécution :

  • Python lancé directement par IRIS avec Embedded Python ;
  • un python3 classique qui charge les bibliothèques Embedded Python d'une installation IRIS locale ;
  • une application Python externe qui se connecte à IRIS via le pilote natif officiel.

Ces trois cas sont utiles, mais ils n'ont pas exactement le même comportement côté imports, configuration système, API objet et accès SQL. Le projet iris-embedded-python-wrapper propose une façade Python stable pour réduire ces différences et garder un même point d'entrée : import iris.

Le problème

Dans un projet Python autour d'IRIS, le même code peut devoir tourner dans plusieurs environnements :

  • dans un terminal IRIS avec iris python iris ou iris session iris puis :py ;
  • dans un script Python local lancé avec python3 ;
  • dans un service Python distant qui se connecte à une instance IRIS.

Sans couche d'abstraction, il faut souvent traiter séparément plusieurs détails :

  • le module iris d'Embedded Python n'est disponible que lorsque le runtime IRIS est correctement chargé ;
  • le SDK natif expose aussi un package iris, ce qui peut créer des collisions ou des imports ambigus ;
  • iris.cls(...) et la connexion DB-API ne suivent pas exactement les mêmes chemins entre embedded et remote ;
  • sous Linux et macOS, les chemins de bibliothèques dynamiques doivent être préparés avant le démarrage du processus Python ;
  • certaines valeurs frontières, par exemple SQL NULL et chaîne vide, peuvent être représentées différemment selon le backend.

L'objectif de ce wrapper est de permettre à l'application de déclarer clairement son contexte d'exécution, tout en conservant un code métier proche entre les modes embedded et natif.

Ce que fournit le wrapper

Le package fournit une façade import iris qui garde disponibles les points d'entrée les plus utilisés :

  • iris.cls(...) pour accéder aux classes IRIS ;
  • iris.connect(...) pour configurer ou ouvrir une connexion selon le contexte ;
  • iris.dbapi pour un accès SQL de type PEP 249 ;
  • iris.runtime pour inspecter et piloter explicitement le mode actif.

Les modes pris en charge sont :

Mode Description Exemple
embedded-kernel Python est lancé par IRIS iris python iris ou :py
embedded-local python3 charge les bibliothèques Embedded Python d'une installation IRIS locale IRISINSTALLDIR, iris.connect(path=...)
native-remote Python se connecte à une instance IRIS distante pilote natif officiel intersystems-irispython
unavailable aucun backend IRIS n'est encore disponible configuration requise avant usage

Installation

Le package est installable avec pip :

pip install iris-embedded-python-wrapper

Le projet dépend du pilote officiel :

intersystems-irispython>=5.0.0

Pour le mode embedded, il faut aussi une installation InterSystems IRIS disponible localement. Pour un accès embedded depuis un processus Python externe, le service %Service_CallIn doit être activé.

Exemple : utiliser Embedded Python

Dans un shell Embedded Python lancé par IRIS :

iris python iris

ou depuis une session IRIS :

USER>:py

on peut importer iris comme d'habitude :

import iris

print(iris.system.Version.GetVersion())
print(iris.runtime.state)

Dans ce contexte, iris.runtime.state doit indiquer :

'embedded-kernel'

Si vous voulez forcer l'utilisation du wrapper depuis un répertoire local du projet :

PYTHONPATH=/path/to/iris-embedded-python-wrapper iris python iris

Exemple : charger IRIS depuis un python3 local

Le mode embedded-local permet de lancer un script Python classique tout en utilisant les bibliothèques Embedded Python d'une installation IRIS.

Sous Linux :

export IRISINSTALLDIR=/opt/iris
export LD_LIBRARY_PATH=$IRISINSTALLDIR/bin:$LD_LIBRARY_PATH
python3 my_script.py

Sous macOS :

export IRISINSTALLDIR=/opt/iris
export DYLD_LIBRARY_PATH=$IRISINSTALLDIR/bin:$DYLD_LIBRARY_PATH
python3 my_script.py

Le wrapper permet aussi de déclarer explicitement le chemin de l'installation IRIS :

import iris

iris.connect(path="/opt/iris")

obj = iris.cls("Ens.StringRequest")._New()
obj.StringValue = "hello from embedded-local"

Point important : sous Unix, iris.connect(path=...) peut configurer les chemins Python au runtime, mais il ne peut pas corriger rétroactivement la résolution des bibliothèques dynamiques si le processus Python a déjà démarré sans LD_LIBRARY_PATH ou DYLD_LIBRARY_PATH correct.

Exemple : garder iris.cls(...) en mode natif distant

Avec le SDK natif, le code distant utilise normalement un handle IRIS explicite :

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")

Avec le wrapper, on peut lier la connexion native une fois, puis garder une syntaxe proche d'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

Le proxy natif convertit le premier _ en %, ce qui permet d'écrire _New() en Python pour appeler %New.

Piloter le runtime explicitement

iris.runtime est la source de vérité du wrapper pour savoir ce qui est actif.

import iris

ctx = iris.runtime.get()

print(ctx.mode)
print(ctx.state)
print(ctx.embedded_available)

Quelques propriétés utiles :

  • iris.runtime.mode : politique sélectionnée, par exemple auto, embedded ou native ;
  • iris.runtime.state : état détecté, par exemple embedded-kernel, embedded-local, native-remote ou unavailable ;
  • iris.runtime.embedded_available : indique si le backend embedded est utilisable ;
  • iris.runtime.iris : handle IRIS natif lié au runtime, si disponible ;
  • iris.runtime.dbapi : connexion DB-API liée explicitement, si disponible.

On peut aussi forcer un mode :

import iris

iris.runtime.configure(mode="embedded")

ou revenir à la détection automatique :

iris.runtime.reset()

Accès SQL avec iris.dbapi

Le wrapper expose une façade DB-API compatible avec les usages courants :

  • connect() ;
  • cursor() ;
  • execute() ;
  • fetchone(), fetchmany(), fetchall() ;
  • commit(), rollback(), close() ;
  • exceptions PEP 249 comme InterfaceError, OperationalError, etc.

En mode embedded :

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()

En mode embedded-local avec chemin explicite :

import iris

conn = iris.dbapi.connect(path="/opt/iris", namespace="USER")
cur = conn.cursor()
cur.execute("SELECT 1")
print(cur.fetchone())

En mode natif distant :

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())

En mode auto, le wrapper choisit le backend selon les arguments fournis et l'état de iris.runtime. Par exemple, si vous fournissez hostname, port, namespace, username et password, la connexion est routée vers le pilote natif.

Le wrapper normalise aussi certains cas embedded pour rapprocher le comportement de la DB-API native :

  • SQL NULL devient None en Python ;
  • une chaîne SQL vide devient "" ;
  • None envoyé comme paramètre reste SQL NULL ;
  • "" envoyé comme paramètre reste une chaîne vide SQL.

Lier un environnement virtuel à Embedded Python

Le projet fournit deux commandes pratiques :

bind_iris
unbind_iris

bind_iris recherche la bibliothèque Python du virtualenv courant, met à jour la configuration Embedded Python d'IRIS et crée une sauvegarde du fichier iris.cpf.

Exemple :

python3 -m venv .venv
. .venv/bin/activate
pip install iris-embedded-python-wrapper
bind_iris

Selon la plateforme et la configuration IRIS, des droits administrateur IRIS peuvent être nécessaires. Sous Windows, un redémarrage de l'instance IRIS peut être requis après modification de la configuration.

Quand utiliser ce wrapper ?

Ce wrapper est utile si vous voulez :

  • écrire du code Python IRIS qui peut tourner en embedded ou en distant ;
  • réduire les différences entre Embedded Python et Native API ;
  • rendre le mode d'exécution observable avec iris.runtime ;
  • utiliser une DB-API unique côté application ;
  • simplifier la configuration d'un virtualenv Python pour Embedded Python ;
  • tester plus facilement les chemins embedded et remote.

Ce n'est pas un remplacement d'InterSystems IRIS ni du SDK natif officiel. C'est une couche de confort qui s'appuie sur ces composants pour rendre le code applicatif plus portable.

Ressources

Conclusion

iris-embedded-python-wrapper apporte une façade simple autour des principaux modes Python d'InterSystems IRIS. Le bénéfice principal est de pouvoir écrire :

import iris

puis de décider explicitement si le code s'exécute en Embedded Python, en embedded-local ou via une connexion native distante. Pour les applications qui doivent passer d'un mode à l'autre, ou pour les équipes qui veulent partager plus de code entre scripts locaux, jobs embedded et services Python externes, cette approche réduit beaucoup de bruit technique.