flask-marshmallow
flask-marshmallow copied to clipboard
Usage with Flask factory pattern?
I wasn't able to find this in the docs, but what is the recommended way to use this library with the Flask factory pattern?
My approach was to initialize the Marshmallow object in my models file and then in register_extensions
call Marshmallow.init_app
like so:
# models.py
from flask_marshmallow import Marshmallow
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
ma = Marshmallow()
def register_extensions(app: Flask):
db.init_app(app)
with app.app_context():
db.create_all()
db.session.commit()
ma.init_app(app)
Is this the recommended approach? It seems to work while trying it out from the flask shell.
That's how I'm doing it as well. Seems to be how people do it from my research. I haven't experienced any problems in the project.
@lnunno Your code example looks correct. I would certainly merge a PR adding this to the docs. =)
@sloria so I've spent a bunch of time digging on this after first landing on issue #44
From reviewing how this all works it appears that as of today, the init_app pattern mostly doesn't work the way people would expect. My expectation would be:
# my_marshmallow_models.py
from flask_marshmallow import Marshmallow
ma = Marshmallow()
from my_dbmodels import SomeModel
class SomeModelSchema(ma.ModelSchema):
class Meta:
model = SomeModel
Then in my factory:
from my_db_module import db
from my_marshmallow_models import ma
app = Flask(__name__)
db.init_app()
ma.init_app(app)
This doesn't work though. It seems however that unless I run init_app before defining my schema, the schema then can't successfully use the session, it's still using the DummySession (at least when resolving the related field). I suspect this is because of the way the monkey-patch occurs and the fact that this is all implemented with metaclasses but I'm not quite clear on how it works. with conventional classes the following test case works:
class ParentClass:
some_attr = 1
class ChildClass(ParentClass):
pass
a = ChildClass()
print(a.some_attr)
ParentClass.some_attr = 2
b = ChildClass()
print(a.some_attr)
print(b.some_attr)
# output:
# 1
# 2
# 2
I'm a bit out of my depth here though on the meta classes, I see the release note for marshmallow 2.0.0b1 mentions OPTIONS_CLASS being validated earlier but I don't really understand the lifecycle of all these variables once metaclasses are involved.
Should this monkey patch actually be handled changing DummySession into a proxy class of some kind so that it's resolved later?
Requiring app construction before model definition becomes extremely restrictive in terms of import orders and file structure for large apps and mostly makes the init_app non-functional
So the way I have it working is to first create the marshmallow instance in a separate module:
# schema.py
import flask_marshmallow
ma = flask_marshmallow.Marshmallow()
Your database module:
# database.py
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
Your model class:
# models.py
from app.schema import ma
from app.database import db
class MyModel(db.Model):
""" Model definition """
class MySchema(ma.ModelSchema):
class Meta:
model = MyModel
Then in your factory:
# __init__.py
from flask import Flask
def create_app():
flask_app = Flask(__name__)
# Initialize database connector
from app.database import db
db.init_app(flask_app)
# Register Marshmallow parsing
from app.schema import ma
ma.init_app(flask_app)
# Now import any module that uses your model declarations
from app.models import MyModel
Don't think this is really an issue to be honest. One initializes flask marshmallow the same way you would any other flask plugin, when it comes to the app factory structure. I Highly recommend taking a look at this if you want a more in-depth description of what I mean: https://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-xv-a-better-application-structure
# app.__init__.py
from flask import Flask
from flask_marshmallow import Marshmallow
from flask_sqlalchemy import SQLAlchemy
from config import Config
db = SQLAlchemy()
ma = Marshmallow()
def create_app():
app = Flask(__name__)
app.config.from_object(Config)
db.init_app(app)
ma.init_app(app)
return app
You can use marshmallow and sqlalchemy we defined above like so:
# app.database.person.py
from app import db, ma
class Person(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(16), nullable=False)
class PersonSchema(ma.ModelSchema):
class Meta:
model = Person
Don't think this is really an issue to be honest. One initializes flask marshmallow the same way you would any other flask plugin, when it comes to the app factory structure. I Highly recommend taking a look at this if you want a more in-depth description of what I mean: https://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-xv-a-better-application-structure
# app.__init__.py from flask import Flask from flask_marshmallow import Marshmallow from flask_sqlalchemy import SQLAlchemy from config import Config db = SQLAlchemy() ma = Marshmallow() def create_app(): app = Flask(__name__) app.config.from_object(Config) db.init_app(app) ma.init_app(app) return app
You can use marshmallow and sqlalchemy we defined above like so:
# app.database.person.py from app import db, ma class Person(db.Model): id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(16), nullable=False) class PersonSchema(ma.ModelSchema): class Meta: model = Person
I just attempted this and I get an error along the lines of ImportError: cannot import name 'ma' from partially initialized module '<app_name>'
. I'm guessing this is because my app imports a blueprint, which imports the schema, so we have a circular import. So unless you never actually plan to use your schema anywhere, I don't think this is workable.
For anyone facing the same error as @amaschas , I think the correct way getting around this is by importing your blueprints inside of the create_app()
function, probably right before you call the register_blueprint()
function. This way, everything else initializes and you don't have the partially initialized error. My understanding is that this is the official way of using blueprints in the factory pattern.