Pregunta

Escenario:

  • Un servidor de aplicaciones basado en .NET ( Wonderware IAS / System Platform ) aloja objetos de automatización que se comunican con diversos equipos en la fábrica.
  • CPython está alojado dentro de este servidor de aplicaciones (usando Python para .NET ).
  • Los objetos de automatización tienen una funcionalidad de scripting incorporada (usando un lenguaje personalizado basado en .NET). Estos scripts llaman funciones de Python.

Las funciones de Python son parte de un sistema para rastrear Work-In-Progress en la fábrica. El propósito del sistema es rastrear los widgets producidos a lo largo del proceso, asegurar que los widgets pasen por el proceso en el orden correcto y verificar que se cumplan ciertas condiciones a lo largo del proceso. El historial de producción del widget y el estado del widget se almacenan en una base de datos relacional, aquí es donde SQLAlchemy desempeña su papel.

Por ejemplo, cuando un widget pasa un escáner, el software de automatización activa el siguiente script (escrito en el lenguaje de script personalizado del servidor de aplicaciones):

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

El script llama a la función 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

Mi pregunta es: ¿Cómo administro mejor las sesiones de SQLAlchemy en este escenario? El servidor de aplicaciones es un proceso de larga duración, que generalmente dura meses entre reinicios. El servidor de aplicaciones es de un solo subproceso.

Actualmente, lo hago de la siguiente manera:

Aplico un decorador a las funciones que pongo a disposición del servidor de aplicaciones:

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

Pregunta: ¿El decorador anterior es adecuado para manejar sesiones en un proceso de larga duración? ¿Debería llamar a session.remove () ?

El objeto de sesión SQLAlchemy es una sesión de alcance:

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

session = scoped_session(sessionmaker())

Quiero mantener la gestión de la sesión fuera de las funciones básicas. Por dos razones:

  1. Hay otra familia de funciones, funciones de secuencia. Las funciones de secuencia llaman a varias de las funciones básicas. Una función de secuencia debe ser igual a una transacción de base de datos.
  2. Necesito poder usar la biblioteca desde otros entornos. a) Desde una aplicación web TurboGears. En ese caso, TurboGears realiza la gestión de la sesión. b) Desde un shell de IPython. En ese caso, commit / rollback será explícito.

(Realmente lamento la larga pregunta. Pero sentí que necesitaba explicar el escenario. ¿Quizás no sea necesario?)

¿Fue útil?

Solución

El decorador descrito es adecuado para aplicaciones de larga ejecución, pero puede tener problemas si comparte accidentalmente objetos entre solicitudes. Para que los errores aparezcan antes y no corrompan nada, es mejor descartar la sesión con session.remove ().

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

O si puede usar el con administrador de contexto:

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

Por cierto, es posible que desee utilizar .with_lockmode ('actualizar') en la consulta para que su validación no se ejecute en datos obsoletos.

Otros consejos

Pídale a su administrador de WonderWare que le dé acceso al Wonderware Historian, puede rastrear los valores de las etiquetas con bastante facilidad a través de llamadas MSSQL a través de sqlalchemy que puede sondear cada cierto tiempo.

Otra opción es usar el kit de herramientas de la orquesta para escuchar las actualizaciones de etiquetas internas y tener un servidor implementado como una plataforma en la galaxia desde la que pueda escuchar.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top