Open rhaamo opened 8 years ago
I am using blueprints too and I had the same issue, the context is "unknown". I am able to get non-empty Context if my WSGI app is wrapped by ReverseProxied middleware (http://flask.pocoo.org/snippets/35/) -- but the Context is then always pointing to the middleware and not the actual code.
Flask (0.11.1) Flask-DebugToolbar (0.10.0) Flask-SQLAlchemy (2.1) SQLAlchemy (1.0.12)
See below an app for reproducing the issue. There are several "tunables" which all are True except "classic" in my production system: reverse_proxied, classic, profiled, blueprinted. Only reverse_proxied seems to have impact on the Context in the debug toolbar.
If I would put all modules into one (mock.py), the Context column would display the correct function and line. So the culprit is perhaps hidden in the module imports -- but I am not able to find it.
mock.py:
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from mock_app import app, db, classic
import mock_view
from mock_model import BaseModel, User
if __name__ == "__main__":
if classic:
session = db.session
meta = db
args = []
else:
engine = create_engine("sqlite:///mock.db")
session = sessionmaker(bind=engine)()
meta = BaseModel.metadata
args = [engine]
meta.drop_all(*args)
meta.create_all(*args)
session.add(User(id=1, name="John"))
session.commit()
app.run()
mock_model.py:
from sqlalchemy.ext.declarative import declarative_base
from mock_app import db, classic
if classic:
BaseModel = db.Model
else:
BaseModel = declarative_base()
if classic:
Integer = db.Integer
Column = db.Column
String = db.String
else:
from sqlalchemy import Integer, Column, String
class User(BaseModel):
__tablename__ = "users" # Not needed by classic
id = Column(Integer, primary_key=True)
name = Column(String)
mock_app.py:
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from werkzeug.contrib.profiler import ProfilerMiddleware
reverse_proxied = True # Context is empty if False and ./mock_app.py:27 (__call__) if True
classic = True # The Flask-SQLAlchemy way
profiled = False
class ReverseProxied:
def __init__(self, wsgi_app):
self.app = wsgi_app
def __call__(self, environ, start_response):
script_name = environ.get("HTTP_X_SCRIPT_NAME", "")
scheme = environ.get("HTTP_X_SCHEME", "")
if script_name:
environ["SCRIPT_NAME"] = script_name
path_info = environ["PATH_INFO"]
if path_info.startswith(script_name):
environ["PATH_INFO"] = path_info[len(script_name):]
if scheme:
environ["wsgi.url_scheme"] = scheme
return self.app(environ, start_response)
app = Flask(__name__)
if reverse_proxied:
app.wsgi_app = ReverseProxied(app.wsgi_app)
if profiled:
app.config["PROFILE"] = True
app.wsgi_app = ProfilerMiddleware(app.wsgi_app, restrictions=[5])
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///mock.db"
if classic:
db = SQLAlchemy(app)
else:
db = SQLAlchemy(session_options={"autocommit": True})
db.init_app(app)
from flask_sqlalchemy import _EngineDebuggingSignalEvents # noqa
_EngineDebuggingSignalEvents(db.get_engine(app), app.import_name).register() # Display SQL queries issued by SQLAlchemy
from flask_debugtoolbar import DebugToolbarExtension
app.debug = True
app.config["SECRET_KEY"] = "123"
app.config["DEBUG_TB_PROFILER_ENABLED"] = True
app.config["SQLALCHEMY_RECORD_QUERIES"] = True
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = True
debug_toolbar = DebugToolbarExtension(app)
app.extensions["debugtoolbar"] = debug_toolbar
mock_view.py:
from flask import Blueprint
from mock_app import app, db
from mock_model import User
blueprinted = False
if blueprinted:
blueprint = Blueprint("blueprint", __name__, template_folder="templates", url_prefix="")
else:
blueprint = app
@blueprint.route("/")
def hello():
username = db.session.query(User.name).filter_by(id=1).scalar()
return "<html><head/><body>Hello {}!</body></html>".format(username)
if blueprinted:
app.register_blueprint(blueprint)
Here's how Flask-SQLAlchemy generates the context(_calling_context): From the most inner frame which issues a db query, it goes upwards to find the frame that matches the "app_path", and set that as the context.
app_path
is what you pass to Flask
when you create the application. Often times you'd use something like app = Flask(__name__)
in your app.py
. And this is where things go south: your app_path
is now something like project.app
, but the frame that issues a db query is probably project.views
. _calling_context
matches the names by checking name.startwith(app_path + '.')
. project.views
never passes that check.
To fix this, simply explicitly pass the name of your app to Flask
, like app = Flask('project')
. Now app_path
is project
and it can correctly match frames in your code.
Wow, thank you!
My Flask application resides in a module app.py in a package called the same as my application. The SQLAlchemy queries are located in several modules (using the "gateway" pattern - DB calls/persistence layer are decoupled both from the presentation/view and the business logic) located in sub-packages (=blueprints ="micro-services") of the main application package.
Changing the name of the Flask app from__name__
to "name of application package" did the trick. So hard-coding the top-level package name works regardless of the inner structure of the Flask application and regardless of the WSGI middleware and blueprints.
Could you please update the documentation (especially https://flask-debugtoolbar.readthedocs.io/en/latest/#usage ) with the hard-coded value + could you please add a warning that such approach may be necessary and why? (or even better: could you change the discovery code to be compatible with the default __name__
approach? the __name__
is used even on Flask's homepage, see http://flask.pocoo.org/)
The context column only shown
<unknown>
on all of my pages. I'm using Blueprints, can this cause issues ? Versions used (not only but related to debugtoolbar):Config extract:
Edit: making queries from the "main" app file shows context, anything outside seems
<unknown>