0 Abonnés · 123 Publications

Python est un langage de programmation interprété de haut niveau pour la programmation à usage général. Créé par Guido van Rossum et publié pour la première fois en 1991, Python a une philosophie de conception qui met l'accent sur la lisibilité du code, notamment en utilisant des espaces blancs importants

Site officiel.

Documentation InterSystems sur les liaisons Python.

Article Lorenzo Scalese · Fév 5, 2025 6m read


Salut la Communauté!
Dans cet article, je présenterai le framework web Python Streamlit.
Ci-dessous, vous trouverez les sujets que nous aborderons:

  • 1-Introduction au framework web Streamlit
  • 2-Installation du module Streamlit
  • 3-Lancement de l'application Streamlit
  • 4-Commandes de base de Streamlit
  • 5-Affichage du contenu multimédia 
  • 6-Widgets d'input
  • 7-Affichage des progrès et de l'état
  • 8-Barre latérale et conteneur
  • 9-Visualisation des données
  • 10-Affichage de DataFrame

Commençons donc par le premier sujet.
1-Introduction au framework web Python Streamlit 

Streamlit est un framework Python open-source qui permet aux data scientists et aux ingénieurs en apprentissage automatique de créer des applications web interactives de manière simple et rapide.
Grâce à sa syntaxe simple et à son intégration facile avec les bibliothèques de science des données les plus répandues, Streamlit est devenu la référence en matière de prototypage et de partage de projets.

Pour plus de détails, veuillez consulterDocumentations Streamlit

2-Installation du module Streamlit

Pour commencer à construire notre application Web Streamlit, il est nécessaire d'installer le module à l'aide du programme d'installation de paquets pip.

Pour installer Streamlit, exécutez la commande suivante:

pip install streamlit

La commande pour tester l'installation se trouve ci-dessous: 

streamlit hello


Lorsque la commande mentionnée ci-dessus est saisissée dans le terminal, la page suivante s'ouvre automatiquement:
Install Streamlit 3

 

3-Lancement de l'application Streamlit

Streamlit est simple à utiliser. Tout d'abord, on ajoute quelques commandes Streamlit à un script Python ordinaire, puis on le lance à l'aide de streamlit run:

streamlit run your_python_file.py 

Aussitôt que vous exécutez le script, un serveur Streamlit local s'active et votre application s'ouvre dans un nouvel onglet de votre navigateur web par défaut.  Notez que l'application est votre canevas, où vous dessinerez des graphiques, des textes, des widgets, des tableaux, etc.

Une autre façon d'exécuter Streamlit est de le faire en tant que module Python. Cela peut s'avérer utile pour configurer un IDE, par exemple PyCharm, afin qu'il fonctionne avec Streamlit:

python -m streamlit run your_python_file.py

N'oubliez pas de sauvegarder le fichier source chaque fois que vous souhaitez mettre à jour votre application. Dans ce cas, Streamlit détecte les changements éventuels et vous demande si vous souhaitez réexécuter votre application. Sélectionnez "Always rerun" (toujours réexécuter) en haut à droite de votre écran pour mettre à jour automatiquement votre application à chaque fois que vous modifiez son code source. Cela vous permettra de travailler dans une boucle interactive rapide : vous saisissez du code, vous l'enregistrez, vous l'essayez en direct, puis vous saisissez à nouveau du code, vous l'enregistrez, vous l'essayez, et ainsi de suite jusqu'à ce que vous soyez satisfait des résultats. Cette boucle étroite entre le codage et la visualisation des résultats en direct est l'une des façons dont Streamlit vous facilite la vie.

4-Commandes de base de Streamlit

Cette fonction permet d'ajouter à une application web des chaînes formatées, des graphiques de Matplotlib, des diagrammes d'Altair, des graphiques de Plotly, des cadres de données, des modèles de Keras et beaucoup d'autres.

Créons le fichier main.py ci-dessous:

import streamlit as st

st.write("Hello ,let's learn how to build a streamlit app together")

Lancez le fichier main.py en exécutant la commande suivante:

streamlit run main.py


st.title() : Cette fonction permet d'ajouter un titre à l'application.
st.header() : Cette fonction est utilisée pour attribuer l'en-tête d'une section.
st.markdown() : Cette fonction est utilisée pour définir la démarque d'une section. 
st.subheader(): Cette fonction est utilisée pour définir le sous-titre d'une section.
st.caption(): Cette fonction est utilisée pour écrire des légendes.
st.code(): Cette fonction est utilisée pour définir un code.  
st.latex(): Cette fonction affiche des expressions mathématiques formatées en LaTeX. 

import streamlit as st

st.title("This is the app title") st.header("This is the header") st.markdown("This is the markdown") st.subheader("This is the subheader") st.caption("This is the caption") st.code("x = 2021") st.latex(r''' a+a r^1+a r^2+a r^3 ''')

Display texts with Streamlit 2

5-Affichage du contenu multimédia 

Nous avons énuméré ci-dessous quelques fonctions permettant d'afficher des images, des vidéos et des fichiers audio.

st.image(): Cette fonction est utilisée pour représenter une image.
st.audio(): Cette fonction est utilisée pour afficher un fichier audio. 
st.video(): Cette fonction est utilisée pour afficher une vidéo.

import streamlit as st

st.subheader("Image :") st.image("kid.jpg")

st.subheader("Audio :") st.audio("audio.mp3")

st.subheader("Video :") st.video("video.mp4")


6-Widgets d'input

Les widgets sont les composants les plus importants de l'interface utilisateur. Streamlit dispose de plusieurs widgets qui vous permettent de créer de l'interactivité directement dans vos applications grâce à des boutons, des curseurs, des saisies de texte, etc.

st.checkbox(): Cette fonction renvoie une valeur booléenne. Lorsque la case est cochée, elle renvoie la valeur True (vrai). Sinon, elle renvoie une valeur False (Faux).
st.button(): Cette fonction permet d'afficher un widget de type bouton. 
st.radio(): Cette fonction permet d'afficher un widget de type bouton radio 
st.selectbox(): Cette fonction permet d'afficher un widget de type sélection. 
st.multiselect(): Cette fonction est utilisée pour afficher un widget de sélection multiple. 
st.select_slider(): Cette fonction est utilisée pour afficher un widget de sélection. 
st.slider(): Cette fonction est utilisée pour afficher un widget de courseur.

import streamlit as st

st.checkbox('Yes') st.button('Click Me') st.radio('Pick your gender', ['Male', 'Female']) st.selectbox('Pick a fruit', ['Apple', 'Banana', 'Orange']) st.multiselect('Choose a planet', ['Jupiter', 'Mars', 'Neptune']) st.select_slider('Pick a mark', ['Bad', 'Good', 'Excellent']) st.slider('Pick a number', 0, 50)

st.number_input(): Cette fonction affiche un widget de saisie numérique..
st.text_input(): Cette fonction expose un widget de saisie de texte.
st.date_input(): Cette fonction affiche un widget de saisie de date pour choisir une date..
st.time_input(): Cette fonction affiche un widget de saisie de l'heure pour choisir une heure.
st.text_area(): Cette fonction montre un widget de saisie de texte avec plus d'une ligne de texte.
st.file_uploader(): Cette fonction est exploitée pour démontrer un widget de téléchargement de fichiers.
st.color_picker(): Cette fonction est exploitée pour démontrer un widget de téléchargement de fichiers.

import streamlit as st

st.number_input('Pick a number', 0, 10) st.text_input('Email address') st.date_input('Traveling date') st.time_input('School time') st.text_area('Description') st.file_uploader('Upload a photo') st.color_picker('Choose your favorite color')

7-Affichage des progrès et de l'état

Nous allons maintenant expliquer comment ajouter une barre de progression et des messages d'état tels qu'erreur et succès à notre application.

st.balloons(): Cette fonction est utilisée pour afficher des ballons de célébration. 
st.progress(): Cette fonction est utilisée pour afficher une barre de progression. 
st.spinner(): Cette fonction est utilisée pour afficher un message d'attente temporaire pendant l'exécution.

import streamlit as st
import time

st.balloons() # Ballons pour une célébration st.subheader("Progress bar") st.progress(10) # Barre de progression st.subheader("Wait the execution") with st.spinner('Wait for it...'): time.sleep(10) # Simulation d'un délai de processus

Affichage des progrès et de l'état with Streamlit 1

st.success(): Cette fonction affiche un message de succès.
st.error(): Cette fonction est utilisée pour afficher un message d'erreur. 
st.warning(): Cette fonction est utilisée pour afficher un message d'avertissement.
st.info(): Cette fonction révèle un message d'information.<
st.exception(): Cette fonction est utilisée pour afficher un message d'exception.

import streamlit as st

st.success("You did it!") st.error("Error occurred") st.warning("This is a warning") st.info("It's easy to build a Streamlit app") st.exception(RuntimeError("RuntimeError exception"))


Affichage des progrès et de l'état with Streamlit 2

8-Barre latérale et conteneur

Nous pouvons également créer une barre latérale ou un conteneur sur votre page pour organiser votre application. La hiérarchie et la disposition des pages de votre application peuvent avoir un impact considérable sur le confort d'utilisation. L'organisation de votre contenu permet aux visiteurs de mieux comprendre votre site et d'y naviguer plus facilement. Cela les aide également à trouver plus rapidement ce qu'ils cherchent et augmente la probabilité qu'ils reviennent. 

Transmettre un élément à st.sidebar()&nbsp permet d'épingler cet élément à gauche, ce qui permet aux utilisateurs de se concentrer sur le contenu et rend l'application plus organisée et plus facile à gérer.

import streamlit as st

st.sidebar.title("This is writter inside sidebar") st.sidebar.button("Click") st.sidebar.radio("Pick your gender",["Male","Female"])

Sidebar

Conteneur

st.container() ;est utilisé pour construire un conteneur invisible dans lequel vous pouvez placer des éléments créant une disposition et une hiérarchie utiles.

import streamlit as st

container = st.container() container.write("This is written inside the container") st.write("This is written outside the container")

Container

import streamlit as st
import numpy as np

with st.container(): st.write("This is inside the container")

st.bar_chart(np.random.randn(<span class="hljs-number">50</span>, <span class="hljs-number">3</span>))

st.write("This is outside the container")

9-Visualisation des données

La visualisation des données simplifie la narration en rassemblant les données dans un format plus simple, en mettant en évidence les tendances et les valeurs aberrantes. Une bonne visualisation transmet un message narratif, en éliminant le bruit des données et en mettant l'accent sur les informations utiles. Cependant, c'est bien plus compliqué que de simplement habiller un graphique pour l'embellir ou d'ajouter la partie "info" d'une infographie.
Une visualisation de données efficace est un délicat exercice d'équilibre entre la forme et la fonction. Un graphique simple pourrait être trop ennuyeux pour attirer l'attention ou communiquer un message puissant, tandis que la visualisation la plus étonnante pourrait ne pas réussir à transmettre la bonne idée. Les données et les éléments visuels doivent fonctionner ensemble. Cependant, combiner une bonne analyse avec une excellente narration est un art. 

st.pyplot(): Cette fonction est utilisée pour afficher une graphique matplotlib.pyplot.

import streamlit as st
import matplotlib.pyplot as plt
import numpy as np

rand = np.random.normal(1, 2, size=20) fig, ax = plt.subplots() ax.hist(rand, bins=15) st.pyplot(fig)

This function is used to display a line chart.

st.line_chart(): Cette fonction permet d'afficher un graphique linéaire.

import streamlit as st
import pandas as pd
import numpy as np

df = pd.DataFrame(np.random.randn(10, 2), columns=['x', 'y']) st.line_chart(df)

This function is used to display a bar chart.

st.bar_chart(): Cette fonction permet d'afficher un diagramme à barres.

import streamlit as st
import pandas as pd
import numpy as np

df = pd.DataFrame(np.random.randn(10, 2), columns=['x', 'y']) st.bar_chart(df)

This function is used to display an area chart.

st.map(): Cette fonction permet d'afficher des cartes dans l'application. Cependant, elle nécessite les valeurs de latitude et de longitude qui ne peuvent pas être nulles/NA.

import pandas as pd
import numpy as np
import streamlit as st

df = pd.DataFrame(     np.random.randn(500, 2) / [50, 50] + [37.76, -122.4], columns=['lat', 'lon'] ) st.map(df)

Display maps with Streamlit 1

10-Affichage de DataFrame

st.dataframe(): Cette commande permet d'afficher un DataFrame sous forme de table interactive. Elle fonctionne avec une variété d'objets de type collection et DataFrame.

import streamlit as st
import pandas as pd
import numpy as np

df = pd.DataFrame(np.random.randn(50, 20), columns=("col %d" % i for i in range(20)))

st.dataframe(df) # Identique à st.write(df)

Vous pouvez également passer un objet Pandas Styler pour modifier le style du DataFrame rendu:

import streamlit as st
import pandas as pd
import numpy as np

df = pd.DataFrame(np.random.randn(10, 20), columns=("col %d" % i for i in range(20)))

st.dataframe(df.style.highlight_max(axis=0))


Summary

Dans cet article, après avoir présenté le framework web Streamlit, j'ai montré comment installer l'application Streamlit et la faire fonctionner. Nous avons également exploré quelques commandes de base, des widgets et des fonctionnalités de visualisation de données.

Dans mon prochain article, nous créerons une application web Streamlit pour nous connecter au jeu de données IRIS et nous explorerons ensemble les concepts avancés de Streamlit.

Merci!

0
1 96
Article Iryna Mykhailova · Jan 10, 2025 7m read


 

Traçage des données gnSSLocation de mon véhicule Rivian R1S à travers le Michigan avec InterSystems Cloud Document et Databricks

Si vous cherchez un cas d'utilisation pour une Document Database, j'ai compris que mon cas préféré, très simple, est la possibilité d'interroger un paquet de JSON, juste à côté de mes autres données avec sql sans vraiment faire grand-chose. C'est un rêve réalisé à partir de la puissante plateforme de données multi-modèles d'InterSystems, et montré ici dans un simple carnet pour visualiser mes données de localisation géographique que mon Rivian R1S émet pour DeezWatts ( Une aventure de données Rivian ).

Voici donc l'approche en 2 étapes, à savoir l'ingestion à et la visualisation de InterSystems Cloud Document, à l'aide du pilote document JDBC.

Déploiement de documents dans InterSystems Cloud

Pour commencer, j'ai lancé un petit déploiement de Cloud Document sur le portail InterSystems Cloud Services Portal, avec un listener activé.

J'ai téléchargé le certificat ssl, et j'ai récupéré les drivers pour JDBC ainsi que le pilote de document qui l'accompagne.

Ingestion

Pour l'ingestion, j'ai voulu comprendre comment extraire un document JSON du système de fichiers et le faire persister en tant que collection dans la base de données de documents via le listener, pour cela j'ai écrit une application Java autonome. Cette approche était plus utilitaire, car tout ce qui était amusant se passait dans le carnet une fois que les données y étaient placées.
 

 
RivianDocDB.java

Ce qui précède est assez proche de JAVA trash, mais cela fonctionne, nous pouvons voir la collection dans le navigateur de collection lors du déploiement.

Databricks

Cela demande un peu d'installation de Databricks, mais cela vaut la peine de travailler avec pyspark la partie amusante.

J'ai ajouté les deux pilotes InterSystems au cluster, et j'ai placé le certificat dans le script d'initialisation du cluster import_cloudsql_certficiate.sh afin qu'il soit ajouté au keystore.

Pour être complet, le cluster utilise Databricks 16, Spark 3.5.0 et Scala 2.12

Visualization

Nous devrions donc être prêts à exécuter une tâche PySpark et à tracer l'endroit où mon fouet a été dans le sous-ensemble de données que je traîne.

Nous utilisons l'ensemble de données de base géo geopandas and geodatasets pour une approche directe de la représentation graphique.

import geopandas as gpd
import geodatasets
from shapely.geometry import Polygon

Il faut un peu de temps pour s'y habituer, mais voici la requête adressée à InterSystems Cloud Document en utilisant la syntaxe des chemins JSON et JSON_TABLE.

dbtablequery = f"(SELECT TOP 1000 lat,longitude FROM JSON_TABLE(deezwatts2 FORMAT COLLECTION, '$' COLUMNS (lat VARCHAR(20) path '$.whip2.data.vehicleState.gnssLocation.latitude', longitude VARCHAR(20) path '$.whip2.data.vehicleState.gnssLocation.longitude' ))) AS temp_table;"

 

J'ai réussi à trouver un site qui facilite grandement la création du chemin json path @ jsonpath.com.

Ensuite, nous établissons la connexion avec le déploiement de la base de données documentaire IRIS Document Database et nous lisons les données dans un cadre de données.

# Read data from InterSystems Lecture des données de la base de données documentaire InterSystems Document Database via la requête ci-dessus
df = (spark.read.format("jdbc") \
  .option("url", "jdbc:IRIS://k8s-05868f04-a88b7ecb-5c5e41660d-404345a22ba1370c.elb.us-east-1.amazonaws.com:443/USER") \
  .option("jars", "/Volumes/cloudsql/iris/irisvolume/intersystems-document-1.0.1.jar") \
  .option("driver", "com.intersystems.jdbc.IRISDriver") \
  .option("dbtable", dbtablequery) \
  .option("sql", "SELECT * FROM temp_table;") \
  .option("user", "SQLAdmin") \
  .option("password", "REDACTED") \
  .option("connection security level","10") \
  .option("sslConnection","true") \
  .load())


Ensuite, nous prenons une carte disponible à partir de jeux de données géographiques, le jeu sdoh est parfait pour une utilisation générique des États-Unis.
 

# La carte sdoh est fantastique grâce aux boîtes de délimitation
michigan = gpd.read_file(geodatasets.get_path("geoda.us_sdoh"))

gdf = gpd.GeoDataFrame( df.toPandas(), geometry=gpd.points_from_xy(df.toPandas()['longitude'].astype(float), df.toPandas()['lat'].astype(float)), crs=michigan.crs #"EPSG:4326" )

Maintenant, la partie la plus intéressante, nous voulons zoomer sur l'endroit où nous voulons placer les points de géolocalisation de l'endroit où le R1S a roulé, pour cela nous avons besoin d'une boîte de délimitation pour l'état du Michigan.

J'ai utilisé un outil très astucieux de Keene pour dessiner la boîte de délimitation de la géo clôture et cela me donne le tableau de coordonnées!

Maintenant le tableau de coordonnées de la boîte de délimitation est en notre possession, il nous faut l'insérer dans un objet Polygone.

polygon = Polygon([
      (
        -87.286377,
        45.9664245
      ),
      (
        -81.6503906,
        45.8134865
      ),
      (
        -82.3864746,
        42.1063737
      ),
      (
        -84.7814941,
        41.3520721
      ),
      (
        -87.253418,
        42.5045029
      ),
      (
        -87.5610352,
        45.8823607
      )
    ])

 

Maintenant, traçons la piste du Rivian R1S! Il s'agit d'environ 10 000 enregistrements (j'ai utilisé l'instruction top ci-dessus pour limiter les résultats)
 

ax = michigan.clip(polygon).plot(color="lightblue", alpha=0.5,linewidth=0.8, edgecolor='gray')
ax.axis('off')
ax.annotate("Data: Rivian R1S Telemetry Data via InterSystems Document Database", xy=(0.01, .085), xycoords='figure fraction', fontsize=14, color='#555555')

gdf.plot(ax=ax, color="red", markersize=1.50, alpha=0.5, figsize=(200,200))

Et voilà, nous l'avons... Détroit, Traverse City, Silver Lake Sand Dunes, Holland, Mullet Lake, Interlachen... Le Michigan à l'état pur, à la Rivian.


 

0
0 30
Article Lorenzo Scalese · Nov 21, 2024 8m read

Dans l'article précédent. Pratiques des membres de la classe et leur exécution au sein de Embedded Python. WNous allons maintenant aborder le processus de changement d'espace de noms, d'accès aux variables globales, de traversée et d'exécution de routine  au sein de Embedded Python.

Avant de passer aux autres fonctions, examinons brièvement la fonction execute du paquet iris. Cette fonction est particulièrement utile pour l'exécution de fonctions ObjectScript arbitraires et l'invocation de classes.

>>> b = iris.execute('return $Piece("test^aaaa","^",2)')
>>> b
'aaaa'
>>> b = iris.execute('return $Extract("123456",2,5)')
>>> b
'2345'
>>> b = iris.execute('return $Length(123456)')
>>> iris.execute('write ##Class(%SYSTEM.SYS).NameSpace()')
LEARNING>>>
>>> b = iris.execute('return ##Class(%SYSTEM.SYS).NameSpace()')
>>> b
'LEARNING'

Commençons!

4. Changement d'espace de noms

Il est souvent nécessaire de changer d'espace de noms en cours d'exécution. Toutefois, contrairement à IRIS, il n'est pas possible de changer directement d'espace de noms dans Embedded Python. Il est donc essentiel d'utiliser les définitions de classes existantes ou de créer une méthode wrapper pour faciliter le changement d'espace de noms.  

ClassMethod SwitchNM() [ Language = python ]
{
    import iris
    print(iris.cls('%SYSTEM.SYS').NameSpace())
    print(iris.system.Process.SetNamespace("USER"))
    try:
        iris.cls('User.EmbeddedPython').pyGetTemplateString()
    except RuntimeError as e:
        print("Wrong NameSpace",e)
}

 

5. Globale

Pour utiliser les capacités d'une globale pour les données de la traversée ou pour l'extraction directe d'informations à partir de systèmes globaux existants, plutôt que par le biais de SQL ou d'objets dans Embedded Python, on peut y accéder directement en employant la fonction gref du paquetage iris. Pour définir ou obtenir des valeurs globales, la fonction gref peut être utilisée pour établir une référence à la variable globale et assigner directement des valeurs dans Python.

 
iris.gref
class gref(builtins.object)
 |  Objet de référence global d'InterSystems IRIS.
 |  UUtilisez la méthode iris.gref() pour obtenir une référence à une globale
 |
 |  Les méthodes sont définies ci-dessous:
 |
 |  __delitem__(self, key, /)
 |      Suppression de self[key].
 |
 |  __getitem__(self, key, /)
 |      Renvoie de self[key].
 |
 |  __len__(self, /)
 |      Renvoie de len(self).
 |
 |  __repr__(self, /)
 |      Renvoie de repr(self).
 |
 |  __setitem__(self, key, value, /)
 |      Mise à la valeur de self[key].
 |
 |  __str__(self, /)
 |      Renvoie de str(self).
 |
 |  data(self, key)
 |      Étant donné les clés d'une globale sous forme de liste, renvoie son état.
 |      Exemple : x = g.data([i,j]) attribue à x les valeurs 0,1,10,11 0-si indéfini, 1-défini, 10-indéfini mais a des descendants, 11-a une valeur et des descendants
 |
 |  get(self, key)
 |      Étant donné les clés d'un global sous forme de liste, renvoie la valeur stockée à ce nœud de globales.
 |      Exemple : x = g.get([i,j]) attribue à x la valeur stockée à la clé i,j de globale g.
 |
 |  getAsBytes(self, key)
 |      Étant donné les clés d'une globale sous forme de liste, renvoie une chaîne de caractères stockée à ce nœud de la globale, sous forme d'octets.
 |      Exemple : x = g.getAsBytes([i,j]) attribue à x la valeur stockée à la clé i,j de la globale g, sous forme d'octets.
 |
 |  keys(self, key)
 |      Traverse une globale à partir de la clé spécifiée, en retournant chaque clé dans la globale.
 |      Exemple : for key in g.keys([i, j]) traverse g à partir de la clé i,j, en retournant chaque clé à son tour. Deuxième argument facultatif 1 ou -1, si -1 inverse l'ordre retourné
 |
 |  kill(self, key)
 |      Étant donné les clés d'une globale sous forme de liste, supprime ce nœud de la globale et sa sous-arborescence.
 |      Exemple : g.kill([i,j]) supprime le nœud stocké à la clé i,j de la globale g et tous ses descendants.
 |
 |  order(self, key)
 |      Étant donné les clés d'une globale sous forme de liste, renvoie la clé suivante de la globale, second argument facultatif 1 ou -1, si -1 renvoie la clé précédente.
 |      Exemple : j = g.order([i,j]) attribue à j la clé de deuxième niveau suivante de la globale g.
 |
 |  orderiter(self, key)
 |      Traverse une globale à partir de la clé spécifiée, en renvoyant la clé et la valeur suivantes sous la forme d'un tuple.
 |      Exemple : pour (clé, valeur) dans g.orderiter([i,j]) traverse g à partir de la clé i,j, en renvoyant la clé et la valeur suivantes. Deuxième argument facultatif : 1 ou -1, si -1, l'ordre retourné est inversé.
 |
 |  query(self, key)
 |      Traverse une globale à partir de la clé spécifiée, en renvoyant chaque clé et chaque valeur sous la forme d'un tuple.
 |      Exemple : pour (clé, valeur) dans g.query([i,j]) traverse g à partir de la clé i,j, en renvoyant chaque clé et chaque valeur à tour de rôle. Deuxième argument facultatif : 1 ou -1, si -1, l'ordre retourné est inversé.
 |
 |  set(self, key, value)
 |      Étant donné les clés d'une globale sous forme de liste, définit la valeur stockée à cette clé de la globale.
 |      Exemple : g.set([i,j], 10) fixe la valeur du nœud à la clé i,j de la globale g à 10
 |
 |  ----------------------------------------------------------------------
 |  Les méthodes statiques sont définies ci-dessous:
 |
 |  __new__(*args, **kwargs) from builtins.type
 |      Création et retour d'un nouvel objet.  Consultez help(type) pour obtenir une signature précise.

5.1 Définition des valeurs globales

ClassMethod SetGlobal() [ Language = python ]
{
import iris
#création d'une référence globale
g = iris.gref('^mygbl') 
g[1],g[2]='Mon','Tue'
g["95752455",1]=iris.execute('return $LFS("Ashok,55720,9639639639,test@gmail.com",",")')
g["85752400",1]=iris.execute('return $LB("Test","9517539635","t@gmail.com")')
g["test","c1"]=iris.execute('return ##Class(MyLearn.EmbeddedPython).executeAndGetResult()') # method wil return some listbuild values# déclaration de valeurs à l'aide de la fonction set
g.set([3],'Wed')
g.set([3,1,1],'Test multilevel')
}

5.2 Obtention des valeurs globales
Récupérez les valeurs globales à partir de python directement en utilisant la méthode subscripts ou get.

ClassMethod GetGlobal() [ Language = python ]
{
    import iris
    #obtient une référence globale
    g = iris.gref('^mybgl') 
    # obtention de valeurs
    print(g[3,1,1])
    print(g.get([2,1]))
    print(g["95752455",1])
}

5.3 Traversée 

order - Traverser la globale est essentiel pour collecter plusieurs niveaux de données de la globale. Cette commande en Embedded Python  order fonctionne de manière similaire à la commande $Order en utilisant la fonction order du fichier iris.gref. Au départ, il est nécessaire d'établir une référence à l'entité globale qui doit être traversée.

Traversée à un seul niveau d'indice

ClassMethod DollarOrder() [ Language = python ]
{
    import iris
    g = iris.gref('^MyLearn.EmbeddedPythonD') # I use my persistent class global
    key = ''
    while True:
        key = g.order([key])
        if key == None:
            breakprint(f'{key} {g.get([key])}')
}

Traversée à plusieurs niveaux d'indices

 
global
zw ^mygbl
^mygbl(1)="Mon"
^mygbl(2)="Tue"
^mygbl(3)="Wed"
^mygbl(3,1,1)="Test multilevel"
^mygbl(85752400,1)=$lb("Test","9517539635","t@gmail.com")
^mygbl(95752455,1)=$lb("Ashok","55720","9639639639","test@gmail.com")
^mygbl("test","c1")=$lb("Test","8527538521","pylearn@gmail.com")
 
ClassMethod DollarOrderMultiLevel() [ Language = python ]
{
 import iris
 g = iris.gref('^mygbl')
 key1= ''whileTrue:
 	key1 = g.order([key1])
 	if key1== None:
 		break
 	key2 = ''whileTrue:
 		key2 = g.order([key1,key2])
 		if key2 == None:
 			break
 		value = g.get([key1,key2])
 		print(key1,key2,value)
}

query - La fonction de requête à partir de iris.gref est similaire à $query. Cette fonction is rassemble toutes les valeurs globales en tuples. Le résultat du tuple contient les identifiants dans la liste et les valeurs sont le tuple suivant. Vous pouvez consulter l'exemple de tuple ci-dessous 

 
tuple
ex: 
zw ^mybgl
^mybgl(1)="Mon"
^mybgl(2)="Tue"
^mybgl(3)="Wed"
^mybgl(3,1,1)="Test multilevel"
^mybgl(95752455,1)=$lb("Ashok","55720","9639639639","test@gmail.com")

Python tuple : ( [ids], data)
(['1'], 'Mon')
(['2'], 'Tue')
(['3'], 'Wed')
(['3', '1', '1'], 'Test multilevel')
(['95752455', '1'], '\x07\x01Ashok\x07\x0155720\x0c\x019639639639\x10\x01test@gmail.com')

ClassMethod DollarQuery() [ Language = python ]
{
 	import iris
 	g = iris.gref('^mygbl')
 	key = g.query()#cela renverra des tuples de tous les indicesfor x in key:
 		print(x) # résultat (['3', '1', '1'], 'Test multilevel')
}

data - la fonction data Vérifie si l'indice donné existe dans le global et renvoie les valeurs $data en utilisant la fonction de données

ClassMethod DollarData() [ Language = python ]
{
    import iris
    g = iris.gref('^mygbl')
    key1= ''
    print(g.data([1]))
}

 

6. Routines

En outre, il est essentiel d'implémenter les membres de la classe. Nous devons exécuter les routines dans le cadre de la mise en œuvre pour les systèmes de base de code hérités et d'autres situations connexes. Par conséquent, il existe une fonction spécifique dans le paquet de la bibliothèque iris qui permet l'invocation de routines à partir de Embedded Python grâce à l'utilisation de la fonction routine.

 
myirispython.mac
myirispython
 123
 q
ir1
 "running ir1"
 q
add(p1,p2) public{
return p1+p2
}
sub(p1,p2)
 c= p1-p2
ClassMethod RunRoutines() [ Language = python ]
{
    import iris
    iris.routine('^myirispython')
    iris.routine('add^myirispython',1,2) # same aswrite$$add^myirispython(1,2)
}

En outre, vous pouvez également exécuter la routine à l'aide de la fonction d'exécution. iris.execute('do ^myirispython')

remarque : si la routine n'est pas trouvée 
>>> iris.routine('^myirispythonT')
Traceback (dernier appel récent):
  File "<input>", line 1, in <module>
RuntimeError: Routine introuvable

Les autres sujets seront abordés dans le prochain article.

0
0 40
Article Lorenzo Scalese · Nov 18, 2024 8m read

Bonjour la communauté,

Dans cet article, je vais décrire et illustrer le processus de mise en œuvre d'ObjectScript au sein de Embedded Python. Cette discussion fera également référence à d'autres articles relatifs à Embedded Python, et répondra aux questions qui ont été utiles à mon apprentissage.

Comme vous le savez peut-être, l'intégration des fonctionnalités de Python dans IRIS est possible depuis un certain temps. Cet article se concentrera sur la manière dont ObjectScript peut être incorporé de manière transparente à Embedded Python.

Essentiellement, Embedded Python sert d'extension qui permet une écriture et une exécution indépendantes. Il permet l'intégration transparente du code Python avec ObjectScript et vice versa, permettant aux deux de s'exécuter dans le même contexte. Cette fonctionnalité améliore considérablement les capacités de votre implémentation.

Pour commencer, vous devez spécifier le langage de votre code Python dans la définition de la classe en utilisant le mot-clé "language" [language = "python"]. Une fois cette étape franchie, vous êtes prêt à écrire votre code Python.

import iris - Ce paquet iris est une bibliothèque Python essentielle qui facilite la communication avec les classes, routines, globales et SQL de l'API native d'InterSystems. Ce paquet est facilement disponible par défaut. Quoi qu'il en soit, il est nécessaire d'importer ce paquet au début de votre code Python si vous souhaitez interagir avec IRIS.

Quelques notes importantes avant d'écrire

  • Vous pouvez utiliser une variable spéciale python __name__ pour référencer le nom de classe dans la définition de classe.
  • Use _ for %Methods ex: %New  == _New , %OpenId == _OpenId

Commençons

Mise en œuvre des éléments d'une classe en Python intégré

1.  Objets et Propriétés

Cette partie est essentielle car elle couvre le processus d'initialisation d'un nouvel objet, la modification des valeurs des objets existants et la configuration des propriétés dans des contextes statiques et dynamiques. Créez votre propre définition de classe et utilisez les propriétés littérales simples

1.1 initialisation new d'un nouvel objet / Modification d'un objet existant

Utilisez _New pour initialiser un nouvel objet et _OpenId(id) pour modifier l'objet existant

ClassMethod SaveIRISClsObject() [ Language = python ]
{
 #cette méthode appelle la méthode de rappel %OnNew et récupère l'objetimport iris
 try:
     iris_obj =  iris.cls(__name__)._New()
     ifnot iris.cls(__name__).IsObj(iris_obj):
      #IsObj est la méthode wrapper d'objectscript : elle contient $Isobject()raise ReferenceError('Object Initlize Error')
 except ReferenceError as e:
     print(e)
     return#définition des propriétés de l'objet et enregistrement des valeurs 
 iris_obj.Name = 'Ashok'
 iris_obj.Phone = 9639639635
 status = iris_obj._Save()
 print(status)
 return status
}

1.2 Accès  aux propriétés

Avant de procéder à la partie sur les propriétés, il est important de noter que le type de données IRIS diffère des types de données Python et que, par conséquent, les types de données de collecte IRIS ne peuvent pas être utilisés directement dans Python. Pour résoudre ce problème, InterSystems a proposé une solution complète pour convertir les types de données IRIS en formats compatibles avec Python, tels que les listes, les ensembles et les tuples. Pour ce faire, il suffit d'importer le module "builtins" dans la base de code IRIS, en utilisant les méthodes de classe ##class(%SYS.Python).Builtins() ou en définissant les builtins = ##class(%SYS.Python).Import("builtins"). Je reviendrai sur ce point dans les prochaines parties.

J'utilise donc cette méthode pour convertir les propriétés $LB en liste  python afin d'accéder aux propriétés au moment de l'exécution en python

LEARNING>Set pyList = ##class(%SYS.Python).ToList($LB("Name","Phone","City"))
 
LEARNING>zw pyList
pyList=5@%SYS.Python  ; ['Name', 'Phone', 'City']  ; <OREF>
ClassMethod GetProperties() [Language = objectscript]
{
    set pyList = ##class(%SYS.Python).ToList($LB("Name","Phone","City"))
    do..pyGetPropertiesAtRunTime(pyList)
}
ClassMethod pyGetPropertiesAtRunTime(properties) [ Language = python ]
{
    import iris
    iris_obj = iris.cls(__name__)._OpenId(1)
    for prop in properties:
        print(getattr(iris_obj,prop))
}

 

1.3 Définition des propriétés au moment de l'exécution.

J'utilise ce dictionnaire python pour désigner ma propriété en tant que clé et, avec les valeurs de propriété correspondantes servant de valeurs dans ce dictionnaire. Vous pouvez vous référer au code fourni ci-dessous et à l'article de la communauté concernant ce jeu de propriétés .

ClassMethod SetProperties()
{
	Set pyDict = ##class(%SYS.Python).Builtins().dict()
	do pyDict.setdefault("Name1", "Ashok kumar")
	do pyDict.setdefault("Phone", "9639639635")
	do pyDict.setdefault("City", "Zanesville")
	Set st = ..pySetPropertiesAtRunTime(pyDict)
}

ClassMethod pySetPropertiesAtRunTime(properties As%SYS.Python) [ Language = python ] { import iris iris_obj = iris.cls(name)._New() for prop in properties: setattr(iris_obj, prop,properties[prop])

status = iris_obj._Save()
<span class="hljs-keyword">return</span> status

}

1.4 Contexte de partage d'objets

Comme j'ai indiqué précédemment, Python et ObjectScript opèrent dans le même contexte de mémoire et partagent des objets. Cela implique que vous pouvez créer ou ouvrir un objet dans la classe InCache et, par la suite, le définir ou le récupérer dans la classe Python.

ClassMethod ClassObjectAccess() [Language = objectscript]
{
	Set obj = ..%OpenId(1)
	Write obj.PropAccess(),! ; prints "Ashok kumar"Do obj.DefineProperty("test")
	Write obj.PropAccess() ; prints "test"
}

Method PropAccess() [ Language = python ] {

return self.Name }

Method DefineProperty(name) [ Language = python ] {

self.Name = name }

2.  Parameters

Get the parameter arbitrary key value pair by using the _GetParameter. Refer the useful community post 

ClassMethod GetParam(parameter = "MYPARAM") [ Language = python ]
{
	import iris
	value = iris.cls(__name__)._GetParameter(parameter)
	print(value)
}

3. La méthode de classe et les méthodes

3.1 La méthode de classe

L'invocation des méthodes et des fonctions de classe est très utile pour l'exécution du code de script d'objet.

Il est possible d'invoquer la méthode de classe en tant qu'appel statique,  par exemple: Do ..Test() 

ClassMethod InvokeStaticClassMethods(clsName = "MyLearn.EmbeddedPython") [ Language = python ]
{
	import iris
	print(iris.cls(clsName).Test())
	# print(iris.cls(__name__).Test()) 
}


Invocation de la méthode de Classe au moment de l'exécution Set method="Test" Do $ClassMethod(class, method, args...)

ClassMethod InvokeClassMethodsRunTime(classMethod As %String = "Test") [ Language = python ]
{
 import iris
 clsMethodRef = getattr(iris.cls(__name__), classMethod) # renvoie la référence de la méthode
 print(clsMethodRef()) 
}

3.2  Méthodes

Invocation des méthodes d'instance est identique au format "script d'objet". Dans le code ci-dessous, j'ai d'abord créé l'objet, puis j'ai appelé la méthode d'instance avec des paramètres.

ClassMethod InvokeInstanceMethodWithActualParameters() [ Language = python ]
{
	import iris
	obj = iris.cls(__name__)._New()
	print(obj.TestMethod(1,2,4))
}

3.3  Transmission d'arguments par valeur et  par référence entre python et ObjectScript

Fondamentalement, la transmission des arguments est  inévitable entre les fonctions et elle en sera de même entre ObjectScript et Python

3.4  Transmission d'arguments par valeur - C'est comme d'habitude la transmission d'arguments par valeur

ClassMethod passbyvalfromCOStoPY()
{
    Set name = "test", dob= "12/2/2002", city="chennai"Do..pypassbyvalfromCOStoPY(name, dob, city)
}

ClassMethod pypassbyvalfromCOStoPY(name As%String, dob As%String, city As%String) [ Language = python ] { print(name,' ',dob,' ',city) }

/// transmission par valeur de python au script d'objetClassMethod pypassbyvalfromPY2COS() [ Language = python ] { import iris name = 'test' dob='12/2/2002' city='chennai' iris.cls(name).passbyvalfromPY2COS(name, dob, city) }

ClassMethod passbyvalfromPY2COS(name As%String, dob As%String, city As%String) { zwrite name,dob,city }

3.5 Transmission par référence- C'est au contraire de la transmission par valeur. Comme Python ne supporte pas nativement l'appel par référence, il faut donc utiliser la fonction iris.ref()  dans le code Python pour que la variable devienne une référence. à savoir, la référence . A ma connaissance, il n'y a pas d'effets du côté du script d'objet concernant les variables de type pass-by-reference (transmission par référence), même lorsque ces variables sont modifiées en Python. Par conséquent, les variables Python seront affectées par ce mécanisme de pass-by-reference lorsque les méthodes du script d'objet seront invoquées

ClassMethod pypassbyReffromPY2COS() [ Language = python ]
{
	import iris
	name='python'
	dob=iris.ref('01/01/1991')
	city = iris.ref('chennai')
	print('before COS ',name,'  ',dob.value,'  ',city.value)
	#transmission par référence de la date de naissance, ville
	iris.cls('MyLearn.EmbeddedPythonUtils').passbyReffromPY2COS(name, dob, city)	
	print('after COS ',name,'  ',dob.value,'  ',city.value)
}

ClassMethod passbyReffromPY2COS(name, ByRef dob, ByRef city) { Set name="object script", dob="12/12/2012", city="miami" }

// résultat LEARNING>do##class(MyLearn.EmbeddedPythonUtils).pypassbyReffromPY2COS() before COS python 01/01/1991 chennai after COS python 12/12/2012 miami


3.5 **kwargs- Il existe un support supplémentaire pour passer les arguments de mot-clé python (**kwargs) à partir d'un script d'objet. InterSystems IRIS n'ayant pas de concept d'arguments de mot-clé, il faut créer un  %DynamicObject pour contenir les paires mot-clé/valeur et passer les valeurs en tant qu' Args...de syntax

J'ai créé le dynamicObject "name""ashok""city""chennai"}et j'y ai inséré les paires clé-valeur requises, que j'ai ensuite transmises au code python.

ClassMethod KWArgs()
{
    set kwargs={ "name": "ashok", "city": "chennai"}
    do..pyKWArgs(kwargs...)
}

ClassMethod pyKWArgs(name, city, dob = "") [ Language = python ] { print(name, city, dob) }

// résultat LEARNING>do##class(MyLearn.EmbeddedPythonUtils).KWArgs() ashok chennai

Je décrirai les globales, les routines et SQL dans le prochain article

0
0 63
Article Sylvain Guilbaud · Oct 25, 2024 7m read

Dans le paysage actuel des données, les activités commerciales sont confrontées à différents défis. L'un d'entre eux consiste à réaliser des analyses à partir d'une couche de données unifiée et harmonisée, accessible à tous les utilisateurs. Une couche capable de fournir les mêmes réponses aux mêmes questions, indépendamment du dialecte ou de l'outil utilisé. La plate-forme de données InterSystems IRIS répond à cette question en ajoutant la solution 'Adaptive Analytics' (Analyse adaptative) qui peut fournir cette couche sémantique unifiée. Il y a beaucoup d'articles dans DevCommunity sur l'utilisation de cette couche sémantique via des outils décisionnels. Cet article couvrira la partie concernant la façon de l'utiliser avec l'IA et également la façon d'obtenir des informations en retour. Allons-y étape par étape...

Qu'est-ce que la solution 'Adaptive Analytics'?

Vous pouvez facilement trouver une définition sur le site web de la Communauté de développeurs. En quelques mots, elle peut fournir des données sous une forme structurée et harmonisée à divers outils de votre choix pour une utilisation et une analyse ultérieures. Elle fournit les mêmes structures de données à différents outils décisionnels. Mais... elle peut également fournir les mêmes structures de données à vos outils IA/ML!

Adaptive Analytics a un composant supplémentaire appelé AI-Link qui construit ce pont entre l'IA et d'informatique décisionnelle.

Qu'est-ce que AI-Link exactement?

Il s'agit d'un composant Python conçu pour permettre une interaction programmatique avec la couche sémantique dans le but de rationaliser les étapes clés du flux de travail de l'apprentissage automatique (ML) (par exemple, l'ingénierie des fonctionnalités).

Avec AI-Link, vous pouvez:

  • accéder de manière programmatique aux fonctionnalités de votre modèle de données analytiques;
  • faire des requêtes, explorer les dimensions et les mesures;
  • alimenter des pipelines de ML; ... et renvoyer les résultats vers votre couche sémantique pour qu'ils soient à nouveau utilisés par d'autres (par exemple, par le biais de Tableau ou d'Excel).

Comme il s'agit d'une bibliothèque Python, elle peut être utilisée dans n'importe quel environnement Python. Y compris les Notebooks. Dans cet article, je vais donner un exemple simple pour atteindre une solution d'analyse adaptative à partir d'un Notebook Jupyter avec l'aide d'AI-Link.

Voici le référentiel git qui aura le Notebook complet à titre d'exemple : https://github.com/v23ent/aa-hands-on

**Conditions préalables **

Les étapes suivantes supposent que vous ayez rempli les conditions préalables ci-dessous:

  1. La solution 'Adaptive Analytics' est en place et fonctionne (avec IRIS Data Platform en tant qu'entrepôt de données).
  2. Jupyter Notebook est opérationnel
  3. La connexion entre 1. et 2. peut être établie

Étape 1: Configuration

Tout d'abord, installons les composants nécessaires dans notre environnement. Ainsi, nous téléchargerons quelques paquets nécessaires au bon déroulement des étapes suivantes. 'atscale' - c'est notre paquetage principal pour se connecter 'prophet' - c'est le paquet dont nous aurons besoin pour faire des prédictions.

pip install atscale prophet

Ensuite, nous devons importer des classes clés représentant certains concepts clés de notre couche sémantique. Client - c'est la classe que nous utiliserons pour établir une connexion avec Adaptive Analytics; Project - c'est la classe qui représente les projets à l'intérieur d'Adaptive Analytics; DataModel - c'est la classe qui représentera notre cube virtuel;

from atscale.client import Client
from atscale.data_model import DataModel
from atscale.project import Project
from prophet import Prophet
import pandas as pd

Étape 2: Connexion

Maintenant, nous devrions être prêts à établir une connexion avec notre source de données.

client = Client(server='http://adaptive.analytics.server', username='sample')
client.connect()

Continuez et spécifiez les détails de connexion de votre instance d'Adaptive Analytics. Lorsque l'on vous demande l'organisation, répondez dans la boîte de dialogue et entrez votre mot de passe de l'instance AtScale.

Une fois la connexion établie, vous devrez sélectionner votre projet dans la liste des projets publiés au serveur. Vous obtiendrez la liste des projets sous la forme d'une invite interactive et la réponse devrait être l'identifiant entier du projet. Le modèle de données est ensuite sélectionné automatiquement s'il est le seul.

project = client.select_project()   
data_model = project.select_data_model()

Étape 3: Explorez votre jeu de données

Il existe un certain nombre de méthodes préparées par AtScale dans la bibliothèque de composants AI-Link. Elles permettent d'explorer votre catalogue de données, d'interroger les données et même d'ingérer des données en retour. La documentation d'AtScale a une référence API complète décrivant tout ce qui est disponible. Voyons d'abord quel est notre jeu de données en appelant quelques méthodes de data_model :

data_model.get_features()
data_model.get_all_categorical_feature_names()
data_model.get_all_numeric_feature_names()

Le résultat devrait ressembler à ceci image

Après avoir examiné un peu la situation, nous pouvons interroger les données qui nous intéressent à l'aide de la méthode 'get_data'. Elle renverra un pandas DataFrame contenant les résultats de la requête.

df = data_model.get_data(feature_list = ['Country','Region','m_AmountOfSale_sum'])
df = df.sort_values(by='m_AmountOfSale_sum')
df.head()

Ce qui affichera votre trame de données: image

Préparons un ensemble de données et affichons-le rapidement sur le graphique

import matplotlib.pyplot as plt

# Nous enregistrons des ventes pour chaque date
dataframe = data_model.get_data(feature_list = ['Date','m_AmountOfSale_sum'])

#  Création d'un graphique linéaire
plt.plot(dataframe['Date'], dataframe['m_AmountOfSale_sum'])

# Ajout des étiquettes et d'un titre
plt.xlabel('Days')
plt.ylabel('Sales')
plt.title('Daily Sales Data')

# Affichage du graphique
plt.show()

Résultat: image

Étape 4: Prédiction

La prochaine étape consistera à tirer profit du pont AI-Link - faisons quelques prédictions simples!

# Chargement des données historiques pour l'entraînement du modèle
data_train = data_model.get_data(
    feature_list = ['Date','m_AmountOfSale_sum'],
    filter_less = {'Date':'2021-01-01'}
    )
data_test = data_model.get_data(
    feature_list = ['Date','m_AmountOfSale_sum'],
    filter_greater = {'Date':'2021-01-01'}
    )

Nous disposons ici de 2 jeux de données différents: pour entraîner notre modèle et pour le tester.

# Pour l'outil que nous avons choisi pour faire la prédiction 'Prophète' ,nous devrons spécifier 2 colonnes: 'ds' et 'y'
data_train['ds'] = pd.to_datetime(data_train['Date'])
data_train.rename(columns={'m_AmountOfSale_sum': 'y'}, inplace=True)
data_test['ds'] = pd.to_datetime(data_test['Date'])
data_test.rename(columns={'m_AmountOfSale_sum': 'y'}, inplace=True)

# Initialisation et ajustement du modèle Prophet
model = Prophet()
model.fit(data_train)

Et puis nous créons une autre trame de données pour accueillir notre prédiction et l'afficher sur le graphique

# Création d'une trame de données prochaine pour la prévision
future = pd.DataFrame()
future['ds'] = pd.date_range(start='2021-01-01', end='2021-12-31', freq='D')

# Prédictions
forecast = model.predict(future)
fig = model.plot(forecast)
fig.show()

Résultat: image

Étape 5: Réécriture

Une fois notre prédiction en place, nous pouvons la renvoyer à l'entrepôt de données et ajouter un agrégat à notre modèle sémantique afin de la refléter pour d'autres utilisateurs. La prédiction serait disponible via n'importe quel autre outil décisionnel pour les analystes décisionnels et les utilisateurs commerciaux. La prédiction elle-même sera placée dans notre entrepôt de données et y sera stockée.

from atscale.db.connections import Iris
db = Iris(
    username,
    host,
    namespace,
    driver,
    schema, 
    port=1972,
    password=None, 
    warehouse_id=None
    )

data_model.writeback(dbconn=db,
                    table_name= 'SalesPrediction',
                    DataFrame = forecast)

data_model.create_aggregate_feature(dataset_name='SalesPrediction',
                                    column_name='SalesForecasted',
                                    name='sum_sales_forecasted',
                                    aggregation_type='SUM')

Fin

C'est ça! Bonne chance avec vos prédictions!

0
0 86
Article Iryna Mykhailova · Oct 15, 2024 8m read

Problèmes de chaînes

J'utilise Python pour accéder aux bases de données IRIS avec JDBC (ou ODBC). Je veux récupérer les données dans pandas dataframe pour manipuler les données et créer des graphiques à partir de celles-ci. Lors de l'utilisation de JDBC, j'ai rencontré un problème avec la gestion des chaînes. Cet article est destiné à aider les personnes qui ont les mêmes problèmes. S'il existe un moyen plus simple de résoudre ce problème, faites-le moi savoir dans les commentaires !

J'utilise OSX, donc je ne sais pas à quel point mon problème est unique. J'utilise Jupyter Notebooks, mais le code serait généralement le même si vous utilisiez n'importe quel autre programme ou cadre en Python.

Le problème JDBC

Lorsque je récupère des données de la base de données, les descriptions de colonnes et toutes les données de type chaîne sont renvoyées en tant que données de type java.lang.String. Si vous imprimez une chaîne de données, elle se présentera sous la forme suivante : "(p,a,i,n,i,n,t,h,e,r,e,a,r)" au lieu de l'attendu "painintherear".

Ceci est probablement dû au fait que les chaînes de caractères de type java.lang.String se présentent sous la forme d'un itérable ou d'un tableau lorsqu'elles sont récupérées à l'aide de JDBC. Cela peut arriver si le pont Python-Java que vous utilisez (par exemple, JayDeBeApi, JDBC) ne convertit pas automatiquement java.lang.String en une chaîne Python en une seule étape.

La représentation de la chaîne str de Python, en revanche, dispose de la chaîne entière en tant qu'unité unique. Lorsque Python récupère une chaîne normale (par exemple via ODBC), elle n'est pas divisée en caractères individuels.

La solution JDBC

Pour résoudre ce problème, vous devez vous assurer que le type java.lang.String est correctement converti en type str de Python. Vous pouvez explicitement gérer cette conversion lors du traitement des données récupérées afin qu'elles ne soient pas interprétées comme un itérable ou une liste de caractères.

Il existe de nombreuses façons de manipuler les chaînes de caractères ; c'est ce que j'ai fait.

import pandas as pd

import pyodbc

import jaydebeapi
import jpype

def my_function(jdbc_used)

    # Un autre code pour créer la connexion se trouve ici

    cursor.execute(query_string)

    if jdbc_used:
        # Récupération des résultats, conversion des données java.lang.String en str Python
        # (java.lang.String est retourné comme suit : "(p,a,i,n,i,n,t,h,e,r,e,a,r)" Conversion en type str "painintherear"
        results = []
        for row in cursor.fetchall():

            converted_row = [str(item) if isinstance(item, jpype.java.lang.String) else item for item in row]

            results.append(converted_row)


        # Obtention des noms des colonnes et vérification qu'il s'agit bien de chaînes Python

        column_names = [str(col[0]) for col in cursor.description]


        # Création du cadre de données

        df = pd.DataFrame.from_records(results, columns=column_names)


        # Vérification des résultats

        print(df.head().to_string())


    else:

        # Je testais aussi ODBC
        # Dans le cas d'ensembles de résultats très volumineux, obtenez les résultats par blocs en utilisant cursor.fetchmany() ou fetchall()
        results = cursor.fetchall()
        # Obtention des noms des colonnes
        column_names = [column[0] for column in cursor.description]
        # Création du cadre de données
        df = pd.DataFrame.from_records(results, columns=column_names)

    # Faites des choses avec votre cadre de données

Le problème ODBC

Lors d'une connexion ODBC, les chaînes de caractères ne sont pas renvoyées ou sont S.O.

Si vous vous connectez à une base de données qui contient des données Unicode (par exemple, des noms dans des langues différentes) ou si votre application doit stocker ou récupérer des caractères qui ne sont pas ASCII, vous devez vous assurer que les données restent correctement encodées lorsqu'elles sont transmises entre la base de données et votre application Python.

La solution ODBC

Ce code garantit que les données de type chaîne sont encodées et décodées en utilisant UTF-8 lors de l'envoi et de la récupération de données dans la base de données. C'est particulièrement important lorsqu'il s'agit de caractères non ASCII ou d'assurer la compatibilité avec les données Unicode.

def create_connection(connection_string, password):
    connection = None

try:
        # print(f"Connecting to {connection_string}")
        connection = pyodbc.connect(connection_string + ";PWD=" + password)

        # Veiller à ce que les chaînes de caractères soient lues correctement
        connection.setdecoding(pyodbc.SQL_CHAR, encoding="utf8")
        connection.setdecoding(pyodbc.SQL_WCHAR, encoding="utf8")
        connection.setencoding(encoding="utf8")

    except pyodbc.Error as e:
        print(f"The error '{e}' occurred")

    return connection

connection.setdecoding(pyodbc.SQL_CHAR, encoding="utf8")

Désigné ci-dessus indique à pyodbc comment décoder les données de caractères de la base de données lors de la récupération des types SQL_CHAR (typiquement, les champs de caractères de longueur fixe).

connection.setdecoding(pyodbc.SQL_WCHAR, encoding="utf8")

Désigné ci-dessus définit le décodage pour SQL_WCHAR, les types de caractères larges (c'est-à-dire les chaînes Unicode, telles que NVARCHAR ou NCHAR dans SQL Server).

connection.setencoding(encoding="utf8")

Désigné ci-dessus garantit que toutes les chaînes ou données de caractères envoyées de Python à la base de données seront encodées en UTF-8, cela signifie que Python traduira son type str interne (qui est Unicode) en octets UTF-8 lors de la communication avec la base de données.


La mise en place de l'ensemble

Installation de JDBC

Installation de JAVA-utiliser dmg

https://www.oracle.com/middleeast/java/technologies/downloads/#jdk23-mac

Mise à jour du shell pour définir la version par défaut

$ /usr/libexec/java_home -V
Matching Java Virtual Machines (2):
    23 (arm64) "Oracle Corporation" - "Java SE 23" /Library/Java/JavaVirtualMachines/jdk-23.jdk/Contents/Home
    1.8.421.09 (arm64) "Oracle Corporation" - "Java" /Library/Internet Plug-Ins/JavaAppletPlugin.plugin/Contents/Home
/Library/Java/JavaVirtualMachines/jdk-23.jdk/Contents/Home
$ echo $SHELL
/opt/homebrew/bin/bash
$ vi ~/.bash_profile

Ajoutez JAVA_HOME à votre chemin

export JAVA_HOME=$(/usr/libexec/java_home -v 23)
export PATH=$JAVA_HOME/bin:$PATH

Obtention du pilote JDBC

https://intersystems-community.github.io/iris-driver-distribution/

Placement du fichier jar quelque part... Je l'ai placé dans $HOME

$ ls $HOME/*.jar
/Users/myname/intersystems-jdbc-3.8.4.jar

Exemple de code

Cela suppose que vous avez configuré ODBC (un exemple à suivre un autre jour, le chien a mangé mes notes...).

Remarque : il s'agit d'un piratage de mon propre code réel. Notez les noms des variables.

import os

import datetime
from datetime import date, time, datetime, timedelta

import pandas as pd
import pyodbc

import jaydebeapi
import jpype

def jdbc_create_connection(jdbc_url, jdbc_username, jdbc_password):

    # Chemin d'accès au pilote JDBC
    jdbc_driver_path = '/Users/yourname/intersystems-jdbc-3.8.4.jar'

    # Il faut s'assurer que JAVA_HOME est défini 
    os.environ['JAVA_HOME']='/Library/Java/JavaVirtualMachines/jdk-23.jdk/Contents/Home'
    os.environ['CLASSPATH'] = jdbc_driver_path

    # Démarrage de la JVM (si elle n'est pas déjà en cours d'exécution)
    if not jpype.isJVMStarted():
        jpype.startJVM(jpype.getDefaultJVMPath(), classpath=[jdbc_driver_path])

    # Connexion aux bases de données:
    connection = None

    try:
        connection = jaydebeapi.connect("com.intersystems.jdbc.IRISDriver",
                                  jdbc_url,
                                  [jdbc_username, jdbc_password],
                                  jdbc_driver_path)
        print("Connection successful")
    except Exception as e:
        print(f"An error occurred: {e}")

    return connection


def odbc_create_connection(connection_string):
    connection = None

    try:
        # print(f"Connecting to {connection_string}")
        connection = pyodbc.connect(connection_string)

        # Veiller à ce que les chaînes de caractères soient lues correctement
        connection.setdecoding(pyodbc.SQL_CHAR, encoding="utf8")
        connection.setdecoding(pyodbc.SQL_WCHAR, encoding="utf8")
        connection.setencoding(encoding="utf8")

    except pyodbc.Error as e:
        print(f"The error '{e}' occurred")

    return connection

# Paramètres

odbc_driver = "InterSystems ODBC"
odbc_host = "your_host"
odbc_port = "51773"
odbc_namespace = "your_namespace"
odbc_username = "username"
odbc_password = "password"

jdbc_host = "your_host"
jdbc_port = "51773"
jdbc_namespace = "your_namespace"
jdbc_username = "username"
jdbc_password = "password"

# Création d'une connexion et des graphiques

jdbc_used = True

if jdbc_used:
    print("Using JDBC")
    jdbc_url = f"jdbc:IRIS://{jdbc_host}:{jdbc_port}/{jdbc_namespace}?useUnicode=true&characterEncoding=UTF-8"
    connection = jdbc_create_connection(jdbc_url, jdbc_username, jdbc_password)
else:
    print("Using ODBC")
    connection_string = f"Driver={odbc_driver};Host={odbc_host};Port={odbc_port};Database={odbc_namespace};UID={odbc_username};PWD={odbc_password}"
    connection = odbc_create_connection(connection_string)


if connection is None:
    print("Unable to connect to IRIS")
    exit()

cursor = connection.cursor()

site = "SAMPLE"
table_name = "your.TableNAME"

desired_columns = [
    "RunDate",
    "ActiveUsersCount",
    "EpisodeCountEmergency",
    "EpisodeCountInpatient",
    "EpisodeCountOutpatient",
    "EpisodeCountTotal",
    "AppointmentCount",
    "PrintCountTotal",
    "site",
]

# Construction de la partie de la requête relative à la sélection des colonnes
column_selection = ", ".join(desired_columns)

query_string = f"SELECT {column_selection} FROM {table_name} WHERE Site = '{site}'"

print(query_string)
cursor.execute(query_string)

if jdbc_used:
    # Récupération des résultats
    results = []
    for row in cursor.fetchall():
        converted_row = [str(item) if isinstance(item, jpype.java.lang.String) else item for item in row]
        results.append(converted_row)

    #  Il faut récupérer les noms des colonnes et s'assurer qu'il s'agit bien de chaînes Python (java.lang.String is returned "(p,a,i,n,i,n,t,h,e,a,r,s,e)" 
    column_names = [str(col[0]) for col in cursor.description]

    # Création du cadre de données
    df = pd.DataFrame.from_records(results, columns=column_names)
    print(df.head().to_string())
else:
    # Dans le cas d'ensembles de résultats très volumineux, obtenez les résultats par blocs en utilisant cursor.fetchmany() ou fetchall()
    results = cursor.fetchall()
    # Obtention des noms des colonnes
    column_names = [column[0] for column in cursor.description]
    # Création du cadre de données
    df = pd.DataFrame.from_records(results, columns=column_names)

    print(df.head().to_string())

# # Construction des graphiques pour un site
# cf.build_7_day_rolling_average_chart(site, cursor, jdbc_used)

cursor.close()
connection.close()

# Arrêt de la JVM (si vous l'avez démarrée)
# jpype.shutdownJVM()
0
0 74
Article Lorenzo Scalese · Oct 10, 2024 30m read

Une expérience sur la manière d'utiliser le cadre LangChain, la recherche vectorielle IRIS et les LLM pour générer une base de données SQL compatible IRIS à partir des invites utilisateur.

Cet article a été rédigé à partir du carnet suivant. Vous pouvez l'utiliser dans un environnement prêt à l'emploi avec l'application suivante dans OpenExchange.

Configuration

Tout d'abord, nous devons installer les bibliothèques nécessaires:

!pip install --upgrade --quiet langchain langchain-openai langchain-iris pandas

Ensuite, nous importons les modules requis et configurons l'environnement:

import os
import datetime
import hashlib
from copy import deepcopy
from sqlalchemy import create_engine
import getpass
import pandas as pd
from langchain_core.prompts import PromptTemplate, ChatPromptTemplate
from langchain_core.example_selectors import SemanticSimilarityExampleSelector
from langchain_openai import OpenAIEmbeddings, ChatOpenAI
from langchain.docstore.document import Document
from langchain_community.document_loaders import DataFrameLoader
from langchain.text_splitter import CharacterTextSplitter
from langchain_core.output_parsers import StrOutputParser
from langchain.globals import set_llm_cache
from langchain.cache import SQLiteCache
from langchain_iris import IRISVector

Nous utiliserons SQLiteCache pour mettre en cache les appels LLM:

# Cache pour les appels LLM
set_llm_cache(SQLiteCache(database_path=".langchain.db"))

Configurez les paramètres de connexion à la base de données IRIS:

# Paramètres de connexion à la base de données IRIS
os.environ["ISC_LOCAL_SQL_HOSTNAME"] = "localhost"
os.environ["ISC_LOCAL_SQL_PORT"] = "1972"
os.environ["ISC_LOCAL_SQL_NAMESPACE"] = "IRISAPP"
os.environ["ISC_LOCAL_SQL_USER"] = "_system"
os.environ["ISC_LOCAL_SQL_PWD"] = "SYS"

Si la clé API OpenAI n'est pas déjà configurée dans l'environnement, demandez à l'utilisateur de la saisir:

if not "OPENAI_API_KEY" in os.environ:
    os.environ["OPENAI_API_KEY"] = getpass.getpass()

Créez la chaîne de connexion pour la base de données IRIS:

# Chaîne de connexion à la base de données IRIS
args = {
    'hostname': os.getenv("ISC_LOCAL_SQL_HOSTNAME"), 
    'port': os.getenv("ISC_LOCAL_SQL_PORT"), 
    'namespace': os.getenv("ISC_LOCAL_SQL_NAMESPACE"), 
    'username': os.getenv("ISC_LOCAL_SQL_USER"), 
    'password': os.getenv("ISC_LOCAL_SQL_PWD")
}
iris_conn_str = f"iris://{args['username']}:{args['password']}@{args['hostname']}:{args['port']}/{args['namespace']}"

Etablissez la connexion avec la base de données IRIS:

# Connexion à la base de données IRIS
engine = create_engine(iris_conn_str)
cnx = engine.connect().connection

Préparez un dictionnaire contenant les renseignements contextuels pour l'invite du système:

# Dict pour les renseignements contextuels de l'invite système
context = {}
context["top_k"] = 3

Création de l'invite

Pour transformer les données de l'utilisateur en requêtes SQL compatibles avec la base de données IRIS, nous devons créer une invite efficace pour le modèle linguistique. Nous commençons par une invite initiale qui fournit des instructions de base pour générer des requêtes SQL. Ce modèle est dérivé des Invites par défaut de LangChain pour MSSQL et personnalisé pour la base de données IRIS.

# Modèle d'invite de base avec instructions SQL pour la base de données IRIS
iris_sql_template = """
Vous êtes un expert InterSystems IRIS.  Compte tenu d'une question d'entrée, créez d'abord une requête InterSystems IRIS syntaxiquement correcte pour exécuter et renvoyer la réponse à la question saisie.
Si l'utilisateur ne spécifie pas dans la question un nombre spécifique d'exemples à obtenir, demandez au maximum {top_k} résultats en utilisant la clause TOP conformément à InterSystems IRIS. Vous pouvez classer les résultats de manière à obtenir les renseignements les plus pertinents de la base de données.
Ne faites jamais de requête pour toutes les colonnes d'une table. Vous ne devez requérir que les colonnes nécessaires pour répondre à la question.. Mettez chaque nom de colonne entre guillemets simples ('') pour indiquer qu'il s'agit d'identifiants délimités.
Veillez à n'utiliser que les noms de colonnes que vous pouvez voir dans les tables ci-dessous. Veillez à ne pas requérir les colonnes qui n'existent pas. Faites également attention à ce que les colonnes se trouvent dans les différents tables.
Veillez à utiliser la fonction CAST(CURRENT_DATE as date) pour obtenir la date du jour, si la question porte sur "aujourd'hui".
Utilisez des guillemets doubles pour délimiter les identifiants des colonnes.
Renvoyez des données SQL simples ; n'appliquez aucune forme de formatage.
"""

Cette invite de base configure le modèle linguistique (LLM) pour qu'il fonctionne comme un expert SQL avec des conseils spécifiques pour la base de données IRIS. Ensuite, nous fournissons une invite auxiliaire avec des renseignements sur le schéma de la base de données pour éviter les hallucinations.

# Extension des modèles SQL pour inclure les renseignements sur le contexte des tables
tables_prompt_template = """
N'utilisez que les tables suivantes:
{table_info}
"""

Afin d'améliorer la précision des réponses du LLM, nous utilisons une technique appelée "incitation en quelques coups" ("few-shot prompting"). Il s'agit de présenter quelques exemples au LLM.

# Extension du modèle SQL pour l'inclusion de quelques exemples
prompt_sql_few_shots_template = """
Vous trouverez ci-dessous un certain nombre d'exemples de questions et de requêtes SQL correspondantes.

{examples_value}
"""

Nous définissons le modèle pour des exemples en quelques coups:

# Modèle d'invite à quelques coups
example_prompt_template = "User input: {input}\nSQL query: {query}"
example_prompt = PromptTemplate.from_template(example_prompt_template)

Nous construisons l'invite utilisateur en utilisant le modèle en quelques coups:

# Modèle d'invite utilisateur
user_prompt = "\n" + example_prompt.invoke({"input": "{input}", "query": ""}).to_string()

Enfin, nous composons toutes les invites pour créer l'invite finale:

# Modèle d'invite complet
prompt = (
    ChatPromptTemplate.from_messages([("system", iris_sql_template)])
    + ChatPromptTemplate.from_messages([("system", tables_prompt_template)])
    + ChatPromptTemplate.from_messages([("system", prompt_sql_few_shots_template)])
    + ChatPromptTemplate.from_messages([("human", user_prompt)])
)
prompt

Cette invite attend les variables examples_value, input, table_info, et top_k.

Voici comment l'invite est structurée:

ChatPromptTemplate(
    input_variables=['examples_value', 'input', 'table_info', 'top_k'], 
    messages=[
        SystemMessagePromptTemplate(
            prompt=PromptTemplate(
                input_variables=['top_k'], 
                template=iris_sql_template
            )
        ), 
        SystemMessagePromptTemplate(
            prompt=PromptTemplate(
                input_variables=['table_info'], 
                template=tables_prompt_template
            )
        ), 
        SystemMessagePromptTemplate(
            prompt=PromptTemplate(
                input_variables=['examples_value'], 
                template=prompt_sql_few_shots_template
            )
        ), 
        HumanMessagePromptTemplate(
            prompt=PromptTemplate(
                input_variables=['input'], 
                template=user_prompt
            )
        )
    ]
)

Pour visualiser la manière dont l'invite sera envoyée au LLM, nous pouvons utiliser des valeurs de remplacement pour les variables requises:

prompt_value = prompt.invoke({
    "top_k": "<top_k>",
    "table_info": "<table_info>",
    "examples_value": "<examples_value>",
    "input": "<input>"
})
print(prompt_value.to_string())
Système: 
Vous êtes un expert d'InterSystems IRIS. Compte tenu d'une question d'entrée, créez d'abord une requête InterSystems IRIS syntaxiquement correcte pour exécuter et renvoyer la réponse à la question saisie.
Si l'utilisateur ne spécifie pas dans la question un nombre spécifique d'exemples à obtenir, demandez au maximum <top_k> résultats en utilisant la clause TOP conformément à InterSystems IRIS. Vous pouvez classer les résultats de manière à obtenir les renseignements les plus pertinents de la base de données.
Ne faites jamais de requête pour toutes les colonnes d'une table. Vous ne devez requérir que les colonnes nécessaires pour répondre à la question.. Mettez chaque nom de colonne entre guillemets simples ('') pour indiquer qu'il s'agit d'identifiants délimités.
Veillez à n'utiliser que les noms de colonnes que vous pouvez voir dans les tables ci-dessous. Veillez à ne pas requérir les colonnes qui n'existent pas. Faites également attention à ce que les colonnes se trouvent dans les différents tables.
Veillez à utiliser la fonction CAST(CURRENT_DATE as date) pour obtenir la date du jour, si la question porte sur "aujourd'hui".
Utilisez des guillemets doubles pour délimiter les identifiants des colonnes.
Renvoyez des données SQL simples; n'appliquez aucune forme de formatage.

Système: 
N'utilisez que les tables suivantes:
<table_info>

Système: 
Vous trouverez ci-dessous un certain nombre d'exemples de questions et de requêtes SQL correspondantes.

<examples_value>

Human: 
User input: <input>
SQL query: 

Maintenant, nous sommes prêts à envoyer cette invite au LLM en fournissant les variables nécessaires. Passons à l'étape suivante lorsque vous êtes prêt.

Fourniture des renseignements sur la table

Pour créer des requêtes SQL précises, nous devons fournir au modèle linguistique (LLM) des renseignements détaillés sur les tables de la base de données. Sans ces renseignements, le LLM pourrait générer des requêtes qui semblent plausibles mais qui sont incorrectes en raison d'hallucinations. Par conséquent, notre première étape consiste à créer une fonction qui récupère les définitions des tables de la base de données IRIS.

Fonction de récupération des définitions de tables

La fonction suivante interroge INFORMATION_SCHEMA pour obtenir les définitions de tables pour un schéma donné. Si une table spécifique est fournie, elle récupère la définition de cette table ; sinon, elle récupère les définitions de toutes les tables du schéma.

def get_table_definitions_array(cnx, schema, table=None):
    cursor = cnx.cursor()

    # Requête de base pour obtenir les renseignements sur les colonnes
    query = """
    SELECT TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME, DATA_TYPE, IS_NULLABLE, COLUMN_DEFAULT, PRIMARY_KEY, null EXTRA
    FROM INFORMATION_SCHEMA.COLUMNS
    WHERE TABLE_SCHEMA = %s
    """
    
    # Paramètres de la requête
    params = [schema]

    # Ajout de filtres optionnels
    if table:
        query += " AND TABLE_NAME = %s"
        params.append(table)
    
    # Exécution de la requête
    cursor.execute(query, params)

    # Récupération des résultats
    rows = cursor.fetchall()
    
    # Traitement des résultats pour générer la (les) définition(s) de table(s)
    table_definitions = {}
    for row in rows:
        table_schema, table_name, column_name, column_type, is_nullable, column_default, column_key, extra = row
        if table_name not in table_definitions:
            table_definitions[table_name] = []
        table_definitions[table_name].append({
            "column_name": column_name,
            "column_type": column_type,
            "is_nullable": is_nullable,
            "column_default": column_default,
            "column_key": column_key,
            "extra": extra
        })

    primary_keys = {}
    
    # Construire la chaîne de sortie
    result = []
    for table_name, columns in table_definitions.items():
        table_def = f"CREATE TABLE {schema}.{table_name} (\n"
        column_definitions = []
        for column in columns:
            column_def = f"  {column['column_name']} {column['column_type']}"
            if column['is_nullable'] == "NO":
                column_def += " NOT NULL"
            if column['column_default'] is not None:
                column_def += f" DEFAULT {column['column_default']}"
            if column['extra']:
                column_def += f" {column['extra']}"
            column_definitions.append(column_def)
        if table_name in primary_keys:
            pk_def = f"  PRIMARY KEY ({', '.join(primary_keys[table_name])})"
            column_definitions.append(pk_def)
        table_def += ",\n".join(column_definitions)
        table_def += "\n);"
        result.append(table_def)

    return result

Récupération des définitions de tables pour un schéma

Pour cet exemple, nous utilisons le schéma "Aviation", qui est disponible ici.

# Récupération des définitions de tables pour un schéma "Aviation"
tables = get_table_definitions_array(cnx, "Aviation")
print(tables)

Cette fonction renvoie les instructions CREATE TABLE (creer une table) pour toutes les tables du schéma "Aviation":

[
    'CREATE TABLE Aviation.Aircraft (\n  Event bigint NOT NULL,\n  ID varchar NOT NULL,\n  AccidentExplosion varchar,\n  AccidentFire varchar,\n  AirFrameHours varchar,\n  AirFrameHoursSince varchar,\n  AirFrameHoursSinceLastInspection varchar,\n  AircraftCategory varchar,\n  AircraftCertMaxGrossWeight integer,\n  AircraftHomeBuilt varchar,\n  AircraftKey integer NOT NULL,\n  AircraftManufacturer varchar,\n  AircraftModel varchar,\n  AircraftRegistrationClass varchar,\n  AircraftSerialNo varchar,\n  AircraftSeries varchar,\n  Damage varchar,\n  DepartureAirportId varchar,\n  DepartureCity varchar,\n  DepartureCountry varchar,\n  DepartureSameAsEvent varchar,\n  DepartureState varchar,\n  DepartureTime integer,\n  DepartureTimeZone varchar,\n  DestinationAirportId varchar,\n  DestinationCity varchar,\n  DestinationCountry varchar,\n  DestinationSameAsLocal varchar,\n  DestinationState varchar,\n  EngineCount integer,\n  EvacuationOccurred varchar,\n  EventId varchar NOT NULL,\n  FlightMedical varchar,\n  FlightMedicalType varchar,\n  FlightPhase integer,\n  FlightPlan varchar,\n  FlightPlanActivated varchar,\n  FlightSiteSeeing varchar,\n  FlightType varchar,\n  GearType varchar,\n  LastInspectionDate timestamp,\n  LastInspectionType varchar,\n  Missing varchar,\n  OperationDomestic varchar,\n  OperationScheduled varchar,\n  OperationType varchar,\n  OperatorCertificate varchar,\n  OperatorCertificateNum varchar,\n  OperatorCode varchar,\n  OperatorCountry varchar,\n  OperatorIndividual varchar,\n  OperatorName varchar,\n  OperatorState varchar,\n  Owner varchar,\n  OwnerCertified varchar,\n  OwnerCountry varchar,\n  OwnerState varchar,\n  RegistrationNumber varchar,\n  ReportedToICAO varchar,\n  SeatsCabinCrew integer,\n  SeatsFlightCrew integer,\n  SeatsPassengers integer,\n  SeatsTotal integer,\n  SecondPilot varchar,\n  childsub bigint NOT NULL DEFAULT $i(^Aviation.EventC("Aircraft"))\n);',
    'CREATE TABLE Aviation.Crew (\n  Aircraft varchar NOT NULL,\n  ID varchar NOT NULL,\n  Age integer,\n  AircraftKey integer NOT NULL,\n  Category varchar,\n  CrewNumber integer NOT NULL,\n  EventId varchar NOT NULL,\n  Injury varchar,\n  MedicalCertification varchar,\n  MedicalCertificationDate timestamp,\n  MedicalCertificationValid varchar,\n  Seat varchar,\n  SeatbeltUsed varchar,\n  Sex varchar,\n  ShoulderHarnessUsed varchar,\n  ToxicologyTestPerformed varchar,\n  childsub bigint NOT NULL DEFAULT $i(^Aviation.AircraftC("Crew"))\n);',
    'CREATE TABLE Aviation.Event (\n  ID bigint NOT NULL DEFAULT $i(^Aviation.EventD),\n  AirportDirection integer,\n  AirportDistance varchar,\n  AirportElevation integer,\n  AirportLocation varchar,\n  AirportName varchar,\n  Altimeter varchar,\n  EventDate timestamp,\n  EventId varchar NOT NULL,\n  EventTime integer,\n  FAADistrictOffice varchar,\n  InjuriesGroundFatal integer,\n  InjuriesGroundMinor integer,\n  InjuriesGroundSerious integer,\n  InjuriesHighest varchar,\n  InjuriesTotal integer,\n  InjuriesTotalFatal integer,\n  InjuriesTotalMinor integer,\n  InjuriesTotalNone integer,\n  InjuriesTotalSerious integer,\n  InvestigatingAgency varchar,\n  LightConditions varchar,\n  LocationCity varchar,\n  LocationCoordsLatitude double,\n  LocationCoordsLongitude double,\n  LocationCountry varchar,\n  LocationSiteZipCode varchar,\n  LocationState varchar,\n  MidAir varchar,\n  NTSBId varchar,\n  NarrativeCause varchar,\n  NarrativeFull varchar,\n  NarrativeSummary varchar,\n  OnGroundCollision varchar,\n  SkyConditionCeiling varchar,\n  SkyConditionCeilingHeight integer,\n  SkyConditionNonCeiling varchar,\n  SkyConditionNonCeilingHeight integer,\n  TimeZone varchar,\n  Type varchar,\n  Visibility varchar,\n  WeatherAirTemperature integer,\n  WeatherPrecipitation varchar,\n  WindDirection integer,\n  WindDirectionIndicator varchar,\n  WindGust integer,\n  WindGustIndicator varchar,\n  WindVelocity integer,\n  WindVelocityIndicator varchar\n);'
]

Avec ces définitions de tables, nous pouvons passer à l'étape suivante, qui consiste à les intégrer dans notre invite pour le LLM. Cela permet de s'assurer que le LLM a des renseignements précis et complets sur le schéma de la base de données lorsqu'il génère des requêtes SQL.

Sélection des tables les plus pertinentes

Lorsque vous travaillez avec des bases de données, en particulier les plus grandes, l'envoi du langage de définition des données (DDL) pour toutes les tables d'une invite peut s'avérer peu pratique. Si cette approche peut fonctionner pour les petites bases de données, les bases de données réelles contiennent souvent des centaines ou des milliers de tables, ce qui rend inefficace le traitement de chacune d'entre elles.

De plus, il est peu probable qu'un modèle linguistique ait besoin de connaître toutes les tables de la base de données pour générer efficacement des requêtes SQL. Pour relever ce défi, nous pouvons exploiter les capacités de recherche sémantique pour sélectionner uniquement les tables les plus pertinentes en fonction de la requête de l'utilisateu.

Approche

Nous y parvenons en utilisant la recherche sémantique avec IRIS Vector Search. Notez que cette méthode est plus efficace si les identifiants de vos éléments SQL (tels que les tables, les champs et les clés) ont des noms significatifs. Si vos identifiants sont des codes arbitraires, envisagez plutôt d'utiliser un dictionnaire de données.

Étapes

  1. Récupération des renseignements sur les tables

Commencez par extraire les définitions des tables dans d'un objet DataFrame pandas:

# Récupérer les définitions de tables dans un objet DataFrame pandas
table_def = get_table_definitions_array(cnx=cnx, schema='Aviation')
table_df = pd.DataFrame(data=table_def, columns=["col_def"])
table_df["id"] = table_df.index + 1
table_df

L'objet DataFrame (table_df) ressemblera à ceci:

col_defid
0CREATE TABLE Aviation.Aircraft (\n Event bigi...1
1CREATE TABLE Aviation.Crew (\n Aircraft varch...2
2CREATE TABLE Aviation.Event (\n ID bigint NOT...3
  1. Répartition des définitions dans des documents

Ensuite, répartissez les définitions des tables dans les documents Langchain. . Cette étape est cruciale pour gérer de gros fragment de texte et extraire des incorporations de texte:

loader = DataFrameLoader(table_df, page_content_column="col_def")
documents = loader.load()
text_splitter = CharacterTextSplitter(chunk_size=400, chunk_overlap=20, separator="\n")
tables_docs = text_splitter.split_documents(documents)
tables_docs

La liste tables_docs qui en résulte contient des documents fractionnés avec des métadonnées, comme suit:

[Document(metadata={'id': 1}, page_content='CREATE TABLE Aviation.Aircraft (\n  Event bigint NOT NULL,\n  ID varchar NOT NULL,\n  ...'),
 Document(metadata={'id': 2}, page_content='CREATE TABLE Aviation.Crew (\n  Aircraft varchar NOT NULL,\n  ID varchar NOT NULL,\n  ...'),
 Document(metadata={'id': 3}, page_content='CREATE TABLE Aviation.Event (\n  ID bigint NOT NULL DEFAULT $i(^Aviation.EventD),\n  ...')]
  1. Extraction des incorporations et stockage dans IRIS

Utilisez maintenant la classe IRISVector de langchain-iris pour extraire les vecteurs d'intégration et les stocker:

tables_vector_store = IRISVector.from_documents(
    embedding=OpenAIEmbeddings(), 
    documents=tables_docs,
    connection_string=iris_conn_str,
    collection_name="sql_tables",
    pre_delete_collection=True
)

Remarque : l'indicateur pre_delete_collection est fixé à True (vrai) à des fins de démonstration, afin de garantir une nouvelle collection à chaque exécution du test. Dans un environnement de production, cet indicateur doit généralement être défini sur False (faux).

  1. Recherche de documents pertinents Avec les incorporations de table stockées, vous pouvez désormais interroger les tables pertinentes en fonction des données de l'utilisateur:
input_query = "List the first 2 manufacturers"
relevant_tables_docs = tables_vector_store.similarity_search(input_query, k=3)
relevant_tables_docs

Par exemple, une requête portant sur les fabricants peut aboutir à un résultat:

[Document(metadata={'id': 1}, page_content='GearType varchar,\n  LastInspectionDate timestamp,\n  ...'),
 Document(metadata={'id': 1}, page_content='AircraftModel varchar,\n  AircraftRegistrationClass varchar,\n  ...'),
 Document(metadata={'id': 3}, page_content='LocationSiteZipCode varchar,\n  LocationState varchar,\n  ...')]

À partir des métadonnées, vous pouvez voir que seul la table ID 1 (Aviation.Avion) est pertinente, ce qui correspond à la requête.

  1. Gestion des cas limites

Bien que cette approche soit généralement efficace, elle n'est pas toujours parfaite. Par exemple, la recherche de sites d'accidents peut également renvoyer des tables moins pertinentes:

input_query = "List the top 10 most crash sites"
relevant_tables_docs = tables_vector_store.similarity_search(input_query, k=3)
relevant_tables_docs

Les résultats peuvent inclure ce qui suit:

[Document(metadata={'id': 3}, page_content='LocationSiteZipCode varchar,\n  LocationState varchar,\n  ...'),
 Document(metadata={'id': 3}, page_content='InjuriesGroundSerious integer,\n  InjuriesHighest varchar,\n  ...'),
 Document(metadata={'id': 1}, page_content='CREATE TABLE Aviation.Aircraft (\n  Event bigint NOT NULL,\n  ID varchar NOT NULL,\n  ...')]

Bien que la table Aviation.Event ait été récupérée deux fois, la table Aviation.Aircraft peut également apparaître, ce qui pourrait être amélioré par un filtrage ou un seuillage supplémentaire. Cela dépasse le cadre de cet exemple et sera laissé à l'appréciation de futures implémentations.

  1. Définition d'une fonction pour récupérer les tables pertinentes

Pour automatiser ce processus, définissez une fonction qui filtre et renvoie les tableaux pertinents en fonction des données de l'utilisateur:

def get_relevant_tables(user_input, tables_vector_store, table_df):
    relevant_tables_docs = tables_vector_store.similarity_search(user_input)
    relevant_tables_docs_indices = [x.metadata["id"] for x in relevant_tables_docs]
    indices = table_df["id"].isin(relevant_tables_docs_indices)
    relevant_tables_array = [x for x in table_df[indices]["col_def"]]
    return relevant_tables_array

Cette fonction permet d'extraire efficacement les tables pertinentes à envoyer au LLM, de réduire la longueur de l'invite et d'améliorer les performances globales de la requête.

Sélection des exemples les plus pertinents ("invitation en quelques coups")

Lorsque vous travaillez avec des modèles linguistiques (LLM), le fait de leur fournir des exemples pertinents permet d'obtenir des réponses précises et adaptées au contexte. Ces exemples, appelés "quelques exemples", guident le LLM dans la compréhension de la structure et du contexte des requêtes qu'il doit traiter.

Dans notre cas, nous devons remplir la variable examples_value avec un ensemble varié de requêtes SQL qui couvrent un large spectre de la syntaxe SQL d'IRIS et des tables disponibles dans la base de données. Cela permet d'éviter que le LLM ne génère des requêtes incorrectes ou non pertinentes.

Définition de requêtes d'exemple

Vous trouverez ci-dessous une liste d'exemples de requêtes conçues pour illustrer diverses opérations SQL:

examples = [
    {"input": "List all aircrafts.", "query": "SELECT * FROM Aviation.Aircraft"},
    {"input": "Find all incidents for the aircraft with ID 'N12345'.", "query": "SELECT * FROM Aviation.Event WHERE EventId IN (SELECT EventId FROM Aviation.Aircraft WHERE ID = 'N12345')"},
    {"input": "List all incidents in the 'Commercial' operation type.", "query": "SELECT * FROM Aviation.Event WHERE EventId IN (SELECT EventId FROM Aviation.Aircraft WHERE OperationType = 'Commercial')"},
    {"input": "Find the total number of incidents.", "query": "SELECT COUNT(*) FROM Aviation.Event"},
    {"input": "List all incidents that occurred in 'Canada'.", "query": "SELECT * FROM Aviation.Event WHERE LocationCountry = 'Canada'"},
    {"input": "How many incidents are associated with the aircraft with AircraftKey 5?", "query": "SELECT COUNT(*) FROM Aviation.Aircraft WHERE AircraftKey = 5"},
    {"input": "Find the total number of distinct aircrafts involved in incidents.", "query": "SELECT COUNT(DISTINCT AircraftKey) FROM Aviation.Aircraft"},
    {"input": "List all incidents that occurred after 5 PM.", "query": "SELECT * FROM Aviation.Event WHERE EventTime > 1700"},
    {"input": "Who are the top 5 operators by the number of incidents?", "query": "SELECT TOP 5 OperatorName, COUNT(*) AS IncidentCount FROM Aviation.Aircraft GROUP BY OperatorName ORDER BY IncidentCount DESC"},
    {"input": "Which incidents occurred in the year 2020?", "query": "SELECT * FROM Aviation.Event WHERE YEAR(EventDate) = '2020'"},
    {"input": "What was the month with most events in the year 2020?", "query": "SELECT TOP 1 MONTH(EventDate) EventMonth, COUNT(*) EventCount FROM Aviation.Event WHERE YEAR(EventDate) = '2020' GROUP BY MONTH(EventDate) ORDER BY EventCount DESC"},
    {"input": "How many crew members were involved in incidents?", "query": "SELECT COUNT(*) FROM Aviation.Crew"},
    {"input": "List all incidents with detailed aircraft information for incidents that occurred in the year 2012.", "query": "SELECT e.EventId, e.EventDate, a.AircraftManufacturer, a.AircraftModel, a.AircraftCategory FROM Aviation.Event e JOIN Aviation.Aircraft a ON e.EventId = a.EventId WHERE Year(e.EventDate) = 2012"},
    {"input": "Find all incidents where there were more than 5 injuries and include the aircraft manufacturer and model.", "query": "SELECT e.EventId, e.InjuriesTotal, a.AircraftManufacturer, a.AircraftModel FROM Aviation.Event e JOIN Aviation.Aircraft a ON e.EventId = a.EventId WHERE e.InjuriesTotal > 5"},
    {"input": "List all crew members involved in incidents with serious injuries, along with the incident date and location.", "query": "SELECT c.CrewNumber AS 'Crew Number', c.Age, c.Sex AS Gender, e.EventDate AS 'Event Date', e.LocationCity AS 'Location City', e.LocationState AS 'Location State' FROM Aviation.Crew c JOIN Aviation.Event e ON c.EventId = e.EventId WHERE c.Injury = 'Serious'"}
]

Sélection d'exemples pertinents

Compte tenu de la liste d'exemples qui ne cesse de s'allonger, il n'est pas pratique de fournir tous les exemples au LLM. Au lieu de cela, nous utilisons la recherche vectorielle d'IRIS et la classe SemanticSimilarityExampleSelector pour identifier les exemples les plus pertinents sur la base des invites de l'utilisateur.

Définition du Sélecteur d'exemples:

example_selector = SemanticSimilarityExampleSelector.from_examples(
    examples,
    OpenAIEmbeddings(),
    IRISVector,
    k=5,
    input_keys=["input"],
    connection_string=iris_conn_str,
    collection_name="sql_samples",
    pre_delete_collection=True
)

Remarque : l'indicateur pre_delete_collection est fixé ici à des fins de démonstration, afin de garantir une nouvelle collection à chaque exécution du test. Dans un environnement de production, cet indicateur doit être défini sur Faux (false) pour éviter les suppressions inutiles.

Requéte du sélecteur:

Pour trouver les exemples les plus pertinents pour une saisie donnée, utilisez le sélecteur comme suit:

input_query = "Find all events in 2010 informing the Event Id and date, location city and state, aircraft manufacturer and model."
relevant_examples = example_selector.select_examples({"input": input_query})

Les résultats pourraient ressembler à ceci:

[{'input': 'List all incidents with detailed aircraft information for incidents that occurred in the year 2012.', 'query': 'SELECT e.EventId, e.EventDate, a.AircraftManufacturer, a.AircraftModel, a.AircraftCategory FROM Aviation.Event e JOIN Aviation.Aircraft a ON e.EventId = a.EventId WHERE Year(e.EventDate) = 2012'},
 {'input': "Find all incidents for the aircraft with ID 'N12345'.", 'query': "SELECT * FROM Aviation.Event WHERE EventId IN (SELECT EventId FROM Aviation.Aircraft WHERE ID = 'N12345')"},
 {'input': 'Find all incidents where there were more than 5 injuries and include the aircraft manufacturer and model.', 'query': 'SELECT e.EventId, e.InjuriesTotal, a.AircraftManufacturer, a.AircraftModel FROM Aviation.Event e JOIN Aviation.Aircraft a ON e.EventId = a.EventId WHERE e.InjuriesTotal > 5'},
 {'input': 'List all aircrafts.', 'query': 'SELECT * FROM Aviation.Aircraft'},
 {'input': 'Find the total number of distinct aircrafts involved in incidents.', 'query': 'SELECT COUNT(DISTINCT AircraftKey) FROM Aviation.Aircraft'}]

Si vous avez spécifiquement besoin d'exemples liés aux quantités, vous pouvez interroger le sélecteur en conséquence:

input_query = "What is the number of incidents involving Boeing aircraft."
quantity_examples = example_selector.select_examples({"input": input_query})

Le résultat peut être comme suit:

[{'input': 'How many incidents are associated with the aircraft with AircraftKey 5?', 'query': 'SELECT COUNT(*) FROM Aviation.Aircraft WHERE AircraftKey = 5'},
 {'input': 'Find the total number of distinct aircrafts involved in incidents.', 'query': 'SELECT COUNT(DISTINCT AircraftKey) FROM Aviation.Aircraft'},
 {'input': 'How many crew members were involved in incidents?', 'query': 'SELECT COUNT(*) FROM Aviation.Crew'},
 {'input': 'Find all incidents where there were more than 5 injuries and include the aircraft manufacturer and model.', 'query': 'SELECT e.EventId, e.InjuriesTotal, a.AircraftManufacturer, a.AircraftModel FROM Aviation.Event e JOIN Aviation.Aircraft a ON e.EventId = a.EventId WHERE e.InjuriesTotal > 5'},
 {'input': 'List all incidents with detailed aircraft information for incidents that occurred in the year 2012.', 'query': 'SELECT e.EventId, e.EventDate, a.AircraftManufacturer, a.AircraftModel, a.AircraftCategory FROM Aviation.Event e JOIN Aviation.Aircraft a ON e.EventId = a.EventId WHERE Year(e.EventDate) = 2012'}]

Ce résultat comprend des exemples qui traitent spécifiquement du comptage et des quantités.

Considérations futures

Bien que le sélecteur SemanticSimilarityExampleSelector soit puissant, il est important de noter que tous les exemples sélectionnés ne sont pas forcément parfaits. Les améliorations futures peuvent impliquer l'ajout de filtres ou de seuils pour exclure les résultats moins pertinents, garantissant que seuls les exemples les plus appropriés sont fournis au LLM.

Test de précision

Pour évaluer les performances de l'invite et de la génération de requêtes SQL, nous devons mettre en place et exécuter une série de tests. L'objectif est d'évaluer dans quelle mesure le LLM génère des requêtes SQL basées sur les données de l'utilisateur, avec et sans l'utilisation de quelques coups basés sur des exemples.

Fonction de génération de requêtes SQL

Nous commençons par définir une fonction qui utilise le LLM pour générer des requêtes SQL en fonction du contexte fourni, de l'invite, de la saisie de l'utilisateur et d'autres paramètres:

def get_sql_from_text(context, prompt, user_input, use_few_shots, tables_vector_store, table_df, example_selector=None, example_prompt=None):
    relevant_tables = get_relevant_tables(user_input, tables_vector_store, table_df)
    context["table_info"] = "\n\n".join(relevant_tables)

    examples = example_selector.select_examples({"input": user_input}) if example_selector else []
    context["examples_value"] = "\n\n".join([
        example_prompt.invoke(x).to_string() for x in examples
    ])
    
    model = ChatOpenAI(model="gpt-3.5-turbo", temperature=0)
    output_parser = StrOutputParser()
    chain_model = prompt | model | output_parser
    
    response = chain_model.invoke({
        "top_k": context["top_k"],
        "table_info": context["table_info"],
        "examples_value": context["examples_value"],
        "input": user_input
    })
    return response

Nous commençons par définir une fonction qui utilise le LLM pour générer des requêtes SQL en fonction du contexte fourni, de l'invite, de la saisie de l'utilisateur et d'autres paramètres

Testez l'invite avec et sans exemples:

# Exécution de l'invite **avec** quelques coups
input = "Find all events in 2010 informing the Event Id and date, location city and state, aircraft manufacturer and model."
response_with_few_shots = get_sql_from_text(
    context, 
    prompt, 
    user_input=input, 
    use_few_shots=True, 
    tables_vector_store=tables_vector_store, 
    table_df=table_df,
    example_selector=example_selector, 
    example_prompt=example_prompt,
)
print(response_with_few_shots)
SELECT e.EventId, e.EventDate, e.LocationCity, e.LocationState, a.AircraftManufacturer, a.AircraftModel
FROM Aviation.Event e
JOIN Aviation.Aircraft a ON e.EventId = a.EventId
WHERE Year(e.EventDate) = 2010
# Exécution de l'invite **sans** quelques coups
input = "Find all events in 2010 informing the Event Id and date, location city and state, aircraft manufacturer and model."
response_with_no_few_shots = get_sql_from_text(
    context, 
    prompt, 
    user_input=input, 
    use_few_shots=False, 
    tables_vector_store=tables_vector_store, 
    table_df=table_df,
)
print(response_with_no_few_shots)
SELECT TOP 3 "EventId", "EventDate", "LocationCity", "LocationState", "AircraftManufacturer", "AircraftModel"
FROM Aviation.Event e
JOIN Aviation.Aircraft a ON e.ID = a.Event
WHERE e.EventDate >= '2010-01-01' AND e.EventDate < '2011-01-01'
Utility Functions for Testing

Pour tester les requêtes SQL générées, nous définissons quelques fonctions utilitaires:

def execute_sql_query(cnx, query):
    try:
        cursor = cnx.cursor()
        cursor.execute(query)
        rows = cursor.fetchall()
        return rows
    except:
        print('Error running query:')
        print(query)
        print('-'*80)
    return None

def sql_result_equals(cnx, query, expected):
    rows = execute_sql_query(cnx, query)
    result = [set(row._asdict().values()) for row in rows or []]
    if result != expected and rows is not None:
        print('Result not as expected for query:')
        print(query)
        print('-'*80)
    return result == expected
# Test SQL pour l'invite **avec** quelques coups
print("SQL is OK" if not execute_sql_query(cnx, response_with_few_shots) is None else "SQL is not OK")
    SQL is OK
# Test SQL pour l'invite **sans** quelques coups
print("SQL is OK" if not execute_sql_query(cnx, response_with_no_few_shots) is None else "SQL is not OK")
    error on running query: 
    SELECT TOP 3 "EventId", "EventDate", "LocationCity", "LocationState", "AircraftManufacturer", "AircraftModel"
    FROM Aviation.Event e
    JOIN Aviation.Aircraft a ON e.ID = a.Event
    WHERE e.EventDate >= '2010-01-01' AND e.EventDate < '2011-01-01'
    --------------------------------------------------------------------------------
    SQL is not OK

Définition et exécution des tests

Définissez un ensemble de scénarios de test et les exécutez:

tests = [{
    "input": "What were the top 3 years with the most recorded events?",
    "expected": [{128, 2003}, {122, 2007}, {117, 2005}]
},{
    "input": "How many incidents involving Boeing aircraft.",
    "expected": [{5}]
},{
    "input": "How many incidents that resulted in fatalities.",
    "expected": [{237}]
},{
    "input": "List event Id and date and, crew number, age and gender for incidents that occurred in 2013.",
    "expected": [{1, datetime.datetime(2013, 3, 4, 11, 6), '20130305X71252', 59, 'M'},
                 {1, datetime.datetime(2013, 1, 1, 15, 0), '20130101X94035', 32, 'M'},
                 {2, datetime.datetime(2013, 1, 1, 15, 0), '20130101X94035', 35, 'M'},
                 {1, datetime.datetime(2013, 1, 12, 15, 0), '20130113X42535', 25, 'M'},
                 {2, datetime.datetime(2013, 1, 12, 15, 0), '20130113X42535', 34, 'M'},
                 {1, datetime.datetime(2013, 2, 1, 15, 0), '20130203X53401', 29, 'M'},
                 {1, datetime.datetime(2013, 2, 15, 15, 0), '20130218X70747', 27, 'M'},
                 {1, datetime.datetime(2013, 3, 2, 15, 0), '20130303X21011', 49, 'M'},
                 {1, datetime.datetime(2013, 3, 23, 13, 52), '20130326X85150', 'M', None}]
},{
    "input": "Find the total number of incidents that occurred in the United States.",
    "expected": [{1178}]
},{
    "input": "List all incidents latitude and longitude coordinates with more than 5 injuries that occurred in 2010.",
    "expected": [{-78.76833333333333, 43.25277777777778}]
},{
    "input": "Find all incidents in 2010 informing the Event Id and date, location city and state, aircraft manufacturer and model.",
    "expected": [
        {datetime.datetime(2010, 5, 20, 13, 43), '20100520X60222', 'CIRRUS DESIGN CORP', 'Farmingdale', 'New York', 'SR22'},
        {datetime.datetime(2010, 4, 11, 15, 0), '20100411X73253', 'CZECH AIRCRAFT WORKS SPOL SRO', 'Millbrook', 'New York', 'SPORTCRUISER'},
        {'108', datetime.datetime(2010, 1, 9, 12, 55), '20100111X41106', 'Bayport', 'New York', 'STINSON'},
        {datetime.datetime(2010, 8, 1, 14, 20), '20100801X85218', 'A185F', 'CESSNA', 'New York', 'Newfane'}
    ]
}]

Évaluation de la précision

Exécutez les tests et calculez la précision:

def execute_tests(cnx, context, prompt, use_few_shots, tables_vector_store, table_df, example_selector, example_prompt):
    tests_generated_sql = [(x, get_sql_from_text(
            context, 
            prompt, 
            user_input=x['input'], 
            use_few_shots=use_few_shots, 
            tables_vector_store=tables_vector_store, 
            table_df=table_df,
            example_selector=example_selector if use_few_shots else None, 
            example_prompt=example_prompt if use_few_shots else None,
        )) for x in deepcopy(tests)]
    
    tests_sql_executions = [(x[0], sql_result_equals(cnx, x[1], x[0]['expected'])) 
                            for x in tests_generated_sql]
    
    accuracy = sum(1 for i in tests_sql_executions if i[1] == True) / len(tests_sql_executions)
    print(f'Accuracy: {accuracy}')
    print('-'*80)

Résultats

# Tests de précision pour les invites exécutées **sans** quelques coups
use_few_shots = False
execute_tests(
    cnx,
    context, 
    prompt, 
    use_few_shots, 
    tables_vector_store, 
    table_df, 
    example_selector, 
    example_prompt
)
    error on running query: 
    SELECT "EventDate", COUNT("EventId") as "TotalEvents"
    FROM Aviation.Event
    GROUP BY "EventDate"
    ORDER BY "TotalEvents" DESC
    TOP 3;
    --------------------------------------------------------------------------------
    error on running query: 
    SELECT "EventId", "EventDate", "C"."CrewNumber", "C"."Age", "C"."Sex"
    FROM "Aviation.Event" AS "E"
    JOIN "Aviation.Crew" AS "C" ON "E"."ID" = "C"."EventId"
    WHERE "E"."EventDate" >= '2013-01-01' AND "E"."EventDate" < '2014-01-01'
    --------------------------------------------------------------------------------
    result not expected for query: 
    SELECT TOP 3 "e"."EventId", "e"."EventDate", "e"."LocationCity", "e"."LocationState", "a"."AircraftManufacturer", "a"."AircraftModel"
    FROM "Aviation"."Event" AS "e"
    JOIN "Aviation"."Aircraft" AS "a" ON "e"."ID" = "a"."Event"
    WHERE "e"."EventDate" >= '2010-01-01' AND "e"."EventDate" < '2011-01-01'
    --------------------------------------------------------------------------------
    accuracy: 0.5714285714285714
    --------------------------------------------------------------------------------
# Tests de précision pour les invites exécutées **avec** quelques coups
use_few_shots = True
execute_tests(
    cnx,
    context, 
    prompt, 
    use_few_shots, 
    tables_vector_store, 
    table_df, 
    example_selector, 
    example_prompt
)
    error on running query: 
    SELECT e.EventId, e.EventDate, e.LocationCity, e.LocationState, a.AircraftManufacturer, a.AircraftModel
    FROM Aviation.Event e
    JOIN Aviation.Aircraft a ON e.EventId = a.EventId
    WHERE Year(e.EventDate) = 2010 TOP 3
    --------------------------------------------------------------------------------
    accuracy: 0.8571428571428571
    --------------------------------------------------------------------------------

Conclusion

La précision des requêtes SQL générées avec des exemples (quelques coups) est environ 49% plus élevée que celles générées sans exemples (85% contre 57%).

Références

0
0 52
Article Guillaume Rongier · Oct 6, 2024 21m read

Cela fait maintenant plus de 2 ans que j'utilise quotidiennement Embedded Python. Il est peut-être temps de partager un retour d'expérience sur ce parcours.

Pourquoi écrire ce commentaire de retour d'expérience? Parce que, je suppose, je suis comme la plupart de mes collègues ici, un développeur ObjectScript, et je pense que la communauté bénéficierait de ce retour d'expérience et pourrait mieux comprendre les avantages et les inconvénients du choix de Embedded Python pour développer quelque chose dans IRIS. Et aussi éviter certains pièges.

image

Introduction

Je suis développeur depuis 2010, et j'ai travaillé avec ObjectScript depuis 2013.

Donc, c'est à peu près 10 ans d'expérience avec ObjectScript.

Depuis 2021, et la sortie de Embedded Python dans IRIS, je me suis lancé un défi :

  • Apprendre Python
  • Faire autant que possible tout ce qui est en Python.

Quand j'ai commencé ce parcours, je n'avais aucune idée de ce qu'était Python. J'ai donc commencé par les bases, et je continue d'apprendre chaque jour.

Débuter avec Python

L'avantage de Python est sa facilité d'apprentissage. C'est encore plus facile quand on connaît déjà ObjectScript.

Pourquoi ? Ils ont beaucoup de choses en commun.

ObjectScriptPython
Non typéNon typé
Langage de scriptLangage de script
Orienté objetOrienté objet
InterprétéInterprété
Intégration simple du CIntégration simple du C

Donc, si vous connaissez ObjectScript, vous en savez déjà beaucoup sur Python.

Mais il y a quelques différences, et certaines d'entre elles ne sont pas faciles à comprendre.

Python n'est pas ObjectScript

Mais il y a quelques différences, et certaines d'entre elles ne sont pas faciles à comprendre.

Pour moi il y a principalement 3 différences :

  • Pep8
  • Modules
  • Dunders

Pep8

Mais qu'est-ce que Pep8 ?

Il s'agit d'un ensemble de règles pour écrire du code Python.

pep8.org

Quelques-unes d'entre elles sont :

  • convention de nommage
  • noms de variables
    • snake_case
  • noms de classes
    • CamelCase
  • indentation
  • longueur de ligne
  • etc.

Pourquoi est-ce important ?

Parce que c'est la façon d'écrire du code Python. Et si vous ne suivez pas ces règles, vous aurez du mal à lire le code écrit par d'autres personnes, et elles auront du mal à lire le vôtre.

En tant que développeurs ObjectScript, nous avons aussi quelques règles à suivre, mais elles ne sont pas aussi strictes que Pep8.

J'ai appris Pep8 à la dure.

Je voudrais vous raconter petite histoire, je suis ingénieur commercial chez InterSystems et je fais beaucoup de démonstrations. Et un jour, que je faisais une démo de Embedded Python à un client, et ce client était un développeur Python, la conversation a tourné court lorsqu'il a vu mon code. Il m'a dit que mon code n'était pas du tout Python (il avait raison), je codais en Python comme je codais en ObjectScript. Et à cause de cela, il m'a dit qu'il n'était plus intéressé par Embedded Python. J'ai été choqué, et j'ai décidé d'apprendre Python de la bonne manière.

Donc, si vous voulez apprendre Python, apprenez d'abord Pep8.

Modules

Les modules sont quelque chose que nous n'avons pas en ObjectScript.

Habituellement, dans les langages orientés objet, vous avez des classes et des paquetages. En Python, vous avez des classes, des paquetages et des modules.

Qu'est-ce qu'un module ?

C'est un fichier avec une extension .py. Et c'est la façon d'organiser votre code.

Vous n'avez pas compris? Moi non plus au début. Prenons un exemple.

Habituellement, quand on veut créer une classe en ObjectScript, on crée un fichier .cls, et on y met sa classe. Et si vous voulez créer une autre classe, vous créez un autre fichier .cls. Et si vous voulez créer un paquetage, vous créez un dossier et vous y placez vos fichiers .cls.

En Python, c'est la même chose, mais Python apporte la possibilité d'avoir plusieurs classes dans un seul fichier. Ce fichier s'appelle un module. Pour information, c'est Pythonic quand il y a plusieurs classes dans un seul fichier.

Prévoyez donc comment vous allez organiser votre code, et comment vous allez nommer vos modules pour ne pas vous retrouver comme moi avec un tas de modules portant le même nom que vos classes.

Un mauvais exemple :

MyClass.py

class MyClass:
    def __init__(self):
        pass

    def my_method(self):
        pass

Pour instancier cette classe, vous allez faire :

import MyClass.MyClass # weird right ?

my_class = MyClass()

Bizarre, hein ?

Dunders

Les dunders sont des méthodes spéciales en Python. Elles sont appelées dunder parce qu'elles commencent et se terminent par un double soulignement.

Ce sont en quelque sorte nos méthodes % en ObjectScript.

Elles sont utilisées pour ce qui suit :

  • constructeur
  • surcharge d'opérateur
  • représentation d'objet
  • etc.

Exemple :

class MyClass:
    def __init__(self):
        pass

    def __repr__(self):
        return "MyClass"

    def __add__(self, other):
        return self + other

Ici, nous avons 3 méthodes de dunder :

  • __init__ : constructeur
  • __repr__ : représentation d'objet
  • __add__ : surcharge d'opérateur

Les méthodes Dunders sont partout en Python. C'est une partie importante de la langue, mais ne vous inquiétez pas, vous les apprendrez rapidement.

Conclusion

Python n'est pas ObjectScript, et vous devrez l'apprendre. Mais ce n'est pas si difficile, et vous l'apprendrez rapidement. Gardez simplement à l'esprit qu'il vous faudra apprendre Pep8, et comment organiser votre code avec des modules et des méthodes dunder.

De bons sites pour apprendre Python :


Embedded Python

Maintenant que vous en savez un peu plus sur Python, parlons de Embedded Python.

Qu'est-ce que Embedded Python ?

Embedded Python est un moyen d'exécuter du code Python dans IRIS. C'est une nouvelle fonctionnalité d' IRIS 2021.2+. Cela signifie que votre code Python sera exécuté dans le même processus qu'IRIS. Par ailleurs, chaque classe ObjectScript est une classe Python, de même pour les méthodes et les attributs et vice versa. 🥳 C'est génial !

Comment utiliser Embedded Python ?

Il y a 3 façons principales d'utiliser Embedded Python :

  • Utilisation la balise de langue dans ObjectScript
    • Méthode Foo() As %String [ Language = python ]
  • Utilisation de la fonction ##class(%SYS.Python).Import()
  • Utilisation de l'interpréteur python
    • python3 -c "import iris; print(iris.system.Version.GetVersion())"

Mais si vous voulez vous intéresser sérieusement à Embedded Python, vous aurez à éviter d'utiliser la balise de langue.

image

Pourquoi ?

  • Parce que ce n'est pas Pythonic
  • Parce que ce n'est pas ObjectScript non plus
  • Parce que vous n'avez pas de débogueur
  • Parce que vous n'avez pas de linter
  • Parce que vous n'avez pas de formateur
  • Parce que vous n'avez pas de cadre de test
  • Parce que vous n'avez pas de gestionnaire de paquets
  • Parce que vous mélangez 2 langues dans le même fichier
  • Parce que lorsque votre processus plante, vous n'avez pas de trace de pile
  • Parce que vous ne pouvez pas utiliser d'environnements virtuels ou d'environnements conda
  • ...

Ne vous méprenez pas, ça marche, ça peut être utile, si vous voulez tester quelque chose rapidement, mais à mon avis, ce n'est pas une bonne pratique.

Alors, qu'est-ce que j'ai appris de ces 2 années de Embedded Python, et comment l'utiliser de la bonne manière ?

Comment j'utilise Embedded Python

Je crois que vous avez deux options :

  • Utiliser les bibliothèques Python comme s'il s'agissait de classes ObjectScript
    • withqvec ##class(%SYS.Python).Import() function
  • Utiliser une première approche en python

Utilisation des bibliothèques et le code Python comme s'il s'agissait de classes ObjectScript

Vous voulez toujours utiliser Python dans votre code ObjectScript, mais vous ne voulez pas utiliser la balise de langue. Alors, que pouvez-vous faire ?

"Tout simplement" utilisez les bibliothèques et le code Python comme s'il s'agissait de classes ObjectScript. Prenons un exemplee :

Vous voulez utiliser la bibliothèque 'requests' ( c'est une bibliothèque pour faire des requêtes HTTP ) dans votre code ObjectScript.

Avec la balise de langue

ClassMethod Get() As %Status [ Language = python ]
{
	import requests

	url = "https://httpbin.org/get"
	# faire une requête d'obtention
	response = requests.get(url)
	# récupérer les données json de la réponse
	data = response.json()
	# itérer sur les données et imprimer les paires clé-valeur
	for key, value in data.items():
		print(key, ":", value)
}

Pourquoi je pense que ce n'est pas une bonne idée ?

Parce que vous mélangez 2 langues dans le même fichier, et que vous n'avez pas de débogueur, de linter, de formateur, etc. Si ce code plante, vous aurez du mal à le déboguer. Vous n'avez pas de trace de pile, et vous ne savez pas d'où vient l'erreur. Et vous n'avez pas d'auto-complétion.

Sans de balise de langue

ClassMethod Get() As %Status
{
	set status = $$$OK
    set url = "https://httpbin.org/get"
    // Importation du module Python "requests" en tant que classe ObjectScript
    set request = ##class(%SYS.Python).Import("requests")
    // Appel de la méthode get de la classe de requête
    set response = request.get(url)
    // Appel de la méthode json de la classe de réponse
	set data = response.json()
    // Ici, les données sont un dictionnaire Python
    // Pour parcourir un dictionnaire Python, vous devez utiliser la méthode dunder et items()
	// Importation du module Embedded Python
	set builtins = ##class(%SYS.Python).Import("builtins")
    // Ici, nous utilisons len du module intégré pour obtenir la longueur du dictionnaire
    For i = 0:1:builtins.len(data)-1 {
        // Maintenant, nous convertissons les éléments du dictionnaire en une liste, et nous obtenons la clé et la valeur en utilisant la méthode dunder __getitem__
		Write builtins.list(data.items())."__getitem__"(i)."__getitem__"(0),": ",builtins.list(data.items())."__getitem__"(i)."__getitem__"(1),!
	}
	quit status
}

Pourquoi je pense que c'est une bonne idée ?

Parce que vous utilisez Python comme s'il s'agissait d'ObjectScript. Vous importez la bibliothèque de requêtes comme une classe ObjectScript et vous l'utilisez comme une classe ObjectScript. Toute la logique est en ObjectScript, et vous utilisez Python comme une bibliothèque. Même pour la maintenance, c'est plus facile à lire et à comprendre, n'importe quel développeur ObjectScript peut comprendre ce code. L'inconvénient est que vous avez à savoir comment utiliser les méthodes de duners, et comment utiliser Python comme s'il s'agissait d'ObjectScript.

Conclusion

Croyez-moi, de cette manière vous obtiendrez un code plus robuste, et vous pourrez le déboguer facilement. Au début, cela semble difficile, mais vous découvrirez les avantages de l'apprentissage de Python plus rapidement que vous ne le pensez.

Utilisation de première approche en python

C'est la façon dont je préfère utiliser Embedded Python.

J'ai construit beaucoup d'outils en utilisant cette approche, et j'en suis très satisfait.

Quelques exemples :

Qu'est-ce qu'une première approche python ?

TIl n'y a qu'une seule règle : Le code Python doit être dans des fichiers .py, le code ObjectScript doit être dans des fichiers .cls

Comment y parvenir ?

L'idée est de créer des classes de wrappers ObjectScript pour appeler le code Python.


Prenons l'exemple de iris-fhir-python-strategy :

Exemple : iris-fhir-python-strategy

Tout d'abord, nous avons à comprendre comment fonctionne le serveur IRIS FHIR.

Chaque serveur IRIS FHIR met en œuvre une Stratégie.

Une Stratégie est un ensemble de deux classes :

SuperclassParamètres de sous-classe
HS.FHIRServer.API.InteractionsStrategyStrategyKey — Spécifie un identifiant unique pour la stratégie InteractionsStrategy.
InteractionsClass — Spécifie le nom de votre sous-classe Interactions.
HS.FHIRServer.API.RepoManagerStrategyClass — Spécifie le nom de votre sous-classe InteractionsStrategy.
StrategyKey — Spécifie un identifiant unique pour la stratégie InteractionsStrategy. Ceci doit correspondre au paramètre StrategyKey dans la sous-classe InteractionsStrategy.

Ces deux classes sont des classes abstraites Abtract Abstract.

  • HS.FHIRServer.API.InteractionsStrategy est une classe Abstract qui doit être mise en œuvre pour personnaliser le comportement du serveur FHIR.
  • HS.FHIRServer.API.RepoManager est une classe Abstract qui doit être mise en œuvre pour personnaliser le stockage du serveur FHIR.

Remarques

Pour notre exemple, nous nous concentrerons uniquement sur la classe HS.FHIRServer.API.InteractionsStrategy même si la classe HS.FHIRServer.API.RepoManager est également implémentée et obligatoire pour personnaliser le serveur FHIR. La classe HS.FHIRServer.API.RepoManager est mise en œuvre par HS.FHIRServer.Storage.Json.RepoManager qui est la mise en œuvre par défaut du serveur FHIR.

Où trouver le code

Tout le code source peut être trouvé dans le référentiel : iris-fhir-python-strategy Le dossier src contient les dossiers suivants :

  • python : contient le code python
  • cls : contient le code ObjectScript utilisé pour appeler le code python

Comment mettre en œuvre une Stratégie

Dans cette démonstration de faisabilité, nous nous intéresserons uniquement à la manière d'implémenter une Strategie en Python, et non à la manière de mettre en œuvre un RepoManager.

Pour mettre en œuvre une Strategie vous devez créer au moins deux classes :

  • Une classe qui hérite de la classe HS.FHIRServer.API.InteractionsStrategy
  • Une classe qui hérite de la classe HS.FHIRServer.API.Interactions

Mise en œuvre d'InteractionsStrategy

La classe HS.FHIRServer.API.InteractionsStrategy vise à personnaliser le comportement du serveur FHIR en remplaçant les méthodes suivantes :

  • GetMetadataResource : appelé pour récupérer les métadonnées du serveur FHIR
    • C'est la seule méthode que nous remplacerons dans cette preuve de concept

HS.FHIRServer.API.InteractionsStrategy a également deux paramètres :

  • StrategyKey : un identifiant unique pour la stratégie InteractionsStrategy
  • InteractionsClass : le nom de votre sous-classe Interactions

Mise en œuvre des Interactions

La classe HS.FHIRServer.API.Interactions vise à personnaliser le comportement du serveur FHIR en remplaçant les méthodes suivantes :

  • OnBeforeRequest : appelée avant l'envoi de la requête au serveur
  • OnAfterRequest : appelée après l'envoi de la requête au serveur
  • PostProcessRead : appelée une fois l'opération de lecture terminée
  • PostProcessSearch : appelée une fois l'opération de recherche terminée
  • Read : appelée pour lire une ressource
  • Add : appelée pour ajouter une ressource
  • Update : appelée pour mettre à jour une ressource
  • Delete : appelée pour supprimer une ressource
  • et bien d'autres...

Nous mettons en œuvre la classe HS.FHIRServer.API.Interactions dans la classe src/cls/FHIR/Python/Interactions.cls.

 
Spoiler
Class FHIR.Python.Interactions Extends (HS.FHIRServer.Storage.Json.Interactions, FHIR.Python.Helper)
{

Parameter OAuth2TokenHandlerClass As%String = "FHIR.Python.OAuth2Token";

Method %OnNew(pStrategy As HS.FHIRServer.Storage.Json.InteractionsStrategy) As%Status { // %OnNew est appelé lors de la création de l'objet.// Le paramètre pStrategy est l'objet de stratégie qui a créé cet objet.// La mise en œuvre par défaut ne fait rien// D'abord, le chemin d'accès à python est défini à partir d'une variable d'environnementset..PythonPath = $system.Util.GetEnviron("INTERACTION_PATH") // Définissez ensuite le nom de la classe python à partir de la variable d'environnementset..PythonClassname = $system.Util.GetEnviron("INTERACTION_CLASS") // Puis définissez le nom du module python à partir de la variable d'environnementset..PythonModule = $system.Util.GetEnviron("INTERACTION_MODULE")

<span class="hljs-keyword">if</span> (<span class="hljs-built_in">..PythonPath</span> = <span class="hljs-string">""</span>) || (<span class="hljs-built_in">..PythonClassname</span> = <span class="hljs-string">""</span>) || (<span class="hljs-built_in">..PythonModule</span> = <span class="hljs-string">""</span>) {
	<span class="hljs-comment">//quit ##super(pStrategy)</span>
	<span class="hljs-keyword">set</span> <span class="hljs-built_in">..PythonPath</span> = <span class="hljs-string">"/irisdev/app/src/python/"</span>
	<span class="hljs-keyword">set</span> <span class="hljs-built_in">..PythonClassname</span> = <span class="hljs-string">"CustomInteraction"</span>
	<span class="hljs-keyword">set</span> <span class="hljs-built_in">..PythonModule</span> = <span class="hljs-string">"custom"</span>
}


<span class="hljs-comment">// Définissez ensuite la classe python</span>
<span class="hljs-keyword">do</span> <span class="hljs-built_in">..SetPythonPath</span>(<span class="hljs-built_in">..PythonPath</span>)
<span class="hljs-keyword">set</span> <span class="hljs-built_in">..PythonClass</span> = <span class="hljs-keyword">##class</span>(FHIR.Python.Interactions).GetPythonInstance(<span class="hljs-built_in">..PythonModule</span>, <span class="hljs-built_in">..PythonClassname</span>)

<span class="hljs-keyword">quit</span> <span class="hljs-keyword">##super</span>(pStrategy)

}

Method OnBeforeRequest( pFHIRService As HS.FHIRServer.API.Service, pFHIRRequest As HS.FHIRServer.API.Data.Request, pTimeout As%Integer) { // OnBeforeRequest est appelée avant le traitement de chaque demande.if$ISOBJECT(..PythonClass) { set body = ##class(%SYS.Python).None() if pFHIRRequest.Json '= "" { set jsonLib = ##class(%SYS.Python).Import("json") set body = jsonLib.loads(pFHIRRequest.Json.%ToJSON()) } do..PythonClass."on_before_request"(pFHIRService, pFHIRRequest, body, pTimeout) } }

Method OnAfterRequest( pFHIRService As HS.FHIRServer.API.Service, pFHIRRequest As HS.FHIRServer.API.Data.Request, pFHIRResponse As HS.FHIRServer.API.Data.Response) { // OnAfterRequest est appelée après le traitement de chaque demande.if$ISOBJECT(..PythonClass) { set body = ##class(%SYS.Python).None() if pFHIRResponse.Json '= "" { set jsonLib = ##class(%SYS.Python).Import("json") set body = jsonLib.loads(pFHIRResponse.Json.%ToJSON()) } do..PythonClass."on_after_request"(pFHIRService, pFHIRRequest, pFHIRResponse, body) } }

Method PostProcessRead(pResourceObject As%DynamicObject) As%Boolean { // PostProcessRead est appelée après la lecture d'une ressource dans la base de données.// Renvoyez 1 pour indiquer que la ressource doit être incluse dans la réponse.// Renvoyez 0 pour indiquer que la ressource doit être exclue de la réponse.if$ISOBJECT(..PythonClass) { if pResourceObject '= "" { set jsonLib = ##class(%SYS.Python).Import("json") set body = jsonLib.loads(pResourceObject.%ToJSON()) } return..PythonClass."post_process_read"(body) } quit1 }

Method PostProcessSearch( pRS As HS.FHIRServer.Util.SearchResult, pResourceType As%String) As%Status { // PostProcessSearch est appelée après l'exécution d'une recherche.// Renvoyez $$$OK pour indiquer que la recherche a abouti.// Renvoyez un code d'erreur pour indiquer que la recherche a échoué.if$ISOBJECT(..PythonClass) { return..PythonClass."post_process_search"(pRS, pResourceType) } quit$$$OK }

Method Read( pResourceType As%String, pResourceId As%String, pVersionId As%String = "") As%DynamicObject { return##super(pResourceType, pResourceId, pVersionId) }

Method Add( pResourceObj As%DynamicObject, pResourceIdToAssign As%String = "", pHttpMethod = "POST") As%String { return##super(pResourceObj, pResourceIdToAssign, pHttpMethod) }

/// Renvoie de VersionId pour la version "supprimée" Method Delete( pResourceType As%String, pResourceId As%String) As%String { return##super(pResourceType, pResourceId) }

Method Update(pResourceObj As%DynamicObject) As%String { return##super(pResourceObj) }

}

La classe FHIR.Python.Interactions hérite de la classe HS.FHIRServer.Storage.Json.Interactions et de la classe FHIR.Python.Helper

La classe HS.FHIRServer.Storage.Json.Interactions est la mise en œuvre par défaut du serveur FHIR.

La classe FHIR.Python.Helper vise à aider à appeler du code Python à partir d'ObjectScript.

La classe FHIR.Python.Interactions remplacent les méthodes suivantes :

  • %OnNew : appelée lors de la création de l'objet
    • nous utilisons cette méthode pour définir le chemin python, le nom de la classe python et le nom du module python à partir des variables d'environnement
    • si les variables d'environnement ne sont pas définies, nous utilisons les valeurs par défaut
    • nous définissons également la classe python
    • nous appelons la méthode %OnNew de la classe parente
Method %OnNew(pStrategy As HS.FHIRServer.Storage.Json.InteractionsStrategy) As %Status
{
	// Définissez d'abord le chemin python à partir d'une variable d'environnement
	set ..PythonPath = $system.Util.GetEnviron("INTERACTION_PATH")
	// Puis définissez le nom de la classe python à partir de la variable d'environnement
	set ..PythonClassname = $system.Util.GetEnviron("INTERACTION_CLASS")
	// Puis définissez le nom du module python à partir de la variable d'environnement
	set ..PythonModule = $system.Util.GetEnviron("INTERACTION_MODULE")

	if (..PythonPath = "") || (..PythonClassname = "") || (..PythonModule = "") {
		// utilisez les valeurs par défaut
		set ..PythonPath = "/irisdev/app/src/python/"
		set ..PythonClassname = "CustomInteraction"
		set ..PythonModule = "custom"
	}

	// Ensuite, définissez la classe python
	do ..SetPythonPath(..PythonPath)
	set ..PythonClass = ..GetPythonInstance(..PythonModule, ..PythonClassname)

	quit ##super(pStrategy)
}
  • OnBeforeRequest : appelée avant l'envoi de la requête au serveur
    • nous appelons la méthode on_before_request de la classe python
    • nous passons l'objet HS.FHIRServer.API.Service, l'objet HS.FHIRServer.API.Data.Request, le corps de la requête et le timeout
Method OnBeforeRequest(
	pFHIRService As HS.FHIRServer.API.Service,
	pFHIRRequest As HS.FHIRServer.API.Data.Request,
	pTimeout As %Integer)
{
	// OnBeforeRequest est appelée avant le traitement de chaque requête.
	if $ISOBJECT(..PythonClass) {
		set body = ##class(%SYS.Python).None()
		if pFHIRRequest.Json '= "" {
			set jsonLib = ##class(%SYS.Python).Import("json")
			set body = jsonLib.loads(pFHIRRequest.Json.%ToJSON())
		}
		do ..PythonClass."on_before_request"(pFHIRService, pFHIRRequest, body, pTimeout)
	}
}
  • OnAfterRequest : appelée après l'envoi de la requête au serveur
    • nous appelons la méthode on_after_request de la classe python
    • nous passons l'objet HS.FHIRServer.API.Service, l'objet HS.FHIRServer.API.Data.Request, l'objet HS.FHIRServer.API.Data.Response et le corps de la réponse
Method OnAfterRequest(
	pFHIRService As HS.FHIRServer.API.Service,
	pFHIRRequest As HS.FHIRServer.API.Data.Request,
	pFHIRResponse As HS.FHIRServer.API.Data.Response)
{
	// OnAfterRequest est appelée après le traitement de chaque requête.
	if $ISOBJECT(..PythonClass) {
		set body = ##class(%SYS.Python).None()
		if pFHIRResponse.Json '= "" {
			set jsonLib = ##class(%SYS.Python).Import("json")
			set body = jsonLib.loads(pFHIRResponse.Json.%ToJSON())
		}
		do ..PythonClass."on_after_request"(pFHIRService, pFHIRRequest, pFHIRResponse, body)
	}
}
  • Et ainsi de suite...

Interactions en Python

La classeFHIR.Python.Interactions appelle les méthodes on_before_request, on_after_request, ... de la classe python.

Voici la classe python abstraite :

import abc
import iris

class Interaction(object):
    __metaclass__ = abc.ABCMeta

    @abc.abstractmethod
    def on_before_request(self, 
                          fhir_service:'iris.HS.FHIRServer.API.Service',
                          fhir_request:'iris.HS.FHIRServer.API.Data.Request',
                          body:dict,
                          timeout:int):
        """
        on_before_request is called before the request is sent to the server.
        param fhir_service: the fhir service object iris.HS.FHIRServer.API.Service
        param fhir_request: the fhir request object iris.FHIRServer.API.Data.Request
        param timeout: the timeout in seconds
        return: None
        """
        

    @abc.abstractmethod
    def on_after_request(self,
                         fhir_service:'iris.HS.FHIRServer.API.Service',
                         fhir_request:'iris.HS.FHIRServer.API.Data.Request',
                         fhir_response:'iris.HS.FHIRServer.API.Data.Response',
                         body:dict):
        """
        on_after_request is called after the request is sent to the server.
        param fhir_service: the fhir service object iris.HS.FHIRServer.API.Service
        param fhir_request: the fhir request object iris.FHIRServer.API.Data.Request
        param fhir_response: the fhir response object iris.FHIRServer.API.Data.Response
        return: None
        """
        

    @abc.abstractmethod
    def post_process_read(self,
                          fhir_object:dict) -> bool:
        """
        post_process_read is called after the read operation is done.
        param fhir_object: the fhir object
        return: True the resource should be returned to the client, False otherwise
        """
        

    @abc.abstractmethod
    def post_process_search(self,
                            rs:'iris.HS.FHIRServer.Util.SearchResult',
                            resource_type:str):
        """
        post_process_search is called after the search operation is done.
        param rs: the search result iris.HS.FHIRServer.Util.SearchResult
        param resource_type: the resource type
        return: None
        """

Mise en œuvre de la classe abstraite python

from FhirInteraction import Interaction

class CustomInteraction(Interaction):

    def on_before_request(self, fhir_service, fhir_request, body, timeout):
        #Extract the user and roles for this request
        #so consent can be evaluated.
        self.requesting_user = fhir_request.Username
        self.requesting_roles = fhir_request.Roles

    def on_after_request(self, fhir_service, fhir_request, fhir_response, body):
        #Clear the user and roles between requests.
        self.requesting_user = ""
        self.requesting_roles = ""

    def post_process_read(self, fhir_object):
        #Evaluate consent based on the resource and user/roles.
        #Returning 0 indicates this resource shouldn't be displayed - a 404 Not Found
        #will be returned to the user.
        return self.consent(fhir_object['resourceType'],
                        self.requesting_user,
                        self.requesting_roles)

    def post_process_search(self, rs, resource_type):
        #Iterate through each resource in the search set and evaluate
        #consent based on the resource and user/roles.
        #Each row marked as deleted and saved will be excluded from the Bundle.
        rs._SetIterator(0)
        while rs._Next():
            if not self.consent(rs.ResourceType,
                            self.requesting_user,
                            self.requesting_roles):
                #Mark the row as deleted and save it.
                rs.MarkAsDeleted()
                rs._SaveRow()

    def consent(self, resource_type, user, roles):
        #Example consent logic - only allow users with the role '%All' to see
        #Observation resources.
        if resource_type == 'Observation':
            if '%All' in roles:
                return True
            else:
                return False
        else:
            return True

Trop long, faisons un résumé

La classeFHIR.Python.Interactions est un wrapper pour appeler la classe python.

Les classes abstraites IRIS sont implémentées pour envelopper les classes abstraites python 🥳.

Cela nous aide à séparer le code python et le code ObjectScript et à bénéficier ainsi du meilleur des deux mondes.

0
0 69
Article Sylvain Guilbaud · Sept 18, 2024 4m read

Pour les développeurs axés sur le backend, le développement du frontend peut être une tâche intimidante, voire cauchemardesque. Au début de ma carrière, les frontières entre frontend et backend étaient brouillées et tout le monde était censé s'occuper des deux. Le CSS, en particulier, a été une lutte constante ; il a été ressenti comme une mission impossible.

Bien que j'apprécie le travail sur le front-end, CSS reste un défi complexe pour moi, d'autant plus que je l'ai appris par essais et erreurs. Le mème de Peter Griffin s'efforçant d'ouvrir des stores illustre parfaitement mon expérience de l'apprentissage du CSS. Peter Griffin CSS

Mais aujourd'hui, tout est changé. Des outils comme Streamlit ont révolutionné le jeu pour les développeurs qui, comme moi, préfèrent le confort de l'écran noir d'un terminal. Fini le temps où l'on se débattait avec des lignes de code qui ressemblaient à des messages cryptiques d'extraterrestres (je te regarde, CSS!). Comme le dit toujours le docteur Károly Zsolnai-Fehér de Two Minute Papers, "Quelle époque pour être en vie !". Avec Streamlit, vous pouvez créer une application web complète en utilisant uniquement du code Python. Voulez-vous le voir à l'œuvre? Attachez vos ceintures, car je suis sur le point de partager ma tentative de création d'un frontend pour SQLZilla en utilisant cet outil génial.

Pour l'installer, il suffit d'ouvrir votre terminal et de lancer le sort suivant:

pip install streamlit

(Ou vous pouvez l'ajouter à votre fichier requirements.txt.)

Créez un fichier, app.py et ajoutez cet extrait de code pour afficher un titre "SQLZilla":

import streamlit as st

st.title("SQLZilla")

L'événement est à vous!

Ouvrez à nouveau votre terminal et tapez la commande suivante pour activer votre création:

streamlit run app.py

Voila! Votre application Streamlit devrait apparaître dans votre navigateur web, affichant fièrement le titre "SQLZilla".

Ajoutez une image en utilisant la méthode d'image, pour la centraliser j'ai juste créé 3 colonnes et je les ajouté au centre (honte à moi)

   st.title("SQLZilla")

   left_co, cent_co, last_co = st.columns(3)
   with cent_co:
       st.image("small_logo.png", use_column_width=True)

Pour gérer les configurations et les résultats des requêtes, vous pouvez utiliser l'état de la session. Vous trouverez ci-dessous la manière d'enregistrer les valeurs de configuration et de stocker les résultats des requêtes:

if 'hostname' not in st.session_state:
    st.session_state.hostname = 'sqlzilla-iris-1'
if 'user' not in st.session_state:
    st.session_state.user = '_system'
if 'pwd' not in st.session_state:
    st.session_state.pwd = 'SYS'
# Add other session states as needed

Pour connecter SQLZilla à une base de données IRIS InterSystems, vous pouvez utiliser SQLAlchemy. Tout d'abord, installez SQLAlchemy avec:

pip install sqlalchemy

Ensuite, configurez la connexion dans votre fichier app.py:

from sqlalchemy import create_engine
import pandas as pd

# Remplacez par vos propres données de connexion
engine = create_engine(f"iris://{user}:{password}@{host}:{port}/{namespace}")

def run_query(query):
    with engine.connect() as connection:
        result = pd.read_sql(query, connection)
        return result

Une fois connecté à la base de données, vous pouvez utiliser Pandas et Streamlit pour afficher les résultats de vos requêtes. Voici un exemple d'affichage d'un DataFrame dans votre application Streamlit:

if 'query' in st.session_state:
    query = st.session_state.query
    df = run_query(query)
    st.dataframe(df)

Pour rendre votre application plus interactive, vous pouvez utiliser st.rerun() pour rafraîchir l'application à chaque changement de la requête:

if 'query' in st.session_state and st.button('Run Query'):
    df = run_query(st.session_state.query)
    st.dataframe(df)
    st.rerun()

Vous pouvez trouver des composants Streamlit variés à utiliser. Dans SQLZilla, j'ai ajouté une version de l'éditeur de code ACE appelée streamlit-code-editor:

from code_editor import code_editor

editor_dict = code_editor(st.session_state.code_text, lang="sql", height=[10, 100], shortcuts="vscode")

if len(editor_dict['text']) != 0:
    st.session_state.code_text = editor_dict['text']

Comme l'assistant SQLZilla est écrit en Python, j'ai simplement appelé la classe:

from sqlzilla import SQLZilla

def assistant_interaction(sqlzilla, prompt):
    response = sqlzilla.prompt(prompt)
    st.session_state.chat_history.append({"role": "user", "content": prompt})
    st.session_state.chat_history.append({"role": "assistant", "content": response})

    if "SELECT" in response.upper():
        st.session_state.query = response

    return response

Bravo! Vous avez créé votre propre SQLZilla. Continuez à explorer Streamlit et améliorez votre application avec d'autres fonctionnalités. Et si vous aimez SQLZilla, votez pour cet incroyable assistant qui convertit le texte en requêtes!

0
0 174
Article Iryna Mykhailova · Sept 16, 2024 5m read

Dans la section précédente, nous avons exploré le processus d'installation et initié l'écriture de l'IRIS en Python natif. Nous allons maintenant examiner la traversée globale et nous intéresser aux objets de classe IRIS. 

get : cette fonction est utilisée pour obtenir des valeurs à partir du nœud de traversée.

deftraversal_firstlevel_subscript():"""
    ^mygbl(235)="test66,62" and ^mygbl(912)="test118,78"
    """for  i in irispy.node('^mygbl'):
        print(i, gbl_node.get(i,''))
0
0 41
Article Iryna Mykhailova · Sept 9, 2024 4m read

Bonjour la communauté

J'ai déjà expérimenté Embedded Python dans IRIS ; cependant, je n'ai pas encore eu l'occasion d'implémenter IRIS en utilisant Native Python. Dans cet article, je souhaite décrire les étapes que j'ai suivies pour commencer à apprendre et à implémenter IRIS dans la source Python. Je tiens également à remercier @Guillaume Rongier et @Luis Angel Pérez Ramos pour leur aide dans la résolution des problèmes que j'ai rencontrés lors de ma récente installation PIP d'IRIS en Python, ce qui lui a finalement permis de fonctionner correctement.

Commençons par écrire IRIS en Python.

0
0 50
Article Lorenzo Scalese · Août 27, 2024 5m read

 

Démarrage rapide des données SQL d'InterSystems Cloud dans Databricks

La mise en œuvre de Databricks en SQL d'InterSystems Cloud se compose de quatre parties.

  • Obtention du certificat et du pilote JDBC Driver pour InterSystems IRIS
  • Ajout d'un script d'initialisation et d'une bibliothèque externe à votre Cluster de calcul Databricks
  • Obtention de données
  • Placement des données

Téléchargement du certificat X.509/du pilote JDBC de Cloud SQL

Naviguez vers la page d'aperçu de votre déploiement, si vous n'avez pas activé de connexions externes, faites-le et téléchargez votre certificat et le pilote jdbc depuis la page d'aperçu.

 

J'ai utilisé intersystems-jdbc-3.8.4.jar et intersystems-jdbc-3.7.1.jar avec succès dans Databricks à partir de la distribution de pilotes Driver Distribution.

Script d'initialisation pour votre cluster Databricks

La façon la plus simple d'importer un ou plusieurs certificats CA personnalisés dans votre cluster Databricks est de créer un script d'initialisation qui ajoute la chaîne complète de certificats CA aux magasins de certificats par défaut SSL et Java de Linux, et définit la propriété REQUESTS_CA_BUNDLE. Collez le contenu du certificat X.509 que vous avez téléchargé dans le bloc supérieur du script suivant:

import_cloudsql_certficiate.sh
#!/bin/bash

cat << 'EOF' > /usr/local/share/ca-certificates/cloudsql.crt
-----BEGIN CERTIFICATE-----
<PASTE>
-----END CERTIFICATE-----
EOF

update-ca-certificates

PEM_FILE="/etc/ssl/certs/cloudsql.pem" PASSWORD="changeit" JAVA_HOME=$(readlink -f /usr/bin/java | sed "s:bin/java::") KEYSTORE="$JAVA_HOME/lib/security/cacerts" CERTS=$(grep 'END CERTIFICATE'$PEM_FILE| wc -l)

# Pour traiter plusieurs certificats avec keytool, vous devez extraire# chacun d'eux du fichier PEM et l'importer dans le KeyStore Java.for N in $(seq 0 $(($CERTS - 1))); do ALIAS="$(basename $PEM_FILE)-$N"echo"Adding to keystore with alias:$ALIAS" cat $PEM_FILE | awk "n==$N { print }; /END CERTIFICATE/ { n++ }" | keytool -noprompt -import -trustcacerts
-alias$ALIAS -keystore $KEYSTORE -storepass $PASSWORDdoneecho"export REQUESTS_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt" >> /databricks/spark/conf/spark-env.sh echo"export SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt" >> /databricks/spark/conf/spark-env.sh

Maintenant vous avez le script initial, téléchargez-le dans le catalogue Unity sur un Volume.

Une fois que le script est sur un volume, vous pouvez ajouter le script initial au cluster à partir du volume dans les propriétés avancées de votre cluster.


Ensuite, ajoutez le pilote/la bibliothèque intersystems jdbc au cluster...

...et démarrez ou redémarrez votre calcul.

Station Databricks - Entrée vers le Cloud SQL d'InterSystems IRIS

 

Créez un Notebook Python dans votre espace de travail, attachez-le à votre cluster et testez le glissement de données vers Databricks.  Sous le capot, Databricks va utiliser pySpark, si cela n'est pas immédiatement évident.

La construction du Dataframe Spark suivant est tout ce qu'il vous faut, vous pouvez récupérer vos informations de connexion à partir de la page d'aperçu comme auparavant.

df = (spark.read
  .format("jdbc")
  .option("url", "jdbc:IRIS://k8s-05868f04-a4909631-ac5e3e28ef-6d9f5cd5b3f7f100.elb.us-east-1.amazonaws.com:443/USER")
  .option("driver", "com.intersystems.jdbc.IRISDriver")
  .option("dbtable", "(SELECT name,category,review_point FROM SQLUser.scotch_reviews) AS temp_table;") 
  .option("user", "SQLAdmin")
  .option("password", "REDACTED")
  .option("driver", "com.intersystems.jdbc.IRISDriver")\
  .option("connection security level","10")\
  .option("sslConnection","true")\
  .load())

df.show()

Illustration de la production d'un dataframe à partir de données dans Cloud SQL... boom!

Station Databricks - Sortie du Cloud SQL d'InterSystems IRIS

 

Prenons maintenant ce que nous avons lu dans IRIS et écrivons-le avec Databricks. Si vous vous souvenez bien, nous n'avons lu que 3 champs dans notre cadre de données, nous allons donc les réécrire immédiatement et spécifier un mode "écraser".

df = (spark.read
  .format("jdbc")
  .option("url", "jdbc:IRIS://k8s-05868f04-a4909631-ac5e3e28ef-6d9f5cd5b3f7f100.elb.us-east-1.amazonaws.com:443/USER")
  .option("driver", "com.intersystems.jdbc.IRISDriver")
  .option("dbtable", "(SELECT TOP 3 name,category,review_point FROM SQLUser.scotch_reviews) AS temp_table;") 
  .option("user", "SQLAdmin")
  .option("password", "REDACTED")
  .option("driver", "com.intersystems.jdbc.IRISDriver")\
  .option("connection security level","10")\
  .option("sslConnection","true")\
  .load())

df.show()

mode = "overwrite" properties = { "user": "SQLAdmin", "password": "REDACTED", "driver": "com.intersystems.jdbc.IRISDriver", "sslConnection": "true", "connection security level": "10", }

df.write.jdbc(url="jdbc:IRIS://k8s-05868f04-a4909631-ac5e3e28ef-6d9f5cd5b3f7f100.elb.us-east-1.amazonaws.com:443/USER", table="databricks_scotch_reviews", mode=mode, properties=properties)

Exécution du Notebook

 
Illustration des données dans le Cloud SQL d'InterSystems!

Les points à prendre en considération

  • Par défaut, PySpark écrit les données en utilisant plusieurs tâches concurrentes, ce qui peut entraîner des écritures partielles si l'une des tâches échoue.
  • Pour garantir que l'opération d'écriture est atomique et cohérente, vous pouvez configurer PySpark de manière à ce que les données soient écrites à l'aide d'une seule tâche (c'est-à-dire en définissant le nombre de partitions à 1) ou utiliser une fonctionnalité spécifique à IRIS, telle que les transactions.
  • En outre, vous pouvez utiliser l'API DataFrame de PySpark pour effectuer des opérations de filtrage et d'agrégation avant de lire les données de la base de données, ce qui peut réduire la quantité de données à transférer sur le réseau.
0
0 44
Article Guillaume Rongier · Juil 25, 2024 28m read

Cet article a pour objectif de fournir au lecteur les informations suivantes:

  • Configuration et utilisation du serveur FHIR
  • Création d'un serveur d'autorisation OAuth2
  • Liaison entre le serveur FHIR et le serveur d'autorisation OAuth2 pour la prise en charge de SMART sur FHIR
  • Utilisation des capacités d'interopérabilité dans "IRIS for Health" pour filtrer les ressources FHIR
  • Création d'une opération personnalisée sur le serveur FHIR

Schéma de l'article:

Schema

Flux de travail de l'article:

Workflow

1. Table des matières

2. Objectifs

Cette session de formation vise à fournir aux participants les compétences suivantes:

  • Configuration et utilisation du serveur FHIR
  • Création d'un serveur d'autorisation OAuth2
  • Liaison entre le serveur FHIR et le serveur d'autorisation OAuth2 pour la prise en charge de SMART sur FHIR
  • Utilisation des capacités d'interopérabilité dans "IRIS for Health" pour filtrer les ressources FHIR
  • Création d'une opération personnalisée sur le serveur FHIR

3. Installation

Pour installer l'environnement de formation, vous devez avoir installé Docker et Docker Compose sur votre machine.

Vous pouvez installer Docker et Docker Compose en suivant les instructions sur le site Docker website.

Une fois Docker et Docker Compose installés, vous pouvez cloner ce dépôt et exécuter la commande suivante:

docker-compose up -d

Cette commande démarre le conteneur "IRIS for Health" et le conteneur "Web Gateway" pour exposer le serveur FHIR via HTTPS.

3.1. Accès au serveur FHIR

Une fois les conteneurs démarrés, vous pouvez accéder au serveur FHIR à l'URL suivante:

https://localhost:4443/fhir/r5/

3.2. Accés au Portail de Gestion d'InterSystems IRIS

Vous pouvez accéder au portail de gestion InterSystems IRIS à l'adresse suivante:

http://localhost:8089/csp/sys/UtilHome.csp

Le nom d'utilisateur et le mot de passe par défaut sont "SuperUser" et "SYS" respectivement.

4. Configuration du serveur d'autorisation OAuth2

Pour configurer le serveur d'autorisation OAuth2, il faut se connecter au portail de gestion InterSystems IRIS et naviguer jusqu'à l'administration du système: System Administration > Security > OAuth 2.0 > Servers.

OAuth2 Servers

Ensuite, nous remplirons le formulaire pour créer un nouveau serveur d'autorisation OAuth2.

4.1. Onglet Généralités

Nous commençons d'abord par l'onglet Généralités.

General Tab

Les paramètres sont les suivants:

  • Description: La description du serveur d'autorisation OAuth2
    • Serveur d'autorisation Oauth2 Auth
  • Émetteur: L'URL du serveur d'autorisation OAuth2
    • https://webgateway/oauth2
    • REMARQUE : Nous utilisons ici l'URL de la passerelle Web Gateway pour exposer le serveur FHIR via HTTPS. Il s'agit du nom DNS interne du conteneur de la passerelle Web Gateway.
  • Types de subventions pris en charge: Les types de subventions pris en charge par le serveur d'autorisation OAuth2
    • Code d'autorisation
    • Informations d'identification du client
    • Autorisation JWT
    • REMARQUE : Nous utiliserons le type de subvention de Client Credentials (informations d'identification du client) pour authentifier le serveur FHIR auprès du serveur d'autorisation OAuth2.
  • Configuration SSL/TLS: La configuration SSL/TLS à utiliser pour le serveur d'autorisation OAuth2.
    • Par défaut: BFC_SSL

4.2. Onglet Périmètre

Ensuite, nous passons à l'onglet Périmètre.

Scope Tab

Nous allons créer 3 périmètres:

  • user/Patient.read: Le périmètre de lecture des ressources disponibles pour les patients
  • VIP: Le périmètre de lecture des ressources disponibles pour les patients VIP
  • user/.: Le périmètre de lecture toutes les ressources, à des fins administratives

4.3. Onglet JWT

Ensuite, nous passons à l'onglet JWT.

JWT Tab

Ici, nous sélectionnons simplement l'algorithme à utiliser pour le JWT..

Nous utiliserons l'algorithme RS256.

Si nécessaire, nous pouvons sélectionner le cryptage pour le JWT. Nous n'utiliserons pas de cryptage pour cette session de formation.

4.4. Onglet Personnalisation

Ensuite, nous passons à l'onglet Personnalisation.

Customization Tab

Voici toutes les classes de personnalisation pour le serveur d'autorisation OAuth2.

Nous changeons les classes suivantes:

  • Classe de génération du jeton: La classe à utiliser pour générer le jeton
    • FROM : %OAuth2.Server.Generate
    • TO : %OAuth2.Server.JWT

Nous pouvons maintenant enregistrer le serveur d'autorisation OAuth2.

Félicitations, nous avons maintenant configuré le serveur d'autorisation OAuth2. 🥳

5. Configuration du Client

Pour configurer le Client, il faut se connecter au portail de gestion InterSystems IRIS et naviguer jusqu'à l'administration du système: System Administration > Security > OAuth 2.0 > Client.

OAuth2 Clients

Pour créer un nouveau client, il faut d'abord enregistrer le serveur d'autorisation OAuth2.

5.1. Enregistrement du serveur d'autorisation OAuth2

Sur la page client, cliquez sur le bouton de création d'une description de serveur Create Server Description.

Create Server Description

5.2. Description du serveur

Dans le formulaire Description du serveur, nous devons remplir les paramètres suivants:

Server Description

  • URL du serveur: L'URL du serveur d'autorisation OAuth2
  • Configuration SSL/TLS: La configuration SSL/TLS à utiliser pour le serveur d'autorisation OAuth2
    • Par défaut: BFC_SSL

Cliquez sur le bouton Discover and Save (Découvrir et enregistrer). Félicitations, nous avons maintenant enregistré le serveur d'autorisation OAuth2.

Server Description

5.3. Création d'un nouveau client

Ensuite, nous pouvons créer un nouveau client.

Sur la page client, nous avons un nouveau bouton Client Configuration (Configuration Client).

Client Configuration

Cliquez sur le bouton de lien Client Configuration vers la description du serveur.

Nous pouvons maintenant créer un nouveau client..

Create Client

5.3.1. Onglet Généralités

Nous commençons d'abord par l'onglet Généralités.

General Tab

Les paramètres sont les suivants:

  • Nom de l'application: Le nom du client
    • App
    • REMARQUE : C'est le nom du client.
  • Nom du client: Le nom du client
    • App
  • *Type de client: Le type du client
    • Confidentiel
    • REMARQUE : Nous utiliserons le type de client "confidentiel" pour authentifier le serveur FHIR auprès du serveur d'autorisation OAuth2.
  • URI de redirection: L'URI de redirection du client
    • https://webgateway/oauth2
    • REMARQUE : Nous utilisons ici l'URL de la passerelle Web Gateway pour exposer le serveur FHIR via HTTPS. Il s'agit du nom DNS interne du conteneur de la passerelle Web Gateway.
    • REMARQUE : Ceci ne sera pas utilisé dans cette session de formation.
  • Types de subventions: Les types de subventions pris en charge par le client
    • Informations d'identification du client
    • REMARQUE : Nous utiliserons le type de subvention de Client Credentials (informations d'identification du client) pour authentifier l'application client (Client Application) auprès du serveur d'autorisation OAuth2.
  • Type d'authentification: Le type d'authentification du client
    • Basique
    • REMARQUE : Nous utiliserons le type d'authentification Basique pour authentifier l'application client (Client Application) auprès du serveur d'autorisation OAuth2.

Maintenant, nous pouvons cliquer sur le bouton Dynamic Registration (Enregistrement dynamique)..

Félicitations, nous avons maintenant créé le client. 🥳

Si nous allons dans l'onglet Client Credentials (Informations d'identification du client), nous pouvons voir les informations d'identification du client.

Notez que les informations d'identification du client sont Client ID et Client Secret (l'Identifiant du client et le Secret du client).

6. Configuration du serveur FHIR

⚠️ AVERTISSEMENT ⚠️ : Assurez-vous d'être sur l'espace de noms FHIRSERVER.

Namespace

Pour configurer le serveur FHIR, il faut se connecter au portail de gestion InterSystems IRIS et naviguer: Health > FHIR Configuration > Servers.

FHIR Servers

Ensuite, nous allons créer un nouveau serveur FHIR.

Cliquez sur le bouton Server Configuration (Configuration du Serveur).

Server Configuration

6.1. Création un nouveau serveur FHIR

Dans le formulaire Configuration du serveur, nous devons remplir les paramètres suivants:

Server Configuration

  • Paquet de base FHIR: Le paquet de base FHIR à utiliser pour le serveur FHIR
    • r5
  • Adresse URL: L'URL du serveur FHIR
    • /fhir/r5
  • Stratégie d'interactions: La stratégie d'interactions à utiliser pour le serveur FHIR
    • FHIR.Python.InteractionsStrategy
    • ⚠️ AVERTISSEMENT ⚠️ : Pas comme sur la photo, il faut sélectionner la stratégie d'interactions FHIR.Python.InteractionsStrategy.

Cliquer sur le bouton Add.

Cela peut prendre quelques minutes. 🕒 Allons prendre un café. ☕️

Félicitations, nous avons maintenant créé le serveur FHIR. 🥳

6.2. Liaison entre le serveur FHIR et le serveur d'autorisation OAuth2

Sélectionnez le serveur FHIR et descendez jusqu'au bouton Edit (modifier).

FHIR Server

Dans le formulaire Serveur FHIR , nous devons remplir les paramètres suivants:

FHIR Server

  • Nom du Client OAuth2: Le nom du Client
    • App

Cliquez sur le bouton Save (sauvegarder).

Félicitations, nous avons maintenant lié le serveur FHIR au serveur d'autorisation OAuth2. 🥳

6.3. Test du serveur FHIR

Pour tester le serveur FHIR, vous pouvez utiliser la commande suivante:

GET https://localhost:4443/fhir/r5/Patient

Sans l'en-tête Authorization, vous obtiendrez une réponse 401 Unauthorized.

Pour authentifier la demande, vous devez ajouter l'en-tête Authorization avec le jeton Bearer.

Pour ce faire, demandons un jeton au serveur d'autorisation OAuth2.

POST https://localhost:4443/oauth2/token
Content-Type: application/x-www-form-urlencoded
Authorization: Basic <client_id>:<client_secret>

grant_type=client_credentials&scope=user/Patient.read&aud=https://localhost:4443/fhir/r5

Vous obtiendrez une réponse 200 OK avec le jeton d'accès access_token et le type de jeton token_type.

Maintenant, vous pouvez utiliser jeton d'accès access_token pour authentifier la demande auprès du serveur FHIR.

GET https://localhost:4443/fhir/r5/Patient
Authorization: Bearer <access_token>
Accept: application/fhir+json

Félicitations, vous avez maintenant authentifié la demande sur le serveur FHIR. 🥳

7. Filtrage des ressources FHIR avec "InterSystems IRIS for Health"

Eh bien, nous aborderont maintenant un grand sujet.

Le but de ce sujet sera de mettre en place les capacités d'interopérabilité d'IRIS for Health entre le serveur FHIR et l'application cliente.

Ci-dessous, une vue macro de l'architecture:

Interoperability

Et voici le flux de travail:

Workflow

Ce que nous remarquons ici, c'est que l'EAI (capacités d'interopérabilité d'IRIS for Health) servira de chemin d'accès pour les demandes entrantes vers le serveur FHIR. Il filtrera la réponse du serveur FHIR en fonction des champs d'application et enverra la réponse filtrée à l'application cliente.

Avant d'aller plus loin, permettez-moi de vous présenter rapidement les capacités d'interopérabilité d'IRIS for Health.

7.1. Cadre d'Interopérabilité

Il s'agit du cadre IRIS Framework.

FrameworkFull

L'objectif de ce cadre est de fournir un moyen de relier différents systèmes entre eux.

Nous avons 4 composants principaux:

  • Services métiers: Le point d'entrée du cadre. Il reçoit la demande entrante et l'envoie à la production.
  • Processus métier: Le flux de travail du cadre. Il traite la demande entrante et l'envoie à l'opération métier.
  • Operations métier: Le point de sortie du cadre. Il traite la demande entrante et l'envoie au service métier.
  • Messages: Les données du cadre. Elles contiennent la requête entrante et la réponse sortante.

Pour cette session de formation, nous utiliserons les composants suivants:

  • Un service métier Business Service pour recevoir la demande de l'application cliente.
  • Un processus métier Business Process pour filtrer la réponse du serveur FHIR en fonction des péramètres.
  • Une opération métier Business Operation pour envoyer les messages au serveur FHIR.

Pour cette session de formation, nous utiliserons une production d'interopérabilité pré-construite.

Nous nous concentrerons uniquement sur le processus métier Business Process permettant de filtrer la réponse du serveur FHIR en fonction des périmètres.

7.2. Installation de l'IoP

Pour cette partie, nous utiliserons l'outil IoP. IoP signifie l'Interopérabilité sur Python..

Vous pouvez installer l'outil IoP en suivant les instructions du référentiel de l'IoP

L'outil IoP est pré-installé dans l'environnement de formation.

Connectez-vous au conteneur en cours d'exécution:

docker exec -it formation-fhir-python-iris-1 bash

Et exécutez la commande suivante:

iop --init

Ainsi l'IoP sera installée sur le conteneur IRIS for Health.

7.3. Création de la Production d'Interopérabilité

Toujours dans le conteneur, exécutez la commande suivante:

iop --migrate /irisdev/app/src/python/EAI/settings.py

Ainsi, la production d'interopérabilité sera créée.

Vous pouvez maintenant accéder à la production d'interopérabilité à l'adresse URL suivante:

http://localhost:8089/csp/healthshare/eai/EnsPortal.ProductionConfig.zen?$NAMESPACE=EAI&$NAMESPACE=EAI&

Vous pouvez maintenant lancer la production.

Félicitations, vous avez créé la production d'interopérabilité. 🥳

7.3.1. Test de la Production d'Interopérabilité

Obtenez un jeton du serveur d'autorisation OAuth2.

POST https://localhost:4443/oauth2/token
Content-Type: application/x-www-form-urlencoded
Authorization : Basic <client_id>:<client_secret>

grant_type=client_credentials&scope=user/Patient.read&aud=https://webgateway/fhir/r5

⚠️ AVERTISSEMENT ⚠️ : we change the aud parameter to the URL of the Web Gateway to expose the FHIR server over HTTPS.

Faites passer un patient par la production d'interopérabilité.

GET https://localhost:4443/fhir/Patient
Authorization : Bearer <Token>
Accept: application/fhir+json

Vous pouvez voir la trace de la requête dans la production d'interopérabilité.

http://localhost:8089/csp/healthshare/eai/EnsPortal.MessageViewer.zen?SOURCEORTARGET=Python.EAI.bp.MyBusinessProcess

7.4. Modification du processus métier

Tout le code pour le processus métier Business Process se trouve dans le fichier suivant : https://github.com/grongierisc/formation-fhir-python/blob/main/src/python/EAI/bp.py

Pour cette session de formation, nous adopterons une approche de développement piloté par les tests, TTD (Test Driven Development)..

Tous les tests pour le processus métier Business Process se trouve dans le fichier suivant : https://github.com/grongierisc/formation-fhir-python/blob/main/src/python/tests/EAI/test_bp.py

7.4.1. Préparation de votre environnement de développement

Pour préparer votre environnement de développement, il faut créer un environnement virtuel.

python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt

7.4.2. Exécution des tests

Pour exécuter les tests, vous pouvez utiliser la commande suivante:

pytest

Les tests échouent.

7.4.3. Mise en œuvre du code

Nous avons 4 fonctions à mettre en œuvre:

  • check_token
  • on_fhir_request
  • filter_patient_resource
  • filter_resources

Vous pouvez mettre en œuvre le code dans https://github.com/grongierisc/formation-fhir-python/blob/main/src/python/EAI/bp.py file.

7.4.3.1. check_token

Cette fonction vérifie si le jeton est valide et si le champ d'application contient le périmètre VIP. Si le jeton est valide et que le périmètre contient le périmètre VIP, la fonction renvoie True (vrai), sinon elle renvoie False (faux). Nous utiliserons la bibliothèque jwt pour décoder le jeton.

Cliquez pour voir le code
def check_token(self, token:str) -> bool:

    # décoder le jeton
    try:
        decoded_token= jwt.decode(token, options={"verify_signature": False})
    except jwt.exceptions.DecodeError:
        return False

    # vérifier si le jeton est valide
    if 'VIP' in decoded_token['scope']:
        return True
    else:
        return False

7.4.3.2. filter_patient_resource

Cette fonction filtrera la ressource patient.

Cela supprimera les champ name, address, telecom and birthdate (nom, adresse, télécom et date de naissance) de la ressource patient..

La fonction renverra la ressource patient filtrée sous forme de chaîne.

Nous utiliserons la bibliothèque fhir.resources pour analyser la ressource patient.

Notez la signature de la fonction.

La fonction prend une chaîne en entrée et renvoie une chaîne comme le résultat.

Nous devons donc analyser la chaîne d'entrée en un objet fhir.resources.patient.Patient, puis analyser l'objet fhir.resources.patient.Patient en une chaîne.

Cliquez pour voir le code
def filter_patient_resource(self, patient_str:str) -> str:
    # filtrer le patient
    p = patient.Patient(**json.loads(patient_str))
    # supprimer le nom
    p.name = []
    # supprimer l'adresse
    p.address = []
    # supprimer le télécom
    p.telecom = []
    # supprimer la date de naissance
    p.birthDate = None

    return p.json()

7.4.3.3. filter_resources

Cette fonction filtrera les ressources.

Nous devons vérifier le type de ressource et filtrer la ressource en fonction du type de ressource.

Si le type de ressource est Bundle (Paquet), nous devons filtrer toutes les entrées du paquet qui sont de type Patient.

Si le type de ressource est Patient, nous devons filtrer la resource de type patient.

La fonction renverra la ressource filtrée sous forme de chaîne.

Nous utiliserons la bibliothèque fhir.resources pour analiser la ressource.

Cliquez pour voir le code
    def filter_resources(self, resource_str:str) -> str:
        # analyser le payload
        payload_dict = json.loads(resource_str)

        # quel est le type de ressource?
        resource_type = payload_dict['resourceType'] if 'resourceType' in payload_dict else 'None'
        self.log_info('Resource type: ' + resource_type)

        # c'est un paquet?
        if resource_type == 'Bundle':
            obj = bundle.Bundle(**payload_dict)
            # filtrer le paquet
            for entry in obj.entry:
                if entry.resource.resource_type == 'Patient':
                    self.log_info('Filtering a patient')
                    entry.resource = patient.Patient(**json.loads(self.filter_patient_resource(entry.resource.json())))

        elif resource_type == 'Patient':
            # filtrer le patient
            obj = patient.Patient(**json.loads(self.filter_patient_resource(resource_str)))
        else:
            return resource_str

        return obj.json()

7.4.3.4. on_fhir_request

Cette fonction sera le point d'entrée du processus métier Business Process.

Elle recevra la demande du service métier Business Service, vérifiera le jeton, filtrera la réponse du serveur FHIR en fonction des champs d'application et enverra la réponse filtrée au service métier Business Service.

La fonction renverra la réponse du serveur FHIR.

Nous utiliserons la bibliothèque iris pour envoyer la requête au serveur FHIR.

Le message sera un objet iris.HS.FHIRServer.Interop.Request.

Cet objet contient la requête au serveur FHIR.

Il consiste des composant suivants: Method, URL, Headers et Payload.

Pour vérifier le jeton, nous utiliserons la fonction check_token et pour obtenir un jeton, il faut utiliser l'en-tête USER:OAuthToken.

Pour filtrer la reponse, nous utiliserons la fonction filter_resources et pour lire la réponse du serveur FHIR, nous utiliserons QuickStream.

Cliquez pour voir le code
def on_fhir_request(self, request:'iris.HS.FHIRServer.Interop.Request'):
    # Faire quelque chose avec la requête
    self.log_info('Received a FHIR request')

    # La passer à la cible
    rsp = self.send_request_sync(self.target, request)

    # Essayer d'obtenir le jeton de la requête
    token = request.Request.AdditionalInfo.GetAt("USER:OAuthToken") or ""

    # Faire quelque chose avec la réponse
    if self.check_token(token):
        self.log_info('Filtering the response')
        # Filtrer la réponse
        payload_str = self.quick_stream_to_string(rsp.QuickStreamId)

        # Si le payload est vide, renvoyer la réponse
        if payload_str == '':
            return rsp

        filtered_payload_string = self.filter_resources(payload_str)
        if filtered_payload_string == '':
            return rsp

        # écrire la chaîne json dans un flux rapide
        quick_stream = self.string_to_quick_stream(filtered_payload_string)

        # renvoyer la réponse
        rsp.QuickStreamId = quick_stream._Id()

    return rsp

7.4.4. Exécution des tests

Pour exécuter les tests, vous pouvez utiliser la commande suivante:

pytest

Les tests passent. 🥳

Vous pouvez maintenant tester le processus métier Business Process avec la production d'interopérabilité.

8. Création de l'opération personnalisée

Dernière partie de la session de formation. 🏁

Nous allons créer une opération personnalisée sur le serveur FHIR.

L'opération personnalisée sera opération de fusion de patients Patient et le résultat sera la différence entre les deux patients.

exemple:

POST https://localhost:4443/fhir/r5/Patient/1/$merge
Authorization : Bearer <Token>
Accept: application/fhir+json
Content-Type: application/fhir+json

{
  "resourceType": "Patient",
  "id": "2",
  "meta": {
    "versionId": "2"
  }
}

La réponse sera la différence entre les 2 patients.

{
    "values_changed": {
        "root['address'][0]['city']": {
            "new_value": "fdsfd",
            "old_value": "Lynnfield"
        },
        "root['meta']['lastUpdated']": {
            "new_value": "2024-02-24T09:11:00Z",
            "old_value": "2024-02-28T13:50:27Z"
        },
        "root['meta']['versionId']": {
            "new_value": "1",
            "old_value": "2"
        }
    }
}

Avant de poursuivre, permettez-moi de présenter rapidement l'opération personnalisée sur le serveur FHIR..

L'opération de personnalisation se décline en trois types:

  • Opération d'instance: L'opération est effectuée sur une instance spécifique d'une ressource.
  • Opération de type: L'opération est effectuée sur un type de ressource.
  • Opération de système: L'opération est effectuée sur le serveur FHIR.

Pour cette session de formation, pour la création de l'opération personnalisée nous utiliserons l'Instance Operation.

8.1. Codage de l'opération personnalisée

Une opération personnalisée doit hériter de la classe OperationHandler à partir du module FhirInteraction.

Voici la signature de la classe OperationHandler:

class OperationHandler(object):

    @abc.abstractmethod
    def add_supported_operations(self,map:dict) -> dict:
        """
        @API Enumerate the name and url of each Operation supported by this class
        @Output map : A map of operation names to their corresponding URL.
        Example:
        return map.put("restart","http://hl7.org/fhir/OperationDefinition/System-restart")
        """

    @abc.abstractmethod
    def process_operation(
        self,
        operation_name:str,
        operation_scope:str,
        body:dict,
        fhir_service:'iris.HS.FHIRServer.API.Service',
        fhir_request:'iris.HS.FHIRServer.API.Data.Request',
        fhir_response:'iris.HS.FHIRServer.API.Data.Response'
    ) -> 'iris.HS.FHIRServer.API.Data.Response':
        """
        @API Process an Operation request.
        @Input operation_name : The name of the Operation to process.
        @Input operation_scope : The scope of the Operation to process.
        @Input fhir_service : The FHIR Service object.
        @Input fhir_request : The FHIR Request object.
        @Input fhir_response : The FHIR Response object.
        @Output : The FHIR Response object.
        """

Comme nous l'avons fait dans la partie précédente, nous utiliserons une approche TTD (Développement piloté par les tests).

Tous les tests pour le Processus d'Affaires sont dans ce fichier : https://github.com/grongierisc/formation-fhir-python/blob/main/src/python/tests/FhirInteraction/test_custom.py

8.1.1. add_supported_operations

Cette fonction ajoute l'opération de fusion de patients Patient aux opérations prises en charge.

La fonction retournera un dictionnaire avec le nom de l'opération et l'adresse URL de l'opération. Sachez que le dictionnaire d'entrée peut être vide.

Le résultat attendu est le suivant:

{
  "resource": 
    {
      "Patient": 
        [
          {
            "name": "merge",
            "definition": "http://hl7.org/fhir/OperationDefinition/Patient-merge"
          }
        ]
    }
}

This json document will be added to the CapabilityStatement of the FHIR server.

Cliquez pour voir le code
def add_supported_operations(self,map:dict) -> dict:
    """
    @API Enumerate the name and url of each Operation supported by this class
    @Output map : A map of operation names to their corresponding URL.
    Example:
    return map.put("restart","http://hl7.org/fhir/OperationDefinition/System-restart")
    """

    # verify the map has attribute resource 
    if not 'resource' in map:
        map['resource'] = {}
    # verify the map has attribute patient in the resource
    if not 'Patient' in map['resource']:
        map['resource']['Patient'] = []
    # add the operation to the map
    map['resource']['Patient'].append({"name": "merge" , "definition": "http://hl7.org/fhir/OperationDefinition/Patient-merge"})

    return map

8.1.2. process_operation

Cette fonction traitera l'opération de fusion Patient.

La fonction renverra la différence entre les 2 patients.

Nous allons utiliser la bibliothèque deepdiff pour obtenir la différence entre les 2 patients.

Les paramètres d'entrée sont les suivants:

  • operation_name: Le nom de l'opération à traiter.
  • operation_scope: Le périmètre de l'opération à traiter.
  • body: Le corps de l'operation.
  • fhir_service: L'objet FHIR Service.
    • fhir_service.interactions.Read
      • Une méthode pour lire une ressource à partir du serveur FHIR.
      • Les paramètres d'entrée sont les suivants:
        • resource_type: Le type de la ressource à lire.
        • resource_id: L'identifiant de la ressource à lire.
      • Le résultat est un objet %DynamicObject
  • fhir_request: L'objet de requête FHIR Request..
    • fhir_request.Json
      • Propriété permettant d'obtenir le corps de la requête, c'est un objet %DynamicObject
  • fhir_response: L'objet de réponse FHIR Response.
    • fhir_response.Json
      • Propriété permettant de définir le corps de la réponse, c'est un objet %DynamicObject.

%DynamicObject est une classe permettant de manipuler des objets JSON.

C'est la même chose qu'un dictionnaire Python mais pour ObjectScript.

Téléchargement d'objet JSON:

json_str = fhir_request.Json._ToJSON()
json_obj = json.loads(json_str)

Définition d'objet JSON:

json_str = json.dumps(json_obj)
fhir_response.Json._FromJSON(json_str)

Assurez-vous que la fonction de traitement process_operation est appelée pour vérifier si operation_name est merge, operation_scope est Instance et RequestMethod est POST.

Cliquez pour voir le code
def process_operation(
    self,
    operation_name:str,
    operation_scope:str,
    body:dict,
    fhir_service:'iris.HS.FHIRServer.API.Service',
    fhir_request:'iris.HS.FHIRServer.API.Data.Request',
    fhir_response:'iris.HS.FHIRServer.API.Data.Response'
) -> 'iris.HS.FHIRServer.API.Data.Response':
    """
    @API Process an Operation request.
    @Input operation_name : The name of the Operation to process.
    @Input operation_scope : The scope of the Operation to process.
    @Input fhir_service : The FHIR Service object.
    @Input fhir_request : The FHIR Request object.
    @Input fhir_response : The FHIR Response object.
    @Output : The FHIR Response object.
    """
    if operation_name == "merge" and operation_scope == "Instance" and fhir_request.RequestMethod == "POST":
        # obtenir la ressource primaire
        primary_resource = json.loads(fhir_service.interactions.Read(fhir_request.Type, fhir_request.Id)._ToJSON())
        # obtenir la ressource secondaire
        secondary_resource = json.loads(fhir_request.Json._ToJSON())
        # retrouver la différence entre les deux ressources
        # utiliser deepdiff pour obtenir la différence entre les deux ressources
        diff = DeepDiff(primary_resource, secondary_resource, ignore_order=True).to_json()

        # créer un nouvel %DynamicObject pour stocker le résultat
        result = iris.cls('%DynamicObject')._FromJSON(diff)

        # mettre le résultat dans la réponse
        fhir_response.Json = result
    
    return fhir_response

à tester :

POST https://localhost:4443/fhir/r5/Patient/1/$merge
Authorization : Bearer <Token>
Accept: application/fhir+json

{
  "resourceType": "Patient",
  "id": "2",
  "meta": {
    "versionId": "2"
  }
}

Vous obtiendrez la différence entre les 2 patients.

{
    "values_changed": {
        "root['address'][0]['city']": {
            "new_value": "fdsfd",
            "old_value": "Lynnfield"
        },
        "root['meta']['lastUpdated']": {
            "new_value": "2024-02-24T09:11:00Z",
            "old_value": "2024-02-28T13:50:27Z"
        },
        "root['meta']['versionId']": {
            "new_value": "1",
            "old_value": "2"
        }
    }
}

Félicitations, vous avez créé l'opération personnalisée. 🥳

9. Trucs et Astuces

9.1. Journal de Csp

In %SYS

set ^%ISCLOG = 5
zw ^ISCLOG

9.2. Solution de BP

Cliquez pour voir le code
from grongier.pex import BusinessProcess
import iris
import jwt
import json
from fhir.resources import patient, bundle

class MyBusinessProcess(BusinessProcess):

    def on_init(self):
        if not hasattr(self, 'target'):
            self.target = 'HS.FHIRServer.Interop.HTTPOperation'
            return

    def on_fhir_request(self, request:'iris.HS.FHIRServer.Interop.Request'):
        # Faire quelque chose avec la requête
        self.log_info('Received a FHIR request')

        # La passer à la cible
        rsp = self.send_request_sync(self.target, request)

        # Essayez d'obtenir le jeton de la requête
        token = request.Request.AdditionalInfo.GetAt("USER:OAuthToken") or ""

        # Faire quelque chose avec la réponse
        if self.check_token(token):
            self.log_info('Filtering the response')
            # Filtrer la reponse
            payload_str = self.quick_stream_to_string(rsp.QuickStreamId)

            # si le payload est vide, renvoyer la réponse
            if payload_str == '':
                return rsp

            filtered_payload_string = self.filter_resources(payload_str)
            if filtered_payload_string == '':
                return rsp

            # écrire la chaîne json dans un flux rapide
            quick_stream = self.string_to_quick_stream(filtered_payload_string)

            # renvoyer la réponse
            rsp.QuickStreamId = quick_stream._Id()

        return rsp
    
    def check_token(self, token:str) -> bool:

        # décoder le jeton
        decoded_token= jwt.decode(token, options={"verify_signature": False})

        # vérifier si le jeton est valide
        if 'VIP' in decoded_token['scope']:
            return True
        else:
            return False

    def quick_stream_to_string(self, quick_stream_id) -> str:
        quick_stream = iris.cls('HS.SDA3.QuickStream')._OpenId(quick_stream_id)
        json_payload = ''
        while quick_stream.AtEnd == 0:
            json_payload += quick_stream.Read()

        return json_payload
    
    def string_to_quick_stream(self, json_string:str):
        quick_stream = iris.cls('HS.SDA3.QuickStream')._New()

        # écrire la chaîne json dans le payload
        n = 3000
        chunks = [json_string[i:i+n] for i in range(0, len(json_string), n)]
        for chunk in chunks:
            quick_stream.Write(chunk)

        return quick_stream

    def filter_patient_resource(self, patient_str:str) -> str:
        # filtrer le patient
        p = patient.Patient(**json.loads(patient_str))
        # supprimer le nom
        p.name = []
        # supprimer l'adresse
        p.address = []
        # supprimer le télécom
        p.telecom = []
        # supprimer la date de naissance
        p.birthDate = None

        return p.json()

    def filter_resources(self, resource_str:str) -> str:
        # analyser le payload
        payload_dict = json.loads(resource_str)

        # quel est le type de ressource?
        resource_type = payload_dict['resourceType'] if 'resourceType' in payload_dict else 'None'
        self.log_info('Resource type: ' + resource_type)

        # c'est un paquet?
        if resource_type == 'Bundle':
            obj = bundle.Bundle(**payload_dict)
            # filrer le paquet
            for entry in obj.entry:
                if entry.resource.resource_type == 'Patient':
                    self.log_info('Filtering a patient')
                    entry.resource = patient.Patient(**json.loads(self.filter_patient_resource(entry.resource.json())))

        elif resource_type == 'Patient':
            # filtrer le patient
            obj = patient.Patient(**json.loads(self.filter_patient_resource(resource_str)))
        else:
            return resource_str

        return obj.json()
0
0 50
Article Pierre LaFay · Juin 26, 2024 3m read

Contexte

Dans les versions >=2021.2 d'InterSystems IRIS, nous pouvons utiliser le [binaire irispython pour écrire directement du code python au-dessus de nos instances IRIS] (https://docs.intersystems.com/iris20212/csp/docbook/DocBook.UI.Page.cls?KEY=AEPYTHON#AEPYTHON_runpython_script). Cela nous permet d'utiliser des paquets python, d'appeler des méthodes, de faire des requêtes SQL et de faire à peu près tout ce qui se fait en Objectscript mais en python.

Par exemple, ci-dessous, je vérifie si un espace de noms est présent :

#!/usr/irissys/bin/irispython
import iris
# call arbitrary class methods
result = iris.cls('%SYS.Namespace').Exists('USER')
if result == 1:
    print(f"Namespace USER is present")

Mais que se passe-t-il si ma méthode dans IRIS a des paramètres spéciaux tels que les paramètres Output et ByRef ? Comment utiliser les paramètres Output dans irispython ?

Par exemple, Ens.Director a beaucoup de paramètres de sortie sur ses méthodes, comment puis-je utiliser ces paramètres en python ?

Ens.Director:GetProductionStatus

// An example method stub from Ens.Director
ClassMethod GetProductionStatus(Output pProductionName As %String, Output pState As %Integer...

Essayer les méthodes de variables normales

À première vue, vous pouvez essayer ce qui suit :

import os
# Set namespace the manual way
os.environ['IRISNAMESPACE'] = 'DEMONSTRATION'

import iris

# TEST 1 avec variables de sortie
productionName, productionState = None, None
status = iris.cls('Ens.Director').GetProductionStatus(productionName, productionState) 

print("Success? -- {}".format(productionState != None))

Mais les deux tests ne renvoient pas les variables de sortie ! Vous pouvez essayer vous-même sur n'importe quel espace de noms Ensemble

Utilisation de iris.ref

L'utilitaire irispython iris.ref peut être utilisé pour capturer les variables Output et ByRef.

  1. Créer un objet iris.ref().
  2. Appelez votre méthode ObjectScript
  3. Utiliser la variable .value pour obtenir le résultat de ce paramètre
import os
# Définir l'espace en dur
os.environ['IRISNAMESPACE'] = 'DEMONSTRATION'

import iris

# TEST 2 avec des variables de sortie
productionName, productionState = iris.ref('productionName'), iris.ref('productionState')
status = iris.cls('Ens.Director').GetProductionStatus(productionName, productionState) 

print("Status: {}".format(status))
# see .value
print("Production: {}".format(productionName.value))
# see .value
print("Production State: {}".format(productionState.value))

image

0
0 50
Article Guillaume Rongier · Mai 24, 2024 5m read

Comme la plupart d'entre vous le savent probablement déjà, depuis environ la fin de 2022 InterSystems IRIS a inclus la fonctionnalité de stockage de colonnes dans sa base de données, eh bien, dans l'article d'aujourd'hui, nous allons la mettre à l'épreuve par rapport au stockage de rangées habituel.

Stockage de colonnes

Quelle est la principale caractéristique de ce type de stockage? Si nous consultons la documentation officielle, nous verrons ce tableau fantastique qui explique les principales caractéristiques des deux types de stockage (par rangées ou par colonnes):

Comme vous pouvez le constater, le stockage de colonnes est principalement conçu pour les tâches analytiques dans le cadre desquelles des requêtes sont lancées sur des champs spécifiques de notre tableau, tandis que le stockage de rangées est plus optimal lorsqu'un grand nombre d'opérations d'insertion, de mise à jour et de suppression sont nécessaires. ainsi que pour l'obtention d'enregistrements complets.

Si vous continuez la lecture de la documentation, vous verrez à quel point il est simple de configurer notre table pour qu'elle puisse utiliser le stockage de colonnes:

CREATETABLEtable (columntype, column2 type2, column3 type3) WITH STORAGETYPE = COLUMNAR

En utilisant cette commande, nous définissons toutes les colonnes de notre tableau avec un stockage de colonnes, mais nous pouvons opter pour un modèle mixte où notre tableau dispose d'un stockage de rangées, mais dont certaines colonnes font appel à un stockage de colonnes.

Ce scénario mixte pourrait être intéressant lorsque des opérations d'agrégation telles que les sommes, les moyennes, etc. sont courantes. Dans un tel cas, nous pourrions définir la colonne qui utilisera le stockage en question:

CREATETABLEtable (columntype, column2 type2, column3 type3 WITH STORAGETYPE = COLUMNAR)

Dans l'exemple précédent, nous avons défini une table avec un stockage de rangées et une colonne (column3) avec un stockage de colonnes.

Comparatif

Pour comparer le temps consacré au stockage de colonnes et au stockage de rangées dans des requêtes différentes, nous avons créé un petit exercice à l'aide de Jupyter Notebook qui insère une série d'enregistrements que nous générons dans deux tables, la première avec un stockage de rangées (Test.PurchaseOrderRow) et la seconde avec un stockage de rangées dans les deux colonnes (Test.PurchaseOrderColumnar)

Test.PurchaseOrderRow

CREATETABLE Test.PurchaseOrderRow (
    ReferenceINTEGER,
    Customer VARCHAR(225),
    PaymentDate DATE,
    Vat NUMERIC(10,2),
    Amount NUMERIC(10,2),
    StatusVARCHAR(10))

Test.PurchaseOrderColumnar

CREATETABLE Test.PurchaseOrderColumnar (
    ReferenceINTEGER,
    Customer VARCHAR(225),
    PaymentDate DATE,
    Vat NUMERIC(10,2),
    Amount NUMERIC(10,2) WITH STORAGETYPE = COLUMNAR,
    StatusVARCHAR(10) WITH STORAGETYPE = COLUMNAR)

Si vous téléchargez le projet Open Exchange et le déployez dans votre Docker local, vous pouvez accéder à l'instance Jupyter Notebook et examiner le fichier PerformanceTests.ipynb, qui sera responsable de la génération des données aléatoires que nous allons stocker en différentes étapes dans nos tables et enfin il nous montrera un graphique avec la performance des opérations d'interrogation.

Jetons un coup d'œil rapide à la configuration de notre projet:

docker-compose.yml

version:'3.7'services:# iris  iris:    init:true    container_name:iris    build:      context:.      dockerfile:iris/Dockerfile    ports:      -52774:52773      -51774:1972    volumes:    -./shared:/shared    environment:    -ISC_DATA_DIRECTORY=/shared/durable    command:--check-capsfalse--ISCAgentfalse# jupyter notebook  jupyter:    build:      context:.      dockerfile:jupyter/Dockerfile    container_name:jupyter    ports:      -"8888:8888"    environment:      -JUPYTER_ENABLE_LAB=yes      -JUPYTER_ALLOW_INSECURE_WRITES=true    volumes:      -./jupyter:/home/jovyan      -./data:/app/data    command:"start-notebook.sh --NotebookApp.token='' --NotebookApp.password=''"

Nous déployons les conteneurs IRIS et Jupyter dans notre docker, en configurant d'abord IRIS avec l'espace de noms "TEST" et les deux tables requises pour le test.

Pour que vous ne vous ennuyiez pas avec du code, vous pouvez consulter le fichier PerformanceTests.ipynb à partir duquel nous allons nous connecter à IRIS, générer les enregistrements à insérer et les stocker dans IRIS

Exécution du test

Les résultats ont été les suivants (en secondes):

Inserts:

Les inserts effectuées sont de type "bulk":

INSERTINTO Test.PurchaseOrderColumnar (Reference, Customer, PaymentDate, Vat, Amount, Status) VALUES (?, ?, ?, ?, ?, ?)

Le temps nécessaire pour chaque insert batch est le suivant:

Nombre total d'inserts

Stockage de rangéesStockage mixte
1000

0.031733

0.041677

5000

0.159338

0.185252

20000

0.565775

0.642662

50000

1.486459

1.747124

100000

2.735016

3.265492

200000

5.395032

6.382278

Sélections:

La sélection lancée inclut une fonction d'agrégation et une condition, toutes deux sur des colonnes avec stockage de colonnes:

SELECTAVG(Amount) FROM Test.PurchaseOrderColumnar WHEREStatus = 'SENT'

Nombre total de lignes

Stockage de rangéesStockage mixte
1000

0.002039

0.001178

5000

0.00328

0.000647

20000

0.005493

0.001555

50000

0.016616

0.000987

100000

0.036112

0.001605

200000

0.070909

0.002738

Conclusions

Comme vous pouvez le voir dans les résultats obtenus, l'opération correspond exactement à ce qui est indiqué dans la documentation. L'inclusion de colonnes avec stockage de colonnes pénalise légèrement les performances au cours des inserts (environ 18% plus lent pour notre exemple) alors que les requêtes sur ces mêmes colonnes améliorent considérablement le temps de réponse (258 fois plus rapide).

Il s'agit sans aucun doute d'un élément à prendre en compte lors de la planification du développement de toute application.

0
0 77
Article Guillaume Rongier · Mai 22, 2024 6m read

Il me semble que c'était hier, lorsque nous avons réalisé un petit projet en Java pour tester les performances d'IRIS, PostgreSQL et MySQL (vous pouvez consulter l'article que nous avons écrit en juin à la fin de cet article). Si vous vous souvenez bien, IRIS était supérieur à PostgreSQL et dépassait considérablement MySQL en termes d'insertions, sans grande différence au niveau des requêtes.

Peu de temps après , @Dmitry Maslennikov m'a demandé "Pourquoi ne le testez-vous pas à partir d'un projet Python ?". Voici donc la version Python des tests que nous avons effectués précédemment en utilisant les connexions JDBC.

Tout d'abord, sachez que je ne suis pas un expert en Python, donc si vous remarquez des choses qui pourraient être améliorées, n'hésitez pas à me contacter.

Pour cet exemple, j'ai utilisé Jupyter Notebook, qui simplifie considérablement le développement en Python et nous permet de voir étape par étape ce que nous faisons. En annexe de cet article vous trouverez l'application qui vous permettra d'effectuer vos propres tests.

Avertissement pour les utilisateurs de Windows

Si vous clonez le projet GitHub dans Visual Studio Code, pour déployer correctement les conteneurs, il vous faudra peut-être changer la configuration de fin de ligne par défaut de CRLF à LF:

Si vous souhaitez reproduire les tests sur vos ordinateurs, vous devez tenir compte des éléments suivants : Docker Desktop demandera des autorisations d'accès aux dossiers de vos ordinateurs dont il a besoin pour déployer le projet. Si vous n'avez pas configuré l'autorisation d'accès à ces dossiers avant de lancer les conteneurs Docker, la création initiale de la table de test dans PostgreSQL échouera, donc avant de lancer le projet, vous devez configurer l'accès partagé aux dossiers du projet dans votre DockerDesktop.

Pour ce faire, vous devez accéder à Paramètres -> Ressources -> Partage de fichiers ("Settings -> Resources -> File sharing") et ajouter à la liste le dossier dans lequel vous avez cloné le projet:

Vous êtes prévenus!

 

Test de performance

Pour ces tests, nous utiliserons une table relativement simple contenant les informations les plus basiques possibles sur un patient. Ici vous pouvez voir la commande pour créer la table en SQL:

CREATETABLE Test.Patient (
    NameVARCHAR(225),
    Lastname VARCHAR(225),
    Photo VARCHAR(5000),
    Phone VARCHAR(14),
    Address VARCHAR(225)    
)

Comme vous pouvez le voir, nous avons défini la photo du patient comme un VARCHAR(5000), puisque nous allons inclure (théoriquement) les informations vectorisées de la photo. Il y a quelques mois, j'ai publié un article expliquant comment utiliser Embedded Python pour implémenter IRIS, un système de reconnaissance faciale (ici) où vous pouvez voir comment les images sont transformées en vecteurs pour une comparaison ultérieure. La question de la vectorisation vient du fait que ce format vectoriel est la norme dans de nombreux modèles d'apprentissage automatique et qu'il est toujours utile de tester avec quelque chose de similaire à la réalité (juste quelque chose)./p>

 

Paramètres de Jupyter Notebook

Pour simplifier au maximum le développement du projet en Python, j'ai utilisé un outil magnifique, le Jupyter Notebook, qui permet de développer pas à pas chacune des fonctionnalités dont nous aurons besoin.

Voici un aperçu de notre Jupyter:

Jetons un coup d'œil aux points les plus intéressants de celui-ci:

Importation de bibliothèques:

import iris
import names
import numpy as np
from datetime import datetime
import psycopg2
import mysql.connector
import matplotlib.pyplot as plt
import random_address
from phone_gen import PhoneNumber

Connexion aux bases de données:

IRIS:

connection_string = "iris:1972/TEST"
username = "superuser"
password = "SYS"
connectionIRIS = iris.connect(connection_string, username, password)
cursorIRIS = connectionIRIS.cursor()
print("Connected")

PostgreSQL:

connectionPostgres = psycopg2.connect(database="testuser",
                        host="postgres",
                        user="testuser",
                        password="testpassword",
                        port="5432")
cursorPostgres = connectionPostgres.cursor()
print("Connected")

MySQL:

connectionMySQL = mysql.connector.connect(
  host="mysql",
  user="testuser",
  password="testpassword"
)
cursorMySQL = connectionMySQL.cursor()
print("Connected")

Génération des valeurs à insérer

phone_number = PhoneNumber("USA")
resultsIRIS = []
resultsPostgres = []
resultsMySQL = []
parameters =  []
for x in range(1000):
    rng = np.random.default_rng()
    parameter = []
    parameter.append(names.get_first_name())
    parameter.append(names.get_last_name())
    parameter.append(str(rng.standard_normal(50)))
    parameter.append(phone_number.get_number())
    parameter.append(random_address.real_random_address_by_state('CA')['address1'])
    parameters.append(parameter)

print("Parameters built")

Insertion dans IRIS

date_before = datetime.now()

cursorIRIS.executemany("INSERT INTO Test.Patient (Name, Lastname, Photo, Phone, Address) VALUES (?, ?, ?, ?, ?)", parameters) connectionIRIS.commit() difference = datetime.now() - date_before print(difference.total_seconds()) resultsIRIS.append(difference.total_seconds())

Insertion dans PostgreSQL

date_before = datetime.now()

cursorPostgres.executemany("INSERT INTO test.patient (name, lastname, photo, phone, address) VALUES (%s,%s,%s,%s,%s)", parameters) connectionPostgres.commit() difference = datetime.now() - date_before print(difference.total_seconds()) resultsPostgres.append(difference.total_seconds())

Insertion dans MySQL

date_before = datetime.now()

cursorMySQL.executemany("INSERT INTO test.patient (name, lastname, photo, phone, address) VALUES (%s,%s,%s,%s,%s)", parameters) connectionMySQL.commit() difference = datetime.now() - date_before print(difference.total_seconds()) resultsMySQL.append(difference.total_seconds())

Pour notre test, j'ai décidé d'insérer les valeurs suivantes dans chaque base de données:

  • 1 insertion de 1000 patients.
  • 1 insertion de 5000 patients.
  • 1 insertion de 20 000 patients.
  • 1 insertion de 50 000 patients.

Gardez à l'esprit, lors de l'exécution des tests, que le processus le plus long est la création des valeurs à insérer par Python. Pour se rapprocher de la réalité, j'ai lancé plusieurs tests à l'avance afin que les bases de données disposent déjà d'un ensemble significatif d'enregistrements (environ 200 000 enregistrements)./p>

Résultats des tests

Insertion de 1000 patients:

  • InterSystems IRIS: 0,037949 seconde.
  • PostgreSQL: 0,106508 seconde.
  • MySQL: 0,053338 seconde.

Insertion de 5,000 patients:

  • InterSystems IRIS: 0,162791 seconde.
  • PostgreSQL: 0,432642 seconde.
  • MySQL: 0,18925 seconde.

Insertion de 20,000 patients:

  • InterSystems IRIS: 0,601944 seconde.
  • PostgreSQL: 1,803113 seconde.
  • MySQL: 0,594396 seconde.

Insertion de 50,000 patients:

  • InterSystems IRIS: 1.482824 seconde.
  • PostgreSQL: 4,581251 secondes.
  • MySQL: 2,162996 secondes.

Bien qu'il s'agisse d'un test assez simple, il est très significatif car il nous permet de voir la tendance de chaque base de données en ce qui concerne les performances d'insertion.

Conclusions

Si nous comparons les performances des tests effectués avec le projet Java et le projet actuel en Python, nous constatons qu'à cette occasion, PostgreSQL est clairement inférieur au projet Python, étant 4 fois plus lent qu'InterSystems IRIS, tandis que MySQL s'est amélioré par rapport à la version Java.

InterSystems IRIS reste incontestablement le meilleur des trois, présentant un comportement plus linéaire et une meilleure performance d'insertion, quelle que soit la technologie utilisée.

Caractéristiques techniques de l'ordinateur portable utilisé pour les tests:

  • Système d'exploitation: Microsoft Windows 11 Pro.
  • Processeur: 13th Gen Intel(R) Core(TM) i9-13900H, 2600 Mhz.
  • Mémoire RAM: 64 Go.
0
0 79
Article Iryna Mykhailova · Avr 29, 2024 10m read

Salut la Communauté !

Dans cet article, nous présentons un aperçu de SQLAlchemy, alors commençons !

SQLAlchemy est une boîte à outils Python SQL qui sert de pont entre votre code Python et le système de base de données relationnelle de votre choix. Créée par Michael Bayer, elle est actuellement disponible en tant que bibliothèque open-source sous la licence MIT. SQLAlchemy prend en charge un large éventail de systèmes de bases de données, notamment PostgreSQL, MySQL, SQLite, Oracle et Microsoft SQL Server, ce qui la rend polyvalente et adaptable aux différentes exigences des projets.

Les outils SQLAlchemy SQL Toolkit et Object Relational Mapper (mappeur objet-relationnel) constituent un jeu complet d'outils pour travailler avec des bases de données et Python. Il comporte plusieurs domaines distincts de fonctionnalité que vous pouvez utiliser individuellement ou dans des combinaisons diverses. Les principaux composants sont illustrés ci-dessous, les dépendances des composants étant organisées en couches :

_images/sqla_arch_small.png
Comme vous le constatez ci-dessus, les deux parties les plus importantes de SQLAlchemy sont le  mapping objet-relationnel (Object Relational Mapper, ORM) et le the noyau (Core).


Object Relational Mapper(ORM)

Le composant ORM permet de faire correspondre des objets Python à des tables de base de données. Il simplifie l'écriture du code qui interagit avec les bases de données. L'ORM s'appuie sur le Core pour fournir les moyens de travailler avec un modèle d'objet de domaine mappé sur un schéma de base de données. Lorsque vous l'utilisez, vous construisez généralement les instructions SQL de la même manière qu'avec le Core. Cependant, la tâche DML, qui fait référence à la persistance des objets commerciaux dans une base de données, est automatisée à l'aide d'un modèle appelé unité de travail. Il convertit les changements d'état par rapport aux objets variables en constructions INSERT, UPDATE et DELETE qui sont ensuite invoquées en fonction de ces objets. Les instructions SELECT sont complétées par des capacités d'automatisation spécifiques à l'ORM et des capacités d'interrogation centrées sur l'objet.

Ci-dessous, vous pouvez observer un exemple basique de définition d'une classe ORM dans SQLAlchemy :

from sqlalchemy import Column, Integer, String
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()

classUser(Base):tablename = 'users' id = Column(Integer, primary_key=True) username = Column(String(50), unique=True) email = Column(String(100))

En utilisant cette classe, vous pouvez facilement créer, faire une requête, mettre à jour et supprimer des enregistrements d'utilisateurs dans votre base de données.

Core

Le composant Core fournit la fonctionnalité sous-jacente pour interagir avec les bases de données, y compris la mise en commun des connexions, la gestion des transactions et la génération d'expressions SQL. L'élément central à la fois du Core et de l'ORM de SQLAlchemy est le langage d'expression SQL qui facilite la construction fluide et composable de requêtes SQL.

Le langage d'expression SQL est une boîte à outils propre, indépendante du paquet ORM. Elle fournit un système de construction d'expressions SQL représentées par des objets composables, qui peuvent ensuite être "exécutées" contre une base de données cible dans le cadre d'une transaction spécifique.

L'ORM est construit sur Core pour fournir les outils permettant de travailler avec un modèle d'objet de domaine mappé sur un schéma de base de données. Lorsque vous l'utilisez, vous construisez généralement les instructions SQL de la même manière qu'avec le Core. Cependant, la tâche DML, qui fait référence à la persistance des objets commerciaux dans une base de données, est automatisée à l'aide d'un modèle appelé unité de travail. Il convertit les changements d'état par rapport aux objets variables en constructions INSERT, UPDATE et DELETE qui sont ensuite invoquées en fonction de ces objets. Les instructions SELECT sont complétées par des capacités d'automatisation spécifiques à l'ORM et des capacités d'interrogation centrées sur l'objet.

Alors que le travail avec le Core et le langage d'expression SQL offre une vue de la base de données centrée sur le schéma, ainsi qu'un paradigme de programmation orienté vers l'immuabilité, l'ORM construit une vue de la base de données centrée sur le domaine, avec un paradigme de programmation qui est plus explicitement orienté vers l'objet et qui repose sur la mutabilité. Une base de données relationnelle étant elle-même un service modifiable, la différence réside dans le fait que le langage d'expression Core/SQL est orienté vers la commande, tandis que l'ORM est orienté vers l'état.

Vous trouverez ci-dessous un exemple de requête SQLAlchemy simple :

from sqlalchemy import create_engine, text

engine = create_engine('sqlite:///my_database.db') conn = engine.connect()

query = text("SELECT username FROM users WHERE id = :user_id") result = conn.execute(query, user_id=1)

SQLAlchemy intègre un système de regroupement des connexions qui gère efficacement les connexions aux bases de données. Elle gère automatiquement la création, la réutilisation et la libération des connexions, réduisant ainsi la charge de travail et améliorant les performances des applications ayant des interactions fréquentes avec la base de données.

Utilisation simple des opérations CRUD de SQLAlchemy

  1. Établissez la connectivité : le moteur
  2. Définissez vos tables de base de données
  3. Utilisez l'ORM pour sauvegarder les données
  4. Récupérez des données à l'aide de l'ORM

1. Établissement de la connectivité - le moteur

Chaque application SQLAlchemy commence par un objet appelé moteur (Engine). Cet objet agit comme une source centrale de connexions à une base de données particulière, en fournissant à la fois des paramètres de base et un espace de stockage appelé "pool de connexions" pour ces connexions à la base de données. Le moteur est généralement un objet global qui ne doit être créé qu'une seule fois pour un serveur de base de données particulier. Il est généralement configuré à l'aide d'une chaîne d'URL décrivant la manière dont il doit se connecter à l'hôte ou au backend de la base de données.

Nous utiliserons une base de données SQLite de type "en mémoire seulement". Le moteur sera assemblé à l'aide de la fonction create_engine() :

from sqlalchemy import create_engine
engine = create_engine("sqlite+pysqlite:///:memory:", echo=True)


2.  Définition  de vos tables de base de données

Pour faire fonctionner l'OR, il faut d'abord définir les tables de base de données à l'aide des classes Python. Par exemple, la classe Python suivante définit une table User :

from sqlalchemy import SQLAlchemy

db = SQLAlchemy()

classUser(db.Model): id = db.Column(db.Integer, primary_key=True) email = db.Column(db.String(150), unique=True) username = db.Column(db.String(150), unique=True) password = db.Column(db.String(150))

3.  Utilisation de l'ORM pour sauvegarder les données

Pour accéder à la table User, nous devons importer User à partir de notre classe de modèle, et exploiter ensuite l'objet session pour ajouter des données :

from models import User
from sqlalchemy import SQLAlchemy

db = SQLAlchemy()

new_user = User(email="email address", username="username", password="Password")) db.session.add(new_user) db.session.commit()

4. Récupération de données à l'aide d'ORM

Nous utiliserons l'objet de requête de la session pour extraire des données de la base de données :

from models import User
from sqlalchemy import SQLAlchemy

db = SQLAlchemy()

Query the User table

users = db.session.query(User).all()

Iterate over the records and print them to the console

for user in users: print(user.username)

 

Cas d'utilisation de SQLAlchemy

SQLAlchemy est largement utilisé dans divers domaines, dont les suivants :

  • Développement Web: De nombreux frameworks web, tels que Flask et Django, s'intègrent parfaitement à SQLAlchemy pour la gestion des bases de données.
  • Analyse des données et rapports: SQLAlchemy aide les scientifiques et les analystes de données à interagir avec les bases de données relationnelles lors de l'analyse des données.
  • Applications Enterprise: Il s'agit d'un choix privilégié pour la création d'applications d'entreprise robustes, évolutives et faciles à maintenir.
  • Microservices: SQLAlchemy prend en charge l'architecture des microservices en permettant à chaque service d'interagir avec sa base de données.

 

Les avantages de l'utilisation de SQLAlchemy

SQLAlchemy présente de nombreux avantages, dont certains sont énumérés ci-dessous :

  • Productivité accrue:  SQLAlchemy peut aider les développeurs à être plus productifs en fournissant une abstraction de haut niveau pour l'accès aux bases de données. Elle permet aux développeurs de se concentrer sur l'écriture de code d'application plutôt que de s'inquiéter de la syntaxe SQL sous-jacente.
  • Erreurs réduites:  SQLAlchemy peut nous aider à réduire les erreurs en fournissant un système de type pour l'accès à la base de données. Cela nous permet de nous assurer que les développeurs utilisent les bons types de données et ne commettent pas d'erreurs.
  • Amélioration de la maintenabilité: SQLAlchemy permet d'améliorer la maintenabilité du code en facilitant la modification du schéma de la base de données. C'est possible parce que le code SQLAlchemy n'est pas couplé étroitement au schéma de la base de données sous-jacente.
  • Portabilité: Le code SQLAlchemy est portable dans n'importe quelle base de données compatible avec SQLAlchemy. Il simplifie la migration des applications vers différentes bases de données.

Conclusion

SQLAlchemy permet aux développeurs Python de travailler avec des bases de données relationnelles d'une manière plus pythonique et plus efficace. Ses capacités ORM, son langage d'expression SQL, son pool de connexions et son support multiplateforme en font un outil précieux pour diverses applications. Peu importe que vous soyez un développeur débutant ou expérimenté, SQLAlchemy peut rationaliser vos tâches relatives aux bases de données et améliorer votre productivité.
Dans mon prochain article, nous utiliserons SQLAlchemy et InterSystems Caché pour démontrer les opérations CRUD.

Merci d'avoir lu !

0
0 307
Article Lorenzo Scalese · Avr 25, 2024 7m read

En tant que modèle linguistique d'IA, ChatGPT est capable d'effectuer une variété de tâches telles que traduire, écrire des chansons, répondre à des questions de recherche et même générer du code informatique. Avec ses capacités impressionnantes, ChatGPT est rapidement devenu un outil populaire pour diverses applications, des chatbots à la création de contenu.
Mais malgré ses capacités avancées, ChatGPT n'est pas en mesure d'accéder à vos données personnelles. Mais malgré ses capacités avancées, ChatGPT n'est pas en mesure d'accéder à vos données personnelles. Ainsi, dans cet article, je vais démontrer les étapes suivantes pour construire une IA ChatGPT personnalisée en utilisant le LangChain Framework:

  • Étape 1: Chargement du document 

  • Étape 2: Division du document en blocs

  • Étape 3: Utilisation de l'incorporation pour des blocs de données et leur conversion en vecteurs

  • Étape 4: Enregistrement des données dans la base de données de vecteurs

  • Étape 5: Obtention des données (question) de l'utilisateur et leur the intégration

  • Étape 6: Connexion à VectorDB et recherche sémantique

  • Étape 7: Récupération des réponses pertinentes basées sur les requêtes de l'utilisateur et leur envoi au LLM(ChatGPT)

  • Étape 8: Obtention d'une réponse de la part de LLM et renvoi de celle-ci à l'utilisateur

  REMARQUE : Veuillez lire mon article précédent LangChain – Unleashing the full potential of LLMs (LangChain - Libération du plein potentiel des LLM) pour obtenir plus de détails sur LangChain et sur la façon d'obtenir la clé API OpenAI

Alors, commençons
     

Étape 1: Chargement du document 

Tout d'abord, il faut charger le document. Nous allons donc importer PyPDFLoader pour le document PDF 

ClassMethod SavePDF(filePath) [ Language = python ]
{
#pour un fichier PDF, il faut importer PyPDFLoader à partir du framework langchainfrom langchain.document_loaders import PyPDFLoader
# pour un fichier CSV, il faut importer csv_loader# pour un fichier Doc, il faut importer UnstructuredWordDocumentLoader# Pour le document texte, il faut importer TextLoader#importation de l'os pour définir la variable d'environnementimport os
#attribution de la clé API OpenAI à une variable d'environnement 
os.environ['OPENAI_API_KEY'] = "apiKey"#Init du lanceur
loader = PyPDFLoader(filePath)   
#Chargement du document 
documents = loader.load()
return documents
}

Étape 2: Division du document en blocs

Les modèles linguistiques sont souvent limités par la quantité de texte qui peut leur être transmise. Il est donc nécessaire de les diviser en blocs moins volumineux. LangChain fournit plusieurs utilitaires pour ce faire.

L'utilisation d'un séparateur de texte (Text Splitter) peut également contribuer à améliorer les résultats des recherches dans les répertoires de vecteurs, car, par exemple, des blocs moins volumineux ont parfois plus de chances de correspondre à une requête. Tester différentes tailles de blocs (et leur chevauchement) est un exercice intéressant pour adapter les résultats à votre cas d'utilisation.

ClassMethod splitText(documents) [ Language = python ]
{
#Afin de diviser le document, il faut importer RecursiveCharacterTextSplitter du framework Langchain  from langchain.text_splitter import RecursiveCharacterTextSplitter
#Init du séparateur de texte, définition de la taille des blocs (1000) et du chevauchement. = 0
text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=0)
#Division du document en plusieurs blocs
texts = text_splitter.split_documents(documents)
return texts
}

Étape 3: Utilisation d'incorporation pour des blocs de données et leur conversion en vecteurs

Les incorporations (embeddings) de texte sont le cœur et l'âme des outils de Large Language Operations.. Techniquement, nous pouvons travailler avec des modèles linguistiques en langage naturel, mais le stockage et l'extraction du langage naturel sont très inefficaces. 

Pour améliorer l'efficacité, il faut transformer les données textuelles en formes vectorielles. Il existe des modèles ML dédiés à la création d'intégrations à partir de textes. Les textes sont convertis en vecteurs multidimensionnels. Une fois ces données incorporées, nous pouvons les regrouper, les trier, les rechercher, etc. Nous pouvons calculer la distance entre deux phrases pour connaître leur degré de parenté. Et le plus intéressant, c'est que ces opérations ne se limitent pas à des mots-clés comme dans les recherches traditionnelles dans les bases de données, mais capturent plutôt la proximité sémantique de deux phrases. Cela rend le système beaucoup plus puissant, grâce à l'apprentissage automatique.
 

Les modèles d'incorporation de texte reçoivent la saisie de texte et renvoient une liste de flottants (embeddings), qui sont la représentation numérique du texte saisi. Les embeddings permettent d'extraire des informations d'un texte. Ces informations peuvent ensuite être utilisées, par exemple, pour calculer les similitudes entre des textes (par exemple, des résumés de films).

Les modèles d'incorporation de texte reçoivent la saisie de texte et renvoient une liste de flottants

    ClassMethod getEmbeddings(query) [ Language = python ]
    {
    #Obtention d'un modèle d'embeddings à partir du framework Langchainfrom langchain.embeddings import OpenAIEmbeddings
    #Definition d'embedding
    embedding = OpenAIEmbeddings()
    return embedding
    }
    

Étape 4: Enregistrement des données dans la base de données de vecteurs

    ClassMethod saveDB(texts,embedding) [ Language = python ]
    {
    #Obtention de la base de données Chroma à partir de langchainfrom langchain.vectorstores import Chroma      
    # Incorporation et stockage des textes# La fourniture d'un répertoire persistant (persist_directory) permet de stocker les embeddings sur le disque# par exemple dans le dossier myData du chemin d'accès à l'application en cours
    persist_directory = "myData"
    vectordb = Chroma.from_documents(documents=texts, embedding=embedding, persist_directory=persist_directory)
    #sauvegarde du document au niveau local
    vectordb.persist()
    vectordb = None
    }
    

Étape 5: Obtention des données (question) de l'utilisateur et leur the intégration

    ClassMethod getVectorData(query) [ Language = python ]
    {
    #REMARQUE: Il faudrait avoir le même embedding utilisée lorsque nous avons sauvegardé des donnéesfrom langchain.embeddings import OpenAIEmbeddings
    #obtention d'embeddings
    embedding = OpenAIEmbeddings()
    #saisie des données de l'utilisateur (paramètre)
    query = query
    #La suite du code...

Étape 6: Connexion à VectorDB et recherche sémantique

#code continue....     from langchain.vectorstores import Chroma
 persist_directory = "myData"## À présent, il est possible de charger la base de données persistante à partir du disque et de l'utiliser comme d'habitude. 
 vectordb = Chroma(persist_directory=persist_directory, embedding_function=embedding)
 return vectordb
 }

Étape 7: Récupération des réponses pertinentes basées sur les requêtes de l'utilisateur et leur envoi au LLM(ChatGPT)

La mémoire de conversation est la façon dont un chatbot peut répondre à de multiples requêtes à la manière d'un chat. Elle assure une conversation cohérente et, sans elle, chaque requête serait traitée comme une entrée totalement indépendante, sans tenir compte des interactions passées.

LLM avec et sans une mémoire de conversation. Les cases bleues représentent les assistances de l'utilisateur et les cases grises représentent les réponses des LLM. Sans mémoire de conversation (à droite), les LLM ne peuvent pas répondre en utilisant la connaissance des interactions précédentes.

La mémoire permet le  Large Language Model (LLM) de se souvenir des interactions précédentes avec l'utilisateur. Par défaut, les LLMs sont  sans état — ce qui signifie que chaque requête entrante est traitée indépendamment des autres interactions. La seule chose qui existe pour un agent sans état est la saisie actuelle, rien d'autre.


Le modèle ConversationalRetrievalChain est un modèle d'IA conversationnelle conçu pour extraire des réponses pertinentes sur la base des requêtes de l'utilisateur. Il fait partie de la technologie de l'équipe de Langchain. Le modèle utilise une approche basée sur la récupération, où il recherche des réponses préexistantes dans une base de données pour trouver la réponse la plus appropriée à une requête donnée. Le modèle est entraîné sur un grand ensemble de conversations pour apprendre les schémas et le contexte afin de fournir des réponses précises et utiles.

ClassMethod retriveResponse(vectordb) [ Language = python ]
{
from langchain.llms import OpenAI
from langchain.memory import ConversationBufferMemory
from langchain.chains import ConversationalRetrievalChain
#La mémoire de conversation est la façon dont un chatbot peut répondre à de multiples requêtes.
memory = ConversationBufferMemory(memory_key="chat_history", return_messages=True)
#Le ConversationalRetrievalChain est un modèle d'IA conversationnelle conçu pour extraire des réponses pertinentes sur la base des requêtes de l'utilisateur.
qa = ConversationalRetrievalChain.from_llm(OpenAI(temperature=0), vectordb.as_retriever(), memory=memory)
return qa
}


Étape 8: Obtention d'une réponse de la part de LLM et renvoi de celle-ci à l'utilisateur 

ClassMethod getAnswer(qa) [ Language = python ]
{
#Obtention d'une réponse de la part de LLM et renvoi de celle-ci à l'utilisateur
getAnswer = qa.run(query)
return getAnswer
}

Pour plus de détails et de fonctionnalités, veuillez consulter mon application irisChatGPT 

Vidéo connexe

<iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="" frameborder="0" height="315" src="https://www.youtube.com/embed/h9JoqbUKFBk" title="YouTube video player" width="560"></iframe>

Je vous remercie

1
0 313
Article Sylvain Guilbaud · Mars 22, 2024 5m read

L'invention et la vulgarisation des grands modèles de langage (tels que GPT-4 d'OpenAI) ont lancé une vague de solutions innovantes capables d'exploiter de grands volumes de données non structurées qui étaient peu pratiques, voire impossibles, à traiter manuellement jusqu'à récemment. Ces applications peuvent inclure la récupération de données (voir le cours ML301 de Don Woodlock pour une excellente introduction à Retrieval Augmented Generation), l'analyse des sentiments, et même des agents d'IA entièrement autonomes, pour n'en nommer que quelques-uns !

0
0 112
Article Iryna Mykhailova · Mars 7, 2024 8m read

Pourquoi connecter Flask à InterSystems IRIS ?

    La première chose qui nous vient à l'esprit lorsque nous envisageons de coupler Flask avec IRIS est un portail permettant d'interagir avec vos clients et partenaires. Un site web où les patients peuvent accéder à leurs examens cliniques en est un bon exemple. Bien entendu, ce cas nécessiterait une toute nouvelle couche de sécurité, que nous n'avons pas abordée dans notre dernier article. Cependant, nous pouvons l'ajouter sans effort avec Werkzeug, par exemple.
Les sites Web qui établissent un lien entre vos produits et vos clients sont, en effet, un excellent exemple de ce que vous pouvez réaliser en connectant ces technologies car Flask est flexible et vous pouvez facilement ajouter et modifier n'importe quelle fonctionnalité avec cette application ou même faire plus de choses si vous avez une certaine expérience avec CI/CD.
Examinons quelques-unes des astuces que vous pouvez réaliser. Si vous vous souvenez de la première application présentée initialement, vous vous rappellerez que Flask n'a pas besoin d'une structure complexe pour fonctionner, ce qui signifie que vous pouvez le construire à partir de zéro en quelques minutes de codage seulement. Ainsi, vous pouvez rapidement créer de multiples interfaces conviviales et des portails interactifs pour afficher et analyser les données. Par exemple, vous pouvez suivre séparément chaque élément d'une production. Vous pouvez également fournir toutes les fonctionnalités d'InterSystems IRIS que vous utilisez fréquemment sur une magnifique plate-forme sans code. 
    Je travaillais pour Innovatium depuis l'année dernière, et cette expérience m'a permis de rencontrer de nombreuses situations où il était utile de prendre en compte la mémoire utilisée dans les ensembles de résultats de requêtes, les requêtes et les tables, de temps en temps. Ce problème est facile à résoudre en créant un portail similaire à celui créé dans mon article précédent sur Django. Comme le développement avec Flask est plus rapide qu'avec Django, il serait facile de construire quelques plateformes différentes pour chaque projet et pour chaque table que nous souhaitons suivre. En outre, si vous avez un peu de temps libre, vous pouvez automatiser votre plateforme pour obtenir des mises à jour en direct avec Flask-SocktetIO.
Avoir un modèle pour démarrer vos projets peut considérablement accélérer le développement. Pour cette raison, nous allons présenter dans la section suivante un modèle de CRUD de Flask-IRIS publié sur OpenExchange (celui que nous avons construit dans la première partie de cette série d'articles). Autrement dit, si vous n'avez pas le temps d'étudier tous les détails abordés dans le dernier article, il vous suffit de télécharger l'application et de continuer à travailler en toute tranquillité sur votre projet, en vous appuyant sur les connaissances que vous avez déjà acquises.
 

L'application OpenExchange

J'ai décidé d'écrire cette série d'articles en m'inspirant de l'idée mentionnée dans l'article sur l'exemple d'application Flask avec SQLAlchemy Example of Flask application with SQLAlchemy IRIS. Cependant, puisque vous êtes déjà peut-être familier avec les frameworks web de Python et la logique de CRUD, je vais vous présenter une application qui vous permettra de prendre le taureau par les cornes. Ainsi, vous ne perdrez pas votre temps à lire avant la concrétisation de votre projet.
    Suivez les étapes ci-dessous pour télécharger et lancer l'application, commencez à l'utiliser et constatez à quel point elle est facile à mettre en œuvre. Il existe une version plus succincte du tutoriel très proche sur son utilisation sur le fichier README du référentiel GitHub associé. Vous pouvez également consulter tous les liens relatifs à l'application sur sa page Open Exchange.

Guide d'installation

    Avant de développer des projets avec Python, il est toujours préférable de configurer un environnement virtuel. Cela vous aidera à ajouter toutes les exigences, configurations et variables spécifiques à ce projet, sans affecter votre ordinateur et d'autres projets.
    Si vous utilisez Windows/Linux/macOS, ouvrez le terminal dans le répertoire où vous souhaitez démarrer votre projet et créez un environnement virtuel à l'aide de la commande suivante:
python -m venv .venv-folder
    La commande susmentionnée peut être décomposée comme suit : "python" définira un environnement Python pour les commandes suivantes, le drapeau -m lancera un module (dans ce cas, venv), et .venv-folder créera un dossier appelé venv-folder dans le répertoire courant (référencé par le point), où le module venv s'exécutera.
    La prochaine étape consistera à activer l'environnement créé avant de télécharger les prérequis et de lancer votre projet dans les règles de l'art.
    Si vous utilisez macOS ou Linux, vous devez taper “. .venv/bin/activate”. Pour Windows, la commande similaire “.venv\Scritps\activate” lancera le fichier approprié pour la tâche.
    Ensuite, vous pouvez cloner le référentiel avec la commande git clone.
git clone https://github.com/heloisatambara/flask-iris.git
    Enfin, installez la configuration requise à l'aide du paquet d'installation "Python Install Package", et vous serez prêt à commencer le codage.
pip install -r requirements.txt
    Comme vous pouvez le vérifier dans le fichier référencé, la commande ci-dessus installera au moins la version 2.3.3 de Flask, au moins la version 3.1.1 de Flask-SQLAlchemy, et au moins la version 0.10.5 de sqlalchemy-iris, par ^CaretDev.

Guide d'utilisation

    Tout d'abord, il faut connecter l'application à la base de données et à l'espace de noms souhaités. Comme vous avez sans doute appris dans l'article précédent, cette connexion se fait sur le fichier flaskr-iris/database.py, au format “iris://username:password@host:port/NAMESPACE”, selon les spécifications de la documentation SQLALchemy documentation pour la fonction create_engine(). Examinez l'exemple ci-dessous.
    engine = create_engine("iris://_SYSTEM:sys@localhost:1972/SAMPLE")
    Vous pouvez maintenant exécuter l'application web et constater que l'exemple développé fonctionne, après avoir vérifié que l'instance est en cours d'exécution, à l'aide de la commande mentionnée.
...\flask-iris> flask --app flaskr-iris run --debug
    Pour adapter l'application à vos besoins, commencez par modifier le fichier flaskr-iris/models.py pour refléter les données à transférer d'IRIS à Python. L'exemple cloné sur GitHub couvre déjà un grand nombre de cas, comme vous pouvez le vérifier dans l'article précédent, dans la section dédiée aux modèles. Si vous avez besoin d'autre chose, vous pouvez consulter la documentation Flask-SQLAlchemy ou SQLAlchemy oncernant respectivement la définition de modèles et les tables déclaratives.
    Ensuite, il est recommandé de poursuivre le développement en modifiant les fichiers dans flaskr-iris/templates, afin d'adapter l'affichage à vos données. La documentation de Jinja documentation contient toutes les options pour coder vos modèles.
    Ensuite, vous pouvez utiliser les exemples de auth.py et blog.py pour créer vos vues. N'oubliez pas d'enregistrer tous les nouveaux Blueprints dans la fabrique create_app(), dans le fichier __init__.py.
    Enfin, avec les fichiers à l'intérieur du dossier static, vous devriez gagner une longueur d'avance pour ajouter une touche de votre marque à la page web.
 

Section supplémentaire - Les erreurs que j'ai rencontrées et la façon de les corriger

  • Commençons par ce qui peut sembler être une erreur de débutant mais qui peut arriver à tout le monde : si vous avez oublié d'initialiser l'instance IRIS à laquelle vous vous connectez, vous risquez de recevoir le message suivant :
sqlalchemy.exc.OperationalError: (intersystems_iris.dbapi._DBAPI.OperationalError) [WinError 10061] No connection could be made because the target machine actively refused it.


Bien sûr, tout ce que vous avez à faire ici est d'appuyer sur " Start InterSystems IRIS " (démarrage d'InterSystems IRIS) et d'exécuter à nouveau votre application Flask.
 

  • L'erreur suivante peut sembler un peu plus difficile à résoudre :
RuntimeError: Working outside of request context.

This typically means that you attempted to use functionality that needed an active HTTP request. Consult the documentation on testing for information about how to avoid this problem.

db.init_app(app)

    Plusieurs raisons peuvent provoquer ce type d'erreur. Si vous l'avez trouvé lors d'un test, utilisez test_client pour simuler une requête complète. Sinon, il vous faudra peut-être déplacer votre code dans une vue. Cependant, j'ai rencontré cette erreur dans une situation peu courante : Je n'avais pas initié la base de données avec l'application Flask. Pour y remédier, lorsque vous définissez votre base de données avec db = SQLAlchemy() ou quelque chose de similaire, vous pouvez vous connecter directement à l'application en faisant ce qui suit :

app = Flask(__name__)
db = SQLAlchemy(app)

Par ailleurs, si vous travaillez avec une fabrique d'applications, comme indiqué dans l'article précédent, vous pouvez connecter l'application plus tard en appelant init_app() :

db = SQLAlchemy()

defcreate_app(): app = Flask(name) db.init_app(app)

  • Si vous choisissez de suivre l'approche décrite dans la première partie de cet article, vous remarquerez peut-être qu'à chaque fois que vous exécutez votre application, celle-ci essayera de créer toutes les bases de données à partir des modèles que vous avez définis. Cela signifie que vous devrez peut-être traiter ce type d'erreur avec un bloc "try/except". Il est également possible d'ignorer l'erreur si la table existe déjà, mais il faut aussi régler d'autres problèmes. Toutefois, si vous choisissez cette solution, vous devrez peut-être supprimer toutes les tables manuellement chaque fois que vous y apporterez des modifications.  Une autre option consiste à utiliser l'attribut "checkfirst" lors de l'appel de la fonction create_all(). Cela permet d'ignorer les tables qui existent déjà, de sorte que les changements ne seront pas appliqués. Si cela correspond à vos besoins, profitez-en. Pour une mise en œuvre à plus grande échelle, vous pouvez utiliser SQLAlchemy afin de supprimer les tables précédemment créées avant d'exécuter create_all(). Attention : les tables seront créées automatiquement sous le format de nom DefaultSchema_snake_case_model_name. Pour illustrer cette idée, dans l'exemple que nous venons d'explorer les tables ont été construites dans IRIS sous les noms de SQLUser_user et SQLUser_post.
try:
with app.app_context():
db.create_all()
except DatabaseError as err:
if 'already exists' in err._sql_message():
print("Databases already exist.")
else:
print(err) 
  • Enfin, si vous placez les modèles dans un autre fichier par rapport à celui que vous avez utilisé pour exécuter la fonction create_all(), vous pouvez avoir l'impression que cette fonction échoue. Cela se produit parce les modèles que vous voulez assembler doivent être importés avant d'essayer de les fabriquer.
from .models import User, Post
db.init_app(app)
try:
with app.app_context():
db.create_all()
0
0 75
Article Iryna Mykhailova · Mars 4, 2024 24m read

Index

Partie 1

  • Présentation de Flask : une revue rapide des documents de Flask (Flask Docs), où vous trouverez toutes les informations dont vous avez besoin pour ce tutoriel;
  • Connexion à InterSystems IRIS : un guide détaillé étape par étape sur l'utilisation de SQLAlchemy pour se connecter à une instance d'IRIS;

Partie 2

  • Discussion sur cette forme de mise en œuvre : pourquoi nous devrions l'utiliser et les situations où elle est applicable.
  • L'application OpenExchange : si vous n'êtes pas intéressé par le développement de cet exemple, vous pouvez passer directement à cette section et apprendre à l'utiliser comme modèle pour votre application flask-iris.
  • Bonus : quelques erreurs commises lors du développement et comment je les ai résolues.

Présentation de Flask

Flask est un outil web écrit en Python et conçu pour vous faciliter la vie lors de la création d'une application web. Il utilise Werkzeug pour gérer l'interface Web Server Gateway Interface (WSGI) - la spécification standard de Python pour la communication avec les serveurs web. Il utilise également Jinja pour gérer les modèles HTML, et le Click CLI toolkit pour créer et gérer le code d'interface de ligne de commande.

Installation

La première étape est facultative. Cependant, c'est une bonne idée de configurer un environnement virtuel avant de commencer des projets. Vous pouvez le faire sur le terminal en modifiant le répertoire vers le dossier que vous voulez utiliser comme racine et en tapant la commande mentionnée ci-dessous.

> python -m venv .venv

Si vous optez pour un environnement virtuel, activez-le avec la commande suivante (sous Windows) avant d'installer les prérequis :

> .venv\Scripts\activate

Enfin, installez les paquets avec pip (paquetage d'installation Python).

> pip install Flask
> pip install Flask-SQLAlchemy
> pip install sqlalchemy-iris

Quickstart

Nous suivrons la structure présentée dans ce Tutoriel, en adaptant les connexions à l'IRIS. Cela minimisera vos problèmes lors de l'amélioration de votre application puisque vous pourrez consulter la Documentation de Flask sans conflits. Cependant, comme Flask est très flexible, il ne vous oblige pas à suivre des modèles, et vous pouvez considérer les étapes suivantes comme de simples suggestions.

L'approche présentée ici consiste à utiliser les applications comme des paquets et à définir une fabrique d'applications pour couvrir les grands projets. Cependant, vous pouvez toujours construire l'application Flask avec cinq lignes de code seulement, comme on le voit ci-dessous.

from flask import Flask

app = Flask(__name__)


@app.route('/')defhello():return'Hello, world!'

 

Une application modèle

Cette section présente les étapes à suivre pour créer l'application flask-iris. Ce tutoriel peut être suivi si vous n'avez jamais utilisé de framework web auparavant. Cependant, si vous voulez vraiment comprendre la théorie de ce type de flux de travail, lisez mon article précédent sur Exemples de travail avec IRIS à partir de Django, en prenant particulièrement en compte l'image au début de l'article. Vous constaterez avec satisfaction que les différents frameworks web ont une logique très similaire. Cependant, dans Flask, nous pouvons définir les URL et les vues ensemble en tant que groupes, appelés plans originaux (blueprints).

TLes conditions requises concernent Flask, Flask-SQLAlchemy et sqlalchemy-iris. Intuitivement, le pont entre l'application web et votre instance IRIS est construit !
 

Le paquet

Tout d'abord, créez ou sélectionnez le dossier que vous souhaitez utiliser comme racine. Appelons-le flaskr-iris. Nous avons besoin que Python comprenne ce dossier comme un paquet, nous devons donc créer un fichier __init_.py. C'est là que nous placerons notre fabrique d'applications.

Étape 1 - Création de la fabrique d'applications

La fabrique d'applications n'est rien de plus qu'une fonction. A l'intérieur de celle-ci, nous devons définir une instance Flask() (l'application), définir ses configurations, la connecter à la base de données, et enregistrer les blueprints si nous choisissons de les utiliser. Nous allons explorer plus en détail les blueprints plus tard, donc pour l'instant vous n'avez pas besoin de vous en préoccuper.

Cette fonction doit être appelée create_app(). Commencez par importer Flask et créez une instance avec l'argument \N_nom\N_ (consultez la documentation pour plus de détails). Ensuite, mappez la propriété config avec une clé secrète et l'URI de la base de données. Utilisez le format "dialect://username:password@host:port/NAMESPACE", en suivant les recommandations de SQLAlchemy. Dans ce cas, le dialecte devrait être 'iris', créé par CaretDev. Le nom d'utilisateur et le mot de passe sont ceux que vous utilisez pour vous connecter à l'instance InterSystems IRIS indiquée par l'hôte et le port, et l'espace de noms parle de lui-même.

# __init__.pyfrom flask import Flask

defcreate_app():# créer et configurer l'application
    app - Flask(__name__, instance_relative_config=True)

    app.config.from_mapping(
        SECRET_KEY = "dev", # remplacer celle-ci par une clé de génération aléatoire lors du déploiement
        SQLALCHEMY_DATABSE_URI = "iris://_SYSTEM:sys@loclhost:1972/SAMPLE"
    )

Les prochaines lignes de notre fabrique auront besoin de la base de données, alors réservons ce fichier pour un instant pendant que nous parlons de données et de modèles.
 

Étape 2 - Gestion des données

Nous utiliserons SQLAlchemy pour importer et gérer les données puisque nous pouvons utiliser Flask-SQLAlchemy et SQLAlchemy-IRIS pour résoudre les problèmes de connexion. Tout d'abord, il faut créer le fichier database.py dans lequel nous importerons la version Flask de SQLAlchemy et l'instancierons. Cette étape ne nécessite pas de fichier séparé. Cependant, elle peut être très utile plus tard pour développer d'autres méthodes pour gérer la base de données.

# database.pyfrom flask_sqlalchemy import SQLAlchemy

db = SQLAlchemy()

Ensuite, il faut définir les modèles qui indiqueront à Python comment interpréter chaque enregistrement dans un tableau. Créez le fichier models.py, importez l'instance de SQLAlchemy et les fonctionnalités dont vous avez besoin pour traiter les données. En suivant l'exemple donné dans la documentation officielle de Flask, nous pouvons créer un blog nécessitant un modèle pour les utilisateurs et un autre pour les messages. Cette implémentation couvre un nombre substantiel de cas et vous donne une bonne longueur d'avance.

Nous allons utiliser le Mapping Objet-Relationnel (ORM) comme suggéré dans SQLAlchemy 2.0, en définissant des colonnes mappées et une relation de type Un-à-plusieurs (One-to-many). Vous pouvez vérifier comment modéliser d'autres types de relations dans le Guide de SQLAlchemy.

# models.pyfrom .database import db
from sqlalchemy.orm import Mapped, mapped_column
from typing import List, Optional
import datetime

classUser(db.Model):
    id: Mapped[int] = mapped_column(db.Integer, primary_key=True)
    username: Mapped[str] = mapped_column(db.String(1000), unique=True, nullable=False)
    password: Mapped[str] = mapped_column(db.String(1000), nullable=False)
    posts: Mapped[List["Post"]] =  db.relationship(back_populates="user")

classPost(db.Model):
    id: Mapped[int] = mapped_column(db.Integer, primary_key=True)
    author_id: Mapped[int] = mapped_column(db.Integer, db.ForeignKey('user.id'), nullable=False)
    user: Mapped["User"] = db.relationship(back_populates="posts")
    created: Mapped[datetime.datetime] = mapped_column(db.DateTime, nullable=False, server_default=db.text('CURRENT_TIMESTAMP'))
    title: Mapped[str] = mapped_column(db.String(1000), nullable=False)
    body: Mapped[Optional[str]] = mapped_column(db.String(1000000))

Le format général à utiliser pour définir les colonnes dans la dernière version de SQLAlchemy est le suivant :

columnName: Mapped[type] = mapped_column(db.Type, arguments)

En ce qui concerne les relations, du côté des "nombreux", vous devrez également définir la clé étrangère.

Obs.: les noms des classes seront convertis de "CamelCase" en "snake_case" lors de la création des tableaux SQL.

Enfin, nous pouvons retourner à la fabrique sur _init_.py_ et faire les connexions. Importez la base de données et les modèles, assignez cette application à l'instance db, et créez toutes les tableaux à l'intérieur de la méthode create_app().

# __init__.pyfrom flask import Flask
from sqlalchemy.exc import DatabaseError

defcreate_app():# créer et configurer l'application
    app = Flask(__name__)

    app.config.from_mapping(
        SECRET_KEY="dev", # remplacer par un système aléatoire lors du déploiement
        SQLALCHEMY_DATABASE_URI = "iris://_SYSTEM:sys@localhost:1972/SAMPLE"
    )

    # avec cette application, flask initialise Alchemyfrom .database import db
    from .models import User, Post

    db.init_app(app)

    try:
        with app.app_context():
            db.create_all()
    except DatabaseError as err:
        if'already exists'in err._sql_message():
            print("Databases already exist.")
        else:
            print(err)

Ensuite, nous passerons à la création des vues.

Étape 3 - Les vues

Étape 3.1 - Les blueprints : l'authentification

Un objet Blueprint a été conçu pour organiser vos vues en groupes et les enregistrer dans l'application. Nous allons créer un Blueprint pour les vues qui traitent de l'authentification et un autre pour l'affichage, l'édition et la suppression, de sorte que cette partie du tutoriel couvrira la gestion CRUD et la gestion des sessions.
En commençant par le Blueprint d'authentification, créez le fichier auth.py et importez les utilitaires de Flask et Werkzeug, la base de données et le modèle Utilisateur. Ensuite, instanciez l'objet Blueprint, en spécifiant son nom et le préfixe de l'URL.

# auth.pyfrom flask import (
    Blueprint, request, g, redirect, url_for, flash, render_template, session
)
from werkzeug.security import generate_password_hash, check_password_hash
from .database import db
from .models import User
import functools

bp = Blueprint('auth', __name__, url_prefix='/auth')

Ensuite, nous pouvons utiliser des décorateurs pour définir des chemins pour l'enregistrement, la connexion et la déconnexion avec toutes les méthodes nécessaires. Dans notre exemple, nous utiliserons GET et POST et commencerons par l'enregistrement.

Le décorateur est accessible comme une méthode de notre Blueprint et possède un argument pour l'URL et un autre pour les méthodes qu'il accepte. À la ligne suivante, créez la fonction indiquant à l'application ce qu'elle doit faire lorsqu'elle reçoit une requête avec les paramètres correspondants. Dans l'exemple suivant, nous aurons accès à la fonction en GETtant ou POSTant l'URL http://host:port/auth/register.
 

# auth.py@bp.route('/register', methods=('GET', 'POST'))defregister():if request.method=='POST':
        pass

Si l'application reçoit un POST renvoyant à '/register', cela signifie qu'un utilisateur souhaite créer un compte. L'accès au nom d'utilisateur et au mot de passe choisis avec l'objet de contenu de la demande est géré par Flask. Ensuite, créez une instance du modèle Utilisateur, en stockant ses propriétés en tant que valeurs reçues. Vous pouvez également utiliser les méthodes werkzeug.security pour protéger le contenu sensible. Ensuite, utilisez la propriété de session de notre base de données, fournie par SQLAlchemy, pour ajouter un nouvel utilisateur au tableau et le valider. À ce stade, les informations ont déjà été envoyées au tableau correspondant dans l'instance IRIS mentionnée à la dernière étape. Enfin, redirigez l'utilisateur vers la page de connexion, que nous créerons à l'étape suivante. N'oubliez pas de traiter les erreurs telles que la réception d'entrées vides, l'intégrité de l'index et la connexion à la base de données. Vous pouvez utiliser flash() pour afficher les détails du problème à l'utilisateur. Nous pouvons également utiliser la même fonction register() pour rendre le modèle de la page d'enregistrement si GET est utilisé dans la requête.

# auth.py@bp.route('/register', methods=('GET', 'POST'))defregister():if request.method=='POST':
        username = request.form['username']
        password = request.form['password']
        error = Noneifnot username:
            error = "Username is required."elifnot password:
            error = "Password is required."if error isNone:
            try:
                user = User(
                    username=username,
                    password=generate_password_hash(password)
                )

                db.session.add(user)
                db.session.commit()
            except Exception as err: # TODO: remplacer par l'équivalent db.IntegrityError de sqlite3
                error = str(err)
            else:
                return redirect(url_for("auth.login"))

        flash(error)

    return render_template('auth/register.html')

Ensuite, nous répétons la même logique pour la page de connexion. Lorsque nous traitons un POST, nous recevons d'abord un nom d'utilisateur et un mot de passe, puis nous vérifions s'ils existent dans notre tableau IRIS. Cette fois, nous n'utiliserons pas la propriété session (bien que nous aurions pu le faire). Nous utiliserons plutôt la méthode one_or_404(). Nous devons le faire parce que le nom d'utilisateur doit être unique, comme nous l'avons défini dans notre modèle. Ainsi, si la requête ne renvoie pas précisément une ligne suite à notre demande, nous pouvons la considérer comme non trouvée. À l'intérieur de cette fonction, enchaîner des commandes SQL pour trouver le résultat requis, en utilisant les modèles comme des tableaux et leurs propriétés comme des colonnes. Enfin, on efface la session de Flask, on ajoute l'utilisateur si la connexion a été effective et on le redirige vers la page d'accueil. S'il s'agit d'un GET, envoyer l'utilisateur à la page de connexion.

#auth.py@bp.route('/login', methods=('GET', 'POST'))deflogin():if request.method=="POST":
        username = request.form['username']
        password = request.form['password']
        error = Nonetry:
            user = db.one_or_404(db.select(User).where(User.username==username))

            ifnot check_password_hash(user.password, password):
                error = "Incorrect password"except Exception as err: # TODO vérifier également cette erreur
            error = f"User {username} not found."if error isNone:
            session.clear()
            session['user_id'] = user.id
            return redirect(url_for('index'))

        flash(error)


    return render_template('auth/login.html')
    

Pour se déconnecter, il suffit d'effacer la session et de rediriger vers la page d'accueil. 

# auth.py@bp.route('/logout')deflogout():
    session.clear()
    return redirect(url_for('index'))

Pour les fonctions suivantes, nous allons traiter l'utilisateur qui se connecte sur différentes requêtes, qui ne sont pas nécessairement liées à l'authentification. L'objet 'g' de Flask est unique pour chaque demande, ce qui signifie que vous pouvez l'utiliser pour définir l'utilisateur actuel à partir de la base de données lorsque vous traitez des accès éventuellement interdits.

Heureusement, le Blueprint possède la propriété before_app_request. Elle peut être utilisée comme décorateur suivi d'une fonction pour déterminer ce qu'il convient de faire lors de la réception d'une requête avant de la traiter. Pour charger l'utilisateur connecté, nous devons à nouveau nous connecter, mais cette fois, nous obtiendrons des informations à partir de la session autres que la requête.

# auth.py@bp.before_app_requestdefload_logged_in_user():
    user_id = session.get('user_id')

    if user_id isNone:
        g.user = Noneelse:
        g.user = db.one_or_404(
            db.select(User).where(User.id==user_id)
        )

Enfin, pour notre dernière fonction d'authentification, il est possible de créer quelque chose qui traitera les vues qui doivent être interdites si l'utilisateur ne s'est pas connecté. Si vous ne voulez pas afficher un simple code 403, redirigez l'utilisateur vers la page de connexion.

# auth.pydeflogin_required(view):    @functools.wraps(view)defwrapped_view(**kwargs):if session.get('user_id') isNone:
            return redirect(url_for('auth.login'))

        return view(**kwargs)

    return wrapped_view

Étape 3.2 - Les blueprints: blog (CRUD)

Là encore, commencez par créer le fichier (appelons-le blog.py) et y importer tout ce qui est nécessaire. Cela peut sembler beaucoup de travail, mais chacune de ces importations prendra tout son sens au fur et à mesure que nous avancerons. Créez également une autre instance de Blueprint.

# blog.pyfrom flask import (
    Blueprint, flash, g, redirect, render_template, request, url_for, session
)
from werkzeug.exceptions import abort
from .auth import login_required
from .database import db
from .models import Post, User
from sqlalchemy import desc

bp = Blueprint('blog', __name__)

Tout d'abord, il faut commencer par créer l'URL par défaut, qui pointe vers la page d'accueil. Ici, nous afficherons les messages stockés dans IRIS, de sorte que la propriété de session de la base de données (qui est une instance SQLAlchemy() de Flask-SQLAlchemy) puisse être utilisée. Il y a plusieurs façons d'exécuter une requête avec le module SQLAlchemy. J'ai choisi d'utiliser la fonction scalars() avec la requête SQL comme argument, enchaînée avec la fonction all(). Vous avez peut-être vu un comportement similaire dans d'autres langages et plugins comme fetchall(). La prochaine étape sera de rendre le modèle de la page d'accueil, en passant la variable où nous avons stocké tous les messages comme argument afin de pouvoir y accéder dans les fichiers HTML. Regardez notre READ (cRud) ci-dessous :

# blog.py@bp.route('/')defindex():
    posts = db.session.scalars(
        db.select(Post).order_by(desc(Post.created))
    ).all()

    return render_template('blog/index.html', posts=posts)

Ensuite, la fonction de création (Crud) d'un nouveau message sera très similaire à l'enregistrement d'un utilisateur. Nous obtenons l'entrée de l'utilisateur à partir de la requête, définissons ses valeurs en fonction des propriétés du modèle de message Post, utilisons l'objet session de la base de données pour faire un ajout (équivalent à l'instruction SQL d'insertion) et validons. Ici, nous allons accéder à l'utilisateur actuel à partir de la requête avec l'objet g. Nous utiliserons également un second décorateur, référençant la fonction login_required(), pour rediriger l'utilisateur vers la page de connexion s'il ne s'est pas encore connecté.

# blog.py@bp.route('/create', methods=('GET', 'POST'))@login_requireddefcreate():if request.method == 'POST':
        title = request.form['title']
        body = request.form['body']
        error = Noneifnot title:
            error = 'Title is required.'if error isnotNone:
            flash(error)
        else:
            post = Post(
                title=title,
                body=body,
                author_id=g.user.id
            )
            db.session.add(post)
            db.session.commit()

            return redirect(url_for('blog.index'))

    return render_template('blog/create.html')

Pour effectuer une mise à jour (crUd), nous utiliserons une approche très similaire. Cependant, au lieu de créer une nouvelle instance de Post, nous la renverrons à partir d'une requête, puisqu'elle existe déjà dans la base de données. Nous pouvons utiliser l'URL pour recevoir un identifiant pour le message (Post). Une fois de plus, le login est nécessaire ici.

# blog.py@bp.route('//update', methods=('GET', 'POST'))@login_requireddefupdate(id):
    post = get_post(id)

    if request.method == 'POST':
        title = request.form['title']
        body = request.form['body']
        error = Noneifnot title:
            error = 'Title is required.'if error isnotNone:
            flash(error)
        else:
            post.title = title
            post.body = body

            db.session.commit()

            return redirect(url_for('blog.index'))

    return render_template('blog/update.html', post=post)

Puisque pour la suppression il nous faudra également accéder au message (Post) par l'ID, nous pouvons créer une fonction get_post(id) et l'utiliser sur les deux vues. De la même manière que pour les autres requêtes que nous avons faites jusqu'à présent, nous pouvons enchaîner les commandes SQL pour l'écrire. Puisque notre modèle pour l'utilisateur a une relation, nous pouvons sélectionner le message Post et le joindre à Utilisateur (User). De cette manière, nous pourrons accéder aux informations de l'utilisateur par le biais de l'objet résultat. Nous pouvons également utiliser abort() pour envoyer des réponses 404 (non trouvé) et 403 (interdit).

# blog.pydefget_post(id, check_author=True):
    post = db.one_or_404(
        db.select(Post).join(User).where(Post.id == id)
    )

    if post isNone:
        abort(404, f"Post id {id} doesn't exist.")

    if check_author and post.author_id != session['user_id']:
        abort(403)

    return post

Enfin, vous pouvez effectuer la suppression en utilisant simplement une fonction de suppression de la session de la base de données.

# blog.py@bp.route('//delete', methods=('POST',))@login_requireddefdelete(id):
    post = get_post(id)

    db.session.delete(post)
    db.session.commit()

    return redirect(url_for('blog.index'))

 

Étape 3.3 - Enregistrer vos blueprints

Enfin, nous pouvons achever notre fabrique d'applications en enregistrant les blueprints et en renvoyant l'application, comme indiqué ci-dessous.

# __init__.pyfrom flask import Flask
from sqlalchemy.exc import DatabaseError

defcreate_app():# créer et configurer l'application
    app = Flask(__name__)

    app.config.from_mapping(
        SECRET_KEY="dev", # remplacer par un système aléatoire lors du déploiement
        SQLALCHEMY_DATABASE_URI = "iris://_SYSTEM:sys@localhost:1972/SAMPLE"
    )

    # avec cette application, flask initialise Alchemyfrom .database import db
    from .models import User, Post
    db.init_app(app)
    try:
        with app.app_context():
            db.create_all()
    except DatabaseError as err:
        if'already exists'in err._sql_message():
            print("Databases already exist.")
        else:
            print(err)

    # enregistrer les blueprintsfrom . import auth
    app.register_blueprint(auth.bp)

    from . import blog
    app.register_blueprint(blog.bp)
    app.add_url_rule('/', endpoint='index')

    return app

 

Étape 4 - Les modèles

Vous devez avoir remarqué que chaque vue se termine par un retour redirect(), qui envoie l'utilisateur vers une autre vue ou renvoie render_template(), ce qui est explicite. Ces fonctions reçoivent des modèles HTML comme arguments, ainsi que tout autre objet que vous pourriez vouloir utiliser à l'intérieur. Cela signifie qu'à ce stade du tutoriel, vous apprendrez à accéder à votre base de données IRIS à partir d'un fichier HTML, ce qui vous permettra de gérer les données avec CSS et JavaScript, et d'évoluer vers d'autres outils de développement web.

Le chemin par défaut de votre application Flask intégrée dans un paquetage pour rechercher des modèles se trouve dans le dossier app/templates. À l'intérieur de ce dossier, il est d'usage d'ajouter un fichier base.html. Le modèle de ces fichiers est le Jinja's. Cela signifie que vous pouvez utiliser {% ... %} pour ajouter des instructions telles que des blocs, des "ifs" (si) et des "fors" (pour), en les terminant toujours (avec {% endblock %}, par exemple). Vous pouvez également utiliser {{ ... }} pour des expressions telles que l'accès aux objets que vous avez passés dans la fonction de rendu du modèle. Enfin, vous pouvez utiliser {# ... #} pour les commentaires. 

Outre les arguments passés dans les vues, vous pouvez accéder à des contextes tels que l'objet g, qui est unique pour chaque requête, et get_flashed_messages() pour afficher les arguments passés dans flash(). Étant donné que nous avons déjà vu dans les étapes précédentes comment effectuer des requêtes et passer le résultat dans le contexte de la requête, l'exemple suivant pour un fichier de base vous montrera comment accéder aux données d'IRIS en utilisant l'objet g. En plus de cela, l'exemple montrera également comment accéder aux données d'IRIS en utilisant l'objet g. En outre, il montre également comment afficher les messages flashés.
 

<!-- templates/base.html --><!DOCTYPE html><title>{% block title %}{% endblock %} - Flaskr</title><linkrel="stylesheet"href="{{ url_for('static', filename='style.css') }}"><nav><h1>Flaskr</h1><ul>
        {% if g.user %}
          <li><span>{{ g.user['username'] }}</span><li><ahref="{{ url_for('auth.logout') }}">Log Out</a>
        {% else %}
          <li><ahref="{{ url_for('auth.register') }}">Register</a><li><ahref="{{ url_for('auth.login') }}">Log In</a>
        {% endif %}
    </ul></nav><sectionclass="content"><header>
        {% block header %}{% endblock %}
    </header>
    {% for message in get_flashed_messages() %}
      <divclass="flash">{{ message }}</div>
    {% endfor %}
    {% block content %}{% endblock %}
</section>

La base étant maintenant définie, vous pouvez enfin créer d'autres pages en agrandissant ce fichier et en personnalisant les blocs. Explorez l'exemple suivant pour la page Create Posts.

<!-- templates/blog/create.html -->
{% extends 'base.html' %}

{% block header %}
    <h1>{% block title %}New Post{% endblock %}</h1>
{% endblock %}

{% block content %}
    <formmethod="post"><labelfor="title">Title</label><inputname="title"id="title"value="{{ request.form['title'] }}"required><labelfor="body">Body</label><textareaname="body"id="body">{{ request.form['body'] }}</textarea><inputtype="submit"value="Save"></form>
{% endblock %}

Le paramètre "name" (nom) de l'entrée sera la clé du dictionnaire request.form, accessible dans les vues. Les pages d'authentification (login et register) ont une syntaxe assez similaire puisqu'elles ne nécessitent qu'un seul formulaire. Cependant, vous pouvez les consulter sur mon dépôt GitHub si vous le souhaitez.

Maintenant, regardons la page d'accueil, appelée index.html.

<!-- templates/blog/index.html -->
{% extends 'base.html' %}

{% block header %}
 <h1>{% block title %}Posts{% endblock %}</h1>
 {% if g.user %}
    <aclass="action"href="{{ url_for('blog.create') }}">New</a>
 {% endif %}
{% endblock %}

{% block content %}
    {% for post in posts %}
        <articleclass="post"><header><div><h1>{{ post['title'] }}</h1><divclass="about">by {{ post.user.username }} on {{ post['created'].strftime('%Y-%m-%d') }}</div></div>
                {% if g.user['id'] == post['author_id'] %}
                    <aclass="action"href="{{ url_for('blog.update', id=post['id'] )}}">Edit</a>
                {% endif%}
            </header><pclass="body">{{ post['body'] }}</p></article>
        {% if not loop.last %}
            <hr>
        {% endif %}
    {% endfor %}
{% endblock %}

Voici quelques éléments qui valent la peine d'être examinés de plus près. Le lien pour créer un nouveau message n'apparaît que lorsque g.user confirme le vrai, ce qui signifie qu'un utilisateur s'est connecté. Il y a également une instruction "for" (pour), itérant à travers l'objet posts renvoyé par une requête sur la vue pour l'URL vide "" et transmis à ce fichier en tant qu'argument de render_template(). Pour chaque message, nous pouvons accéder au nom d'utilisateur lié à chaque utilisateur avec une syntaxe simple (post.user.username), grâce à la db.relationship ajoutée dans le modèle utilisateur User. Une autre façon d'accéder aux données est d'utiliser les messages comme des dictionnaires et d'afficher leurs propriétés avec une syntaxe d'index (comme post['title']). Portez une attention particulière à la façon dont le lien de mise à jour d'un article prend son ID comme argument et n'apparaît que si son utilisateur correspond à celui qui est actuellement connecté.

Enfin, nous avons la page de mise à jour, qui renvoie simultanément à la vue de suppression. Elle est analogue à la page Créer des messages, mais elle possède une logique particulière pour définir les valeurs par défaut à partir de la base de données. Je laisserai une illustration ci-dessous au cas où vous décideriez de l'explorer.

<!-- templates/blog/udpate.html -->
{% extends 'base.html' %}

{% block header %}
    <h1>{% block title %}Edit "{{ post['title'] }}"{% endblock %}</h1>
{% endblock %}

{% block content %}
    <formmethod="post"><labelfor="title">Title</label><inputname="title"id="title"value="{{ request.form['title'] or post['title'] }}"required><labelfor="body">Body</label><textareaname="body"id="body">{{ request.form['body'] or post['body'] }}</textarea><inputtype="submit"value="Save"></form><hr><formaction="{{ url_for('blog.delete', id=post['id']) }}"method="post"><inputtype="submit"class="danger"value="Delete"onclick="return confirm('Are you sure?');"></form>
{% endblock %}

 

Partie 5 - Ajout de fichiers statiques

Nous disposons à présent d'une interface interactive qui reçoit des informations depuis l'utilisateur jusqu'à la base de données et vice-versa, ainsi que d'objets IRIS accessibles en Python à l'aide de SQLAlchemy et de modèles créés avec la syntaxe des classes. Dans la dernière étape, nous avons également réussi à transformer ces objets en contenu d'éléments HTML, ce qui signifie que vous pouvez facilement les personnaliser et interagir avec eux à l'aide de CSS et de JavaScript.

Si l'on revient à la base HTML, on remarque la ligne contenant un lien renvoyant à l'élément stylesheet, juste au début :

Le premier argument de la fonction url_for() à l'intérieur de l'expression href est le dossier à l'intérieur de la racine où vous pouvez trouver les statiques. Le second, bien sûr, est le nom du fichier. A partir de maintenant, le concept est à vous ! Créez le fichier style.css et modifiez-le à votre guise.

C'est fait !

Avec tous les fichiers que nous avons créés, le dossier racine devrait ressembler à l'image ci-dessous :

Maintenant, rendez-vous dans le répertoire racine du terminal et tapez

flask --app flaskr-iris run --debug

Suivez ensuite le lien http://127.0.0.1:5000 pour voir comment tout fonctionne.  

Conclusion

Dans cet article, nous avons vu comment Flask rend l'apport d'informations depuis une instance IRIS vers une application web possible et facile, en la transformant en un CRUD entièrement personnalisable, avec la possibilité de se connecter à d'autres bases de données et à d'autres systèmes. Désormais, vous pouvez facilement créer, afficher, mettre à jour et supprimer des informations d'une base de données IRIS, via une interface qui vous permet non seulement de personnaliser son apparence, ainsi que de traiter les données avec n'importe quel outil Python sans difficulté.

Vous pouvez me contacter pour me faire part de vos doutes ou des idées que vous avez rencontrées après la lecture ! Dans le prochain article, j'aborderai plus en détail la théorie de cette implémentation et présenterai l'application OpenExchange développée ici. En prime, je révèlerai quelques erreurs que j'ai rencontrées et les solutions que j'ai réussi à trouver dans la dernière section.

0
0 408
Article Pierre LaFay · Mars 3, 2024 5m read

Comme la plupart d'entre vous le savent probablement déjà, depuis environ la fin de l'année 2022, InterSystems IRIS a inclus la fonctionnalité de stockage en colonnes dans sa base de données. Dans l'article d'aujourd'hui, nous allons la mettre à l'épreuve en la comparant au stockage en lignes habituel.

Stockage en colonnes

Quelle est la principale caractéristique de ce type de stockage ? Eh bien, si l'on consulte la documentation officielle nous verrons ce fantastique tableau qui explique les principales caractéristiques des deux types de stockage (par lignes ou par colonnes) :

0
0 89
Article Sylvain Guilbaud · Fév 16, 2024 2m read

Pourquoi j'ai décidé d'écrire ceci

Dans mon dernier article, j'ai parlé du renvoi de valeurs avec Python. Mais les renvoyer est simple, ce qui peut rendre les choses plus difficiles, c'est ce dont je vais parler aujourd'hui : où la valeur est traitée.

Objet Python dans IRIS

En suivant l'exemple du dernier article, nous avons la méthode : objet dans IRIS

Class python.returnTest [ Abstract ]
{

ClassMethod returnSomething(pValue... As%String) As%Integer [ Language = python ]
{
	return pValue
}

}
0
0 65
Article Sylvain Guilbaud · Fév 15, 2024 4m read

Django, un framework Web de haut niveau écrit en Python, est devenu un incontournable pour les développeurs à la recherche d'une solution robuste, efficace et facile à apprendre pour créer des applications Web. Sa popularité vient de sa polyvalence, offrant aux développeurs une boîte à outils efficace pour créer des applications Web. L'intégration de Django avec InterSystems IRIS introduit une synergie dynamique, offrant aux développeurs une solution complète de développement Web et de gestion de bases de données. C'est pourquoi sur le Portail des Idées, @Evgeny Shvarov a suggéré qu'avoir des Exemples pour travailler avec IRIS et Django serait bénéfique. Dans cet article, nous explorerons deux projets créés pour répondre à l'idée publiée —Django-iris par @Dmitry Maslennikov et Iris-size-django par @Heloisa.Paiva.

   

0
0 53
Article Pierre LaFay · Fév 7, 2024 8m read

Introduction

Il n'y a pas si longtemps, j'ai vu l'idée de using Python Class Definition Syntax to create IRIS classes

sur le portail d'idées d'InterSystems. Elle a attiré mon attention car l'intégration d'un maximum de syntaxes donne de la visibilité aux produits d'InterSystems pour les programmeurs ayant de l'expérience dans de nombreux langages.

0
0 65
Article Sylvain Guilbaud · Fév 1, 2024 6m read

Bonjour La Communauté,

Le langage SQL reste le moyen le plus pratique pour récupérer de l'information stockée en base de données.

Le format JSON est très souvent utilisé dans les échanges de données.

Il est donc fréquent de chercher à obtenir des données au format JSON à partir de requêtes SQL.

Vous trouverez ci-dessous des exemples simples qui pourront vous aider à répondre à ce besoin à partir de code en ObjectScript et en Python.

ObjectScript : via le SQL dynamique avec %SQL.Statement + les structures JSON avec %DynamicObject et %DynamicArray

1
0 304
Article Sylvain Guilbaud · Jan 29, 2024 13m read

Nous avons un délicieux dataset avec des recettes écrites par plusieurs utilisateurs de Reddit, mais la plupart des informations sont du texte libre comme le titre ou la description d'un article. Voyons comment nous pouvons très facilement charger l'ensemble de données, extraire certaines fonctionnalités et l'analyser à l'aide des fonctionnalités du grand modèle de langage OpenAI contenu dans Embedded Python et le framework Langchain.

Chargement de l'ensemble de données

Tout d’abord, nous devons charger l’ensemble de données ou pouvons-nous simplement nous y connecter ?

Il existe différentes manières d'y parvenir : par exemple CSV Record Mapper vous pouvez utiliser dans une production d'interopérabilité ou même de belles applications OpenExchange comme csvgen.

Nous utiliserons Foreign Tables. Une fonctionnalité très utile pour projeter des données physiquement stockées ailleurs vers IRIS SQL. Nous pouvons l'utiliser pour avoir une toute première vue des fichiers de l'ensemble de données.

Nous créons un Foreign Server:

CREATE FOREIGN SERVER dataset FOREIGN DATA WRAPPER CSV HOST '/app/data/'

Et puis une table étrangère qui se connecte au fichier CSV:

CREATE FOREIGN TABLE dataset.Recipes (
  CREATEDDATE DATE,
  NUMCOMMENTS INTEGER,
  TITLE VARCHAR,
  USERNAME VARCHAR,
  COMMENT VARCHAR,
  NUMCHAR INTEGER
) SERVER dataset FILE 'Recipes.csv' USING
{
  "from": {
    "file": {
       "skip": 1
    }
  }
}

Et voilà, nous pouvons immédiatement exécuter des requêtes SQL sur dataset.Recipes: image

## De quelles données avons-nous besoin ? L’ensemble de données est intéressant et nous avons faim. Cependant, si nous voulons décider d'une recette à cuisiner, nous aurons besoin de plus d'informations que nous pourrons utiliser pour analyser.

Nous allons travailler avec deux classes persistantes (tables):

  • yummy.data.Recipe: une classe contenant le titre et la description de la recette et quelques autres propriétés que nous souhaitons extraire et analyser (par exemple Score, Difficulty, Ingredients, CuisineType, PreparationTime)
  • yummy.data.RecipeHistory: une classe simple pour enregistrer que faisons-nous avec la recette

Nous pouvons maintenant charger nos tables yummy.data* avec le contenu de l'ensemble de données:

do ##class(yummy.Utils).LoadDataset()

Cela a l'air bien, mais nous devons encore découvrir comment générer des données pour les champs Score, Difficulty, Ingredients, PreparationTime et CuisineType. ## Analyser les recettes Nous souhaitons traiter le titre et la description de chaque recette et :

  • Extraire des informations telles que Difficulté, Ingrédients, Type de Cuisine, etc.
  • Construire notre propre score en fonction de nos critères afin que nous puissions décider de ce que nous voulons cuisiner.

Nous allons utiliser ce qui suit :

  • yummy.analysis.Analysis - une structure d'analyse générique que nous pouvons réutiliser au cas où nous souhaiterions construire plus d'analyse.
  • yummy.analysis.SimpleOpenAI - une analyse qui utilise le modèle Embedded Python + Langchain Framework + OpenAI LLM.

LLM (large language models) sont vraiment un excellent outil pour traiter le langage naturel.

LangChainest prêt à fonctionner en Python, nous pouvons donc l'utiliser directement dans InterSystems IRIS en utilisant Embedded Python.

La classe complète SimpleOpenAI ressemble à ceci:

/// Analyse OpenAI simple pour les recettes
Class yummy.analysis.SimpleOpenAI Extends Analysis
{

Property CuisineType As %String;

Property PreparationTime As %Integer;

Property Difficulty As %String;

Property Ingredients As %String;

/// Run
/// Vous pouvez essayer ceci depuis un terminal :
/// set a = ##class(yummy.analysis.SimpleOpenAI).%New(##class(yummy.data.Recipe).%OpenId(8))
/// do a.Run()
/// zwrite a
Method Run()
{
    try {
        do ..RunPythonAnalysis()

        set reasons = ""

        // mes types de cuisine préférés
        if "spanish,french,portuguese,italian,korean,japanese"[..CuisineType {
            set ..Score = ..Score + 2
            set reasons = reasons_$lb("It seems to be a "_..CuisineType_" recipe!")
        }

        // je ne veux pas passer toute la journée à cuisiner :)
        if (+..PreparationTime < 120) {
            set ..Score = ..Score + 1
            set reasons = reasons_$lb("You don't need too much time to prepare it") 
        }
        
        // bonus pour les ingrédients préférés !
        set favIngredients = $listbuild("kimchi", "truffle", "squid")
        for i=1:1:$listlength(favIngredients) {
            set favIngred = $listget(favIngredients, i)
            if ..Ingredients[favIngred {
                set ..Score = ..Score + 1
                set reasons = reasons_$lb("Favourite ingredient found: "_favIngred)
            }
        }

        set ..Reason = $listtostring(reasons, ". ")

    } catch ex {
        throw ex
    }
}

/// Mettre à jour la recette avec les résultats de l'analyse
Method UpdateRecipe()
{
    try {
        // appeler d'abord l'implémentation de la classe parent
        do ##super()

        // ajouter des résultats d'analyse spécifiques à OpenAI
        set ..Recipe.Ingredients = ..Ingredients
        set ..Recipe.PreparationTime = ..PreparationTime
        set ..Recipe.Difficulty = ..Difficulty
        set ..Recipe.CuisineType = ..CuisineType

    } catch ex {
        throw ex
    }
}

/// Exécuter une analyse à l'aide de Embedded Python + Langchain
/// do ##class(yummy.analysis.SimpleOpenAI).%New(##class(yummy.data.Recipe).%OpenId(8)).RunPythonAnalysis(1)
Method RunPythonAnalysis(debug As %Boolean = 0) [ Language = python ]
{
    # load OpenAI APIKEY from env
    import os
    from dotenv import load_dotenv, find_dotenv
    _ = load_dotenv('/app/.env')

    # account for deprecation of LLM model
    import datetime
    current_date = datetime.datetime.now().date()
    # date after which the model should be set to "gpt-3.5-turbo"
    target_date = datetime.date(2024, 6, 12)
    # set the model depending on the current date
    if current_date > target_date:
        llm_model = "gpt-3.5-turbo"
    else:
        llm_model = "gpt-3.5-turbo-0301"

    from langchain.chat_models import ChatOpenAI
    from langchain.prompts import ChatPromptTemplate
    from langchain.chains import LLMChain

    from langchain.output_parsers import ResponseSchema
    from langchain.output_parsers import StructuredOutputParser

    # init llm model
    llm = ChatOpenAI(temperature=0.0, model=llm_model)

    # prepare the responses we need
    cuisine_type_schema = ResponseSchema(
        name="cuisine_type",
        description="What is the cuisine type for the recipe? \
                     Answer in 1 word max in lowercase"
    )
    preparation_time_schema = ResponseSchema(
        name="preparation_time",
        description="How much time in minutes do I need to prepare the recipe?\
                     Anwer with an integer number, or null if unknown",
        type="integer",
    )
    difficulty_schema = ResponseSchema(
        name="difficulty",
        description="How difficult is this recipe?\
                     Answer with one of these values: easy, normal, hard, very-hard"
    )
    ingredients_schema = ResponseSchema(
        name="ingredients",
        description="Give me a comma separated list of ingredients in lowercase or empty if unknown"
    )
    response_schemas = [cuisine_type_schema, preparation_time_schema, difficulty_schema, ingredients_schema]

    # get format instructions from responses
    output_parser = StructuredOutputParser.from_response_schemas(response_schemas)
    format_instructions = output_parser.get_format_instructions()
    
    analysis_template = """\
    Interprete and evaluate a recipe which title is: {title}
    and the description is: {description}
    
    {format_instructions}
    """
    prompt = ChatPromptTemplate.from_template(template=analysis_template)

    messages = prompt.format_messages(title=self.Recipe.Title, description=self.Recipe.Description, format_instructions=format_instructions)
    response = llm(messages)

    if debug:
        print("======ACTUAL PROMPT")
        print(messages[0].content)
        print("======RESPONSE")
        print(response.content)

    # populate analysis with results
    output_dict = output_parser.parse(response.content)
    self.CuisineType = output_dict['cuisine_type']
    self.Difficulty = output_dict['difficulty']
    self.Ingredients = output_dict['ingredients']
    if type(output_dict['preparation_time']) == int:
        self.PreparationTime = output_dict['preparation_time']

    return 1
}

}

La méthode RunPythonAnalysis c'est ici qu'entre en jeu OpenAI :). Vous pouvez le lancer directement depuis votre terminal pour une recette donnée :

do ##class(yummy.analysis.SimpleOpenAI).%New(##class(yummy.data.Recipe).%OpenId(12)).RunPythonAnalysis(1)

Nous obtiendrons un résultat comme celui-ci :

USER>do ##class(yummy.analysis.SimpleOpenAI).%New(##class(yummy.data.Recipe).%OpenId(12)).RunPythonAnalysis(1)
======ACTUAL PROMPT
                    Interprete and evaluate a recipe which title is: Folded Sushi - Alaska Roll
                    and the description is: Craving for some sushi but don't have a sushi roller? Try this easy version instead. It's super easy yet equally delicious!
[Video Recipe](https://www.youtube.com/watch?v=1LJPS1lOHSM)
# Ingredients
Serving Size:  \~5 sandwiches      
* 1 cup of sushi rice
* 3/4 cups + 2 1/2 tbsp of water
* A small piece of konbu (kelp)
* 2 tbsp of rice vinegar
* 1 tbsp of sugar
* 1 tsp of salt
* 2 avocado
* 6 imitation crab sticks
* 2 tbsp of Japanese mayo
* 1/2 lb of salmon  
# Recette     
* Place 1 cup of sushi rice into a mixing bowl and wash the rice at least 2 times or until the water becomes clear. Then transfer the rice into the rice cooker and add a small piece of kelp along with 3/4 cups plus 2 1/2 tbsp of water. Cook according to your rice cookers instruction.
* Combine 2 tbsp rice vinegar, 1 tbsp sugar, and 1 tsp salt in a medium bowl. Mix until everything is well combined.
* After the rice is cooked, remove the kelp and immediately scoop all the rice into the medium bowl with the vinegar and mix it well using the rice spatula. Make sure to use the cut motion to mix the rice to avoid mashing them. After thats done, cover it with a kitchen towel and let it cool down to room temperature.
* Cut the top of 1 avocado, then slice into the center of the avocado and rotate it along your knife. Then take each half of the avocado and twist. Afterward, take the side with the pit and carefully chop into the pit and twist to remove it. Then, using your hand, remove the peel. Repeat these steps with the other avocado. Dont forget to clean up your work station to give yourself more space. Then, place each half of the avocado facing down and thinly slice them. Once theyre sliced, slowly spread them out. Once thats done, set it aside.
* Remove the wrapper from each crab stick. Then, using your hand, peel the crab sticks vertically to get strings of crab sticks. Once all the crab sticks are peeled, rotate them sideways and chop them into small pieces, then place them in a bowl along with 2 tbsp of Japanese mayo and mix until everything is well mixed.
* Place a sharp knife at an angle and thinly slice against the grain. The thickness of the cut depends on your preference. Just make sure that all the pieces are similar in thickness.
* Grab a piece of seaweed wrap. Using a kitchen scissor, start cutting at the halfway point of seaweed wrap and cut until youre a little bit past the center of the piece. Rotate the piece vertically and start building. Dip your hand in some water to help with the sushi rice. Take a handful of sushi rice and spread it around the upper left hand quadrant of the seaweed wrap. Then carefully place a couple slices of salmon on the top right quadrant. Then place a couple slices of avocado on the bottom right quadrant. And finish it off with a couple of tsp of crab salad on the bottom left quadrant. Then, fold the top right quadrant into the bottom right quadrant, then continue by folding it into the bottom left quadrant. Well finish off the folding by folding the top left quadrant onto the rest of the sandwich. Afterward, place a piece of plastic wrap on top, cut it half, add a couple pieces of ginger and wasabi, and there you have it.

                    
Le résultat doit être un extrait de code de démarque formaté selon le schéma suivant, incluant les caractères "```json" and "```" :
json
{
        "cuisine_type": string  // Quel est le type de cuisine de la recette ? Réponse en 1 mot maximum en minuscule
        "preparation_time": integer  // De combien de temps en minutes ai-je besoin pour préparer la recette ? Répondez avec un nombre entier, ou nul si inconnu
        "difficulty": string  // À quel point cette recette est-elle difficile ? Répondez avec l'une de ces valeurs : facile, normal, difficile, très difficile
        "ingredients": string  // Donnez-moi une liste d'ingrédients séparés par des virgules en minuscules ou vide si inconnu
}

                    
======RESPONSE
json
{
        "cuisine_type": "japanese",
        "preparation_time": 30,
        "difficulty": "easy",
        "ingredients": "sushi rice, water, konbu, rice vinegar, sugar, salt, avocado, imitation crab sticks, japanese mayo, salmon"
}

Ça à l'air bon. Il semble que notre invite OpenAI soit capable de renvoyer des informations utiles. Exécutons toute la classe d'analyse depuis le terminal :

set a = ##class(yummy.analysis.SimpleOpenAI).%New(##class(yummy.data.Recipe).%OpenId(12))
do a.Run()
zwrite a
USER>zwrite a
a=37@yummy.analysis.SimpleOpenAI  ; <OREF>
+----------------- general information ---------------
|      oref value: 37
|      class name: yummy.analysis.SimpleOpenAI
| reference count: 2
+----------------- attribute values ------------------
|        CuisineType = "japanese"
|         Difficulty = "easy"
|        Ingredients = "sushi rice, water, konbu, rice vinegar, sugar, salt, avocado, imitation crab sticks, japanese mayo, salmon"
|    PreparationTime = 30
|             Reason = "It seems to be a japanese recipe!. You don't need too much time to prepare it"
|              Score = 3
+----------------- swizzled references ---------------
|           i%Recipe = ""
|           r%Recipe = "30@yummy.data.Recipe"
+-----------------------------------------------------

## Analyser toutes les recettes ! Naturellement, vous souhaitez exécuter l’analyse sur toutes les recettes que nous avons chargées.

Vous pouvez analyser une gamme d’identifiants de recettes de cette façon :

USER>do ##class(yummy.Utils).AnalyzeRange(1,10)
> Recipe 1 (1.755185s)
> Recipe 2 (2.559526s)
> Recipe 3 (1.556895s)
> Recipe 4 (1.720246s)
> Recipe 5 (1.689123s)
> Recipe 6 (2.404745s)
> Recipe 7 (1.538208s)
> Recipe 8 (1.33001s)
> Recipe 9 (1.49972s)
> Recipe 10 (1.425612s)

Après cela, regardez à nouveau votre tableau de recettes et vérifiez les résultats.

select * from yummy_data.Recipe

image

Je pense que je pourrais essayer la pizza à la courge poivrée ou le kimchi coréen au tofu et au porc :). De toute façon, je devrai vérifier à la maison :)

Notes finales

Vous pouvez trouver l'exemple complet sur https://github.com/isc-afuentes/recipe-inspector

Avec cet exemple simple, nous avons appris à utiliser les techniques LLM pour ajouter des fonctionnalités ou analyser certaines parties de vos données dans InterSystems IRIS.

Avec ce point de départ, vous pourriez penser à :

  • Utiliser InterSystems BI pour explorer et parcourir vos données à l'aide de cubes et de tableaux de bord.
  • Créer une application Web et fournir une interface utilisateur (par exemple Angular) pour cela, vous pouvez exploiter des packages tels que RESTForms2 pour générer automatiquement des API REST pour vos classes persistantes.
  • Et pourquoi garder en base l'information indiquant les recettes que vous aimez et celles que vous n'aimez pas, puis d'essayer de déterminer si une nouvelle recette vous plaira ? Vous pourriez essayer une approche IntegratedML, ou même une approche LLM fournissant des exemples de données et construisant un cas d'utilisation RAG (Retrieval Augmented Generation).

Quelles autres choses pourriez-vous essayer ? Laissez-moi savoir ce que vous pensez!

0
0 208