我有一个基于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
代替db
和set_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
。