Coverage for dao/league_dao.py: 27.27%

97 statements  

« prev     ^ index     » next       coverage.py v7.10.6, created at 2026-04-13 11:37 +0000

1""" 

2League Data Access Object. 

3 

4Handles all database operations related to leagues and divisions including: 

5- League CRUD operations 

6- Division CRUD operations 

7- League-division associations 

8""" 

9 

10import structlog 

11 

12from dao.base_dao import BaseDAO, dao_cache, invalidates_cache 

13 

14logger = structlog.get_logger() 

15 

16# Cache patterns for invalidation 

17LEAGUES_CACHE_PATTERN = "mt:dao:leagues:*" 

18DIVISIONS_CACHE_PATTERN = "mt:dao:divisions:*" 

19 

20 

21class LeagueDAO(BaseDAO): 

22 """Data access object for league and division operations.""" 

23 

24 # === League Query Methods === 

25 

26 @dao_cache("leagues:all") 

27 def get_all_leagues(self) -> list[dict]: 

28 """Get all leagues ordered by name.""" 

29 try: 

30 response = self.client.table("leagues").select("*").order("name").execute() 

31 return response.data 

32 except Exception: 

33 logger.exception("Error querying leagues") 

34 return [] 

35 

36 @dao_cache("leagues:by_id:{league_id}") 

37 def get_league_by_id(self, league_id: int) -> dict | None: 

38 """Get league by ID.""" 

39 try: 

40 response = self.client.table("leagues").select("*").eq("id", league_id).execute() 

41 return response.data[0] if response.data else None 

42 except Exception: 

43 logger.exception("Error querying league", league_id=league_id) 

44 return None 

45 

46 # === League CRUD Methods === 

47 

48 @invalidates_cache(LEAGUES_CACHE_PATTERN) 

49 def create_league(self, league_data: dict) -> dict: 

50 """Create new league.""" 

51 try: 

52 response = self.client.table("leagues").insert(league_data).execute() 

53 return response.data[0] 

54 except Exception: 

55 logger.exception("Error creating league") 

56 raise 

57 

58 @invalidates_cache(LEAGUES_CACHE_PATTERN) 

59 def update_league(self, league_id: int, league_data: dict) -> dict: 

60 """Update league.""" 

61 try: 

62 response = self.client.table("leagues").update(league_data).eq("id", league_id).execute() 

63 return response.data[0] if response.data else None 

64 except Exception: 

65 logger.exception("Error updating league", league_id=league_id) 

66 raise 

67 

68 @invalidates_cache(LEAGUES_CACHE_PATTERN) 

69 def delete_league(self, league_id: int) -> bool: 

70 """Delete league (will fail if divisions exist due to FK constraint).""" 

71 try: 

72 self.client.table("leagues").delete().eq("id", league_id).execute() 

73 return True 

74 except Exception: 

75 logger.exception("Error deleting league", league_id=league_id) 

76 raise 

77 

78 # === Division Query Methods === 

79 

80 @dao_cache("divisions:all") 

81 def get_all_divisions(self) -> list[dict]: 

82 """Get all divisions with league info.""" 

83 try: 

84 response = ( 

85 self.client.table("divisions") 

86 .select("*, leagues!divisions_league_id_fkey(id, name, description, is_active)") 

87 .order("name") 

88 .execute() 

89 ) 

90 return response.data 

91 except Exception: 

92 logger.exception("Error querying divisions") 

93 return [] 

94 

95 @dao_cache("divisions:by_league:{league_id}") 

96 def get_divisions_by_league(self, league_id: int) -> list[dict]: 

97 """Get divisions filtered by league.""" 

98 try: 

99 response = ( 

100 self.client.table("divisions") 

101 .select("*, leagues!divisions_league_id_fkey(id, name, description)") 

102 .eq("league_id", league_id) 

103 .order("name") 

104 .execute() 

105 ) 

106 return response.data 

107 except Exception: 

108 logger.exception("Error querying divisions for league", league_id=league_id) 

109 return [] 

110 

111 def get_division_by_name(self, name: str) -> dict | None: 

112 """Get a division by name (case-insensitive exact match). 

113 

114 Returns the division record (id, name). 

115 For match-scraper integration, this helps look up divisions by name. 

116 No caching - used for scraper lookups. 

117 """ 

118 try: 

119 response = ( 

120 self.client.table("divisions") 

121 .select("id, name") 

122 .ilike("name", name) # Case-insensitive match 

123 .limit(1) 

124 .execute() 

125 ) 

126 

127 if response.data and len(response.data) > 0: 

128 return response.data[0] 

129 return None 

130 

131 except Exception: 

132 logger.exception("Error getting division by name", division_name=name) 

133 return None 

134 

135 # === Division CRUD Methods === 

136 

137 @invalidates_cache(DIVISIONS_CACHE_PATTERN) 

138 def create_division(self, division_data: dict) -> dict: 

139 """Create a new division. 

140 

141 Args: 

142 division_data: Dict with keys: name, description (optional), league_id (required) 

143 """ 

144 try: 

145 logger.debug("Creating division", division_data=division_data) 

146 result = self.client.table("divisions").insert(division_data).execute() 

147 logger.debug("Division created successfully", division=result.data[0]) 

148 return result.data[0] 

149 except Exception as e: 

150 logger.exception("Error creating division") 

151 raise e 

152 

153 @invalidates_cache(DIVISIONS_CACHE_PATTERN) 

154 def update_division(self, division_id: int, division_data: dict) -> dict | None: 

155 """Update a division. 

156 

157 Args: 

158 division_id: Division ID to update 

159 division_data: Dict with any of: name, description, league_id 

160 """ 

161 try: 

162 result = self.client.table("divisions").update(division_data).eq("id", division_id).execute() 

163 return result.data[0] if result.data else None 

164 except Exception as e: 

165 logger.exception("Error updating division") 

166 raise e 

167 

168 @invalidates_cache(DIVISIONS_CACHE_PATTERN) 

169 def delete_division(self, division_id: int) -> bool: 

170 """Delete a division.""" 

171 try: 

172 result = self.client.table("divisions").delete().eq("id", division_id).execute() 

173 return len(result.data) > 0 

174 except Exception as e: 

175 logger.exception("Error deleting division") 

176 raise e