Coverage for opt/mealie/lib/python3.12/site-packages/mealie/services/scheduler/tasks/create_timeline_events.py: 20%

68 statements  

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

1from datetime import UTC, datetime, time, timedelta 1a

2 

3from dateutil.tz import tzlocal 1a

4from pydantic import UUID4 1a

5from sqlalchemy.orm import Session 1a

6 

7from mealie.db.db_setup import session_context 1a

8from mealie.repos.all_repositories import get_repositories 1a

9from mealie.schema.household.household import HouseholdRecipeUpdate 1a

10from mealie.schema.meal_plan.new_meal import PlanEntryType 1a

11from mealie.schema.recipe.recipe import RecipeSummary 1a

12from mealie.schema.recipe.recipe_timeline_events import RecipeTimelineEventCreate, TimelineEventType 1a

13from mealie.schema.response.pagination import PaginationQuery 1a

14from mealie.schema.user.user import DEFAULT_INTEGRATION_ID 1a

15from mealie.services.event_bus_service.event_bus_service import EventBusService 1a

16from mealie.services.event_bus_service.event_types import ( 1a

17 EventOperation, 

18 EventRecipeData, 

19 EventRecipeTimelineEventData, 

20 EventTypes, 

21) 

22from mealie.services.household_services.household_service import HouseholdService 1a

23 

24 

25def _create_mealplan_timeline_events_for_household( 1a

26 event_time: datetime, session: Session, group_id: UUID4, household_id: UUID4 

27) -> None: 

28 repos = get_repositories(session, group_id=group_id, household_id=household_id) 

29 household_service = HouseholdService(group_id, household_id, repos) 

30 event_bus_service = EventBusService(session=session) 

31 

32 timeline_events_to_create: list[RecipeTimelineEventCreate] = [] 

33 recipes_to_update: dict[UUID4, RecipeSummary] = {} 

34 recipe_id_to_slug_map: dict[UUID4, str] = {} 

35 

36 local_tz = tzlocal() 

37 mealplans = repos.meals.get_today(tz=local_tz) 

38 for mealplan in mealplans: 

39 if not (mealplan.recipe and mealplan.user_id): 

40 continue 

41 

42 user = repos.users.get_one(mealplan.user_id) 

43 if not user: 

44 continue 

45 

46 # TODO: make this translatable 

47 if mealplan.entry_type == PlanEntryType.side: 

48 event_subject = f"{user.full_name} made this as a side" 

49 

50 else: 

51 event_subject = f"{user.full_name} made this for {mealplan.entry_type.value}" 

52 

53 query_start_time = datetime.combine(datetime.now(UTC).date(), time.min) 

54 query_end_time = query_start_time + timedelta(days=1) 

55 query = PaginationQuery( 

56 query_filter=( 

57 f'recipe_id = "{mealplan.recipe_id}" ' 

58 f'AND timestamp >= "{query_start_time.isoformat()}" ' 

59 f'AND timestamp < "{query_end_time.isoformat()}" ' 

60 f'AND subject = "{event_subject}"' 

61 ) 

62 ) 

63 

64 # if this event already exists, don't create it again 

65 events = repos.recipe_timeline_events.page_all(pagination=query) 

66 if events.items: 

67 continue 

68 

69 # bump up the last made date 

70 household_to_recipe = household_service.get_household_recipe(mealplan.recipe.slug) 

71 last_made = household_to_recipe.last_made if household_to_recipe else None 

72 if (not last_made or last_made.date() < event_time.date()) and mealplan.recipe_id not in recipes_to_update: 

73 recipes_to_update[mealplan.recipe_id] = mealplan.recipe 

74 

75 timeline_events_to_create.append( 

76 RecipeTimelineEventCreate( 

77 user_id=user.id, 

78 subject=event_subject, 

79 event_type=TimelineEventType.info, 

80 timestamp=event_time, 

81 recipe_id=mealplan.recipe_id, 

82 ) 

83 ) 

84 

85 recipe_id_to_slug_map[mealplan.recipe_id] = mealplan.recipe.slug 

86 

87 if not timeline_events_to_create: 

88 return 

89 

90 # TODO: use bulk operations 

91 for event in timeline_events_to_create: 

92 new_event = repos.recipe_timeline_events.create(event) 

93 event_bus_service.dispatch( 

94 integration_id=DEFAULT_INTEGRATION_ID, 

95 group_id=group_id, 

96 household_id=household_id, 

97 event_type=EventTypes.recipe_updated, 

98 document_data=EventRecipeTimelineEventData( 

99 operation=EventOperation.create, 

100 recipe_slug=recipe_id_to_slug_map[new_event.recipe_id], 

101 recipe_timeline_event_id=new_event.id, 

102 ), 

103 ) 

104 

105 for recipe in recipes_to_update.values(): 

106 household_service.set_household_recipe(recipe.slug, HouseholdRecipeUpdate(last_made=event_time)) 

107 repos.recipes.patch(recipe.slug, {"last_made": event_time}) 

108 event_bus_service.dispatch( 

109 integration_id=DEFAULT_INTEGRATION_ID, 

110 group_id=group_id, 

111 household_id=household_id, 

112 event_type=EventTypes.recipe_updated, 

113 document_data=EventRecipeData(operation=EventOperation.update, recipe_slug=recipe.slug), 

114 ) 

115 

116 

117def _create_mealplan_timeline_events_for_group(event_time: datetime, session: Session, group_id: UUID4) -> None: 1a

118 repos = get_repositories(session, group_id=group_id) 

119 households_data = repos.households.page_all(PaginationQuery(page=1, per_page=-1)) 

120 household_ids = [household.id for household in households_data.items] 

121 for household_id in household_ids: 

122 _create_mealplan_timeline_events_for_household(event_time, session, group_id, household_id) 

123 

124 

125def create_mealplan_timeline_events() -> None: 1a

126 event_time = datetime.now(UTC) 

127 

128 with session_context() as session: 

129 repos = get_repositories(session) 

130 groups_data = repos.groups.page_all(PaginationQuery(page=1, per_page=-1)) 

131 group_ids = [group.id for group in groups_data.items] 

132 

133 for group_id in group_ids: 

134 _create_mealplan_timeline_events_for_group(event_time, session, group_id)