In SQLAlchemy, how to recreate the table. first, I create a table ToDoTask, with 4 columns: content, id, priority and status by the following code:
Base = declarative_base()
class ToDoTask(Base):
__tablename__ = 'todotask'
content = Column(String(250), nullable = False)
id = Column(Integer, primary_key = True)
priority = Column(Integer)
status = Column(String(8))
engine = create_engine('sqlite:///todotask.db')
Base.metadata.create_all(engine)
then if I found I need to redesign the table by add a new column,for example Time. I add the column into the Class, and rerun, but checked with inspect function and found the Time column is not added.
so how to do this?
If I get it right, I think you should use a migration tool like Alembic
Related
There are 3 tables: Account, Role, User. Both Role and User have a foreign key account_id that points to Account.
A user can have multiple roles, hence the roles_users table which acts as the secondary relation table between Role and User.
The Account table is a tenant table for our app, it is used to separate different customers.
Note that all tables have (besides Account) have composite primary keys with account_id. This is done for a few reasons, but let's say it's done to keep everything consistent.
Now if I have a simple secondary relationship (User.roles - the one that is commented out) all works as expected. Well kind of.. it throws a legitimate warning (though I believe it should be an error):
SAWarning: relationship 'User.roles' will copy column role.account_id to column roles_users.account_id, which conflicts with relationship(s): 'User.roles' (copies user.account_id to roles_users.account_id). Consider applying viewonly=True to read-only relationships, or provide a primaryjoin condition marking writable columns with the foreign() annotation.
That's why I created the second relation User.roles - the one that is not commented out. Querying works as expected which has 2 conditions on join and everything. However I get this error when I try to save some roles on the user:
sqlalchemy.orm.exc.UnmappedColumnError: Can't execute sync rule for source column 'roles_users.role_id'; mapper 'Mapper|User|user' does not map this column. Try using an explicit `foreign_keys` collection which does not include destination column 'role.id' (or use a viewonly=True relation).
As far as I understand it, SA is not able to figure out how to save the secondary because it has a custom primaryjoin and secondaryjoin so it proposes to use viewonly=True which has the effect of just ignoring the roles relation when saving the model.
The question is how to save the roles for a user without having to do it by hand (the example is commented out in the code). In the real app we have many secondary relationships and we're saving them in many places. It would be super hard to rewrite them all.
Is there a solution to keep using User.roles = some_roles while keeping the custom primaryjoin and secondaryjoin below?
The full example using SA 1.1.9:
from sqlalchemy import create_engine, Column, Integer, Text, Table, ForeignKeyConstraint, ForeignKey, and_
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import foreign, relationship, Session
Base = declarative_base()
class Account(Base):
__tablename__ = 'account'
id = Column(Integer, primary_key=True)
roles_users = Table(
'roles_users', Base.metadata,
Column('account_id', Integer, primary_key=True),
Column('user_id', Integer, primary_key=True),
Column('role_id', Integer, primary_key=True),
ForeignKeyConstraint(['user_id', 'account_id'], ['user.id', 'user.account_id']),
ForeignKeyConstraint(['role_id', 'account_id'], ['role.id', 'role.account_id']),
)
class Role(Base):
__tablename__ = 'role'
id = Column(Integer, primary_key=True)
account_id = Column(Integer, ForeignKey('account.id'), primary_key=True)
name = Column(Text)
def __str__(self):
return '<Role {} {}>'.format(self.id, self.name)
class User(Base):
__tablename__ = 'user'
id = Column(Integer, primary_key=True)
account_id = Column(Integer, ForeignKey('account.id'), primary_key=True)
name = Column(Text)
# This works as expected: It saves data in roles_users
# roles = relationship(Role, secondary=roles_users)
# This custom relationship - does not work
roles = relationship(
Role,
secondary=roles_users,
primaryjoin=and_(foreign(Role.id) == roles_users.c.role_id,
Role.account_id == roles_users.c.account_id),
secondaryjoin=and_(foreign(id) == roles_users.c.user_id,
account_id == roles_users.c.account_id))
engine = create_engine('sqlite:///')
Base.metadata.create_all(engine)
session = Session(engine)
# Create our account
a = Account()
session.add(a)
session.commit()
# Create 2 roles
u_role = Role()
u_role.id = 1
u_role.account_id = a.id
u_role.name = 'user'
session.add(u_role)
m_role = Role()
m_role.id = 2
m_role.account_id = a.id
m_role.name = 'member'
session.add(m_role)
session.commit()
# Create 1 user
u = User()
u.id = 1
u.account_id = a.id
u.name = 'user'
# This does not work
u.roles = [u_role, m_role]
session.add(u)
session.commit()
# Works as expected
i = roles_users.insert()
i = i.values([
dict(account_id=a.id, role_id=u_role.id, user_id=u.id),
dict(account_id=a.id, role_id=m_role.id, user_id=u.id),
])
session.execute(i)
# re-fetch user from db
u = session.query(User).first()
for r in u.roles:
print(r)
NOTE: Switching primaryjoin with secondaryjoin does not help.
Solution for posterity sake - switch foreign wrappers and careful with primary vs secondary joins:
Instead of this:
roles = relationship(
Role,
secondary=roles_users,
primaryjoin=and_(foreign(Role.id) == roles_users.c.role_id,
Role.account_id == roles_users.c.account_id),
secondaryjoin=and_(foreign(id) == roles_users.c.user_id,
account_id == roles_users.c.account_id))
Do this:
roles = relationship(
Role,
secondary=roles_users,
primaryjoin=and_(id == foreign(roles_users.c.user_id), account_id == foreign(roles_users.c.account_id)),
secondaryjoin=and_(Role.id == foreign(roles_users.c.role_id), Role.account_id == roles_users.c.account_id),
)
I have this table structure in SQLAlchemy:
User(Base)
__tablename__ = 'users'
id = Column(Integer, primary_key = True)
addresses = relationship("Address", backref="user")
Address(Base)
__talbename__ = 'addresses'
id = Column(Integer, primary_key = True)
user_id = Column(Integer, ForeignKey(users.id))
So a Many (addresses) to One (user) relationship.
My question is now how can I easily add a an address to a user without deleting the addresses already stored.
In pure SQL I would just insert a row in the address table with a foreign key to the right user.
This is how I'm doing it right now in SQLAlchemy (method in User Class):
def add_address(self, address):
adds = self.addresses
adds.append(address)
self.addresses = adds
So What I'm basically doing is first finding all the addresses, then appending the list to overwrite the list again with the extra address.
I was wondering if there is a more efficient way to do this?
You can define another relationship between User and Address, which will not be loaded using lazy='noload' (see Setting Noload):
class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key = True)
addresses = relationship("Address", backref="user")
addresses_noload = relationship("Address", lazy='noload')
def add_address(self, address):
adds = self.addresses_noload # #note: use the relationship which does not load items from DB
adds.append(address)
Also see sqlalchemy add child in one-to-many relationship for similar question.
Right now I'm doing this:
class MyTest(Base):
__tablename__ = 'mytest'
id = Column(Integer, primary_key = True)
name = Column(String(255), nullable=False)
created_at = Column(DateTime)
Base.metadata.create_all(engine)
But in the tutorial, another way is this:
user = Table('user', metadata,
Column('user_id', Integer, primary_key = True),
Column('user_name', String(16), nullable = False),
Column('email_address', String(60)),
Column('password', String(20), nullable = False)
)
Which method should I be using? By the way, I will be using sqlalchemy-migrate, (I don't know if that will change the answer)
If you want to just access the data from the table and want to use SQLAlchemy as a mediator then you have to use TABLE. But if you want to use the each row of the table as an separate object then you have to use declarative base.
Which way you have to use is up to you and how you want to use SQLAlchemy.
This is my file so far:
from sqlalchemy import create_engine, ForeignKey
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship, backref
from sqlalchemy import Column, Integer, String
from sqlalchemy import Table, Text
engine = create_engine('mysql://root:ababab#localhost/alctest',
echo=False)
Base = declarative_base()
class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key = True)
name = Column(String(100))
fullname = Column(String(100))
password = Column(String(100))
addresses = relationship("Address", order_by="Address.id", backref="user")
def __init__(self, name, fullname, password):
self.name = name
self.fullname = fullname
self.password = password
def __repr__(self):
return "<User('%s','%s', '%s')>" % (self.name, self.fullname, self.password)
class Address(Base):
__tablename__ = 'addresses'
id = Column(Integer, primary_key = True)
email_address = Column(String(100), nullable=False)
#foreign key, must define relationship
user_id = Column(Integer, ForeignKey('users.id'))
user = relationship("User", backref = backref('addresses',order_by=id))
Base.metadata.create_all(engine)
This file is pretty simple. It creates a User and Address tables. After I run this file, the tables are created.
But now I want to add a column to "User". How can I do that? What do I have to do?
You can add column with Table.append_column method.
test = Column('test', Integer)
User.__table__.append_column(test)
But this will not fire the ALTER TABLE command to add that column in database. As per doc given for append_column that command you have to run manually after adding that column in model.
Short answer: You cannot: AFAIK, currently there is no way to do it from sqlalchemy directly.
Howerever, you can use sqlalchemy-migrate for this if you change your model frequently and have different versions rolled out to production. Else it might be an overkill and you may be better off generating the ALTER TABLE ... scripts manually.
I am brand new to sqlalchemy. Trying to get a query to work and am having issues with a join.
I have two tables both of which have a column named "Id" and I need to join on that table. My code looks like this:
table1 = server.tab1
table2 = server.tab2
joined = server.join(table1,table2, table1.Id == table2.Id)
where = table1.createDate > start
results = joined.filter(where).all()
This results in the following error message:
Implicitly combining column table1.Id with column table2.Id under attribute 'Id'. Please configure one or more attributes for these same-named columns explicitly.
Question is, how do I configure these attributes?
TIA!
With sql soup
joined = server.session.query(table1).join((table2,table1.id == table2.id))
where = table1.createDate > start
results = joined.filter(where).all()
I had this same issue so I figured I would add the solution I came up with (based on http://www.mail-archive.com/sqlalchemy#googlegroups.com/msg23735.html). It's certainly not the cleanest thing I've ever coded, but using your example from above, it would be approximately:
from sqlalchemy import select
aliased_table1 = select([
table1.c.Id.label("renamed_id_col"),
table1.c.any_other_columns_you_want_returned_but_not_renamed,
...
]).correlate(None).alias()
joined = server.join(aliased_table1, table2, aliased_table1.c.renamed_id_col == table2.Id)
where = aliased_table1.c.createDate > start
results = joined.filter(where).all()
One way about this is to label all the columns in one of the tables, so that you don't have any collision of the column names:
table1 = server.tab1
table2 = server.with_labels(server.tab2)
joined = server.join(table1,table2, table1.Id == table2.tab2_Id)
where = table1.createDate > start
results = joined.filter(where).all()
table2 ends up being a labeled table, where all the column names are prefaced with the table name, so as not to interfere with the column names in table1.
You can use join feature provided by sqlalchemy,
see the example below, no need to do it manually sqlalchme does it for us,
from sqlalchemy import create_engine, Column, String, Integer, Table, ForeignKey
from sqlalchemy.orm import mapper, relationship
from sqlalchemy.schema import MetaData
from sqlalchemy.orm.session import sessionmaker
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
class User(Base):
__tablename__ = 'users'
id = Column('user_id',Integer,primary_key = True)
name = Column('user_name',String(20))
addresses = relationship("Address",backref="user")
class Address(Base):
__tablename__ = 'addresses'
id = Column('user_id',ForeignKey('users.user_id'))
address = Column('address',String(30))
pk = Column('address_id',Integer,primary_key=1)
if __name__ == "__main__":
engine = create_engine("sqlite://", echo = True)
Base.metadata.create_all(engine)
session = sessionmaker(bind=engine)()
u1 = User(name='japan')
session.add(u1)
session.commit()
u1.addresses.append(Address(address='a1'))
u1.addresses.append(Address(address='a2'))
session.flush()
q = session.query(User).join(User.addresses).all()
print "="*23,q