Управление сеансом SQLAlchemy в длительном процессе

Сценарий:

  • Сервер приложений на базе .NET (Wonderware IAS/System Platform) содержит средства автоматизации объекты, которые сообщаются с различным оборудованием в цехе.
  • CPython размещается внутри этого сервера приложений (с использованием Python для .NET).
  • Объекты автоматизации имеют встроенные функции сценариев (с использованием пользовательского языка на основе .NET). Эти скрипты вызывают функции Python.

Функции Python являются частью системы отслеживания незавершенного производства на заводе. Целью системы является отслеживание производимых виджетов в процессе, обеспечение того, чтобы виджеты проходили через процесс в правильном порядке, и проверка выполнения определенных условий в ходе процесса. История создания виджета и состояние виджета хранятся в реляционной базе данных, и именно здесь SQLAlchemy играет свою роль.

Например, когда виджет проходит сканер, программное обеспечение автоматизации запускает следующий сценарий (написанный на пользовательском языке сценариев сервера приложений):

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

Скрипт вызывает функцию 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

У меня вопрос: Как мне лучше всего управлять сеансами SQLAlchemy в этом сценарии? Сервер приложений — это длительный процесс, обычно работающий месяцами между перезапусками. Сервер приложений является однопоточным.

В настоящее время я делаю это следующим образом:

Я применяю декоратор к функциям, которые я делаю доступными для сервера приложений:

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

Вопрос: Подходит ли описанный выше декоратор для обработки сеансов в длительном процессе? Должен ли я вызывать session.remove()?

Объект сеанса SQLAlchemy является сеансом с заданной областью действия:

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

session = scoped_session(sessionmaker())

Я хочу, чтобы управление сеансом не входило в основные функции. По двум причинам:

  1. Существует еще одно семейство функций, функции последовательности. Функции последовательности вызывают несколько основных функций. Одна функция последовательности должна равняться одной транзакции базы данных.
  2. Мне нужно иметь возможность использовать библиотеку из других сред. а) Из веб-приложения TurboGears. В этом случае управление сеансом осуществляется TurboGears. б) Из оболочки IPython. В этом случае фиксация/откат будет явной.

(Я искренне извиняюсь за длинный вопрос. Но я чувствовал, что должен объяснить сценарий. Возможно, в этом нет необходимости?)


person codeape    schedule 14.09.2009    source источник


Ответы (2)


Описанный декоратор подходит для долго работающих приложений, но вы можете столкнуться с проблемами, если случайно разделите объекты между запросами. Чтобы ошибки появлялись раньше и ничего не испортили, лучше сбросить сессию с помощью session.remove().

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

Или, если вы можете использовать диспетчер контекста with:

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

Кстати, вы можете использовать .with_lockmode('update') в запросе, чтобы ваша проверка не выполнялась на устаревших данных.

person Ants Aasma    schedule 14.09.2009

Попросите вашего администратора WonderWare предоставить вам доступ к Wonderware Historian, вы можете довольно легко отслеживать значения тегов с помощью вызовов MSSQL через sqlalchemy, которые вы можете периодически опрашивать.

Другой вариант — использовать набор инструментов archestra для прослушивания обновлений внутренних тегов и развертывания сервера в качестве платформы в галактике, с которой вы можете прослушивать.

person silvrhand    schedule 16.06.2010