flask-sqlalchemy-如何获取独立于请求的数据库会话

时间:2019-11-27 09:46:14

标签: multithreading flask sqlalchemy python-multithreading

我正在寻找获得与请求无关的数据库会话的最佳方法(正确的方法)。

问题如下:我正在构建一个必须访问数据库的Web应用程序。公开的端点接受一个请求,执行第一个工作,然后创建一个线程(将执行艰苦的工作),启动该线程,并使用“作业”的唯一ID答复客户端。同时,线程继续其工作(并且它必须访问数据库),并且客户端可以执行轮询以检查状态。我不是使用专用框架来执行此后台工作,而是仅使用一个简单线程。我只能在任何时候运行一个后台线程,因此我将状态保持为单例。

应用程序是使用应用程序工厂设计https://flask.palletsprojects.com/en/1.1.x/patterns/appfactories/

创建的

我将Gunicorn用作WSGI服务器,将sqlite用作数据库。

代码的基本结构如下(我正在删除业务逻辑和导入,但是概念仍然存在)

api_jobs.py


@bp.route('/jobs', methods=['POST'])
def create_job():

    data = request.get_json(force=True) or {}
    name = data['name']

    job_controller = JobController() # This is a singleton
    job_process = job_controller.start_job(name)
    job_process_dict = job_process.to_dict()

    return jsonify(job_process_dict)

controller.py


class Singleton(type):
    _instances = {}
    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
        return cls._instances[cls]

class JobController(object):
    __metaclass__ = Singleton

    def __init__(self):
        self.job_thread = None

    def start_job(self, name):
        if self.job_thread is not None:
            job_id = self.job_thread.job_id
            job_process = JobProcess.query.get(job_id)
            if job_process.status != 'end':
                raise ValueError('A job process is already ongoing!')
            else:
                self.job_thread = None

        job_process = JobProcess(name)
        db.session.add(job_process)
        db.session.commit() # At this step I create the ID

        self.job_thread = JobThread(db.session, job_process.id)
        self.job_thread.start()

        return job_process

class JobThread(threading.Thread):
       def __init__(self, db_session, job_id):
            self.job_id = job_id
            self.db_session = db_session
            self.session = self.db_session()

       def run(self):
             self.job_process = self.session.query(JobProcess).get(self.job_id)
             self.job_process.status = 'working'
             self.session.commit()
             i = 0
             while True:
                   sleep(1)
                   print('working hard')
                   i = i +1
                   if i > 10:
                         break
             self.job_process.status = 'end'
             self.session.commit()
             self.db_session.remove()

models.py

class JobProcess(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    status = db.Column(db.String(64))
    name = db.Column(db.String(64))

    def to_dict(self):
        data = {
            'id': self.id,
            'status': self.status,
            'name': self.name,
        }
        return data

根据我的理解,调用self.session = self.db_session()实际上没有任何作用(由于sqlalchemy使用注册表,如果我没有记错的话,它也是代理),但这是我的最佳尝试发现创建了一个“新的/分离的/有用的”会话。

我签出https://docs.sqlalchemy.org/en/13/orm/contextual.html#using-thread-local-scope-with-web-applications是为了获得独立于请求的数据库会话,但是即使使用建议的创建新会话工厂的方法(sessionmaker + scoped_session),也无法正常工作。

在代码稍作更改的情况下,我所获得的错误是多个,在此配置中,错误是

DetachedInstanceError: Instance <JobProcess at 0x7f875f81c350> is not bound to a Session; attribute refresh operation cannot proceed (Background on this error at: http://sqlalche.me/e/bhk3)

基本问题仍然存在:是否可以创建一个将驻留在线程内的会话,并且我将负责创建/删除会话?

1 个答案:

答案 0 :(得分:0)

遇到DetachedInstanceError的原因是,您试图将会话从主线程传递到作业线程。 Sqlalchemy使用线程本地存储来管理会话,因此无法在两个线程之间共享单个会话。您只需要在作业线程的run方法中创建一个新会话。