Domanda

Scenario:

  • Un server applicazioni basato su .NET ( Wonderware IAS / System Platform ) ospita oggetti di automazione che comunicano con varie apparecchiature sul pavimento della fabbrica.
  • CPython è ospitato all'interno di questo application server (usando Python per .NET ).
  • Gli oggetti di automazione dispongono di funzionalità di scripting integrate (utilizzando un linguaggio personalizzato basato su .NET). Questi script chiamano funzioni Python.

Le funzioni Python fanno parte di un sistema per tracciare i lavori in corso sul piano di fabbrica. Lo scopo del sistema è di tracciare i widget prodotti lungo il processo, assicurarsi che i widget passino attraverso il processo nell'ordine corretto e verificare che determinate condizioni siano soddisfatte lungo il processo. La cronologia di produzione e lo stato del widget sono memorizzati in un database relazionale, qui SQLAlchemy fa la sua parte.

Ad esempio, quando un widget passa attraverso uno scanner, il software di automazione attiva il seguente script (scritto nel linguaggio di scripting personalizzato del server delle applicazioni):

' 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;

Lo script chiama la funzione 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

La mia domanda è: Come posso gestire al meglio le sessioni SQLAlchemy in questo scenario? Il server delle applicazioni è un processo di lunga durata, in genere mesi tra i riavvii. Il server delle applicazioni è a thread singolo.

Attualmente, lo faccio nel modo seguente:

Applico un decoratore alle funzioni che rendo disponibili al server delle applicazioni:

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

Domanda: Il decoratore sopra è adatto per gestire le sessioni in un processo di lunga durata? Devo chiamare session.remove () ?

L'oggetto sessione SQLAlchemy è una sessione con ambito:

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

session = scoped_session(sessionmaker())

Voglio mantenere la gestione della sessione fuori dalle funzioni di base. Per due motivi:

  1. Esiste un'altra famiglia di funzioni, le funzioni di sequenza. Le funzioni di sequenza richiamano molte delle funzioni di base. Una funzione di sequenza dovrebbe essere uguale a una transazione del database.
  2. Devo essere in grado di utilizzare la libreria da altri ambienti. a) Da un'applicazione Web TurboGears. In tal caso, la gestione della sessione viene eseguita da TurboGears. b) Da una shell IPython. In tal caso, il commit / rollback sarà esplicito.

(Mi dispiace davvero per la lunga domanda. Ma sentivo di dover spiegare lo scenario. Forse non è necessario?)

È stato utile?

Soluzione

Il decoratore descritto è adatto per applicazioni di lunga durata, ma è possibile che si verifichino problemi se si condividono accidentalmente oggetti tra le richieste. Per far apparire gli errori in precedenza e non danneggiare nulla, è meglio scartare la sessione con session.remove ().

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

O se è possibile utilizzare il con gestore contesto:

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

A proposito, potresti voler usare .with_lockmode ('update') sulla query in modo che la tua validazione non venga eseguita su dati non aggiornati.

Altri suggerimenti

Chiedi al tuo amministratore WonderWare di darti accesso a Wonderware Historian, puoi monitorare abbastanza facilmente i valori dei tag tramite chiamate MSSQL su sqlalchemy che puoi eseguire il polling ogni tanto.

Un'altra opzione è quella di utilizzare il toolkit di archestra per ascoltare gli aggiornamenti dei tag interni e disporre di un server distribuito come piattaforma nella galassia da cui è possibile ascoltare.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top