I have a model like
class Conversation(CommonBase, Base):
[...]
title = schema.Column(types.String(50), nullable=True)
updated_at = schema.Column(
types.DateTime,
onupdate=now)
[...]
Now I need to do a data migration that should not change the updated_at value, but only the title. How do I do that?
Related
Each of my mapped class contains created_by and updated_by audit properties that I would like to set automatically upon INSERT and UPDATE of respective objects.
class User(Base):
__tablename__ = 'user'
id = Column(BigInteger, primary_key=True)
name = Column(Text, nullable=False)
...
class Address(Base):
__tablename__ = 'address'
id = Column(BigInteger, primary_key=True)
street = Column(Text, nullable=False)
...
created_by = Column(BigInteger) # references user.id
updated_by = Column(BigInteger) # references user.id
...
Is there a way to handle this centrally in SQLAlchemy? I looked at the events but it appears it needs to be setup for every single mapped class individually (note the SomeClass in the decorator).
#event.listens_for(SomeClass, 'before_insert')
def on_insert(mapper, connection, target):
target.created_by = context["current_user"] # I want to be able to do this not just for 'SomeClass' but for all mapped classes
#event.listens_for(SomeClass, 'before_update')
def on_update(mapper, connection, target):
target.updated_by = context["current_user"] # I want to be able to do this not just for 'SomeClass' but for all mapped classes
One solution here is to use the default parameters in the Column class provided by sqlalchemy. You can actually pass a callable to both default (to execute when first created) and onupdate to execute whenever updated.
def get_current_user():
return context["user"].id
class Address(Base):
__tablename__ = 'address'
...
created_by = Column(default = get_current_user)
updated_by = Column(default = get_current_user, onupdate=get_current_user)
Managed to figure it out, though somewhat concerned about using a dunder method __subclasses__() on declarative_base. If there is a better alternative do suggest.
def on_insert(mapper, connection, target):
target.created_by = context["user"].id
target.updated_at = datetime.utcnow()
def on_update(mapper, connection, target):
target.updated_by = context["user"].id
target.updated_at = datetime.utcnow()
Base.metadata.create_all()
mapped_classes = Base.__subclasses__()
for mapped_class in mapped_classes:
event.listen(mapped_class, 'before_insert', on_insert)
event.listen(mapped_class, 'before_update', on_update)
The context being referred to here is actually starlette-context
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
Say I have these 2 objects, with a one-to-many relationship:
class A(Base):
...
collection = relationship("B")
class B(Base):
...
a_id = Column(Integer, ForeignKey('table_for_a.id'), nullable=False)
key = Column(String(50), nullable=False)
How can I query A's which have B("apple"), B("orange") and B("banana") in A.collection at the same time?
Thanks.
After a while digging, I found out that I could get the desired result like so:
from sqlalchemy import and_
...
session.query(A).filter(
and_(
A.collection.any(key="apple"),
A.collection.any(key="orange"),
A.collection.any(key="banana")
)
).all()
I have model like this:
class CreatedMixin(DeclarativeBase):
__abstract__ = True
#declared_attr
def updated_by(cls):
return Column(Integer, ForeignKey('user.user_id',
onupdate="cascade", ondelete="restrict"),
onupdate=CURRENT_USER_ID)
updated_at = Column(DateTime, nullable=False, default=dt.now(),
onupdate=dt.now())
And auth with repoze.what-quickstart. How to get CURRENT_USER_ID?
I think you could do the following
identity = request.environ.get('repoze.who.identity')
and the gettin the user id
id = identity['user'].id
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.