How can I remove an autoupdate on sqlalchemy Column? - python

When updating two other column values, the created column, which remains untouched on my code, updates to the current time by itself.
This is the Model
class Query(db.Model):
__tablename__ = "query"
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(2000))
created = db.Column(db.DateTime)
modified = db.Column(db.DateTime)
launched = db.Column(db.Integer)
exception = db.Column(db.String(500), nullable=True)
enable = db.Column(db.Boolean)
expired = db.Column(db.Boolean)
app_id = db.Column(db.String(20))
app_secret = db.Column(db.String(40))
page_id = db.Column(db.String(20))
token = db.Column(db.String(300))
updating code
query.launched = query.launched + 1
query.modified = until
db.session.commit()
What I expected is the modified column to be updated to the 'until' time, which already happens, but the created column to stay untouched.

I got the error. It was in my sql syntax. I still didn't figure out why did it happen, but it was resolved by changing this:
-created TIMESTAMP NOT NULL
+created TIMESTAMP DEFAULT CURRENT_TIMESTAMP

Related

sqlalchemy not honoring uniqueconstraint

I have the model below. I setup the unique constraint on the customer and the IP's in the table. I dont want to be able to add a new customer with the same name and/or ip's. If I attempt to do that, I expect SQLAlchemy to error out. Right now it is not, it is just allowing me to add multiple entries with the same IP and Customer name. My DB backend is SQLite
class SubInterfaces(db.Model):
__tablename__ = 'subinterfaces'
__table_args__ = (
db.UniqueConstraint('hub_wan1_public_ip', 'ip_transit', 'customer', 'neighbor_ip', name='unique_sub_interfaces'),
)
id = db.Column(db.Integer, primary_key=True)
carrier_vlan = db.Column(db.Integer)
cust_vlan_ipsec = db.Column(db.Integer)
cust_vlan_wan = db.Column(db.Integer)
hub_wan1_public_ip = db.Column(db.String)
ip_transit = db.Column(db.String)
neighbor_ip = db.Column(db.String)
user_account = db.Column(db.String)
customer = db.Column(db.String)
created_at = db.Column(db.DateTime, default=datetime.datetime.utcnow())
status = db.Column(db.Integer)
location = db.Column(db.String)
is_primary = db.Column(db.Boolean)
#### Add customer piece ####
dict_to_add = {
"customer": request.json['customer'],
"hub_wan1_public_ip": request.json['HUB1_WAN1_PUBLIC_IP'],
"ip_transit": request.json['PRIMARY_TRANSIT'],
"location": request.json['LOCATION']
}
add_user = SubInterfaces(**dict_to_add)
db.session.add(add_user)
db.session.commit()
Not sure why the constrains are not working, but as a workaround I am doing a count on the DB and if more than 1, error out.
#first check if a customer exists, if it does error out
active_customer = response['customer']
count_customer = db.session.query(SubInterfaces).filter_by(customer=active_customer).count()
if (count_customer >= 1):
raise BadRequest(f"Customer {active_customer} already exists", 400, {'ext': 1})

Flask SQL-Alchemy creating hierarchy table many foreign keys to same table

Have got a roles table:
from datetime import datetime
# from models.user_role import UserRoleModel
from models.role_hierarchy import RoleHierarchyModel
class RoleModel(db.Model):
__tablename__ = "security_role"
role_id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(255))
description = db.Column(db.Text)
created_timestamp = db.Column(db.DateTime, default=datetime.now())
updated_timestamp = db.Column(db.DateTime, default=datetime.now())
role = db.relationship('UserRoleModel', back_populates='role')
parent_role = db.relationship('RoleHierarchyModel',
back_populates='parent_role',
primaryjoin="RoleHierarchyModel.parent_role_id == RoleModel.role_id")
child_role = db.relationship('RoleHierarchyModel',
back_populates='child_role',
primaryjoin="RoleHierarchyModel.child_role_id == RoleModel.role_id")
and a Roles Hierarchy table
class RoleHierarchyModel(db.Model):
__tablename__ = "security_role_hierarchy"
role_h_id = db.Column(db.Integer, primary_key=True)
parent_role_id = db.Column(db.Integer, db.ForeignKey('security_role.role_id'))
child_role_id = db.Column(db.Integer, db.ForeignKey('security_role.role_id'))
created_at = db.Column(db.DateTime, default=datetime.now())
updated_at = db.Column(db.DateTime, default=datetime.now())
parent_role = db.relationship('RoleModel', back_populates='parent_role'
, foreign_keys='RoleHierarchyModel.parent_role_id')
child_role = db.relationship('RoleModel', back_populates='child_role'
, foreign_keys='RoleHierarchyModel.child_role_id')
Not sure if I have set this up correctly nor how to get it into the database correctly once I have done this. What I want to create is a role called super user that has other roles associated with it
I try:
base_role = RoleModel(title='Read')
other_base_role = RoleModel(title='Write')
base_role.save_to_db()
other_base_role.save_to_db()
super_user = RoleModel(title='Super)
hierarchy1 = RoleHierarchyModel()
hierarchy2 = RoleHierarchyModel()
hierarchy1.child_role(base_role)
hierarchy2.child_role(other_base_role)
super_user.parent_role.append(hierarchy1)
super_user.parent_role.append(hierarchy2)
but I keep getting the following error:
sqlalchemy.exc.InvalidRequestError: This Session's transaction has been rolled back due to a previous exception during flush. To begin a new transaction with this Session, first issue Session.rollback(). Original
exception was: Attempting to flush an item of type <class 'models.role.RoleModel'> as a member of collection "RoleModel.child_role". Expected an object of type <class 'models.role_hierarchy.RoleHierarchyModel'> or
a polymorphic subclass of this type. (Background on this error at: http://sqlalche.me/e/7s2a)
what am I doing wrong?
In general, there should be no need to have two classes, which is kind of where your problem started.
To make this a proper tree, have a look at this answer. You only need to reference the parent_id through a column, and let SQLAlchemy get you the children through a relationship object.
Suppose you store the rights a role has in a list somehow. Then, if you want to see if someone has the right to do something, or if you want to return a list of all rights a person has (directly and indirectly):
class Role:
_rights = ['canChangePassword', 'canEditPosts', 'canDeletePosts'...]
def has_right(self, rightName):
if rightName in self._rights
return True
for child in self.children:
if child.has_right(rightName):
return True
return False
def get_rights(self):
all_rights = self._rights
for child in self.children:
all_rights += self.children.get_rights()
# there can be duplicates, cast to a set to remove them
return list(set(all_rights))
I can also see how rights are granted based on the description or title, but in that case, just iterate the same way and construct a list of role titles from itself and all children. Use recursion to your advantage here.
Found the answer hidden in the SQLAlchemy documentation: https://docs.sqlalchemy.org/en/13/orm/join_conditions.html#handling-multiple-join-paths
https://docs.sqlalchemy.org/en/13/orm/join_conditions.html#handling-multiple-join-paths
Roles table now looks like:
class RoleModel(db.Model):
__tablename__ = "security_role"
role_id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(255))
description = db.Column(db.Text)
created_timestamp = db.Column(db.DateTime, default=datetime.now())
updated_timestamp = db.Column(db.DateTime, default=datetime.now())
role = db.relationship('UserRoleModel', back_populates='role')
child_roles = db.relationship('RoleModel', secondary='security_role_hierarchy'
, primaryjoin='RoleModel.role_id==RoleHierarchyModel.parent_role_id'
, secondaryjoin="RoleModel.role_id==RoleHierarchyModel.child_role_id",
backref="parent_roles")
and the hierarchy table is now:
class RoleHierarchyModel(db.Model):
__tablename__ = "security_role_hierarchy"
role_h_id = db.Column(db.Integer, primary_key=True)
parent_role_id = db.Column(db.Integer, db.ForeignKey('security_role.role_id'))
child_role_id = db.Column(db.Integer, db.ForeignKey('security_role.role_id'))
created_at = db.Column(db.DateTime, default=datetime.now())
updated_at = db.Column(db.DateTime, default=datetime.now())

SQLAlchemy set default value of column depending on ID

I already know that for general cases: this question is sufficient: SQLAlchemy set default value of one column to that of another column
But when my column depends on the Id column it doesn't work because id is auto generated.
For eg: Using the same example as the other question
class Substance(Base):
__tablename__ = "substances"
id = Column(Integer, primary_key=True)
code = Column(String, unique=True)
name = Column(String, unique=True)
subtance_hash = Column(String, unique=True, default=hashgen)
def hashgen(context):
uid = context.get_current_parameters()['id'] + 100
name = context.get_current_parameters()['name']
code = context.get_current_parameters()['code']
return some_hash_function(uid, name, code)
def some_hash_function(uid, name, code):
# this is a stupid example for completeness
return str(uid) + str(name) + str(code)
The problem here is that uid is None because it will be auto generated while committing the query I think?
So it throws an error:
sqlalchemy.exc.StatementError: (builtins.KeyError) 'id'
Is it even possible to do this? If so how?
You can flush your session to set the id before the commit. You can then calculate your hash. You can also use the event system is SQLAlchemy to listen for after_flush events and calculate the hash there. For example:
class Substance(Base):
__tablename__ = "substances"
id = Column(Integer, primary_key=True)
code = Column(String, unique=True)
name = Column(String, unique=True)
subtance_hash = Column(String, unique=True)
#event.listens_for(sessionOrSessionFactory, 'after_flush')
def hashgen(session, flush_context):
for obj in session:
if isinstance(obj, Substance):
obj.substance_hash = some_hash_function(obj)
def some_hash_function(obj):
# this is a stupid example for completeness
return str(obj.id + 100) + str(obj.name) + str(obj.code)
Another method is to make the hash hybrid_property https://docs.sqlalchemy.org/en/13/orm/extensions/hybrid.html
I haven't played around with hybrid properties much so I will leave it for someone else to contribute an answer for that.

Instance <xxx> has been deleted after commit(), but when and why?

Here's a really simple piece of code. After adding the "poll" instance to the DB and committing, I cannot later read it. SQLAlchemy fails with the error:
Instance '<PollStat at 0x7f9372ea72b0>' has been deleted, or its row is otherwise not present.
Weirdly, this does not happen if I replace the ts_start/ts_run primary key by an integer autoincrement one. Is it possible that DateTime columns are not suitable as primary key?
db = Session()
poll = models.PollStat(
ts_start=datetime.datetime.now(),
ts_run=ts_run,
polled_tools=0)
db.add(poll)
db.commit() # I want to commit here in case something fails later
print(poll.polled_tools) # this fails
PollStat in module models.py:
class PollStat(Base):
__tablename__ = 'poll_stat'
ts_run = Column(Integer, primary_key=True)
ts_start = Column(DateTime, primary_key=True)
elapsed_ms = Column(Integer, default=None)
polled_tools = Column(Integer, default=0)
But if I do this:
class PollStat(Base):
__tablename__ = 'poll_stat'
id = Column(Integer, primary_key=True)
ts_run = Column(Integer)
ts_start = Column(DateTime)
elapsed_ms = Column(Integer, default=None)
polled_tools = Column(Integer, default=0)
it works. Why?
For anyone that still has this problem, this error happened to me because I submitted a JSON object with an id of 0; I use the same form for adding and editing said object, so when editing this would normally have an ID number, but when creating the item, the id property needs to be deleted before inserting the item. Some databases don't accept an ID of 0. In the end the row is created but the ID of 0 no longer matches the current ID, hence why the error pops up.

SQLAlchemy delete association objects

I'm trying to batch delete objects from an association table by filtering on a column in one of the relationships. I use the following call in SQLAlchemy to make the delete
db.session.query(UserPaper).join(Paper, (UserPaper.paper_id ==
Paper.id)).filter(UserPaper.user_id == user.id).filter(Paper.journal_id
== journal.id).delete()
and it results in the following error
OperationalError: (OperationalError) (1054, "Unknown column 'papers.journal_id'
in 'where clause'") 'DELETE FROM userpapers WHERE userpapers.user_id = %s AND
papers.journal_id = %s' (1L, 1L)
Without the delete at the end, the SQLAlchemy query is
SELECT userpapers.user_id AS userpapers_user_id, userpapers.paper_id AS
userpapers_paper_id, userpapers.created AS userpapers_created,
userpapers.read_at AS userpapers_read_at, userpapers.score AS userpapers_score
FROM userpapers JOIN papers ON userpapers.paper_id = papers.id
WHERE userpapers.user_id = :user_id_1 AND papers.journal_id = :journal_id_1
which is correct. From the error I can see that when I append delete() to the query the join part of SQL statement gets lost and the database doesn't know how to find the papers.journal_id column obviously. What I don't understand is why does that happen?
This is the setup of my ORM objects
class User(db.Model):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String(255), unique=True)
papers = db.relationship("UserPaper", backref=db.backref('users'), lazy='dynamic')
class Paper(db.Model):
__tablename__ = 'papers'
id = db.Column(db.Integer, primary_key = True)
title = db.Column(db.String(1024))
journal_id = db.Column(db.Integer, db.ForeignKey('journals.id'))
class UserPaper(db.Model):
__tablename__ = 'userpapers'
user_id = db.Column(db.Integer, db.ForeignKey('users.id'), primary_key=True)
paper_id = db.Column(db.Integer, db.ForeignKey('papers.id'), primary_key=True)
paper = db.relationship("Paper", backref=db.backref('user_paper'))
read_at = db.Column(db.DateTime)
score = db.Column(db.Integer)
class Journal(db.Model):
__tablename__ = 'journals'
id = db.Column(db.Integer, primary_key = True)
title = db.Column(db.String(100), index = True, unique = True)
papers = db.relationship('Paper', backref = 'journal', lazy = 'dynamic')
I had the same problem with SQLALchemy 0.9 using MySQL 5.6. It looks like a bug/limitation. However, one better way to get arround (in comparison to creating the query, looping through the results and deleting them one by one) is to perform this task in two subsequent queries:
paperQuery = db.session.query(Paper.id)\
filter(Paper.journal_id == journal.id)
baseQuery = db.session.query(UserPaper)\
.filter(UserPaper.paper_id.in_(paperQuery.subquery()))
.filter(UserPaper.user_id == user.id).delete(synchronize_session='fetch')
It worked well for me, it should solve you issue too.

Categories