Skip to content

Commit ac60fe2

Browse files
committed
add logs for patch, add tests
1 parent b6a5d09 commit ac60fe2

3 files changed

Lines changed: 76 additions & 20 deletions

File tree

fastapi_jsonapi/data_layers/sqla_orm.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -316,12 +316,14 @@ async def update_object(
316316

317317
await self.before_update_object(obj, model_kwargs=new_data, view_kwargs=view_kwargs)
318318

319+
missing = object()
320+
319321
has_updated = False
320322
for field_name, new_value in new_data.items():
321323
# TODO: get field alias (if present) and get attribute by alias (rarely used, but required)
322-
missing = object()
323324

324325
if (old_value := getattr(obj, field_name, missing)) is missing:
326+
log.warning("No field %r on %s. Make sure schema conforms model.", field_name, type(obj))
325327
continue
326328

327329
if old_value != new_value:

tests/schemas.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -343,3 +343,8 @@ class SelfRelationshipSchema(BaseModel):
343343
resource_type="self_relationship",
344344
),
345345
)
346+
347+
348+
class CustomUserAttributesSchema(UserBaseSchema):
349+
spam: str
350+
eggs: str

tests/test_api/test_api_sqla_with_includes.py

Lines changed: 68 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import logging
2-
from itertools import chain
2+
from itertools import chain, zip_longest
33
from json import dumps
44
from typing import Dict, List
55
from uuid import uuid4
@@ -26,6 +26,7 @@
2626
Workplace,
2727
)
2828
from tests.schemas import (
29+
CustomUserAttributesSchema,
2930
IdCastSchema,
3031
PostAttributesBaseSchema,
3132
PostCommentAttributesBaseSchema,
@@ -47,10 +48,10 @@ def association_key(data: dict):
4748

4849

4950
def build_app_custom(
50-
schema,
51-
schema_in_patch,
52-
schema_in_post,
5351
model,
52+
schema,
53+
schema_in_patch=None,
54+
schema_in_post=None,
5455
resource_type: str = "misc",
5556
) -> FastAPI:
5657
router: APIRouter = APIRouter()
@@ -922,10 +923,10 @@ async def test_create_user_and_fetch_data(self, app: FastAPI, client: AsyncClien
922923
async def test_create_id_by_client(self):
923924
resource_type = "user"
924925
app = build_app_custom(
925-
UserSchema,
926-
UserPatchSchema,
927-
UserInSchemaAllowIdOnPost,
928-
User,
926+
model=User,
927+
schema=UserSchema,
928+
schema_in_post=UserInSchemaAllowIdOnPost,
929+
schema_in_patch=UserPatchSchema,
929930
resource_type=resource_type,
930931
)
931932

@@ -957,7 +958,12 @@ async def test_create_id_by_client(self):
957958
}
958959

959960
async def test_create_id_by_client_uuid_type(self):
960-
app = build_app_custom(IdCastSchema, IdCastSchema, IdCastSchema, IdCast)
961+
resource_type = fake.word()
962+
app = build_app_custom(
963+
model=IdCast,
964+
schema=IdCastSchema,
965+
resource_type=resource_type,
966+
)
961967

962968
new_id = str(uuid4())
963969
create_body = {
@@ -968,14 +974,14 @@ async def test_create_id_by_client_uuid_type(self):
968974
}
969975

970976
async with AsyncClient(app=app, base_url="http://test") as client:
971-
url = app.url_path_for("get_misc_list")
977+
url = app.url_path_for(f"get_{resource_type}_list")
972978
res = await client.post(url, json=create_body)
973979
assert res.status_code == status.HTTP_201_CREATED, res.text
974980
assert res.json() == {
975981
"data": {
976982
"attributes": {},
977983
"id": new_id,
978-
"type": "misc",
984+
"type": resource_type,
979985
},
980986
"jsonapi": {"version": "1.0"},
981987
"meta": None,
@@ -984,10 +990,8 @@ async def test_create_id_by_client_uuid_type(self):
984990
async def test_create_with_relationship_to_the_same_table(self):
985991
resource_type = "self_relationship"
986992
app = build_app_custom(
987-
SelfRelationshipSchema,
988-
SelfRelationshipSchema,
989-
SelfRelationshipSchema,
990-
SelfRelationship,
993+
model=SelfRelationship,
994+
schema=SelfRelationshipSchema,
991995
resource_type=resource_type,
992996
)
993997

@@ -1108,10 +1112,10 @@ class UserPatchSchemaWithExtraAttribute(UserPatchSchema):
11081112

11091113
resource_type = "user"
11101114
app = build_app_custom(
1111-
UserSchema,
1112-
UserPatchSchemaWithExtraAttribute,
1113-
UserPatchSchemaWithExtraAttribute,
1114-
User,
1115+
model=User,
1116+
schema=UserSchema,
1117+
schema_in_post=UserPatchSchemaWithExtraAttribute,
1118+
schema_in_patch=UserPatchSchemaWithExtraAttribute,
11151119
resource_type=resource_type,
11161120
)
11171121
new_attrs = UserPatchSchemaWithExtraAttribute(
@@ -1132,6 +1136,51 @@ class UserPatchSchemaWithExtraAttribute(UserPatchSchema):
11321136
res = await client.patch(url, json=patch_user_body)
11331137
assert res.status_code == status.HTTP_200_OK, res.text
11341138

1139+
async def test_update_schema_has_extra_fields(self, user_1: User, caplog):
1140+
resource_type = "user_extra_fields"
1141+
app = build_app_custom(
1142+
model=User,
1143+
schema=UserAttributesBaseSchema,
1144+
schema_in_patch=CustomUserAttributesSchema,
1145+
resource_type=resource_type,
1146+
)
1147+
1148+
new_attributes = CustomUserAttributesSchema(
1149+
age=fake.pyint(),
1150+
name=fake.user_name(),
1151+
spam=fake.word(),
1152+
eggs=fake.word(),
1153+
)
1154+
create_body = {
1155+
"data": {
1156+
"attributes": new_attributes.dict(),
1157+
"id": user_1.id,
1158+
},
1159+
}
1160+
1161+
async with AsyncClient(app=app, base_url="http://test") as client:
1162+
url = app.url_path_for(f"update_{resource_type}_detail", obj_id=user_1.id)
1163+
res = await client.patch(url, json=create_body)
1164+
1165+
assert res.status_code == status.HTTP_200_OK, res.text
1166+
assert res.json() == {
1167+
"data": {
1168+
"attributes": UserAttributesBaseSchema(**new_attributes.dict()).dict(),
1169+
"id": str(user_1.id),
1170+
"type": resource_type,
1171+
},
1172+
"jsonapi": {"version": "1.0"},
1173+
"meta": None,
1174+
}
1175+
1176+
messages = [x.message for x in caplog.get_records("call") if x.levelno == logging.WARNING]
1177+
messages.sort()
1178+
for log_message, expected in zip_longest(
1179+
messages,
1180+
sorted([f"No field {name!r}" for name in ("spam", "eggs")]),
1181+
):
1182+
assert expected in log_message
1183+
11351184

11361185
class TestPatchObjectRelationshipsToOne:
11371186
async def test_ok_when_foreign_key_of_related_object_is_nullable(

0 commit comments

Comments
 (0)