I am getting this strange error, and I'm saying strange because I made a change to an unrelated table.
I am trying to query my tDevice table which looks like this:
class TDevice(Base):
__tablename__ = 'tDevice'
ixDevice = Column(Integer, primary_key=True)
ixDeviceType = Column(Integer, ForeignKey('tDeviceType.ixDeviceType'), nullable=False)
ixSubStation = Column(Integer, ForeignKey('tSubStation.ixSubStation'), nullable=False)
ixModel = Column(Integer, ForeignKey('tModel.ixModel'), nullable=True)
ixParentDevice = Column(Integer, ForeignKey('tDevice.ixDevice'), nullable=True)
sDeviceName = Column(Unicode(255), nullable=False)#added
children = relationship('TDevice',
backref=backref('parent', remote_side=[ixDevice]))
device_type = relationship('TDeviceType',
backref=backref('devices'))
model = relationship('TModel',
backref=backref('devices'))
sub_station = relationship('TSubStation',
backref=backref('devices'))
and this is how I query it:
Device = DBSession.query(TDevice).filter(TDevice.ixDevice == device_id).one()
as soon as this line is executed, I get the error:
ArgumentError: relationship 'report_type' expects a class or a mapper argument (received: <class 'sqlalchemy.sql.schema.Table'>)
The only changes I've made is add a report_type relationship in my tReportTable
which now looks like this:
class TReport(Base):
__tablename__ = 'tReport'
ixReport = Column(Integer, primary_key=True)
ixDevice = Column(Integer, ForeignKey('tDevice.ixDevice'), nullable=False)
ixJob = Column(Integer, ForeignKey('tJob.ixJob'), nullable=False)
ixReportType = Column(Integer, ForeignKey('tReportType.ixReportType'), nullable=False) # added
report_type = relationship('tReportType',
uselist=False,
backref=backref('report'))
device = relationship('TDevice',
uselist=False,
backref=backref('report'))
job = relationship('TJob',
uselist=False,
backref=backref('report'))
I'm still new to SqlAlchemy so I can't seem to see how adding that relationship should be causing this error if I am iterating another table
not happy with myself since it's such a dumb mistake but here is my culprit:
report_type = relationship('tReportType',
uselist=False,
backref=backref('report'))
should be:
report_type = relationship('TReportType',
uselist=False,
backref=backref('report'))
capital T instead of t, I should be referencing the class, not my actual table name: 'tReportType' -> 'TReportType'
Related
I can't describe the column "invited_name" (column_property). I don't know how to do this correctly.
class Worker(declarative_base()):
__tablename__ = "staff_worker_info"
id = Column(Integer, primary_key=True)
first_name = Column(String(40), nullable=False)
last_name = Column(String(40), nullable=False)
invited_id = Column(Integer, ForeignKey('staff_worker_info.id'))
invited = relationship("Worker", uselist=False, remote_side=[id], join_depth=1)
# I don't know how to describe this column
invited_name = column_property(
select([Worker.first_name]). \
where(Worker.id == invited_id).\
label('invited_n'))
I understand why this doesn't work, but I don't know how to write it differently.
I should get such a SQL query.
SELECT staff_worker_info.id, staff_worker_info.first_name staff_worker_info.last_name, staff_worker_info.invited_id,
(SELECT worker_invited.first_name
FROM staff_worker_info AS worker_invited
WHERE staff_worker_info.invited_id = worker_invited.id) AS invited_n,
FROM staff_worker_info
Might be a bit late, but I recently faced a similar question. I think your problem is quite easy to solve with only the relationship. If you want you can also solve it by using a column_property.
First, using the relationship. If you make the invited relationship joined, then the actual query that is send to the database is a self-join. You can access the first name via that relationship (reference https://docs.sqlalchemy.org/en/14/orm/self_referential.html).
class Worker(declarative_base()):
__tablename__ = "staff_worker_info"
id = Column(Integer, primary_key=True)
first_name = Column(String(40), nullable=False)
last_name = Column(String(40), nullable=False)
invited_id = Column(Integer, ForeignKey('staff_worker_info.id'))
invited = relationship("Worker", uselist=False, remote_side=[id], join_depth=1, lazy='joined')
#property
def invited_name(self):
return self.invited.first_name
Then, if the query you want to do is more complex, and it requires you to create a column_property, you can also do it as follows (reference https://docs.sqlalchemy.org/en/14/orm/mapped_sql_expr.html):
from sqlalchemy import inspect
from sqlalchemy.orm import aliased
class Worker(declarative_base()):
__tablename__ = "staff_worker_info"
id = Column(Integer, primary_key=True)
first_name = Column(String(40), nullable=False)
last_name = Column(String(40), nullable=False)
invited_id = Column(Integer, ForeignKey('staff_worker_info.id'))
invited = relationship("Worker", uselist=False, remote_side=[id], join_depth=1)
# Requires alias
worker = aliased(Worker)
inspect(Worker).add_property(
"invited_name",
column_property(
select([worker.first_name]). \
where(worker.id == Worker.invited_id)
)
)
I found a method. But he did not like it.
invited_name = column_property(
select([text("invited_table.first_name")]).
where(text("invited_table.id = staff_worker_info.invited_id")).
select_from(text("staff_worker_info AS invited_table")).
label('invited_n'))
Let's assume we have the following code in some Models.py file:
class Person(db.Model):
__tablename__ = 'Persons'
ID = db.Column(db.Integer, primary_key=True, nullable=False)
Name = db.Column(db.String(255), nullable=False)
class House(db.Model):
__tablename__ = 'Houses'
ID = db.Column(db.Integer,primary_key=True,nullable=False)
OwnerID = db.Column(db.Integer, nullable=False)
TenantID = db.Column(db.Integer, nullable=False)
__table_args__ = (
db.ForeignKeyConstraint(
['OwnerID'],
['Persons.ID'],
),
db.ForeignKeyConstraint(
['TenantID'],
['Persons.ID'],
),
)
OwnerBackref = db.relationship('Person', backref='OwnerBackref', lazy=True, foreign_keys=[OwnerID])
TenantBackref = db.relationship('Person', backref='TenantBackref', lazy=True, foreign_keys=[TenantID])
And we want to reflect these models using the automap base, so we have this code in another module called Database.py:
Base = automap_base()
engine = create_engine(DB_CONNECTION, pool_size=10, max_overflow=20)
db_session = scoped_session(sessionmaker(autocommit=False, autoflush=False, bind=engine))
Base.prepare(engine, reflect=True)
Person = Base.classes.Persons
House = Base.classes.Houses
Now, when I import House in some other module I want to be able to do this:
h = db_session.query(House).first()
print(h.OwnerBackref.Name)
print(h.TenantBackref.Name)
But instead I get an error saying that those 2 backrefs do not exist and instead a field called 'persons' gets added to my House object but the problem here is that it links only 1 (either the Tenant either the Owner). By this I mean that if I do this:
print(h.persons.Name)
It will only print the Name either for the respective Tenant either for the Owner leaving me with no way of accessing the informations for the other one. (Note here that the names that I set to the backrefs are nowhere to be found)
So, my question is how can I use the backrefs I created to access my desired informations ? Am I doing something wrong here ?
The error in your code is that you are using foreign_keys= to define the relationship between the tables but you are passing the local key name not the foreign key name to the function. For your code you cannot use foreign_keys= to define the relationship within the House model as there is only one possible foreign key Person.ID but two possible local keys House.OwnerID and House.TenantID. The primaryjoin= argument should be used instead to specify this.
class Person(db.Model):
__tablename__ = 'Persons'
ID = db.Column(db.Integer, primary_key=True)
Name = db.Column(db.String(255), nullable=False)
class House(db.Model):
__tablename__ = 'Houses'
ID = db.Column(db.Integer,primary_key=True)
OwnerID = db.Column(db.Integer, db.ForeignKey('Persons.ID'), nullable=False)
TenantID = db.Column(db.Integer, db.ForeignKey('Persons.ID'), nullable=False)
Owner = db.relationship('Person', backref='HousesOwned', primaryjoin='House.OwnerID == Person.ID')
Tenant = db.relationship('Person', backref='HousesOccupied', primaryjoin='House.TenantID == Person.ID')
If you placed the relationship statements in in the Person model rather than the House model then you could use either foreign_keys= or primaryjoin= to define the relationship. The following code will result in exactly the same relationships as in the previous code.
class Person(db.Model):
__tablename__ = 'Persons'
ID = db.Column(db.Integer, primary_key=True)
Name = db.Column(db.String(255), nullable=False)
HousesOwned = db.relationship('House', backref='Owner', foreign_keys='[House.OwnerID]')
HousesOccupied = db.relationship('House', backref='Tenant', foreign_keys='[House.TenantID]')
class House(db.Model):
__tablename__ = 'Houses'
ID = db.Column(db.Integer,primary_key=True)
OwnerID = db.Column(db.Integer, db.ForeignKey('Persons.ID'), nullable=False)
TenantID = db.Column(db.Integer, db.ForeignKey('Persons.ID'), nullable=False)
I created a Table a Bmarks which has two foreign keys which have relation with same table Url_hash
class Hashed(Base):
__tablename__ = "url_hash"
hash_id = Column(Unicode(22), primary_key=True)
url = Column(UnicodeText)
clicks = Column(Integer, default=0)
def __init__(self, url):
cleaned_url = str(unidecode(url))
self.hash_id = unicode(generate_hash(cleaned_url))
self.url = url
class Bmark(Base):
__tablename__ = "bmarks"
bid = Column(Integer, autoincrement=True, primary_key=True)
hash_id = Column(Unicode(22), ForeignKey('url_hash.hash_id'))
clean_hash_id = Column(Unicode(22), ForeignKey('url_hash.hash_id'))
description = Column(UnicodeText())
extended = Column(UnicodeText())
stored = Column(DateTime, default=datetime.utcnow)
updated = Column(DateTime, onupdate=datetime.utcnow)
clicks = Column(Integer, default=0)
inserted_by = Column(Unicode(255))
username = Column(Unicode(255), ForeignKey('users.username'),
nullable=False,)
tag_str = Column(UnicodeText())
hashed = relation(Hashed,
foreign_keys="Bmark.hash_id",
backref="bmark",
uselist=False
)
clean_hashed = relation(Hashed,
foreign_keys="Bmark.clean_hash_id",
backref="bmark",
uselist=False
)
I am trying to store url after cleaning it a little bit like removing headers,utm parameters etc for indexing purposes
Error is occurring while creating the database
sqlalchemy.exc.ArgumentError: Error creating backref 'bmark' on relationship 'Bmark.clean_hashed': property of that name exists on mapper 'Mapper|Hashed|url_hash'
Actually the error message is very informative.
Just rename one of your backref="bmark" to something else like backref="my_clean_bmark".
I have two models:
class Report(Base):
__tablename__ = 'report'
id = Column(Integer, primary_key=True)
class ReportPhoto(Base):
__tablename__ = 'report_photo'
id = Column(Integer, primary_key=True)
report_id = Column(Integer, ForeignKey(Report.id), nullable=False)
report = relationship(Report, uselist=False, backref=backref('report_photo', uselist=True))
And I would like to add column to Report model which indicates is there any records within ReportPhoto. I try to use column_property this way:
class Report(Base):
__tablename__ = 'report'
id = Column(Integer, primary_key=True)
has_photo = column_property(
select(ReportPhoto.any())
)
but get an error NameError: name 'ReportPhoto' is not defined. How I can fix this issue?
something like that should work:
class ReportPhoto(Base):
__tablename__ = 'report_photo'
id = Column(Integer, primary_key=True)
report_id = Column(Integer, ForeignKey('report.id'), nullable=False)
class Report(Base):
__tablename__ = 'report'
id = Column(Integer, primary_key=True)
report_photos = relationship(ReportPhoto, backref='report')
has_photo = column_property(
exists().where(ReportPhoto.report_id==id)
)
I will add to #Vladimir lliev's response with some clarification for anyone else that might not see how to do this.
Place the table that will have the 'foreign table referencing' column_property after that which it references. In this case, it means placing Report after ReportPhoto. This will solve your NameError, however, you would be left with a new error on your ReportPhoto foreign key reference. To solve this, place your foreign key table reference in quotes. You can read more by referencing the declarative documentation (e.g., declarative.py) and looking under "Configuring Relationships" --- specifically, read the portion on quoting your foreign references.
With your code, this would look like:
class ReportPhoto(Base):
# This now goes first
__tablename__ = 'report_photo'
id = Column(Integer, primary_key=True)
# Notice the quotations around Report references here
report_id = Column(Integer, ForeignKey("Report.id"), nullable=False)
# Notice the quotations around Report references here
report = relationship("Report",
uselist=False,
backref=backref("report_photo", uselist=True))
class Report(Base):
# This is now _after_ ReportPhoto
__tablename__ = 'report'
id = Column(Integer, primary_key=True)
# ReportPhoto now exists and we will not trip a NameError exception
has_photo = column_property(
select(ReportPhoto.any())
)
I have read the SQLAlchemy documentation and tutorial about building many-to-many relation but I could not figure out how to do it properly when the association table contains more than the 2 foreign keys.
I have a table of items and every item has many details. Details can be the same on many items, so there is a many-to-many relation between items and details
I have the following:
class Item(Base):
__tablename__ = 'Item'
id = Column(Integer, primary_key=True)
name = Column(String(255))
description = Column(Text)
class Detail(Base):
__tablename__ = 'Detail'
id = Column(Integer, primary_key=True)
name = Column(String)
value = Column(String)
My association table is (It's defined before the other 2 in the code):
class ItemDetail(Base):
__tablename__ = 'ItemDetail'
id = Column(Integer, primary_key=True)
itemId = Column(Integer, ForeignKey('Item.id'))
detailId = Column(Integer, ForeignKey('Detail.id'))
endDate = Column(Date)
In the documentation, it's said that I need to use the "association object". I could not figure out how to use it properly, since it's mixed declarative with mapper forms and the examples seem not to be complete. I added the line:
details = relation(ItemDetail)
as a member of Item class and the line:
itemDetail = relation('Detail')
as a member of the association table, as described in the documentation.
when I do item = session.query(Item).first(), the item.details is not a list of Detail objects, but a list of ItemDetail objects.
How can I get details properly in Item objects, i.e., item.details should be a list of Detail objects?
From the comments I see you've found the answer. But the SQLAlchemy documentation is quite overwhelming for a 'new user' and I was struggling with the same question. So for future reference:
ItemDetail = Table('ItemDetail',
Column('id', Integer, primary_key=True),
Column('itemId', Integer, ForeignKey('Item.id')),
Column('detailId', Integer, ForeignKey('Detail.id')),
Column('endDate', Date))
class Item(Base):
__tablename__ = 'Item'
id = Column(Integer, primary_key=True)
name = Column(String(255))
description = Column(Text)
details = relationship('Detail', secondary=ItemDetail, backref='Item')
class Detail(Base):
__tablename__ = 'Detail'
id = Column(Integer, primary_key=True)
name = Column(String)
value = Column(String)
items = relationship('Item', secondary=ItemDetail, backref='Detail')
Like Miguel, I'm also using a Declarative approach for my junction table. However, I kept running into errors like
sqlalchemy.exc.ArgumentError: secondary argument <class 'main.ProjectUser'> passed to to relationship() User.projects must be a Table object or other FROM clause; can't send a mapped class directly as rows in 'secondary' are persisted independently of a class that is mapped to that same table.
With some fiddling, I was able to come up with the following. (Note my classes are different than OP's but the concept is the same.)
Example
Here's a full working example
from sqlalchemy import create_engine, Column, Integer, String, ForeignKey
from sqlalchemy.orm import declarative_base, relationship, Session
# Make the engine
engine = create_engine("sqlite+pysqlite:///:memory:", future=True, echo=False)
# Make the DeclarativeMeta
Base = declarative_base()
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True)
name = Column(String)
projects = relationship('Project', secondary='project_users', back_populates='users')
class Project(Base):
__tablename__ = "projects"
id = Column(Integer, primary_key=True)
name = Column(String)
users = relationship('User', secondary='project_users', back_populates='projects')
class ProjectUser(Base):
__tablename__ = "project_users"
id = Column(Integer, primary_key=True)
notes = Column(String, nullable=True)
user_id = Column(Integer, ForeignKey('users.id'))
project_id = Column(Integer, ForeignKey('projects.id'))
# Create the tables in the database
Base.metadata.create_all(engine)
# Test it
with Session(bind=engine) as session:
# add users
usr1 = User(name="bob")
session.add(usr1)
usr2 = User(name="alice")
session.add(usr2)
session.commit()
# add projects
prj1 = Project(name="Project 1")
session.add(prj1)
prj2 = Project(name="Project 2")
session.add(prj2)
session.commit()
# map users to projects
prj1.users = [usr1, usr2]
prj2.users = [usr2]
session.commit()
with Session(bind=engine) as session:
print(session.query(User).where(User.id == 1).one().projects)
print(session.query(Project).where(Project.id == 1).one().users)
Notes
reference the table name in the secondary argument like secondary='project_users' as opposed to secondary=ProjectUser
use back_populates instead of backref
I made a detailed writeup about this here.
Previous Answer worked for me, but I used a Class base approach for the table ItemDetail. This is the Sample code:
class ItemDetail(Base):
__tablename__ = 'ItemDetail'
id = Column(Integer, primary_key=True, index=True)
itemId = Column(Integer, ForeignKey('Item.id'))
detailId = Column(Integer, ForeignKey('Detail.id'))
endDate = Column(Date)
class Item(Base):
__tablename__ = 'Item'
id = Column(Integer, primary_key=True)
name = Column(String(255))
description = Column(Text)
details = relationship('Detail', secondary=ItemDetail.__table__, backref='Item')
class Detail(Base):
__tablename__ = 'Detail'
id = Column(Integer, primary_key=True)
name = Column(String)
value = Column(String)
items = relationship('Item', secondary=ItemDetail.__table__, backref='Detail')