python-social-auth / social-app-flask-sqlalchemy

Python Social Auth - Flask - SQLAlchemy
BSD 3-Clause "New" or "Revised" License
7 stars 5 forks source link

SQLAlchemy Error about implicitly combining uid columns #1

Open dcrosta opened 6 years ago

dcrosta commented 6 years ago

I'm trying to set up PSA with Flask & SQLAlchemy. I've gotten as far as calling init_social, but as soon as I try to access my app (run flask shell or load a page in flask run, etc), I get this error:

Traceback (most recent call last):
[...]
  File "/Users/dcrosta/src/myproject/myproject/core.py", line 91, in set_up_auth
    init_social(app, db.session)
  File "/Users/dcrosta/.virtualenvs/myproject/lib/python3.6/site-packages/social_flask_sqlalchemy/models.py", line 80, in init_social
    UserSocialAuth.uid = Column(String(UID_LENGTH))
  File "/Users/dcrosta/.virtualenvs/myproject/lib/python3.6/site-packages/sqlalchemy/ext/declarative/api.py", line 69, in __setattr__
    _add_attribute(cls, key, value)
  File "/Users/dcrosta/.virtualenvs/myproject/lib/python3.6/site-packages/sqlalchemy/ext/declarative/base.py", line 658, in _add_attribute
    cls.__mapper__.add_property(key, value)
  File "/Users/dcrosta/.virtualenvs/myproject/lib/python3.6/site-packages/sqlalchemy/orm/mapper.py", line 1854, in add_property
    self._configure_property(key, prop, init=self.configured)
  File "/Users/dcrosta/.virtualenvs/myproject/lib/python3.6/site-packages/sqlalchemy/orm/mapper.py", line 1638, in _configure_property
    prop = self._property_from_column(key, prop)
  File "/Users/dcrosta/.virtualenvs/myproject/lib/python3.6/site-packages/sqlalchemy/orm/mapper.py", line 1768, in _property_from_column
    raise sa_exc.InvalidRequestError(msg)
sqlalchemy.exc.InvalidRequestError: Implicitly combining column social_auth_usersocialauth.uid with column social_auth_usersocialauth.uid under attribute 'uid'.  Please configure one or more attributes for these same-named columns explicitly.

I'm using:

$ pip list | egrep -i "social|flask|sqlalchemy"
Flask                            1.0.2
Flask-Login                      0.4.1
Flask-SQLAlchemy                 2.3.2
python-social-auth               0.3.6
social-auth-app-flask            1.0.0
social-auth-app-flask-sqlalchemy 1.0.1
social-auth-core                 1.7.0
social-auth-storage-sqlalchemy   1.1.0
SQLAlchemy                       1.2.8

As near as I can tell, there's only one actual definition of uid, which is at https://github.com/python-social-auth/social-app-flask-sqlalchemy/blob/master/social_flask_sqlalchemy/models.py#L80 -- everywhere else in the class hierarchy, uid is assigned to None. But I'm hoping that there's something obvious I've missed here, or something you've run into before that can help.

Thanks!

dcrosta commented 6 years ago

Ah, I've found the issue. When running with flask run ..., it looks like Flask loads the app module several times. In my particular case, I use a factory function to create the application object, which ultimately results in init_social being called several times. Here's a small reproducing case:

social.py

from flask import Flask
from models import db
from social_flask_sqlalchemy.models import init_social

def create_application():
    app = Flask(__name__)

    app.config["SOCIAL_AUTH_USER_MODEL"] = "models.User"
    app.config["SOCIAL_AUTH_STORAGE"] = "social_flask_sqlalchemy.models.FlaskStorage"
    init_social(app, db.session)

    return app

application = create_application()

models.py

from flask_sqlalchemy import SQLAlchemy

db = SQLAlchemy()

class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(256))
    name = db.Column(db.String(256))
    email = db.Column(db.String(256))

Run with FLASK_APP=social flask run to see the traceback.

I have, for now, worked around this by returning a singleton from my app factory, but this is a bit janky. If there was some way this could be fixed in social-auth itself, that would be easier for me or anyone else who uses factories like I do (which makes testing a lot nicer).

miiichael commented 1 year ago

Specifically, this footgun is the result of init_social() always importing the module your USER_MODEL is in, even if (as I've just discovered) your app is trivial enough that you put your model(s) in the same module as the app itself...

In @dcrosta's example, I think all that was needed was an if __name__ == "__main__": before the last line of social.py.