Skip to content
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

Usage with Flask factory pattern? #74

Open
lnunno opened this issue Mar 8, 2018 · 7 comments
Open

Usage with Flask factory pattern? #74

lnunno opened this issue Mar 8, 2018 · 7 comments

Comments

@lnunno
Copy link

lnunno commented Mar 8, 2018

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.

@bitfinity
Copy link

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.

@sloria
Copy link
Member

sloria commented May 28, 2018

@lnunno Your code example looks correct. I would certainly merge a PR adding this to the docs. =)

@mcclurem
Copy link

@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

@bennycooly
Copy link

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

@dfilter
Copy link

dfilter commented Apr 24, 2020

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

@amaschas
Copy link

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.

bouttier added a commit to PnX-SI/GeoNature that referenced this issue Oct 17, 2022
ma.init_app(app) replace DummySession() by db.session but its too late,
schemas have already been created, so replace it as soon as ma have been initialized.

marshmallow-code/flask-marshmallow#44
marshmallow-code/flask-marshmallow#74
marshmallow-code/flask-marshmallow#111
bouttier added a commit to PnX-SI/GeoNature that referenced this issue Oct 17, 2022
ma.init_app(app) replace DummySession() by db.session but its too late,
schemas have already been created, so replace it as soon as ma have been initialized.

marshmallow-code/flask-marshmallow#44
marshmallow-code/flask-marshmallow#74
marshmallow-code/flask-marshmallow#111
bouttier added a commit to PnX-SI/GeoNature that referenced this issue Oct 17, 2022
ma.init_app(app) replace DummySession() by db.session but its too late,
schemas have already been created, so replace it as soon as ma have been initialized.

marshmallow-code/flask-marshmallow#44
marshmallow-code/flask-marshmallow#74
marshmallow-code/flask-marshmallow#111
@MarkusSchiffer
Copy link

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

8 participants