Python SqlAlchemy Pass Query to View Serialization Issue - python

What is the best way to pass a sqlalchemy query's result to the view?
I have a declaratively declared table such as:
class Greeting(Base):
__tablename__ = 'greetings'
id = Column(Integer, primary_key=True)
author = Column(String)
content = Column(Text)
date = Column(DateTime)
def __init__(self, author, content, date = datetime.datetime.now()):
self.author = author
self.content = content
self.date = date
Then, I run a query with q = session.query(Greeting).order_by(Greeting.date), but when I try to simply return q, it throws some JSON serialization error. From what I understand, this is due to the date field. Is there any simple way to fix this?

Take a look at http://www.sqlalchemy.org/docs/core/serializer.html.
Serializer/Deserializer objects for usage with SQLAlchemy query structures, allowing “contextual” deserialization.
Any SQLAlchemy query structure, either based on sqlalchemy.sql.* or sqlalchemy.orm.* can be used. The mappers, Tables, Columns, Session etc. which are referenced by the structure are not persisted in serialized form, but are instead re-associated with the query structure when it is deserialized.

Related

SQLAlchemy model #property with query for another model

I'm currently working on migrating a Flask application from the Flask-SQLAlchemy package to the SQLAlchemy 1.4.2 package. With the Flask-SQLAlchemy model, I can do something like this:
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
class Author(db.Model):
__tablename__ = 'authors'
id = db.Column(db.String, primary_key=True)
name = db.Column(db.String)
surname = db.Column(db.String)
class Book(db.Model):
__tablename__ = 'books'
id = db.Column(db.String, primary_key=True)
name = db.Column(db.String)
year = db.Column(db.Integer)
author_id = db.Column(db.String)
#property
def author(self):
return Author.query.get(self.author_id)
I can then access the author property on a Book object and it returns me an Author object. Let's say I know a book and I want to get info about the author like this:
book: Book = Book.query.get(book_id)
author: Author = book.author
However, with the SQLAlchemy, I have to use session.get, which means I would have to pass my current session as a parameter:
class Book(db.Model):
...
def author(self, session):
return session.get(Author, author_id)
Is there a way to preserve the simplicity of book.author while using the SQLAlchemy package? I have little experience with the pure SQLAlchemy, so if you can push me in the right direction, it will be appreciated.
Edit:
In my case, at the time of adding the book, the author is not necessarily known to the system yet, causing an error because of an unknown foreign key in the case of the relationship solution.
As I mentioned in the question edit, the author (or its equivalent in the real problem I'm working on) is not necessarily known at the time of adding the book. In case I'm adding a book when the author is not known yet, the program raises an error because of an unknown foreign key when I'm using a relationship. Therefore, the correct solution in this case is the one proposed by Ilja Everilä. The solution then looks like:
from sqlalchemy.orm import object_session
...
class Book(db.Model):
...
#property
def author(self):
return object_session(self).get(Author, self.author_id)

SQLAlchemy many-to-many with single link for multiple tables

I'm writing a SQLAlchemy based ORM for a database where multiple pairs of tables are connected by a single link table. This link table has four columns: from_table_name, from_table_key, to_table_key, to_table_key.
I want to set up a many to many relationship between two tables in the database. Normally I would do something like:
class Table1(Base):
__tablename__ = 'table_1'
id = Column(Integer, primary_key=True)
# some other columns here
data_from_table_2 = relationship('Table2', secondary='link')
class Table2(Base):
__tablename__ = 'table_2'
id = Column(Integer, primary_key=True)
# some other columns here
data_from_table_1 = relationship('Table1', secondary='link')
class Link(Base):
__tablename__ = 'link'
table_1_id = Column(Integer, ForeignKey('table_1.id'), primary_key=True)
table_2_id = Column(Integer, ForeignKey('employee.id'), primary_key=True)
However, this doesn't work in the given case because I need to specify not just the key in my link table, but also the name of the table being related.
For inspiration, I've been looking through the SQLAlchemy docs on relationships here but haven't found anything useful. Does anyone know how to set up a many to many relationship between the two tables above?
As pointed out in the comments to this question, the presented database schema is a confusing design. However, the premise of the question indicates that the schema was created externally and it is up to the developer to come up with an ORM.
The requested relationship can bee implemented manually via class properties and custom queries. For example:
class Table1(Base):
__tablename__ = 'table_1'
id = Column(Integer, primary_key=True)
#property
def data_from_table_2(self):
with Session() as session:
return session.query(Reference).filter(
Link.table_1_id == self.id and
Link.table_1_name == self.__tablename__
).all()
Note that depending on your use case, you may want to use the session attached to the database object
#property
def data_from_table_2(self):
session = object_session(self)
return session.query(Reference).filter(
Link.table_1_id == self.id and
Link.table_1_name == self.__tablename__
).all()

How should i use sqlalchemy to access the schema

I have a file called models.py in which i state:
Base = declarative_base()
class Vehicle(Base):
__tablename__ = 'vehicle'
id = Column(Integer, primary_key=True)
code = Column(String(15), nullable=False)
description = Column(String(100), default='')
vehicletype_id = ForeignKey('VehicleType.id')
Base.metadata.create_all(engine)
Which creates the database tables in my PostgreSQL database.
In my app.py should i now use:
from models.py import Vehicle
<do something with the Vehicle object>
or should i use something like:
meta = MetaData()
meta.reflect(bind=engine)
vehicle = meta.tables['vehicle']
when i want to access the schema of the table and the data in the database in that table.
I want to be able to create an API call (flask-jsonrpc) that gives the schema of a table , and another API call that returns the data from that table in the PostgreSQL database.
Since you're already using declarative ORM approach (by declaring your Vehicle class), there is no point to reflect it. Reflection is normally used when you're dealing with existing database and advanced features (such as defining custom relationships) are not important to you.

ArgumentError in joinedload

I have these models:
class User(UserMixin, db.Model):
__tablename__ = 'users_user'
...
country = db.Column(db.Integer, db.ForeignKey('countries.id'))
class Country(db.Model):
__tablename__ = 'countries'
id = db.Column(db.Integer, primary_key=True)
...
user_country = db.relationship('User', backref='user_country', lazy='joined')
I am trying this query:
User.query.options(joinedload(Country.user_country)).filter_by(id=current_user.get_id()).first()
That will throw this error:
ArgumentError: Can't find property 'user_country' on any entity specified in this Query.
Note the full path from root (Mapper|User|users_user) to target entity must be specified.
What is wrong here?
The joinedload here is unnecessary.
By default relationships are lazily-loaded. This causes additional SELECT queries to be issued to retrieve the data. joinedload is one of the ways to force the relationship to be eagerly loaded by using a JOIN instead.
In this case, however, you've defaulted the relationship between User and Country to use eager loading by specifying lazy='joined'. This would reduce your query to
User.query.filter(id=current_user.get_id()).first()
While this will help you with the ArgumentError, we can go a little further. The query itself is unnecessary as well. current_user already has the data for its related Country because of the eager join. Accessing current_user.user_country will not send any additional queries to the database.

Filtering relationships in SQL Alchemy

I have the following scenario:
class Author(Base):
__tablename__ = 'author'
id = Column(Integer, primary_key = True)
name = Column(String)
books = relationship('Books', backref='author')
class Book(Base):
__tablename__ = 'book'
id = Column(Integer, primary_key = True)
title = Column(String)
What I would like to do is load all authors who have a book containing SQL in
the title. i.e.
authors = session.query(Author)\
.join(Author.books)\
.filter(Book.title.like('%SQL%')\
.all()
Seems simple.
What I would then like to do is iterate over the authors and display their
books. I would expect that when accessing authors[0].books, it will return ONLY
books that have 'SQL' in their title. However, I am getting ALL books assigned
to that author. The filter is applied to the list of authors but not their
books when I access the relationship.
How can I structure my query such that if I filter on a relationship (i.e.
books), when I go to access that relationship, the filtering is still applied?
Please read Routing Explicit Joins/Statements into Eagerly Loaded Collections. Then using contains_eager you can structure your query and get exactly what you want:
authors = (
session.query(Author)
.join(Author.books)
.options(contains_eager(Author.books)) # tell SA that we load "all" books for Authors
.filter(Book.title.like('%SQL%'))
).all()
Please note that you are actually tricking sqlalchemy into thinking that it has loaded all the collection of Author.books, and as such your session will know false information about the real state of the world.
In short, this is not possible. (If it were, an Author instance would have different books attribute depending on how it was queried, which doesn't make sense.)
What you could do instead is query the reverse relationship:
books = session.query(Book) \
.filter(Book.title.like('%SQL%')) \
.all()
Then you can access .author on each book to collect books written by the same author together.

Categories