Fetch data from 3 different tables with SQLAlchemy or SQLModel - api

I want to fetch data from 3 tables with SQLAlchemy or SQLModel. For example lets say that my tables are the following:
class A(SQLModel, table=true):
id: int
title: str
class B(SQLModel, table=true):
id: int
a_id: foreign_key("a.id")
name: str
class C(SQLModel, table=true):
id: int
b_id: foreign_key("b.id")
text: str
The response that I want to have is the following:
[
{
"id": 1,
"title": "This is A table",
"b": [
{
"id": 1,
"name": "This is B table",
"c":[
{
"id":1,
"text": "My text from c"
}
]
}
]
}
]
I am trying with selectinload but doesn't work
query = (
select(A)
.where(A.id == a_id)
.options(
selectinload(A.b).joinedload(
B.c
)
)
)
try:
response = (await session.exec(query)).one()
except NoResultFound:
raise HTTPException(status_code=404, detail="Data not found")
Thank you in advance

I have tried these models with newer version of sqlalchemy (not sqlmodel) it works as shown in this example (sqlalchemy=1.4.39)
class A(Base):
__tablename__ = 'a'
id = Column(Integer, primary_key=True)
title = Column(String(length=100))
b = relationship("B", backref="a")
class B(Base):
__tablename__ = 'b'
id = Column(Integer, primary_key=True)
name = Column(String(length=100))
a_id = Column(ForeignKey('a.id', ondelete='CASCADE'))
c = relationship("C", backref="b")
class C(Base):
__tablename__ = 'c'
id = Column(Integer, primary_key=True)
text = Column(String(length=100))
b_id = Column(ForeignKey('b.id', ondelete='CASCADE'))
Query:
stmt = select(A).options(selectinload(A.b).selectinload(B.c))
resp = await session.execute(query)
return resp.scalars().all()
(Prepare pydantic model related to this query)
schema.py
class CResp(BaseModel):
id: int
text: str
class BResp(BaseModel):
id: int
name: str
c: List[CResp]
class AResp(BaseModel):
id: int
title: str
b: List[BResp]
views.py
#app.get("/", response_model=List[AResp])
async def index(session=Depends(get_session)):
query = select(A).options(selectinload(A.b).selectinload(B.c))
resp = await session.execute(query)
return resp.scalars().all()
If you want to use this query with sqlmodel, you can try some other versions of sqlalchemy as mentioned here (SQLModel error: "AttributeError: 'Team' object has no attribute 'heroes'"), sqlmodel has some problems with SQLalchemy 1.4.36+ versions

Related

Foreign key filter in a child object

I have the models:
class Boss(models.Model):
fullname = models.TextField()
class Shop(models.Model):
name = models.TextField()
address = models.TextField()
phone = models.TextField()
boss = models.ForeignKey(
Boss, on_delete=models.CASCADE, related_name="shops"
)
class Employee(models.Model):
name = models.TextField()
phone = models.TextField()
shop = models.ForeignKey(
Shop, on_delete=models.CASCADE, related_name="employees"
)
class WorkSpace(models.Model):
name = models.TextField()
employee = models.ForeignKey(
Shop, on_delete=models.CASCADE, related_name="work_spaces"
)
Serializers:
class WorkSpaceSerializer(serializers.ModelSerializer):
class Meta:
model = WorkSpace
fields = ["id","type"]
class EmployeeSerializer(serializers.ModelSerializer):
work_spaces = WorkSpaceSerializer(many=True, read_only=True)
class Meta:
model = Employee
fields = ["id","work_spaces","name","phone"]
class ShopSerializer(serializers.ModelSerializer):
employees = EmployeeSerializer(many=True, read_only=True)
class Meta:
model = Shop
fields = ["id","employees","phone","name","address"]
class BossSerializer(serializers.ModelSerializer):
shops = ShopSerializer(many=True, read_only=True)
class Meta:
model = Boss
fields = ["id","fullname","shops"]
View
class BossListView(
ListModelMixin,
GenericViewSet,
):
queryset = Boss.objects.all()
serializer_class = BossSerializer
permission_classes = [IsAuthenticated]
def get_queryset(self):
workspace_type = self.request.query_params.get("workspace_type")
qs = super().get_queryset()
if workspace_type:
qs = qs.filter(shops__employees__work_spaces__type=workspace_type).distinct()
return qs
I filtered with
Boss.objects.filter(shops__employees__work_spaces__type=C1)
and got:
{
"shops": [
{
"id": 32,
"name": "Garden flowers",
"address": "5 st. Hool-Maa",
"phone": "879124851861598",
"employees": [
{
"id": 150,
"name": "Mike",
"phone": "8154451246",
"work_spaces": [
{
"id": 497,
"type": "B12"
},
{
"id": 15,
"type": "Z5"
},
{
"id": 33,
"type": "C1"
}
]
}
]
}
]
}
But I only need C1 from work_spaces:
[{
"id": 33,
"type": "C1"
}]
How can I exclude other work_spaces from the queryset or do I need to convert the result to a list and then filter using a for loop? There can be many workspaces, and I don't need to show them all to the user, I need information about the Boss, the Shop, the Employee..
According to your view, you are applying the lower() method on the workspace_type.
So if you have workspace_type=C1, applying lower() will make workspace_type=c1, and your filter won't match any record.
I make here the assumption that your values have a capital letter.

One to many query using marshmallow for response not working

I'm trying to make a query and the response as follows:
get_data = request.get_json()
email = get_data.get('email')
result = User.query.join(User_Preference).filter(User.email==email).first()
dump_data = developer_schema.dump(result)
return jsonify({'data' : dump_data})
from the following two tables defined as follows:
class User_Preference(db.Model):
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer, db.ForeignKey(User.id))
preference = db.Column(db.String(45))
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String(50), unique=True)
password = db.Column(db.String(256))
experience = db.Column(db.Integer)
avatar = db.Column(db.String(256))
revenue = db.Column(db.Integer)
preferences = relationship("User_Preference")
These are the marshmallow schemas I am using:
class DeveloperPreferences(ma.Schema):
class Meta:
fields = ('user_id', 'preference')
class DeveloperSchema(ma.Schema):
class Meta:
fields = ('id', 'email', 'avatar')
#model = User
preferences = ma.Nested(DeveloperPreferences, many = True)
However, the return I am getting is as follows:
{
"data": {
"avatar": "example.com",
"email": "example#test.com",
"id": 10
}
}
That is only the user part of the query is being returned not the preferences. Anyone have any idea on how to solve this?
In order to also display the preferences in the result, it is necessary to also specify the field in the fields to be displayed.
When defining the nested fields, you slipped into the meta specification with the indentation. However, the fields are set at the schema level.
class DeveloperPreferenceSchema(ma.Schema):
class Meta:
fields = ('user_id', 'preference')
class DeveloperSchema(ma.Schema):
class Meta:
fields = ('id', 'email', 'avatar', 'preferences')
preferences = ma.Nested(DeveloperPreferenceSchema, many=True)
The result should then be as follows.
{
"data": {
"avatar": "example.com",
"email": "example#test.com",
"id": 10,
"preferences": [
{
"preference": "anything",
"user_id": 10
},
{
"preference": "something",
"user_id": 10
}
]
}
}

How to filter flask-marshmallow nested field?

I working on a user's public-facing profile page, where I would like to display the user's profile as well as their "published" recipes. I have the following UserSchema, but this schema displays all recipes including the one that has not been published. I want to strictly display only the published recipes. Is there a way to filter out recipes that have not been published? I looked through marshmallow documentation but could not find an answer.
class User(db.Model):
__tablename__ = 'user'
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), nullable=False, unique=True)
email = db.Column(db.String(200), nullable=False, unique=True)
password = db.Column(db.String(200))
recipes = db.relationship('Recipe', backref='user')
class UserSchema(Schema):
class Meta:
ordered = True
id = fields.Int(dump_only=True)
username = fields.String(required=True)
email = fields.Email(required=True)
password = fields.Method(required=True)
recipes = fields.Nested("RecipeSchema", many=True, exclude=("author",))
Following is the RecipeModel and RecipeSchema,
class Recipe(db.Model):
__tablename__ = 'recipe'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(100), nullable=False)
description = db.Column(db.String(200))
is_publish = db.Column(db.Boolean(), default=False)
user_id = db.Column(db.Integer(), db.ForeignKey("user.id"))
class RecipeSchema(Schema):
class Meta:
ordered = True
id = fields.Integer(dump_only=True)
name = fields.String(required=True, validate=[validate.Length(max=100)])
description = fields.String(validate=[validate.Length(max=200)])
is_publish = fields.Boolean(dump_only=True)
author = fields.Nested(UserSchema, attribute='user', dump_only=True, exclude=('email', ))
Resource responsible for returning user profile data is:
from schemas.user import UserSchema
user_schema = UserSchema()
class UserResource(Resource):
#classmethod
def get(cls, _id: int):
user = User.query.filter_by(id=_id).first()
if not user:
return {"message": gettext("user_not_found")}, 404
return user_schema.dump(user), 200
Current output is
{
"id": "1",
"username": "john.doe",
"recipes": [
{
"id": "1",
"name": "cheese pizza",
"description": "yummy",
"is_publish": true
},
{
"id": "2",
"name": "Potato Salad",
"description": "tags with sepearate function",
"is_publish": false
}
]
}
I want it to be
{
"id": "1",
"username": "john.doe",
"recipes": [
{
"id": "1",
"name": "cheese pizza",
"description": "yummy",
"is_publish": true
}
]
}

How to Serialize/Deserialize table using schema in lua

I have the following table:
local obj = {
firstname = "John",
lastname = "Bush",
age = 22,
height = 186,
friends = {
{ firstname = "Paul", lastname = "Graham", age = 20, height = 178 },
{ firstname = "Gianou", lastname = "Kwnstantina", age = 25, height = 172 }
},
bodies = { 4, 3, 10 }
}
and I want to transform it to this: (when inserting to database)
local tuple = {
'John', 'Bush', 22, 186, { { 'Paul', 'Graham', 20, 178 }, { 'Gianou', 'Kwnstantina', 25, 172 } }, { 4, 3, 10 }
}
which I managed to generate using:
function encode(schema, data)
local array = {}
function rec(arr, schema, d)
for i, field in ipairs(schema) do
arr[i] = {}
if field.type == "array" and type(field.items) == "table" then
for it, piece in ipairs(d[field.name]) do
arr[i][it] = {}
rec(arr[i][it], field.items, piece)
end
else
arr[i] = d[field.name]
end
end
end
rec(array, schema, data)
return array
end
My problem is that I want to reconstruct that data to the initial form when I retrieve it. I found some tools that can do this in other languages but the problem is that they transform the data to binary, I just want that array.
To be able to reconstruct I need something like a schema:
local schema = {
{ name = "firstname", type = "string" },
{ name = "lastname", type = "string" },
{ name = "age", type = "int" },
{ name = "height", type = "int" },
{ name = "friends", type = "array", items = {
{ name = "firstname", type = "string" },
{ name = "lastname", type = "string" },
{ name = "age", type = "int" },
{ name = "height", type = "int" }
}},
{ name = "bodies", type = "array", items = "int"}
}
I found that it's pretty easy to encode it just by retrieving the values but being able to validate and decode it seems pretty hard, is there a concept (name) for this operation in computer science, or a library that I can study (it needs to use a schema)?
I need a keyword to google...
Thank you!
EDIT: Just reversing the encode I found a simple way to decode.
function decode(schema, data)
local obj = {}
function rec(object, sch, d)
for i, field in ipairs(sch) do
if field.type == "array" and type(field.items) == "table" then
object[field.name] = {}
for it, piece in ipairs(d[i]) do
object[field.name][it] = {}
rec(object[field.name][it], field.items, piece)
end
else
object[field.name] = d[i]
end
end
end
rec(obj, schema, data)
return obj
end
I would still love suggestions on the code and where can I found more info on this type of info.
present version: https://pastebin.com/Ub4vZ0GU

Map two fields into one with DRF

Say I have model Team and it has a one-to-many relation with Player and Standin
I can serialize them like this:
class TeamSerializer(serializers.ModelSerializer):
...
players = PlayerSerializer(many=True)
standins = StandinSerializer(many=True)
class Meta:
model = Team
fields = '__all__'
So output will be:
{
"id": 17,
...
"players": [...],
"standins": [...]
}
How could i get:
{
"id": 17,
...
"roster": {
"players": [...],
"standins": [...]
}
}
override the to_representation method on your sereializer:
def to_representation(self, instance):
ret = super(TeamSerializer, self).to_representation(instance)
ret['roster'] = {
'players': ret['players'],
'standins': ret['standins']
}
del ret['players']
del ret['standins']
return ret