Skip to content
This repository has been archived by the owner on Feb 5, 2024. It is now read-only.

Commit

Permalink
feat: treat variable meals as empty in /shift (#37)
Browse files Browse the repository at this point in the history
* chore: add test script

* feat: improve meal shifting

* fix: ensure docs folder exists before creating index

* feat: treat variable meals as empty in /shift

* chore: update shift test script

* chore: bump version to 1.8

* chore: apply pylint suggestions
  • Loading branch information
sralloza authored Mar 13, 2022
1 parent 31ed26f commit dbf0f23
Show file tree
Hide file tree
Showing 9 changed files with 151 additions and 26 deletions.
22 changes: 8 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,6 @@ Features:

Deployment is done via docker.

Notes:

- Remember you can use the tag you like.
- The Dockerfile is designed to work on ARM systems, like a Raspberry Pi.

```shell
# Build and push it to dockerhub
docker buildx build -t sralloza/meal-planner:$VERSION --platform=linux/arm/v7,linux/amd64 --push .
Expand All @@ -31,36 +26,36 @@ docker buildx build -t sralloza/meal-planner:$VERSION --platform=linux/arm/v7,li
docker buildx build -t sralloza/meal-planner:$VERSION --platform=linux/arm/v7,linux/amd64 --load .
```

## Environment
### Environment

You need to supply the following environment variables (required ones are marked with 🚩). Settings are grouped in categories.

### Server
#### Server

- 🚩 **API_TOKEN** (`str`): token of the API. In order to use the API, users will have to provide this token in their requests via the `X-TOKEN` header.
- **ENABLE_PROMETHEUS** (`bool`): if `True`, the API will enable the prometheus endpoint `/metrics`. Defaults to `False`.
- **PRODUCTION** (`bool`): if `True` the server will run on production environment. Defaults to `False`.
- **DISABLE_CRON_INTEGRATION** (`bool`) if `True`, the server will not launch cron jobs. It is useful to launch replicas, enabling cron integration in only one of them. It is also useful to deploy on Kubernetes, as the cron jobs can be implemented via `CronJob`.

### AWS
#### AWS

- 🚩 **AWS_ACCESS_KEY_ID** (`str`): AWS access key id.
- 🚩 **AWS_SECRET_ACCESS_KEY** (`str`): AWS secret access key.
- 🚩 **S3_BUCKET_NAME** (`str`): name of the S3 bucket to save the backups.
- **S3_FILE_NAME** (`str`): filename to save the backups in the AWS S3 Bucket. Defaults to `meals.json`.

### Notion
#### Notion

- **NOTION_ADD_DAY_AFTER_TOMORROW** (`bool`): if `True`, the meals of the day after tomorrow will also be added to Notion. Defaults to `True`.
- 🚩 **NOTION_BLOCK_ID** (`uuid`): id of the notion block where the meals will be showed.
- 🚩 **NOTION_KEY** (`str`): notion key to use the notion API.

### Todoist
#### Todoist

- 🚩 **TODOIST_PROJECT_ID** (`int`): todoist project id where the tasks will be added.
- 🚩 **TODOIST_TOKEN** (`str`): todoist token to use the todoist API.

### Database
#### Database

- 🚩 **MYSQL_DATABASE** (`str`): database name.
- 🚩 **MYSQL_HOST** (`str`): mysql host.
Expand All @@ -69,13 +64,12 @@ You need to supply the following environment variables (required ones are marked
- 🚩 **MYSQL_USER** (`str`): mysql user.
- **WAIT_FOR_IT_ADDRESS** (`str`): if is set, it will wait for the database to be ready for max 120 seconds. Must be set to `$MYSQL_HOST:$MYSQL_PORT`. This switch should not be used in Kubernetes deployments, as `initContainers` are designed to cover this exact use case.

### Other
#### Other

- **LOCALE_WEEKDAY_NAMES** (`list(str)`): weekday names, starting with Monday and ending with Sunday. Must contain 7 elements (one for each week day).
- **NULL_STR** (`str`): string to represent an empty `lunch1` or `dinner`. Defaults to `N/A`.
- **VARIABLE_STR** (`str`): string to represent a variable meal. Defaults to `Variable`. It's used in the `/shift` endpoint, where all meals which are equal to `VARIABLE_STR` will be treated as empty meals.

## Future

AWS and notion settings are currently needed. If you want to use this app without one of them (or both) add an issue and I'll make it optional and configurable.

As said before, the docker image is designed to work on ARM systems. If you want AMD64 support please fill an issue.
1 change: 1 addition & 0 deletions app/core/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ class Settings(BaseSettings):
# Other
LOCALE_WEEKDAY_NAMES: Optional[List[str]]
NULL_STR = "N/A"
VARIABLE_STR = "Variable"

@validator("DATABASE_URI", pre=True)
def assemble_db_connection(cls, v: Optional[str], values: Dict[str, Any]) -> Any:
Expand Down
32 changes: 32 additions & 0 deletions app/core/meals.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,19 @@
from fastapi import Query
from pydantic import parse_obj_as

from ..models.meal import Meal
from ..utils.misc import lowercase
from .config import settings

NULL_MAP = {
"lunch1": settings.NULL_STR,
"lunch1_frozen": False,
"lunch2": None,
"lunch2_frozen": False,
"dinner": settings.NULL_STR,
"dinner_frozen": False,
}


class SwapMode(Enum):
"""Valid swap modes."""
Expand Down Expand Up @@ -53,3 +66,22 @@ def swap_attrs(obj1: Any, obj2: Any, attrname: str):
def set_attrs(old: Any, new: Any, attrnames: List[str]):
for attr in attrnames:
setattr(new, attr, getattr(old, attr))


def can_override_meal_from_shift(meal: Meal, attrnames: List[str]) -> bool:
"""Checks if the meal has all the attrs to null.
A meal can be shifted (override some attrs from other meal) if all
attrnames are considered null.
"""

var_str = lowercase(settings.VARIABLE_STR)

for attrname in attrnames:
attr = lowercase(getattr(meal, attrname))
null_str = lowercase(NULL_MAP[attrname])
null_str = null_str.lower() if isinstance(null_str, str) else null_str

if attr not in (null_str, var_str):
return False
return True
20 changes: 10 additions & 10 deletions app/crud/crud_meal.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,18 @@
from sqlalchemy.orm.session import Session

from ..core.config import settings
from ..core.meals import SwapMode, set_attrs, swap_attrs
from ..core.meals import (
NULL_MAP,
SwapMode,
can_override_meal_from_shift,
set_attrs,
swap_attrs,
)
from ..crud.base import CRUDBase
from ..models import Meal
from ..schemas.meal import MealCreate, MealUpdate
from ..utils.misc import get_current_week

NULL_MAP = {
"lunch1": settings.NULL_STR,
"lunch1_frozen": False,
"lunch2": None,
"lunch2_frozen": False,
"dinner": settings.NULL_STR,
"dinner_frozen": False,
}


class CRUDMeal(CRUDBase[Meal, MealCreate, MealUpdate]):
"""Meal CRUD operations."""
Expand Down Expand Up @@ -177,6 +174,9 @@ def get_days_to_shift(
next_day_meal = self.create(db, obj_in=new_meal)
return [first_meal, next_day_meal]

if can_override_meal_from_shift(next_day_meal, attrnames):
return [first_meal, next_day_meal]

return self.get_days_to_shift(db, next_day_meal, attrnames) + [first_meal]

def remove_week(self, db: Session, *, week: int):
Expand Down
9 changes: 9 additions & 0 deletions app/utils/misc.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
from datetime import datetime
from pathlib import Path
from typing import TypeVar

from toml import loads

T = TypeVar("T")


def get_version():
data = loads(Path(__file__).parent.parent.with_name("pyproject.toml").read_text())
Expand All @@ -11,3 +14,9 @@ def get_version():

def get_current_week():
return datetime.now().isocalendar()[1]


def lowercase(obj: T) -> T:
if isinstance(obj, str):
return obj.lower()
return obj
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "meal-planner"
version = "1.7.0"
version = "1.8.0"
description = ""
authors = ["Diego Alloza González <[email protected]>"]
packages = [
Expand Down
2 changes: 1 addition & 1 deletion scripts/load-docs.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ def load_docs(source: str, extra_week: bool):

meals = filter_meals(meals)

MD_DIR.mkdir(exist_ok=True)
for week, weekly_meals in groupby(meals, lambda x: x.id.isocalendar()[1]):
weeks.append(week)
weekly_meals = list(weekly_meals)
Expand Down Expand Up @@ -96,7 +97,6 @@ def get_default_md_text():

def create_md(week: int, weekly_meals: List[Meal], override=True):
"""Creates the markdown file."""
MD_DIR.mkdir(exist_ok=True)
md_filepath = MD_DIR / f"{week}.md"
content = ""
weekly_meals.sort(key=lambda x: x.id)
Expand Down
65 changes: 65 additions & 0 deletions test-scripts/test-data.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
[
{
"date": "2022-03-14",
"lunch1": "LUNES",
"lunch2": "LUNES",
"dinner": "LUNES",
"lunch1_frozen": false,
"lunch2_frozen": false,
"dinner_frozen": false
},
{
"date": "2022-03-15",
"lunch1": "MARTES",
"lunch2": "MARTES",
"dinner": "MARTES",
"lunch1_frozen": false,
"lunch2_frozen": false,
"dinner_frozen": false
},
{
"date": "2022-03-16",
"lunch1": "MIÉRCOLES",
"lunch2": "MIÉRCOLES",
"dinner": "MIÉRCOLES",
"lunch1_frozen": false,
"lunch2_frozen": false,
"dinner_frozen": false
},
{
"date": "2022-03-17",
"lunch1": "JUEVES",
"lunch2": "JUEVES",
"dinner": "JUEVES",
"lunch1_frozen": false,
"lunch2_frozen": false,
"dinner_frozen": false
},
{
"date": "2022-03-18",
"lunch1": "VIERNES",
"lunch2": "VIERNES",
"dinner": "VIERNES",
"lunch1_frozen": false,
"lunch2_frozen": false,
"dinner_frozen": false
},
{
"date": "2022-03-19",
"lunch1": "SÁBADO",
"lunch2": "SÁBADO",
"dinner": "SÁBADO",
"lunch1_frozen": false,
"lunch2_frozen": false,
"dinner_frozen": false
},
{
"date": "2022-03-20",
"lunch1": "DOMINGO",
"lunch2": "DOMINGO",
"dinner": "DOMINGO",
"lunch1_frozen": false,
"lunch2_frozen": false,
"dinner_frozen": false
}
]
24 changes: 24 additions & 0 deletions test-scripts/test-shift.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#!/bin/bash
set -e
source /home/sralloza/Documents/meal-planner/.venv/bin/activate

: ${MEAL_PLANNER_API_TOKEN:?must set \$MEAL_PLANNER_API_TOKEN}

DATE="2022-03-14"
SHIFT_MODE="all"

# wait-for-it.sh 127.0.0.1:8000 --timeout 60

echo -e "\nClearing database"
http delete :8000/meals/week/10 x-token:$MEAL_PLANNER_API_TOKEN -ph | head -1
http delete :8000/meals/week/11 x-token:$MEAL_PLANNER_API_TOKEN -ph | head -1
http delete :8000/meals/week/12 x-token:$MEAL_PLANNER_API_TOKEN -ph | head -1

echo -e "\nPutting dummy data in database"
http post :8000/meals/bulk x-token:$MEAL_PLANNER_API_TOKEN < test-scripts/test-data.json > /dev/null
http :8000/meals x-token:$MEAL_PLANNER_API_TOKEN -pb | jtbl

echo -e "\nExecuting shift ($DATE, $SHIFT_MODE)"
http put :8000/meals/shift/$DATE x-token:$MEAL_PLANNER_API_TOKEN mode==$SHIFT_MODE -pb | jtbl
echo ""
http :8000/meals x-token:$MEAL_PLANNER_API_TOKEN -pb | jtbl

0 comments on commit dbf0f23

Please sign in to comment.