From 6c8bcf2dacd017d1f288c677d1bcef6cb6dadd87 Mon Sep 17 00:00:00 2001 From: dklimpel <5740567+dklimpel@users.noreply.github.com> Date: Sat, 12 Feb 2022 21:13:08 +0100 Subject: [PATCH 1/7] Add type hints to `synapse/storage/databases/main` --- mypy.ini | 3 - synapse/handlers/presence.py | 10 ++- synapse/storage/databases/main/presence.py | 62 +++++++++++++------ .../storage/databases/main/purge_events.py | 13 ++-- .../storage/databases/main/user_directory.py | 22 +++---- synapse/types.py | 6 +- 6 files changed, 73 insertions(+), 43 deletions(-) diff --git a/mypy.ini b/mypy.ini index 63848d664c57..610660b9b721 100644 --- a/mypy.ini +++ b/mypy.ini @@ -31,14 +31,11 @@ exclude = (?x) |synapse/storage/databases/main/group_server.py |synapse/storage/databases/main/metrics.py |synapse/storage/databases/main/monthly_active_users.py - |synapse/storage/databases/main/presence.py - |synapse/storage/databases/main/purge_events.py |synapse/storage/databases/main/push_rule.py |synapse/storage/databases/main/receipts.py |synapse/storage/databases/main/roommember.py |synapse/storage/databases/main/search.py |synapse/storage/databases/main/state.py - |synapse/storage/databases/main/user_directory.py |synapse/storage/schema/ |tests/api/test_auth.py diff --git a/synapse/handlers/presence.py b/synapse/handlers/presence.py index 067c43ae4714..47a6b02f1ea6 100644 --- a/synapse/handlers/presence.py +++ b/synapse/handlers/presence.py @@ -43,6 +43,7 @@ Tuple, Type, Union, + cast, ) from prometheus_client import Counter @@ -147,7 +148,10 @@ def __init__(self, hs: "HomeServer"): self._busy_presence_enabled = hs.config.experimental.msc3026_enabled active_presence = self.store.take_presence_startup_info() - self.user_to_current_state = {state.user_id: state for state in active_presence} + if active_presence: + self.user_to_current_state = { + state.user_id: state for state in active_presence + } @abc.abstractmethod async def user_syncing( @@ -224,7 +228,9 @@ async def current_state_for_users( states.update(new) self.user_to_current_state.update(new) - return states + # mypy does not realize that is Dict[str, UserPresenceState]] + # and not Dict[str, Optional[UserPresenceState]]] + return cast(Dict[str, UserPresenceState], states) @abc.abstractmethod async def set_state( diff --git a/synapse/storage/databases/main/presence.py b/synapse/storage/databases/main/presence.py index 4f05811a77eb..bd18c7429abe 100644 --- a/synapse/storage/databases/main/presence.py +++ b/synapse/storage/databases/main/presence.py @@ -12,15 +12,23 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import TYPE_CHECKING, Dict, Iterable, List, Tuple +from typing import TYPE_CHECKING, Dict, Iterable, List, Optional, Tuple, cast from synapse.api.presence import PresenceState, UserPresenceState from synapse.replication.tcp.streams import PresenceStream from synapse.storage._base import SQLBaseStore, make_in_list_sql_clause -from synapse.storage.database import DatabasePool, LoggingDatabaseConnection +from synapse.storage.database import ( + DatabasePool, + LoggingDatabaseConnection, + LoggingTransaction, +) from synapse.storage.engines import PostgresEngine from synapse.storage.types import Connection -from synapse.storage.util.id_generators import MultiWriterIdGenerator, StreamIdGenerator +from synapse.storage.util.id_generators import ( + AbstractStreamIdGenerator, + MultiWriterIdGenerator, + StreamIdGenerator, +) from synapse.util.caches.descriptors import cached, cachedList from synapse.util.caches.stream_change_cache import StreamChangeCache from synapse.util.iterutils import batch_iter @@ -35,7 +43,7 @@ def __init__( database: DatabasePool, db_conn: LoggingDatabaseConnection, hs: "HomeServer", - ): + ) -> None: super().__init__(database, db_conn, hs) # Used by `PresenceStore._get_active_presence()` @@ -54,19 +62,21 @@ def __init__( database: DatabasePool, db_conn: LoggingDatabaseConnection, hs: "HomeServer", - ): + ) -> None: super().__init__(database, db_conn, hs) self._can_persist_presence = ( hs.get_instance_name() in hs.config.worker.writers.presence ) + self._presence_id_gen: AbstractStreamIdGenerator + if isinstance(database.engine, PostgresEngine): self._presence_id_gen = MultiWriterIdGenerator( db_conn=db_conn, db=database, stream_name="presence_stream", - instance_name=self._instance_name, + instance_name=self._instance_name, # type: ignore[attr-defined] tables=[("presence_stream", "instance_name", "stream_id")], sequence_name="presence_stream_sequence", writers=hs.config.worker.writers.presence, @@ -109,7 +119,9 @@ async def update_presence(self, presence_states) -> Tuple[int, int]: return stream_orderings[-1], self._presence_id_gen.get_current_token() - def _update_presence_txn(self, txn, stream_orderings, presence_states): + def _update_presence_txn( + self, txn: LoggingTransaction, stream_orderings, presence_states + ) -> None: for stream_id, state in zip(stream_orderings, presence_states): txn.call_after( self.presence_stream_cache.entity_has_changed, state.user_id, stream_id @@ -150,7 +162,7 @@ def _update_presence_txn(self, txn, stream_orderings, presence_states): state.last_user_sync_ts, state.status_msg, state.currently_active, - self._instance_name, + self._instance_name, # type: ignore[attr-defined] ) for stream_id, state in zip(stream_orderings, presence_states) ], @@ -183,19 +195,23 @@ async def get_all_presence_updates( if last_id == current_id: return [], current_id, False - def get_all_presence_updates_txn(txn): + def get_all_presence_updates_txn( + txn: LoggingTransaction, + ) -> Tuple[List[Tuple[int, list]], int, bool]: sql = """ SELECT stream_id, user_id, state, last_active_ts, last_federation_update_ts, last_user_sync_ts, - status_msg, - currently_active + status_msg, currently_active FROM presence_stream WHERE ? < stream_id AND stream_id <= ? ORDER BY stream_id ASC LIMIT ? """ txn.execute(sql, (last_id, current_id, limit)) - updates = [(row[0], row[1:]) for row in txn] + updates = cast( + List[Tuple[int, list]], + [(row[0], row[1:]) for row in txn], + ) upper_bound = current_id limited = False @@ -210,7 +226,7 @@ def get_all_presence_updates_txn(txn): ) @cached() - def _get_presence_for_user(self, user_id): + def _get_presence_for_user(self, user_id: str) -> None: raise NotImplementedError() @cachedList( @@ -218,7 +234,9 @@ def _get_presence_for_user(self, user_id): list_name="user_ids", num_args=1, ) - async def get_presence_for_users(self, user_ids): + async def get_presence_for_users( + self, user_ids: Iterable[str] + ) -> Dict[str, UserPresenceState]: rows = await self.db_pool.simple_select_many_batch( table="presence_stream", column="user_id", @@ -257,7 +275,9 @@ async def should_user_receive_full_presence_with_token( True if the user should have full presence sent to them, False otherwise. """ - def _should_user_receive_full_presence_with_token_txn(txn): + def _should_user_receive_full_presence_with_token_txn( + txn: LoggingTransaction, + ) -> bool: sql = """ SELECT 1 FROM users_to_send_full_presence_to WHERE user_id = ? @@ -271,7 +291,7 @@ def _should_user_receive_full_presence_with_token_txn(txn): _should_user_receive_full_presence_with_token_txn, ) - async def add_users_to_send_full_presence_to(self, user_ids: Iterable[str]): + async def add_users_to_send_full_presence_to(self, user_ids: Iterable[str]) -> None: """Adds to the list of users who should receive a full snapshot of presence upon their next sync. @@ -353,10 +373,12 @@ async def get_presence_for_all_users( return users_to_state - def get_current_presence_token(self): + def get_current_presence_token(self) -> int: return self._presence_id_gen.get_current_token() - def _get_active_presence(self, db_conn: Connection): + def _get_active_presence( + self, db_conn: Connection + ) -> Optional[List[UserPresenceState]]: """Fetch non-offline presence from the database so that we can register the appropriate time outs. """ @@ -379,12 +401,12 @@ def _get_active_presence(self, db_conn: Connection): return [UserPresenceState(**row) for row in rows] - def take_presence_startup_info(self): + def take_presence_startup_info(self) -> Optional[List[UserPresenceState]]: active_on_startup = self._presence_on_startup self._presence_on_startup = None return active_on_startup - def process_replication_rows(self, stream_name, instance_name, token, rows): + def process_replication_rows(self, stream_name, instance_name, token, rows) -> None: if stream_name == PresenceStream.NAME: self._presence_id_gen.advance(instance_name, token) for row in rows: diff --git a/synapse/storage/databases/main/purge_events.py b/synapse/storage/databases/main/purge_events.py index e87a8fb85dce..2e3818e43244 100644 --- a/synapse/storage/databases/main/purge_events.py +++ b/synapse/storage/databases/main/purge_events.py @@ -13,9 +13,10 @@ # limitations under the License. import logging -from typing import Any, List, Set, Tuple +from typing import Any, List, Set, Tuple, cast from synapse.api.errors import SynapseError +from synapse.storage.database import LoggingTransaction from synapse.storage.databases.main import CacheInvalidationWorkerStore from synapse.storage.databases.main.state import StateGroupWorkerStore from synapse.types import RoomStreamToken @@ -55,7 +56,11 @@ async def purge_history( ) def _purge_history_txn( - self, txn, room_id: str, token: RoomStreamToken, delete_local_events: bool + self, + txn: LoggingTransaction, + room_id: str, + token: RoomStreamToken, + delete_local_events: bool, ) -> Set[int]: # Tables that should be pruned: # event_auth @@ -273,7 +278,7 @@ def _purge_history_txn( """, (room_id,), ) - (min_depth,) = txn.fetchone() + (min_depth,) = cast(Tuple[int], txn.fetchone()) logger.info("[purge] updating room_depth to %d", min_depth) @@ -318,7 +323,7 @@ async def purge_room(self, room_id: str) -> List[int]: "purge_room", self._purge_room_txn, room_id ) - def _purge_room_txn(self, txn, room_id: str) -> List[int]: + def _purge_room_txn(self, txn: LoggingTransaction, room_id: str) -> List[int]: # First we fetch all the state groups that should be deleted, before # we delete that information. txn.execute( diff --git a/synapse/storage/databases/main/user_directory.py b/synapse/storage/databases/main/user_directory.py index f7c778bdf22b..e7fddd24262a 100644 --- a/synapse/storage/databases/main/user_directory.py +++ b/synapse/storage/databases/main/user_directory.py @@ -58,7 +58,7 @@ def __init__( database: DatabasePool, db_conn: LoggingDatabaseConnection, hs: "HomeServer", - ): + ) -> None: super().__init__(database, db_conn, hs) self.server_name = hs.hostname @@ -234,10 +234,10 @@ def _get_next_batch( processed_event_count = 0 for room_id, event_count in rooms_to_work_on: - is_in_room = await self.is_host_joined(room_id, self.server_name) + is_in_room = await self.is_host_joined(room_id, self.server_name) # type: ignore[attr-defined] if is_in_room: - users_with_profile = await self.get_users_in_room_with_profiles(room_id) + users_with_profile = await self.get_users_in_room_with_profiles(room_id) # type: ignore[attr-defined] # Throw away users excluded from the directory. users_with_profile = { user_id: profile @@ -368,7 +368,7 @@ def _get_next_batch(txn: LoggingTransaction) -> Optional[List[str]]: for user_id in users_to_work_on: if await self.should_include_local_user_in_dir(user_id): - profile = await self.get_profileinfo(get_localpart_from_id(user_id)) + profile = await self.get_profileinfo(get_localpart_from_id(user_id)) # type: ignore[attr-defined] await self.update_profile_in_user_dir( user_id, profile.display_name, profile.avatar_url ) @@ -397,7 +397,7 @@ async def should_include_local_user_in_dir(self, user: str) -> bool: # technically it could be DM-able. In the future, this could potentially # be configurable per-appservice whether the appservice sender can be # contacted. - if self.get_app_service_by_user_id(user) is not None: + if self.get_app_service_by_user_id(user) is not None: # type: ignore[attr-defined] return False # We're opting to exclude appservice users (anyone matching the user @@ -405,17 +405,17 @@ async def should_include_local_user_in_dir(self, user: str) -> bool: # they could be DM-able. In the future, this could potentially # be configurable per-appservice whether the appservice users can be # contacted. - if self.get_if_app_services_interested_in_user(user): + if self.get_if_app_services_interested_in_user(user): # type: ignore[attr-defined] # TODO we might want to make this configurable for each app service return False # Support users are for diagnostics and should not appear in the user directory. - if await self.is_support_user(user): + if await self.is_support_user(user): # type: ignore[attr-defined] return False # Deactivated users aren't contactable, so should not appear in the user directory. try: - if await self.get_user_deactivated_status(user): + if await self.get_user_deactivated_status(user): # type: ignore[attr-defined] return False except StoreError: # No such user in the users table. No need to do this when calling @@ -433,20 +433,20 @@ async def is_room_world_readable_or_publicly_joinable(self, room_id: str) -> boo (EventTypes.RoomHistoryVisibility, ""), ) - current_state_ids = await self.get_filtered_current_state_ids( + current_state_ids = await self.get_filtered_current_state_ids( # type: ignore[attr-defined] room_id, StateFilter.from_types(types_to_filter) ) join_rules_id = current_state_ids.get((EventTypes.JoinRules, "")) if join_rules_id: - join_rule_ev = await self.get_event(join_rules_id, allow_none=True) + join_rule_ev = await self.get_event(join_rules_id, allow_none=True) # type: ignore[attr-defined] if join_rule_ev: if join_rule_ev.content.get("join_rule") == JoinRules.PUBLIC: return True hist_vis_id = current_state_ids.get((EventTypes.RoomHistoryVisibility, "")) if hist_vis_id: - hist_vis_ev = await self.get_event(hist_vis_id, allow_none=True) + hist_vis_ev = await self.get_event(hist_vis_id, allow_none=True) # type: ignore[attr-defined] if hist_vis_ev: if ( hist_vis_ev.content.get("history_visibility") diff --git a/synapse/types.py b/synapse/types.py index f89fb216a656..53be3583a013 100644 --- a/synapse/types.py +++ b/synapse/types.py @@ -51,7 +51,7 @@ if TYPE_CHECKING: from synapse.appservice.api import ApplicationService - from synapse.storage.databases.main import DataStore + from synapse.storage.databases.main import DataStore, PurgeEventsStore # Define a state map type from type/state_key to T (usually an event ID or # event) @@ -485,7 +485,7 @@ def __attrs_post_init__(self) -> None: ) @classmethod - async def parse(cls, store: "DataStore", string: str) -> "RoomStreamToken": + async def parse(cls, store: "PurgeEventsStore", string: str) -> "RoomStreamToken": try: if string[0] == "s": return cls(topological=None, stream=int(string[1:])) @@ -502,7 +502,7 @@ async def parse(cls, store: "DataStore", string: str) -> "RoomStreamToken": instance_id = int(key) pos = int(value) - instance_name = await store.get_name_from_instance_id(instance_id) + instance_name = await store.get_name_from_instance_id(instance_id) # type: ignore[attr-defined] instance_map[instance_name] = pos return cls( From 43846dca0729aaed552b2a596c9beebeba8a3318 Mon Sep 17 00:00:00 2001 From: dklimpel <5740567+dklimpel@users.noreply.github.com> Date: Sat, 12 Feb 2022 21:18:19 +0100 Subject: [PATCH 2/7] newsfile --- changelog.d/11984.misc | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/11984.misc diff --git a/changelog.d/11984.misc b/changelog.d/11984.misc new file mode 100644 index 000000000000..8e405b922674 --- /dev/null +++ b/changelog.d/11984.misc @@ -0,0 +1 @@ +Add missing type hints to storage classes. \ No newline at end of file From e3c90b77e5134aa199d444b65b78017ac26c6e73 Mon Sep 17 00:00:00 2001 From: dklimpel <5740567+dklimpel@users.noreply.github.com> Date: Sat, 12 Feb 2022 22:34:31 +0100 Subject: [PATCH 3/7] try to fix errors --- synapse/handlers/presence.py | 5 +---- synapse/storage/databases/main/presence.py | 10 ++++------ 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/synapse/handlers/presence.py b/synapse/handlers/presence.py index 47a6b02f1ea6..f01f64b2f75e 100644 --- a/synapse/handlers/presence.py +++ b/synapse/handlers/presence.py @@ -148,10 +148,7 @@ def __init__(self, hs: "HomeServer"): self._busy_presence_enabled = hs.config.experimental.msc3026_enabled active_presence = self.store.take_presence_startup_info() - if active_presence: - self.user_to_current_state = { - state.user_id: state for state in active_presence - } + self.user_to_current_state = {state.user_id: state for state in active_presence} @abc.abstractmethod async def user_syncing( diff --git a/synapse/storage/databases/main/presence.py b/synapse/storage/databases/main/presence.py index bd18c7429abe..12dcb5038995 100644 --- a/synapse/storage/databases/main/presence.py +++ b/synapse/storage/databases/main/presence.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import TYPE_CHECKING, Dict, Iterable, List, Optional, Tuple, cast +from typing import TYPE_CHECKING, Dict, Iterable, List, Tuple, cast from synapse.api.presence import PresenceState, UserPresenceState from synapse.replication.tcp.streams import PresenceStream @@ -376,9 +376,7 @@ async def get_presence_for_all_users( def get_current_presence_token(self) -> int: return self._presence_id_gen.get_current_token() - def _get_active_presence( - self, db_conn: Connection - ) -> Optional[List[UserPresenceState]]: + def _get_active_presence(self, db_conn: Connection) -> List[UserPresenceState]: """Fetch non-offline presence from the database so that we can register the appropriate time outs. """ @@ -401,9 +399,9 @@ def _get_active_presence( return [UserPresenceState(**row) for row in rows] - def take_presence_startup_info(self) -> Optional[List[UserPresenceState]]: + def take_presence_startup_info(self) -> List[UserPresenceState]: active_on_startup = self._presence_on_startup - self._presence_on_startup = None + self._presence_on_startup = [] return active_on_startup def process_replication_rows(self, stream_name, instance_name, token, rows) -> None: From 0c8d4af10af505eef1a5abdc1868cdd148ffd8d0 Mon Sep 17 00:00:00 2001 From: dklimpel <5740567+dklimpel@users.noreply.github.com> Date: Tue, 15 Feb 2022 16:51:22 +0100 Subject: [PATCH 4/7] rework `self._instance_name` --- synapse/storage/databases/main/presence.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/synapse/storage/databases/main/presence.py b/synapse/storage/databases/main/presence.py index 12dcb5038995..70c1257658d1 100644 --- a/synapse/storage/databases/main/presence.py +++ b/synapse/storage/databases/main/presence.py @@ -64,9 +64,10 @@ def __init__( hs: "HomeServer", ) -> None: super().__init__(database, db_conn, hs) + self._instance_name = hs.get_instance_name() self._can_persist_presence = ( - hs.get_instance_name() in hs.config.worker.writers.presence + self._instance_name in hs.config.worker.writers.presence ) self._presence_id_gen: AbstractStreamIdGenerator @@ -76,7 +77,7 @@ def __init__( db_conn=db_conn, db=database, stream_name="presence_stream", - instance_name=self._instance_name, # type: ignore[attr-defined] + instance_name=self._instance_name, tables=[("presence_stream", "instance_name", "stream_id")], sequence_name="presence_stream_sequence", writers=hs.config.worker.writers.presence, @@ -162,7 +163,7 @@ def _update_presence_txn( state.last_user_sync_ts, state.status_msg, state.currently_active, - self._instance_name, # type: ignore[attr-defined] + self._instance_name, ) for stream_id, state in zip(stream_orderings, presence_states) ], From bf4384c591d7c642a49646314b727e049f82937b Mon Sep 17 00:00:00 2001 From: dklimpel <5740567+dklimpel@users.noreply.github.com> Date: Wed, 16 Feb 2022 08:24:36 +0100 Subject: [PATCH 5/7] rework `current_state_for_users` --- synapse/handlers/presence.py | 33 +++++++++++----------- synapse/storage/databases/main/presence.py | 4 +-- 2 files changed, 18 insertions(+), 19 deletions(-) diff --git a/synapse/handlers/presence.py b/synapse/handlers/presence.py index f01f64b2f75e..0101474678cc 100644 --- a/synapse/handlers/presence.py +++ b/synapse/handlers/presence.py @@ -43,7 +43,6 @@ Tuple, Type, Union, - cast, ) from prometheus_client import Counter @@ -205,29 +204,29 @@ async def current_state_for_users( Returns: dict: `user_id` -> `UserPresenceState` """ - states = { - user_id: self.user_to_current_state.get(user_id, None) - for user_id in user_ids - } + states = {} + missing = [] + for user_id in user_ids: + state = self.user_to_current_state.get(user_id, None) + if state: + states[user_id] = state + else: + missing.append(user_id) - missing = [user_id for user_id, state in states.items() if not state] if missing: # There are things not in our in memory cache. Lets pull them out of # the database. res = await self.store.get_presence_for_users(missing) states.update(res) - missing = [user_id for user_id, state in states.items() if not state] - if missing: - new = { - user_id: UserPresenceState.default(user_id) for user_id in missing - } - states.update(new) - self.user_to_current_state.update(new) - - # mypy does not realize that is Dict[str, UserPresenceState]] - # and not Dict[str, Optional[UserPresenceState]]] - return cast(Dict[str, UserPresenceState], states) + for user_id in missing: + # if user has no state in database, create the state + if not res.get(user_id, None): + new = {user_id: UserPresenceState.default(user_id)} + states.update(new) + self.user_to_current_state.update(new) + + return states @abc.abstractmethod async def set_state( diff --git a/synapse/storage/databases/main/presence.py b/synapse/storage/databases/main/presence.py index 70c1257658d1..d3c4611686f8 100644 --- a/synapse/storage/databases/main/presence.py +++ b/synapse/storage/databases/main/presence.py @@ -64,14 +64,14 @@ def __init__( hs: "HomeServer", ) -> None: super().__init__(database, db_conn, hs) + self._instance_name = hs.get_instance_name() + self._presence_id_gen: AbstractStreamIdGenerator self._can_persist_presence = ( self._instance_name in hs.config.worker.writers.presence ) - self._presence_id_gen: AbstractStreamIdGenerator - if isinstance(database.engine, PostgresEngine): self._presence_id_gen = MultiWriterIdGenerator( db_conn=db_conn, From 46be186c162136f5774aa2ab2b49954210587345 Mon Sep 17 00:00:00 2001 From: Dirk Klimpel <5740567+dklimpel@users.noreply.github.com> Date: Mon, 21 Feb 2022 14:41:01 +0100 Subject: [PATCH 6/7] Apply suggestions from code review Co-authored-by: Erik Johnston --- synapse/handlers/presence.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/synapse/handlers/presence.py b/synapse/handlers/presence.py index 0101474678cc..63640922f7b1 100644 --- a/synapse/handlers/presence.py +++ b/synapse/handlers/presence.py @@ -221,10 +221,10 @@ async def current_state_for_users( for user_id in missing: # if user has no state in database, create the state - if not res.get(user_id, None): - new = {user_id: UserPresenceState.default(user_id)} - states.update(new) - self.user_to_current_state.update(new) + if user_id not in res: + new_state = UserPresenceState.default(user_id) + states[user_id] = new_state + self.user_to_current_state[user_id] = new_state return states From 10ef20348c69c5eec56041b505c9652a5701bbcb Mon Sep 17 00:00:00 2001 From: Dirk Klimpel <5740567+dklimpel@users.noreply.github.com> Date: Mon, 21 Feb 2022 16:31:41 +0100 Subject: [PATCH 7/7] Apply suggestions from code review --- synapse/handlers/presence.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/synapse/handlers/presence.py b/synapse/handlers/presence.py index 63640922f7b1..b223b72623e8 100644 --- a/synapse/handlers/presence.py +++ b/synapse/handlers/presence.py @@ -221,7 +221,7 @@ async def current_state_for_users( for user_id in missing: # if user has no state in database, create the state - if user_id not in res: + if not res.get(user_id, None): new_state = UserPresenceState.default(user_id) states[user_id] = new_state self.user_to_current_state[user_id] = new_state