ЗОДБ с Торнадо

У меня есть небольшое веб-приложение, созданное с использованием Tornado, где я хотел бы использовать ZODB для хранения данных. Согласно документации ZODB, многопоточные программы поддерживаются, но они должны запускать новое соединение для каждого потока. Я думаю, это означает, что я должен сделать что-то вроде

### On startup
dbFilename = os.path.join(os.path.dirname(os.path.abspath(__file__)), "Data.fs")
db = DB(FileStorage(dbFilename))

### Example handler
class Example(tornado.web.RequestHandler):
    def get(self):
        try:
            conn = db.open()
            root = conn.root()
            ### do stuff with root here
            root._p_changed = 1  ## Include these lines for writes
            transaction.commit() ## on sub-elements
        finally:
            conn.close()

Во-первых, новое соединение все еще необходимо для всех обработчиков, взаимодействующих с БД, или только для тех, которые делают запись? Было бы разумно установить одно соединение при запуске и использовать его для всех моих операций чтения, а затем выполнять вышеуказанное соединение "Петь и танцевать" только тогда, когда мне нужно что-то написать?

Во-вторых, каков идиоматический способ абстрагирования этого паттерна в Python? У меня есть что-то вроде

def withDB(fn):
    try:
        conn = db.open()
        root = conn.root()
        res = fn(root)
        root._p_changed = 1
        transaction.commit()
        return res
    finally:
        conn.close()

def delete(formName):
    def local(root):
        ### do stuff with root here
    return withDB(local)

в виду, но это, вероятно, мой показ на Лиспе.

Общая проверка на подходе также будет приветствоваться.

1 ответ

Решение

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

Так что для соединения я бы использовал пул на поток, предоставленный ZODB.DBвозможно кэширование соединения по запросу (как делает pyramid_zodbconn).

В ваших обработчиках запросов вы можете использовать менеджер транзакций в качестве менеджера контекста:

class Example(tornado.web.RequestHandler):
    def get(self):
        connection = some_connection_pool.get_connection()
        with transaction.manager:
            root = conn.root()
            res = fn(root)
            root._p_changed = 1

С использованием transaction.manager Объект как менеджер контекста гарантирует, что транзакция запускается при входе и фиксируется при выходе без исключения, прерывается при выходе с исключением.

Вы также можете создать менеджер контекста для обработки соединения ZODB:

from contextlib import contextmanager

@contextmanager
def zodbconn(db):
    conn = db.open()
    yield conn.root()
    conn.close()

затем используйте это как менеджер контекста вместе с менеджером транзакций:

class Example(tornado.web.RequestHandler):
    def get(self):
        with zodbconn(db) as root, transaction.manager:
            res = fn(root)
            root._p_changed = 1

Этот менеджер контекста берет объект базы данных и возвращает корневой объект, автоматически закрывая соединение при повторном выходе из контекста.

Другие вопросы по тегам