Coverage for functions \ flipdare \ firestore \ context \ _model_context_factory.py: 82%

50 statements  

« 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# 

12 

13""" 

14Base classes for model contexts and their factories. 

15 

16Provides common validation, error handling, and factory patterns for 

17context objects that wrap multiple related models (e.g., Friend + Users, 

18Dare + Users, Group + Members). 

19 

20All models are wrapped in PersistedWrapper to guarantee doc_id exists, 

21eliminating the need for doc_id validation checks. 

22""" 

23 

24from __future__ import annotations 

25 

26from abc import ABC, abstractmethod 

27from typing import TYPE_CHECKING, Any 

28 

29from flipdare.app_types import DatabaseDict 

30from flipdare.firestore import DareDb, FlagDb, FriendDb, GroupDb, UserDb 

31from flipdare.firestore.context._model_context import ModelContext 

32 

33from flipdare.wrapper import GroupWrapper, PersistedWrapper, UserWrapper 

34 

35__all__ = ["ModelContextFactory"] 

36 

37if TYPE_CHECKING: 

38 from flipdare.manager.db_manager import DbManager 

39 

40 

41# TModel is bound to BaseWrapper - contexts will store PersistedWrapper[BaseWrapper] 

42class ModelContextFactory[TModel: PersistedWrapper[Any], TContext: ModelContext](ABC): 

43 """ 

44 Abstract base factory for creating context objects from various input types. 

45 

46 Subclasses should implement factory methods to create contexts from: 

47 - Model instances 

48 - Document IDs (strings) 

49 - Raw data dictionaries 

50 - Existing context objects 

51 """ 

52 

53 def __init__(self, db_manager: DbManager | None = None) -> None: 

54 self._db_manager = db_manager 

55 

56 @abstractmethod 

57 def create(self, obj: Any) -> TContext | None: 

58 """ 

59 Polymorphic factory method to create context from various types. 

60 

61 Args: 

62 obj: Can be a model instance, doc ID, dict, or existing context 

63 

64 Returns: 

65 Context instance or None if creation fails 

66 

67 """ 

68 ... 

69 

70 @abstractmethod 

71 def _from_id(self, doc_id: str) -> TContext | None: 

72 """Create context from document ID. Override in subclass if needed.""" 

73 ... 

74 

75 @abstractmethod 

76 def _from_data(self, data: DatabaseDict) -> TContext | None: 

77 """Create context from raw data dict. Override in subclass if needed.""" 

78 ... 

79 

80 @abstractmethod 

81 def _from_model(self, model: TModel) -> TContext | None: 

82 """Create context from raw model instance. Override in subclass if needed.""" 

83 ... 

84 

85 @property 

86 def db_manager(self) -> DbManager: 

87 from flipdare.services import get_db_manager 

88 

89 if self._db_manager is None: 

90 self._db_manager = get_db_manager() 

91 return self._db_manager 

92 

93 @property 

94 def dare_db(self) -> DareDb: 

95 return self.db_manager.dare_db 

96 

97 @property 

98 def user_db(self) -> UserDb: 

99 return self.db_manager.user_db 

100 

101 @property 

102 def friend_db(self) -> FriendDb: 

103 return self.db_manager.friend_db 

104 

105 @property 

106 def group_db(self) -> GroupDb: 

107 return self.db_manager.group_db 

108 

109 @property 

110 def flag_db(self) -> FlagDb: 

111 return self.db_manager.flag_db 

112 

113 def get_user(self, user_id: str) -> UserWrapper | None: 

114 """Get user by ID, returns UserWrapper or None.""" 

115 try: 

116 # user_db.get() already returns PersistedWrapper[UserWrapper] 

117 return self.user_db.get(user_id) 

118 except Exception: 

119 return None 

120 

121 def get_group(self, gid: str) -> GroupWrapper | None: 

122 """Get group by ID, returns GroupWrapper or None.""" 

123 try: 

124 # group_db.get() already returns PersistedWrapper[GroupWrapper] 

125 return self.group_db.get(gid) 

126 except Exception: 

127 return None