Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 17 additions & 10 deletions fastapi_jsonapi/data_layers/sqla_orm.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,19 +173,26 @@ async def apply_relationships(self, obj: TypeModel, data_create: BaseJSONAPIItem

if relationship_info.many:
assert isinstance(relationship_in, BaseJSONAPIRelationshipDataToManySchema)
related_data = await self.get_related_objects_list(
related_model=related_model,
related_id_field=relationship_info.id_field_name,
ids=[r.id for r in relationship_in.data],
)

related_data = []
if relationship_in.data:
related_data = await self.get_related_objects_list(
related_model=related_model,
related_id_field=relationship_info.id_field_name,
ids=[r.id for r in relationship_in.data],
)
else:
assert isinstance(relationship_in, BaseJSONAPIRelationshipDataToOneSchema)
related_data = await self.get_related_object(
related_model=related_model,
related_id_field=relationship_info.id_field_name,
id_value=relationship_in.data.id,
)

if relationship_in.data:
related_data = await self.get_related_object(
related_model=related_model,
related_id_field=relationship_info.id_field_name,
id_value=relationship_in.data.id,
)
else:
setattr(obj, relation_name, None)
continue
try:
hasattr(obj, relation_name)
except MissingGreenlet:
Expand Down
9 changes: 7 additions & 2 deletions tests/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -305,8 +305,13 @@ class SelfRelationship(Base):
),
nullable=True,
)
# parent = relationship("SelfRelationship", back_populates="s")
self_relationship = relationship("SelfRelationship", remote_side=[id])
children_objects = relationship(
"SelfRelationship",
backref=backref("parent_object", remote_side=[id]),
)

if TYPE_CHECKING:
parent_object: Optional["SelfRelationship"]


class ContainsTimestamp(Base):
Expand Down
16 changes: 14 additions & 2 deletions tests/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -400,13 +400,25 @@ class CustomUUIDItemSchema(CustomUUIDItemAttributesSchema):
id: UUID = Field(client_can_set_id=True)


class SelfRelationshipSchema(BaseModel):
class SelfRelationshipAttributesSchema(BaseModel):
name: str
self_relationship: Optional["SelfRelationshipSchema"] = Field(

class Config:
orm_mode = True


class SelfRelationshipSchema(SelfRelationshipAttributesSchema):
parent_object: Optional["SelfRelationshipSchema"] = Field(
relationship=RelationshipInfo(
resource_type="self_relationship",
),
)
children_objects: Optional[list["SelfRelationshipSchema"]] = Field(
relationship=RelationshipInfo(
resource_type="self_relatiosnhip",
many=True,
),
)


class CascadeCaseSchema(BaseModel):
Expand Down
120 changes: 117 additions & 3 deletions tests/test_api/test_api_sqla_with_includes.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
CustomUUIDItemAttributesSchema,
PostAttributesBaseSchema,
PostCommentAttributesBaseSchema,
SelfRelationshipAttributesSchema,
SelfRelationshipSchema,
UserAttributesBaseSchema,
UserBioAttributesBaseSchema,
Expand Down Expand Up @@ -1437,7 +1438,7 @@ async def test_create_with_relationship_to_the_same_table(self):
"name": "child",
},
"relationships": {
"self_relationship": {
"parent_object": {
"data": {
"type": resource_type,
"id": parent_object_id,
Expand All @@ -1446,7 +1447,7 @@ async def test_create_with_relationship_to_the_same_table(self):
},
},
}
url = f"{url}?include=self_relationship"
url = f"{url}?include=parent_object"
res = await client.post(url, json=create_with_relationship_body)
assert res.status_code == status.HTTP_201_CREATED, res.text

Expand All @@ -1458,7 +1459,7 @@ async def test_create_with_relationship_to_the_same_table(self):
"attributes": {"name": "child"},
"id": child_object_id,
"relationships": {
"self_relationship": {
"parent_object": {
"data": {
"id": parent_object_id,
"type": "self_relationship",
Expand Down Expand Up @@ -2042,6 +2043,60 @@ async def test_update_resource_error_same_id(
],
}

async def test_remove_to_one_relationship_using_by_update(self, async_session: AsyncSession):
resource_type = "self_relationship"
with suppress(KeyError):
RoutersJSONAPI.all_jsonapi_routers.pop(resource_type)

app = build_app_custom(
model=SelfRelationship,
schema=SelfRelationshipSchema,
resource_type=resource_type,
)

parent_obj = SelfRelationship(name=fake.name())
child_obj = SelfRelationship(name=fake.name(), parent_object=parent_obj)
async_session.add_all([parent_obj, child_obj])
await async_session.commit()

assert child_obj.self_relationship_id == parent_obj.id

async with AsyncClient(app=app, base_url="http://test") as client:
expected_name = fake.name()
update_body = {
"data": {
"id": str(child_obj.id),
"attributes": {
"name": expected_name,
},
"relationships": {
"parent_object": {
"data": None,
},
},
},
}
params = {
"include": "parent_object",
}
url = app.url_path_for(f"update_{resource_type}_detail", obj_id=child_obj.id)
res = await client.patch(url, params=params, json=update_body)
assert res.status_code == status.HTTP_200_OK, res.text
assert res.json() == {
"data": {
"attributes": SelfRelationshipAttributesSchema(name=expected_name).dict(),
"id": str(child_obj.id),
"relationships": {"parent_object": {"data": None}},
"type": "self_relationship",
},
"included": [],
"jsonapi": {"version": "1.0"},
"meta": None,
}

await async_session.refresh(child_obj)
assert child_obj.self_relationship_id is None


class TestPatchRelationshipsToMany:
async def test_ok(
Expand Down Expand Up @@ -2217,6 +2272,65 @@ async def test_relationship_not_found(
],
}

async def test_remove_to_many_relationship_using_by_update(self, async_session: AsyncSession):
resource_type = "self_relationship"
with suppress(KeyError):
RoutersJSONAPI.all_jsonapi_routers.pop(resource_type)

app = build_app_custom(
model=SelfRelationship,
schema=SelfRelationshipSchema,
resource_type=resource_type,
)

parent_obj = SelfRelationship(name=fake.name())
child_obj_1 = SelfRelationship(name=fake.name(), parent_object=parent_obj)
child_obj_2 = SelfRelationship(name=fake.name(), parent_object=parent_obj)
async_session.add_all([parent_obj, child_obj_1, child_obj_2])
await async_session.commit()

assert child_obj_1.self_relationship_id == parent_obj.id
assert child_obj_2.self_relationship_id == parent_obj.id
assert len(parent_obj.children_objects) == 2 # noqa PLR2004

async with AsyncClient(app=app, base_url="http://test") as client:
expected_name = fake.name()
update_body = {
"data": {
"id": str(parent_obj.id),
"attributes": {
"name": expected_name,
},
"relationships": {
"children_objects": {
"data": None,
},
},
},
}
params = {
"include": "children_objects",
}
url = app.url_path_for(f"update_{resource_type}_detail", obj_id=parent_obj.id)
res = await client.patch(url, params=params, json=update_body)
assert res.status_code == status.HTTP_200_OK, res.text
assert res.json() == {
"data": {
"attributes": SelfRelationshipAttributesSchema(name=expected_name).dict(),
"id": str(parent_obj.id),
"relationships": {"children_objects": {"data": []}},
"type": "self_relationship",
},
"included": [],
"jsonapi": {"version": "1.0"},
"meta": None,
}

await async_session.refresh(child_obj_1)
await async_session.refresh(child_obj_2)
assert child_obj_1.self_relationship_id is None
assert child_obj_2.self_relationship_id is None


class TestDeleteObjects:
async def test_delete_object_and_fetch_404(
Expand Down