-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcreate_recipes.py
135 lines (98 loc) · 3.62 KB
/
create_recipes.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
from dataclasses import dataclass
from typing import TypeAlias, Protocol, NamedTuple
from uuid import UUID, uuid4
class MacroNutrients(NamedTuple):
carbohydrates: float
proteins: float
fats: float
IngredientId: TypeAlias = str
@dataclass
class Ingredient:
"""Holds information about ingredient.
In this context we simply care about name, calories and macros.
Properties:
- macronutrients: amount macronutrients per 100 gram.
- kilocalories: amount of kilocalories per 100 gram.
"""
id: IngredientId
macronutrients: MacroNutrients
kilocalories: float
RecipeId: TypeAlias = UUID
@dataclass
class Recipe:
"""Holds information about recipe.
In this context we simply care about list of ingredients and quantities.
Properties:
- ingredients: list of (quantity, ingredient) tuples the recipe calls for.
- yields_: number servings the recipe provides.
"""
id: RecipeId
name: str
ingredients: list[tuple[float, Ingredient]]
yield_: int
def contains(self, ingredient: Ingredient | IngredientId) -> bool:
ingredient_id = (
ingredient if isinstance(ingredient, IngredientId) else ingredient.id
)
return any([i[1].id == ingredient_id for i in self.ingredients])
def macros_per_serving(self) -> MacroNutrients:
(total_carbohydrate, total_protein, total_fat) = 0.0, 0.0, 0.0
for weight, ingredient in self.ingredients:
carbohydrates, proteins, fats = ingredient.macronutrients
total_carbohydrate += weight * carbohydrates / 100
total_protein += weight * proteins / 100
total_fat += weight * fats / 100
return MacroNutrients(
total_carbohydrate / self.yield_,
total_protein / self.yield_,
total_fat / self.yield_,
)
def kilocalories_per_serving(self) -> float:
total_kilocalories = sum(w * i.kilocalories / 100 for w, i in self.ingredients)
return total_kilocalories / self.yield_
@dataclass
class RecipeDTO:
name: str
ingredients: list[tuple[float, IngredientId]]
yield_: int
class IngredientRepository(Protocol):
def find(self, ingredient_id: IngredientId) -> Ingredient | None: ...
class RecipeRepository(Protocol):
def find(self, recipe_id: RecipeId) -> Recipe | None: ...
def all(self) -> list[Recipe]: ...
def add(self, recipe: Recipe): ...
class IngredientNotFound(Exception):
pass
class RecipeNotFound(Exception):
pass
class GetRecipeUseCase:
def __init__(self, recipe_repository: RecipeRepository):
self.recipe_repository = recipe_repository
def __call__(self, recipe_id: RecipeId) -> Recipe:
recipe = self.recipe_repository.find(recipe_id)
if recipe is None:
raise RecipeNotFound()
return recipe
class CreateRecipeUseCase:
def __init__(
self,
recipe_repository: RecipeRepository,
ingredient_repository: IngredientRepository,
):
self.recipe_repository = recipe_repository
self.ingredient_repository = ingredient_repository
def __call__(self, new_recipe: RecipeDTO) -> Recipe:
ingredients = [
(quantity, self.ingredient_repository.find(id))
for quantity, id in new_recipe.ingredients
]
if any(i[1] is None for i in ingredients):
raise IngredientNotFound()
recipe = Recipe(
id=uuid4(),
name=new_recipe.name,
ingredients=ingredients, # type: ignore
yield_=new_recipe.yield_,
)
self.recipe_repository.add(recipe)
return recipe