gerenciamento de sessão SQLAlchemy no processo de longa duração
-
07-07-2019 - |
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:
- 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.
- 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?)
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.