Add and update with sqlalchemy in single commit - python

I am new in sqlalchemy. I want to do add and update in single transaction for same model.code snippet is below. Application throwing error like 'Session' object has no attribute 'update'
current_date = datetime.datetime.now()
try:
session = Session()
user = UserProvision()
user.username = admin["username"]
user.password= admin["password"]
user.client_id= admin["client_id"]
user.fname= admin["fname"]
user.lname= admin['lname']
user.phone= admin['phone']
session.add(user)
session.flush()
user_id = user.user_id
user.name = admin["fname"]+" "+admin["lname"]
user.setCreated_by=user_id
user.setModified_by=user_id
user.setCreated_name=admin["fname"]+" "+admin["lname"]
user.setModified_name=admin["fname"]+" "+admin["lname"]
user.setLast_reset_date=current_date
user.setLast_reset_by = current_date
session.update(user)
session.flush()
session.commit()
except Exception as ex:
print ex.__str__()
finally:
session.close()

When you've added the model object to the session its state is already tracked for changes. There's no need to explicitly mark it as updated, and as you've noted there is no such method Session.update(). Simply remove that line and your code should work as expected.
The tracking is achieved through instrumentation of model class attributes:
The SQLAlchemy mapping process, among other things, adds database-enabled descriptors to a mapped class which each represent a particular database column or relationship to a related class.
In other words when your model class is constructed the Column attributes will be replaced with InstrumentedAttribute descriptor instances that among other things keep track of changes to the value.
Note that there's no need to manually flush just before Session.commit():
Flush pending changes and commit the current transaction.

Related

Python sqlalchemy - independent transactions

I've got a question about transactions in flask-sqlalchemy.
def funca():
instance = MyModel.query.get(5)
try:
instance.some_field = 'test'
instance.do_something()
instance.do_something_other() # Let's pretend that this causes exception
db.session.commit()
except Exception:
db.session.rollback()
# My model method
def do_something(self):
external_id = self.make_request_to_external_service()
self.external_id = external_id
# My model method
def do_something_other(self):
external_details = self.make_another_external_request()
self.external_details = external_details
How can I achieve now, that on Exception this will rollback only some_field and external_details changes and external_id is still saved in db?
Can I use nested transaction or can I start manually independent transactions for these 3 operations?
Problem here is that do_something will get external_id and it must be saved in db, because I can't do it second time, also if I won't rollback this transaction some_field will cause desynchronization between my db and external service.
For this example's purposes let's pretend that I can't do do_something before instance.some_field = 'test', because I could run this and commit this independently, but I can't do that in my real life app.

SQLAlchemy ORM Event hook for attribute persisted

I am working on finding a way in SQLAlchemy events to call an external API upon an attribute gets updated and persisted into the database. Here is my context:
An User model with an attribute named birthday. When an instance of User model gets updated and saved, I want to call to an external API to update this user's birthday accordingly.
I've tried Attribute Events, however, it generates too many hits and there is no way to guarantee that the set/remove attribute event would get persisted eventually (auto commit is set to False and transaction gets rolled back when errors occurred.)
Session Events would not work either because it requires a Session/SessionFactory as a parameter and there are just so many places in the code based that sessions have been used.
I have been looking at all the possible SQLAlchemy ORM event hooks in the official documentation but I couldn't find any one of them satisfy my requirement.
I wonder if anyone else has any insight into how to implement this kind of combination event trigger in SQLAlchemy. Thanks.
You can do this by combining multiple events. The specific events you need to use depend on your particular application, but the basic idea is this:
[InstanceEvents.load] when an instance is loaded, note down the fact that it was loaded and not added to the session later (we only want to save the initial state if the instance was loaded)
[AttributeEvents.set/append/remove] when an attribute changes, note down the fact that it was changed, and, if necessary, what it was changed from (these first two steps are optional if you don't need the initial state)
[SessionEvents.before_flush] when a flush happens, note down which instances are actually being saved
[SessionEvents.before_commit] before a commit completes, note down the current state of the instance (because you may not have access to it anymore after the commit)
[SessionEvents.after_commit] after a commit completes, fire off the custom event handler and clear the instances that you saved
An interesting challenge is the ordering of the events. If you do a session.commit() without doing a session.flush(), you'll notice that the before_commit event fires before the before_flush event, which is different from the scenario where you do a session.flush() before session.commit(). The solution is to call session.flush() in your before_commit call to force the ordering. This is probably not 100% kosher, but it works for me in production.
Here's a (simple) diagram of the ordering of events:
begin
load
(save initial state)
set attribute
...
flush
set attribute
...
flush
...
(save modified state)
commit
(fire off "object saved and changed" event)
Complete Example
from itertools import chain
from weakref import WeakKeyDictionary, WeakSet
from sqlalchemy import Column, String, Integer, create_engine
from sqlalchemy import event
from sqlalchemy.orm import sessionmaker, object_session
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
engine = create_engine("sqlite://")
Session = sessionmaker(bind=engine)
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True)
birthday = Column(String)
#event.listens_for(User.birthday, "set", active_history=True)
def _record_initial_state(target, value, old, initiator):
session = object_session(target)
if session is None:
return
if target not in session.info.get("loaded_instances", set()):
return
initial_state = session.info.setdefault("initial_state", WeakKeyDictionary())
# this is where you save the entire object's state, not necessarily just the birthday attribute
initial_state.setdefault(target, old)
#event.listens_for(User, "load")
def _record_loaded_instances_on_load(target, context):
session = object_session(target)
loaded_instances = session.info.setdefault("loaded_instances", WeakSet())
loaded_instances.add(target)
#event.listens_for(Session, "before_flush")
def track_instances_before_flush(session, context, instances):
modified_instances = session.info.setdefault("modified_instances", WeakSet())
for obj in chain(session.new, session.dirty):
if session.is_modified(obj) and isinstance(obj, User):
modified_instances.add(obj)
#event.listens_for(Session, "before_commit")
def set_pending_changes_before_commit(session):
session.flush() # IMPORTANT
initial_state = session.info.get("initial_state", {})
modified_instances = session.info.get("modified_instances", set())
del session.info["modified_instances"]
pending_changes = session.info["pending_changes"] = []
for obj in modified_instances:
initial = initial_state.get(obj)
current = obj.birthday
pending_changes.append({
"initial": initial,
"current": current,
})
initial_state[obj] = current
#event.listens_for(Session, "after_commit")
def after_commit(session):
pending_changes = session.info.get("pending_changes", {})
del session.info["pending_changes"]
for changes in pending_changes:
print(changes) # this is where you would fire your custom event
loaded_instances = session.info["loaded_instances"] = WeakSet()
for v in session.identity_map.values():
if isinstance(v, User):
loaded_instances.add(v)
def main():
engine = create_engine("sqlite://", echo=False)
Base.metadata.create_all(bind=engine)
session = Session(bind=engine)
user = User(birthday="foo")
session.add(user)
user.birthday = "bar"
session.flush()
user.birthday = "baz"
session.commit() # prints: {"initial": None, "current": "baz"}
user.birthday = "foobar"
session.commit() # prints: {"initial": "baz", "current": "foobar"}
session.close()
if __name__ == "__main__":
main()
As you can see, it's a little complicated and not very ergonomic. It would be nicer if it were integrated into the ORM, but I also understand there may be reasons for not doing so.

sqlalchemy object saved to complete action

Im new to sqlalchemy, Im query object and expunge of session for prevent the session save change, after add the object with change at session but not flush or commit.
when the ends controller, the session save my object, i dont want that, I want the object is lost if I did not flush or commit
my code:
object = model.DBSession.query(model.Object).filter_by( field = value ).first()
model.DBSession.expunge(object)
object.field = gfhggghfg
object.field2 = hsjsjsjsjs
model.DBSession.add(object)
#finish controller turbogearsr the session save the change. I have autocommit and autoflush = False
You don't even need to expunge the object, as TurboGears provides a transaction manager you can just doom the transaction so that TurboGears will throw it away instead of committing the changes:
import transaction
#expose('mytemplate')
def my_controller(self):
object = model.DBSession.query(model.Object).filter_by( field = value ).first()
object.field = 'Hello'
transaction.doom() # This will prevent changes to be committed.
return dict(value=object.field)
To disable it for the whole project edit config/app_cfg.py and add:
base_config.use_transaction_manager = False

Sqlalchemy session.refresh does not refresh object

I have the following mapping (straight from SA examples):
class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
name = Column(String)
fullname = Column(String)
password = Column(String)
I'm working with a MySql DB and the table has an innoDB engine.
I have a single record in my table:
1|'user1'|'user1 test'|'password'
I've opened a session with the following code:
from sqlalchemy.orm.session import sessionmaker
from sqlalchemy.engine import create_engine
from sqlalchemy.orm.scoping import scoped_session
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
db_engine = create_engine('mysql://...#localhost/test_db?charset=utf8',echo=False,pool_recycle=1800)
session_factory = sessionmaker(bind=db_engine,autocommit=False,autoflush=False)
session_maker = scoped_session(session_factory)
session = session_maker()
user_1 = session.query(User).filter(User.id==1).one()
user_1.name # This prints: u'user1'
Now, when I change the record's name in the DB to 'user1_change' and commit it and then refresh the object like this:
session.refresh(user_1)
user_1.name # This still prints: u'user1' and not u'user1_change'
It still prints: u'user1' and not u'user1_change'.
What am I missing (or setting up wrong) here?
Thanks!
From the docs:
Note that a highly isolated transaction will return the same values as were previously read in that same transaction, regardless of changes in database state outside of that transaction
SQLAlchemy uses a transactional unit of work model, wherein each transaction is assumed to be internally consistent. A session is an interface on top of a transaction. Since a transaction is assumed to be internally consistent, SQLAlchemy will only (well, not quite, but for ease of explanation...) retrieve a given piece of data from the database and update the state of the associated objects once per transaction. Since you already queried for the object in the same session transaction, SQLAlchemy will not update the data in that object from the database again within that transaction scope. If you want to poll the database, you'll need to do it with a fresh transaction each time.
session.refresh() didn't work for me either. Even though I saw a low-level SELECT the object was not updated after the refresh.
This answer https://stackoverflow.com/a/11121788/562267 hints to doing an actual commit/rollback to reset the session, and that worked for me:
user_1 = session.query(User).filter(User.id==1).one()
user_1.name # This prints: u'user1'
# update the database from another client here
session.commit()
user_1 = session.query(User).filter(User.id==1).one()
user_1.name # Should be updated now.
Did you try with "expire" as described in the official doc:
http://docs.sqlalchemy.org/en/rel_0_8/orm/session.html#refreshing-expiring
# expire objects obj1, obj2, attributes will be reloaded
# on the next access:
session.expire(user_1)
session.refresh(user_1)
Using expire on a object results in a reload that will occur upon next access.
Merge the session.
u = session.query(User).get(id)
u.name = 'user1_changed'
u = session.merge(u)
This will update the database and return the newer object.

SqlAlchemy, generated object Id and Zope transaction

I am using SQLAlchemy together with Zope transactions. My object is something like:
class MyItem(DeclarativeBase):
# ....
id = Column(Integer, primary_key=True)
# ....
then when I create a new object, and commit transaction, I would like to obtain genarated id, for example:
mi = MyItem()
DBSession.add(mi)
transaction.commit()
print mi.id # currently object is expired (not persistent with this session)
Is there some easy way to achieve this behaviour?
You can just use the default session pattern; add the object, and then flush to save the object to the database. Do not commit the transaction, let Zope handle that:
mi = MyItem()
DBSession.add(mi)
DBSession.flush()
print mi.id
Your object will be added to the database in the current transaction, and thus the primary key will be known.

Categories

Resources