I'm starting with FastAPI and SQLAlchemy, and I have a question about loading models in the correct order to satisfy SQLAlchemy models relationship. I've got two models, Profile and Account:
Profile:
from sqlalchemy import Column, Integer, String
from sqlalchemy.orm import relationship
from project.core.database import Base
class Profile(Base):
__tablename__ = "profiles"
id = Column(Integer, primary_key=True)
name = Column(String(10), nullable=False)
slug = Column(String(10), nullable=False)
order = Column(Integer, unique=True, nullable=False)
accounts = relationship(
"Account", foreign_keys="[Account.profile_id]", back_populates="profile"
)
Account:
from sqlalchemy import Column, Integer, String, LargeBinary, ForeignKey
from sqlalchemy.orm import relationship
from project.core.database import Base
class Account(Base):
__tablename__ = "accounts"
id = Column(Integer, primary_key=True)
name = Column(String(255), nullable=False)
email = Column(String(255), nullable=False)
password = Column(LargeBinary, nullable=False)
profile_id = Column(Integer, ForeignKey("profiles.id"))
profile = relationship(
"Profile", foreign_keys=[profile_id], back_populates="accounts"
)
And in my project.core.database file, I created a method to import the models, since I was having issues with models not being located when the relationship was attempted to be made.
def import_all():
import project.models.profile
import project.models.account
def init_db():
import_all()
My question is, is there a smarter way to load the models in the correct order? Because now I only have two models, but soon it can grow to dozens of models and I think this will become such a monster to manage.
I looked for resources and examples, but everything I found created the models in a single file.
Thanks in advance!
Let's take a look at full-stack-fastapi-postgresql, a boilerplate template created by the author of FastAPI:
base.py
from app.db.base_class import Base
from app.models.item import Item
from app.models.user import User
init_db.py
from app.db import base # noqa: F401 import db models
# make sure all SQL Alchemy models are imported (app.db.base) before initializing DB
# otherwise, SQL Alchemy might fail to initialize relationships properly
# for more details: https://github.com/tiangolo/full-stack-fastapi-postgresql/issues/28
def init_db(db: Session) -> None:
...
So, as you can see it's a polucalar way to import models, here you can find additional discussion: https://github.com/tiangolo/full-stack-fastapi-postgresql/issues/28
Related
I'm using flask-admin with flask-sqlalchemy as a simple admin interface to create & edit items in our database/flask app.
We have two models one which is a parent and one a child. At the moment our process is:
create a new parent item with no children
create a new child and set the child's parent
To save time I would like a single form for creating a new parent where we can enter all the details of the parent as well as a child and create the parent then create the child setting a foreign key between the child and the parent that was just created.
Is there a way to do this with flask-admin?
Have you tried Flask Admin inline models?
Here is an example with a one-to-many relationship. Unfortunately, FlaskAdmin does not provide complete support for one-to-many relationships (source).
Currently, it only supports providing an inline model on the "one" side of the relationship. Given two models: Post, and Guide, where one guide has many posts, you would have something like this:
from flask import Flask
from flask_admin import Admin
from flask_admin.contrib.sqla import ModelView
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///storage.sqlite3'
app.secret_key = 'secret'
db = SQLAlchemy(app)
# defining the "one" side of the relationship
class Guide(db.Model):
__tablename__ = "guides"
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(255), nullable=False, unique=True)
description = db.Column(db.String(512), nullable=False)
posts = db.relationship("Post", back_populates="guide")
# defining the "many" side of the relationship
class Post(db.Model):
__tablename__ = "posts"
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(255), nullable=False)
content = db.Column(db.Text, nullable=False)
guide_id = db.Column(
db.Integer, db.ForeignKey("guides.id", ondelete="CASCADE"), nullable=False
)
guide = db.relationship("Guide", back_populates="posts")
db.create_all()
admin = Admin(app, template_mode="bootstrap3")
# creating a custom model view for the Guide model.
class ModelViewWithInlineModel(ModelView):
# here you provide a tuple with the inline models you want to
# render to the user
inline_models = (Post,)
# using FlaskAdmin ModelView default implementation for the Post model
admin.add_view(ModelView(Post, db.session))
# using our custom ModelView with the Post as an inline model
admin.add_view(ModelViewWithInlineModel(Guide, db.session))
if __name__ == '__main__':
app.run(debug=True)
I have an existing model that I can't change, written in Flask-SQLAlchemy.
I'm writing another app that uses the same model, but without the need for Flask, therefore I'm working with the regular SQLAlchemy module.
Unfortunately, I'm getting a lot of:
'AttributeError: module 'DB' has no attribute 'Model'
for all kind of attributes - such as Column, Integer, etc
Is there a way to use Flask-SQLAlchemy with a regular SQLAlchemy app?
There is an example of one of my Model Class:
class Table_name(Model):
__tablename__ = 'table_name'
id = db.Column(db.INTEGER, primary_key=True)
field1 = db.Column(db.INTEGER, db.ForeignKey('table1.id'), nullable=False)
field2 = db.Column(db.TEXT, nullable=False)
field3 = db.Column(db.INTEGER, db.ForeignKey('table2.id'), nullable=False)
time = db.Column(db.TIMESTAMP, nullable=False)
Unfortunately I can't change them
I've had the same dilemma. If it's a small or one-off, you can hack in a Flask app object without really using it, like so:
from flask_sqlalchemy import SQLAlchemy
throw_away_app = Flask(__name__)
db = SQLAlchemy(throw_away_app)
with self.throw_away_app.app_context():
(perform your db operation)
That works relatively well for simple things like scripts. But if you're sharing a model across multiple projects and you simply cannot alter the existing Flask project, it's unfortunately probably not a good solution.
If that's the case, and you simply cannot alter the existing codebase at all, it probably makes sense to create a new model class and connect to the existing database using vanilla SQLAlchemy.
BTW, for any future programmers wondering how to get some of the benefits of Flask-SQLAlchemy without Flask, consider: sqlalchemy-mixins.
from sqlalchemy import create_engine
from sqlalchemy import Column, Integer, String, Text, TIMESTAMP, ForeignKey
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
Base = declarative_base()
# connect in memory sqlite database or you can connect your own database
engine = create_engine('sqlite:///:memory:', echo=True)
# create session and bind engine
Session = sessionmaker(bind=engine)
class Table_name(Base):
__tablename__ = 'table_name'
id = Column(Integer, primary_key=True)
field1 = Column(Integer, ForeignKey('table1.id'), nullable=False)
field2 = Column(Text, nullable=False)
field3 = Column(Integer, ForeignKey('table2.id'), nullable=False)
time = Column(TIMESTAMP, nullable=False)
table = Table_name(field1=1, fi....)
session.add(table)
Now you can use your ORM as usual like flask-sqlalchemy .
Docs: https://docs.sqlalchemy.org/en/13/orm/tutorial.html
I have two models, one is Identification which contains two IDs (first_id and second_id) and the second is User. The idea is that only authorised users will be given their first_id and second_id pair of values. They go to the site and login by entering the two id's plus a username and password (which they generate there and then).
I am trying to achieve two things here:
Pre-populate the Identification table with many (let's say 100) first_id/second_id values that will serve as the correct value pairs for logging in.
Set up the User class in such a way that only if the user enters a correct first_id/second_id pair in the login form can they log in (presumable this involves checking the form data with the Identification table somehow).
Here are the model classes:
class Identification(db.Model):
id = db.Column(db.Integer, primary_key=True)
first_id= db.Column(db.Text, unique=True)
second_id= db.Column(db.Text, unique=True)
def __init__(self, first_id, second_id):
self.first_id= first_id
self.second_id= second_id
def __repr__(self):
return f"ID: {self.id}, first_id: {self.first_id}, second_id: {self.second_id}"
class User(db.Model, UserMixin):
__tablename__ = 'user'
first_id= db.relationship('Identification', backref = 'identificationFID', uselist=False)
second_id = db.relationship('Identification', backref = 'identificationSID', uselist=False)
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.Text, unique=True, index=True)
password_hash = db.Column(db.Text(128))
identification_id = db.Column(db.Integer, db.ForeignKey('identification.id'), unique=True)
first_id = db.Column(db.Text, unique=True)
second_id = db.Column(db.Text, unique=True)
I would appreciate any help on this as I'm struggling and this is really above my understanding of python/Flask. Thanks all!
The answer above didn't work for me, because the create_tables() function since being part of the User class, requested that I pass an Instance of that class.
The solution I came up with, was to call the function after db.create_all(). This seemed like a good place to put the call, because of the #app.before_first_request decorator.
init.py
#app.before_first_request
def create_tables():
"""Create Tables and populate certain ones"""
db.create_all()
from app.models.init_defaults import init_defaults
init_defaults()
init_defaults.py
def init_defaults():
"""Pre-Populate Role Table"""
if Role.query.first() is None:
roles = ['Admin', 'Master', 'Apprentice']
for role in roles:
user_role = Role(access_level=role)
if role != 'Apprentice':
user_role.set_password('Passw0rd!')
db.session.add(user_role)
db.session.commit()
pass
Due to the decorator the function is now only called once per instance. Another solution I could imagine working, would be to use events:
https://dzone.com/articles/how-to-initialize-database-with-default-values-in
Note: This is a development solution not fit for production.
You can use mock data to populate these tables.
create a function in this py file where you can add objects to DB using ORM
and then call the function in __init__.py, which will populate data once your flask server starts.
Update:-
here is a code for your reference.
Model.py
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import create_engine
from sqlalchemy import Column, Integer, String
from sqlalchemy.orm import sessionmaker
Base = declarative_base()
engine = create_engine('sqlite:///:memory:', echo=True)
Session = sessionmaker(bind=engine)
session = Session()
class User(Base):
\__tablename__ = 'users'
id = Column(Integer, primary_key=True)
name = Column(String)
fullname = Column(String)
#Create getter setters
def create_tables():
Base.metadata.create_all(engine)
user = User()
user.id=1
user.name="ABC"
user.fullname="ABCDEF"
session.add(user)
# similarly create more user objects with mock data and add it using session
__init__.py
from model import User
User.create_tables()
Reference
i'm using sqlalchemy + alembic + Flask and i can't map circular classes.
apps/users/models.py:
class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
email = Column(String)
password = Column(String)
session = relationship("Session", back_populates='user', cascade='all,delete', lazy='dynamic')
notes = relationship('Note2User', back_populates='user', cascade='all,delete', lazy='dynamic')
apps/notes/models.py:
class Note2User(Base):
__tablename__ = 'notes_users_m2m'
id = Column(Integer, primary_key=True)
user_id = Column(Integer, ForeignKey('users.id', ondelete='CASCADE'), nullable=False)
user = relationship('User', back_populates='notes')
note_id = Column(Integer, ForeignKey('notes.id', ondelete='CASCADE'), nullable=False)
note = relationship('Note', back_populates='users')
Table Note2User made for m2m relationship User <-> Notes, but when i start app and done some request, gets error:
InvalidRequestError: When initializing mapper Mapper|User|users,
expression 'Note2User' failed to locate a name ("name 'Note2User' is
not defined"). If this is a class name, consider adding this
relationship() to the class after
both dependent classes have been defined.
Initializing db in db/init.py: (dunder name)
from sqlalchemy import create_engine, MetaData
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import Session
engine = create_engine('postgresql+psycopg2://server:12345#localhost:5432/test')
Base = declarative_base()
meta = MetaData()
meta.reflect(bind=engine)
db_session = Session(bind=engine)
Add an import for Note2User class in apps/users/models.py file so this model gets defined first before initializing that relatioship in User class which refrences it.
like this
# file: apps/users/models.py
from ..notes.models import Note2User
You need to import the user.models module into the notes.model module and vice versa. It would look something like this:
# file app/users/models.py
import app.notes.models as notes
# use it like this
notes.Notes2User()
# file app/notes/models.py
import app.users.models as users
users.User()
The advantage to this is that you will avoid circular dependency problems as your program inevitably grows. I had so many problems with circular dependencies when I was creating an app with your same stack. The only solution was to ditch the
from . import Foo
and only use
import bar.foo as foo
It is considered best practice to use the import syntax for this reason.
Reference.
Simplified, I have the following class structure (in a single file):
Base = declarative_base()
class Item(Base):
__tablename__ = 'item'
id = Column(BigInteger, primary_key=True)
# ... skip other attrs ...
class Auction(Base):
__tablename__ = 'auction'
id = Column(BigInteger, primary_key=True)
# ... skipped ...
item_id = Column('item', BigInteger, ForeignKey('item.id'))
item = relationship('Item', backref='auctions')
I get the following error from this:
sqlalchemy.exc.InvalidRequestError
InvalidRequestError: When initializing mapper Mapper|Auction|auction, expression
'Item' failed to locate a name ("name 'Item' is not defined"). If this is a
class name, consider adding this relationship() to the Auction class after
both dependent classes have been defined.
I'm not sure how Python cannot find the Item class, as even when passing the class, rather than the name as a string, I get the same error. I've been struggling to find examples of how to do simple relationships with SQLAlchemy so if there's something fairly obvious wrong here I apologise.
This all turned out to be because of the way I've set SQLAlchemy up in Pyramid. Essentially you need to follow this section to the letter and make sure you use the same declarative_base instance as the base class for each model.
I was also not binding a database engine to my DBSession which doesn't bother you until you try to access table metadata, which happens when you use relationships.
if it's a subpackage class, add Item and Auction class to __init__.py in the subpackage.
The SQLAlchemy documentation on Importing all SQLAlchemy Models states in part:
However, due to the behavior of SQLAlchemy's "declarative" configuration mode, all modules which hold active SQLAlchemy models need to be imported before those models can successfully be used. So, if you use model classes with a declarative base, you need to figure out a way to get all your model modules imported to be able to use them in your application.
Once I imported all of the models (and relationships), the error about not finding the class name was resolved.
Note: My application does not use Pyramid, but the same principles apply.
Case with me
Two models defined in separate files, one is Parent and the other is Child, related with a Foreign Key. When trying to use Child object in celery, it gave
sqlalchemy.exc.InvalidRequestError: When initializing mapper Mapper|Child|child, expression 'Parent' failed to locate a name ("name 'Parent' is not defined"). If this is a class name, consider adding this relationship() to the <class 'app.models.child'>
parent.py
from app.models import *
class Parent(Base):
__tablename__ = 'parent'
id = Column(BigInteger, primary_key=True, autoincrement=True)
name = Column(String(60), nullable=False, unique=True)
number = Column(String(45), nullable=False)
child.py
from app.models import *
class Child(Base):
__tablename__ = 'child'
id = Column(BigInteger, primary_key=True, autoincrement=True)
parent_id = Column(ForeignKey('parent.id'), nullable=False)
name = Column(String(60), nullable=False)
parent = relationship('Parent')
Solution
Add an import statement for Parent in beginning of child.py
child.py (modified)
from app.models import *
from app.models.parent import Parent # import Parent in child.py 👈👈
class Child(Base):
__tablename__ = 'child'
id = Column(BigInteger, primary_key=True, autoincrement=True)
parent_id = Column(ForeignKey('parent.id'), nullable=False)
name = Column(String(60), nullable=False)
parent = relationship('Parent')
Why this worked
The order in which models get loaded is not fixed in SQLAlchemy.
So, in my case, Child was being loaded before Parent. Hence, SQLAlchemy can't find what is Parent. So, we just imported Parent before Child gets loaded.
Namaste 🙏
I've solved the same error by inheriting a 'db.Model' instead of 'Base'... but I'm doing the flask
Eg:
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
class someClass(db.Model):
someRelation = db.relationship("otherClass")
Also, even though this doesn't apply to the OP, for anyone landing here having gotten the same error, check to make sure that none of your table names have dashes in them.
For example, a table named "movie-genres" which is then used as a secondary in a SQLAlchemy relationship will generate the same error "name 'movie' is not defined", because it will only read as far as the dash. Switching to underscores (instead of dashes) solves the problem.
My Solution
One models file, or even further, if you need.
models.py
from sqlalchemy import Boolean, BigInteger, Column, DateTime, Float, ForeignKey, BigInteger, Integer, String
from sqlalchemy.orm import relationship
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
from .parent import Parent
from .child import Child
parent.py
from sqlalchemy import Boolean, BigInteger, Column, DateTime, Float, ForeignKey, BigInteger, Integer, String
from sqlalchemy.orm import relationship
from sqlalchemy.ext.declarative import declarative_base
#Base = declarative_base()
class Parent(Base):
__tablename__ = 'parent'
id = Column(BigInteger, primary_key=True, autoincrement=True)
name = Column(String(60), nullable=False, unique=True)
number = Column(String(45), nullable=False)
child.py
from sqlalchemy import Boolean, BigInteger, Column, DateTime, Float, ForeignKey, BigInteger, Integer, String
from sqlalchemy.orm import relationship
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
class Child(Base):
__tablename__ = 'child'
id = Column(BigInteger, primary_key=True, autoincrement=True)
parent_id = Column(ForeignKey('parent.id'), nullable=False)
name = Column(String(60), nullable=False)
parent = relationship('Parent')
Why this worked
Same Deepam answer, but with just one models.py file to import another models
I had a different error, but the answers in here helped me fix it.
The error I received:
sqlalchemy.exc.InvalidRequestError: When initializing mapper mapped class Parent->parents, expression 'Child' failed to locate a name ('Child'). If this is a class name, consider adding this relationship() to the <class 'parent.Parent'> class after both dependent classes have been defined.
My set-up is similar toDeepam's answer.
Briefly what I do different:
I have multiple separate .py files for each db.Model.
I use a construct/fill database .py file that pre-fills db.Model objects in either Multi-threading or single threading way
What caused the error:
Only in multi-threaded set up the error occured
This construct/fill .py script did import Parent, but not Child.
What fixed it:
Adding an import to Child fixed it.
I had yet another solution, but this helped clue me in. I was trying to implement versioning, from https://docs.sqlalchemy.org/en/14/orm/examples.html#versioning-objects using the "history_mapper" class.
I got this same error. All I had to do to fix it was change the order in which my models were imported.
Use back_populates for relationship mapping in both models.
Also keep in mind to import both the models in the models/__init__.py
Base = declarative_base()
class Item(Base):
__tablename__ = 'item'
id = Column(BigInteger, primary_key=True)
# ... skip other attrs ...
auctions = relationship('Auction', back_populates='item')
class Auction(Base):
__tablename__ = 'auction'
id = Column(BigInteger, primary_key=True)
# ... skipped ...
item_id = Column('item', BigInteger, ForeignKey('item.id'))
item = relationship('Item', back_populates='auctions')