Pergunta

Cenário:

  • servidor de aplicativos baseados em .NET A ( Wonderware IAS / System Platform ) anfitriões objetos de automação que se comunicam com vários equipamentos no chão de fábrica.
  • CPython está hospedado dentro deste servidor de aplicativos (usando Python for .NET ).
  • Os objetos de automação têm funcionalidade de script embutido (usando um costume, linguagem baseada em .NET). Esses scripts chamar funções Python.

As funções Python são parte de um sistema para controlar o trabalho em andamento no chão de fábrica. A finalidade do sistema é para rastrear os widgets produzidos ao longo do processo, asseguram que os widgets passar pelo processo na ordem correcta, e verificar se certas condições são satisfeitas ao longo do processo. A história da produção widget e estado do widget é armazenado em um banco de dados relacional, isto é onde SQLAlchemy desempenha o seu papel.

Por exemplo, quando um widget passa um scanner, o software de automação desencadeia o seguinte roteiro (escrito em linguagem de script personalizada do servidor de aplicativos):

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

O script chama a função WidgetScanned python:

# 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

A minha pergunta é: Como faço para melhor gerenciar as sessões de SQLAlchemy neste cenário O servidor de aplicativos é um processo de longa duração, normalmente executando meses entre reiniciado?. O servidor de aplicativos é single-threaded.

Atualmente, eu fazê-lo da seguinte maneira:

eu aplicar um decorador para as funções que eu faço disponível para o servidor de aplicativos:

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

Pergunta:? é o decorador acima adequado para lidar com sessões em um processo de longa duração Devo chamar session.remove()

O objeto de sessão SQLAlchemy é um escopo de sessão:

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

session = scoped_session(sessionmaker())

Eu quero manter o gerenciamento de sessão fora das funções básicas. Por duas razões:

  1. Há uma outra família de funções, funções de seqüência. As funções de sequência chamar várias das funções básicas. Uma função de seqüência deve ser igual a uma transação de banco de dados.
  2. Eu preciso ser capaz de usar a biblioteca de outros ambientes. a) A partir de um aplicativo TurboGears web. Nesse caso, o gerenciamento de sessão é feito por TurboGears. b) A partir de um shell IPython. Nesse caso, commit / rollback será explícita.

(Eu realmente sinto muito para a longa pergunta. Mas eu senti que eu precisava para explicar o cenário. Talvez não seja necessário?)

Foi útil?

Solução

O decorador descrito é adequado para aplicações de execução longa, mas você pode ter problemas se você acidentalmente compartilhar objetos entre pedidos. Para fazer com que os erros aparecem mais cedo e não corrupto qualquer coisa é melhor para descartar a sessão com session.remove ().

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

Ou se você pode usar o gerenciador contexto with:

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

A propósito, você pode querer usar .with_lockmode('update') na consulta para que o seu validar não é executado em dados obsoletos.

Outras dicas

Peça ao administrador WonderWare para lhe dar acesso à Wonderware Historian, você pode acompanhar os valores das marcas muito facilmente via MSSQL chamadas de sqlalchemy que você pode pesquisar cada tantas vezes.

Outra opção é usar o kit de ferramentas ArchestrA para ouvir as atualizações tag interno e ter um servidor implantado como uma plataforma na galáxia que você pode ouvir a partir de.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top