Question

Scénario:

  • Un serveur d'applications .NET ( plate-forme Wonderware IAS / System . ) héberge des objets d'automatisation qui communiquent avec divers équipements de l'usine.
  • CPython est hébergé sur ce serveur d'applications (à l'aide de Python pour .NET ).
  • Les objets d'automatisation ont une fonctionnalité de script intégrée (utilisant un langage personnalisé basé sur .NET). Ces scripts appellent des fonctions Python.

Les fonctions Python font partie d’un système permettant de suivre les travaux en cours dans l’usine. Le but du système est de suivre les widgets produits tout au long du processus, de s’assurer qu’ils passent à travers le processus dans le bon ordre et de vérifier que certaines conditions sont remplies tout au long du processus. L'historique de production du widget et son état sont stockés dans une base de données relationnelle. C'est ici que SQLAlchemy joue son rôle.

Par exemple, lorsqu'un widget passe un scanner, le logiciel d'automatisation déclenche le script suivant (écrit dans le langage de script personnalisé du serveur d'applications):

' wiget_id and scanner_id provided by automation object
' ExecFunction() takes care of calling a CPython function
retval = ExecFunction("WidgetScanned", widget_id, scanner_id);
' if the python function raises an Exception, ErrorOccured will be true
' in this case, any errors should cause the production line to stop.
if (retval.ErrorOccured) then
    ProductionLine.Running = False;
    InformationBoard.DisplayText = "ERROR: " + retval.Exception.Message;
    InformationBoard.SoundAlarm = True
end if;

Le script appelle la fonction python WidgetScanned :

# pywip/functions.py
from pywip.database import session
from pywip.model import Widget, WidgetHistoryItem
from pywip import validation, StatusMessage
from datetime import datetime

def WidgetScanned(widget_id, scanner_id):
    widget = session.query(Widget).get(widget_id)
    validation.validate_widget_passed_scanner(widget, scanner) # raises exception on error

    widget.history.append(WidgetHistoryItem(timestamp=datetime.now(), action=u"SCANNED", scanner_id=scanner_id))
    widget.last_scanner = scanner_id
    widget.last_update = datetime.now()

    return StatusMessage("OK")

# ... there are a dozen similar functions

Ma question est la suivante: Comment gérer au mieux les sessions SQLAlchemy dans ce scénario? Le serveur d'applications est un processus de longue durée, qui s'exécute généralement plusieurs mois plus tard. Le serveur d'applications est à thread unique.

Actuellement, je le fais de la manière suivante:

J'applique un décorateur aux fonctions proposées au serveur d'applications:

# pywip/iasfunctions.py
from pywip import functions

def ias_session_handling(func):
    def _ias_session_handling(*args, **kwargs):
        try:
            retval = func(*args, **kwargs)
            session.commit()
            return retval
        except:
            session.rollback()
            raise
    return _ias_session_handling

# ... actually I populate this module with decorated versions of all the functions in pywip.functions dynamically
WidgetScanned = ias_session_handling(functions.WidgetScanned)

Question: Le décorateur ci-dessus convient-il au traitement de sessions dans un processus de longue durée? Dois-je appeler session.remove () ?

L'objet de session SQLAlchemy est une session étendue:

# pywip/database.py
from sqlalchemy.orm import scoped_session, sessionmaker

session = scoped_session(sessionmaker())

Je souhaite que la gestion de session ne soit pas gérée par les fonctions de base. Pour deux raisons:

  1. Il existe une autre famille de fonctions, les fonctions séquentielles. Les fonctions de séquence appellent plusieurs des fonctions de base. Une fonction de séquence doit être égale à une transaction de base de données.
  2. Je dois pouvoir utiliser la bibliothèque à partir d'autres environnements. a) À partir d'une application Web TurboGears. Dans ce cas, la gestion de session est effectuée par TurboGears. b) À partir d'un shell IPython. Dans ce cas, commit / rollback sera explicite.

(Je suis vraiment désolé pour la longue question. Mais j’ai ressenti le besoin d’expliquer le scénario. Peut-être pas nécessaire?)

Était-ce utile?

La solution

Le décorateur décrit est adapté aux applications de longue durée, mais vous pouvez rencontrer des problèmes si vous partagez accidentellement des objets entre des demandes. Pour que les erreurs apparaissent plus tôt et ne corrompent rien, il est préférable de supprimer la session avec session.remove ().

try:
    try:
        retval = func(*args, **kwargs)
        session.commit()
        return retval
    except:
        session.rollback()
        raise
finally:
    session.remove()

Ou si vous pouvez utiliser le gestionnaire de contexte avec :

try:
    with session.registry().transaction:
        return func(*args, **kwargs)
finally:
    session.remove()

Au fait, vous pouvez utiliser .with_lockmode ('update') pour la requête afin que votre validation ne s'exécute pas sur des données obsolètes.

Autres conseils

Demandez à votre administrateur WonderWare de vous donner accès à Wonderware Historian. Vous pouvez facilement suivre les valeurs des balises via des appels MSSQL via sqlalchemy que vous pouvez interroger de temps à autre.

Une autre option consiste à utiliser le toolkit archestra pour écouter les mises à jour de tags internes et à déployer un serveur en tant que plate-forme de la galaxie à partir de laquelle vous pouvez écouter.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top