-
-
Notifications
You must be signed in to change notification settings - Fork 902
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Reload the DB URL/bind config when calling db.create_all
#1172
Comments
This is intentional. Factory should create app, set config, then init extensions. def create_app(test_config=None):
app = Flask(__name__)
app.config.from_mapping(default config values)
if test_config is None:
app.config.from_prefixed_env()
else:
app.config.from_mapping(test_config)
db.init_app(app)
# blueprints, etc.
return app |
There's a few other things that are subtly wrong with your test setup. You call |
The problem is that Flask-SQLAlchemy allows configuring the entire set of engine options, not just a name and URL. Once engines are created they're part of SQLAlchemy, not directly configurable. Determining if any engine configuration has changed is not trivial. It would have to be checked on each access to each engine, even though this would be unnecessary overhead for every request except in tests that change engine config. This is a problem for any extension that manages third-party objects that live longer than the app/request context, not just Flask-SQLAlchemy. It's a primary issue that using an app factory solves. |
The app factory does solve the problem. But in this way, we force users to use the factory to create an app. What if they just need to create a simple project with only a single module Whether or not this issue can be resolved, I think it's necessary to add a tip in the docs to indicate the engine-related configuration is not rewriteable (#1175). And I'll update my Flask tutorial/book and introduce the app factory before the test chapter. Thanks for pointing out the errors in my test setup. I pushed the request context because I want to use |
It might be possible to allow Perhaps a |
I would go with the I believe we shouldn't limit the way users create the app, especially as an essential Flask extension. |
@greyli Checkout the docs for Flask extension development for the following guideline
You're not expected to simply know this, unless you've read the section on flask extension development, which I think is a source of confusion among developers who just use flask extensions. Ignore the guideline at your own peril, as many Flask extensions are predicated on this kind of logic and the results will lack a certain determinism when you try to circumvent this rule. As someone who likes to break rules I experimented with this idea and there was nothing fruitful to come of it. The main problem is that unless you make the assumption that an app context will always be available and app config values are consistently looked up from current_app.config only across all extensions and your app, you're going to see behavior that is not deterministic manifesting as flakiness in your testing suite. What I do is this. I use an app factory, that app factory takes a list of extensions to initialize (call init_app) on before returning. I instantiate the app in init.py and again in a pytest |
I would also like to see @pytest.fixture(scope="session")
def db(worker_id: str) -> Generator[None, None, None]:
if worker_id != "master": # pragma: no cover
db_url_worker = f"{str(settings.DATABASE_URL)}-{worker_id}"
assert "test" in db_url_worker # noqa: S101
# Update the setting for the flask app:
flask_app.config["SQLALCHEMY_DATABASE_URI"] = db_url_worker
settings.DATABASE_URL = Secret(db_url_worker)
# fsa.recreate_engines(flask_app) For normal, non parallel testing I don't need this craziness as I can se the URL statically in a |
But for that, you should be setting the config before you init the database. That's what the app factory is for, your app fixture would set the config when creating the app, and you'd just use the @pytest.fixture
def app(worker_id):
...
yield create_app({"SQLALCHEMY_DATABASE_URI": db_url_worker}) |
The issue is that the I have the app defined at module level: app = Flask(__name__)
app.config.from_mapping(config)
...
admin = Admin(app, name="MyApp", template_mode="bootstrap3")
....
login_manager = LoginManager(app)
@app.route("/login/")
def login():
.... which would require a massive refactoring to move all that to a factory. And even if I could move that to a factory, the issue is I am sub-mounting the WSGI flask app under an ASGI app and the tests are running against the ASGI app so I actually don't even have an app fixture nor could I directly use it. |
Yes, that is the issue, you should use an app factory since you want to have tests with different config. And you could still use a factory, you would have a factory for the top-level app that also configured the mounted app. |
Right but even with an app factory, I would still have a problem since the ASGI app is not created through a factory and even if it were, it isn't clear how I would (easily) inject the new WSGI app into it. |
I mean, I can't help you with refactoring your specific case here, but I guarantee you it's possible to refactor your code to use a factory for what you need. |
After 3.0, the engines is created in
init_app
, so there is no way to update the configuration after instantiating the extension object. However, we do need to update the config in some use cases. For example, in a simple application without using the app factory:In the test, you will want to use a different database URL, so you reset the config
SQLALCHEMY_DATABASE_URI
:It works in 2.x, but not 3.x.
I think we need to reload the config (
SQLALCHEMY_DATABASE_URI
andSQLALCHEMY_BINDS
) before callingcreate_all
, but it seems there is no way to update the engine URL after creating it.The text was updated successfully, but these errors were encountered: