-
-
Notifications
You must be signed in to change notification settings - Fork 900
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
Flask-SQLAlchemy does not behave like SQLAlchemy with the same classes, causing a runtime error #1268
Comments
@davidism Here is a minimal code to reproduce the issue and here is the relevant discussion link if interests you to participate in the discussion there. |
the issue is flask-sqlalchemy is autonaming tables so that the subclasses are not single table inheritance. defining like this:
or using what's not clear yet is why sqlalchemy is complaining about these columns if it sees joined table inheritance. |
oh OK, it's because flask-sqlalchemy does this flask-sqlalchemy/src/flask_sqlalchemy/model.py Lines 244 to 262 in d349bdb
where it then deletes the tablename so that the mapping ends up being single inheritance. This is changing the model in the middle of the declarative scan to be single inheritance, so that's why SQLAlchemy is both not seeing the model as single inheritance, but then later thinks it is single inheritance. so this is a bug for...someone. I think I had to change when single inheritance is first determined in order for the |
I don’t see a way to address this in Flask-SQLAlchemy. Yes, we’re doing weird things with table creation, but that’s because there’s no better hooks to accomplish this in SQLAlchemy itself. If there were appropriate extension points for controlling the table name and inheritance during definition, we would use it. I’ve also discussed a bit of what we do with SQLAlchemy devs in the past due to other changes. |
this is really not about a lack of events. we have tons of events. what is happening here is flask is assuming something about the exact order of steps which occur within the declarative process, assuming that the determination if the class should be set up as single table inheritance happens after the in SQLAlchemy 2.0, 99% of the code still works this way but an important part of the "single" inheritance question is now determined before the columns are scanned, which at the moment is something really small, whether the all-new We can very easily "make it work the old way" by just removing this ".single" attribute and taking the one test we have right now which tests that these approaches are of course bad ideas because we are still just having dueling non-defined behaviors. I dont yet see a way to support flask without some extremely flask-specific feature added to declarative, because as it stands flask's assumptions create a dependency cycle:
So there's no event alone that can allow this to work; Flask's current assumptions are slightly incompatible with ours. mapped_column() would need to have use_existing_column somehow work without knowing if the class intends to be single inheritance, and we'd then have to add basically a guarantee throughout declarative that is basically hardcoded to this particular convention that flask-sqlalchemy uses. At the core of this, Flask-sqlalchemy has a feature where you dont have to explicitly state class Base(DeclarativeBase):
@declared_attr.directive
def __tablename__(cls):
return cls.__name__.lower()
class Person(Base):
id = mapped_column(Integer, primary_key=True)
class Engineer(Person):
name = mapped_column(String) SQLAlchemy would see this as an error, the user forgot to put a primary key on their "Engineer" joined table. Flask sees it as a directive that Engineer has no table and assumes single inheritance. There's a bit of a clash of design choices happening here. SQLAlchemy especially in 2.0 tries in all cases where we can decide to never assume in the face of ambiguity and to raise an error instead. We of course started out in the early 2000's making a whole slate of implicit choices and assumptions but that's really where a lot of user confusion comes from and that's why the Zen of Python has an opinion on this. I kind of think Flask-SQLAlchemy's "guess" here is a bit dated and should move towards encouraging the user to explicitly state whether a class intends to be single- or joined- inheritance. class mapping is confusing enough without guesses happening. |
To be clear, I'm not blaming SQLAlchemy for any of this, we're clearly the ones hacking around things. It already worked like this when I inherited the project, then I did a bunch of work to clean it up and make it work "correctly" in more cases. I was definitely diving into the internals, tracing how the metaclass and I do want to keep the auto table naming behavior, as I think that's pretty useful and results in a good/consistent naming scheme. But if I remember correctly, one of the reasons for detecting primary keys and table inheritance is because if we don't, then a tablename is always generated and causes some other sort of inheritance. Either way it's a bit inconvenient. So I'm open to deprecating then removing our current behavior, especially on the advice/request of SQLAlchemy. The main problem is figuring out how to deprecate it, and what to instruct users to do instead. It's been years since I really looked at this code I wrote. If you have any suggestions for what to do, I'm happy to discuss it and figure it out with you. |
The Single Table Inheritance setup ( the first code below which is very self explanatory ) the following error is emmitted
Reproduce Error
To replicate it just run the following:
Expected Result
The code should work similar to the same code that uses SQLAlchemy instead of Flask-SQLAlchemy below
Environment:
Python version: 3.9.16
Flask-SQLAlchemy version: 3.1.1
SQLAlchemy version: 2.0.21
The text was updated successfully, but these errors were encountered: