Skip to content

Commit

Permalink
Tests and lint
Browse files Browse the repository at this point in the history
  • Loading branch information
danielkjellid committed Jun 17, 2024
1 parent f259d9f commit d034d0a
Show file tree
Hide file tree
Showing 16 changed files with 109 additions and 30 deletions.
3 changes: 2 additions & 1 deletion nest/core/utils/s3.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ def s3_asset_delete(*, storage_key: str | None = None) -> None:
"Storage key cannot be empty, to able to cleanup remote folder."
)

storage = S3Storage(aws_s3_bucket_name=settings.AWS_S3_BUCKET_NAME)
bucket_name: str = settings.AWS_S3_BUCKET_NAME # type: ignore[misc]
storage = S3Storage(aws_s3_bucket_name=bucket_name)
parent_path_key = storage_key.rsplit("/", 1)[0] # Get everything before the last /

if storage.exists(parent_path_key):
Expand Down
9 changes: 8 additions & 1 deletion nest/recipes/core/managers.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from datetime import timedelta
from typing import TYPE_CHECKING

from django.db.models import DurationField, Q, Sum
from django.db.models import Count, DurationField, Q, Sum
from django.db.models.functions import Coalesce

from nest.core.managers import BaseQuerySet
Expand Down Expand Up @@ -40,3 +40,10 @@ def annotate_duration(self) -> BaseQuerySet["models.Recipe"]:
timedelta(seconds=0),
),
)

def annotate_num_plan_usages(self) -> BaseQuerySet["models.Recipe"]:
"""
Annotate how many times this recipe has been used in plans.
"""

return self.annotate(num_plan_usages=Count("plan_items__recipe_plan"))
1 change: 1 addition & 0 deletions nest/recipes/core/records.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,3 +114,4 @@ class RecipeDetailRecord(RecipeRecord):
health_score: RecipeHealthScore | None # TODO: Needs to be annotated
ingredient_item_groups: list[RecipeIngredientItemGroupRecord]
steps: list[RecipeStepRecord]
num_plan_usages: int
11 changes: 7 additions & 4 deletions nest/recipes/core/selectors.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
from decimal import Decimal

from nest.core.exceptions import ApplicationError

from ..ingredients.selectors import get_recipe_ingredient_item_groups_for_recipe
Expand All @@ -11,14 +9,18 @@
RecipeDurationRecord,
RecipeRecord,
)
from ...core.types import FetchedResult


def get_recipe(*, pk: int) -> RecipeDetailRecord:
"""
Get a recipe instance.
"""
recipe = Recipe.objects.filter(id=pk).annotate_duration().first()
recipe = (
Recipe.objects.filter(id=pk)
.annotate_duration()
.annotate_num_plan_usages()
.first()
)

if not recipe:
raise ApplicationError(message="Recipe does not exist.")
Expand All @@ -45,6 +47,7 @@ def get_recipe(*, pk: int) -> RecipeDetailRecord:
health_score=None,
ingredient_item_groups=ingredient_groups,
steps=steps,
num_plan_usages=getattr(recipe, "num_plan_usages", 0),
)


Expand Down
13 changes: 12 additions & 1 deletion nest/recipes/plans/algorithm.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,14 @@ def __init__(
self,
budget: Decimal,
total_num_recipes: int,
num_portions_per_recipe: int,
num_pescatarian: int,
num_vegetarian: int,
applicable_recipes: list[RecipeDetailRecord],
) -> None:
self.budget = budget
self.total_num_recipes = total_num_recipes
self.num_portions_per_recipe = num_portions_per_recipe
self.num_pescatarian = num_pescatarian
self.num_vegetarian = num_vegetarian
self.recipes = applicable_recipes
Expand All @@ -43,12 +45,18 @@ def _get_products_dataframe(self) -> pl.DataFrame:
product_data = []

for recipe in self.recipes:
recipe_portion_factor = (
self.num_portions_per_recipe / recipe.default_num_portions
)

for group in recipe.ingredient_item_groups:
for item in group.ingredient_items:
product = item.ingredient.product

portion_quantity = item.portion_quantity * recipe_portion_factor

converted_quantity = convert_unit_quantity(
quantity=item.portion_quantity,
quantity=portion_quantity,
from_unit=item.portion_quantity_unit,
to_unit=product.unit,
piece_weight=product.unit_quantity,
Expand Down Expand Up @@ -86,6 +94,7 @@ def _calculate_score_for_recipes(self) -> list[RecipeScoreData]:
"equal_products": 10,
"pescatarian": 5,
"vegetarian": 1,
"num_usages": -3, # Punish recipes that's often used in plans.
}

for recipe in self.recipes:
Expand Down Expand Up @@ -134,6 +143,8 @@ def _calculate_score_for_recipes(self) -> list[RecipeScoreData]:
):
recipe_score += score_weights["vegetarian"]

recipe_score += recipe.num_plan_usages * score_weights["num_usages"]

recipes_data.append(
RecipeScoreData(
recipe_id=recipe.id,
Expand Down
2 changes: 1 addition & 1 deletion nest/recipes/plans/managers.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from nest.core.managers import BaseQuerySet

if TYPE_CHECKING:
from nest.recipes.meal_plans import models
from nest.recipes.plans import models


class RecipePlanQuerySet(BaseQuerySet["models.RecipePlan"]):
Expand Down
8 changes: 6 additions & 2 deletions nest/recipes/plans/models.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
from django.db import models

from nest.core.models import BaseModel

from ..core.models import Recipe
from .managers import RecipePlanQuerySet, RecipePlanItemQuerySet
from .managers import RecipePlanItemQuerySet, RecipePlanQuerySet

# Create your models here.

Expand All @@ -28,7 +30,9 @@ class RecipePlan(BaseModel):
objects = _RecipePlanManager()

def __str__(self) -> str:
return f"{self.title} - {self.from_date.date()}"
if self.from_date:
return f"{self.title} - {self.from_date.date()}"
return self.title


_RecipePlanItemManager = models.Manager.from_queryset(RecipePlanItemQuerySet)
Expand Down
11 changes: 5 additions & 6 deletions nest/recipes/plans/selectors.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
from datetime import timedelta

from django.db.models import Count
from django.utils import timezone

from nest.recipes.core.enums import RecipeStatus
Expand All @@ -16,20 +15,19 @@


def find_recipes_applicable_for_plan(
*, grace_period_weeks: int = RECIPE_GRACE_PERIOD_WEEKS
*, grace_period_weeks: int | None = RECIPE_GRACE_PERIOD_WEEKS
) -> list[RecipeDetailRecord]:
first_possible_from_date = timezone.now() - timedelta(weeks=grace_period_weeks)
print(first_possible_from_date.date())
recipes = (
Recipe.objects.exclude(
plan_items__recipe_plan__from_date__lte=first_possible_from_date,
plan_items__recipe_plan__from_date__lt=first_possible_from_date,
)
.filter(
status=RecipeStatus.PUBLISHED,
)
# TODO: Necessary? Should this rather be weighted in the algo?
.annotate(num_plan_usages=Count("plan_items"))
.annotate_duration()
.order_by("-num_plan_usages")
.annotate_num_plan_usages()
)

recipe_ids = [recipe.id for recipe in recipes]
Expand Down Expand Up @@ -58,6 +56,7 @@ def find_recipes_applicable_for_plan(
health_score=None,
ingredient_item_groups=recipe_ingredient_item_groups[recipe.id],
steps=recipe_steps[recipe.id],
num_plan_usages=getattr(recipe, "num_plan_usages", 0),
)
for recipe in recipes
]
12 changes: 8 additions & 4 deletions nest/recipes/plans/services.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from django.utils.text import slugify

from nest.homes.records import HomeRecord
from nest.recipes.core.records import RecipeRecord, RecipeDetailRecord
from nest.recipes.core.records import RecipeDetailRecord
from nest.recipes.plans.algorithm import PlanDistributor
from nest.recipes.plans.models import RecipePlan, RecipePlanItem
from nest.recipes.plans.selectors import find_recipes_applicable_for_plan
Expand All @@ -17,7 +17,7 @@ def create_weekly_recipe_plan_for_home(
home: HomeRecord,
from_date: date,
auto_generated: bool = False,
):
) -> None:
num_items = 7
week_number = from_date.isocalendar().week
to_date = from_date + timedelta(days=num_items)
Expand All @@ -29,12 +29,14 @@ def create_weekly_recipe_plan_for_home(
)

create_recipe_plan(
home=home,
title=title,
description=description,
from_date=from_date,
budget=home.weekly_budget,
num_items=num_items,
num_portions_per_recipe=home.num_residents,
num_pescatarian=0,
num_vegetarian=0,
grace_period_weeks=home.num_weeks_recipe_rotation,
)

Expand All @@ -46,11 +48,12 @@ def create_recipe_plan(
description: str | None = None,
from_date: date,
budget: Decimal,
num_portions_per_recipe: int,
num_items: int,
num_pescatarian: int,
num_vegetarian: int,
grace_period_weeks: int | None = None,
):
) -> None:
plan_slug = slugify(title)
recipe_plan = RecipePlan.objects.create(
title=title,
Expand All @@ -65,6 +68,7 @@ def create_recipe_plan(
plan_distributor = PlanDistributor(
budget=budget,
total_num_recipes=num_items,
num_portions_per_recipe=num_portions_per_recipe,
num_pescatarian=num_pescatarian,
num_vegetarian=num_vegetarian,
applicable_recipes=applicable_recipes,
Expand Down
16 changes: 15 additions & 1 deletion poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ django-store-kit = "^0.1.2"
urllib3 = "^2.0.7"
psycopg2 = "^2.9.9"
polars = "^0.20.31"
freezegun = "^1.5.1"

[tool.poetry.group.dev.dependencies]
ruff = "^0.1.4"
Expand Down
7 changes: 6 additions & 1 deletion schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -2759,7 +2759,8 @@
"isPescatarian",
"duration",
"ingredientItemGroups",
"steps"
"steps",
"numPlanUsages"
],
"properties": {
"id": {
Expand Down Expand Up @@ -2834,6 +2835,10 @@
"items": {
"$ref": "#/components/schemas/RecipeStepRecord"
}
},
"numPlanUsages": {
"title": "Num Plan Usages",
"type": "integer"
}
}
},
Expand Down
5 changes: 2 additions & 3 deletions tests/recipes/fixtures.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from datetime import timedelta, datetime
from datetime import datetime, timedelta
from decimal import Decimal
from typing import Callable, TypedDict

Expand All @@ -16,7 +16,6 @@
from nest.recipes.steps.enums import RecipeStepType
from nest.recipes.steps.models import RecipeStep


##########
# Recipe #
##########
Expand Down Expand Up @@ -486,7 +485,7 @@ def _create_recipe_plan_item(spec: RecipePlanItemSpec) -> RecipePlanItem:

@pytest.fixture
def recipe_plan_item(
create_instance, create_recipe_plan_item_from_spec, default_recipe_
create_instance, create_recipe_plan_item_from_spec, default_recipe_plan_item_spec
):
return create_instance(
create_callback=create_recipe_plan_item_from_spec,
Expand Down
2 changes: 0 additions & 2 deletions tests/recipes/test_plans_algorithm.py

This file was deleted.

35 changes: 33 additions & 2 deletions tests/recipes/test_plans_selectors.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,33 @@
def test_selector_find_recipes_applicable_for_plan():
assert False
from datetime import datetime, timedelta

import pytest
from freezegun import freeze_time

from nest.recipes.core.enums import RecipeStatus
from nest.recipes.plans.selectors import find_recipes_applicable_for_plan

_FREEZE_TIME = datetime(year=2024, month=1, day=8, hour=12, minute=0, second=0)
_GRACE_PERIOD = timedelta(weeks=2)


@freeze_time(_FREEZE_TIME)
@pytest.mark.recipes(
recipe1={"title": "Recipe 1", "status": RecipeStatus.PUBLISHED},
recipe2={"title": "Recipe 2", "status": RecipeStatus.DRAFT},
recipe3={"title": "Recipe 3", "status": RecipeStatus.PUBLISHED},
)
@pytest.mark.recipe_plans(
plan1={"title": "Plan 1", "from_date": _FREEZE_TIME - _GRACE_PERIOD}
)
@pytest.mark.recipe_plan_item(recipe_plan="plan1", recipe="recipe3")
def test_selector_find_recipes_applicable_for_plan(
recipes, recipe_plans, recipe_plan_item, django_assert_num_queries
):
with django_assert_num_queries(3):
applicable_recipes = find_recipes_applicable_for_plan(grace_period_weeks=1)

recipe_ids = [recipe.id for recipe in applicable_recipes]

assert recipes["recipe1"].id in recipe_ids
assert recipes["recipe3"].id not in recipe_ids
assert recipes["recipe2"].id not in recipe_ids
3 changes: 2 additions & 1 deletion tests/recipes/test_plans_services.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@

import pytest
from django.utils import timezone

from nest.recipes.plans.algorithm import PlanDistributor
from nest.recipes.plans.models import RecipePlan, RecipePlanItem
from nest.recipes.plans.services import create_recipe_plan, _create_recipe_plan_items
from nest.recipes.plans.services import _create_recipe_plan_items, create_recipe_plan
from tests.factories.records import RecipeDetailRecordFactory

pytestmark = pytest.mark.django_db
Expand Down

0 comments on commit d034d0a

Please sign in to comment.