Coverage for functions \ flipdare \ firestore \ context \ friend_context.py: 63%
116 statements
« prev ^ index » next coverage.py v7.13.0, created at 2026-05-08 12:22 +1000
« prev ^ index » next coverage.py v7.13.0, created at 2026-05-08 12:22 +1000
1#!/usr/bin/env python
2# Copyright (c) 2026 Flipdare Pty Ltd. All rights reserved.
3#
4# This file is part of Flipdare's proprietary software and contains
5# confidential and copyrighted material. Unauthorised copying,
6# modification, distribution, or use of this file is strictly
7# prohibited without prior written permission from Flipdare Pty Ltd.
8#
9# This software includes third-party components licensed under MIT,
10# BSD, and Apache 2.0 licences. See THIRD_PARTY_NOTICES for details.
11#
13from __future__ import annotations
15from typing import TYPE_CHECKING, Any, override
17from flipdare.app_log import LOG
18from flipdare.app_types import DatabaseDict
19from flipdare.firestore.context._model_context import ModelContext
20from flipdare.firestore.context._model_context_factory import ModelContextFactory
21from flipdare.generated.model.friend_model import FriendModel
22from flipdare.wrapper import FriendWrapper, UserWrapper
24if TYPE_CHECKING:
25 from flipdare.manager.db_manager import DbManager
27__all__ = ["FriendContext", "FriendContextFactory"]
30class FriendContextFactory(ModelContextFactory[FriendWrapper, "FriendContext"]):
31 """Factory for creating FriendContext instances from various input types."""
33 def __init__(self, db_manager: DbManager | None = None) -> None:
34 super().__init__(db_manager=db_manager)
36 @override
37 def create(self, obj: Any) -> FriendContext | None:
38 if isinstance(obj, FriendContext):
39 return obj
40 if isinstance(obj, FriendWrapper):
41 return self._from_model(obj)
42 if isinstance(obj, str):
43 return self._from_id(obj)
44 obj_data: DatabaseDict = obj # set explicity type for type checkers.
45 return self._from_data(obj_data)
47 @override
48 def _from_id(self, doc_id: str) -> FriendContext | None:
49 try:
50 friend = self.friend_db.get(doc_id)
51 if friend is None:
52 LOG().error(f"Friend {doc_id} not found in db.")
53 return None
54 return self._from_model(friend.model)
55 except Exception:
56 LOG().error(f"Error retrieving friend {doc_id} from db.")
57 return None
59 @override
60 def _from_data(self, data: DatabaseDict) -> FriendContext | None:
61 friend: FriendWrapper | None = None
62 try:
63 friend = FriendWrapper.from_dict(data)
64 except Exception:
65 return None
67 return self._from_model(friend)
69 @override
70 def _from_model(self, model: FriendWrapper | FriendModel) -> FriendContext | None:
71 from_uid = model.from_uid
72 to_uid = model.to_uid
74 model_id = model.doc_id if isinstance(model, FriendWrapper) else model.id
75 if model_id is None:
76 LOG().error(f"Friend model is missing doc_id: {model}")
77 return None
79 try:
80 try:
81 from_user = self.get_user(from_uid) # Returns UserWrapper | None
82 except Exception:
83 LOG().error(f"From user {from_uid} for friend {model_id} not found in db.")
84 from_user = None
85 try:
86 to_user = self.get_user(to_uid) # Returns UserWrapper | None
87 except Exception:
88 LOG().error(f"To user {to_uid} for friend {model_id} not found in db.")
89 to_user = None
91 # Wrap friend model
92 if isinstance(model, FriendModel):
93 model = FriendWrapper.from_model(model)
95 return FriendContext(friend=model, from_user=from_user, to_user=to_user)
96 except Exception:
97 return None
100class FriendContext(ModelContext):
101 """Context wrapping a friend relationship with both users."""
103 def __init__(
104 self,
105 friend: FriendWrapper,
106 from_user: UserWrapper | None = None,
107 to_user: UserWrapper | None = None,
108 ) -> None:
109 self._friend_model = friend
110 self._from_user = from_user
111 self._to_user = to_user
112 # Call super().__init__() LAST - it calls validate()
113 super().__init__()
115 @property
116 def from_user(self) -> UserWrapper:
117 self._require_valid("access from_user")
118 return self._from_user # type: ignore
120 @property
121 def from_id(self) -> str:
122 self._require_valid("access from_id")
123 assert self._from_user is not None # for mypy
124 return self._from_user.doc_id
126 @property
127 def to_user(self) -> UserWrapper:
128 self._require_valid("access to_user")
129 return self._to_user # type: ignore
131 @property
132 def to_id(self) -> str:
133 self._require_valid("access to_id")
134 assert self._to_user is not None # for mypy
135 return self._to_user.doc_id
137 @property
138 def friend(self) -> FriendWrapper:
139 self._require_valid("access friend_model")
140 return self._friend_model
142 @property
143 def friend_id(self) -> str:
144 self._require_valid("access friend_id")
145 assert self._friend_model is not None # for mypy
146 return self._friend_model.doc_id
148 @property
149 @override
150 def doc_id(self) -> str:
151 return self.friend.doc_id
153 @override
154 def validate(self) -> bool:
155 """Validate that all required models exist and have doc_ids."""
156 return (
157 self._is_model_valid(self._from_user)
158 and self._is_model_valid(self._to_user)
159 and self._is_model_valid(self._friend_model)
160 )
162 @property
163 @override
164 def _error_messages(self) -> list[str]:
165 """Build list of validation errors."""
166 errors: list[str] = []
167 if err := self._validate_model(self._from_user, "from_user"):
168 errors.append(err)
169 if err := self._validate_model(self._to_user, "to_user"):
170 errors.append(err)
171 if err := self._validate_model(self._friend_model, "friend_model"):
172 errors.append(err)
173 return errors