How do I create a non-persistent Elixir / SQLAlchemy object?
Due to stale data that is not available in the database, but some external files, I want to create a SQLAlchemy object that contains data read from external files, but not written to the database if I execute session.flush()
My code looks like this:
try:
return session.query(Phone).populate_existing().filter(Phone.mac == ident).one()
except:
return self.createMockPhoneFromLicenseFile(ident)
def createMockPhoneFromLicenseFile(self, ident):
# Some code to read necessary data from file deleted....
phone = Phone()
phone.mac = foo
phone.data = bar
phone.state = "Read from legacy file"
phone.purchaseOrderPosition = self.getLegacyOrder(ident)
# SQLAlchemy magic doesn't seem to work here, probably because we don't insert the created
# phone object into the database. So we set the id fields manually.
phone.order_id = phone.purchaseOrderPosition.order_id
phone.order_position_id = phone.purchaseOrderPosition.order_position_id
return phone
Everything works fine, except that on session.flush()
, executed later in the application, SQLAlchemy tries to write the created Phone object to the database (which fortunately failed as phone.state is longer than the data type allows) which destroys the function that issues flush.
Is there a way to prevent SQLAlchemy from trying to write an object like this?
Update
So far I haven't found anything on
using_mapper_options(save_on_init=False)
in the Elixir documentation (maybe you can provide a link?), I thought it was worth trying (I would prefer a way to prevent a single instance from being written instead of the whole entity).
At first, the statement seemed to have no effect and I suspected that my versions of SQLAlchemy / Elixir were too old, but then I found out that the connection to the PurchaseOrderPosition object (which I did not change) was made with
phone.purchaseOrderPosition = self.getLegacyOrder(ident)
causes the phone object to be recorded again. If I delete the statement, everything is fine.
a source to share
You need to do
import elixir
elixir.options_defaults['mapper_options'] = { 'save_on_init': False }
to prevent the instances Entity
you create from being automatically added to the session. Ideally, this should be done as early as possible in your code. You can also do this on an entity basis, via using_mapper_options(save_on_init=False)
- see the Elixir documentation for more details.
Update:
See this post on the Elixir mailing list indicating this is a solution.
Also, as Ants Aasma points out, you can use cascade parameters in relation to Elixir to tune cascade parameters in SQLAlchemy. See this page for details .
a source to share
Well sqlalchemy is not the default.
Consider the following stand-alone example code.
from sqlalchemy import Column, Integer, Unicode, create_engine
from sqlalchemy.orm import create_session
from sqlalchemy.ext.declarative import declarative_base
e = create_engine('sqlite://')
Base = declarative_base(bind=e)
class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
name = Column(Unicode(50))
# create the empty table and a session
Base.metadata.create_all()
s = create_session(bind=e, autoflush=False, autocommit=False)
# assert the table is empty
assert s.query(User).all() == []
# create a new User instance but don't save it to database:
u = User()
u.name = 'siebert'
# I could run s.add(u) here but I won't
s.flush()
s.commit()
# assert the table is still empty
assert s.query(User).all() == []
So I'm not sure if the implication is adding your instances to the session. You usually have to manually invoke s.add(u)
to go to the session. I am not familiar with the elixir, so maybe this is some kind of elixir trick ... Maybe you can remove it from the session using session.expunge()
.
a source to share
Old post, but I faced a similar problem, in my case in sqlalchemy it was caused by a cascade on the backrefs:
http://docs.sqlalchemy.org/en/rel_0_7/orm/session.html#backref-cascade
Disable it on your backrefs so that you explicitly add things to the session
a source to share