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

Commit

Permalink
Merge branch 'release/0.16.0'
Browse files Browse the repository at this point in the history
Signed-off-by: Sebastian Rutofski <sebastian.rutofski@gmail.com>
  • Loading branch information
SebRut committed Aug 13, 2020
2 parents cb53ac0 + db7c83b commit 30a010c
Show file tree
Hide file tree
Showing 7 changed files with 275 additions and 22 deletions.
10 changes: 10 additions & 0 deletions pygrocy/base.py
@@ -0,0 +1,10 @@
def get_val(obj):
if hasattr(obj, 'as_dict'):
as_attr = getattr(obj, 'as_dict')
return as_attr()
return obj


class DataModel(object):
def as_dict(self):
return {k: get_val(getattr(self, k)) for k, v in self.__class__.__dict__.items() if isinstance(v, property)}
118 changes: 109 additions & 9 deletions pygrocy/grocy.py
@@ -1,16 +1,20 @@
import base64
from datetime import datetime
from enum import Enum
from typing import List, Dict

from .base import DataModel
from .grocy_api_client import (DEFAULT_PORT_NUMBER, ChoreDetailsResponse,
CurrentChoreResponse, CurrentStockResponse,
GrocyApiClient,
LocationData, MissingProductResponse,
ProductDetailsResponse,
MealPlanResponse,
RecipeDetailsResponse,
ShoppingListItem, TransactionType, UserDto, TaskResponse)


class Product(object):
class Product(DataModel):
def __init__(self, response):
if isinstance(response, CurrentStockResponse):
self._init_from_CurrentStockResponse(response)
Expand Down Expand Up @@ -87,7 +91,7 @@ def is_partly_in_stock(self) -> int:
return self._is_partly_in_stock


class Group(object):
class Group(DataModel):
def __init__(self, raw_product_group: LocationData):
self._id = raw_product_group.id
self._name = raw_product_group.name
Expand All @@ -106,7 +110,7 @@ def description(self) -> str:
return self._description


class ShoppingListProduct(object):
class ShoppingListProduct(DataModel):
def __init__(self, raw_shopping_list: ShoppingListItem):
self._id = raw_shopping_list.id
self._product_id = raw_shopping_list.product_id
Expand Down Expand Up @@ -139,7 +143,7 @@ def product(self) -> Product:
return self._product


class User(object):
class User(DataModel):
def __init__(self, user_dto: UserDto):
self._id = user_dto.id
self._username = user_dto.username
Expand Down Expand Up @@ -168,15 +172,15 @@ def display_name(self) -> str:
return self._display_name


class Chore(object):
class PeriodType(Enum):
class Chore(DataModel):
class PeriodType(str, Enum):
MANUALLY = 'manually'
DYNAMIC_REGULAR = 'dynamic-regular'
DAILY = 'daily'
WEEKLY = 'weekly'
MONTHLY = 'monthly'

class AssignmentType(Enum):
class AssignmentType(str, Enum):
NO_ASSIGNMENT = 'no-assignment'
WHO_DID_LEAST_DID_FIRST = 'who-did-least-did-first'
RANDOM = 'random'
Expand Down Expand Up @@ -224,7 +228,10 @@ def _init_from_ChoreDetailsResponse(self, response: ChoreDetailsResponse):

self._last_tracked_time = response.last_tracked
self._next_estimated_execution_time = response.next_estimated_execution_time
self._last_done_by = User(response.last_done_by)
if response.last_done_by is not None:
self._last_done_by = User(response.last_done_by)
else:
self._last_done_by = None
self._track_count = response.track_count
if response.next_execution_assigned_user is not None:
self._next_execution_assigned_user = User(response.next_execution_assigned_user)
Expand Down Expand Up @@ -304,7 +311,7 @@ def next_execution_assigned_user(self) -> User:
return self._next_execution_assigned_user


class Task(object):
class Task(DataModel):
def __init__(self, response: TaskResponse):
self._id = response.id
self._name = response.name
Expand All @@ -325,6 +332,85 @@ def name(self) -> str:
return self._name


class RecipeItem(DataModel):
def __init__(self, response: RecipeDetailsResponse):
self._id = response.id
self._name = response.name
self._description = response.description
self._base_servings = response.base_servings
self._desired_servings = response.desired_servings
self._picture_file_name = response.picture_file_name

@property
def id(self) -> int:
return self._id

@property
def name(self) -> str:
return self._name

@property
def description(self) -> str:
return self._description

@property
def base_servings(self) -> int:
return self._base_servings

@property
def desired_servings(self) -> int:
return self._desired_servings

@property
def picture_file_name(self) -> str:
return self._picture_file_name

def get_picture_url_path(self, width: int = 400):
if self.picture_file_name:
b64name = base64.b64encode(self.picture_file_name.encode('ascii'))
path = "files/recipepictures/" + str(b64name, "utf-8")

return f"{path}?force_serve_as=picture&best_fit_width={width}"


class MealPlanItem(DataModel):
def __init__(self, response: MealPlanResponse):
self._id = response.id
self._day = response.day
self._recipe_id = response.recipe_id
self._recipe_servings = response.recipe_servings
self._note = response.note

@property
def id(self) -> int:
return self._id

@property
def day(self) -> datetime:
return self._day

@property
def recipe_id(self) -> int:
return self._recipe_id

@property
def recipe_servings(self) -> int:
return self._recipe_servings

@property
def note(self) -> str:
return self._note

@property
def recipe(self) -> RecipeItem:
return self._recipe

def get_details(self, api_client: GrocyApiClient):
recipe = api_client.get_recipe(self.recipe_id)
if recipe:
self._recipe = RecipeItem(recipe)


class Grocy(object):
def __init__(self, base_url, api_key, port: int = DEFAULT_PORT_NUMBER, verify_ssl=True):
self._api_client = GrocyApiClient(base_url, api_key, port, verify_ssl)
Expand Down Expand Up @@ -434,3 +520,17 @@ def tasks(self) -> List[Task]:

def complete_task(self, task_id):
return self._api_client.complete_task(task_id)

def meal_plan(self, get_details: bool = False) -> List[MealPlanItem]:
raw_meal_plan = self._api_client.get_meal_plan()
meal_plan = [MealPlanItem(data) for data in raw_meal_plan]

if get_details:
for item in meal_plan:
item.get_details(self._api_client)
return meal_plan

def recipe(self, recipe_id: int) -> RecipeItem:
recipe = self._api_client.get_recipe(recipe_id)
if recipe:
return RecipeItem(recipe)
78 changes: 78 additions & 0 deletions pygrocy/grocy_api_client.py
Expand Up @@ -40,6 +40,75 @@ def note(self) -> str:
def amount(self) -> float:
return self._amount

class MealPlanResponse(object):
def __init__(self, parsed_json):
self._id = parse_int(parsed_json.get('id'))
self._day = parse_date(parsed_json.get('day'))
self._type = parsed_json.get('type')
self._recipe_id = parse_int(parsed_json.get('recipe_id'))
self._recipe_servings = parse_int(parsed_json.get('recipe_servings'))
self._note = parsed_json.get('note', None)
self._product_id = parsed_json.get('product_id')
self._product_amount = parse_float(parsed_json.get('product_amount'), 0)
self._product_qu_id = parsed_json.get('product_qu_id')
self._row_created_timestamp = parse_date(parsed_json.get('row_created_timestamp'))
self._userfields = parsed_json.get('userfields')

@property
def id(self) -> int:
return self._id

@property
def day(self) -> datetime:
return self._day

@property
def recipe_id(self) -> int:
return self._recipe_id

@property
def recipe_servings(self) -> int:
return self._recipe_servings

@property
def note(self) -> str:
return self._note


class RecipeDetailsResponse(object):
def __init__(self, parsed_json):
self._id = parse_int(parsed_json.get('id'))
self._name = parsed_json.get('name')
self._description = parsed_json.get('description')
self._base_servings = parse_int(parsed_json.get('base_servings'))
self._desired_servings = parse_int(parsed_json.get('desired_servings'))
self._picture_file_name = parsed_json.get('picture_file_name')
self._row_created_timestamp = parse_date(parsed_json.get('row_created_timestamp'))
self._userfields = parsed_json.get('userfields')

@property
def id(self) -> int:
return self._id

@property
def name(self) -> str:
return self._name

@property
def description(self) -> str:
return self._description

@property
def base_servings(self) -> int:
return self._base_servings

@property
def desired_servings(self) -> int:
return self._desired_servings

@property
def picture_file_name(self) -> str:
return self._picture_file_name

class QuantityUnitData(object):
def __init__(self, parsed_json):
Expand Down Expand Up @@ -537,3 +606,12 @@ def get_tasks(self) -> List[TaskResponse]:
def complete_task(self, task_id: int):
url = f"tasks/{task_id}/complete"
self._do_post_request(url)

def get_meal_plan(self) -> List[MealPlanResponse]:
parsed_json = self._do_get_request("objects/meal_plan")
return [MealPlanResponse(data) for data in parsed_json]

def get_recipe(self, object_id: int) -> RecipeDetailsResponse:
parsed_json = self._do_get_request(f"objects/recipes/{object_id}")
if parsed_json:
return RecipeDetailsResponse(parsed_json)
6 changes: 3 additions & 3 deletions requirements-dev.txt
@@ -1,4 +1,4 @@
coveralls~=2.0.0
coveralls~=2.1.1
doctr~=1.8.0
pdoc3~=0.8.1
responses~=0.10.14
pdoc3~=0.8.4
responses~=0.10.15
2 changes: 1 addition & 1 deletion setup.py
Expand Up @@ -5,7 +5,7 @@

setuptools.setup(
name="pygrocy",
version="0.15.0",
version="0.16.0",
author="Sebastian Rutofski",
author_email="kontakt@sebastian-rutofski.de",
description="",
Expand Down
29 changes: 29 additions & 0 deletions test/test_MealPlanResponse.py
@@ -0,0 +1,29 @@
import json
from datetime import datetime
from unittest import TestCase

from pygrocy.grocy_api_client import MealPlanResponse


class TestMealPlanResponse(TestCase):
def test_parse(self):
input_json = """{
"id": "7",
"day": "2020-08-16",
"type": "recipe",
"recipe_id": "4",
"recipe_servings": "3",
"note": null,
"product_id": null,
"product_amount": "0.0",
"product_qu_id": null,
"row_created_timestamp": "2020-08-12 14:37:06",
"userfields": null
}"""

response = MealPlanResponse(json.loads(input_json))

assert response.id == 7
assert response.recipe_id == 4
assert response.recipe_servings == 3
self.assertIsInstance(response.day, datetime)

0 comments on commit 30a010c

Please sign in to comment.