使用Flask-SQLAlchemy

时间:2018-05-30 12:40:07

标签: python mysql flask sqlalchemy

我有一个基于Linux的应用程序,用Python-SQLAlchemy-MySQL构建。现在,我正在使用Flask-SQLAlchemy创建一个WebUI。目前,用户可以使用CLI工具访问数据库。

接下来我会解释我们的设置,这可能有点长,但请耐心等待我!

我们有三个用户级别:readonly,update和admin,每个级别都有一个对应的mysql用户。根据我公司的要求,我们需要将这些密码存储在加密文件中。例如,如果用户foo具有只读访问权限,则在其主目录中,它们具有仅对其可读的文件,其中包含字典。使用自定义python模块,我们传入应用程序的名称,并从最高访问权限(admin)到最低权限(readonly)迭代每个mysql用户。这将读取用户的加密文件,直到找到最高访问级别的mysql用户。然后它将解密在该文件中找到的密码。此代码方便地包含在一个函数中,该函数将构建mysql数据库连接字符串w /找到的mysql用户和解密密码以与SQLAlchemy一起使用。这非常有效。

我需要使用Web UI遵循相同的模式。根据公司要求,Web UI需要通过全局登录进行门控。这通过安装一些将与本地SQLite DB交互的自定义​​模块来工作。数据库维护用户列表,允许的上下文(即允许他们在此服务器上访问的Web应用程序)和可选组。这与apache / wsgi相关联。当请求进入时,将针对此数据库检查用户。如果用户尚未登录,则会将其重定向到全局登录页面。成功登录后,它们会被退回到原始网址。 Apache设置为将请求转发到wsgi的应用程序。可以使用request.environ['REMOTE_USER']访问用户ID。

对于Web UI,我在apache的$ HOME中创建了一个apache readonly文件,该文件存储了admin,update和readonly用户的加密密码。这是为了获取请求用户有权使用的mysql用户而获取加密密码的文件。

问题在于:在收到请求用户之前,我无法设置app.config['SQLALCHEMY_DATABASE_URI']并致电db = SQLAlchemy(app)

我已经设法通过在@app.before_request函数中粘贴此代码来使其工作,但是当我打开调试模式时,我得到一个指向行db = SQLAlchemy(app)的500错误:

AssertionError: A setup function was called after the first request was 
handled.  This usually indicates a bug in the application where a module was 
not imported and decorators or other functionality was called too late.
To fix this make sure to import all your view modules, database models and 
everything related at a central place before the application starts serving 
requests.

这让我相信以下不是正确的方法。有谁能建议更好的方法?如果这是唯一的方法,只要我关闭调试模式就会出现问题吗?

wsgi.py:

import os
import sys
import logging

logging.basicConfig(stream=sys.stderr)

sys.path.append(os.path.dirname(os.path.abspath(__file__)))

from app.flaskapp import app as application

应用程序/ flaskapp.py:

import os
import re
import json

from flask import g, redirect, Flask, render_template, request, Response, 
    session, Blueprint
from flask.ext.sqlalchemy import SQLAlchemy
# this is models from the existing application
import foo.db.orm as orm
# module used to create the db connection string, establish connection
# to database, etc for CLI level tools
# used here just to get the mysql connection string
from foo.db.dbcontext import DBContext
# module used to query the company authentication gate
# SQLite database
from company.turnstile import Turnstile

app = Flask(__name__)

# if debug = True here, get 500 error
app.config.from_envvar('APP_CONFIG_FILE')

# SQLALCHEMY_DATABASE_URI and db will be set in
# set_database() before_request   
app.config['SQLALCHEMY_DATABASE_URI'] = None
db = None

@app.before_request
def set_database():

    ''' Set the database connection string (with decrypted
    password) and db object by looking up the request user\'s
    access group in Turnstile (which is a mysql login name).
    '''    
    global db
    # get user id from the request object forwarded
    # by apache from company auth gate
    userid = request.environ.get('REMOTE_USER', None)

    # the following is irrelevant redacted code that queries the company 
    # auth database to get the user's access level. This is
    # simply the corresponding mysql user (stores string in dbuser)
    <redacted_code>
    # end redacted

    # set the db uri with the appropriate access level
    # this will decrypt the password from apache's readonly file and create 
    # the mysql connection string
    app.config['SQLALCHEMY_DATABASE_URI'] = DBContext.getDatabaseUrl(dbuser)
    db = SQLAlchemy(app)

    # now that we have the db, make sure the user is in the application db
    user = orm.AuthUser.getOne(db.session, username=userid)
    if not user:
        # redirect to error page
        # ...

    # inject user and dbuser into g
    g.user   = user
    g.dbuser = group

@app.route('/')
def index():
    # returns "num users: 4" in response
    return 'num users: %d' % len(orm.AuthUser.getAll(db.session))

if __name__ == '__main__':
    app.run()

一方面的问题:我将使用蓝图构建我的项目,其中许多都需要数据库集。我计划将set_database函数移动到app/util/hooks.py并将其转换为装饰函数,例如:

from flask import Blueprint, render_template
import app.util.hooks as hooks

mod = Blueprint('dashboard', __name__)

@mod.route('/dashboard')
@hooks.set_database
def main():
    return render_template('dashboard/index.html')

在试运行中,我能够分别在flask.current_app中使用g.db = SQLAlchemy(current_app)app代替dbset_database,就像它在在flaskapp.py内。将db设置为g属性是最好的方法吗?我无法从db app.flaskapp app/util/hooks.py导入environ

更新 我回过头来想通知我可以将这段代码移到中间件中,因为我已经使用了wsgi。由于app.wsgi_app可以在中间件中访问,我需要的唯一信息就在那里,我可以在那里移动我的“用户访问组”查找代码,并将动态生成的数据库连接字符串设置为应用程序的“SQLALCHEMY_DATABASE_URI”配置。我使用推荐的中间件调用重新分配app.wsgi_app = middleware.SimpleMiddleWare(app.wsgi_app)app = middleware.SimpleMiddleware(app)而不是app),但在这种情况下我需要传入from flask_sqlalchemy import SQLAlchemy db = SQLAlchemy() 来设置数据库连接字符串:

database.py

from flask import Flask, ...
from app.middleware.auth_middleware import AuthMiddleware
from app.database import db

app = Flask(__name__)
app.wsgi_app = AuthMiddlware(app, app.wsgi_app)
db.init_app(app)
...

flaskapp.py

from foo.db.dbcontext import DBContext
# module used to query the company authentication gate
# SQLite database
from company.turnstile import Turnstile
from werkzeug.exceptions import Unauthorized

class AuthMiddleware(object):

    def __init__(self, app, wsgi_app):
        self.app = app
        self.wsgi_app = wsgi_app

    def __call__(self, environ, start_response):
        # get userid from environ forwarded by apache from
        # company auth gate
        userid = environ.get('REMOTE_USER', None)

        # the following is irrelevant redacted code that queries the company 
        # auth database to get the user's access level. This is
        # simply the corresponding mysql user (stores string in dbuser)
        <redacted_code>
        # end redacted

        # set the db url with the appropriate access level
        self.app.config['SQLALCHEMY_DATABASE_URI'] = DBContext.getDatabaseUrl(dbuser)

        return self.wsgi_app(environ, start_response)

中间件/ auth_middleware.py

@hooks.set_database

使用这种技术,我不再需要用AssertionError来装饰需要进行数据库访问的每个路由,而且我没有使用调试模式的SELECT b.BillOfLadingNumber, b.ShipFromCity, b.ShipFromState, b.ShipFromZip, b.DeliveryDate, b.ShipToCity, b.ShipToState, b.ShipToZip, Sum(i.Weight) as Total_Weight, Sum(i.Pieces) as Piece_Count FROM vw_BillsOfLading b Left Outer join vw_Items i on i.TransactionId = b.Id WHERE BillOfLadingNumber in ('100277','100310','100814','100867','101118','101124','101530','101630','101657','101694','101760','102153','102241','102276','102284') GROUP BY b.BillOfLadingNumber,b.ShipFromCity, b.ShipFromState, b.ShipFromZip, b.DeliveryDate, b.ShipToCity, b.ShipToState, b.ShipToZip

0 个答案:

没有答案