How to access autoincrement value after committing an object - sqlalchemy

I'm using flask-sqlalchemy. My class is:
class Game(db.Model):
userid = db.Column(db.Integer, primary_key=True)
gameid = db.Column(db.Integer, primary_key=True)
gameid is set to auto-increment in the database.
When I run this function:
#app.route("/requestgame")
def requestgame():
game = Game()
game.userid = session["userid"]
db.session.add(game)
db.session.commit()
session["gameid"] = game.gameid
return "gameid {}".format(game.gameid)
I get "ObjectDeletedError: Instance '' has been deleted, or its row is otherwise not present."
How can I get the gameid to return?
It looks like someone asked the same question about regular SQLAlchemy here:
SQLAlchemy Obtain Primary Key With Autoincrement Before Commit
but I have tried the recommended solution (calling session.flush()) and it doesn't seem to make any difference.

Related

SQLAlchemy: Counting multiple relationships - best way?

Imagine the following (example) datamodel:
class Organization(db.Model):
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
friendly_name = db.Column(db.Text, nullable=False)
users = db.relationship('Users', back_populates='organizations')
groups = db.relationship('Groups', back_populates='organizations')
class User(db.Model):
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
organization_id = db.Column(db.Integer, db.ForeignKey('organizations.id'))
organizations = relationship("Organization", back_populates="users")
class Group(db.Model):
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
organization_id = db.Column(db.Integer, db.ForeignKey('organizations.id'))
organizations = relationship("Organization", back_populates="groups")
(so basically an Organization has User and Group relationships)
What we want is to retrieve the counts for users and groups. Result should be similar to the following:
id
friendly_name
users_count
groups_count
1
o1
33
3
2
o2
12
2
3
o3
1
0
This can be achieved with a query similar to
query = db.session.query(
Organization.friendly_name,
func.count(User.id.distinct()).label('users_count'),
func.count(Group.id.distinct()).label('groups_count'),
) \
.outerjoin(User, Organization.users) \
.outerjoin(Group, Organization.groups) \
.group_by(Organization.id)
which seems quite overkill. The first intuitive approach would be something like
query = db.session.query(
Organization.friendly_name,
func.count(distinct(Organization.users)).label('users_count'),
func.count(distinct(Organization.groups).label('groups_count'),
)# with or without outerjoins
which is not working (Note: With one relationship it would work).
a) Whats the difference between User.id.distinct() and distinct(Organization.users) in this case?
b) What would be the best/most performant/recommended way in SQLAlchemy to get a count for each relationship an Object has?
Bonus): If instead of Organization.friendly_name the whole Model would be selected (...query(Organization, func....)) SQLAlchemy returns a tuple with the format t(Organization, users_count, groups_count) as result. Is there a way to just return the Organization with the two counts as additional fields? (as SQL would)
b:
You can try a window function to count users and groups with good performance:
query = db.session.query(
Organization.friendly_name,
func.count().over(partition_by=(User.id, Organization.id)).label('users_count')
func.count().over(partition_by=(Group.id, Organization.id)).label('groups_count')
)
.outerjoin(User, Organization.users)
.outerjoin(Group, Organization.groups)
bonus:
To return count as a field of Organization, you can use hybrid_property, but you would not be happy with the performance.

Optimizing hybrid_properties in SQLAlchemy

I have a piece of working code but it is very inefficient, instead of a single query with a join. I get one initial query, followed by one query per row in the response.
I have to following scenario:
class Job(Base, SerializeMixin, JobInterface):
__tablename__ = 'job_subjobs'
id = Column(Integer, primary_key=True, autoincrement=True)
group_id = Column(Integer, ForeignKey("job_groups.id"), nullable=False)
class Crash(Base, SerializeMixin):
__tablename__ = 'crashes'
id = Column(Integer, primary_key=True, autoincrement=True)
job_id = Column(Integer, ForeignKey("job_subjobs.id", ondelete='CASCADE'), nullable=False)
job = relationship('Job', backref='Crash')
#hybrid_property
def job_identifier(self):
return "{}:{}".format(self.job.group_id, self.job.id)
So given the above and I perform a query for all Crashes, It will perform one SELECT for all crashes. When I iterate and ask for job_identifier it will then do one separate SELECT for each crash.
self.session.query(Crash).all()
Is there someway i can create a #hybrid_property referencing a different table and have it JOIN from the beginning and preload the expression?
I've experimented with #xxx.expression without success. If all else fails I can add another foreign key in Crash table, but I would like to avoid changing current data structure if possible.
ended up using:
jobs = relationship('Job', backref='Crash', lazy='joined')

On update entity with version_id column (Optimistic Offline Lock)

I am facing the following problem while I am trying to update some values on an entity with version_id column.
Here is the snippet of code which I am using:
BASE = declarative_base()
class Job(BASE):
__tablename__ = 'jobs'
id = Column(String(36), primary_key=True, default=uuid.uuid4)
worker_id = Column(String(36), nullable=True)
status = Column(String(255), nullable=True)
timeout = Column(DateTime, nullable=False)
version_id = Column(String(36))
__mapper_args__ = {
'version_id_col': version_id,
'version_id_generator': lambda version: uuid.uuid4()
}
Approach #1 for updating a job entity:
query = session.query(Job)
.filter_by(id=job_id)
.update({'worker_id': worker_id, 'timeout': new_timeout)
SQLAlchemy generated the following query (mysql.log):
UPDATE jobs
SET worker_id='4a2350a1-f3ce-48f9-b29f-cc22e2e625fd',
timeout='2014-03-22 09:47:04.337521'
WHERE jobs.id ='84844301-446a-4912-85d7-92d9d462de5b'
Approach #2 for updating a job entity:
job = session.query(Job).filter_by(id=job_id).first();
job.worker_id = worker_id
job.timeout = new_timeout
session.add(job);
session.flush()
SQLAlchemy generated the following query (mysql.log):
UPDATE jobs
SET worker_id='1634939b-d3c6-462d-8254-d7f09640ba71',
timeout='2014-03-22 11:10:51.879507',
version_id='aa9d3f74-3e4a-4bae-af38-fe0530dbf9b7'
WHERE jobs.id = '3e6ab855-179e-4f4f-bf3a-847cf19b30fa'
AND jobs.version_id = '2c1288ba-ad27-444c-9f56-764df4898c52'
We can notice with Approach#1 the version_id column was never considered but in Approach#2 it was taken into consideration.
I thought even with Approach#1 sqlalchemy will be using the class_mapper.
Couldn't understand why is this difference of behavior.
Can someone please help me to understand why with Approach#1 the update call did not take into consideration the version_id column? OR am I missing anything?
As I could find any answer, went ahead and posted my question in 'sqlalchemy-dev' group.
Thanks to Michael Bayer for sparing his time to answer my question.
Thought would share the link to the answer here for benefiting others.
https://groups.google.com/forum/#!topic/sqlalchemy-devel/JUqlWKtRa4g

SQLALchemy filter_by() on a foreign key producting weird sql

I'm attempting to filter on a foreign key and none of the SO answers I've searched for have lent any results.
Where are my query statements.
testing = Comments\
.filter(Comments.post_id==post_id)
print(testing)
testing = Comments\
.query.join(Post, aliased=True)\
.filter(Comments.post_id==post_id)
print(testing)
Here's what my class definitions looks like
class Comments(db.Model):
comment_id = db.Column(db.Integer, primary_key=True)
post_id = db.Column(
db.Integer,
db.ForeignKey("Post.post_id"),
nullable=False)
class post(db.Model):
post_id = db.Column(db.Integer, primary_key=True)
Comments = db.relationship(
'Comments',
backref='Post',
lazy='dynamic')
The actual SQL queries which are being produced from the first and second case. They both have this weird :post_id_1 thing. In both cases I'm getting a null set back.
FROM "Comments"
WHERE "Comments".post_id = :post_id_1
FROM "Comments" JOIN "post" AS "post_1" ON "post_1".post_id = "Comments".post_id
WHERE "Comments".post_id = :post_id_1
If I do a simple
Select * from Comments where post_id = 1
in the mysql CLI I get a result set.
Your model definition is weird, the following part is not correctly indented:
Comments = db.relationship(
'Comments',
backref='Post',
lazy='dynamic')
Or maybe it's just a copy/paste issue (just to be sure).
What you call "weird :esc_id_1 thing" is in fact an named placeholder. They will be replaced by the real value when the SQL statement will be executed (this is mainly to avoid SQL injection, the driver is responsible to escape values).

sqlalchemy child field order_by on backref

Answer no longer needed as I changed focus in code. (see my comment in answer) Post answers for future reference...
How do I retrieve results from a one to many backref ordered by a field in the child? I need all somethings for the gid ordered by index. But at this time they are retrieved randomly even though they are ordered in the ms sql server.
I have in TurboGears 2 datamodels.py:
`class Parcel(DeclarativeBase):
__tablename__ = 'GENERAL'
__table_args__ = ({'autoload': True})
gid = Column(Integer, primary_key=True)`
somethings = relationship('Something', backref='Parcel')
'class Something(DeclarativeBase):
__tablename__ = 'SKETCH'
__table_args__ = ({'autoload': True})
gid = Column(Integer, ForeignKey('GENERAL.gid'), primary_key=True)
index = Column(Integer, primary_key=True)
In Turbogears root.py:
query = DBSession.query(Parcel)
query = query.options(joinedload('somethings')
query=session.filter(Parcel.gid==gid)
Returns all somethings for gid unordered.
DBSession.query(Something).filter_by(gid=gid).order_by(Something.index).all()
edit: relationship() accepts a keyword argument order_by to order instances when you use the relationship. If you want to specify the ordering for the reverse direction, you can use the backref() function instead of the backref keyword and use the same order_by keyword argument as with relationship().