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

74 statements  

« prev     ^ index     » next       coverage.py v7.10.6, created at 2025-11-25 15:32 +0000

1from collections.abc import Iterable 1a

2from typing import cast 1a

3from uuid import UUID 1a

4 

5from pydantic import UUID4 1a

6from slugify import slugify 1a

7from sqlalchemy import func, select 1a

8from sqlalchemy.exc import IntegrityError 1a

9 

10from mealie.db.models._model_base import SqlAlchemyBase 1a

11from mealie.db.models.household import Household, HouseholdToRecipe 1a

12from mealie.db.models.recipe.category import Category 1a

13from mealie.db.models.recipe.recipe import RecipeModel 1a

14from mealie.db.models.recipe.tag import Tag 1a

15from mealie.db.models.recipe.tool import Tool 1a

16from mealie.db.models.users.users import User 1a

17from mealie.repos.repository_generic import GroupRepositoryGeneric, HouseholdRepositoryGeneric 1a

18from mealie.schema.household import ( 1a

19 HouseholdCreate, 

20 HouseholdInDB, 

21 HouseholdRecipeOut, 

22 HouseholdStatistics, 

23 UpdateHousehold, 

24) 

25 

26 

27class RepositoryHousehold(GroupRepositoryGeneric[HouseholdInDB, Household]): 1a

28 def create(self, data: HouseholdCreate | dict) -> HouseholdInDB: 1a

29 if isinstance(data, HouseholdCreate): 29 ↛ 32line 29 didn't jump to line 32 because the condition on line 29 was always true1ab

30 data = data.model_dump() 1ab

31 

32 if not data.get("group_id"): 32 ↛ 33line 32 didn't jump to line 33 because the condition on line 32 was never true1ab

33 data["group_id"] = self.group_id 

34 max_attempts = 10 1ab

35 original_name = cast(str, data["name"]) 1ab

36 

37 attempts = 0 1ab

38 while True: 1ab

39 try: 1ab

40 data["slug"] = slugify(data["name"]) 1ab

41 return super().create(data) 1ab

42 except IntegrityError: 

43 self.session.rollback() 

44 attempts += 1 

45 if attempts >= max_attempts: 

46 raise 

47 

48 data["name"] = f"{original_name} ({attempts})" 

49 

50 def create_many(self, data: Iterable[HouseholdInDB | dict]) -> list[HouseholdInDB]: 1a

51 # since create uses special logic for resolving slugs, we don't want to use the standard create_many method 

52 return [self.create(new_household) for new_household in data] 

53 

54 def update(self, match_value: str | int | UUID4, new_data: UpdateHousehold | dict) -> HouseholdInDB: 1a

55 if isinstance(new_data, HouseholdCreate): 

56 new_data.slug = slugify(new_data.name) 

57 else: 

58 new_data["slug"] = slugify(new_data["name"]) 

59 

60 return super().update(match_value, new_data) 

61 

62 def update_many(self, data: Iterable[UpdateHousehold | dict]) -> list[HouseholdInDB]: 1a

63 # since update uses special logic for resolving slugs, we don't want to use the standard update_many method 

64 return [ 

65 self.update(household["id"] if isinstance(household, dict) else household.id, household) 

66 for household in data 

67 ] 

68 

69 def get_by_name(self, name: str) -> HouseholdInDB | None: 1a

70 if not self.group_id: 70 ↛ 71line 70 didn't jump to line 71 because the condition on line 70 was never true1ab

71 raise Exception("group_id not set") 

72 dbhousehold = ( 1ab

73 self.session.execute(select(self.model).filter_by(name=name, group_id=self.group_id)) 

74 .scalars() 

75 .one_or_none() 

76 ) 

77 if dbhousehold is None: 77 ↛ 78line 77 didn't jump to line 78 because the condition on line 77 was never true1ab

78 return None 

79 return self.schema.model_validate(dbhousehold) 1ab

80 

81 def get_by_slug_or_id(self, slug_or_id: str | UUID) -> HouseholdInDB | None: 1a

82 if isinstance(slug_or_id, str): 82 ↛ 88line 82 didn't jump to line 88 because the condition on line 82 was always true1c

83 try: 1c

84 slug_or_id = UUID(slug_or_id) 1c

85 except ValueError: 1c

86 pass 1c

87 

88 if isinstance(slug_or_id, UUID): 88 ↛ 89line 88 didn't jump to line 89 because the condition on line 88 was never true1c

89 return self.get_one(slug_or_id) 

90 else: 

91 return self.get_one(slug_or_id, key="slug") 1c

92 

93 def statistics(self, group_id: UUID4, household_id: UUID4) -> HouseholdStatistics: 1a

94 def model_count(model: type[SqlAlchemyBase], *, filter_household: bool = True) -> int: 1c

95 stmt = select(func.count(model.id)).filter_by(group_id=group_id) 1c

96 if filter_household: 1c

97 stmt = stmt.filter_by(household_id=household_id) 1c

98 return self.session.scalar(stmt) 1c

99 

100 return HouseholdStatistics( 1c

101 # household-level statistics 

102 total_recipes=model_count(RecipeModel), 

103 total_users=model_count(User), 

104 # group-level statistics 

105 total_categories=model_count(Category, filter_household=False), 

106 total_tags=model_count(Tag, filter_household=False), 

107 total_tools=model_count(Tool, filter_household=False), 

108 ) 

109 

110 

111class RepositoryHouseholdRecipes(HouseholdRepositoryGeneric[HouseholdRecipeOut, HouseholdToRecipe]): 1a

112 def get_by_recipe(self, recipe_id: UUID4) -> HouseholdRecipeOut | None: 1a

113 if not self.household_id: 

114 raise Exception("household_id not set") 

115 

116 stmt = select(HouseholdToRecipe).filter( 

117 HouseholdToRecipe.household_id == self.household_id, HouseholdToRecipe.recipe_id == recipe_id 

118 ) 

119 result = self.session.execute(stmt).scalars().one_or_none() 

120 return None if result is None else self.schema.model_validate(result)