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 Pierre LaFay · Jan 20, 2024 9m read

Avec l'avènement d'Embedded Python, une myriade de cas d'utilisation sont désormais possibles depuis IRIS, directement en utilisant les librairies Python pour des opérations plus complexes. L'une de ces opérations consiste à utiliser des outils de traitement du langage naturel tels que la comparaison de similarités textuelles.

Configuration de Python intégré pour utiliser la librairie de transformateurs de phrases

0
0 170
Article Pierre LaFay · Déc 30, 2023 6m read

Cela semble être 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 en souvenez, IRIS était supérieur à PostgreSQL et clairement supérieur à MySQL en termes d'insertions, sans grande différence en termes de requêtes.

0
0 125
Article Guillaume Rongier · Déc 15, 2023 13m read

1. IRIS RAG Demo

IRIS RAG Demo

Ceci est une simple démo de l'IRIS avec un exemple de RAG (Retrieval Augmented Generation). Le backend est écrit en Python en utilisant IRIS et IoP, le modèle LLM est orca-mini et est servi par le serveur ollama. Le frontend est un chatbot écrit avec Streamlit.

1.1. Quest-ce que RAG?

RAG signifie Retrieval Augmented Generation, il permet d'utiliser un modèle LLM (GPT-3.5/4, Mistral, Orca, etc.) avec une base de connaissances.

Pourquoi est-ce important ? Parce que cela permet d'utiliser une base de connaissances pour répondre aux questions, et d'utiliser le LLM pour générer la réponse.

Par exemple, si vous demandez "Qu'est-ce que le module grongier.pex ?" directement au LLM, il ne pourra pas répondre, car il ne sait pas ce qu'est ce module (et peut-être que vous ne le savez pas non plus 🤪).

Mais si vous posez la même question à RAG, il pourra répondre, car il utilisera la base de connaissances qui sait ce qu'est le module grongier.pex pour trouver la réponse.

Maintenant que vous savez ce qu'est RAG, voyons comment cela fonctionne.

1.2. Comment ça marche?

Tout d'abord, nous devons comprendre comment fonctionne un LLM. Les LLM sont entraînés pour prédire le mot suivant, étant donné les mots précédents. Ainsi, si vous lui donnez une phrase, il essaiera de prédire le mot suivant, et ainsi de suite. Facile, non ?

Pour interagir avec un LLM, vous devez généralement lui donner une requête, et il générera le reste de la phrase. Par exemple, si vous lui donnez la requête Qu'est-ce que le module grongier.pex ?, il générera le reste de la phrase, et cela ressemblera à ceci :

Je suis désolé, mais je ne connais pas le module Pex que vous avez mentionné. Pouvez-vous fournir plus d'informations ou de contexte à ce sujet ?

Ok, comme prévu, il ne sait pas ce qu'est le module grongier.pex. Mais que se passe-t-il si nous lui donnons une requête qui contient la réponse ? Par exemple, si nous lui donnons la requête Qu'est-ce que le module grongier.pex ? C'est un module qui vous permet de faire X, Y et Z., il générera le reste de la phrase, et cela ressemblera à ceci :

Le module grongier.pex est un module qui vous permet de faire X, Y et Z.

Ok, maintenant il sait ce qu'est le module grongier.pex.

Mais que se passe-t-il si nous ne savons pas ce qu'est le module grongier.pex ? Comment pouvons-nous lui donner une requête qui contient la réponse ? Eh bien, c'est là que la base de connaissances entre en jeu.

RAG

L'idée de RAG est d'utiliser la base de connaissances pour trouver le contexte, puis d'utiliser le LLM pour générer la réponse.

Pour trouver le contexte, RAG utilisera un retriever. Le retriever recherchera la base de connaissances pour les documents les plus pertinents, puis RAG utilisera le LLM pour générer la réponse.

Pour rechercher la base de connaissances, nous utiliserons la recherche vectorielle.

La recherche vectorielle est une technique qui permet de trouver les documents les plus pertinents étant donné une requête. Elle fonctionne en convertissant les documents et la requête en vecteurs, puis en calculant la similarité cosinus entre le vecteur de la requête et les vecteurs des documents. Plus la similarité cosinus est élevée, plus le document est pertinent.

Pour plus d'informations sur la recherche vectorielle, vous pouvez consulter ce lien. Merci à @Dmitry Maslennikov pour son article.

Vector Search

Maintenant que nous savons comment fonctionne RAG, voyons comment l'utiliser.

1.3. Installation de la démo

Pour installer la démo, vous devez avoir Docker et Docker Compose installés sur votre machine.

Ensuite, il suffit de cloner le repo et d'exécuter la commande docker-compose up.

git clone https://github.com/grongierisc/iris-rag-demo
cd iris-rag-demo
docker-compose up

⚠️ tout est local, rien n'est envoyé dans le cloud, donc soyez patient, cela peut prendre quelques minutes pour démarrer.

1.4. Usage

Une fois la démo démarrée, vous pouvez accéder au frontend à l'adresse http://localhost:8501.

Frontend

Vous pouvez poser des questions sur l'IRIS, par exemple :

  • Qu'est-ce que le module grongier.pex ?

Question

Comme vous pouvez le voir, la réponse n'est pas très bonne, car le LLM ne sait pas ce qu'est le module grongier.pex.

Maintenant, essayons avec RAG :

Uploader la documentation du module grongier.pex, elle se trouve dans le dossier docs, fichier grongier.pex.md.

Ensuite, posez la même question :

  • Qu'est-ce que le module grongier.pex ?

Question

Comme vous pouvez le voir, la réponse est bien meilleure, car le LLM sait maintenant ce qu'est le module grongier.pex.

Vous pouvez voir les détails dans les logs :

Aller dans le portail de gestion à l'adresse http://localhost:53795/csp/irisapp/EnsPortal.ProductionConfig.zen?$NAMESPACE=IRISAPP&$NAMESPACE=IRISAPP& et cliquer sur l'onglet Messages.

Premièrement, vous verrez le message envoyé au processus RAG :

Message

Ensuite, la requête de recherche dans la base de connaissances (base de données vectorielle) :

Message

Et enfin la nouvelle requête envoyée au LLM :

Message

1.5. Comment fonctionne la démo ?

La démo est composée de 3 parties :

  • Le frontend, écrit avec Streamlit
  • Le backend, écrit avec Python et IRIS
  • La base de connaissances Chroma et la base de données vectorielle
  • Le LLM, Orca-mini, servi par le serveur Ollama

1.5.1. Le frontend

Le frontend est écrit avec Streamlit, c'est un simple chatbot qui vous permet de poser des questions.

Rien de bien compliqué ici, juste un simple chatbot.

import os
import tempfile
import time
import streamlit as st
from streamlit_chat import message

from grongier.pex import Director

_service = Director.create_python_business_service("ChatService")

st.set_page_config(page_title="ChatIRIS")


def display_messages():
    st.subheader("Chat")
    for i, (msg, is_user) in enumerate(st.session_state["messages"]):
        message(msg, is_user=is_user, key=str(i))


def process_input():
    if st.session_state["user_input"] and len(st.session_state["user_input"].strip()) > 0:
        user_text = st.session_state["user_input"].strip()
        with st.spinner(f"Thinking about {user_text}"):
            rag_enabled = False
            if len(st.session_state["file_uploader"]) > 0:
                rag_enabled = True
            time.sleep(1) # help the spinner to show up
            agent_text = _service.ask(user_text, rag_enabled)

        st.session_state["messages"].append((user_text, True))
        st.session_state["messages"].append((agent_text, False))


def read_and_save_file():

    for file in st.session_state["file_uploader"]:
        with tempfile.NamedTemporaryFile(delete=False,suffix=f".{file.name.split('.')[-1]}") as tf:
            tf.write(file.getbuffer())
            file_path = tf.name

        with st.spinner(f"Ingesting {file.name}"):
            _service.ingest(file_path)
        os.remove(file_path)

    if len(st.session_state["file_uploader"]) > 0:
        st.session_state["messages"].append(
            ("File(s) successfully ingested", False)
        )

    if len(st.session_state["file_uploader"]) == 0:
        _service.clear()
        st.session_state["messages"].append(
            ("Clearing all data", False)
        )

def page():
    if len(st.session_state) == 0:
        st.session_state["messages"] = []
        _service.clear()

    st.header("ChatIRIS")

    st.subheader("Upload a document")
    st.file_uploader(
        "Upload document",
        type=["pdf", "md", "txt"],
        key="file_uploader",
        on_change=read_and_save_file,
        label_visibility="collapsed",
        accept_multiple_files=True,
    )

    display_messages()
    st.text_input("Message", key="user_input", on_change=process_input)


if __name__ == "__main__":
    page()

💡 Je n'utilise que :

_service = Director.create_python_business_service("ChatService")

Pour créer un lien entre le frontend et le backend.

ChatService est un simple service métier dans la production d'interopérabilité.

1.5.2. Le backend

Le backend est écrit avec Python et IRIS.

Il est composé de 3 parties :

  • Le service métier
    • point d'entrée du frontend
  • Le processus métier
    • effectuer la recherche dans la base de connaissances si nécessaire
  • Deux opérations métier
    • Une pour la base de connaissances
      • Ingestion des documents
      • Recherche des documents
      • Effacer les documents
    • Une pour le LLM
      • Générer la réponse

1.5.2.1. Le business service

Le service métier est un simple service métier qui permet :

  • D'uploader des documents
  • De poser des questions
  • De vider la base de données vectorielle
from grongier.pex import BusinessService

from rag.msg import ChatRequest, ChatClearRequest, FileIngestionRequest

class ChatService(BusinessService):

    def on_init(self):
        if not hasattr(self, "target_chat"):
            self.target_chat = "ChatProcess"
        if not hasattr(self, "target_vector"):
            self.target_vector = "VectorOperation"

    def ingest(self, file_path: str):
        # build message
        msg = FileIngestionRequest(file_path=file_path)
        # send message
        self.send_request_sync(self.target_vector, msg)

    def ask(self, query: str, rag: bool = False):
        # build message
        msg = ChatRequest(query=query)
        # send message
        response = self.send_request_sync(self.target_chat, msg)
        # return response
        return response.response

    def clear(self):
        # build message
        msg = ChatClearRequest()
        # send message
        self.send_request_sync(self.target_vector, msg)

Si vous regardez le code, vous verrez que le service métier est très simple, il ne fait que passer entre l'opération et le processus.

1.5.2.2. Le business process

Le processus métier est aussi un simple processus qui permet de rechercher la base de connaissances si nécessaire.

from grongier.pex import BusinessProcess

from rag.msg import ChatRequest, ChatResponse, VectorSearchRequest

class ChatProcess(BusinessProcess):
    """
    the aim of this process is to generate a prompt from a query
    if the vector similarity search returns a document, then we use the document's content as the prompt
    if the vector similarity search returns nothing, then we use the query as the prompt
    """
    def on_init(self):
        if not hasattr(self, "target_vector"):
            self.target_vector = "VectorOperation"
        if not hasattr(self, "target_chat"):
            self.target_chat = "ChatOperation"

        # prompt template
        self.prompt_template = "Given the context: \n {context} \n Answer the question: {question}"


    def ask(self, request: ChatRequest):
        query = request.query
        prompt = ""
        # build message
        msg = VectorSearchRequest(query=query)
        # send message
        response = self.send_request_sync(self.target_vector, msg)
        # if we have a response, then use the first document's content as the prompt
        if response.docs:
            # add each document's content to the context
            context = "\n".join([doc['page_content'] for doc in response.docs])
            # build the prompt
            prompt = self.prompt_template.format(context=context, question=query)
        else:
            # use the query as the prompt
            prompt = query
        # build message
        msg = ChatRequest(query=prompt)
        # send message
        response = self.send_request_sync(self.target_chat, msg)
        # return response
        return response

Comme je le disais, le processus est très simple, il ne fait que passer entre l'opération et le processus.

Si la recherche vectorielle retourne des documents, alors il utilisera le contenu des documents comme prompt, sinon il utilisera la requête comme prompt.

1.5.2.3. L'opération LLM

L'opération LLM est une simple opération qui permet de générer la réponse.


class ChatOperation(BusinessOperation):

    def __init__(self):
        self.model = None

    def on_init(self):
        self.model = Ollama(base_url="http://ollama:11434",model="orca-mini")

    def ask(self, request: ChatRequest):
        return ChatResponse(response=self.model(request.query))

Difficile de faire plus simple, non ?

1.5.2.4. L'opération vectorielle

L'opération vectorielle est une opération qui permet d'ingérer des documents, de rechercher des documents et de vider la base de données vectorielle.


class VectorOperation(BusinessOperation):

    def __init__(self):
        self.text_splitter = None
        self.vector_store = None

    def on_init(self):
        self.text_splitter = RecursiveCharacterTextSplitter(chunk_size=1024, chunk_overlap=100)
        self.vector_store = Chroma(persist_directory="vector",embedding_function=FastEmbedEmbeddings())

    def ingest(self, request: FileIngestionRequest):
        file_path = request.file_path
        file_type = self._get_file_type(file_path)
        if file_type == "pdf":
            self._ingest_pdf(file_path)
        elif file_type == "markdown":
            self._ingest_markdown(file_path)
        elif file_type == "text":
            self._ingest_text(file_path)
        else:
            raise Exception(f"Unknown file type: {file_type}")

    def clear(self, request: ChatClearRequest):
        self.on_tear_down()

    def similar(self, request: VectorSearchRequest):
        # do a similarity search
        docs = self.vector_store.similarity_search(request.query)
        # return the response
        return VectorSearchResponse(docs=docs)

    def on_tear_down(self):
        docs = self.vector_store.get()
        for id in docs['ids']:
            self.vector_store.delete(id)
        
    def _get_file_type(self, file_path: str):
        if file_path.lower().endswith(".pdf"):
            return "pdf"
        elif file_path.lower().endswith(".md"):
            return "markdown"
        elif file_path.lower().endswith(".txt"):
            return "text"
        else:
            return "unknown"

    def _store_chunks(self, chunks):
        ids = [str(uuid.uuid5(uuid.NAMESPACE_DNS, doc.page_content)) for doc in chunks]
        unique_ids = list(set(ids))
        self.vector_store.add_documents(chunks, ids = unique_ids)
        
    def _ingest_text(self, file_path: str):
        docs = TextLoader(file_path).load()
        chunks = self.text_splitter.split_documents(docs)
        chunks = filter_complex_metadata(chunks)

        self._store_chunks(chunks)
        
    def _ingest_pdf(self, file_path: str):
        docs = PyPDFLoader(file_path=file_path).load()
        chunks = self.text_splitter.split_documents(docs)
        chunks = filter_complex_metadata(chunks)

        self._store_chunks(chunks)

    def _ingest_markdown(self, file_path: str):
        # Document loader
        docs = TextLoader(file_path).load()

        # MD splits
        headers_to_split_on = [
            ("#", "Header 1"),
            ("##", "Header 2"),
        ]

        markdown_splitter = MarkdownHeaderTextSplitter(headers_to_split_on=headers_to_split_on)
        md_header_splits = markdown_splitter.split_text(docs[0].page_content)

        # Split
        chunks = self.text_splitter.split_documents(md_header_splits)
        chunks = filter_complex_metadata(chunks)

        self._store_chunks(chunks)

Si vous regardez le code, vous verrez que l'opération vectorielle est un peu plus complexe que les autres. Les raisons sont les suivantes :

  • Nous devons ingérer des documents
  • Nous devons rechercher des documents
  • Nous devons vider la base de données vectorielle

Pour ingérer des documents, nous devons d'abord les charger, puis les diviser en morceaux, puis les stocker dans la base de données vectorielle.

Le processus de diviser est important, car cela permettra à la recherche vectorielle de trouver les documents les plus pertinents.

Par exemple, si nous avons un document qui contient 1000 mots, et que nous le divisons en 10 morceaux de 100 mots, alors la recherche vectorielle pourra trouver les documents les plus pertinents, car elle pourra comparer les vecteurs de la requête avec les vecteurs des morceaux.

Dans le cas des markdowns, nous utilisons également les en-têtes pour diviser le document en morceaux.

1.6. Remarques générales

Tout cela peut être fait avec langchains, mais je voulais vous montrer comment le faire avec le framework d'interopérabilité. Et le rendre plus accessible à tous pour comprendre comment le principe des RAG fonctionne.

2
0 566
Question Laurent Jouron · Déc 11, 2023

Bonjour,

Je suis en train de faire un test avec django sur notre base de données. Le but étant de créer une API pour voir la réactivité de celle-ci.

J'ai créé le projet sans problème, j'ai réussi à créer les 16 000 lignes de models avec inspect_db. Tout se passait bien jusqu'au moment où j'ai voulu testé mon projet, 'Accès refusé'.

De ce que j'ai pu voir, Il n'y aurait pas d'erreur de code, car les seules erreurs retournées sont les suivantes.

Je ne pense pas que ce soit un problème majeur, plutôt un manque de pratique sur votre système. Auriez-vous une idée à me soumettre?

3
0 116
Question Cyril Grosjean · Déc 12, 2023

Bonjour,

Nous avons voulu mettre en pré-production un flux 100% python, cependant lorsqu'on a lancé la commande iop pour tester avec l'utilisateur qui va déployer via une CI/CD le code, voici ce qu'on obtient:

ssh.gitlab est notre utilisateur, nous sommes sur un serveur Windows 10 et ssh.gitlab est admin.

Y a-t-il des modifications à faire sur le portail d'IRIS ? (Un utilisateur est créé pour la CI/CD suite à ce post)

Merci d'avance !

Cordialement,

Cyril

2
0 65
Question Cyril Grosjean · Déc 11, 2023

Bonjour,

En souhaitant créer une production qui récupère un fichier provenant d'une API et qui envoie ce fichier sur un serveur SFTP, j'ai rencontré un problème avec la librairie de Guillaume Rongier.

Je reçois des bytes depuis une opération jusque là aucun problème, j'ai lié un SFTP à mon opération, les credentials sont les bons, l'adresse ip, le port, le dossier dans lequel déposer le fichier également. Cependant j'ai cette erreur:

ERROR <Ens>ErrException: <METHOD DOES NOT EXIST>PutStream+11^EnsLib.FTP.OutboundAdapter.1 *Rewind,%SYS.Python -- logged as '-'
number - @''

Voici mon code:

1
0 76
Question Cyril Grosjean · Déc 4, 2023

Bonjour,

Je suis toujours en train de préparer le terrain pour inclure la librairie de Guillaume Rongier pour utiliser InterSystems entièrement via Python. Cependant notre environnement local tourne sur Docker avec l'OS linux, jusque là nous n'avons aucun problème avec la librairie. Le problème arrive lorsque l'on passe sur la pré-production où iris est installé directement sur Windows. J'installe les librairies requises avec le package pip et un fichier requirements.txt contenant ceci:
 

5
0 92
Article Niels Genne · Nov 24, 2023 4m read

Comment déployer les productions IRIS sereinement et plus rapidement ?

L'objectif des productions d'interopérabilité est de vous permettre de connecter des systèmes afin de transformer et d'acheminer des messages entre eux. Pour connecter des systèmes, vous développez, configurez, déployez et gérez des productions qui intègrent plusieurs solutions.

C’est ce que nous dit la documentation InterSystems sur son site de référence, mais que faut-il réellement faire pour déployer une production ?

On peut composer les productions, selon les usages, pour connecter des systèmes externes à IRIS Data Platform. Il est pour cela nécessaire de créer un environnement propre à chaque production à savoir les composants suivants :

  • un Business service 📨
  • un Business processus (facultatif) 📘
  • une Business opération 💉
  • des schémas de définition de table (.cls; classes) 📅
  • un fichier d’initialisation d’espace de noms (.cpf) 📋

Bien sûr l’importance d’employer les productions pour traiter les messages réside dans le fait de pouvoir tracer chaque message et remonter ainsi la trace des accidents de parcours de chaque événement indésirable.

Et si je vous disais qu’il est possible de déployer vos productions à l’aide de notre framework d’interopérabilité IRIS d’un coup de baguette magique ?🪄

Explications

L’approche mainframe de laquelle provient notre framework permet de déployer à vitesse grand « V » des productions IRIS InterSystems® sans devoir recréer tous les composants à la main.

L’emploi du framework permet d’ajouter une fonctionnalité intéressante autorisant la lecture des données des tables à déployer avec la production : l’ajout d’une API sortante (RestForms2).

Sounds good :)

➡️Les données deviennent ainsi interrogeables et restituables dans un format JSON.

Le framework va générer tous les composants en se basant sur un fichier de spécifications fonctionnelles rempli en accord avec le métier et notre chef de projet (dont le rôle est de veiller à ce que chaque information nécessaire trouve sa place).

Le script agit en deux temps à savoir : la construction du flux ETL et du point de chute des données. 📦🪂

Une fois rempli conformément à ce qui est attendu, le fichier de spécifications fonctionnelles permet de générer dans un premier temps : le fichier de sérialisation des messages (classes de données ; obj.py), le fichier de structure des données dans chaque message (msg.py), le fichier de génération de messages (bs.py) et le fichier d’ingestion des messages dans la base de données correspondante (bo.py); dans un second temps : il sert à créer/supprimer les tables dans la base de données sous forme d’un script SQL comprenant des instructions DDL (Data Definition Language).

De quoi vous faire gagner beaucoup de temps ! ⌚ Le plus beau dans tout cela, c’est que le framework est déployable facilement depuis un container Docker ! 🐳

Intérêts

Toujours pas convaincu(e) ? En quoi utiliser ce framework vous ferait gagner 80% du temps ?⏱️

Et si je vous disais que le code déployé par le framework est validé par l’éditeur InterSystems®, qu’il permet à votre équipe de travailler sur du code uniformisé, que lors de campagnes de maintenance cette possibilité vous incite à être plus efficace en cas de mise à jour de code ou de recherche de bug, qu’il vous permet d’interagir avec vos données à l’aide d’un mécanisme de REST API (issu du dépôt des paquets compatibles InterSystems IRIS toutes versions confondues). Cela fait sens pour vous ? 👌

Qu’entend-on par « le code est validé par l’éditeur » ? ✅ Simplement qu’il respecte les standards Python et ceux de l’éditeur en matière d’architecture, d’appels aux mécanismes internes d’IRIS InterSystems® également qu’il sait s’interfacer avec le langage ObjectScript et vice versa.

La suite

Si cet article fait écho à vos besoins ou si vous êtes simplement curieux de voir comment ce framework pourrait révolutionner votre manière de travailler sous IRIS InterSystems® : rendez vous sur notre site web et/ou demander un accès à notre Discord Server pour discuter avec l’un de nos experts.

Suivez-nous aussi sur LinkedIn profile

Prochainement, nous vous ferons découvrir un cas d’usage du framework en environnement opérationnel 😉 !

0
0 105
Question Augustin MADET · Oct 24, 2023
import os

# Récupérer les variables d'environnement
db_host = os.getenv('DB_HOST')
db_port = os.getenv('DB_PORT')
db_namespace = os.getenv('DB_NAMESPACE')
db_username = os.getenv('DB_USERNAME')
db_password = os.getenv('DB_PASSWORD')

# Créer une connexion à la base de données
conn = irisnative.createConnection(db_host, db_port, db_namespace, db_username, db_password)

# Créer une instance IRIS à partir de cette connexion
iris_native = irisnative.createIris(conn)

status = iris_native.classMethodValue('%SYSTEM.OBJ', 'Load', 'Production.cls', 'ck')

if status == 1:
    
4
1 106
Question Laurent Jouron · Oct 24, 2023

Est-il possible d'utiliser Django avec InterSystems Iris, dont les classes sont implémentées en ObjectScript ?

Class EpErp.ARTICLES Extends (%Persistent, %JSON.Adaptor, %XML.Adaptor) [ ClassType = persistent, SqlTableName = ARTICLES ]

{

Parameter IDPROPERTY = "IDARTICLES";

Property IDARTICLES As %BigInt [ SqlColumnNumber = 2, SqlFieldName = IDARTICLES ];

Property dhDateCreation As %TimeStamp [ SqlColumnNumber = 3, SqlFieldName = dhDateCreation ];

Property sCode As %String(MAXLEN = 30) [ SqlColumnNumber = 4, SqlFieldName = sCode ];

7
0 108
Article Lorenzo Scalese · Oct 4, 2023 8m read

Description du cas

Imaginons que vous soyez un développeur en Python ou que vous disposiez d'une équipe bien formée et spécialisée en Python, mais que le délai qui vous est imparti pour analyser certaines données dans IRIS soit serré. Bien sûr, InterSystems offre de nombreux outils pour toutes sortes d'analyses et de traitements. Cependant, dans le scénario donné, il est préférable de faire le travail en utilisant le bon vieux Pandas et de laisser IRIS pour une autre fois.
    Dans la situation décrite ci-dessus et dans bien d'autres cas, il se peut que vous souhaitiez extraire des tables d'IRIS pour gérer des données en dehors des produits d'InterSystems. Cependant, vous pouvez également avoir besoin de faire les choses dans l'autre sens lorsque vous avez un tableau externe dans n'importe quel format, à savoir CSV, TXT ou Pickle, que vous avez besoin d'importer et d'utiliser les outils d'IRIS sur celui-ci.
Indépendamment du fait que vous ayez ou non à faire face à un problème décrit ci-dessus, Innovatium m'a appris qu'il est toujours utile de connaître plusieurs façons de résoudre un problème de codage. La bonne nouvelle, c'est qu'il n'est pas nécessaire de passer par le processus laborieux de création d'un nouveau tableau, de transfert de toutes les lignes et d'ajustement de chaque type lorsque l'on importe un tableau d'IRIS.
    Cet article vous montrera comment transformer rapidement un tableau IRIS en une trame de données Pandas et inversement avec seulement quelques lignes de code. Vous pouvez consulter le code à partir de mon GitHub, où vous trouverez Jupiter Notebook avec toutes les étapes de ce tutoriel.

Importation du tableau d'IRIS

Bien entendu, vous devez commencer par importer les bibliothèques nécessaires à ce projet.

import pandas as pd
import sqlalchemy as db

L'étape suivante consiste à créer la connexion entre le fichier Python et l'instance IRIS. Pour ce faire, nous utiliserons la fonction create_engine() de SQLAlchemy, avec une chaîne en tant que paramètre. Cette chaîne doit contenir des informations sur le dialecte de l'opération, le nom d'utilisateur et le mot de passe, l'hôte et le port de l'instance, ainsi que l'espace de noms de destination. Pour plus d'informations sur les concepts de base de l'utilisation de sqlalchemy-iris, consultez l'un de mes articles précédents, SQLAlchemy - la façon la plus simple d'utiliser Python et SQL avec les bases de données d'IRIS.

engine = db.create_engine("iris://_system:SYS@localhost:1972/SAMPLE")
connection = engine.connect()

Ensuite, nous pouvons déclarer une variable qui contiendra la trame de données et appeler la fonction read_sql_table() de Pandas sur cette connexion, en spécifiant le nom du tableau sous la forme d'une chaîne avec le schéma. Vous pouvez également indiquer le schéma dans un autre argument, ce qui est, en fait, préférable car le fait d'avoir un point sur la chaîne de nom peut provoquer des erreurs dans certains cas.

df = pd.read_sql_table("NameAge", connection, schema="PD")

C'est une bonne pratique de vérifier si le tableau avec lequel nous travaillons existe dans le schéma que nous voulons utiliser et, bien sûr, s'il y a un schéma dont nous avons besoin au départ. Dans la dernière section de cet article, vous apprendrez comment le faire ainsi que quelques autres conseils. À partir de maintenant, si vous avez l'habitude d'utiliser Pandas, vous pouvez effectuer toutes les modifications et analyses que vous souhaitez puisque vous savez quoi faire. Explorez l'exemple suivant pour voir comment cela fonctionne.

Envoi d'un tableau à IRIS

Avant de commencer, modifions un peu notre trame de données, à titre d'exemple. Nous pouvons adapter les valeurs d'une colonne à nos besoins (par exemple, ajouter des lignes et des colonnes, etc.). Après avoir joué un peu, j'ai mis les noms en minuscules et j'ai ajouté une nouvelle personne et une colonne sur la base des données existantes. Vous pouvez consulter l'illustration suivante pour voir le résultat.

Nous pouvons maintenant le renvoyer à IRIS par une seule ligne de code. Il suffit de spécifier le moteur et le nom du tableau.

df.to_sql("NameAge", con=engine, schema="PD", if_exists="replace")

Une fois de plus, nous devons placer le schéma dans un argument séparément du nom du tableau afin d'éviter certaines erreurs et un comportement indésirable. En outre, l'argument if_exists spécifie ce qu'il faut faire s'il existe déjà un tableau portant le même nom dans le schéma concerné. Les valeurs possibles sont : "replace" (remplacer), "fail" (échouer, la valeur par défaut) et "append" (ajouter). Bien entendu, l'option "replace" supprime le tableau et en crée un nouveau à l'aide d'une commande SQL, tandis que l'option "append" ajoute les données au tableau existant. N'oubliez pas que cette méthode ne vérifie pas les valeurs répétées ; soyez donc prudent lorsque vous utilisez cet attribut. Enfin, la valeur "fail" provoque l'erreur suivante :

Gardez à l'esprit que si vous spécifiez un nom de tableau qui n'existe pas, la fonction le créera.

Vous pouvez maintenant lancer une requête dans IRIS pour voir ce qu'il y a de nouveau ou aller dans le Portail de gestion dans la partie consacrée à SQL. N'oubliez pas que si vous avez utilisé la valeur "replace", vous devez consulter le code source de la classe, car la méthode la réécrit complètement. Cela signifie que si vous avez implémenté des méthodes, vous devez les laisser dans une superclasse.

Plus d'astuces sur sqlalchemy-iris

Si vous rencontrez quelques problèmes que vous n'avez pas pu résoudre avec les informations partagées dans d'autres communautés ou forums liés au code de votre application, vous trouverez peut-être ici l'aide dont vous avez besoin. Vous y découvrirez une liste d'astuces pour trouver des détails sur le moteur et le dialecte.

Caractéristiques spécifiques au dialecte

SQL Alchemy travaille avec des dialectes qui sont automatiquement choisis en fonction de votre moteur. Lorsque vous utilisez la fonction create_engine() pour vous connecter à une base de données IRIS, le dialecte choisi est sqlalchemy-iris by Dmitry Maslennikov.
Vous pouvez accéder à ses caractéristiques et les modifier à l'aide de la propriété de dialecte de votre moteur.

engine = db.create_engine("iris://_system:SYS@localhost:1972/SAMPLE")
engine.dialect

Avec l'extension IntelliCode de VSCode, vous pouvez rechercher toutes les options de cette propriété ou consulter le code source sur CaretDev's GitHub.

Vérification des schémas disponibles dans un moteur

Une fonction spéciale du dialecte digne d'être soulignée est la fonction get_schema_names(). Faites attention ! Les informations suivantes peuvent être cruciales pour vous si vous voulez éviter les erreurs dans votre code et pour l'itération.

connection = engine.connect()
engine.dialect.get_schema_names(connection)

 

Vérification des tableaux disponibles dans un schéma

Examinons une situation semblable. Il se peut que vous ayez besoin de connaître les tableaux disponibles à partir d'un schéma. Dans ce cas, vous pouvez utiliser l'inspection. Exécutez la fonction inspect() sur le moteur et enregistrez-la dans une variable. Cette même variable sera utilisée pour accéder à une autre fonction, get_table_names(). Elle renverra une liste des noms de tableaux dans le schéma spécifié ou dans la valeur par défaut "SQLUser".

inspection = db.inspect(engine)
inspection.get_table_names(schema="Sample")

De plus, si vous souhaitez utiliser plus de fonctionnalités de SQL Alchemy sur vos données, vous pouvez déclarer une base et faire en sorte que ses métadonnées reflètent un schéma du moteur.

b = db.orm.declarative_base()
b.metadata.reflect(engine, schema="Sample")

Si vous avez besoin de plus d'informations pour résoudre ce problème, consultez la Documentation SQL Alchemy et le dépôt sqlalchemy-iris GitHub Repository. Vous pouvez également m'envoyer un message ou laisser un commentaire, et nous essaierons de découvrir le secret ensemble.

Considérations finales

L'approche de mise en œuvre présentée dans cet article met l'accent sur l'utilisation des instances IRIS en tant que fournisseurs de services en nuage et permet d'effectuer une analyse sur différentes bases. Elle facilite la surveillance simultanée de toutes les instances dans toutes leurs qualités et la comparaison de leurs performances et de leur utilisation. Si vous combinez ces connaissances avec le développement décrit dans un autre article à propos d'un portail réalisé avec Django, vous pouvez rapidement construire un gestionnaire puissant pour autant de fonctionnalités et d'instances que vous le souhaitez.
    Cette implémentation est également un moyen efficace de transférer des données de l'extérieur d'IRIS vers une classe bien construite. Comme vous êtes peut-être familier avec d'autres fonctions trouvées dans Pandas pour traiter de nombreux langages différents, à savoir CSV, JSON, HTML, Excel et Pickle, il vous sera facile de changer read_sql\table en read_csv, read_json, ou toute autre option. Certes, je dois vous avertir que l'intégration avec InterSystems à partir de certains types de données n'est pas une fonctionnalité intégrée et qu'elle n'est donc pas toujours facile à mettre en œuvre. Cependant, l'union de SQL Alchemy et de Pandas sera toujours utile pour exporter des données depuis IRIS.
    Ainsi, dans cet article, nous avons appris qu'IRIS possède tous les outils dont vous avez besoin pour vous aider à développer et à intégrer facilement les dispositifs existants de votre système ou les dispositifs de votre domaine d'expertise.
 

0
0 146
Article Sylvain Guilbaud · Oct 2, 2023 13m read

Pour le prochain Concours Python, j'aimerais faire une petite démo, sur la création d'une simple application REST en Python, qui utilisera IRIS comme base de données. Et utiliser les outils suivants

  • Le cadre FastAPI, très performant, facile à apprendre, rapide à coder, prêt pour la production.
  • SQLAlchemy est la boîte à outils SQL et le Mapping objet-relationnel de Python qui donne aux développeurs en Python toute la puissance et la flexibilité de SQL.
  • Alembic est un outil léger de migration de base de données à utiliser avec le SQLAlchemy Database Toolkit pour Python.
  • Uvicorn est une implémentation de serveur web ASGI pour Python.

Préparation de l'environnement

En supposant que Python soit déjà installé, au moins en version 3.7., il faut créer un dossier de projet, et y créer un fichier requirements.txt avec le contenu suivant

fastapi==0.101.1
alembic==1.11.1
uvicorn==0.22.0
sqlalchemy==2.0.20
sqlalchemy-iris==0.10.5

 Je vous conseille d'utiliser l'environnement virtuel en python, nous allons créer un nouvel environnement et l'activer.

python -m venv env && source env/bin/activate

Et maintenant, nous pouvons installer nos dépendances

pip install -r requirements.txt

Démarrage rapide

Créons l'Api REST la plus simple avec FastAPI. Pour ce faire, créons app/main.py

from fastapi import FastAPI

app = FastAPI(
    title='TODO Application',
    version='1.0.0',
)

@app.get("/ping")asyncdefpong():return {"ping": "pong!"}

Pour l'instant, il suffit de démarrer notre application, et elle devrait déjà fonctionner. Pour démarrer le serveur, nous allons utiliser uvicorn

$ uvicorn app.main:app         
INFO:     Processus de serveur lancé [94936]
INFO:     En attente du lancement de l'application.
INFO:     Application startup compléte.
INFO:     Uvicorn fonctionne sur http://127.0.0.1:8000 ( Appuyez sur CTRL+C pour quitter)

Et nous pouvons soumettre une requête de ping.

$ curl http://localhost:8000/ping
{"ping":"pong!"}

FastAPI propose une interface utilisateur permettant de tester l'API.

Environnement Dockerisé

Pour ajouter IRIS à notre application, nous allons utiliser des conteneurs. L'image d'IRIS sera utilisée telle quelle, mais il nous faut construire une image Docker pour l'application python. Et nous aurons besoin de Dockerfile

FROM python:3.11-slim-buster

WORKDIR /usr/src/app
RUN --mount=type=bind,src=.,dst=. \
    pip install --upgrade pip && \
    pip install -r requirements.txt
COPY . .
ENTRYPOINT [ "/usr/src/app/entrypoint.sh" ]

Pour lancer l'application à l'intérieur du conteneur, il faut un simple entrypoint.sh.

#!/bin/sh
# Exécution des migrations SQL, pour mettre à jour le schéma de la base de données
alembic upgrade head

# Lancement de l'application Python
uvicorn app.main:app \
      --workers 1 \
      --host 0.0.0.0 \
      --port 8000 "$@"

N'oubliez pas d'ajouter un drapeau d'exécution

chmod +x entrypoint.sh

Et combinez avec IRIS dans docker-compose.yml

version:"3"services:  iris:    image:intersystemsdc/iris-community    ports:      -1972    environment:      -IRISUSERNAME=demo      -IRISPASSWORD=demo    healthcheck:      test:/irisHealth.sh      interval:5s  app:    build:.    ports:      -8000:8000    environment:      -DATABASE_URL=iris://demo:demo@iris:1972/USER    volumes:      -./:/usr/src/app    depends_on:      iris:        condition:service_healthy    command:      ---reload

Construisons-le

docker-compose build

Le premier modèle de données

Maintenant, déclarons l'accès à notre base de données IRIS à l'application, en ajoutant le fichier app/db.py, qui configurera SQLAlchemy pour accéder à notre base de données définie à travers l'URL passée par docker-compose.yml. Ce fichier contient également quelques gestionnaires que nous utiliserons plus tard dans l'application.

import os

from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import DeclarativeMeta, declarative_base
from sqlalchemy.orm import sessionmaker

DATABASE_URL = os.environ.get("DATABASE_URL")
ifnot DATABASE_URL:
    DATABASE_URL = "iris://demo:demo@localhost:1972/USER"
engine = create_engine(DATABASE_URL, echo=True, future=True)

Base: DeclarativeMeta = declarative_base()

SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)


definit_db():
    engine.connect()


defget_session():
    session = SessionLocal()
    yield session

Et nous sommes prêts à définir le premier et unique modèle de notre application. Nous créons et éditons le fichier app/models.py, il utilisera SQLAlchemy pour définir le modèle, nommé Todo, à trois colonnes, id, title, et description.

from sqlalchemy import Column, Integer, String, Text
from app.db import Base


classTodo(Base):
    __tablename__ = 'todo'
    id = Column(Integer, primary_key=True, index=True)
    title = Column(String(200), index=True, nullable=False)
    description = Column(Text, nullable=False)

Préparation de la migration SQL

Dans ce monde changeant, nous savons que notre application sera améliorée à l'avenir, que la structure de nos tableaux n'est pas définitive, que nous pouvons ajouter des tableaux, des colonnes, des index, etc. Dans ce cas, le meilleur scénario consiste à utiliser des outils de migration SQL, qui permettent de mettre à jour la structure actuelle de la base de données en fonction de la version de notre application, et grâce à ces outils, il est également possible de la rétrograder, au cas où quelque chose ne fonctionnerait pas. Bien que dans ce projet nous utilisions Python et SQLAlchemy, l'auteur de SQLAlchemy propose son outil nommé Alembic, et nous allons l'utiliser ici.

We need to start IRIS and container with our application, at this moment we need bash, to be able to run commands

$ docker-compose run --entrypoint bash app
[+] Creating 2/0
 ✔ Réseau  fastapi-iris-demo_default   Crée                                                                                                                                                        0.0s 
 ✔ Conteneur fastapi-iris-demo-iris-1  Crée                                                                                                                                                        0.0s 
[+] Exécution 1/1
 ✔ Conteneur fastapi-iris-demo-iris-1  Lancé                                                                                                                                                        0.1s 
root@7bf903cd2721:/usr/src/app# 

Exécution de la commande alembic init app/migrations

root@7bf903cd2721:/usr/src/app# alembic init app/migrations
  Création du répertoire '/usr/src/app/app/migrations' ...  exécuté
  Création du répertoire '/usr/src/app/app/migrations/versions' ...  exécuté
  Génération de /usr/src/app/app/migrations/README ...  exécuté
  Génération de /usr/src/app/app/migrations/script.py.mako ...  exécuté
  Génération de /usr/src/app/app/migrations/env.py ...  exécuté
  Génération de /usr/src/app/alembic.ini ...  exécuté
  Veuillez modifier les paramètres de configuration/connexion/logging dans '/usr/src/app/alembic.ini' avant de continuer.
root@7bf903cd2721:/usr/src/app#

Cette configuration alembic a été préalablement configurée, et nous devons la corriger pour qu'elle corresponde aux besoins de notre application. Pour ce faire, il faut éditer le fichier app/migrations/env.py. Ce n'est que le début du fichier, qui doit être mis à jour, en se concentrant sur la mise à jour de sqlalchemy.url et target_metadata. Ce qui se trouve en dessous reste inchangé

import os
import urllib.parse
from logging.config import fileConfig

from sqlalchemy import engine_from_config
from sqlalchemy import pool

from alembic import context

# il s'agit de l'objet Alembic Config, qui permet# d'accéder aux valeurs du fichier .ini utilisé.
config = context.config

DATABASE_URL = os.environ.get("DATABASE_URL")

decoded_uri = urllib.parse.unquote(DATABASE_URL)
config.set_main_option("sqlalchemy.url", decoded_uri)

# Interprétation du fichier de configuration pour l'enregistrement Python.# Cette ligne met en place les enregistreurs de façon basique.if config.config_file_name isnotNone:
    fileConfig(config.config_file_name)

# ajoutez ici l'objet MetaData de votre modèle# pour la prise en charge de l'autogénérationfrom app.models import Base
target_metadata = Base.metadata
# target_metadata = non applicable

Nous avons déjà un modèle, maintenant il faut créer une migration, avec la commande alembic revision --autogenerate (alembic revision ---autogénérer).

root@7bf903cd2721:/usr/src/app# alembic revision --autogenerate
INFO  [alembic.runtime.migration] Contexte impl IRISImpl.
INFO  [alembic.runtime.migration] Cela suppose un DDL non transactionnel.
INFO  [alembic.autogenerate.compare] Détection du tableau "todo" ajouté
INFO  [alembic.autogenerate.compare] Détection d'un index ajouté 'ix_todo_id' sur '['id']'
INFO  [alembic.autogenerate.compare] Détection d'un index ajouté 'ix_todo_title' sur '['title']'
  Generating /usr/src/app/app/migrations/versions/1e4d3b4d51ca_.py ... exécuté
root@7bf903cd2721:/usr/src/app# 
 
Let's see generated migration

Maintenant il faut appliquer ceci à la base de données, avec la commande alembic upgrade head, où "head" est un mot-clé pour mettre à jour vers la dernière version.

root@7bf903cd2721:/usr/src/app# alembic upgrade head
INFO  [alembic.runtime.migration] Contexte impl IRISImpl.
INFO  [alembic.runtime.migration] Cela suppose un DDL non transactionnel.
INFO  [alembic.runtime.migration] Exécution de la mise à jour -> 1e4d3b4d51ca, message vide
 
Rétrogradation
Si, au cours de la mise à jour de l'application, nous découvrons que nous devons revenir en arrière, nous pouvons rétrograder la base de données, par exemple la dernière révision sera head-1.
<pre>root@7bf903cd2721:/usr/src/app# alembic downgrade head-1

INFO  [alembic.runtime.migration] Contexte impl IRISImpl. INFO  [alembic.runtime.migration] Cela suppose un DDL non transactionnel. INFO  [alembic.runtime.migration] Exécution de la rétrogradation 1e4d3b4d51ca -> , message vide

<p>
  et pour rétrograder complètement vers un état vide, utilisez le mot-clé <strong>base</strong>
</p>

Vérifiez l'état actuel à tout moment, ce qui vous donnera des informations sur les migrations manquantes.

root@7bf903cd2721:/usr/src/app# alembic check
INFO  [alembic.runtime.migration] Contexte impl IRISImpl.
INFO  [alembic.runtime.migration] Cela suppose un DDL non transactionnel.
Aucune nouvelle opération de mise à jour détectée.

Accessibilité des données

Donc, nous pouvons maintenant retourner au REST, et il nous faut le faire fonctionner, quitter le conteneur actuel et lancer le service d'application comme d'habitude maintenant, uvicorn a un drapeau --reload, donc, il vérifiera les changements dans les fichiers python et redémarrera lorsque nous les changerons.

$ docker-compose up app
[+] Running 2/0
 ✔ Conteneur fastapi-iris-demo-iris-1 Lancé                                                                                                                                                        0.0s 
 ✔ Conteneur fastapi-iris-demo-app-1   Crée                                                                                                                                                       0.0s 
Attaching to fastapi-iris-demo-app-1, fastapi-iris-demo-iris-1
fastapi-iris-demo-app-1   | INFO  [alembic.runtime.migration] Contexte impl IRISImpl.
fastapi-iris-demo-app-1   | INFO  [alembic.runtime.migration] Cela suppose un DDL non transactionnel.
fastapi-iris-demo-app-1   | INFO:     Surveillance des modifications apportées aux répertoires : ['/usr/src/app']
fastapi-iris-demo-app-1   | INFO:     Uvicorn lancé sur http://0.0.0.0:8000 (Appuyez sur CTRL+C pour quitter)
fastapi-iris-demo-app-1   | INFO:     Lancement du processus de rechargement [8] à l'aide de StatReload
fastapi-iris-demo-app-1   | INFO:     Lancement du processus de serveur [10]
fastapi-iris-demo-app-1   | INFO:     En attente du démarrage de l'application.
fastapi-iris-demo-app-1   | INFO:     Démarrage de l'application achevé.

FastAPI utilise le projet pydantic, pour déclarer le schéma de données, et nous en avons besoin aussi, créons app/schemas.py, les mêmes colonnes que dans models.py mais sous une forme simple en Python

from pydantic import BaseModel


classTodoCreate(BaseModel):
    title: str
    description: str


classTodo(TodoCreate):
    id: int

    classConfig:
        from_attributes = True

Déclaration des opérations crud dans app/crud.py, où nous travaillons avec la base de données en utilisant l'ORM de SQLAlchemy.

from sqlalchemy.orm import Session
from . import models, schemas


defget_todos(db: Session, skip: int = 0, limit: int = 100):return db.query(models.Todo).offset(skip).limit(limit).all()


defcreate_todo(db: Session, todo: schemas.TodoCreate):
    db_todo = models.Todo(**todo.dict())
    db.add(db_todo)
    db.commit()
    db.refresh(db_todo)
    return db_todo

Pour finir, nous pouvons mettre à jour notre app/main.py, et ajouter des itinéraires pour lire et créer des todos.

from fastapi import FastAPI, Depends
from .db import init_db, get_session
from . import crud, schemas

app = FastAPI(
    title='TODO Application',
    version='1.0.0',
)


@app.on_event("startup")defon_startup():
    init_db()


@app.get("/ping")asyncdefpong():return {"ping": "pong!"}


@app.get("/todo", response_model=list[schemas.Todo])asyncdefread_todos(skip: int = 0, limit: int = 100, session=Depends(get_session)):
    todos = crud.get_todos(session, skip=skip, limit=limit)
    return todos


@app.post("/todo", response_model=schemas.Todo)asyncdefcreate_todo(todo: schemas.TodoCreate, session=Depends(get_session)):return crud.create_todo(db=session, todo=todo)

La page de documentation "docs" a été mise à jour en conséquence, et nous pouvons maintenant jouer avec.

 
Essayez !
Ajouter une nouvelle todo
<p>
  <img src="/sites/default/files/inline/images/images/image(6813).png" />
</p>

<p>
  Et vérifiez ce que nous avons ici
</p>

<p>
  <img src="/sites/default/files/inline/images/images/image(6814).png" />
</p>

Vérifions-le dans IRIS

─$ docker-compose exec iris irissqlcli iris+emb:///
Serveur:  IRIS pour UNIX (Ubuntu Server LTS pour les conteneurs "ARM64 Containers") 2023.2 (Build 227U) Mon Jul 31 2023 17:43:25 EDT
Version: 0.5.4
[SQL]irisowner@/usr/irissys/:USER> .tables
+-------------------------+
| TABLE_NAME              |
+-------------------------+
| SQLUser.alembic_version |
| SQLUser.todo            |
+-------------------------+
Temps: 0.043s
[SQL]irisowner@/usr/irissys/:USER> select * from todo
+----+-------+---------------------+
| id | titre | description         |
+----+-------+---------------------+
| 1  | démo  | cela marche vraiment |
+----+-------+---------------------+
1 rang dans le jeu
Temps: 0.004s
[SQL]irisowner@/usr/irissys/:USER> select * from alembic_version
+--------------+
| version_num  |
+--------------+
| 1e4d3b4d51ca |
+--------------+
1 rang dans le jeu
Temps: 0.045s
[SQL]irisowner@/usr/irissys/:USER>

 

J'espère que vous avez apprécié la facilité d'utilisation de Python et de FastAPI pour la création de REST. Le code source de ce projet est disponible sur github https://github.com/caretdev/fastapi-iris-demo

0
0 126
Article Iryna Mykhailova · Sept 25, 2023 6m read

FHIR a transformé le secteur des soins de santé en fournissant un modèle de données normalisé pour la création d'applications de soins de santé et en favorisant l'échange de données entre les différents systèmes de soins de santé. La norme FHIR est basée sur des approches modernes axées sur les API, ce qui la rend plus accessible aux développeurs mobiles et web. Cependant, l'interaction avec les API FHIR peut encore s'avérer difficile, en particulier lorsqu'il s'agit de requêter des données à l'aide du langage naturel.

Nous présentons l'application Chaîne OpenAPI et IA - FHIR une solution qui permet aux utilisateurs d'interagir avec les API FHIR à l'aide de requêtes en langage naturel. Conçue avec OpenAI, LangChain et Streamlit, cette application simplifie le processus d'interrogation des API FHIR et le rend plus facile à utiliser.

 

Quelles sont les spécifications OpenAPI de FHIR ?

Les spécifications OpenAPI (anciennement connues sous le nom de "Swagger" et faisant actuellement partie de l'" Initiative OpenAPI " (https://www.openapis.org/)) sont devenues un outil essentiel dans le monde du développement logiciel, permettant aux développeurs de concevoir, de documenter et d'interagir avec les API de manière plus efficace. Les spécifications OpenAPI définissent un format normalisé, lisible par une machine, pour décrire les API RESTful, offrant ainsi un moyen clair et cohérent de comprendre leurs capacités et de les utiliser de manière efficace.

Dans le domaine des soins de santé, FHIR s'est imposé comme une norme de premier plan pour l'échange de données et l'interopérabilité. Pour améliorer les capacités d'interopérabilité de FHIR, HL7 a officiellement documenté les spécifications OpenAPI de FHIR, qui permettent aux développeurs d'intégrer de manière transparente les ressources et les opérations FHIR dans leurs solutions logicielles.

 

Avantages des spécifications OpenAPI de FHIR :

  1. Description normalisée de l'API : Les spécifications OpenAPI fournissent une description complète et normalisée des ressources, des opérations et des interactions FHIR. Les développeurs peuvent facilement comprendre la structure et les capacités des API basées sur FHIR, ce qui facilite la création d'intégrations et l'interaction avec les systèmes de santé.
  2. Promotion de l'interopérabilité : La collaboration entre les développeurs est encouragée, ce qui favorise l'adoption des normes et des meilleures pratiques FHIR. Les spécifications fournissent un langage et un cadre communs pour discuter des intégrations et des implémentations basées sur FHIR, ce qui favorise la collaboration entre les développeurs.
  3. Documentation et tests améliorés : Documentation interactive et séries de tests pour une meilleure compréhension et une meilleure validation. Les développeurs peuvent créer une documentation détaillée sur les API, ce qui permet aux autres développeurs de comprendre et d'utiliser plus facilement les API basées sur FHIR. Les séries de tests basées sur les spécifications permettent d'effectuer des tests complets et de valider les intégrations d'API, garantissant ainsi la fiabilité et la précision des échanges de données de santé.
  4. Expérience améliorée pour les développeurs : Génération automatique de bibliothèques client et de SDK pour une intégration transparente. Cela simplifie le processus d'intégration et réduit le temps et les efforts nécessaires pour incorporer la fonctionnalité FHIR dans leurs applications.

 

Comment les chaînes FHIR, OpenAI et OpenAPI fonctionnent-elles ensemble ?

L'application Chaîne OpenAPI et IA - FHIR s'appuie sur LangChain pour charger et analyser les spécifications OpenAPI (Chaîne OpenAPI). Ensuite, sur la base de ces spécifications, la chaîne d'invites fournies par OpenAI vise à comprendre les requêtes en langage naturel et à les convertir en demandes d'API FHIR appropriées. Les utilisateurs peuvent poser des questions en langage naturel et l'application interagira avec l'API FHIR choisie pour récupérer les informations pertinentes.

Par exemple, un utilisateur peut demander : "Quelle est la dernière mesure de la tension artérielle du patient John Doe (ID 111) ?" L'application traduira alors cette requête en une demande d'API FHIR, récupérera les données requises et les présentera à l'utilisateur dans un format facilement compréhensible.

 

Avantages de l'application Chaîne OpenAPI et IA - FHIR

  1. Interactions faciles à utiliser : L'application permet aux utilisateurs d'interagir avec les API FHIR à l'aide de requêtes en langage naturel, ce qui facilite l'accès et l'analyse des données de santé pour les utilisateurs non techniques.
  2. Amélioration de l'efficacité : L'application rationalise le processus d'interrogation des API FHIR, réduisant ainsi le temps et les efforts nécessaires pour obtenir des informations pertinentes. Elle permet également de réduire le nombre de clics (temps passé) pour trouver une information particulière à partir de l'application.
  3. Personnalisable : Les normes FHIR simplifient la récupération de données cohérentes à partir de n'importe quel serveur FHIR, ce qui permet une personnalisation aisée. Il peut être configuré sans effort pour s'intégrer de manière transparente à n'importe quelle API FHIR, offrant ainsi une solution flexible et adaptable aux diverses exigences en matière de données de santé.

 

Premiers pas avec l'application Chaîne OpenAPI et IA - FHIR

Pour commencer à utiliser l'application Chaîne OpenAPI et IA - FHIR, suivez les étapes suivantes :

  1. Obtenez une clé API d'OpenAI auprès de la Plate-forme OpenAI.
  2. Obtenez un point de terminaison API du serveur FHIR. Vous pouvez utiliser votre propre exemple de serveur FHIR (accès non authentifié nécessaire) ou créer un exemple de serveur temporaire en suivant les instructions données dans la Plate-forme d'apprentissage de FHIR pour InterSystems IRIS.
  3. Essayez l'application en ligne ou ou configurez-la localement en suivant les instructions fournies.

Grâce à l'intégration de capacités d'IA et de traitement du langage naturel, l'application haîne OpenAPI et IA - FHIR offre un moyen plus intuitif pour interagir avec les API FHIR, rendant les données de santé plus accessibles et plus faciles à analyser pour les utilisateurs de tous niveaux techniques.

Votez pour notre application dans le cadre du concours Grand Prix si vous la trouvez pertinente !

Si vous pensez à des applications qui pourraient utiliser cette implémentation, n'hésitez pas à les partager dans le fil de discussion.

0
0 73
Article Guillaume Rongier · Sept 22, 2023 8m read

Description

Avec le Serveur InterSystems IRIS FHIR, vous pouvez construire une stratégie pour personnaliser le comportement du serveur (pour plus de détails, consultez documentation).

Image

Ce référentiel contient une stratégie Python qui peut être utilisée comme point de départ pour construire votre propre stratégie en Python.

Cette stratégie de démonstration présente les caractéristiques suivantes :

  • Mettre à jour la déclaration de capacité pour supprimer la ressource Account (compte).
  • Simuler un système de gestion de consentement pour accorder ou non l'accès à la ressource Observation.
    • Si l'utilisateur a des droits suffisants, la ressource Observation est renvoyée.
    • Sinon, la ressource Observation n'est pas renvoyée.

Installation

Étapes d'installation

  1. Clonez le référentiel suivant
git clone git@github.com:grongierisc/iris-fhir-python-strategy.git
  1. Construisez l'image docker
docker-compose build
  1. Lancez l'image docker
docker-compose up -d
  1. Ouvrez le serveur FHIR dans votre navigateur
GET http://localhost:8083/fhir/r4/metadata
Accept: application/json+fhir

La ressource Account (compte) ne peut pas figurer dans la Déclaration de capacité.

GET http://localhost:8083/fhir/r4/Account
Accept: application/json+fhir

retours :

{
  "resourceType": "OperationOutcome",
  "issue": [
    {
      "severity": "error",
      "code": "not-supported",
      "diagnostics": "<HSFHIRErr>ResourceNotSupported",
      "details": {
        "text": "Resource type 'Account' is not supported."
      }
    }
  ]
}
  1. Ouvrez le compte d'un patient sans authentification (vous ne devez pas avoir accès à l'Observation)
GET http://localhost:8083/fhir/r4/Observation?patient=178
Content-Type: application/json+fhir
Accept: application/json+fhir

retours :

{
  "resourceType": "Bundle",
  "id": "feaa09c0-1cb7-11ee-b77a-0242c0a88002",
  "type": "searchset",
  "timestamp": "2023-07-07T11:07:49Z",
  "total": 0,
  "link": [
    {
      "relation": "self",
      "url": "http://localhost:8083/fhir/r4/Observation?patient=178"
    }
  ]
}
  1. Ouvrez le compte d'un patient avec authentification (vous devez avoir accès à l'Observation).
GET http://localhost:8083/fhir/r4/Observation?patient=178
Content-Type: application/json+fhir
Accept: application/json+fhir
Authorization: Basic U3VwZXJVc2VyOlNZUw==

retours :

{
  "resourceType": "Bundle",
  "id": "953a1b06-1cb7-11ee-b77b-0242c0a88002",
  "type": "searchset",
  "timestamp": "2023-07-07T11:08:04Z",
  "total": 100,
  "link": [
    {
      "relation": "self",
      "url": "http://localhost:8083/fhir/r4/Observation?patient=178"
    }
  ],
  "entry": [
    {
      "fullUrl": "http://localhost:8083/fhir/r4/Observation/277",
      "resource": {
        "resourceType": "Observation",
        "id": "277",
        "status": "final",
        "category": [
          ...
        ],
      }
    },
    ...
  ]
}

Vous trouverez plus de détails dans la section suivante "Consentement".

Consentement

Le système de gestion de consentement est simulé par la méthode consent (consentement) de la classe CustomInteraction (interaction personnalisée) du module custom (personnalisation).

La méthode consent retourne True (vrai) si l'utilisateur a les droits suffisants pour accéder à la ressource, False (faux) dans le cas contraire.

    def consent(self, resource_type, user, roles):
        #Exemple de logique de consentement: autoriser uniquement les utilisateurs ayant le rôle "%All" à consulter
        #les ressources "Observation".
        if resource_type == 'Observation':
            if '%All' in roles:
                return True
            else:
                return False
        else:
            return True

La fonction consent fait partie de la classe CustomInteraction.

La classe CustomInteraction est une implémentation de la classe Interaction.

La classe Interaction est une classe abstraite qui doit être implémentée par la stratégie. Elle fait partie du module FhirInteraction.

class Interaction(object):
    __metaclass__ = abc.ABCMeta

    @abc.abstractmethod
    def on_before_request(self,
                          fhir_service:'iris.HS.FHIRServer.API.Service',
                          fhir_request:'iris.FHIRServer.API.Data.Request',
                          body:dict,
                          timeout:int):
        """
        on_before_request est appelé avant que la demande ne soit envoyée au serveur.
        param fhir_service: objet service fhir  iris.HS.FHIRServer.API.Service
        param fhir_request: objet service fhir  iris.FHIRServer.API.Data.Request
        param timeout: le délai en secondes
        return: None
        """


    @abc.abstractmethod
    def on_after_request(self,
                         fhir_service:'iris.HS.FHIRServer.API.Service',
                         fhir_request:'iris.FHIRServer.API.Data.Request',
                         fhir_response:'iris.FHIRServer.API.Data.Response',
                         body:dict):
        """
        on_after_request est appelée après l'envoi de la demande au serveur.
        param fhir_service: objet service fhir  iris.HS.FHIRServer.API.Service
        param fhir_request: objet requête fhir iris.FHIRServer.API.Data.Request
        param fhir_response: objet réponse fhir iris.FHIRServer.API.Data.Response
        return: None
        """


    @abc.abstractmethod
    def post_process_read(self,
                          fhir_object:dict) -> bool:
        """
        post_process_read est appelée après l'opération de lecture.
        param fhir_object: objet fhir
        return: "True" la ressource doit être renvoyée au client, "False" dans le cas contraire
        """


    @abc.abstractmethod
    def post_process_search(self,
                            rs:'iris.HS.FHIRServer.Util.SearchResult',
                            resource_type:str):
        """
        post_process_search est appelée une fois l'opération de recherche terminée.
        param rs: résultat de recherche iris.HS.FHIRServer.Util.SearchResult
        param resource_type: type de ressource
        return: None
        """

La classe CustomInteraction est une implémentation de la classe Interaction.

class CustomInteraction(Interaction):

    def on_before_request(self, fhir_service, fhir_request, body, timeout):
        #Extraction de l'utilisateur et des rôles pour cette demande
        #afin que le consentement puisse être évalué.
        self.requesting_user = fhir_request.Username
        self.requesting_roles = fhir_request.Roles

    def on_after_request(self, fhir_service, fhir_request, fhir_response, body):
        #Suppression de l'utilisateur et des rôles entre les demandes.
        self.requesting_user = ""
        self.requesting_roles = ""

    def post_process_read(self, fhir_object):
        #Évaluation du consentement en fonction de la ressource et de l'utilisateur/des rôles.
        #La valeur 0 indique que cette ressource ne doit pas être affichée - un message 404 Not Found (404 introuvable)
        #sera renvoyé à l'utilisateur.
        return self.consent(fhir_object['resourceType'],
                        self.requesting_user,
                        self.requesting_roles)

    def post_process_search(self, rs, resource_type):
        #Itération sur chaque ressource de l'ensemble de recherche et évaluation
        #du consentement en fonction de la ressource et de l'utilisateur/des rôles.
        #Chaque ligne marquée comme supprimée et sauvegardée sera exclue de l'ensemble.
        rs._SetIterator(0)
        while rs._Next():
            if not self.consent(rs.ResourceType,
                            self.requesting_user,
                            self.requesting_roles):
                #Sélection de la ligne comme supprimée et sauvegarde.
                rs.MarkAsDeleted()
                rs._SaveRow()

    def consent(self, resource_type, user, roles):
        #Exemple de logique de consentement: autoriser uniquement les utilisateurs ayant le rôle "%All" à consulter
        #les ressources "Observation".
        if resource_type == 'Observation':
            if '%All' in roles:
                return True
            else:
                return False
        else:
            return True

Vous pouvez modifier le module custom pour implémenter votre propre logique de consentement.

Toutes les modifications apportées au module custom seront directement reflétées dans le serveur FHIR.

Il est possible d'implémenter d'autres comportements en surchargeant les classes Interaction.

  • WIP

Personnalisation de la ressource CapabilityStatement

Le serveur IRIS FHIR fournit une ressource CapabilityStatement par défaut en fonction du Guide d'implémentation fourni au moment de l'installation.

De plus amples informations sur la manière de personnaliser la Resource CapabilityStatement sont disponibles à l'adresse FHIR CapabilityStatement.

Pour cet exemple, le Guide d'implémentation est FHIR R4 brut.

Pour personnaliser la ressource CapabilityStatement, vous pouvez modifier le module custom.

La classe CustomStrategy est une implémentation de la classe Strategy(stratégie).

La classe Strategy est une classe abstraite qui doit être implémentée par la stratégie. Elle fait partie du module FhirInteraction.

class Strategy(object):
    __metaclass__ = abc.ABCMeta

    @abc.abstractmethod
    def on_get_capability_statement(self,capability_statement:dict)-> dict:
        """
        on_after_get_capability_statement est appelé une fois la déclaration de capacité récupérée.
        param capability_statement: la déclaration de capacité
        return: None
        """

La méthode on_get_capability_statement est appelée après la génération de la ressource CapabilityStatement.

La classe CustomStrategy est une implémentation de la classe Strategy.

class CustomStrategy(Strategy):

    def on_get_capability_statement(self, capability_statement):
        # Exemple : Compte de ressources
        capability_statement['rest'][0]['resource'] = [resource for resource in capability_statement['rest'][0]['resource'] if resource['type'] != 'Account']
        return capability_statement

Vous pouvez modifier le module custom pour implémenter votre propre Custom CapabilityStatement.

Pour appliquer les modifications, vous devez mettre à jour la configuration du serveur fhir.

cd /irisdev/app/src/python
/usr/irissys/bin/irispython
>>> import custom
>>> custom.set_capability_statement()
0
0 59
Article Sylvain Guilbaud · Sept 4, 2023 6m read

Lorsque nous démarrons le développement avec IRIS, nous disposons d'un kit de distribution ou, dans le cas de Docker, nous extrayons l'image Docker, puis nous devons souvent l'initialiser et configurer l'environnement de développement. Nous devrons peut-être créer des bases de données, des espaces de noms, activer/désactiver certains services, créer des ressources. Nous devons souvent importer du code et des données dans l'instance IRIS et exécuter du code personnalisé pour lancer la solution.

Il existe de nombreux modèles sur Open Exchange dans lesquels nous suggérons comment initialiser REST, l'interopérabilité, l'analytics, le développement Fullstack et bien d'autres modèles utilisant ObjectScript. Mais qu'en est-il si nous souhaitons utiliser uniquement Python pour configurer l'environnement de développement d'un projet Embedded Python avec IRIS ?

De fait, la récente sortie de Embedded Python template est devenu le passe-partout du pur Python qui pourrait être un point de départ pour les développeurs qui créent des projets Python sans avoir besoin d'utiliser et d'apprendre ObjectScript. Cet article explique comment ce modèle pourrait être utilisé pour initialiser IRIS. C'est parti!

0
0 80
Article Sylvain Guilbaud · Août 31, 2023 7m read

Description du cas

Imaginons que vous soyez un développeur Python ou que vous disposiez d'une équipe bien formée et spécialisée en Python, mais que le délai dont vous disposez pour analyser certaines données dans IRIS est serré. Bien entendu, InterSystems propose de nombreux outils pour toutes sortes d’analyses et de traitements. Cependant, dans le scénario donné, il est préférable de faire le travail en utilisant les bons vieux Pandas et de laisser l'IRIS pour une autre fois.

0
0 125
Article Iryna Mykhailova · Août 7, 2023 3m read
   _________ ___ ____  
  |__  /  _ \_ _|  _ \
    / /| |_) | || |_) |
   / /_|  __/| ||  __/
  /____|_|  |___|_|    

À partir de la version 2021.1, InterSystems IRIS a commencé à fonctionner avec l'exécution python dans le noyau du moteur. Cependant, il n'y avait aucun moyen d'installer des paquets à partir de l'instance. Le principal attrait de Python est son énorme écosystème de paquets. C'est dans cette optique que je vous présente mon projet secondaire zpip, un wrapper pip qui peut être appelé depuis le terminal iris.

Qu'est-ce que zpip ?

zpip est un wrapper pour python pip qui permet aux développeurs d'ajouter rapidement des paquets à une instance via le terminal InterSystems IRIS.

Fonctionnalités

  • python pip wrapper pour InterSystems IRIS
  • Installation et désinstallation des paquets python
  • Ajout du mot-clé zpip à la langue lors de l'installation

Installation de zpip

%SYS> zpm "install zpip"

TODO: Liste des tâches à accomplir

  • API appelable avec retour des états

Utiliisation de zpip

Les commandes pip* sont toutes supportées, cependant, toute commande interactive nécessite l'utilisation de la version non-interactive de la commande. Par exemple, pour désinstaller un paquet, vous devrez utiliser -y dans la commande pour confirmer le processus.

Installation de paquets python avec zpip

// Installation de plusieurs paquets
// belles bibliothèques en demande
%SYS> zpip "install requests bs4"

... en action:

%SYS>zpip "install emoji"

Processing /home/irisowner/.cache/pip/wheels/ae/80/43/3b56e58669d65ea9ebf38b9574074ca248143b61f45e114a6b/emoji-2.1.0-py3-none-any.whl
Installing collected packages: emoji
Successfully installed emoji-2.1.0

%SYS>

Spécification d'un répertoire d'installation différent :

// Installation vers une autre cible de paquetage python
$SYS> zpip "install --target '/durable/iconfig/lib/python' emoji"

Désinstallation d'un paquet python

// Nécessite -y!
%SYS>zpip "uninstall -y emoji"
Found existing installation: emoji 2.1.0
Uninstalling emoji-2.1.0:
  Successfully uninstalled emoji-2.1.0

Autres commandes pip utiles

liste des paquets

// Liste des paquets
%SYS> zpip "list"
Paquet                      Version    
---------------------------- -----------
absl-py                      1.1.0      
argon2-cffi                  21.3.0     
argon2-cffi-bindings         21.2.0     
asttokens                    2.0.5      
astunparse                   1.6.3      
attrs                        21.4.0     
backcall                     0.2.0      
beautifulsoup4               4.11.1     
bleach                       5.0.0      
bs4                          0.0.1   
...

Limitations

  • ILes commandes interactives ne sont pas prises en charge
    • Utilisation de -y pour les désinstallations
  • La recherche peut ne pas fonctionner en fonction de la configuration du système
  • Utilisation de l'infrastructure pip du système d'exploitation sous-jacent, de sorte que votre installation dépend de la version pip du système d'exploitation.
0
0 65
Article Irène Mykhailova · Août 4, 2023 1m read

Titre:  Une nouvelle version alpha de Python 3.12, plus rapide et plus légère, est désormais disponible

Résumé:  Une nouvelle version alpha de Python 3.12, plus rapide et plus légère, est désormais disponible, apportant plusieurs améliorations au niveau du multitraitement et de la gestion des erreurs. Cette mise à jour promet des performances améliorées et une meilleure efficacité dans le traitement des tâches concurrentes. Les développeurs peuvent attendre avec impatience cette version qui devrait offrir une expérience encore plus fluide et une gestion des erreurs plus robuste. Python continue de s'améliorer en tant que langage de programmation de choix pour les projets de toutes tailles et de toutes complexités.

0
0 63
Article Lorenzo Scalese · Juil 31, 2023 11m read

Notre objectif

Dans le dernier article, nous avons parlé de quelques éléments de démarrage pour Django. Nous avons appris à commencer le projet, à nous assurer que nous disposons de tous les éléments requis et à créer une matrice CRUD. Cependant, aujourd'hui, nous allons un peu plus loin.
Aujourd'hui, nous allons donc connecter IRIS à un environnement Python, construire quelques fonctions et les afficher sur une page web. Ce sera similaire à la dernière discussion, mais nous irons assez loin pour que vous puissiez faire quelque chose de nouveau, mais pas assez pour que vous vous sentiez perdus.
Dans ce projet, nous obtiendrons des informations sur les globales dans IRIS afin de suivre leur taille et de comprendre le coût de chaque espace de noms et de chaque table en Mo.

Le projet

Chaque étape est disponible sur mon Référentiel GitHub, dans l'historique des commits.

Les éléments de démarrage

Nous suivons quelques étapes similaires à celles de l'article précédent, alors cette partie vous sera familière.

  • Allez dans le répertoire souhaité sur le terminal et tapez ce qui suit.

django-admin startproject globalSize

  • Ajoutez requirements.txt avec le texte suivant, et tapez

pip install -r requirements.txt

pour être sûr de remplir les conditions requises.

# Django en lui-même
django>=4.2.1# Pilote InterSystems IRIS pour Django
django-iris==0.2.2
  • Dans globalSize/settings.py, ajoutez IRIS dans les configurations DATABASES :
DATABASES = {
	‘default’: {
		‘ENGINE’: ‘django_iris’,
		‘NAME’: ‘USER’,
		‘USER’: ‘_system’,
		‘PASSWORD’: ‘SYS’,
		‘HOST’: ‘localhost’,
		‘PORT’: 1972,
	}
}
  • N'oubliez pas d'ajouter un .gitignore et un .editorconfig. Il est également pratique d'avoir un linter de votre choix, mais cela dépasse le cadre de cet article.

 

Création de l'application et du modèle

Nous avons créé une application et un modèle dans le dernier article, cette section devrait donc vous être familière, même s'il s'agit d'une application et d'un modèle différents.

  • Pour créer l'application, saisissez

python manage.py startapp globals

  • Dans _globals/models.py, _créez un modèle avec les informations que vous souhaitez afficher sur vos globales :
classirisGlobal(models.Model):
	database = models.CharField(max_length=40)
	name = models.CharField(max_length=40)
	allocatedsize = models.FloatField()
	size = models.FloatField()


	def__str__(self):return self.name
  • Dans _settings.py, _ajoutez la nouvelle application à INSTALLED_APPS :
INSTALLED_APPS = [
	…,
	‘globals’,
]

 

Configuration des URL et de la page d'accueil

Une fois de plus, nous suivons quelques étapes similaires à celles de l'article précédent.

  • La fonction incluse dans django.urls est importée dans _globalSize/urls.py et un nouveau chemin est ajouté à globals.urls dans urlpatterns.
from django.urls import path, include
urlpatterns = [
    …,
    path(‘globals/’, include(‘globals.urls’)),
]
  • Créez les URLs pour l'application, en ajoutant le fichier globals/urls.py avec le texte suivant.
from django.urls import path
from .views import home
urlpatterns = [
	path(‘’, home),
]
  • Créez la vue importée dans la dernière étape. Dans view.py, ajoutez la fonction ci-dessous.
defhome(request):return render(request, “index.html”)
  • Pour finir, ajoutez le fichier globals/templates/index.html et générez la page d'accueil comme vous le souhaitez. Consultez l'exemple ci-dessous :


  
    hello world!
  

Si vous entrez les commandes ci-dessous et suivez le lien http://127.0.0.1:8000/globals/, vous aurez une page affichant "hello world !".

python manage.py makemigrations<br>python manage.py migrate<br>python manage.py runserver

Affichage des globales dans les pages d'accueil et d'administration

  • Importez le modèle et enregistrez-le dans le fichier admin.py.
from .models import irisGlobal
admin.site.register(irisGlobal)
  • Importez le modèle dans views.py et renvoyez-le dans la fonction.
from .models import irisGlobal
defhome(request):
	globals = irisGlobal.objects.all()
	return render(request, “index.html”, {“globals”: globals})
  • Nous pouvons maintenant accéder aux valeurs globales à partir de _index.html _comme nous le souhaitons. Consultez l'exemple ci-dessous.
ID  -  DATABASE          /       GLOBAL       - Size / Allocated
  {% for global in globals %}
  >
    {{ global.id }} - {{ global.database }}    {{ global.name  }}  -  {{ global.size  }}  /  {{ global.allocatedsize }}
  
  {% endfor %}

 

Récupération de données

Récupération de données
À ce stade, le projet est prêt à recevoir des informations. Il existe de nombreuses façons de modeler ce projet, mais pour ma part, j'utiliserai l'approche de Python afin que nous puissions apprendre une nouvelle solution qu'il est possible d'intégrer à Django et à IRIS.
Nous avons besoin de quelques méthodes pour récupérer toutes les données. Nous pouvons utiliser InterSystems IRIS Cloud SQL avec le pilote DB-API pour nous connecter à l'instance que nous voulons analyser - il n'est pas nécessaire que ce soit la même que celle où nous avons connecté Django.
L'organiser dans un nouveau dossier que nous pouvons traiter comme un module est une bonne pratique. Pour cela, créez le dossier api dans les globaux, ajoutez un fichier vide _init_.py_ pour que Python le reconnaisse comme un module, et commencez à écrire le fichier qui contiendra les méthodes. Nous pouvons l'appeler methods.py.


Création de la connexion

Pour connecter notre environnement Python à l'IRIS d'InterSystems, nous devons suivre quelques étapes décrites dans la section "ObjectScript dans l'environnement Python" de l'article précédent [Python et IRIS dans la pratique] (https://community.intersystems.com/post/python-and-iris-practice-examples).
A partir de maintenant, c'est simple ; nous importons iris, nous passons l'adresse de la connexion (l'instance d'IRIS que nous voulons analyser dans le format suivant : host:port/namespace), un nom d'utilisateur et un mot de passe à la méthode iris.connect et nous créons IRIS de Python. Jetez un coup d'œil au code ci-dessous.

import intersystems_iris as iris
from django.db import connection as djangoconnection

# connection par iris
conn_params = djangoconnection.get_connection_params()
conn_params[“namespace”] = “%SYS”
connection = iris.connect(**conn_params)
irisPy = iris.createIRIS(connection)

 

Obtention des répertoires de bases de données

Puisque nous voulons récupérer les tailles des globales, nous avons besoin (bien sûr) de leurs tailles, de leurs noms et de leurs adresses - ce que nous appelons des bases de données.
Je vais vous montrer une version simplifiée de la fonction, mais rappelez-vous qu'il est préférable de vérifier chaque étape et chaque connexion, et de lancer une exception si quelque chose ne va pas.
Tout comme nous le ferions en ObjectScript, nous avons besoin d'une requête SQL pour pouvoir la préparer, l'exécuter et récupérer une liste contenant tous les répertoires de la base de données dans son jeu de résultats. Nous pouvons faire tout cela facilement avec les fonctions "irisPy.classMethodSomething()", où Something (quelque chose) représente le type que la méthode doit retourner, et irisObject.invoke(), où nous pouvons accéder à n'importe quoi à partir de l'irisObject référencé. Voici un exemple.

defgetAllDatabaseDirectories():try:
	   # vérification de la connexion faite dans irisPy, et si elle est fixée à l'espace de noms %SYS
	   databaseDirectoriesList = []
	   with connection.cursor() as cursor:
		cursor.execute(“SELECT DISTINCT %EXACT(Directory) FROM Config.Databases WHERE SectionHeader = ?”, [“Databases”,],)
		databaseDirectoriesList = [row[0] for row in cursor]

    except Exception as error:
        return str(error)

    return databaseDirectoriesList

La variable statement est définie comme un objet généré par la méthode %New de la classe IRIS %SQL.Statement. Il est alors possible d'invoquer la méthode %Prepare à partir de l'objet instancié, avec une chaîne de requête comme argument. Ensuite, nous pouvons invoquer les méthodes %Execute et %Next pour exécuter la requête et boucler sur l'ensemble des résultats, en ajoutant les informations souhaitées à une liste Python pour un accès facile.
Il est facile de trouver chaque répertoire de base de données dans le tableau Config.Databases, situé uniquement dans l'espace de noms %SYS de chaque instance d'IRIS. Consultez-la dans le portail de gestion si vous le souhaitez, vous y trouverez d'autres informations intéressantes.

Récupération de toutes les globales d'une base de données

Cette fonction est très similaire à la précédente. Cependant, nous disposons maintenant d'une requête de classe prête à l'emploi. Une fois de plus, nous avons besoin d'une requête SQL, nous pouvons donc préparer la requête DirectoryList à partir de la classe %SYS.GlobalQuery. Ensuite, nous l'exécutons avec un répertoire de base de données comme argument et nous récupérons une liste contenant tous les globales de cette base de données.

defgetGlobalsList(databaseDirectory: str):try:
        statement = irisPy.classMethodObject("%SQL.Statement", "%New")
        status = statement.invoke("%PrepareClassQuery", "%SYS.GlobalQuery","DirectoryList")

        result = statement.invoke("%Execute", databaseDirectory)

        globalList = []
        while (result.invoke("%Next")!=0):
            globalList.append(result.invoke("%Get", "Name"))

    except Exception as error:
        return str(error)

    return globalList

Récupération des tailles des globales et des tailles allouées

Enfin, nous pouvons accéder aux informations cibles. Heureusement, IRIS dispose d'une méthode intégrée pour récupérer la taille et la taille allouée à condition de fournir une base de données et une paire de globales.

defgetGlobalSize(databaseDirectory: str, globalName: str):try:
        globalUsed = iris.IRISReference(0)
        globalAllocated = iris.IRISReference(0)
        status = irisPy.classMethodObject("%GlobalEdit", "GetGlobalSize", databaseDirectory, globalName, globalAllocated, globalUsed, 0)

    except Exception as error:
        return str(error)

    return (globalUsed.getValue(), globalAllocated.getValue())

Cette fois, nous avons besoin de la fonction IRISReference(0) du module iris pour recevoir les tailles de la fonction "GetGlobalSize" par référence. Ensuite, nous pouvons accéder à la valeur avec la méthode getValue().

Affichage complet sur la page d'accueil

Enfin, nous pouvons utiliser ces fonctions pour afficher les données sur la page d'accueil. Nous disposons déjà d'un moyen pour accéder aux informations et d'un tableau, il ne nous reste plus qu'à le remplir. Pour cela, je veux créer un bouton de mise à jour.
Tout d'abord, nous ajoutons un lien vers le fichier index.html.

href = "{% url 'update' %}">updatea>body>

Ajoutez le lien vers la liste urlpatterns, dans urls.py.

Add the link to the urlpatterns list, in urls.py.
from .views import home, update
urlpatterns = [
    path('', home),
    path('update', update, name="update"),
]

Ensuite, créez la vue, dans views.py.

from django.shortcuts import render, redirect
from .api.methods import *
defupdate(request):
    irisGlobal.objects.all().delete()
    databaseList = getAllDatabaseDirectories()

    for database in databaseList:
        globalList = getGlobalsList(database)

        for glob in globalList:
            used, allocated = getGlobalSize(database, glob)
            irisGlobal.objects.create(database=database, name=glob, size=used, allocatedsize=allocated)

    return redirect(home)

 

Pour cette vue, nous devons d'abord importer la fonction de redirection de django.shortcuts, ainsi que les méthodes que nous venons de construire.  
C'est une bonne idée de supprimer toutes les données précédentes du tableau afin que les globales éventuellement supprimées disparaissent. Comme le nombre de globales n'est probablement pas gigantesque, il est préférable de procéder ainsi plutôt que de vérifier chaque enregistrement pour voir s'il a été supprimé ou s'il a besoin d'une mise à jour.  
Ensuite, nous obtenons tous les répertoires des bases de données afin de pouvoir, pour chaque base de données, vérifier tous les globales qui s'y trouvent, et pour chaque globale, nous pouvons avoir sa taille utilisée et sa taille allouée.  
À ce stade, le modèle Django est rempli et prêt à récupérer des données, nous redirigeons donc vers la vue d'accueil.  
Si vous accédez à <http://127.0.0.1:8000/globals/> et cliquez sur le lien de mise à jour que nous avons ajouté, la page devrait se recharger et dans quelques secondes, elle affichera la liste des globales, avec ses bases de données, ses tailles et ses tailles allouées, comme dans l'image ci-dessous.


Ajout d'une agrégation

Vous seriez surpris de savoir à quel point il est simple d'ajouter quelques options d'analyse rapide, telles qu'une somme ou un décompte. Il n'est pas nécessaire de maîtriser Django pour créer quelques tableaux de bord sur cette page et, après cette section, vous devriez être en bonne position pour commencer.
Nous savons déjà que la vue accueil est responsable du rendu de l'index. Jusqu'à présent, nous avons généré la variable "globals", contenant toutes les données, et l'avons passée à l'index.html. Nous allons faire quelque chose de similaire, mais avec des fonctions d'agrégation. Nous allons créer une variable pour chaque somme, utiliser les méthodes aggregate() et Sum(), et les ajouter à l'argument "context list" de la fonction "render". Et bien sûr, n'oubliez pas d'importer "Sum" de django.db.models. Consultez la fonction ci-dessous.

defhome(request):
	globals = irisGlobal.objects.all()
	sumSize = globals.aggregate(Sum("size"))
	sumAllocated = globals.aggregate(Sum("allocatedsize"))
	return render(request, "index.html", {"globals": globals, "sumSize": sumSize, "sumAllocated":sumAllocated})

Nous pouvons maintenant l'ajouter au fichier index.html et ajouter quelques paragraphes sous la liste (

élément). Dans ces paragraphes, nous pouvons accéder au décompte de toutes les globales et aux sommes, comme indiqué ci-dessous.

…
  showing results for {{globals.count}} globalsp>total size: {{sumSize.size__sum}}p><total allocated size: {{sumAllocated.allocatedsize__sum}}p>body>html>

Actualisez le lien et vous devriez obtenir le résultat suivant.


La fin... ou presque

Dans cet article, nous avons appris comment InterSystems IRIS stocke les données en mémoire, comment y accéder à partir de Python, comment construire une API, et comment utiliser IRIS comme un système en nuage, de sorte que nous puissions le suivre et l'analyser facilement. Nous pouvons voir à l'horizon des requêtes plus complexes, la création de tableaux de bord, l'automatisation des mises à jour et l'ajout d'un système de notification.
Dans le prochain article, je me rapprocherai de cet horizon, en montrant comment filtrer et ordonner les données avant de les afficher, en ajoutant quelques options modifiables côté client, et enfin, en ajoutant une pincée de CSS pour donner du charme à l'ensemble.
Souhaitez-vous voir quelque chose que je n'ai pas encore dit ? N'hésitez pas à me contacter si vous avez des idées ou des besoins sur lesquels vous aimeriez que j'écrive.

0
0 171
Article Irène Mykhailova · Juil 28, 2023 1m read

Titre:  Le plan de travail pour la future version 3.13 de Python a été dévoilé

Résumé:  Le plan de travail pour la future version 3.13 de Python a été dévoilé, mettant l'accent sur deux objectifs majeurs : la réduction d'au moins 50% du temps passé dans l'interpréteur et une meilleure gestion de la mémoire. L'article présente les principales pistes envisagées pour atteindre ces objectifs ambitieux. Des améliorations spécifiques dans le fonctionnement de l'interpréteur Python sont explorées, visant à accélérer l'exécution des scripts et à rendre Python encore plus performant. La gestion de la mémoire est également au centre des préoccupations, avec des projets pour réduire la consommation et optimiser l'utilisation des ressources système. Cette future version de Python promet des avancées significatives qui pourraient impacter positivement les performances et l'efficacité des applications développées en Python. Les développeurs et passionnés de Python ont de quoi être enthousiastes quant aux améliorations à venir dans cette version 3.13 !

0
0 92
Article Guillaume Rongier · Juil 24, 2023 7m read

Je suis fier d'annoncer la nouvelle version du logiciel iris-pex-embedded-python (v2.3.1) avec une nouvelle interface en ligne de commande.

Cette ligne de commande est appelée iop et signifie Interoperability On Python (interopérabilité sur Python).

Tout d'abord, j'aimerais présenter en quelques mots le projet et les principaux changements depuis la version 1.

Un bref historique du projet

La version 1.0 était une preuve de concept visant à montrer comment le cadre d'interopérabilité d'IRIS peut être utilisé avec une approche python first (priorité à Python) tout en restant compatible avec n'importe quel code ObjectScript existant.

Qu'est-ce que cela signifie ? Cela signifie que tout développeur python peut utiliser le cadre d'interopérabilité d'IRIS sans aucune connaissance d'ObjectScript.

Exemple :

from grongier.pex import BusinessOperation

class MyBusinessOperation(BusinessOperation):

    def on_message(self, request):
        self.log.info("Demande reçue")

Formidable, n'est-ce pas ?

Avec la version 1.1, j'ai ajouté la possibilité d'enregistrer ces classes python dans IRIS avec une fonction d'aide "help".

from grongier.pex import Utils

Utils.register_file("/src/MyBusinessOperation.py")

La version 2.0 est une version majeure car on peut maintenant installer ce projet avec pip.

pip install iris-pex-embedded-python

Quoi de neuf dans la version 2.3.1

La version 2.3.1 est une version majeure car elle introduit une nouvelle interface en ligne de commande.

Cette interface en ligne de commande peut être utilisée avec ce projet python basé sur ce module ou peut-être avec des projets qui n'utilisent pas ce module.

Laissez-moi la présenter et expliquer pourquoi elle peut être utilisée dans des projets non python.

L'interface en ligne de commande

La ligne de commande fait partie de ce projet, pour l'installer il suffit d'installer ce projet avec pip.

pip install iris-pex-embedded-python

Vous pouvez ensuite utiliser la ligne de commande iop pour lancer le cadre d'interopérabilité.

iop

résultat :

utilisation: iop [-h] [-d DEFAULT] [-l] [-s START] [-k] [-S] [-r] [-M MIGRATE] [-e EXPORT] [-x] [-v] [-L]
les arguments optionnels:
  -h, --help            affichage de  help (aide) et du nom de production par défaut
  -d DEFAULT, --default DEFAULT
                        définition de la production par défaut
  -l, --lists           liste de productions
  -s START, --start START
                        démarrage de la production
  -k, --kill            Suppression de la production (arrêt forcé)
  -S, --stop            Arrêt de la production
  -r, --restart         redémarrage de la production
  -M MIGRATE, --migrate MIGRATE
                        migration de la production et des classes avec un fichier de configuration
  -e EXPORT, --export EXPORT
                        exportation de la production
  -x, --status          état de la production
  -v, --version         version d''affichage
  -L, --log             affichage du journal

production par défaut : UnitTest.Production

Voici quelques exemples.

help (aide)

La commande "help" affiche l'aide et le nom de production par défaut.

iop -h

résultat :

utilisation: python3 -m grongier.pex [-h] [-d DEFAULT] [-l] [-s START] [-k] [-S] [-r] [-M MIGRATE] [-e EXPORT] [-x] [-v] [-L]
...
production par défaut : PEX.Production

default (par défaut)

La commande "default" permet de définir la production par défaut.

Si aucun argument n'est fourni, la production par défaut est affichée.

iop -d

résultat :

production par défaut : PEX.Production

Si un argument est fourni, la production par défaut est définie.

iop -d PEX.Production

lists (les listes)

La commande "lists" permet de dresser la liste des productions.

iop -l

utilisation :

{
    "PEX.Production": {
        "Status": "Stopped",
        "LastStartTime": "2023-05-31 11:13:51.000",
        "LastStopTime": "2023-05-31 11:13:54.153",
        "AutoStart": 0
    }
}

start (lancement)

La commande "start" permet de lancer une production.

Pour quitter la commande, il faut appuyer sur CTRL+C.

iop -s PEX.Production

Si aucun argument n'est donné, la commande "start" lance la production par défaut.

iop -s

résultat :

2021-08-30 15:13:51.000 [PEX.Production] INFO: Starting production
2021-08-30 15:13:51.000 [PEX.Production] INFO: Starting item Python.FileOperation
2021-08-30 15:13:51.000 [PEX.Production] INFO: Starting item Python.EmailOperation
...

kill (suppression)

La commande "kill" peut supprimer une production (arrêt forcé).

La commande "kill" est la même que la commande stop mais avec un arrêt forcé.

La commande "Kill" ne prend pas d'argument parce qu'une seule production peut être en cours d'exécution.

iop -k

stop (arrêt)

La commande "stop" permet d'arrêter une production.

La commande "stop" ne prend pas d'argument parce qu'une seule production peut être en cours d'exécution.

iop -S

restart (redémarrage)

La commande "restart" permet de relancer une production.

La commande "restart" ne prend pas d'argument parce qu'une seule production peut être en cours d'exécution.

iop -r

migrate (migration)

La commande "migrate" permet de migrer une production et des classes avec un fichier de configuration.

La commande "migrate" doit prendre le chemin absolu du fichier de configuration.

Le fichier de configuration doit se trouver dans le même dossier que le code python.

iop -M /tmp/settings.py

export (exportation)

La commande "export" permet d'exporter une production.

Si aucun argument n'est donné, la commande "export" exporte la production par défaut.

iop -e

Si un argument est donné, la commande "export" exécute la production indiquée dans l'argument.

iop -e PEX.Production

résultat :

{
    "Production": {
        "@Name": "PEX.Production",
        "@TestingEnabled": "true",
        "@LogGeneralTraceEvents": "false",
        "Description": "",
        "ActorPoolSize": "2",
        "Item": [
            {
                "@Name": "Python.FileOperation",
                "@Category": "",
                "@ClassName": "Python.FileOperation",
                "@PoolSize": "1",
                "@Enabled": "true",
                "@Foreground": "false",
                "@Comment": "",
                "@LogTraceEvents": "true",
                "@Schedule": "",
                "Setting": [
                    {
                        "@Target": "Adapter",
                        "@Name": "Charset",
                        "#text": "utf-8"
                    },
                    {
                        "@Target": "Adapter",
                        "@Name": "FilePath",
                        "#text": "/irisdev/app/output/"
                    },
                    {
                        "@Target": "Host",
                        "@Name": "%settings",
                        "#text": "path=/irisdev/app/output/"
                    }
                ]
            }
        ]
    }
}

status (état)

La commande "status" permet d'indiquer l'état d'une production.

La commande "status" ne prend pas d'argument parce qu'une seule production peut être en cours d'exécution.

iop -x

résultat :

{
    "Production": "PEX.Production",
    "Status": "stopped"
}

Un état peut être :

  • arrêté
  • en cours d'exécution
  • suspendu
  • compliqué

version

La commande "version" permet d'afficher la version.

iop -v

résultat :

2.3.0

log (journal)

La commande "log" permet d'afficher le journal.

Pour quitter la commande, il faut appuyer sur CTRL+C.

iop -L

résultat :

2021-08-30 15:13:51.000 [PEX.Production] INFO: Starting production
2021-08-30 15:13:51.000 [PEX.Production] INFO: Starting item Python.FileOperation
2021-08-30 15:13:51.000 [PEX.Production] INFO: Starting item Python.EmailOperation
...

Peut-on l'utiliser en dehors du projet iris-pex-embedded-python ?

À vous de choisir.

Mais, avant de vous quitter, laissez-moi vous expliquer pourquoi je pense que cela peut être utilisé en dehors du projet iris-pex-embedded-python.

Tout d'abord, parce que cela permet d'interagir avec la production sans avoir besoin d'utiliser un shell iris. Cela signifie qu'il est plus facile à l'utiliser dans un script.

Deuxièmement, parce que settings.py peut être utilisé pour importer la production et les classes avec des variables d'environnement.

Voici un exemple de settings.py :

import os

PRODUCTIONS = [
        {
            'UnitTest.Production': {
                "Item": [
                    {
                        "@Name": "Python.FileOperation",
                        "@ClassName": "Python.FileOperation",
                        "Setting": {
                            "@Target": "Host",
                            "@Name": "%settings",
                            "#text": os.environ['SETTINGS']
                        }
                    }
                ]
            }
        }
    ]

Faites attention à la valeur #text. C'est une variable d'environnement. Pas mal, non ?

Est-ce que vous vous voyez utiliser cet outil en ligne de commande, cela vaut-il la peine de continuer à le développer ?

Merci pour avoir lu cet article et vos commentaires sont les bienvenus.

0
0 78