Skip to content

Commit a5dfc0d

Browse files
authored
Merge pull request #82 from mts-ai/fix-update-relationship
Fix update relationship
2 parents 39d995b + 651172e commit a5dfc0d

File tree

4 files changed

+155
-17
lines changed

4 files changed

+155
-17
lines changed

fastapi_jsonapi/data_layers/sqla_orm.py

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -173,19 +173,26 @@ async def apply_relationships(self, obj: TypeModel, data_create: BaseJSONAPIItem
173173

174174
if relationship_info.many:
175175
assert isinstance(relationship_in, BaseJSONAPIRelationshipDataToManySchema)
176-
related_data = await self.get_related_objects_list(
177-
related_model=related_model,
178-
related_id_field=relationship_info.id_field_name,
179-
ids=[r.id for r in relationship_in.data],
180-
)
176+
177+
related_data = []
178+
if relationship_in.data:
179+
related_data = await self.get_related_objects_list(
180+
related_model=related_model,
181+
related_id_field=relationship_info.id_field_name,
182+
ids=[r.id for r in relationship_in.data],
183+
)
181184
else:
182185
assert isinstance(relationship_in, BaseJSONAPIRelationshipDataToOneSchema)
183-
related_data = await self.get_related_object(
184-
related_model=related_model,
185-
related_id_field=relationship_info.id_field_name,
186-
id_value=relationship_in.data.id,
187-
)
188186

187+
if relationship_in.data:
188+
related_data = await self.get_related_object(
189+
related_model=related_model,
190+
related_id_field=relationship_info.id_field_name,
191+
id_value=relationship_in.data.id,
192+
)
193+
else:
194+
setattr(obj, relation_name, None)
195+
continue
189196
try:
190197
hasattr(obj, relation_name)
191198
except MissingGreenlet:

tests/models.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -305,8 +305,13 @@ class SelfRelationship(Base):
305305
),
306306
nullable=True,
307307
)
308-
# parent = relationship("SelfRelationship", back_populates="s")
309-
self_relationship = relationship("SelfRelationship", remote_side=[id])
308+
children_objects = relationship(
309+
"SelfRelationship",
310+
backref=backref("parent_object", remote_side=[id]),
311+
)
312+
313+
if TYPE_CHECKING:
314+
parent_object: Optional["SelfRelationship"]
310315

311316

312317
class ContainsTimestamp(Base):

tests/schemas.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -400,13 +400,25 @@ class CustomUUIDItemSchema(CustomUUIDItemAttributesSchema):
400400
id: UUID = Field(client_can_set_id=True)
401401

402402

403-
class SelfRelationshipSchema(BaseModel):
403+
class SelfRelationshipAttributesSchema(BaseModel):
404404
name: str
405-
self_relationship: Optional["SelfRelationshipSchema"] = Field(
405+
406+
class Config:
407+
orm_mode = True
408+
409+
410+
class SelfRelationshipSchema(SelfRelationshipAttributesSchema):
411+
parent_object: Optional["SelfRelationshipSchema"] = Field(
406412
relationship=RelationshipInfo(
407413
resource_type="self_relationship",
408414
),
409415
)
416+
children_objects: Optional[list["SelfRelationshipSchema"]] = Field(
417+
relationship=RelationshipInfo(
418+
resource_type="self_relatiosnhip",
419+
many=True,
420+
),
421+
)
410422

411423

412424
class CascadeCaseSchema(BaseModel):

tests/test_api/test_api_sqla_with_includes.py

Lines changed: 117 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
CustomUUIDItemAttributesSchema,
5353
PostAttributesBaseSchema,
5454
PostCommentAttributesBaseSchema,
55+
SelfRelationshipAttributesSchema,
5556
SelfRelationshipSchema,
5657
UserAttributesBaseSchema,
5758
UserBioAttributesBaseSchema,
@@ -1437,7 +1438,7 @@ async def test_create_with_relationship_to_the_same_table(self):
14371438
"name": "child",
14381439
},
14391440
"relationships": {
1440-
"self_relationship": {
1441+
"parent_object": {
14411442
"data": {
14421443
"type": resource_type,
14431444
"id": parent_object_id,
@@ -1446,7 +1447,7 @@ async def test_create_with_relationship_to_the_same_table(self):
14461447
},
14471448
},
14481449
}
1449-
url = f"{url}?include=self_relationship"
1450+
url = f"{url}?include=parent_object"
14501451
res = await client.post(url, json=create_with_relationship_body)
14511452
assert res.status_code == status.HTTP_201_CREATED, res.text
14521453

@@ -1458,7 +1459,7 @@ async def test_create_with_relationship_to_the_same_table(self):
14581459
"attributes": {"name": "child"},
14591460
"id": child_object_id,
14601461
"relationships": {
1461-
"self_relationship": {
1462+
"parent_object": {
14621463
"data": {
14631464
"id": parent_object_id,
14641465
"type": "self_relationship",
@@ -2042,6 +2043,60 @@ async def test_update_resource_error_same_id(
20422043
],
20432044
}
20442045

2046+
async def test_remove_to_one_relationship_using_by_update(self, async_session: AsyncSession):
2047+
resource_type = "self_relationship"
2048+
with suppress(KeyError):
2049+
RoutersJSONAPI.all_jsonapi_routers.pop(resource_type)
2050+
2051+
app = build_app_custom(
2052+
model=SelfRelationship,
2053+
schema=SelfRelationshipSchema,
2054+
resource_type=resource_type,
2055+
)
2056+
2057+
parent_obj = SelfRelationship(name=fake.name())
2058+
child_obj = SelfRelationship(name=fake.name(), parent_object=parent_obj)
2059+
async_session.add_all([parent_obj, child_obj])
2060+
await async_session.commit()
2061+
2062+
assert child_obj.self_relationship_id == parent_obj.id
2063+
2064+
async with AsyncClient(app=app, base_url="http://test") as client:
2065+
expected_name = fake.name()
2066+
update_body = {
2067+
"data": {
2068+
"id": str(child_obj.id),
2069+
"attributes": {
2070+
"name": expected_name,
2071+
},
2072+
"relationships": {
2073+
"parent_object": {
2074+
"data": None,
2075+
},
2076+
},
2077+
},
2078+
}
2079+
params = {
2080+
"include": "parent_object",
2081+
}
2082+
url = app.url_path_for(f"update_{resource_type}_detail", obj_id=child_obj.id)
2083+
res = await client.patch(url, params=params, json=update_body)
2084+
assert res.status_code == status.HTTP_200_OK, res.text
2085+
assert res.json() == {
2086+
"data": {
2087+
"attributes": SelfRelationshipAttributesSchema(name=expected_name).dict(),
2088+
"id": str(child_obj.id),
2089+
"relationships": {"parent_object": {"data": None}},
2090+
"type": "self_relationship",
2091+
},
2092+
"included": [],
2093+
"jsonapi": {"version": "1.0"},
2094+
"meta": None,
2095+
}
2096+
2097+
await async_session.refresh(child_obj)
2098+
assert child_obj.self_relationship_id is None
2099+
20452100

20462101
class TestPatchRelationshipsToMany:
20472102
async def test_ok(
@@ -2217,6 +2272,65 @@ async def test_relationship_not_found(
22172272
],
22182273
}
22192274

2275+
async def test_remove_to_many_relationship_using_by_update(self, async_session: AsyncSession):
2276+
resource_type = "self_relationship"
2277+
with suppress(KeyError):
2278+
RoutersJSONAPI.all_jsonapi_routers.pop(resource_type)
2279+
2280+
app = build_app_custom(
2281+
model=SelfRelationship,
2282+
schema=SelfRelationshipSchema,
2283+
resource_type=resource_type,
2284+
)
2285+
2286+
parent_obj = SelfRelationship(name=fake.name())
2287+
child_obj_1 = SelfRelationship(name=fake.name(), parent_object=parent_obj)
2288+
child_obj_2 = SelfRelationship(name=fake.name(), parent_object=parent_obj)
2289+
async_session.add_all([parent_obj, child_obj_1, child_obj_2])
2290+
await async_session.commit()
2291+
2292+
assert child_obj_1.self_relationship_id == parent_obj.id
2293+
assert child_obj_2.self_relationship_id == parent_obj.id
2294+
assert len(parent_obj.children_objects) == 2 # noqa PLR2004
2295+
2296+
async with AsyncClient(app=app, base_url="http://test") as client:
2297+
expected_name = fake.name()
2298+
update_body = {
2299+
"data": {
2300+
"id": str(parent_obj.id),
2301+
"attributes": {
2302+
"name": expected_name,
2303+
},
2304+
"relationships": {
2305+
"children_objects": {
2306+
"data": None,
2307+
},
2308+
},
2309+
},
2310+
}
2311+
params = {
2312+
"include": "children_objects",
2313+
}
2314+
url = app.url_path_for(f"update_{resource_type}_detail", obj_id=parent_obj.id)
2315+
res = await client.patch(url, params=params, json=update_body)
2316+
assert res.status_code == status.HTTP_200_OK, res.text
2317+
assert res.json() == {
2318+
"data": {
2319+
"attributes": SelfRelationshipAttributesSchema(name=expected_name).dict(),
2320+
"id": str(parent_obj.id),
2321+
"relationships": {"children_objects": {"data": []}},
2322+
"type": "self_relationship",
2323+
},
2324+
"included": [],
2325+
"jsonapi": {"version": "1.0"},
2326+
"meta": None,
2327+
}
2328+
2329+
await async_session.refresh(child_obj_1)
2330+
await async_session.refresh(child_obj_2)
2331+
assert child_obj_1.self_relationship_id is None
2332+
assert child_obj_2.self_relationship_id is None
2333+
22202334

22212335
class TestDeleteObjects:
22222336
async def test_delete_object_and_fetch_404(

0 commit comments

Comments
 (0)