In SqlAlchemy, should I use classical mapping or the other way? - mysql

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.

Related

Can I specify the join conditions of a relationship when writing a query instead of doing so in my model class?

I have three models...
class Customer(db.Model, TimestampMixin):
pk = db.Column(db.Integer, primary_key=True)
name = db.Column(db.Text, nullable=False)
class User(db.Model, TimestampMixin):
pk = db.Column(db.Integer, primary_key=True)
name = db.Column(db.Text, nullable=False)
class CustomerUserStat(db.Model):
__table_args__ = (
UniqueConstraint("customer_pk", "user_pk", name="customer_pk_user_pk"),
)
pk = db.Column(db.Integer, primary_key=True)
customer_pk = db.Column(db.Integer, db.ForeignKey("customers.pk"), nullable=False)
user_pk = db.Column(db.Integer, db.ForeignKey("users.pk"), nullable=False)
data = db.Column(db.Integer, nullable=False, default=0)
I’ve tried...
m.Customer.query.outerjoin(
m.CustomerUserStat,
(m.CustomerUserStat.user_pk == entity.pk)
& (m.CustomerUserStat.customer_pk == m.Customer.pk)
).add_entity(m.CustomerUserStat)
But that gives me a tuple with a Customer object and a CustomerUserStat (or None if one doesn’t exist), close but not quite what I am looking for.
I’ve also tried adding a userstats relationship to the Customer model and ...
m.Customer.query.outerjoin(m.Customer.userstats
).filter(m.CustomerUserStat.user_pk == user.pk
).options(contains_eager(m.Customer.userstats))
But that didn’t produce any results if the user's CustomerUserStat record was missing.
Ultimately, I would like to write a query that will give me a list of Customer objects with an attribute that has an instance of CustomerUserStat for a user that I specify in the query and I need to be able to order by the CustomerUserStat.data field and paginate the results.
It looks to me like you are looking for the AssociationProxy pattern
I came up with this hack to get the query that I needed. In my Flask view function, I defined a new class that inherits from the original and added the relationship to it. Then I use the new class to create the query.
class TempCustomer(m.Customer):
userstats = db.relationship(
m.CustomerUserStat,
primaryjoin=(m.Customer.pk == m.CustomerUserStat.customer_pk) &
(m.CustomerUserStat.user_pk == agent.pk),
uselist=False,
viewonly=True,
lazy="joined",
)
q = TempCustomer.query.outerjoin(m.CustomerUserStat, (m.Customer.pk ==
m.CustomerUserStat.customer_pk) & (m.CustomerUserStat.user_pk == agent.pk),)
This works for me, but doesn't seem like the best solution.
It also results in two LEFT JOINS for the same data which is probably not the most efficient.

SQLAlchemy lazy sa.select([])

I use column properties to refer to properties of other tables.
I'd like to be able to select arbitrary information from other models indepentend of the declaration order of the models.
Just like sa.orm.relationship declares its relation by strings.
The problem I face is, that property_of_a can not be initialized, because ModelB is not declared at that moment.
Here is a simplified (not working) example.
A working alternative for this example might use sa.ext.associationproxy
I do not think, that I can use association proxies because I'd like to be able to use CONCAT, GROUP_CONCAT and IF-THEN-ELSE Queries.
Is there a way to initialize a sa.select query lazy (for example by strings like sa.orm.relationship)
import sqlalchemy as sa
Base = sa.ext.declarative.declarative_base()
metadata = Base.metadata
class ModelA(Base):
__tablename__ = "model_a_table"
id = sa.Column(sa.Integer, primary_key=True)
name = sa.Column(sa.String(255), nullable=False, unique=True)
model_b_id = sa.Column(sa.ForeignKey('model_b_table.id', ondelete='SET NULL'), index=True)
model_b = sa.orm.relationship("ModelB")
property_of_a = sa.orm.column_property(sa.select([ModelB.name]).where(ModelB.id == model_b_id))
class ModelB(Base):
__tablename__ = "model_b_table"
id = sa.Column(sa.Integer, primary_key=True)
name = sa.Column(sa.String(255), nullable=False, unique=True)
model_a_id = sa.Column(sa.ForeignKey('model_a_table.id', ondelete='SET NULL'), index=True)
model_b = sa.orm.relationship("ModelA")
property_of_b = sa.orm.column_property(sa.select([ModelA.name]).where(ModelB.id == model_a_id))
sa.orm.configure_mappers()

SQLAlchemy: how to recreate the table

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

SQLAlchemy Add Many to One row logic

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.

After I create my tables using SQLAlchemy, how can I add additional columns to it?

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.