SQLAlchemy Using relationship() - python

I am using SQLAlchemy here, trying to make a couple tables and link them and am having problems implementing this.
class Team(Base):
__tablename__ = "teams"
id = Column(Integer, primary_key=True)
espn_team_id = Column(Integer, unique=True, nullable=False)
games = relationship("Game", order_by="Game.date")
def __init__(self, name):
self.name = name
self.espn_team_id = espn_team_id
self.games = games
class Game(Base):
__tablename__ = "games"
id = Column(Integer, primary_key=True)
espn_game_id=Column(Integer, unique=True, nullable=False)
date = Column(Date)
h_espn_id = Column(Integer, ForeignKey('teams.espn_team_id'))
a_espn_id = Column(Integer, ForeignKey('teams.espn_team_id'))
I have this in one file which I use to create the tables. Then in another file I use the insert() function to put values into both tables. I think if I have a team with espn_team_id 360, and then I put in multiple games into the game table which have either h_espn_id=360, or a_espn_id=360, i should be able to do:
a = Table("teams", metadata, autoload=True)
a = session.query(a).filter(a.c.espn_team_id==360).first().games
and it should give me a list of all games team with ID 360 has played. But instead I get this error
AttributeError: 'NamedTuple' object has no attribute 'games'
What am I misunderstanding about SQLAlchemy or relational databases here?

Firstly, you don't have to create another Table object, as it is available as Team.__table__. Anyway, you can just query the mapped class, e.g.
query = Session.query(Team).filter(Team.espn_team_id == 360)
team360 = query.one()
games = team360.games
Refer to the documentation for methods .one(), .first(), and .all(): http://docs.sqlalchemy.org/en/latest/orm/query.html

Here is the solution I found, took way too long to understand this...
class Team(Base):
__tablename__ = "teams"
id = Column(Integer, primary_key=True)
name = Column(String)
espn_team_id = Column(Integer, unique=True, nullable=False)
h_games = relationship(
"Game",
primaryjoin="Game.h_espn_id==Team.espn_team_id",
order_by="Game.date")
a_games = relationship(
"Game",
primaryjoin="Game.a_espn_id==Team.espn_team_id",
order_by="Game.date")
#hybrid_property
def games(self):
return self.h_games+self.a_games
def __init__(self, name):
self.name = name
self.espn_team_id = espn_team_id
self.h_games = h_games
self.a_games = a_games
self.games = games

Related

sqlalchemy column_property in self-referential

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

Creating new instances in SQLAlchemy using lookup values instead of foreign key IDs

I have an existing database an want to build an SQLAlchemy wrapper to use the DB in Python. Lookup tables like the following are commonly used in the DB:
class Industry(Base):
__tablename__ = 'industry'
id = Column(Integer, primary_key=True)
name = Column(String, nullable=True)
class IndustrySector(Base):
__tablename__ = 'industry_sector'
id = Column(Integer, primary_key=True)
industry_id = Column(Integer, ForeignKey('industry.id'), nullable=False)
name = Column(String, nullable=True)
What I would like to do is to create a new instance of IndustrySector using the name of the industry rather than the (technical) key of the industry, i.e.,
new_industry_sector = IndustrySector(industry_id = 'Manufacturing', name = 'Textile')
instead of
manu_industry_id = session.query(Industry.id).filter(Industry.name=='Manufacturing').first().id
new_industry_sector = IndustrySector(name = 'Textile', industry_id = new_industry_id)
Obviously, above example can't work because I am filtering on the ID rather than the name. But I don't know how to get the name of the foreign-keyed table into this. Of course I could simply add a #classmethod that handles the lookup, but if there exists any built-in functionality I'd much rather use that.
Any help / pointers are appreciated
You could create a constructor which will replace the string with the id value whenever an object is created.
class IndustrySector(Base):
__tablename__ = 'industry_sector'
id = Column(Integer, primary_key=True)
industry_id = Column(Integer, ForeignKey('industry.id'), nullable=False)
name = Column(String, nullable=True)
def __init__(self, industry_id, name):
self.name = name
self.industry_id = fetch_id(industry_id)
def fetch_id(industry_id):
# fetch and return the id

SqlAlchemy Database Issue

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 am having an issue populating my tables for my pyramid project

So I recently changed from sqlite to postgres so that I could push my site to heroku. The issue I am having is that the tables are now getting built out of order??
class Data(Base):
__tablename__ = 'data'
id = Column(Integer, autoincrement=True, primary_key=True)
data_type = Column(Integer, primary_key=True, unique=True)
value = Column(Text, primary_key=True, unique=True)
range_lower = Column(Integer)
range_upper = Column(Integer)
gold = Column(Boolean)
def __init__(self, data_type, value, range_lower=0, range_upper=0, gold=False):
self.data_type = data_type
self.value = value
self.range_lower = range_lower
self.range_upper = range_upper
self.gold = gold
class Page(Base):
__tablename__ = 'page'
id = Column(Integer, autoincrement=True, primary_key=True)
data_type = Column(Integer, ForeignKey('data.data_type'))
description = Column(Text)
annotations_per_page = Column(Integer)
uses_gold = Column(Boolean)
def __init__(self, data_type, description='default description', annotations_per_page=1, uses_gold=False):
self.data_type = data_type
self.description = description
self.annotations_per_page = annotations_per_page
self.uses_gold = uses_gold
The issue I am having is that sqlalchemy/pyramid or whatever the populate script generated by python setup.py develop on one of these types of projects, is trying to create the table Page before the table Data. Since there are foreign keys in Page that depend on Table, this is failing. I can't find any mention of this issue in the documentation, any insight would be greatly appreciated.
-Sam
you need to use database metadata
and these functions
metadata =MetaData()
creat_all()
it generates the tables in order of their dependency.
ref:http://docs.sqlalchemy.org/en/rel_0_7/core/schema.html

How to build many-to-many relations using SQLAlchemy: a good example

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

Categories