Fix some types in various places regarding profile field values

I believe the correct type is `JsonValue | dict[str, JsonValue]`, except deep down in the displayname/avatar_url store methods.

Removes a dubious cast by correctly casting in `get_profile_data_for_users`
This commit is contained in:
Jason Robinson
2026-06-25 22:40:00 +03:00
parent e278dfdb03
commit 8bace9f97b
5 changed files with 48 additions and 23 deletions
+11 -3
View File
@@ -632,7 +632,7 @@ class ProfileHandler:
async def get_profile_field(
self, target_user: UserID, field_name: str
) -> JsonValue:
) -> JsonValue | dict[str, JsonValue]:
"""
Fetch a user's profile from the database for local users and over federation
for remote users.
@@ -676,12 +676,16 @@ class ProfileHandler:
target_user: UserID,
requester: Requester,
field_name: str,
new_value: str,
new_value: JsonValue | dict[str, JsonValue],
by_admin: bool = False,
propagate: bool = False,
) -> None:
"""Wrapper function for setting any profile field for a user."""
if field_name == ProfileFields.DISPLAYNAME:
if not isinstance(new_value, str):
raise SynapseError(
400, "'displayname' must be a string", errcode=Codes.INVALID_PARAM
)
await self.set_displayname(
target_user=target_user,
requester=requester,
@@ -690,6 +694,10 @@ class ProfileHandler:
propagate=propagate,
)
elif field_name == ProfileFields.AVATAR_URL:
if not isinstance(new_value, str):
raise SynapseError(
400, "'avatar_url' must be a string", errcode=Codes.INVALID_PARAM
)
await self.set_avatar_url(
target_user=target_user,
requester=requester,
@@ -721,7 +729,7 @@ class ProfileHandler:
target_user: UserID,
requester: Requester,
field_name: str,
new_value: JsonValue,
new_value: JsonValue | dict[str, JsonValue],
*,
by_admin: bool = False,
) -> None:
+13 -9
View File
@@ -252,7 +252,7 @@ class SyncResult:
presence: list[UserPresenceState]
account_data: list[JsonDict]
# user ID -> {profile field -> value | null if unset }
profile_updates: dict[str, dict[str, JsonValue | None] | None]
profile_updates: dict[str, dict[str, JsonValue | dict[str, JsonValue]] | None]
joined: list[JoinedSyncResult]
invited: list[InvitedSyncResult]
knocked: list[KnockedSyncResult]
@@ -2213,7 +2213,9 @@ class SyncHandler:
profile_data_by_user = await self.store.get_profile_data_for_users(user_ids)
# Serialise the profile updates into the sync response format.
profile_updates: dict[str, dict[str, JsonValue | None] | None] = {}
profile_updates: dict[
str, dict[str, JsonValue | dict[str, JsonValue]] | None
] = {}
for other_user_id in user_ids:
profile_data = profile_data_by_user.get(other_user_id)
if profile_data is None:
@@ -2221,12 +2223,10 @@ class SyncHandler:
# in initial sync.
continue
per_user_updates: dict[str, JsonValue] = {}
per_user_updates: dict[str, JsonValue | dict[str, JsonValue]] = {}
for field_name in profile_fields:
if profile_data.get(field_name):
per_user_updates[field_name] = cast(
JsonValue, profile_data[field_name]
)
per_user_updates[field_name] = profile_data[field_name]
if per_user_updates:
profile_updates[other_user_id] = per_user_updates
@@ -2321,7 +2321,9 @@ class SyncHandler:
# Serialise the profile updates into the sync response format.
# user ID -> {profile field -> value | null if unset }
profile_updates: dict[str, dict[str, JsonValue | None] | None] = {}
profile_updates: dict[
str, dict[str, JsonValue | dict[str, JsonValue]] | None
] = {}
# Process field updates and users who have events in the sync response
if users:
@@ -2351,7 +2353,7 @@ class SyncHandler:
profile_updates[other_user_id] = None
continue
per_user_updates: dict[str, JsonValue] = {}
per_user_updates: dict[str, JsonValue | dict[str, JsonValue]] = {}
if include_users and other_user_id in include_users:
# Include the full profile as this user has events in
# a lazy loaded sync response, except for fields we've recently
@@ -3443,7 +3445,9 @@ class SyncResultBuilder:
presence: list[UserPresenceState] = attr.Factory(list)
account_data: list[JsonDict] = attr.Factory(list)
profile_updates: dict[str, dict[str, JsonValue | None] | None] = attr.Factory(dict)
profile_updates: dict[str, dict[str, JsonValue | dict[str, JsonValue]] | None] = (
attr.Factory(dict)
)
joined: list[JoinedSyncResult] = attr.Factory(list)
invited: list[InvitedSyncResult] = attr.Factory(list)
knocked: list[KnockedSyncResult] = attr.Factory(list)
+2 -2
View File
@@ -21,7 +21,7 @@ from twisted.web.server import Request
from synapse.api.constants import Membership
from synapse.http.server import HttpServer
from synapse.replication.http._base import ReplicationEndpoint
from synapse.types import JsonDict, UserID, create_requester
from synapse.types import JsonDict, JsonValue, UserID, create_requester
if TYPE_CHECKING:
from synapse.server import HomeServer
@@ -65,7 +65,7 @@ class ReplicationProfileSetFieldValue(ReplicationEndpoint):
user_id: str,
requester_id: str,
field_name: str,
new_value: str,
new_value: JsonValue | dict[str, JsonValue],
by_admin: bool = False,
propagate: bool = False,
authenticated_entity: str | None = None,
+3 -1
View File
@@ -150,7 +150,9 @@ class ProfileFieldRestServlet(RestServlet):
await self.profile_handler.check_profile_query_allowed(user, requester_user)
if field_name == ProfileFields.DISPLAYNAME:
field_value: JsonValue = await self.profile_handler.get_displayname(user)
field_value: (
JsonValue | dict[str, JsonValue]
) = await self.profile_handler.get_displayname(user)
elif field_name == ProfileFields.AVATAR_URL:
field_value = await self.profile_handler.get_avatar_url(user)
else:
+19 -8
View File
@@ -265,7 +265,9 @@ class ProfileWorkerStore(SQLBaseStore):
desc="get_profile_avatar_url",
)
async def get_profile_field(self, user_id: UserID, field_name: str) -> JsonValue:
async def get_profile_field(
self, user_id: UserID, field_name: str
) -> JsonValue | dict[str, JsonValue]:
"""
Get a custom profile field for a user.
@@ -277,7 +279,9 @@ class ProfileWorkerStore(SQLBaseStore):
The string value if the field exists, otherwise raises 404.
"""
def get_profile_field(txn: LoggingTransaction) -> JsonValue:
def get_profile_field(
txn: LoggingTransaction,
) -> JsonValue | dict[str, JsonValue]:
# This will error if field_name has double quotes in it, but that's not
# possible due to the grammar.
field_path = f'$."{field_name}"'
@@ -295,7 +299,9 @@ class ProfileWorkerStore(SQLBaseStore):
# Test exists first since value being None is used for both
# missing and a null JSON value.
exists, value = cast(tuple[bool, JsonValue], txn.fetchone())
exists, value = cast(
tuple[bool, JsonValue | dict[str, JsonValue]], txn.fetchone()
)
if not exists:
raise StoreError(404, "No row found")
return value
@@ -312,7 +318,9 @@ class ProfileWorkerStore(SQLBaseStore):
)
# If value_type is None, then the value did not exist.
value_type, value = cast(tuple[str | None, JsonValue], txn.fetchone())
value_type, value = cast(
tuple[str | None, JsonValue | dict[str, JsonValue]], txn.fetchone()
)
if not value_type:
raise StoreError(404, "No row found")
# If value_type is object or array, then need to deserialize the JSON.
@@ -548,7 +556,7 @@ class ProfileWorkerStore(SQLBaseStore):
async def get_profile_data_for_users(
self, user_ids: Collection[str]
) -> dict[str, dict[str, str | JsonDict | None]]:
) -> dict[str, dict[str, JsonValue | dict[str, JsonValue]]]:
"""Fetch displayname/avatar_url/custom fields for a list of users.
Currently, this returns only local users as the `profiles` table only
@@ -571,7 +579,7 @@ class ProfileWorkerStore(SQLBaseStore):
desc="get_profile_data_for_users",
)
results: dict[str, dict[str, str | JsonDict | None]] = {}
results: dict[str, dict[str, JsonValue | dict[str, JsonValue]]] = {}
for full_user_id, displayname, avatar_url, fields in rows:
user_fields = fields or {}
# The SQLite driver doesn't automatically convert JSON to
@@ -708,7 +716,7 @@ class ProfileWorkerStore(SQLBaseStore):
txn: LoggingTransaction,
user_id: UserID,
new_field_name: str,
new_value: JsonValue,
new_value: JsonValue | dict[str, JsonValue],
) -> None:
# For each entry there are 4 quotes (2 each for key and value), 1 colon,
# and 1 comma.
@@ -835,7 +843,10 @@ class ProfileWorkerStore(SQLBaseStore):
)
async def set_profile_field(
self, user_id: UserID, field_name: str, new_value: JsonValue
self,
user_id: UserID,
field_name: str,
new_value: JsonValue | dict[str, JsonValue],
) -> None:
"""
Set a custom profile field for a user.