SQLAlchemy Model Circular Import - python

I have two models in the same module named models. They are a 1-1 relationship and have been configured per the SQLAlchemy docs.
Vehicle.py
from models.AssetSetting import AssetSetting
class Vehicle(Base):
__tablename__ = 'vehicles'
vehicle_id = Column(Integer, primary_key=True)
...
settings = relationship('AssetSetting', backref=backref('asset_settings'))
AssetSetting.py
from models.Vehicle import Vehicle
class AssetSetting(Base):
__tablename__ = 'asset_settings'
asset_alert_setting_id = Column(Integer, primary_key=True, autoincrement=True)
...
vehicle = relationship('vehicles', foreign_keys=Column(ForeignKey('vehicles.vehicle_id')))
If I use the string relationship building (i.e. ForeignKey('vehicles.vehicle_id')) I get the error:
sqlalchemy.exc.InvalidRequestError:
When initializing mapper Mapper|AssetSetting|asset_settings, expression 'vehicles' failed to locate a name ("name 'vehicles' is not defined").
If this is a class name, consider adding this relationship() to the <class 'models.AssetSetting.AssetSetting'> class after both dependent classes have been defined.
If I use the class mapping, I get the classic circular import error:
Traceback (most recent call last):
File "tracking_data_runner.py", line 7, in <module>
from models.Tracker import Tracker
File "/.../models/Tracker.py", line 5, in <module>
from models.Vehicle import Vehicle
File "/.../models/Vehicle.py", line 13, in <module>
from models.Tracker import Tracker
ImportError: cannot import name 'Tracker'
I believe I could fix this issue by putting the files in the same package but would prefer to keep them separate. Thoughts?

To avoid circular import errors, you should use string relationship building, but both of your models have to use the same Base - the same declarative_base instance. Instantiate your Base once and use it when initializing both Vehicle and AssetSetting.
Or, you may explicitly map the table names and classes to help mapper relate your models:
Base = declarative_base(class_registry={"vehicles": Vehicle, "asset_settings": AssetSetting})

I discovered my problem was two fold:
I was referencing Vehicles improperly in my relationship. It should be relationship('Vehicle' not relationship('vehicles'
Apparently it is improper to declare the FK inside the relationship as I did in AssetSettings.py (foreign_keys=Column(ForeignKey('vehicles.vehicle_id'))). I had to declare the FK and then pass it in to the relationship.
My configurations look like this now:
Vehicle.py
class Vehicle(Base, IDiagnostable, IUsage, ITrackable):
__tablename__ = 'vehicles'
vehicle_id = Column(Integer, primary_key=True)_id = Column(Integer)
settings = relationship('AssetSetting', backref=backref('asset_settings'))
AssetSetting.py
class AssetSetting(Base):
__tablename__ = 'asset_settings'
asset_alert_setting_id = Column(Integer, primary_key=True, autoincrement=True)
vehicle_id = Column(ForeignKey('vehicles.vehicle_id'))
vehicle = relationship('Vehicle', foreign_keys=vehicle_id)

Your __tablename__ is referencing vehicles but your Foreign Key is referencing vehicle.vehicle_id

Related

Flask + sqlalchemy + marshmallow - Error on relationship - One to Many

Why this code returns an error?
Error: When initializing mapper Mapper|Pessoa|pessoa, expression 'Imovel' failed to locate a name ("name 'Imovel' is not defined").
from flask_sqlalchemy import SQLAlchemy
from flask_marshmallow import Marshmallow
db=SQLAlchemy()
ma=Marshmallow()
class Pessoa(db.Model):
__tablename__ = 'pessoa'
idLocal= db.Column(db.Integer, primary_key=True)
Nome=db.Column(db.String(100), default=u'')
imovelList = db.relationship("Imovel", back_populates="PessoaList")
def get_id(self):
return self.idLocal
class PessoaSchema(ma.ModelSchema):
class Meta: model = Pessoa
class Imovel(db.Model):
__tablename__ = 'imovel'
idLocal= db.Column(db.Integer, primary_key=True)
CodigoImovel=db.Column(db.String(30), default=u'')
idPessoa = db.Column(db.Integer, db.ForeignKey('pessoa.idLocal'))
PessoaList = db.relationship("Pessoa", back_populates="imovelList")
def get_id(self):
return self.idLocal
class ImovelSchema(ma.ModelSchema):
class Meta: model = Imovel
You have an 'order of declaration' issue. Relationships are initialized immediately when their Mappers are constructed when the relationship is defined with a String. But when you define the relationship on "Imovel" you have yet to declare a Mapper called "Imovel". The Imovel Mapper or class is defined a couple lines after that.
So you could move the Imovel Mapper above the Pessoa Mapper, except then you would get the exact same error as you are also building a relationship from Imovel to Pessoa.
So instead you want to declare your relationship using a callable function that will return the "Imovel" Mapper. This function will typically only be called after all Mappers are constructed. Thus by using a lambda function we can ensure the relationship is not invoked until you have had a chance to setup the Imovel class.
In practice, to fix this error, replace this line
imovelList = db.relationship("Imovel", back_populates="PessoaList")
with this
imovelList = db.relationship(lambda: Imovel, back_populates="PessoaList")

CompileError with inherited parameter in sql-alchemy table class

I Have a SQLalchemy class (flask-sqlalchemy) which inherits from another class and looks like this
class Cat(db.Model, Category):
__tablename__ = 'Cat'
id = db.Column(db.String(1000), primary_key=True, nullable=False)
parent_id = db.Column(db.String(1000), db.ForeignKey('SocArxivCategory.id'))
parent_category = db.relationship('Cat',
backref=db.backref('children',
order_by=Category.name,
lazy='selectin'),
remote_side='Cat.id')
and the parent class is
class Category():
__tablename__ = 'Category'
name = db.Column(db.String(2000), nullable=False)
You see that the Cat table builds a relationship using the Category.name attribute. But this seems to cause an error
....
File "/usr/local/lib/python2.7/site-packages/sqlalchemy/sql/compiler.py", line 692, in _fallback_column_name
raise exc.CompileError("Cannot compile Column object until "
CompileError: Cannot compile Column object until its 'name' is assigned.
so it seems to try to build the relationship before inheriting the name attribute? I am not sure whether this is an SQLalchemy issue or a Python issue? Any idea how to fix this?
Ok after a bit of searching and more consulting of the sqlalchemy docs I found a solution. Changing the definition of the relationship to
parent_category = db.relationship('Cat',
backref=db.backref('children',
order_by=lambda: Cat.name,
lazy='selectin'),
remote_side='Cat.id')
did the trick

sqlalchemy + flask: class is not defined

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.

SQLAlchemy with Flask blueprints: circular imports

I've got two models, Company and Employee, in a many-to-one relationship. They are defined in different Flask blueprints. I'm trying to add a cascade, which makes me need to define a relationship on Company (instead of just a relationship on Employee with backref set).
company_blueprint/models.py:
class Company(Base):
id = Column(Integer, primary_key=True)
employees = relationship("Employee", back_populates="company", cascade="all")
employee_blueprint/models.py:
from app.company_blueprint.models import Company
class Employee(Base):
name = Column(String)
company_id = Column(ForeignKey(Company.id))
company = relationship("company", back_populates="employees")
The problem is, when I try to delete a company in company_blueprint/views.py, the Employee model is not loaded. I get:
sqlalchemy.exc.InvalidRequestError: When initializing mapper Mapper|Company|company, expression 'Employee' failed to locate a name ("name 'Employee' is not defined"). If this is a class name, consider adding this relationship() to the <class 'app.company_model.models.Company'> class after both dependent classes have been defined.
I could try to import Employee in company_blueprint.models, but then I'm running into a circular import problem.
How do I fix this?
Edit: Thanks to Paradoxis, for now I've settled on the following:
Using strings to refer to foreign key columns, e.g. company_id = Column(ForeignKey("company.id"))
In my app.py, first import all models before anything else, i.e.
-
import flask
import app.employee_blueprint.models
import app.company_blueprint.models
# import other views, modules, etc.
This still feels a bit awkward.
I am using such relationships in most of the model classes. According to my understanding, you don't need any special thing or relationship in both classes. Simply add a relationship in parent model class and add a backref, it will work.
class Company(Base):
id = Column(Integer, primary_key=True)
employees = relationship("Employee", backref="company", cascade="all, delete-orphan")

SQLAlchemy cannot find a class name

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')

Categories

Resources