Coverage for dao/league_dao.py: 27.27%
97 statements
« prev ^ index » next coverage.py v7.10.6, created at 2026-04-13 14:26 +0000
« prev ^ index » next coverage.py v7.10.6, created at 2026-04-13 14:26 +0000
1"""
2League Data Access Object.
4Handles all database operations related to leagues and divisions including:
5- League CRUD operations
6- Division CRUD operations
7- League-division associations
8"""
10import structlog
12from dao.base_dao import BaseDAO, dao_cache, invalidates_cache
14logger = structlog.get_logger()
16# Cache patterns for invalidation
17LEAGUES_CACHE_PATTERN = "mt:dao:leagues:*"
18DIVISIONS_CACHE_PATTERN = "mt:dao:divisions:*"
21class LeagueDAO(BaseDAO):
22 """Data access object for league and division operations."""
24 # === League Query Methods ===
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 []
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
46 # === League CRUD Methods ===
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
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
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
78 # === Division Query Methods ===
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 []
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 []
111 def get_division_by_name(self, name: str) -> dict | None:
112 """Get a division by name (case-insensitive exact match).
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 )
127 if response.data and len(response.data) > 0:
128 return response.data[0]
129 return None
131 except Exception:
132 logger.exception("Error getting division by name", division_name=name)
133 return None
135 # === Division CRUD Methods ===
137 @invalidates_cache(DIVISIONS_CACHE_PATTERN)
138 def create_division(self, division_data: dict) -> dict:
139 """Create a new division.
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
153 @invalidates_cache(DIVISIONS_CACHE_PATTERN)
154 def update_division(self, division_id: int, division_data: dict) -> dict | None:
155 """Update a division.
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
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