How to add alias/description to columns in SQLAlchemy models? - python

I need to add descriptive names to columns that will be used as keys for JSON data. I could not find any example or documentation related to that.
from datetime import datetime
from . import db
class MyTable(db.Model):
__tablename__ = "MyTable"
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
name = db.Column(db.String(255)
created_at = db.Column(db.DateTime)
modified_at = db.Column(db.DateTime)
row=Table.query.first()
for c in row.__table__.columns:
print(c.name)
print(c.description)
There is a description attribute in column but I cannot find, how can I update this. There is no such keyword argument in db.Column method.
Is there anyway to update this with some descriptive name and later use it for generating JSON with descriptive keys. There are some other alternatives like doc or defining a mapping dictionary but I want to use this specifically.

Related

How to autogenerate UUID for Postgres in Python?

I'm trying to create objects in Postgres db.
I'm using this approach https://websauna.org/docs/narrative/modelling/models.html#uuid-primary-keys
class Role(Base):
__tablename__ = 'role'
# Pass `binary=False` to fallback to CHAR instead of BINARY
id = sa.Column(UUIDType(binary=False), primary_key=True)
But when I create object
user_role = Role(name='User')
db.session.add(user_role)
db.session.commit()
I have the following error:
sqlalchemy.exc.IntegrityError: (psycopg2.IntegrityError) null value in column "id" violates not-null constraint
Looks like I didn't provide any ID. So, how I can make the database auto-generate it or generate on my own?
You appear to be using this code. It's missing a default for the column. You're emulating this SQL:
id UUID PRIMARY KEY DEFAULT uuid_generate_v4()
But you've already linked to the correct code.
id = Column(UUID(as_uuid=True),
primary_key=True,
server_default=sqlalchemy.text("uuid_generate_v4()"),)
Alternatively if you don't want to load a Postgres UUID extension, you can create the UUIDs in Python.
from uuid import uuid4
id = Column(UUID(as_uuid=True),
primary_key=True,
default=uuid4,)
You could use the uuid module and just set a column default. For example:
from uuid import uuid4
from sqlalchemy import Column, String
class Role(Base):
__tablename__ = 'role'
id = Column(String, primary_key=True, default=uuid4)
What I actually came to is:
import uuid
class SomeClass(db.Model):
__tablename__ = 'someclass'
id = db.Column(UUID(as_uuid=True),
primary_key=True, default=lambda: uuid.uuid4().hex)
import uuid
myId = uuid.uuid4()
print(myId)

Can I "touch" a SQLAlchemy record to trigger "onupdate"?

Here's a SQLAlchemy class:
class MyGroup(Base):
__tablename__ = 'my_group'
group_id = Column(Integer, Sequence('my_seq'), primary_key=True)
group_name = Column(String(200), nullable=False, index=True)
date_created = Column(DateTime, default=func.now())
date_updated = Column(DateTime, default=func.now(), onupdate=func.now())
Anytime I add a group_name or (for example) update the group_name, the date_updated field will get updated. That's great.
But sometimes there are cases where I want to mark a group as "updated" even if the group record itself did not change (for example, if data in a different but related table is updated).
I could do it manually:
group = session.query(MyGroup).filter(MyGroup.group_name=='Some Group').one()
group.date_updated = datetime.datetime.utcnow()
session.commit()
but I'd really rather let the model do it in its own way, rather than recreate a Python process to manually update the date. (For example, to avoid mistakes like where maybe the model uses now() while the manual function mistakenly uses utcnow())
Is there a way with SQLAlchemy to "touch" a record (kind of like UNIX touch) that wouldn't alter any of the record's other values but would trigger the onupdate= function?
Just to add to this answer, the documentation states the following:
The Column.default and Column.onupdate keyword arguments also accept Python functions. These functions are invoked at the time of insert or update if no other value for that column is supplied, and the value returned is used for the column’s value.
Key part being: are invoked at the time of insert or update if no other value for that column is supplied.
Key part of the key part: if no other value for that column is supplied
So with a simple update statement with empty values, does the trick:
from sqlalchemy import update
stmt = update(ModelName).where(ModelName.column.in_(column_values)).values()
db.engine.execute(update_product_mapping_info)
I am using the sqlalchemy.sql.expression.update for this, documentation here.
Here's the Model column definition I have:
from datetime import datetime
last_updated = Column(db.DateTime, onupdate=datetime.utcnow)
To show a complete example, building on #Chayemor's answer I did the following:
import sqlalchemy.sql.functions as func
from sqlalchemy import Column, Integer, String, update
from . import database as db
Base = declarative_base()
class Object(Base):
__tablename__ = "objects"
id = Column(Integer, primary_key=True, nullable=False)
last_update = Column(
DateTime,
server_default=func.now(),
onupdate=func.current_timestamp()
)
def touch(self):
stmt = update(Game).where(Game.id == self.id)
db.engine.execute(stmt)
From here, running obj.touch() updates its last_update field in the database without changing any other data.
Another way to do this is to call orm.attributes.flag_modified on an instance and attribute. SQLAlchemy will mark the attribute as modified even though it is unchanged and generate an update.
with Session() as s, s.begin():
mg = s.execute(sa.select(MyGroup)).scalar_one()
orm.attributes.flag_modified(mg, 'group_name')
Note that the "dummy" update will be included in the generated SQL's SET clause
UPDATE tbl
SET group_name=%(group_name)s,
date_updated=now()
WHERE tbl.group_id = %(tbl_group_id)s
in contrast with that generated by Chayemor's answer:
UPDATE tbl
SET date_updated=now()
WHERE tbl.group_name = %(group_name_1)s
This may be significant (consider triggers for example).
I haven't looked at the source but from the docs it seems that this is only triggered by issuing a SQL UPDATE command:
onupdate – A scalar, Python callable, or ClauseElement representing a default value to be applied to the column within UPDATE statements, which wil be invoked upon update if this column is not present in the SET clause of the update. This is a shortcut to using ColumnDefault as a positional argument with for_update=True.
If your concern is ensuring that your "touch" uses the same function as the onupdate function you could define a method on your model to perform the touch and have the onupdate parameter point this method.
I think something like this would work:
class MyGroup(Base):
__tablename__ = 'my_group'
group_id = Column(Integer, Sequence('my_seq'), primary_key=True)
group_name = Column(String(200), nullable=False, index=True)
date_created = Column(DateTime, default=func.now())
date_updated = Column(
DateTime,
default=self.get_todays_date,
onupdate=self.get_todays_date)
def get_todays_date(self):
return datetime.datetime.utcnow()
def update_date_updated(self):
self.date_updated = self.get_todays_date()
You could then update your record like this:
group.update_date_updated()
session.commit()

Creating second model upon instantiation of the first within sqlachemy

I am currently working with some legacy code that looks as follows:
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Unicode
from sqlalchemy.dialects.postgresql import ARRAY, TEXT
Base = declarative_base()
class Book(Base):
__tablename__ = 'book'
id = Column(Integer, primary_key=True)
title = Column(Unicode)
keywords = Column('keywords', ARRAY(TEXT), primary_key=False)
The keywords are currently being kept as an array, but I'd like to flatten this out and have them be in their own separate model
class Keyword():
__tablename__ = 'keyword'
id = Column(Integer, primary_key=True)
book_id = Column(Integer, ForeignKey('book.id', ondelete='cascade'),
nullable=False)
keyword = Column(Unicode)
How can I make it such that when a Book() is created, it also creates the
accompanying keywords? As an intermediate step for migrating the API, I'd like to keep the current array column, but also have the accompanying Keyword() instances be created.
I could do this within an __init__ method, but would need to know what the current Session() was, in order to run a commit. I could also perhaps use a property attribute, attached to keywords, but am not sure how that would work given that I am working with a class that inherits from SQLAlchemy's base, and not with a regular class that I have defined. What's the correct way to do this?
You can use object_session to find out the session of a given instance.
But if you define relationship between a Book and Keywords, you should not need even bother:
class Book(Base):
# ...
rel_keywords = relationship('Keyword', backref='book')
def init_keyword_relationship(self):
for kw in self.keywords:
self.rel_keywords.add(Keyword(keyword=kw))
sess = # ... get_session...
books = sess.query(Book).all()
for book in books:
book.init_keyword_relationship()
sess.commit()
However, I would do a migration once and get rid of the keywords array in order not to add a logic to keep those in sync.

Get custom attributes of a class Python

Is it possible to get the name of our custom attributes of a class in Python ? For instance, here's my class :
class User(db.Model):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key = True)
login = db.Column(db.String(100))
password = db.Column(db.String(100))
first_name = db.Column(db.String(100))
last_name = db.Column(db.String(100))
email = db.Column(db.String(100))
age = db.Column(db.Integer)
sex = db.Column(db.String(10))
What I want is to get the list of my class attributes (and only those that I defined !). I was thinking about using dir(self) and filtering on those not starting with __ but it's not really revelant because there are other fields who are built-in such as metadata, query and so on.
I saw a function getattr (or getattribute) but it's only for a given field.
I don't want to use a dict of keys because it have to stay generic and I don't want to modify the dict everytime I add a field.
As I'm using SqlAlchemy ORM, I got this when trying self.__dict__ :
{'_sa_instance_state': <sqlalchemy.orm.state.InstanceState object at 0x7ffbcf252050>}
I also tried a lot of things such as those described here :
Python dictionary from an object's fields but nothing worked.
Does anyone have a solution ?
Thanks !

What's the proper way to describe an associative object by SQLalchemy the declarative way

I'm looking for a way to describe an associative object the declarative way. Beyond storing the foreign keys in the association table, I need to store information like the creation date of the association.
Today, my model looks like that :
# Define the User class
class User(Base):
__tablename__ = 'users'
# Define User fields
id = schema.Column(types.Integer(unsigned=True),
schema.Sequence('users_seq_id', optional=True), primary_key=True)
password = schema.Column(types.Unicode(64), nullable=False)
# Define the UserSubset class
class UserSubset(Base):
__tablename__ = 'subsets'
# Define UserSubset fields
id = schema.Column(types.Integer(unsigned=True),
schema.Sequence('subsets_seq_id', optional=True), primary_key=True)
some_short_description = schema.Column(types.Unicode(50), nullable=False)
# Define the subset memberships table
subset_memberships = schema.Table('group_memberships', Base.metadata,
schema.Column('user_id', types.Integer(unsigned=True), ForeignKey('users.id')),
schema.Column('subset_id', types.Integer(unsigned=True), ForeignKey('subsets.id')),
schema.Column('created', types.DateTime(), default=now, nullable=False),
)
Can I connect everything in an associative object ? Or should I change stop using the declarative way ?
What you are using at the moment is just a Many-to-Many-relation. How to work with association objects is described in the docs.
There is also an extension called associationproxy which simplifies the relation.
As you can see in the manual, configuring a one to many relation is really simple:
class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
name = Column(String(50))
addresses = relation("Address", backref="user")
class Address(Base):
__tablename__ = 'addresses'
id = Column(Integer, primary_key=True)
email = Column(String(50))
user_id = Column(Integer, ForeignKey('users.id'))
Many to many relations isn't much harder:
There’s nothing special about many-to-many with declarative. The secondary argument to relation() still requires a Table object, not a declarative class. The Table should share the same MetaData object used by the declarative base:
keywords = Table('keywords', Base.metadata,
Column('author_id', Integer, ForeignKey('authors.id')),
Column('keyword_id', Integer, ForeignKey('keywords.id'))
)
class Author(Base):
__tablename__ = 'authors'
id = Column(Integer, primary_key=True)
keywords = relation("Keyword", secondary=keywords)
You should generally not map a class and also specify its table in a many-to-many relation, since the ORM may issue duplicate INSERT and DELETE statements.
Anyway, what you seem to be doing might be better served with inheritance. Of course, there can be complex table relations that will be a pathological case for the declarative way, but this doesn't seem to be one of them.
One more thing, code comments should state what the following code does ans why, not how it does it. Having a # Define the User class comment is almost like having a line of code saying a = 1 # assing value 1 to variable "a".

Categories

Resources