Python - REST сервер с Flask - как получить доступ к базе данных в разных потоках? - PullRequest
0 голосов
/ 18 марта 2020

У меня есть большая программа, в которой while True l oop в основном выполняет много задач, а также та же программа должна выступать в качестве REST-сервера, например, получать сообщения через POST и предоставлять информацию клиентам через ответы на GETs. Лучший способ объяснить мою текущую дилемму - это пример скелета:

Main.py:

# Main.py

import collections
import Database as Database

flaskApp = Flask(__name__)

messageQueue = collections.deque()

def main():
    global messageQueue

    Database.init()

    # start Flask on a separate thread so main can keep going
    # (i.e. calling flaskApp.run() here not on a separate thread would block)
    flaskAppThread = threading.Thread(target=flaskApp.run)
    flaskAppThread.start()

    # various other init stuff here

    # beginning of main loop that does various things and runs as long as the program is running
    while True:

        # other item 1 here
        # other item 2 here
        # etc.

        # based on the results of item 1 and 2, add stuff to the database
        Database.addStuff(stuffToAdd)


        importantInfo = Database.getImportantInfo()

        # do something with important info here

        # if the messageQueue is not empty, process any messages
        if messageQueue:
            # process messageQueue contents here

            # clear the queue once done processing queued up messages
            messageQueue = collections.deque()
        # end if

        # other item 3 here
        # other item 4 here
        # etc.

        if someExitCondition:
            break
        # end if

    # end while

    # close down hardware I/O, threads, database connections, etc. here

# end main

# Flask REST function to make a list of stuff available to client upon request
@flaskApp.route('/get_list_of_stuff', methods=['GET'])
def restGetListOfStuff():
    listOfStuff = Database.getListOfStuff()
    return jsonify({'listOfStuff': listOfStuff})
# end function

# Flas REST function to receive a message from client via POST, the message will contain some sort of information
@flaskApp.route('/message', methods=['POST'])
def restMessage():
    message = request.json['message']
    messageQueue.append(message)
    return jsonify( { 'message': '' } ), 200
# end function

if __name__ == '__main__':
    main()

Database.py:

# Database.py

import sqlite3

class Database(object):

    @staticmethod
    def init():
        Database.conn = sqlite3.connect('/path/to/database.sqlite')
        Database.cur = Database.conn.cursor()
    # end function

    @staticmethod
    def addStuff(stuffToAdd):
        Database.cur.execute('INSERT INTO SomeTable ' + str(stuffToAdd))
    # end function

    @staticmethod
    def getImportantInfo():
        Database.cur.execute('SELECT * FROM ImportantInfoTable')
        results = Database.cur.fetchall()
        return results
    # end function

    @staticmethod
    def getListOfStuff():
        Database.cur.execute('SELECT * FROM TableWithStuff')
        results = Database.cur.fetchall()
        return results
    # end function

    # many other database functions here

# end class

Обратите внимание на следующее:

-Main должен получать REST-сообщения от клиентов через POST, затем изменять поведение на основе полученных сообщений

-Main должен предоставлять информацию клиентам по запросу через REST GET

-Main имеет некоторое время True l oop, которое всегда работает во время работы программы и должно выполнять различные действия, поэтому нет никаких строк о someActivity.run(), какой блок может быть запущен в main в главном потоке

- в настоящее время я использую SQLite в качестве базы данных, при необходимости могу изменить на MySQL (в настоящее время у меня сложилось впечатление, что это может не помочь)

- Очередь сообщений должна быть добавлена ​​функциями Flask и считана с помощью main, поэтому здесь нельзя использовать мультипроцессы, потому что тогда в памяти очереди сообщений будут отдельные копии вместо одной копии, которую могут оба ee

С вышеупомянутой архитектурой проблема заключается в том, что программа запускается в функции restGetListOfStuff на линии listOfStuff = Database.getListOfStuff() Я получаю эту ошибку:

sqlite3.ProgrammingError: SQLite objects created in a thread can only be used in that same thread. The object was created in thread id 140597538834240 and this is thread id 140593565001472.

Я понимаю, что это за ошибка утверждая, что существуют отдельные потоки, один в main, а другой в restGetListOfStuff, оба обращаются к базе данных, и что SQLite не позволяет этого.

Поэтому мой вопрос, как эту архитектуру можно изменить на избежать этой ошибки?

Я искал волшебный c способ заставить линию в main:

importantInfo = Database.getImportantInfo()

работать в том же потоке, что и flaskApp но я не смог найти способ сделать это до сих пор. Я не уверен, что это даже возможно. Я попытался создать отдельную функцию, украшенную @flaskApp.route, которая вызывала Database.getImportantInfo() и вызывала ее из main, но это не сработало, потому что эта функция все еще выполняется в том же потоке, что и main, если вызывается из main.

Какие у меня есть варианты? Будет только одна база данных, поэтому мне очень нравится, что есть только один класс базы данных, который фактически является c (шаблоны проектирования люди называют это «синглтоном»). Есть ли способ сохранить эту общую архитектуру, которая мне нравится, но обойти ошибку SQLite в отдельном потоке?

Большинство решений, которые я могу найти, включают в себя такие вещи, как открытие отдельного соединения с базой данных внутри каждого Flask функция, которая включала бы дублирование кода базы данных как в основной, так и в основной базе данных, что я бы предпочел не делать, а также я обеспокоен тем, что открытие и закрытие многих подключений к базе данных может вызвать проблемы с производительностью, поэтому я бы предпочел не go в этом направлении, если этого можно избежать.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...