Coverage for opt/mealie/lib/python3.12/site-packages/mealie/repos/repository_factory.py: 100%

184 statements  

« prev     ^ index     » next       coverage.py v7.15.2, created at 2026-10-07 03:04 +0000

1from collections.abc import Sequence 

2from functools import cached_property 

3 

4from pydantic import UUID4 

5from sqlalchemy import select 

6from sqlalchemy.orm import Session 

7 

8from mealie.db.models.group import Group, ReportEntryModel, ReportModel 

9from mealie.db.models.group.exports import GroupDataExportsModel 

10from mealie.db.models.group.preferences import GroupPreferencesModel 

11from mealie.db.models.household.cookbook import CookBook 

12from mealie.db.models.household.events import GroupEventNotifierModel 

13from mealie.db.models.household.household import Household 

14from mealie.db.models.household.household_to_recipe import HouseholdToRecipe 

15from mealie.db.models.household.invite_tokens import GroupInviteToken 

16from mealie.db.models.household.mealplan import GroupMealPlan, GroupMealPlanRules 

17from mealie.db.models.household.preferences import HouseholdPreferencesModel 

18from mealie.db.models.household.recipe_action import GroupRecipeAction 

19from mealie.db.models.household.shopping_list import ( 

20 ShoppingList, 

21 ShoppingListItem, 

22 ShoppingListItemRecipeReference, 

23 ShoppingListMultiPurposeLabel, 

24 ShoppingListRecipeReference, 

25) 

26from mealie.db.models.household.webhooks import GroupWebhooksModel 

27from mealie.db.models.labels import MultiPurposeLabel 

28from mealie.db.models.recipe.category import Category 

29from mealie.db.models.recipe.comment import RecipeComment 

30from mealie.db.models.recipe.ingredient import IngredientFoodModel, IngredientUnitModel 

31from mealie.db.models.recipe.recipe import RecipeModel 

32from mealie.db.models.recipe.recipe_timeline import RecipeTimelineEvent 

33from mealie.db.models.recipe.shared import RecipeShareTokenModel 

34from mealie.db.models.recipe.tag import Tag 

35from mealie.db.models.recipe.tool import Tool 

36from mealie.db.models.users import LongLiveToken, User 

37from mealie.db.models.users.password_reset import PasswordResetModel 

38from mealie.db.models.users.user_to_recipe import UserToRecipe 

39from mealie.repos.repository_cookbooks import RepositoryCookbooks 

40from mealie.repos.repository_foods import RepositoryFood 

41from mealie.repos.repository_household import RepositoryHousehold, RepositoryHouseholdRecipes 

42from mealie.repos.repository_meal_plan_rules import RepositoryMealPlanRules 

43from mealie.repos.repository_units import RepositoryUnit 

44from mealie.schema.cookbook.cookbook import ReadCookBook 

45from mealie.schema.group.group_exports import GroupDataExport 

46from mealie.schema.group.group_preferences import ReadGroupPreferences 

47from mealie.schema.household.group_events import GroupEventNotifierOut 

48from mealie.schema.household.group_recipe_action import GroupRecipeActionOut 

49from mealie.schema.household.group_shopping_list import ( 

50 ShoppingListItemOut, 

51 ShoppingListItemRecipeRefOut, 

52 ShoppingListMultiPurposeLabelOut, 

53 ShoppingListOut, 

54 ShoppingListRecipeRefOut, 

55) 

56from mealie.schema.household.household import HouseholdInDB, HouseholdRecipeOut 

57from mealie.schema.household.household_preferences import ReadHouseholdPreferences 

58from mealie.schema.household.invite_token import ReadInviteToken 

59from mealie.schema.household.webhook import ReadWebhook 

60from mealie.schema.labels import MultiPurposeLabelOut 

61from mealie.schema.meal_plan.new_meal import ReadPlanEntry 

62from mealie.schema.meal_plan.plan_rules import PlanRulesOut 

63from mealie.schema.recipe import Recipe, RecipeCommentOut, RecipeToolOut 

64from mealie.schema.recipe.recipe_category import CategoryOut, TagOut 

65from mealie.schema.recipe.recipe_ingredient import IngredientFood, IngredientUnit 

66from mealie.schema.recipe.recipe_share_token import RecipeShareToken 

67from mealie.schema.recipe.recipe_timeline_events import RecipeTimelineEventOut 

68from mealie.schema.reports.reports import ReportEntryOut, ReportOut 

69from mealie.schema.user import GroupInDB, LongLiveTokenInDB, PrivateUser 

70from mealie.schema.user.user import UserRatingOut 

71from mealie.schema.user.user_passwords import PrivatePasswordResetToken 

72 

73from ._utils import NOT_SET, NotSet 

74from .repository_generic import GroupRepositoryGeneric, HouseholdRepositoryGeneric 

75from .repository_group import RepositoryGroup 

76from .repository_meals import RepositoryMeals 

77from .repository_recipes import RepositoryRecipes 

78from .repository_shopping_list import RepositoryShoppingList 

79from .repository_users import RepositoryUserRatings, RepositoryUsers 

80 

81PK_ID = "id" 

82PK_SLUG = "slug" 

83PK_TOKEN = "token" 

84PK_GROUP_ID = "group_id" 

85PK_HOUSEHOLD_ID = "household_id" 

86 

87 

88class RepositoryCategories(GroupRepositoryGeneric[CategoryOut, Category]): 

89 def get_empty(self) -> Sequence[Category]: 

90 stmt = select(Category).filter(~Category.recipes.any()) 

91 

92 return self.session.execute(stmt).scalars().all() 

93 

94 

95class RepositoryTags(GroupRepositoryGeneric[TagOut, Tag]): 

96 def get_empty(self) -> Sequence[Tag]: 

97 stmt = select(Tag).filter(~Tag.recipes.any()) 

98 return self.session.execute(stmt).scalars().all() 

99 

100 

101class AllRepositories: 

102 """ 

103 `AllRepositories` class is the data access layer for all database actions within 

104 Mealie. Database uses composition from classes derived from AccessModel. These 

105 can be substantiated from the AccessModel class or through inheritance when 

106 additional methods are required. 

107 """ 

108 

109 def __init__( 

110 self, 

111 session: Session, 

112 *, 

113 group_id: UUID4 | None | NotSet = NOT_SET, 

114 household_id: UUID4 | None | NotSet = NOT_SET, 

115 ) -> None: 

116 self.session = session 

117 self.group_id = group_id 

118 self.household_id = household_id 

119 

120 # ================================================================ 

121 # Recipe 

122 

123 @cached_property 

124 def recipes(self) -> RepositoryRecipes: 

125 return RepositoryRecipes( 

126 self.session, PK_SLUG, RecipeModel, Recipe, group_id=self.group_id, household_id=self.household_id 

127 ) 

128 

129 @cached_property 

130 def ingredient_foods(self) -> RepositoryFood: 

131 return RepositoryFood(self.session, PK_ID, IngredientFoodModel, IngredientFood, group_id=self.group_id) 

132 

133 @cached_property 

134 def ingredient_units(self) -> RepositoryUnit: 

135 return RepositoryUnit(self.session, PK_ID, IngredientUnitModel, IngredientUnit, group_id=self.group_id) 

136 

137 @cached_property 

138 def tools(self) -> GroupRepositoryGeneric[RecipeToolOut, Tool]: 

139 return GroupRepositoryGeneric(self.session, PK_ID, Tool, RecipeToolOut, group_id=self.group_id) 

140 

141 @cached_property 

142 def comments(self) -> GroupRepositoryGeneric[RecipeCommentOut, RecipeComment]: 

143 # Since users can comment on recipes that belong to other households, 

144 # this is a group repository, rather than a household repository. 

145 return GroupRepositoryGeneric(self.session, PK_ID, RecipeComment, RecipeCommentOut, group_id=self.group_id) 

146 

147 @cached_property 

148 def categories(self) -> RepositoryCategories: 

149 return RepositoryCategories(self.session, PK_ID, Category, CategoryOut, group_id=self.group_id) 

150 

151 @cached_property 

152 def tags(self) -> RepositoryTags: 

153 return RepositoryTags(self.session, PK_ID, Tag, TagOut, group_id=self.group_id) 

154 

155 @cached_property 

156 def recipe_share_tokens(self) -> GroupRepositoryGeneric[RecipeShareToken, RecipeShareTokenModel]: 

157 return GroupRepositoryGeneric( 

158 self.session, PK_ID, RecipeShareTokenModel, RecipeShareToken, group_id=self.group_id 

159 ) 

160 

161 @cached_property 

162 def recipe_timeline_events(self) -> GroupRepositoryGeneric[RecipeTimelineEventOut, RecipeTimelineEvent]: 

163 # Since users can post events on recipes that belong to other households, 

164 # this is a group repository, rather than a household repository. 

165 return GroupRepositoryGeneric( 

166 self.session, PK_ID, RecipeTimelineEvent, RecipeTimelineEventOut, group_id=self.group_id 

167 ) 

168 

169 # ================================================================ 

170 # User 

171 

172 @cached_property 

173 def users(self) -> RepositoryUsers: 

174 return RepositoryUsers(self.session, PK_ID, User, PrivateUser, group_id=self.group_id) 

175 

176 @cached_property 

177 def user_ratings(self) -> RepositoryUserRatings: 

178 return RepositoryUserRatings(self.session, PK_ID, UserToRecipe, UserRatingOut, group_id=self.group_id) 

179 

180 @cached_property 

181 def api_tokens(self) -> GroupRepositoryGeneric[LongLiveTokenInDB, LongLiveToken]: 

182 return GroupRepositoryGeneric(self.session, PK_ID, LongLiveToken, LongLiveTokenInDB, group_id=self.group_id) 

183 

184 @cached_property 

185 def tokens_pw_reset(self) -> GroupRepositoryGeneric[PrivatePasswordResetToken, PasswordResetModel]: 

186 return GroupRepositoryGeneric( 

187 self.session, PK_TOKEN, PasswordResetModel, PrivatePasswordResetToken, group_id=self.group_id 

188 ) 

189 

190 # ================================================================ 

191 # Group 

192 

193 @cached_property 

194 def groups(self) -> RepositoryGroup: 

195 return RepositoryGroup(self.session, PK_ID, Group, GroupInDB) 

196 

197 @cached_property 

198 def group_preferences(self) -> GroupRepositoryGeneric[ReadGroupPreferences, GroupPreferencesModel]: 

199 return GroupRepositoryGeneric( 

200 self.session, PK_GROUP_ID, GroupPreferencesModel, ReadGroupPreferences, group_id=self.group_id 

201 ) 

202 

203 @cached_property 

204 def group_exports(self) -> GroupRepositoryGeneric[GroupDataExport, GroupDataExportsModel]: 

205 return GroupRepositoryGeneric( 

206 self.session, PK_ID, GroupDataExportsModel, GroupDataExport, group_id=self.group_id 

207 ) 

208 

209 @cached_property 

210 def group_reports(self) -> GroupRepositoryGeneric[ReportOut, ReportModel]: 

211 return GroupRepositoryGeneric(self.session, PK_ID, ReportModel, ReportOut, group_id=self.group_id) 

212 

213 @cached_property 

214 def group_report_entries(self) -> GroupRepositoryGeneric[ReportEntryOut, ReportEntryModel]: 

215 return GroupRepositoryGeneric(self.session, PK_ID, ReportEntryModel, ReportEntryOut, group_id=self.group_id) 

216 

217 # ================================================================ 

218 # Household 

219 

220 @cached_property 

221 def households(self) -> RepositoryHousehold: 

222 return RepositoryHousehold(self.session, PK_ID, Household, HouseholdInDB, group_id=self.group_id) 

223 

224 @cached_property 

225 def household_preferences(self) -> HouseholdRepositoryGeneric[ReadHouseholdPreferences, HouseholdPreferencesModel]: 

226 return HouseholdRepositoryGeneric( 

227 self.session, 

228 PK_HOUSEHOLD_ID, 

229 HouseholdPreferencesModel, 

230 ReadHouseholdPreferences, 

231 group_id=self.group_id, 

232 household_id=self.household_id, 

233 ) 

234 

235 @cached_property 

236 def household_recipes(self) -> RepositoryHouseholdRecipes: 

237 return RepositoryHouseholdRecipes( 

238 self.session, 

239 PK_ID, 

240 HouseholdToRecipe, 

241 HouseholdRecipeOut, 

242 group_id=self.group_id, 

243 household_id=self.household_id, 

244 ) 

245 

246 @cached_property 

247 def cookbooks(self) -> RepositoryCookbooks: 

248 return RepositoryCookbooks( 

249 self.session, PK_ID, CookBook, ReadCookBook, group_id=self.group_id, household_id=self.household_id 

250 ) 

251 

252 @cached_property 

253 def group_invite_tokens(self) -> HouseholdRepositoryGeneric[ReadInviteToken, GroupInviteToken]: 

254 return HouseholdRepositoryGeneric( 

255 self.session, 

256 PK_TOKEN, 

257 GroupInviteToken, 

258 ReadInviteToken, 

259 group_id=self.group_id, 

260 household_id=self.household_id, 

261 ) 

262 

263 @cached_property 

264 def group_recipe_actions(self) -> HouseholdRepositoryGeneric[GroupRecipeActionOut, GroupRecipeAction]: 

265 return HouseholdRepositoryGeneric( 

266 self.session, 

267 PK_ID, 

268 GroupRecipeAction, 

269 GroupRecipeActionOut, 

270 group_id=self.group_id, 

271 household_id=self.household_id, 

272 ) 

273 

274 # ================================================================ 

275 # Meal Plan 

276 

277 @cached_property 

278 def meals(self) -> RepositoryMeals: 

279 return RepositoryMeals( 

280 self.session, PK_ID, GroupMealPlan, ReadPlanEntry, group_id=self.group_id, household_id=self.household_id 

281 ) 

282 

283 @cached_property 

284 def group_meal_plan_rules(self) -> RepositoryMealPlanRules: 

285 return RepositoryMealPlanRules( 

286 self.session, 

287 PK_ID, 

288 GroupMealPlanRules, 

289 PlanRulesOut, 

290 group_id=self.group_id, 

291 household_id=self.household_id, 

292 ) 

293 

294 # ================================================================ 

295 # Shopping List 

296 

297 @cached_property 

298 def group_shopping_lists(self) -> RepositoryShoppingList: 

299 return RepositoryShoppingList( 

300 self.session, PK_ID, ShoppingList, ShoppingListOut, group_id=self.group_id, household_id=self.household_id 

301 ) 

302 

303 @cached_property 

304 def group_shopping_list_item(self) -> HouseholdRepositoryGeneric[ShoppingListItemOut, ShoppingListItem]: 

305 return HouseholdRepositoryGeneric( 

306 self.session, 

307 PK_ID, 

308 ShoppingListItem, 

309 ShoppingListItemOut, 

310 group_id=self.group_id, 

311 household_id=self.household_id, 

312 ) 

313 

314 @cached_property 

315 def group_shopping_list_item_references( 

316 self, 

317 ) -> HouseholdRepositoryGeneric[ShoppingListItemRecipeRefOut, ShoppingListItemRecipeReference]: 

318 return HouseholdRepositoryGeneric( 

319 self.session, 

320 PK_ID, 

321 ShoppingListItemRecipeReference, 

322 ShoppingListItemRecipeRefOut, 

323 group_id=self.group_id, 

324 household_id=self.household_id, 

325 ) 

326 

327 @cached_property 

328 def group_shopping_list_recipe_refs( 

329 self, 

330 ) -> HouseholdRepositoryGeneric[ShoppingListRecipeRefOut, ShoppingListRecipeReference]: 

331 return HouseholdRepositoryGeneric( 

332 self.session, 

333 PK_ID, 

334 ShoppingListRecipeReference, 

335 ShoppingListRecipeRefOut, 

336 group_id=self.group_id, 

337 household_id=self.household_id, 

338 ) 

339 

340 @cached_property 

341 def shopping_list_multi_purpose_labels( 

342 self, 

343 ) -> HouseholdRepositoryGeneric[ShoppingListMultiPurposeLabelOut, ShoppingListMultiPurposeLabel]: 

344 return HouseholdRepositoryGeneric( 

345 self.session, 

346 PK_ID, 

347 ShoppingListMultiPurposeLabel, 

348 ShoppingListMultiPurposeLabelOut, 

349 group_id=self.group_id, 

350 household_id=self.household_id, 

351 ) 

352 

353 @cached_property 

354 def group_multi_purpose_labels(self) -> GroupRepositoryGeneric[MultiPurposeLabelOut, MultiPurposeLabel]: 

355 return GroupRepositoryGeneric( 

356 self.session, PK_ID, MultiPurposeLabel, MultiPurposeLabelOut, group_id=self.group_id 

357 ) 

358 

359 # ================================================================ 

360 # Events 

361 

362 @cached_property 

363 def group_event_notifier(self) -> HouseholdRepositoryGeneric[GroupEventNotifierOut, GroupEventNotifierModel]: 

364 return HouseholdRepositoryGeneric( 

365 self.session, 

366 PK_ID, 

367 GroupEventNotifierModel, 

368 GroupEventNotifierOut, 

369 group_id=self.group_id, 

370 household_id=self.household_id, 

371 ) 

372 

373 @cached_property 

374 def webhooks(self) -> HouseholdRepositoryGeneric[ReadWebhook, GroupWebhooksModel]: 

375 return HouseholdRepositoryGeneric( 

376 self.session, PK_ID, GroupWebhooksModel, ReadWebhook, group_id=self.group_id, household_id=self.household_id 

377 )