From 271fef7feae7e79de0de5fa38e71789359d82ccf Mon Sep 17 00:00:00 2001 From: Sebastian Rutofski Date: Tue, 24 Aug 2021 20:16:05 +0200 Subject: [PATCH 1/8] add meal plan section support (#182) --- pygrocy/data_models/generic.py | 1 + pygrocy/data_models/meal_items.py | 38 ++ pygrocy/grocy.py | 12 +- pygrocy/grocy_api_client.py | 28 ++ ...test_get_meal_plan_with_details_valid.yaml | 455 +++++++++++++++++- ...ctions.test_get_section_by_id_invalid.yaml | 43 ++ ...Sections.test_get_section_by_id_valid.yaml | 44 ++ ...lPlanSections.test_get_sections_valid.yaml | 45 ++ test/test_meal_plan.py | 7 +- test/test_meal_plan_sections.py | 27 ++ 10 files changed, 684 insertions(+), 16 deletions(-) create mode 100644 test/cassettes/test_meal_plan_sections/TestMealPlanSections.test_get_section_by_id_invalid.yaml create mode 100644 test/cassettes/test_meal_plan_sections/TestMealPlanSections.test_get_section_by_id_valid.yaml create mode 100644 test/cassettes/test_meal_plan_sections/TestMealPlanSections.test_get_sections_valid.yaml create mode 100644 test/test_meal_plan_sections.py diff --git a/pygrocy/data_models/generic.py b/pygrocy/data_models/generic.py index d85af48..f14c157 100644 --- a/pygrocy/data_models/generic.py +++ b/pygrocy/data_models/generic.py @@ -23,3 +23,4 @@ class EntityType(str, Enum): USER_ENTITIES = "userentities" USER_OBJECTS = "userobjects" MEAL_PLAN = "meal_plan" + MEAL_PLAN_SECTIONS = "meal_plan_sections" diff --git a/pygrocy/data_models/meal_items.py b/pygrocy/data_models/meal_items.py index 7d2126b..36c20ee 100644 --- a/pygrocy/data_models/meal_items.py +++ b/pygrocy/data_models/meal_items.py @@ -5,6 +5,7 @@ from pygrocy.grocy_api_client import ( GrocyApiClient, MealPlanResponse, + MealPlanSectionResponse, RecipeDetailsResponse, ) @@ -50,6 +51,30 @@ def get_picture_url_path(self, width: int = 400): return f"{path}?force_serve_as=picture&best_fit_width={width}" +class MealPlanSection(DataModel): + def __init__(self, response: MealPlanSectionResponse): + self._id = response.id + self._name = response.name + self._sort_number = response.sort_number + self._row_created_timestamp = response.row_created_timestamp + + @property + def id(self) -> int: + return self._id + + @property + def name(self) -> str: + return self._name + + @property + def sort_number(self) -> int: + return self._sort_number + + @property + def row_created_timestamp(self) -> datetime: + return self._row_created_timestamp + + class MealPlanItem(DataModel): def __init__(self, response: MealPlanResponse): self._id = response.id @@ -58,6 +83,7 @@ def __init__(self, response: MealPlanResponse): self._recipe_id = response.recipe_id self._recipe_servings = response.recipe_servings self._note = response.note + self._section_id = response.section_id @property def id(self) -> int: @@ -83,8 +109,20 @@ def note(self) -> str: def recipe(self) -> RecipeItem: return self._recipe + @property + def section_id(self) -> int: + return self._section_id + + @property + def section(self) -> MealPlanSection: + return self._section + def get_details(self, api_client: GrocyApiClient): if self.recipe_id: recipe = api_client.get_recipe(self.recipe_id) if recipe: self._recipe = RecipeItem(recipe) + if self.section_id: + section = api_client.get_meal_plan_section(self.section_id) + if section: + self._section = MealPlanSection(section) diff --git a/pygrocy/grocy.py b/pygrocy/grocy.py index adc79e6..51c6bb6 100644 --- a/pygrocy/grocy.py +++ b/pygrocy/grocy.py @@ -8,7 +8,7 @@ from .data_models.battery import Battery from .data_models.chore import Chore from .data_models.generic import EntityType -from .data_models.meal_items import MealPlanItem, RecipeItem +from .data_models.meal_items import MealPlanItem, MealPlanSection, RecipeItem from .data_models.product import Group, Product, ShoppingListProduct from .data_models.task import Task from .data_models.user import User # noqa: F401 @@ -237,3 +237,13 @@ def delete_generic(self, entity_type: EntityType, object_id: int): def get_generic_objects_for_type(self, entity_type: EntityType): return self._api_client.get_generic_objects_for_type(entity_type.value) + + def meal_plan_sections(self) -> List[MealPlanSection]: + raw_sections = self._api_client.get_meal_plan_sections() + return [MealPlanSection(section) for section in raw_sections] + + def meal_plan_section(self, meal_plan_section_id: int) -> MealPlanSection: + section = self._api_client.get_meal_plan_section(meal_plan_section_id) + + if section: + return MealPlanSection(section) diff --git a/pygrocy/grocy_api_client.py b/pygrocy/grocy_api_client.py index 4cb183b..978a358 100644 --- a/pygrocy/grocy_api_client.py +++ b/pygrocy/grocy_api_client.py @@ -8,6 +8,7 @@ import requests +from pygrocy import EntityType from pygrocy.utils import ( localize_datetime, parse_bool_int, @@ -68,6 +69,7 @@ def __init__(self, parsed_json): parsed_json.get("row_created_timestamp") ) self._userfields = parsed_json.get("userfields") + self._section_id = parse_int(parsed_json.get("section_id")) @property def id(self) -> int: @@ -89,6 +91,10 @@ def recipe_servings(self) -> int: def note(self) -> str: return self._note + @property + def section_id(self) -> int: + return self._section_id + class RecipeDetailsResponse(object): def __init__(self, parsed_json): @@ -539,6 +545,16 @@ def __init__(self, parsed_json): ) +class MealPlanSectionResponse(object): + def __init__(self, parsed_json): + self.id = parse_int(parsed_json.get("id")) + self.name = parsed_json.get("name") + self.sort_number = parse_int(parsed_json.get("sort_number")) + self.row_created_timestamp = parse_date( + parsed_json.get("row_created_timestamp") + ) + + def _enable_debug_mode(): _LOGGER.setLevel(logging.DEBUG) @@ -818,3 +834,15 @@ def delete_generic(self, entity_type: str, object_id: int): def get_generic_objects_for_type(self, entity_type: str): return self._do_get_request(f"objects/{entity_type}") + + def get_meal_plan_sections(self) -> List[MealPlanSectionResponse]: + parsed_json = self.get_generic_objects_for_type(EntityType.MEAL_PLAN_SECTIONS) + if parsed_json: + return [MealPlanSectionResponse(resp) for resp in parsed_json] + + def get_meal_plan_section(self, meal_plan_section_id) -> MealPlanSectionResponse: + parsed_json = self._do_get_request( + f"objects/meal_plan_sections?query%5B%5D=id%3D{meal_plan_section_id}" + ) + if parsed_json and len(parsed_json) == 1: + return MealPlanSectionResponse(parsed_json[0]) diff --git a/test/cassettes/test_meal_plan/TestMealPlan.test_get_meal_plan_with_details_valid.yaml b/test/cassettes/test_meal_plan/TestMealPlan.test_get_meal_plan_with_details_valid.yaml index 13001ee..38aeb65 100644 --- a/test/cassettes/test_meal_plan/TestMealPlan.test_get_meal_plan_with_details_valid.yaml +++ b/test/cassettes/test_meal_plan/TestMealPlan.test_get_meal_plan_with_details_valid.yaml @@ -15,11 +15,12 @@ interactions: response: body: string: !!binary | - H4sIAAAAAAAEA83U3QqCMBQH8FeRcz1hm2a25+guQoaOGvjVnIVE795SKXO0yxh4MQ7nP/zB2Tnc - QRbACIKCD8CAYkpCTEOcAgI9tMLUlMilOaD5kM2BqZx1Ql1lferGS+pGm0TdlyWCVjVFn+ux/bvC - q6avNTD8abr0iz7V3LJcCa5FkWlZiU7zql38HIkCsmMxYVECDzQJqCXYuQUm4JcgWgsIdgtMwC9B - bAmIW2ACfgk2loC6Bebh+CVILEHkFnj3DraWIHYLvJuidC3Ai100rsj31Lx26bQcf48R7M+yC8zH - gzn8p9V6fALTAMx5HgYAAA== + H4sIAAAAAAAEA9WVzWrDMAzHXyX47IDlpF3i59htlGBisxkaO3OclTL67lOTsKXN5h03gw9C6C/r + B/p4eidGEQGUKHkmgnDGIWc8ZxWhJJx7jT6vW4MGXYxmEczuZtD+zdjnYUpiXUCFHY9HSnrv1NiG + KfzWIzs32kAE+wp6HVdx3p2a1msZtGqC6fQQZNevioMig1qUIIo9VqWcxT8x16DbYJydEuVwoTMa + 36DVcTQUJIJW3KMBi6OhIBG0coMGcTQUJIK226DxOBoOZyJo+w1aEUdLZ9YeNmhlHC2dhqzu0dhq + Q04b/bMBr6t/3uU/dyR5fDFDhk9mi/ifXIL6lrPK+S+D94fdicWVGVSCgdhdZ+jbIweXwweKUAJF + wAcAAA== headers: Access-Control-Allow-Headers: - '*' @@ -34,7 +35,7 @@ interactions: Content-Type: - application/json Date: - - Sat, 21 Aug 2021 19:42:50 GMT + - Tue, 24 Aug 2021 18:09:33 GMT Server: - nginx/1.20.1 Transfer-Encoding: @@ -84,7 +85,49 @@ interactions: Content-Type: - application/json Date: - - Sat, 21 Aug 2021 19:42:50 GMT + - Tue, 24 Aug 2021 18:09:33 GMT + Server: + - nginx/1.20.1 + Transfer-Encoding: + - chunked + X-Powered-By: + - PHP/8.0.9 + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-requests/2.26.0 + accept: + - application/json + method: GET + uri: https://localhost/api/objects/meal_plan_sections?query%5B%5D=id%3D-1 + response: + body: + string: !!binary | + H4sIAAAAAAAEAyXKwQqAIAwA0F+JnRWcXWS/EiGWO3hQYy46RP9e0Pm95YaSgSwaaKkyEICB0UVj + O+vG8pP0K+7CSTlHLZWHpnp81zuP1gXr58kFwpkwwLO+Gg7z9FQAAAA= + headers: + Access-Control-Allow-Headers: + - '*' + Access-Control-Allow-Methods: + - GET, POST, PUT, DELETE, OPTIONS + Access-Control-Allow-Origin: + - '*' + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Tue, 24 Aug 2021 18:09:33 GMT Server: - nginx/1.20.1 Transfer-Encoding: @@ -134,7 +177,49 @@ interactions: Content-Type: - application/json Date: - - Sat, 21 Aug 2021 19:42:50 GMT + - Tue, 24 Aug 2021 18:09:33 GMT + Server: + - nginx/1.20.1 + Transfer-Encoding: + - chunked + X-Powered-By: + - PHP/8.0.9 + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-requests/2.26.0 + accept: + - application/json + method: GET + uri: https://localhost/api/objects/meal_plan_sections?query%5B%5D=id%3D-1 + response: + body: + string: !!binary | + H4sIAAAAAAAEAyXKwQqAIAwA0F+JnRWcXWS/EiGWO3hQYy46RP9e0Pm95YaSgSwaaKkyEICB0UVj + O+vG8pP0K+7CSTlHLZWHpnp81zuP1gXr58kFwpkwwLO+Gg7z9FQAAAA= + headers: + Access-Control-Allow-Headers: + - '*' + Access-Control-Allow-Methods: + - GET, POST, PUT, DELETE, OPTIONS + Access-Control-Allow-Origin: + - '*' + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Tue, 24 Aug 2021 18:09:34 GMT Server: - nginx/1.20.1 Transfer-Encoding: @@ -184,7 +269,49 @@ interactions: Content-Type: - application/json Date: - - Sat, 21 Aug 2021 19:42:50 GMT + - Tue, 24 Aug 2021 18:09:34 GMT + Server: + - nginx/1.20.1 + Transfer-Encoding: + - chunked + X-Powered-By: + - PHP/8.0.9 + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-requests/2.26.0 + accept: + - application/json + method: GET + uri: https://localhost/api/objects/meal_plan_sections?query%5B%5D=id%3D-1 + response: + body: + string: !!binary | + H4sIAAAAAAAEAyXKwQqAIAwA0F+JnRWcXWS/EiGWO3hQYy46RP9e0Pm95YaSgSwaaKkyEICB0UVj + O+vG8pP0K+7CSTlHLZWHpnp81zuP1gXr58kFwpkwwLO+Gg7z9FQAAAA= + headers: + Access-Control-Allow-Headers: + - '*' + Access-Control-Allow-Methods: + - GET, POST, PUT, DELETE, OPTIONS + Access-Control-Allow-Origin: + - '*' + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Tue, 24 Aug 2021 18:09:34 GMT Server: - nginx/1.20.1 Transfer-Encoding: @@ -234,7 +361,49 @@ interactions: Content-Type: - application/json Date: - - Sat, 21 Aug 2021 19:42:50 GMT + - Tue, 24 Aug 2021 18:09:34 GMT + Server: + - nginx/1.20.1 + Transfer-Encoding: + - chunked + X-Powered-By: + - PHP/8.0.9 + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-requests/2.26.0 + accept: + - application/json + method: GET + uri: https://localhost/api/objects/meal_plan_sections?query%5B%5D=id%3D-1 + response: + body: + string: !!binary | + H4sIAAAAAAAEAyXKwQqAIAwA0F+JnRWcXWS/EiGWO3hQYy46RP9e0Pm95YaSgSwaaKkyEICB0UVj + O+vG8pP0K+7CSTlHLZWHpnp81zuP1gXr58kFwpkwwLO+Gg7z9FQAAAA= + headers: + Access-Control-Allow-Headers: + - '*' + Access-Control-Allow-Methods: + - GET, POST, PUT, DELETE, OPTIONS + Access-Control-Allow-Origin: + - '*' + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Tue, 24 Aug 2021 18:09:34 GMT Server: - nginx/1.20.1 Transfer-Encoding: @@ -284,7 +453,49 @@ interactions: Content-Type: - application/json Date: - - Sat, 21 Aug 2021 19:42:51 GMT + - Tue, 24 Aug 2021 18:09:34 GMT + Server: + - nginx/1.20.1 + Transfer-Encoding: + - chunked + X-Powered-By: + - PHP/8.0.9 + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-requests/2.26.0 + accept: + - application/json + method: GET + uri: https://localhost/api/objects/meal_plan_sections?query%5B%5D=id%3D-1 + response: + body: + string: !!binary | + H4sIAAAAAAAEAyXKwQqAIAwA0F+JnRWcXWS/EiGWO3hQYy46RP9e0Pm95YaSgSwaaKkyEICB0UVj + O+vG8pP0K+7CSTlHLZWHpnp81zuP1gXr58kFwpkwwLO+Gg7z9FQAAAA= + headers: + Access-Control-Allow-Headers: + - '*' + Access-Control-Allow-Methods: + - GET, POST, PUT, DELETE, OPTIONS + Access-Control-Allow-Origin: + - '*' + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Tue, 24 Aug 2021 18:09:34 GMT Server: - nginx/1.20.1 Transfer-Encoding: @@ -334,7 +545,49 @@ interactions: Content-Type: - application/json Date: - - Sat, 21 Aug 2021 19:42:51 GMT + - Tue, 24 Aug 2021 18:09:34 GMT + Server: + - nginx/1.20.1 + Transfer-Encoding: + - chunked + X-Powered-By: + - PHP/8.0.9 + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-requests/2.26.0 + accept: + - application/json + method: GET + uri: https://localhost/api/objects/meal_plan_sections?query%5B%5D=id%3D-1 + response: + body: + string: !!binary | + H4sIAAAAAAAEAyXKwQqAIAwA0F+JnRWcXWS/EiGWO3hQYy46RP9e0Pm95YaSgSwaaKkyEICB0UVj + O+vG8pP0K+7CSTlHLZWHpnp81zuP1gXr58kFwpkwwLO+Gg7z9FQAAAA= + headers: + Access-Control-Allow-Headers: + - '*' + Access-Control-Allow-Methods: + - GET, POST, PUT, DELETE, OPTIONS + Access-Control-Allow-Origin: + - '*' + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Tue, 24 Aug 2021 18:09:34 GMT Server: - nginx/1.20.1 Transfer-Encoding: @@ -384,7 +637,183 @@ interactions: Content-Type: - application/json Date: - - Sat, 21 Aug 2021 19:42:51 GMT + - Tue, 24 Aug 2021 18:09:34 GMT + Server: + - nginx/1.20.1 + Transfer-Encoding: + - chunked + X-Powered-By: + - PHP/8.0.9 + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-requests/2.26.0 + accept: + - application/json + method: GET + uri: https://localhost/api/objects/meal_plan_sections?query%5B%5D=id%3D-1 + response: + body: + string: !!binary | + H4sIAAAAAAAEAyXKwQqAIAwA0F+JnRWcXWS/EiGWO3hQYy46RP9e0Pm95YaSgSwaaKkyEICB0UVj + O+vG8pP0K+7CSTlHLZWHpnp81zuP1gXr58kFwpkwwLO+Gg7z9FQAAAA= + headers: + Access-Control-Allow-Headers: + - '*' + Access-Control-Allow-Methods: + - GET, POST, PUT, DELETE, OPTIONS + Access-Control-Allow-Origin: + - '*' + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Tue, 24 Aug 2021 18:09:35 GMT + Server: + - nginx/1.20.1 + Transfer-Encoding: + - chunked + X-Powered-By: + - PHP/8.0.9 + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-requests/2.26.0 + accept: + - application/json + method: GET + uri: https://localhost/api/objects/meal_plan_sections?query%5B%5D=id%3D-1 + response: + body: + string: !!binary | + H4sIAAAAAAAEAyXKwQqAIAwA0F+JnRWcXWS/EiGWO3hQYy46RP9e0Pm95YaSgSwaaKkyEICB0UVj + O+vG8pP0K+7CSTlHLZWHpnp81zuP1gXr58kFwpkwwLO+Gg7z9FQAAAA= + headers: + Access-Control-Allow-Headers: + - '*' + Access-Control-Allow-Methods: + - GET, POST, PUT, DELETE, OPTIONS + Access-Control-Allow-Origin: + - '*' + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Tue, 24 Aug 2021 18:09:35 GMT + Server: + - nginx/1.20.1 + Transfer-Encoding: + - chunked + X-Powered-By: + - PHP/8.0.9 + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-requests/2.26.0 + accept: + - application/json + method: GET + uri: https://localhost/api/objects/recipes/2 + response: + body: + string: !!binary | + H4sIAAAAAAAEA7WVTW/bMAyG/wqh0wY4aZKuA+Z5BnbfrdcAhiyxjhp9TaIyBMP++yi3KNIOWIHO + OzgHiXzIvKZf/hRGi3bXCC8dilbcRjkdkMjAGGyYPGYUjdCYVTKRTPAc0x22/beQ0IGJubhuf8UH + Xbw8g27sNQMSZEMcMPbAfGpABZ+RkArfSM35yvgJ0BpKDWTUoI104IMv7gxokgsaCF1kkvEno4sn + KARWjtwAIMFcBcHJyUuQ1nwvZwZgklzsiXcKtkQqcg1fCU6YAmDINVsqVXKNJ7gvmQLowk9tHOd7 + lJBwLG4Nt9w0KO5TwlFmDVMZMU0JfcPdciUJJI/GSb7P0isqnJ8JLnR64FZBZi3Wf7l7plOXo/SQ + 6Wzxy16MUh2nFIrXK1X7bCFN47vdzU0D88/m/ee96F9qy6+gUvoLTf6bxlwr9l2xfWdN/1a9GcLZ + lbC88I/s/VXtcZlZ3qe9f6n527SupKVmurL+fa4rZaHZrqg/fGDR+Z7/8tN3/9YZn/tcxEsqaSk/ + qaxXPGX+9tiwU/gxqISSUA9kHGdJF9m6d5vddrXZrbbXsP3Ufti21x85Ohr2q4TDnbE4PC4CtouH + RbC+jxPHjDLjkDGd2K+zaLfzUjCJ+c8OfaBBHVAdh3wIMXKwNZlEu2kEnWPdMD4kJ22tmoIuioa6 + gHyxthGFUXcGreYC9eTXb5+DW9eeBgAA + headers: + Access-Control-Allow-Headers: + - '*' + Access-Control-Allow-Methods: + - GET, POST, PUT, DELETE, OPTIONS + Access-Control-Allow-Origin: + - '*' + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Tue, 24 Aug 2021 18:09:35 GMT + Server: + - nginx/1.20.1 + Transfer-Encoding: + - chunked + X-Powered-By: + - PHP/8.0.9 + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-requests/2.26.0 + accept: + - application/json + method: GET + uri: https://localhost/api/objects/meal_plan_sections?query%5B%5D=id%3D1 + response: + body: + string: !!binary | + H4sIAAAAAAAEAw3LwQpAQBAG4FfRf15lh8IcvYa0LUZJi3ZHDvLunL++/sE6g63B7oOA0UXx2+KT + wiAdUd1+hVEiuDSIx+2m31Vmp2uQpD6c/6GCbF40OVWZrblqmQjv8AEGfHFxWwAAAA== + headers: + Access-Control-Allow-Headers: + - '*' + Access-Control-Allow-Methods: + - GET, POST, PUT, DELETE, OPTIONS + Access-Control-Allow-Origin: + - '*' + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Tue, 24 Aug 2021 18:09:35 GMT Server: - nginx/1.20.1 Transfer-Encoding: diff --git a/test/cassettes/test_meal_plan_sections/TestMealPlanSections.test_get_section_by_id_invalid.yaml b/test/cassettes/test_meal_plan_sections/TestMealPlanSections.test_get_section_by_id_invalid.yaml new file mode 100644 index 0000000..edf76ff --- /dev/null +++ b/test/cassettes/test_meal_plan_sections/TestMealPlanSections.test_get_section_by_id_invalid.yaml @@ -0,0 +1,43 @@ +interactions: +- request: + body: null + headers: + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-requests/2.26.0 + accept: + - application/json + method: GET + uri: https://localhost/api/objects/meal_plan_sections?query%5B%5D=id%3D1000 + response: + body: + string: !!binary | + H4sIAAAAAAAEA4uOBQApu0wNAgAAAA== + headers: + Access-Control-Allow-Headers: + - '*' + Access-Control-Allow-Methods: + - GET, POST, PUT, DELETE, OPTIONS + Access-Control-Allow-Origin: + - '*' + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Tue, 24 Aug 2021 17:57:24 GMT + Server: + - nginx/1.20.1 + Transfer-Encoding: + - chunked + X-Powered-By: + - PHP/8.0.9 + status: + code: 200 + message: OK +version: 1 diff --git a/test/cassettes/test_meal_plan_sections/TestMealPlanSections.test_get_section_by_id_valid.yaml b/test/cassettes/test_meal_plan_sections/TestMealPlanSections.test_get_section_by_id_valid.yaml new file mode 100644 index 0000000..9287794 --- /dev/null +++ b/test/cassettes/test_meal_plan_sections/TestMealPlanSections.test_get_section_by_id_valid.yaml @@ -0,0 +1,44 @@ +interactions: +- request: + body: null + headers: + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-requests/2.26.0 + accept: + - application/json + method: GET + uri: https://localhost/api/objects/meal_plan_sections?query%5B%5D=id%3D1 + response: + body: + string: !!binary | + H4sIAAAAAAAEAw3LwQpAQBAG4FfRf15lh8IcvYa0LUZJi3ZHDvLunL++/sE6g63B7oOA0UXx2+KT + wiAdUd1+hVEiuDSIx+2m31Vmp2uQpD6c/6GCbF40OVWZrblqmQjv8AEGfHFxWwAAAA== + headers: + Access-Control-Allow-Headers: + - '*' + Access-Control-Allow-Methods: + - GET, POST, PUT, DELETE, OPTIONS + Access-Control-Allow-Origin: + - '*' + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Tue, 24 Aug 2021 17:57:00 GMT + Server: + - nginx/1.20.1 + Transfer-Encoding: + - chunked + X-Powered-By: + - PHP/8.0.9 + status: + code: 200 + message: OK +version: 1 diff --git a/test/cassettes/test_meal_plan_sections/TestMealPlanSections.test_get_sections_valid.yaml b/test/cassettes/test_meal_plan_sections/TestMealPlanSections.test_get_sections_valid.yaml new file mode 100644 index 0000000..904c0e7 --- /dev/null +++ b/test/cassettes/test_meal_plan_sections/TestMealPlanSections.test_get_sections_valid.yaml @@ -0,0 +1,45 @@ +interactions: +- request: + body: null + headers: + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-requests/2.26.0 + accept: + - application/json + method: GET + uri: https://localhost/api/objects/meal_plan_sections + response: + body: + string: !!binary | + H4sIAAAAAAAEA4uuVspMUbLSNdRRykvMTVWyUlLSUSrOLyqJzyvNTUotgkgV5ZfHJxelJpakpsSX + ZOamFpck5hYA1RoZGBnqGljoGhkrGFhYGRpbGVoo1epAjESY6ATUmZ2WWFyCbrSxjhJBk00UDM2t + TCytjIyUamMBqqo+Xq4AAAA= + headers: + Access-Control-Allow-Headers: + - '*' + Access-Control-Allow-Methods: + - GET, POST, PUT, DELETE, OPTIONS + Access-Control-Allow-Origin: + - '*' + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Tue, 24 Aug 2021 17:49:32 GMT + Server: + - nginx/1.20.1 + Transfer-Encoding: + - chunked + X-Powered-By: + - PHP/8.0.9 + status: + code: 200 + message: OK +version: 1 diff --git a/test/test_meal_plan.py b/test/test_meal_plan.py index 1afee8b..e03d3e4 100644 --- a/test/test_meal_plan.py +++ b/test/test_meal_plan.py @@ -1,6 +1,6 @@ import pytest -from pygrocy.data_models.meal_items import RecipeItem +from pygrocy.data_models.meal_items import MealPlanSection, RecipeItem class TestMealPlan: @@ -20,7 +20,7 @@ def test_get_meal_plan_valid(self, grocy): def test_get_meal_plan_with_details_valid(self, grocy): meal_plan = grocy.meal_plan(get_details=True) - assert len(meal_plan) == 8 + assert len(meal_plan) == 9 item = next(item for item in meal_plan if item.id == 1) assert item.day.day == 8 assert item.recipe_id == 1 @@ -35,3 +35,6 @@ def test_get_meal_plan_with_details_valid(self, grocy): assert recipe.base_servings == 1 assert recipe.description[:4] == "

" assert recipe.picture_file_name == "pizza.jpg" + + section_item = next(item for item in meal_plan if item.section_id == 1) + assert isinstance(section_item.section, MealPlanSection) diff --git a/test/test_meal_plan_sections.py b/test/test_meal_plan_sections.py new file mode 100644 index 0000000..7d12268 --- /dev/null +++ b/test/test_meal_plan_sections.py @@ -0,0 +1,27 @@ +import datetime + +import pytest + + +class TestMealPlanSections: + @pytest.mark.vcr + def test_get_sections_valid(self, grocy): + sections = grocy.meal_plan_sections() + + assert len(sections) == 2 + section = sections[1] + assert section.id == 1 + assert section.name == "Breakfast" + assert section.sort_number == 3 + assert isinstance(section.row_created_timestamp, datetime.datetime) + + @pytest.mark.vcr + def test_get_section_by_id_valid(self, grocy): + section = grocy.meal_plan_section(1) + + assert section.id == 1 + + @pytest.mark.vcr + def test_get_section_by_id_invalid(self, grocy): + section = grocy.meal_plan_section(1000) + assert section is None From 4875fcd1d0d3b1f15c8c5e3408f77d133758826c Mon Sep 17 00:00:00 2001 From: Sebastian Rutofski Date: Tue, 31 Aug 2021 13:03:55 +0200 Subject: [PATCH 2/8] add basic support for product and note type (#183) --- pygrocy/data_models/meal_items.py | 17 + pygrocy/grocy_api_client.py | 8 + ...MealPlan.test_get_meal_plan_with_note.yaml | 48 + ...t_get_meal_plan_with_note_and_details.yaml | 868 +++++++++++++++++ ...lPlan.test_get_meal_plan_with_product.yaml | 911 ++++++++++++++++++ test/test_meal_plan.py | 20 +- 6 files changed, 1871 insertions(+), 1 deletion(-) create mode 100644 test/cassettes/test_meal_plan/TestMealPlan.test_get_meal_plan_with_note.yaml create mode 100644 test/cassettes/test_meal_plan/TestMealPlan.test_get_meal_plan_with_note_and_details.yaml create mode 100644 test/cassettes/test_meal_plan/TestMealPlan.test_get_meal_plan_with_product.yaml diff --git a/pygrocy/data_models/meal_items.py b/pygrocy/data_models/meal_items.py index 36c20ee..1437407 100644 --- a/pygrocy/data_models/meal_items.py +++ b/pygrocy/data_models/meal_items.py @@ -1,5 +1,6 @@ import base64 from datetime import datetime +from enum import Enum from pygrocy.base import DataModel from pygrocy.grocy_api_client import ( @@ -75,6 +76,12 @@ def row_created_timestamp(self) -> datetime: return self._row_created_timestamp +class MealPlanItemType(str, Enum): + NOTE = "note" + PRODUCT = "product" + RECIPE = "recipe" + + class MealPlanItem(DataModel): def __init__(self, response: MealPlanResponse): self._id = response.id @@ -84,6 +91,8 @@ def __init__(self, response: MealPlanResponse): self._recipe_servings = response.recipe_servings self._note = response.note self._section_id = response.section_id + self._type = MealPlanItemType(response.type) + self._product_id = response.product_id @property def id(self) -> int: @@ -117,6 +126,14 @@ def section_id(self) -> int: def section(self) -> MealPlanSection: return self._section + @property + def type(self) -> MealPlanItemType: + return self._type + + @property + def product_id(self) -> int: + return self._product_id + def get_details(self, api_client: GrocyApiClient): if self.recipe_id: recipe = api_client.get_recipe(self.recipe_id) diff --git a/pygrocy/grocy_api_client.py b/pygrocy/grocy_api_client.py index 978a358..5364042 100644 --- a/pygrocy/grocy_api_client.py +++ b/pygrocy/grocy_api_client.py @@ -95,6 +95,14 @@ def note(self) -> str: def section_id(self) -> int: return self._section_id + @property + def type(self) -> str: + return self._type + + @property + def product_id(self) -> int: + return self._product_id + class RecipeDetailsResponse(object): def __init__(self, parsed_json): diff --git a/test/cassettes/test_meal_plan/TestMealPlan.test_get_meal_plan_with_note.yaml b/test/cassettes/test_meal_plan/TestMealPlan.test_get_meal_plan_with_note.yaml new file mode 100644 index 0000000..c2c9d19 --- /dev/null +++ b/test/cassettes/test_meal_plan/TestMealPlan.test_get_meal_plan_with_note.yaml @@ -0,0 +1,48 @@ +interactions: +- request: + body: null + headers: + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-requests/2.26.0 + accept: + - application/json + method: GET + uri: https://localhost/api/objects/meal_plan + response: + body: + string: !!binary | + H4sIAAAAAAAEA9WW22qEMBCGX0VyrZCJutU8R+9KEdG0DayJ1bjLUvruHQ9ss5ttCoXSBrwIw/yT + +cgcfHgjsiUcYtLWJ8IJowwSyhJakJiYUy/QNohG4iHeDtUmWM3VKIaDVM/jEkRpgwo17fcx6Qfd + To1Z3C8tdacnZQinn06vk+U36GPVDKI2oq2M7MRo6q63koM0gpJnwNMdZtVqhXdirFE0Rmq1BErg + PV7RmINW+tFQEAhaeo0G1I+GgkDQMgcN/GgoCAQtd9CYHw2bMxC0nYOW+tHC6bU7By3zo4VTkMU1 + GrUm5DLRzwU4j/51ln9dkeT+RY4RfnW0if/JJigvOYuEfdN4f1idmFwWQcEp8HzuoZtL7rzjALef + vb9RbTXeD57Q6KcJb/2th8P88ghmNL78aNykwxX++AEo0x9inwgAAA== + headers: + Access-Control-Allow-Headers: + - '*' + Access-Control-Allow-Methods: + - GET, POST, PUT, DELETE, OPTIONS + Access-Control-Allow-Origin: + - '*' + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Wed, 25 Aug 2021 11:53:54 GMT + Server: + - nginx/1.20.1 + Transfer-Encoding: + - chunked + X-Powered-By: + - PHP/8.0.9 + status: + code: 200 + message: OK +version: 1 diff --git a/test/cassettes/test_meal_plan/TestMealPlan.test_get_meal_plan_with_note_and_details.yaml b/test/cassettes/test_meal_plan/TestMealPlan.test_get_meal_plan_with_note_and_details.yaml new file mode 100644 index 0000000..6f05396 --- /dev/null +++ b/test/cassettes/test_meal_plan/TestMealPlan.test_get_meal_plan_with_note_and_details.yaml @@ -0,0 +1,868 @@ +interactions: +- request: + body: null + headers: + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-requests/2.26.0 + accept: + - application/json + method: GET + uri: https://localhost/api/objects/meal_plan + response: + body: + string: !!binary | + H4sIAAAAAAAEA9WW22qEMBCGX0VyrZCJutU8R+9KEdG0DayJ1bjLUvruHQ9ss5ttCoXSBrwIw/yT + +cgcfHgjsiUcYtLWJ8IJowwSyhJakJiYUy/QNohG4iHeDtUmWM3VKIaDVM/jEkRpgwo17fcx6Qfd + To1Z3C8tdacnZQinn06vk+U36GPVDKI2oq2M7MRo6q63koM0gpJnwNMdZtVqhXdirFE0Rmq1BErg + PV7RmINW+tFQEAhaeo0G1I+GgkDQMgcN/GgoCAQtd9CYHw2bMxC0nYOW+tHC6bU7By3zo4VTkMU1 + GrUm5DLRzwU4j/51ln9dkeT+RY4RfnW0if/JJigvOYuEfdN4f1idmFwWQcEp8HzuoZtL7rzjALef + vb9RbTXeD57Q6KcJb/2th8P88ghmNL78aNykwxX++AEo0x9inwgAAA== + headers: + Access-Control-Allow-Headers: + - '*' + Access-Control-Allow-Methods: + - GET, POST, PUT, DELETE, OPTIONS + Access-Control-Allow-Origin: + - '*' + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Wed, 25 Aug 2021 11:55:40 GMT + Server: + - nginx/1.20.1 + Transfer-Encoding: + - chunked + X-Powered-By: + - PHP/8.0.9 + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-requests/2.26.0 + accept: + - application/json + method: GET + uri: https://localhost/api/objects/recipes/1 + response: + body: + string: !!binary | + H4sIAAAAAAAEA7WVTW/TQBCG/8rIJ5CcNE4pEsZY4s4Biasla7w7dbbZL/YjKEX8d2ZNVbVFAimY + g3OY2Xlm8nr87vdKyapt6sqioaqtPqv7e6zqSlIUQfmknOVod2j6Ty6QAeVjNt1wxYHOP41BN/XS + aRcgqsQHph6YmGoQzkZKlDJnUHK9UHYG0iqFGiJJkAoNWGezOQOpYJyERMYzSdmTktkmyAk0TjwA + UIKlC4HB2SKgVl/zmQEUkJs98k5OZ58ybuFjghMFB+RiqUYhciznE9zlmBzIzE8ZnJY8IQSastnC + Fx4aBM+JcMQoYc4ThTmQrXla7oSQ8KgMcj6iFSlzfUzwRKdf3CLIosX2D7lnOnXRo4WYzpo+DNWE + 4jgHl63ciDJnC2GeXu1vbmpYfnav3w9V/1JbfgWF0j/R5L9pzL1832Xdd1r1l+rNEK4uhPWFf2AP + V2XGdXZ5CIN9qfllWhfSWjtdWP++14Wy0m4X1G8+sOp+L3/58bu/dMeXOVfxkkJay08K6y+esnx7 + bNjBfRtFIEwkx6QMV6HxbN373b7Z7Pab5hqad+2bpr1+y6e9Yr8KNN4qTeOD9fti/ds7P3N+wkhj + pHBir47L/cAXggrMfha0Lo3iQOI4xoPzng9rFVPV7uoqnX25T6wLBnXpGJzMIo3lurFZ67rKjLpV + pCU3KJEfPwFRig1KjAYAAA== + headers: + Access-Control-Allow-Headers: + - '*' + Access-Control-Allow-Methods: + - GET, POST, PUT, DELETE, OPTIONS + Access-Control-Allow-Origin: + - '*' + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Wed, 25 Aug 2021 11:55:40 GMT + Server: + - nginx/1.20.1 + Transfer-Encoding: + - chunked + X-Powered-By: + - PHP/8.0.9 + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-requests/2.26.0 + accept: + - application/json + method: GET + uri: https://localhost/api/objects/meal_plan_sections?query%5B%5D=id%3D-1 + response: + body: + string: !!binary | + H4sIAAAAAAAEAyXKwQqAIAwA0F+JnRWcXWS/EiGWO3hQYy46RP9e0Pm95YaSgSwaaKkyEICB0UVj + O+vG8pP0K+7CSTlHLZWHpnp81zuP1gXr58kFwpkwwLO+Gg7z9FQAAAA= + headers: + Access-Control-Allow-Headers: + - '*' + Access-Control-Allow-Methods: + - GET, POST, PUT, DELETE, OPTIONS + Access-Control-Allow-Origin: + - '*' + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Wed, 25 Aug 2021 11:55:40 GMT + Server: + - nginx/1.20.1 + Transfer-Encoding: + - chunked + X-Powered-By: + - PHP/8.0.9 + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-requests/2.26.0 + accept: + - application/json + method: GET + uri: https://localhost/api/objects/recipes/2 + response: + body: + string: !!binary | + H4sIAAAAAAAEA7WVTW/bMAyG/wqh0wY4aZKuA+Z5BnbfrdcAhiyxjhp9TaIyBMP++yi3KNIOWIHO + OzgHiXzIvKZf/hRGi3bXCC8dilbcRjkdkMjAGGyYPGYUjdCYVTKRTPAc0x22/beQ0IGJubhuf8UH + Xbw8g27sNQMSZEMcMPbAfGpABZ+RkArfSM35yvgJ0BpKDWTUoI104IMv7gxokgsaCF1kkvEno4sn + KARWjtwAIMFcBcHJyUuQ1nwvZwZgklzsiXcKtkQqcg1fCU6YAmDINVsqVXKNJ7gvmQLowk9tHOd7 + lJBwLG4Nt9w0KO5TwlFmDVMZMU0JfcPdciUJJI/GSb7P0isqnJ8JLnR64FZBZi3Wf7l7plOXo/SQ + 6Wzxy16MUh2nFIrXK1X7bCFN47vdzU0D88/m/ee96F9qy6+gUvoLTf6bxlwr9l2xfWdN/1a9GcLZ + lbC88I/s/VXtcZlZ3qe9f6n527SupKVmurL+fa4rZaHZrqg/fGDR+Z7/8tN3/9YZn/tcxEsqaSk/ + qaxXPGX+9tiwU/gxqISSUA9kHGdJF9m6d5vddrXZrbbXsP3Ufti21x85Ohr2q4TDnbE4PC4CtouH + RbC+jxPHjDLjkDGd2K+zaLfzUjCJ+c8OfaBBHVAdh3wIMXKwNZlEu2kEnWPdMD4kJ22tmoIuioa6 + gHyxthGFUXcGreYC9eTXb5+DW9eeBgAA + headers: + Access-Control-Allow-Headers: + - '*' + Access-Control-Allow-Methods: + - GET, POST, PUT, DELETE, OPTIONS + Access-Control-Allow-Origin: + - '*' + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Wed, 25 Aug 2021 11:55:40 GMT + Server: + - nginx/1.20.1 + Transfer-Encoding: + - chunked + X-Powered-By: + - PHP/8.0.9 + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-requests/2.26.0 + accept: + - application/json + method: GET + uri: https://localhost/api/objects/meal_plan_sections?query%5B%5D=id%3D-1 + response: + body: + string: !!binary | + H4sIAAAAAAAEAyXKwQqAIAwA0F+JnRWcXWS/EiGWO3hQYy46RP9e0Pm95YaSgSwaaKkyEICB0UVj + O+vG8pP0K+7CSTlHLZWHpnp81zuP1gXr58kFwpkwwLO+Gg7z9FQAAAA= + headers: + Access-Control-Allow-Headers: + - '*' + Access-Control-Allow-Methods: + - GET, POST, PUT, DELETE, OPTIONS + Access-Control-Allow-Origin: + - '*' + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Wed, 25 Aug 2021 11:55:40 GMT + Server: + - nginx/1.20.1 + Transfer-Encoding: + - chunked + X-Powered-By: + - PHP/8.0.9 + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-requests/2.26.0 + accept: + - application/json + method: GET + uri: https://localhost/api/objects/recipes/3 + response: + body: + string: !!binary | + H4sIAAAAAAAEA7VVTWvcQAz9K2JOLXg3+9EU6rqG3nvLdcHIM4p3svPV+diwlP73atwQkhQaSN2D + fRhJT8/PmqcfQivR7hvh0JJoxQ06da/lkZJohKIkow5Ze8eh7rjtv/lIFnRIxXaHKz7owtMz6MZe + eeMjJJ05YeyBYXMD0rtEmXLhCCqul9pNQEbn2EAiBUqjBeddsRcgHa1XkMkGRtLurFVxGUoGgyMT + AMowdyGwODkENPp7uTAAReRmj3hnb0rIBdfwNcOZogfyqVajlCXV/Ax3JWUPqvBTidMcJ4RIY7Fr + uGHSIJknwgmTgqmMFKdIrmG23Akh40lb5HhCJ3Ph+pThiU6/casgsxbrv8Se6dSlgA5Svhj6chAj + ytMUfXFqJSvPFuI0vttdXzcwvzbvPx9E/1Jb/gUVpX+iyX/TmHuFvium74zu36o3g3B1RVhe+Afs + w1XluMwsH+LBvdT8bVpXpKVmumL9+1xXlIVmu0L94QOLzvf8yY/3/q0zPvNcxEsq0lJ+UrFe8ZT5 + 7rFhR38/yEiYSQ1ZW65CG9i6d5vddrXZrbZ72H5qP2zb/UfODpr9KtJwqw0ND/7PJvbg/+u7MHHS + iImGRPHMhp1Eu523go7c4Nmh83ngrSFPQzr6EDjZ6JRFu2lEvoS6WZyPFk1tG70qMg918bhiTCMK + Q91qMoob1JOfvwBbCM5klgYAAA== + headers: + Access-Control-Allow-Headers: + - '*' + Access-Control-Allow-Methods: + - GET, POST, PUT, DELETE, OPTIONS + Access-Control-Allow-Origin: + - '*' + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Wed, 25 Aug 2021 11:55:40 GMT + Server: + - nginx/1.20.1 + Transfer-Encoding: + - chunked + X-Powered-By: + - PHP/8.0.9 + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-requests/2.26.0 + accept: + - application/json + method: GET + uri: https://localhost/api/objects/meal_plan_sections?query%5B%5D=id%3D-1 + response: + body: + string: !!binary | + H4sIAAAAAAAEAyXKwQqAIAwA0F+JnRWcXWS/EiGWO3hQYy46RP9e0Pm95YaSgSwaaKkyEICB0UVj + O+vG8pP0K+7CSTlHLZWHpnp81zuP1gXr58kFwpkwwLO+Gg7z9FQAAAA= + headers: + Access-Control-Allow-Headers: + - '*' + Access-Control-Allow-Methods: + - GET, POST, PUT, DELETE, OPTIONS + Access-Control-Allow-Origin: + - '*' + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Wed, 25 Aug 2021 11:55:40 GMT + Server: + - nginx/1.20.1 + Transfer-Encoding: + - chunked + X-Powered-By: + - PHP/8.0.9 + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-requests/2.26.0 + accept: + - application/json + method: GET + uri: https://localhost/api/objects/recipes/4 + response: + body: + string: !!binary | + H4sIAAAAAAAEA7WVTW/bMAyG/wqh0wY4aZK2A+Z5BnbfYcCuAQxZYh01+ppEZQiG/fdRXlGkHdAB + nXdwDiL5kHlNv/ohjBbtTSO8dCha8UV6JY+YRSM0ZpVMJBM8B7rDtv8cEjowMRfX7a/4oIuXZ9CN + vQ42JMiGOGHsgaHUgAo+IyEVjkjN9cr4CdAaSg1k1KCNdOCDL+4MaJILGghdZJLxJ6OLJygEVo48 + ACDB3AXByclLkNZ8K2cGYJLc7JF3CrZEKnINnwhOmAJgyLVaKlVyzSe4L5kC6MJPHRznOEpIOBa3 + hq88NCieU8JRZg1TGTFNCX3D03InCSSPxkmOZxaOCtdnggudfnOrILMW6xdiT3TqcpQeMp0tftyL + UarjlELxeqXqnC2kaXyzu71tYP7ZvP2wF/1zbfkVVEp/ocl/05h7xb4rtu+s6V+rN0O4uhKWF/6B + vb+qMy6zy/u09881f53WlbTUTlfWv+91pSy02xX1hw8sut/zX3787l+74/Oci3hJJS3lJ5X1F0+Z + vz027BS+DyqhJNQDGcdV0kW27t1mt11tdqvtNWzftzfb9vodZ0fDfpVwuDMWhwf3Z7eY3X99HydO + GWXGIWM6sV1n0W7nO8Ekxj859IEGdUB1HPIhxMjJ1mQS7aYRdI71VvEhOWlr0xR0UTTUS8cXaxtR + GHVn0GpuUE9+/gJlZaYokgYAAA== + headers: + Access-Control-Allow-Headers: + - '*' + Access-Control-Allow-Methods: + - GET, POST, PUT, DELETE, OPTIONS + Access-Control-Allow-Origin: + - '*' + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Wed, 25 Aug 2021 11:55:41 GMT + Server: + - nginx/1.20.1 + Transfer-Encoding: + - chunked + X-Powered-By: + - PHP/8.0.9 + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-requests/2.26.0 + accept: + - application/json + method: GET + uri: https://localhost/api/objects/meal_plan_sections?query%5B%5D=id%3D-1 + response: + body: + string: !!binary | + H4sIAAAAAAAEAyXKwQqAIAwA0F+JnRWcXWS/EiGWO3hQYy46RP9e0Pm95YaSgSwaaKkyEICB0UVj + O+vG8pP0K+7CSTlHLZWHpnp81zuP1gXr58kFwpkwwLO+Gg7z9FQAAAA= + headers: + Access-Control-Allow-Headers: + - '*' + Access-Control-Allow-Methods: + - GET, POST, PUT, DELETE, OPTIONS + Access-Control-Allow-Origin: + - '*' + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Wed, 25 Aug 2021 11:55:41 GMT + Server: + - nginx/1.20.1 + Transfer-Encoding: + - chunked + X-Powered-By: + - PHP/8.0.9 + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-requests/2.26.0 + accept: + - application/json + method: GET + uri: https://localhost/api/objects/recipes/1 + response: + body: + string: !!binary | + H4sIAAAAAAAEA7WVTW/TQBCG/8rIJ5CcNE4pEsZY4s4Biasla7w7dbbZL/YjKEX8d2ZNVbVFAimY + g3OY2Xlm8nr87vdKyapt6sqioaqtPqv7e6zqSlIUQfmknOVod2j6Ty6QAeVjNt1wxYHOP41BN/XS + aRcgqsQHph6YmGoQzkZKlDJnUHK9UHYG0iqFGiJJkAoNWGezOQOpYJyERMYzSdmTktkmyAk0TjwA + UIKlC4HB2SKgVl/zmQEUkJs98k5OZ58ybuFjghMFB+RiqUYhciznE9zlmBzIzE8ZnJY8IQSastnC + Fx4aBM+JcMQoYc4ThTmQrXla7oSQ8KgMcj6iFSlzfUzwRKdf3CLIosX2D7lnOnXRo4WYzpo+DNWE + 4jgHl63ciDJnC2GeXu1vbmpYfnav3w9V/1JbfgWF0j/R5L9pzL1832Xdd1r1l+rNEK4uhPWFf2AP + V2XGdXZ5CIN9qfllWhfSWjtdWP++14Wy0m4X1G8+sOp+L3/58bu/dMeXOVfxkkJay08K6y+esnx7 + bNjBfRtFIEwkx6QMV6HxbN373b7Z7Pab5hqad+2bpr1+y6e9Yr8KNN4qTeOD9fti/ds7P3N+wkhj + pHBir47L/cAXggrMfha0Lo3iQOI4xoPzng9rFVPV7uoqnX25T6wLBnXpGJzMIo3lurFZ67rKjLpV + pCU3KJEfPwFRig1KjAYAAA== + headers: + Access-Control-Allow-Headers: + - '*' + Access-Control-Allow-Methods: + - GET, POST, PUT, DELETE, OPTIONS + Access-Control-Allow-Origin: + - '*' + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Wed, 25 Aug 2021 11:55:41 GMT + Server: + - nginx/1.20.1 + Transfer-Encoding: + - chunked + X-Powered-By: + - PHP/8.0.9 + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-requests/2.26.0 + accept: + - application/json + method: GET + uri: https://localhost/api/objects/meal_plan_sections?query%5B%5D=id%3D-1 + response: + body: + string: !!binary | + H4sIAAAAAAAEAyXKwQqAIAwA0F+JnRWcXWS/EiGWO3hQYy46RP9e0Pm95YaSgSwaaKkyEICB0UVj + O+vG8pP0K+7CSTlHLZWHpnp81zuP1gXr58kFwpkwwLO+Gg7z9FQAAAA= + headers: + Access-Control-Allow-Headers: + - '*' + Access-Control-Allow-Methods: + - GET, POST, PUT, DELETE, OPTIONS + Access-Control-Allow-Origin: + - '*' + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Wed, 25 Aug 2021 11:55:41 GMT + Server: + - nginx/1.20.1 + Transfer-Encoding: + - chunked + X-Powered-By: + - PHP/8.0.9 + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-requests/2.26.0 + accept: + - application/json + method: GET + uri: https://localhost/api/objects/recipes/2 + response: + body: + string: !!binary | + H4sIAAAAAAAEA7WVTW/bMAyG/wqh0wY4aZKuA+Z5BnbfrdcAhiyxjhp9TaIyBMP++yi3KNIOWIHO + OzgHiXzIvKZf/hRGi3bXCC8dilbcRjkdkMjAGGyYPGYUjdCYVTKRTPAc0x22/beQ0IGJubhuf8UH + Xbw8g27sNQMSZEMcMPbAfGpABZ+RkArfSM35yvgJ0BpKDWTUoI104IMv7gxokgsaCF1kkvEno4sn + KARWjtwAIMFcBcHJyUuQ1nwvZwZgklzsiXcKtkQqcg1fCU6YAmDINVsqVXKNJ7gvmQLowk9tHOd7 + lJBwLG4Nt9w0KO5TwlFmDVMZMU0JfcPdciUJJI/GSb7P0isqnJ8JLnR64FZBZi3Wf7l7plOXo/SQ + 6Wzxy16MUh2nFIrXK1X7bCFN47vdzU0D88/m/ee96F9qy6+gUvoLTf6bxlwr9l2xfWdN/1a9GcLZ + lbC88I/s/VXtcZlZ3qe9f6n527SupKVmurL+fa4rZaHZrqg/fGDR+Z7/8tN3/9YZn/tcxEsqaSk/ + qaxXPGX+9tiwU/gxqISSUA9kHGdJF9m6d5vddrXZrbbXsP3Ufti21x85Ohr2q4TDnbE4PC4CtouH + RbC+jxPHjDLjkDGd2K+zaLfzUjCJ+c8OfaBBHVAdh3wIMXKwNZlEu2kEnWPdMD4kJ22tmoIuioa6 + gHyxthGFUXcGreYC9eTXb5+DW9eeBgAA + headers: + Access-Control-Allow-Headers: + - '*' + Access-Control-Allow-Methods: + - GET, POST, PUT, DELETE, OPTIONS + Access-Control-Allow-Origin: + - '*' + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Wed, 25 Aug 2021 11:55:41 GMT + Server: + - nginx/1.20.1 + Transfer-Encoding: + - chunked + X-Powered-By: + - PHP/8.0.9 + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-requests/2.26.0 + accept: + - application/json + method: GET + uri: https://localhost/api/objects/meal_plan_sections?query%5B%5D=id%3D-1 + response: + body: + string: !!binary | + H4sIAAAAAAAEAyXKwQqAIAwA0F+JnRWcXWS/EiGWO3hQYy46RP9e0Pm95YaSgSwaaKkyEICB0UVj + O+vG8pP0K+7CSTlHLZWHpnp81zuP1gXr58kFwpkwwLO+Gg7z9FQAAAA= + headers: + Access-Control-Allow-Headers: + - '*' + Access-Control-Allow-Methods: + - GET, POST, PUT, DELETE, OPTIONS + Access-Control-Allow-Origin: + - '*' + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Wed, 25 Aug 2021 11:55:41 GMT + Server: + - nginx/1.20.1 + Transfer-Encoding: + - chunked + X-Powered-By: + - PHP/8.0.9 + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-requests/2.26.0 + accept: + - application/json + method: GET + uri: https://localhost/api/objects/recipes/4 + response: + body: + string: !!binary | + H4sIAAAAAAAEA7WVTW/bMAyG/wqh0wY4aZK2A+Z5BnbfYcCuAQxZYh01+ppEZQiG/fdRXlGkHdAB + nXdwDiL5kHlNv/ohjBbtTSO8dCha8UV6JY+YRSM0ZpVMJBM8B7rDtv8cEjowMRfX7a/4oIuXZ9CN + vQ42JMiGOGHsgaHUgAo+IyEVjkjN9cr4CdAaSg1k1KCNdOCDL+4MaJILGghdZJLxJ6OLJygEVo48 + ACDB3AXByclLkNZ8K2cGYJLc7JF3CrZEKnINnwhOmAJgyLVaKlVyzSe4L5kC6MJPHRznOEpIOBa3 + hq88NCieU8JRZg1TGTFNCX3D03InCSSPxkmOZxaOCtdnggudfnOrILMW6xdiT3TqcpQeMp0tftyL + UarjlELxeqXqnC2kaXyzu71tYP7ZvP2wF/1zbfkVVEp/ocl/05h7xb4rtu+s6V+rN0O4uhKWF/6B + vb+qMy6zy/u09881f53WlbTUTlfWv+91pSy02xX1hw8sut/zX3787l+74/Oci3hJJS3lJ5X1F0+Z + vz027BS+DyqhJNQDGcdV0kW27t1mt11tdqvtNWzftzfb9vodZ0fDfpVwuDMWhwf3Z7eY3X99HydO + GWXGIWM6sV1n0W7nO8Ekxj859IEGdUB1HPIhxMjJ1mQS7aYRdI71VvEhOWlr0xR0UTTUS8cXaxtR + GHVn0GpuUE9+/gJlZaYokgYAAA== + headers: + Access-Control-Allow-Headers: + - '*' + Access-Control-Allow-Methods: + - GET, POST, PUT, DELETE, OPTIONS + Access-Control-Allow-Origin: + - '*' + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Wed, 25 Aug 2021 11:55:41 GMT + Server: + - nginx/1.20.1 + Transfer-Encoding: + - chunked + X-Powered-By: + - PHP/8.0.9 + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-requests/2.26.0 + accept: + - application/json + method: GET + uri: https://localhost/api/objects/meal_plan_sections?query%5B%5D=id%3D-1 + response: + body: + string: !!binary | + H4sIAAAAAAAEAyXKwQqAIAwA0F+JnRWcXWS/EiGWO3hQYy46RP9e0Pm95YaSgSwaaKkyEICB0UVj + O+vG8pP0K+7CSTlHLZWHpnp81zuP1gXr58kFwpkwwLO+Gg7z9FQAAAA= + headers: + Access-Control-Allow-Headers: + - '*' + Access-Control-Allow-Methods: + - GET, POST, PUT, DELETE, OPTIONS + Access-Control-Allow-Origin: + - '*' + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Wed, 25 Aug 2021 11:55:41 GMT + Server: + - nginx/1.20.1 + Transfer-Encoding: + - chunked + X-Powered-By: + - PHP/8.0.9 + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-requests/2.26.0 + accept: + - application/json + method: GET + uri: https://localhost/api/objects/meal_plan_sections?query%5B%5D=id%3D-1 + response: + body: + string: !!binary | + H4sIAAAAAAAEAyXKwQqAIAwA0F+JnRWcXWS/EiGWO3hQYy46RP9e0Pm95YaSgSwaaKkyEICB0UVj + O+vG8pP0K+7CSTlHLZWHpnp81zuP1gXr58kFwpkwwLO+Gg7z9FQAAAA= + headers: + Access-Control-Allow-Headers: + - '*' + Access-Control-Allow-Methods: + - GET, POST, PUT, DELETE, OPTIONS + Access-Control-Allow-Origin: + - '*' + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Wed, 25 Aug 2021 11:55:41 GMT + Server: + - nginx/1.20.1 + Transfer-Encoding: + - chunked + X-Powered-By: + - PHP/8.0.9 + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-requests/2.26.0 + accept: + - application/json + method: GET + uri: https://localhost/api/objects/recipes/2 + response: + body: + string: !!binary | + H4sIAAAAAAAEA7WVTW/bMAyG/wqh0wY4aZKuA+Z5BnbfrdcAhiyxjhp9TaIyBMP++yi3KNIOWIHO + OzgHiXzIvKZf/hRGi3bXCC8dilbcRjkdkMjAGGyYPGYUjdCYVTKRTPAc0x22/beQ0IGJubhuf8UH + Xbw8g27sNQMSZEMcMPbAfGpABZ+RkArfSM35yvgJ0BpKDWTUoI104IMv7gxokgsaCF1kkvEno4sn + KARWjtwAIMFcBcHJyUuQ1nwvZwZgklzsiXcKtkQqcg1fCU6YAmDINVsqVXKNJ7gvmQLowk9tHOd7 + lJBwLG4Nt9w0KO5TwlFmDVMZMU0JfcPdciUJJI/GSb7P0isqnJ8JLnR64FZBZi3Wf7l7plOXo/SQ + 6Wzxy16MUh2nFIrXK1X7bCFN47vdzU0D88/m/ee96F9qy6+gUvoLTf6bxlwr9l2xfWdN/1a9GcLZ + lbC88I/s/VXtcZlZ3qe9f6n527SupKVmurL+fa4rZaHZrqg/fGDR+Z7/8tN3/9YZn/tcxEsqaSk/ + qaxXPGX+9tiwU/gxqISSUA9kHGdJF9m6d5vddrXZrbbXsP3Ufti21x85Ohr2q4TDnbE4PC4CtouH + RbC+jxPHjDLjkDGd2K+zaLfzUjCJ+c8OfaBBHVAdh3wIMXKwNZlEu2kEnWPdMD4kJ22tmoIuioa6 + gHyxthGFUXcGreYC9eTXb5+DW9eeBgAA + headers: + Access-Control-Allow-Headers: + - '*' + Access-Control-Allow-Methods: + - GET, POST, PUT, DELETE, OPTIONS + Access-Control-Allow-Origin: + - '*' + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Wed, 25 Aug 2021 11:55:41 GMT + Server: + - nginx/1.20.1 + Transfer-Encoding: + - chunked + X-Powered-By: + - PHP/8.0.9 + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-requests/2.26.0 + accept: + - application/json + method: GET + uri: https://localhost/api/objects/meal_plan_sections?query%5B%5D=id%3D1 + response: + body: + string: !!binary | + H4sIAAAAAAAEAw3LwQpAQBAG4FfRf15lh8IcvYa0LUZJi3ZHDvLunL++/sE6g63B7oOA0UXx2+KT + wiAdUd1+hVEiuDSIx+2m31Vmp2uQpD6c/6GCbF40OVWZrblqmQjv8AEGfHFxWwAAAA== + headers: + Access-Control-Allow-Headers: + - '*' + Access-Control-Allow-Methods: + - GET, POST, PUT, DELETE, OPTIONS + Access-Control-Allow-Origin: + - '*' + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Wed, 25 Aug 2021 11:55:42 GMT + Server: + - nginx/1.20.1 + Transfer-Encoding: + - chunked + X-Powered-By: + - PHP/8.0.9 + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-requests/2.26.0 + accept: + - application/json + method: GET + uri: https://localhost/api/objects/meal_plan_sections?query%5B%5D=id%3D-1 + response: + body: + string: !!binary | + H4sIAAAAAAAEAyXKwQqAIAwA0F+JnRWcXWS/EiGWO3hQYy46RP9e0Pm95YaSgSwaaKkyEICB0UVj + O+vG8pP0K+7CSTlHLZWHpnp81zuP1gXr58kFwpkwwLO+Gg7z9FQAAAA= + headers: + Access-Control-Allow-Headers: + - '*' + Access-Control-Allow-Methods: + - GET, POST, PUT, DELETE, OPTIONS + Access-Control-Allow-Origin: + - '*' + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Wed, 25 Aug 2021 11:55:42 GMT + Server: + - nginx/1.20.1 + Transfer-Encoding: + - chunked + X-Powered-By: + - PHP/8.0.9 + status: + code: 200 + message: OK +version: 1 diff --git a/test/cassettes/test_meal_plan/TestMealPlan.test_get_meal_plan_with_product.yaml b/test/cassettes/test_meal_plan/TestMealPlan.test_get_meal_plan_with_product.yaml new file mode 100644 index 0000000..7101807 --- /dev/null +++ b/test/cassettes/test_meal_plan/TestMealPlan.test_get_meal_plan_with_product.yaml @@ -0,0 +1,911 @@ +interactions: +- request: + body: null + headers: + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-requests/2.26.0 + accept: + - application/json + method: GET + uri: https://localhost/api/objects/meal_plan + response: + body: + string: !!binary | + H4sIAAAAAAAEA9WW22qEMBCGX0VyrZDxVM1z9K4UEU1bYTVWY8tS9t078XzYpqVQugEvwpBJ5jMz + 88/DBylywsAmeXomjLjUBYe6Do2ITeS55mhreFbgwh4XyegwmJOWN29F9dz2h1RCokfVnU42qRuR + d5nst28taSm6ShJGl02v3WpfI96TrOGp5Hkii5K3Mi3rVXDgWRAzH5gXYlS5qPBOPKvlmSxE1R/k + wMUe0NwDWqxHQwdD0Lw9GlA9GjoYguYf0ECPhg6GoAUHNFePhsVpCFp4QPP0aObU2t0BzdejmZOQ + 0R6Nrjpk39HnBFStf+jlX2ckuX8pWgu/1Bqdb0QJ4i1n5LjfFN4/ZicG51sQMQosUDV0VeRmjQNU + v7V+o/eq8H7xhFI8dXjrXz0cxhdYoNBYP2hcpVskHHbjifo5S/GNQaJhzMmfJOl2IFEeWK8T7jSf + 4LWTaZxPlIBqh5OZLGSgFOsqGVwePwG8geHkeAkAAA== + headers: + Access-Control-Allow-Headers: + - '*' + Access-Control-Allow-Methods: + - GET, POST, PUT, DELETE, OPTIONS + Access-Control-Allow-Origin: + - '*' + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Wed, 25 Aug 2021 12:05:21 GMT + Server: + - nginx/1.20.1 + Transfer-Encoding: + - chunked + X-Powered-By: + - PHP/8.0.9 + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-requests/2.26.0 + accept: + - application/json + method: GET + uri: https://localhost/api/objects/recipes/1 + response: + body: + string: !!binary | + H4sIAAAAAAAEA7WVTW/TQBCG/8rIJ5CcNE4pEsZY4s4Biasla7w7dbbZL/YjKEX8d2ZNVbVFAimY + g3OY2Xlm8nr87vdKyapt6sqioaqtPqv7e6zqSlIUQfmknOVod2j6Ty6QAeVjNt1wxYHOP41BN/XS + aRcgqsQHph6YmGoQzkZKlDJnUHK9UHYG0iqFGiJJkAoNWGezOQOpYJyERMYzSdmTktkmyAk0TjwA + UIKlC4HB2SKgVl/zmQEUkJs98k5OZ58ybuFjghMFB+RiqUYhciznE9zlmBzIzE8ZnJY8IQSastnC + Fx4aBM+JcMQoYc4ThTmQrXla7oSQ8KgMcj6iFSlzfUzwRKdf3CLIosX2D7lnOnXRo4WYzpo+DNWE + 4jgHl63ciDJnC2GeXu1vbmpYfnav3w9V/1JbfgWF0j/R5L9pzL1832Xdd1r1l+rNEK4uhPWFf2AP + V2XGdXZ5CIN9qfllWhfSWjtdWP++14Wy0m4X1G8+sOp+L3/58bu/dMeXOVfxkkJay08K6y+esnx7 + bNjBfRtFIEwkx6QMV6HxbN373b7Z7Pab5hqad+2bpr1+y6e9Yr8KNN4qTeOD9fti/ds7P3N+wkhj + pHBir47L/cAXggrMfha0Lo3iQOI4xoPzng9rFVPV7uoqnX25T6wLBnXpGJzMIo3lurFZ67rKjLpV + pCU3KJEfPwFRig1KjAYAAA== + headers: + Access-Control-Allow-Headers: + - '*' + Access-Control-Allow-Methods: + - GET, POST, PUT, DELETE, OPTIONS + Access-Control-Allow-Origin: + - '*' + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Wed, 25 Aug 2021 12:05:21 GMT + Server: + - nginx/1.20.1 + Transfer-Encoding: + - chunked + X-Powered-By: + - PHP/8.0.9 + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-requests/2.26.0 + accept: + - application/json + method: GET + uri: https://localhost/api/objects/meal_plan_sections?query%5B%5D=id%3D-1 + response: + body: + string: !!binary | + H4sIAAAAAAAEAyXKwQqAIAwA0F+JnRWcXWS/EiGWO3hQYy46RP9e0Pm95YaSgSwaaKkyEICB0UVj + O+vG8pP0K+7CSTlHLZWHpnp81zuP1gXr58kFwpkwwLO+Gg7z9FQAAAA= + headers: + Access-Control-Allow-Headers: + - '*' + Access-Control-Allow-Methods: + - GET, POST, PUT, DELETE, OPTIONS + Access-Control-Allow-Origin: + - '*' + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Wed, 25 Aug 2021 12:05:21 GMT + Server: + - nginx/1.20.1 + Transfer-Encoding: + - chunked + X-Powered-By: + - PHP/8.0.9 + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-requests/2.26.0 + accept: + - application/json + method: GET + uri: https://localhost/api/objects/recipes/2 + response: + body: + string: !!binary | + H4sIAAAAAAAEA7WVTW/bMAyG/wqh0wY4aZKuA+Z5BnbfrdcAhiyxjhp9TaIyBMP++yi3KNIOWIHO + OzgHiXzIvKZf/hRGi3bXCC8dilbcRjkdkMjAGGyYPGYUjdCYVTKRTPAc0x22/beQ0IGJubhuf8UH + Xbw8g27sNQMSZEMcMPbAfGpABZ+RkArfSM35yvgJ0BpKDWTUoI104IMv7gxokgsaCF1kkvEno4sn + KARWjtwAIMFcBcHJyUuQ1nwvZwZgklzsiXcKtkQqcg1fCU6YAmDINVsqVXKNJ7gvmQLowk9tHOd7 + lJBwLG4Nt9w0KO5TwlFmDVMZMU0JfcPdciUJJI/GSb7P0isqnJ8JLnR64FZBZi3Wf7l7plOXo/SQ + 6Wzxy16MUh2nFIrXK1X7bCFN47vdzU0D88/m/ee96F9qy6+gUvoLTf6bxlwr9l2xfWdN/1a9GcLZ + lbC88I/s/VXtcZlZ3qe9f6n527SupKVmurL+fa4rZaHZrqg/fGDR+Z7/8tN3/9YZn/tcxEsqaSk/ + qaxXPGX+9tiwU/gxqISSUA9kHGdJF9m6d5vddrXZrbbXsP3Ufti21x85Ohr2q4TDnbE4PC4CtouH + RbC+jxPHjDLjkDGd2K+zaLfzUjCJ+c8OfaBBHVAdh3wIMXKwNZlEu2kEnWPdMD4kJ22tmoIuioa6 + gHyxthGFUXcGreYC9eTXb5+DW9eeBgAA + headers: + Access-Control-Allow-Headers: + - '*' + Access-Control-Allow-Methods: + - GET, POST, PUT, DELETE, OPTIONS + Access-Control-Allow-Origin: + - '*' + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Wed, 25 Aug 2021 12:05:21 GMT + Server: + - nginx/1.20.1 + Transfer-Encoding: + - chunked + X-Powered-By: + - PHP/8.0.9 + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-requests/2.26.0 + accept: + - application/json + method: GET + uri: https://localhost/api/objects/meal_plan_sections?query%5B%5D=id%3D-1 + response: + body: + string: !!binary | + H4sIAAAAAAAEAyXKwQqAIAwA0F+JnRWcXWS/EiGWO3hQYy46RP9e0Pm95YaSgSwaaKkyEICB0UVj + O+vG8pP0K+7CSTlHLZWHpnp81zuP1gXr58kFwpkwwLO+Gg7z9FQAAAA= + headers: + Access-Control-Allow-Headers: + - '*' + Access-Control-Allow-Methods: + - GET, POST, PUT, DELETE, OPTIONS + Access-Control-Allow-Origin: + - '*' + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Wed, 25 Aug 2021 12:05:21 GMT + Server: + - nginx/1.20.1 + Transfer-Encoding: + - chunked + X-Powered-By: + - PHP/8.0.9 + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-requests/2.26.0 + accept: + - application/json + method: GET + uri: https://localhost/api/objects/recipes/3 + response: + body: + string: !!binary | + H4sIAAAAAAAEA7VVTWvcQAz9K2JOLXg3+9EU6rqG3nvLdcHIM4p3svPV+diwlP73atwQkhQaSN2D + fRhJT8/PmqcfQivR7hvh0JJoxQ06da/lkZJohKIkow5Ze8eh7rjtv/lIFnRIxXaHKz7owtMz6MZe + eeMjJJ05YeyBYXMD0rtEmXLhCCqul9pNQEbn2EAiBUqjBeddsRcgHa1XkMkGRtLurFVxGUoGgyMT + AMowdyGwODkENPp7uTAAReRmj3hnb0rIBdfwNcOZogfyqVajlCXV/Ax3JWUPqvBTidMcJ4RIY7Fr + uGHSIJknwgmTgqmMFKdIrmG23Akh40lb5HhCJ3Ph+pThiU6/casgsxbrv8Se6dSlgA5Svhj6chAj + ytMUfXFqJSvPFuI0vttdXzcwvzbvPx9E/1Jb/gUVpX+iyX/TmHuFvium74zu36o3g3B1RVhe+Afs + w1XluMwsH+LBvdT8bVpXpKVmumL9+1xXlIVmu0L94QOLzvf8yY/3/q0zPvNcxEsq0lJ+UrFe8ZT5 + 7rFhR38/yEiYSQ1ZW65CG9i6d5vddrXZrbZ72H5qP2zb/UfODpr9KtJwqw0ND/7PJvbg/+u7MHHS + iImGRPHMhp1Eu523go7c4Nmh83ngrSFPQzr6EDjZ6JRFu2lEvoS6WZyPFk1tG70qMg918bhiTCMK + Q91qMoob1JOfvwBbCM5klgYAAA== + headers: + Access-Control-Allow-Headers: + - '*' + Access-Control-Allow-Methods: + - GET, POST, PUT, DELETE, OPTIONS + Access-Control-Allow-Origin: + - '*' + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Wed, 25 Aug 2021 12:05:21 GMT + Server: + - nginx/1.20.1 + Transfer-Encoding: + - chunked + X-Powered-By: + - PHP/8.0.9 + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-requests/2.26.0 + accept: + - application/json + method: GET + uri: https://localhost/api/objects/meal_plan_sections?query%5B%5D=id%3D-1 + response: + body: + string: !!binary | + H4sIAAAAAAAEAyXKwQqAIAwA0F+JnRWcXWS/EiGWO3hQYy46RP9e0Pm95YaSgSwaaKkyEICB0UVj + O+vG8pP0K+7CSTlHLZWHpnp81zuP1gXr58kFwpkwwLO+Gg7z9FQAAAA= + headers: + Access-Control-Allow-Headers: + - '*' + Access-Control-Allow-Methods: + - GET, POST, PUT, DELETE, OPTIONS + Access-Control-Allow-Origin: + - '*' + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Wed, 25 Aug 2021 12:05:22 GMT + Server: + - nginx/1.20.1 + Transfer-Encoding: + - chunked + X-Powered-By: + - PHP/8.0.9 + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-requests/2.26.0 + accept: + - application/json + method: GET + uri: https://localhost/api/objects/recipes/4 + response: + body: + string: !!binary | + H4sIAAAAAAAEA7WVTW/bMAyG/wqh0wY4aZK2A+Z5BnbfYcCuAQxZYh01+ppEZQiG/fdRXlGkHdAB + nXdwDiL5kHlNv/ohjBbtTSO8dCha8UV6JY+YRSM0ZpVMJBM8B7rDtv8cEjowMRfX7a/4oIuXZ9CN + vQ42JMiGOGHsgaHUgAo+IyEVjkjN9cr4CdAaSg1k1KCNdOCDL+4MaJILGghdZJLxJ6OLJygEVo48 + ACDB3AXByclLkNZ8K2cGYJLc7JF3CrZEKnINnwhOmAJgyLVaKlVyzSe4L5kC6MJPHRznOEpIOBa3 + hq88NCieU8JRZg1TGTFNCX3D03InCSSPxkmOZxaOCtdnggudfnOrILMW6xdiT3TqcpQeMp0tftyL + UarjlELxeqXqnC2kaXyzu71tYP7ZvP2wF/1zbfkVVEp/ocl/05h7xb4rtu+s6V+rN0O4uhKWF/6B + vb+qMy6zy/u09881f53WlbTUTlfWv+91pSy02xX1hw8sut/zX3787l+74/Oci3hJJS3lJ5X1F0+Z + vz027BS+DyqhJNQDGcdV0kW27t1mt11tdqvtNWzftzfb9vodZ0fDfpVwuDMWhwf3Z7eY3X99HydO + GWXGIWM6sV1n0W7nO8Ekxj859IEGdUB1HPIhxMjJ1mQS7aYRdI71VvEhOWlr0xR0UTTUS8cXaxtR + GHVn0GpuUE9+/gJlZaYokgYAAA== + headers: + Access-Control-Allow-Headers: + - '*' + Access-Control-Allow-Methods: + - GET, POST, PUT, DELETE, OPTIONS + Access-Control-Allow-Origin: + - '*' + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Wed, 25 Aug 2021 12:05:22 GMT + Server: + - nginx/1.20.1 + Transfer-Encoding: + - chunked + X-Powered-By: + - PHP/8.0.9 + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-requests/2.26.0 + accept: + - application/json + method: GET + uri: https://localhost/api/objects/meal_plan_sections?query%5B%5D=id%3D-1 + response: + body: + string: !!binary | + H4sIAAAAAAAEAyXKwQqAIAwA0F+JnRWcXWS/EiGWO3hQYy46RP9e0Pm95YaSgSwaaKkyEICB0UVj + O+vG8pP0K+7CSTlHLZWHpnp81zuP1gXr58kFwpkwwLO+Gg7z9FQAAAA= + headers: + Access-Control-Allow-Headers: + - '*' + Access-Control-Allow-Methods: + - GET, POST, PUT, DELETE, OPTIONS + Access-Control-Allow-Origin: + - '*' + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Wed, 25 Aug 2021 12:05:22 GMT + Server: + - nginx/1.20.1 + Transfer-Encoding: + - chunked + X-Powered-By: + - PHP/8.0.9 + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-requests/2.26.0 + accept: + - application/json + method: GET + uri: https://localhost/api/objects/recipes/1 + response: + body: + string: !!binary | + H4sIAAAAAAAEA7WVTW/TQBCG/8rIJ5CcNE4pEsZY4s4Biasla7w7dbbZL/YjKEX8d2ZNVbVFAimY + g3OY2Xlm8nr87vdKyapt6sqioaqtPqv7e6zqSlIUQfmknOVod2j6Ty6QAeVjNt1wxYHOP41BN/XS + aRcgqsQHph6YmGoQzkZKlDJnUHK9UHYG0iqFGiJJkAoNWGezOQOpYJyERMYzSdmTktkmyAk0TjwA + UIKlC4HB2SKgVl/zmQEUkJs98k5OZ58ybuFjghMFB+RiqUYhciznE9zlmBzIzE8ZnJY8IQSastnC + Fx4aBM+JcMQoYc4ThTmQrXla7oSQ8KgMcj6iFSlzfUzwRKdf3CLIosX2D7lnOnXRo4WYzpo+DNWE + 4jgHl63ciDJnC2GeXu1vbmpYfnav3w9V/1JbfgWF0j/R5L9pzL1832Xdd1r1l+rNEK4uhPWFf2AP + V2XGdXZ5CIN9qfllWhfSWjtdWP++14Wy0m4X1G8+sOp+L3/58bu/dMeXOVfxkkJay08K6y+esnx7 + bNjBfRtFIEwkx6QMV6HxbN373b7Z7Pab5hqad+2bpr1+y6e9Yr8KNN4qTeOD9fti/ds7P3N+wkhj + pHBir47L/cAXggrMfha0Lo3iQOI4xoPzng9rFVPV7uoqnX25T6wLBnXpGJzMIo3lurFZ67rKjLpV + pCU3KJEfPwFRig1KjAYAAA== + headers: + Access-Control-Allow-Headers: + - '*' + Access-Control-Allow-Methods: + - GET, POST, PUT, DELETE, OPTIONS + Access-Control-Allow-Origin: + - '*' + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Wed, 25 Aug 2021 12:05:22 GMT + Server: + - nginx/1.20.1 + Transfer-Encoding: + - chunked + X-Powered-By: + - PHP/8.0.9 + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-requests/2.26.0 + accept: + - application/json + method: GET + uri: https://localhost/api/objects/meal_plan_sections?query%5B%5D=id%3D-1 + response: + body: + string: !!binary | + H4sIAAAAAAAEAyXKwQqAIAwA0F+JnRWcXWS/EiGWO3hQYy46RP9e0Pm95YaSgSwaaKkyEICB0UVj + O+vG8pP0K+7CSTlHLZWHpnp81zuP1gXr58kFwpkwwLO+Gg7z9FQAAAA= + headers: + Access-Control-Allow-Headers: + - '*' + Access-Control-Allow-Methods: + - GET, POST, PUT, DELETE, OPTIONS + Access-Control-Allow-Origin: + - '*' + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Wed, 25 Aug 2021 12:05:22 GMT + Server: + - nginx/1.20.1 + Transfer-Encoding: + - chunked + X-Powered-By: + - PHP/8.0.9 + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-requests/2.26.0 + accept: + - application/json + method: GET + uri: https://localhost/api/objects/recipes/2 + response: + body: + string: !!binary | + H4sIAAAAAAAEA7WVTW/bMAyG/wqh0wY4aZKuA+Z5BnbfrdcAhiyxjhp9TaIyBMP++yi3KNIOWIHO + OzgHiXzIvKZf/hRGi3bXCC8dilbcRjkdkMjAGGyYPGYUjdCYVTKRTPAc0x22/beQ0IGJubhuf8UH + Xbw8g27sNQMSZEMcMPbAfGpABZ+RkArfSM35yvgJ0BpKDWTUoI104IMv7gxokgsaCF1kkvEno4sn + KARWjtwAIMFcBcHJyUuQ1nwvZwZgklzsiXcKtkQqcg1fCU6YAmDINVsqVXKNJ7gvmQLowk9tHOd7 + lJBwLG4Nt9w0KO5TwlFmDVMZMU0JfcPdciUJJI/GSb7P0isqnJ8JLnR64FZBZi3Wf7l7plOXo/SQ + 6Wzxy16MUh2nFIrXK1X7bCFN47vdzU0D88/m/ee96F9qy6+gUvoLTf6bxlwr9l2xfWdN/1a9GcLZ + lbC88I/s/VXtcZlZ3qe9f6n527SupKVmurL+fa4rZaHZrqg/fGDR+Z7/8tN3/9YZn/tcxEsqaSk/ + qaxXPGX+9tiwU/gxqISSUA9kHGdJF9m6d5vddrXZrbbXsP3Ufti21x85Ohr2q4TDnbE4PC4CtouH + RbC+jxPHjDLjkDGd2K+zaLfzUjCJ+c8OfaBBHVAdh3wIMXKwNZlEu2kEnWPdMD4kJ22tmoIuioa6 + gHyxthGFUXcGreYC9eTXb5+DW9eeBgAA + headers: + Access-Control-Allow-Headers: + - '*' + Access-Control-Allow-Methods: + - GET, POST, PUT, DELETE, OPTIONS + Access-Control-Allow-Origin: + - '*' + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Wed, 25 Aug 2021 12:05:22 GMT + Server: + - nginx/1.20.1 + Transfer-Encoding: + - chunked + X-Powered-By: + - PHP/8.0.9 + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-requests/2.26.0 + accept: + - application/json + method: GET + uri: https://localhost/api/objects/meal_plan_sections?query%5B%5D=id%3D-1 + response: + body: + string: !!binary | + H4sIAAAAAAAEAyXKwQqAIAwA0F+JnRWcXWS/EiGWO3hQYy46RP9e0Pm95YaSgSwaaKkyEICB0UVj + O+vG8pP0K+7CSTlHLZWHpnp81zuP1gXr58kFwpkwwLO+Gg7z9FQAAAA= + headers: + Access-Control-Allow-Headers: + - '*' + Access-Control-Allow-Methods: + - GET, POST, PUT, DELETE, OPTIONS + Access-Control-Allow-Origin: + - '*' + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Wed, 25 Aug 2021 12:05:22 GMT + Server: + - nginx/1.20.1 + Transfer-Encoding: + - chunked + X-Powered-By: + - PHP/8.0.9 + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-requests/2.26.0 + accept: + - application/json + method: GET + uri: https://localhost/api/objects/recipes/4 + response: + body: + string: !!binary | + H4sIAAAAAAAEA7WVTW/bMAyG/wqh0wY4aZK2A+Z5BnbfYcCuAQxZYh01+ppEZQiG/fdRXlGkHdAB + nXdwDiL5kHlNv/ohjBbtTSO8dCha8UV6JY+YRSM0ZpVMJBM8B7rDtv8cEjowMRfX7a/4oIuXZ9CN + vQ42JMiGOGHsgaHUgAo+IyEVjkjN9cr4CdAaSg1k1KCNdOCDL+4MaJILGghdZJLxJ6OLJygEVo48 + ACDB3AXByclLkNZ8K2cGYJLc7JF3CrZEKnINnwhOmAJgyLVaKlVyzSe4L5kC6MJPHRznOEpIOBa3 + hq88NCieU8JRZg1TGTFNCX3D03InCSSPxkmOZxaOCtdnggudfnOrILMW6xdiT3TqcpQeMp0tftyL + UarjlELxeqXqnC2kaXyzu71tYP7ZvP2wF/1zbfkVVEp/ocl/05h7xb4rtu+s6V+rN0O4uhKWF/6B + vb+qMy6zy/u09881f53WlbTUTlfWv+91pSy02xX1hw8sut/zX3787l+74/Oci3hJJS3lJ5X1F0+Z + vz027BS+DyqhJNQDGcdV0kW27t1mt11tdqvtNWzftzfb9vodZ0fDfpVwuDMWhwf3Z7eY3X99HydO + GWXGIWM6sV1n0W7nO8Ekxj859IEGdUB1HPIhxMjJ1mQS7aYRdI71VvEhOWlr0xR0UTTUS8cXaxtR + GHVn0GpuUE9+/gJlZaYokgYAAA== + headers: + Access-Control-Allow-Headers: + - '*' + Access-Control-Allow-Methods: + - GET, POST, PUT, DELETE, OPTIONS + Access-Control-Allow-Origin: + - '*' + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Wed, 25 Aug 2021 12:05:22 GMT + Server: + - nginx/1.20.1 + Transfer-Encoding: + - chunked + X-Powered-By: + - PHP/8.0.9 + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-requests/2.26.0 + accept: + - application/json + method: GET + uri: https://localhost/api/objects/meal_plan_sections?query%5B%5D=id%3D-1 + response: + body: + string: !!binary | + H4sIAAAAAAAEAyXKwQqAIAwA0F+JnRWcXWS/EiGWO3hQYy46RP9e0Pm95YaSgSwaaKkyEICB0UVj + O+vG8pP0K+7CSTlHLZWHpnp81zuP1gXr58kFwpkwwLO+Gg7z9FQAAAA= + headers: + Access-Control-Allow-Headers: + - '*' + Access-Control-Allow-Methods: + - GET, POST, PUT, DELETE, OPTIONS + Access-Control-Allow-Origin: + - '*' + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Wed, 25 Aug 2021 12:05:22 GMT + Server: + - nginx/1.20.1 + Transfer-Encoding: + - chunked + X-Powered-By: + - PHP/8.0.9 + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-requests/2.26.0 + accept: + - application/json + method: GET + uri: https://localhost/api/objects/meal_plan_sections?query%5B%5D=id%3D-1 + response: + body: + string: !!binary | + H4sIAAAAAAAEAyXKwQqAIAwA0F+JnRWcXWS/EiGWO3hQYy46RP9e0Pm95YaSgSwaaKkyEICB0UVj + O+vG8pP0K+7CSTlHLZWHpnp81zuP1gXr58kFwpkwwLO+Gg7z9FQAAAA= + headers: + Access-Control-Allow-Headers: + - '*' + Access-Control-Allow-Methods: + - GET, POST, PUT, DELETE, OPTIONS + Access-Control-Allow-Origin: + - '*' + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Wed, 25 Aug 2021 12:05:22 GMT + Server: + - nginx/1.20.1 + Transfer-Encoding: + - chunked + X-Powered-By: + - PHP/8.0.9 + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-requests/2.26.0 + accept: + - application/json + method: GET + uri: https://localhost/api/objects/recipes/2 + response: + body: + string: !!binary | + H4sIAAAAAAAEA7WVTW/bMAyG/wqh0wY4aZKuA+Z5BnbfrdcAhiyxjhp9TaIyBMP++yi3KNIOWIHO + OzgHiXzIvKZf/hRGi3bXCC8dilbcRjkdkMjAGGyYPGYUjdCYVTKRTPAc0x22/beQ0IGJubhuf8UH + Xbw8g27sNQMSZEMcMPbAfGpABZ+RkArfSM35yvgJ0BpKDWTUoI104IMv7gxokgsaCF1kkvEno4sn + KARWjtwAIMFcBcHJyUuQ1nwvZwZgklzsiXcKtkQqcg1fCU6YAmDINVsqVXKNJ7gvmQLowk9tHOd7 + lJBwLG4Nt9w0KO5TwlFmDVMZMU0JfcPdciUJJI/GSb7P0isqnJ8JLnR64FZBZi3Wf7l7plOXo/SQ + 6Wzxy16MUh2nFIrXK1X7bCFN47vdzU0D88/m/ee96F9qy6+gUvoLTf6bxlwr9l2xfWdN/1a9GcLZ + lbC88I/s/VXtcZlZ3qe9f6n527SupKVmurL+fa4rZaHZrqg/fGDR+Z7/8tN3/9YZn/tcxEsqaSk/ + qaxXPGX+9tiwU/gxqISSUA9kHGdJF9m6d5vddrXZrbbXsP3Ufti21x85Ohr2q4TDnbE4PC4CtouH + RbC+jxPHjDLjkDGd2K+zaLfzUjCJ+c8OfaBBHVAdh3wIMXKwNZlEu2kEnWPdMD4kJ22tmoIuioa6 + gHyxthGFUXcGreYC9eTXb5+DW9eeBgAA + headers: + Access-Control-Allow-Headers: + - '*' + Access-Control-Allow-Methods: + - GET, POST, PUT, DELETE, OPTIONS + Access-Control-Allow-Origin: + - '*' + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Wed, 25 Aug 2021 12:05:22 GMT + Server: + - nginx/1.20.1 + Transfer-Encoding: + - chunked + X-Powered-By: + - PHP/8.0.9 + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-requests/2.26.0 + accept: + - application/json + method: GET + uri: https://localhost/api/objects/meal_plan_sections?query%5B%5D=id%3D1 + response: + body: + string: !!binary | + H4sIAAAAAAAEAw3LwQpAQBAG4FfRf15lh8IcvYa0LUZJi3ZHDvLunL++/sE6g63B7oOA0UXx2+KT + wiAdUd1+hVEiuDSIx+2m31Vmp2uQpD6c/6GCbF40OVWZrblqmQjv8AEGfHFxWwAAAA== + headers: + Access-Control-Allow-Headers: + - '*' + Access-Control-Allow-Methods: + - GET, POST, PUT, DELETE, OPTIONS + Access-Control-Allow-Origin: + - '*' + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Wed, 25 Aug 2021 12:05:22 GMT + Server: + - nginx/1.20.1 + Transfer-Encoding: + - chunked + X-Powered-By: + - PHP/8.0.9 + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-requests/2.26.0 + accept: + - application/json + method: GET + uri: https://localhost/api/objects/meal_plan_sections?query%5B%5D=id%3D-1 + response: + body: + string: !!binary | + H4sIAAAAAAAEAyXKwQqAIAwA0F+JnRWcXWS/EiGWO3hQYy46RP9e0Pm95YaSgSwaaKkyEICB0UVj + O+vG8pP0K+7CSTlHLZWHpnp81zuP1gXr58kFwpkwwLO+Gg7z9FQAAAA= + headers: + Access-Control-Allow-Headers: + - '*' + Access-Control-Allow-Methods: + - GET, POST, PUT, DELETE, OPTIONS + Access-Control-Allow-Origin: + - '*' + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Wed, 25 Aug 2021 12:05:22 GMT + Server: + - nginx/1.20.1 + Transfer-Encoding: + - chunked + X-Powered-By: + - PHP/8.0.9 + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-requests/2.26.0 + accept: + - application/json + method: GET + uri: https://localhost/api/objects/meal_plan_sections?query%5B%5D=id%3D1 + response: + body: + string: !!binary | + H4sIAAAAAAAEAw3LwQpAQBAG4FfRf15lh8IcvYa0LUZJi3ZHDvLunL++/sE6g63B7oOA0UXx2+KT + wiAdUd1+hVEiuDSIx+2m31Vmp2uQpD6c/6GCbF40OVWZrblqmQjv8AEGfHFxWwAAAA== + headers: + Access-Control-Allow-Headers: + - '*' + Access-Control-Allow-Methods: + - GET, POST, PUT, DELETE, OPTIONS + Access-Control-Allow-Origin: + - '*' + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Wed, 25 Aug 2021 12:05:23 GMT + Server: + - nginx/1.20.1 + Transfer-Encoding: + - chunked + X-Powered-By: + - PHP/8.0.9 + status: + code: 200 + message: OK +version: 1 diff --git a/test/test_meal_plan.py b/test/test_meal_plan.py index e03d3e4..0a809cf 100644 --- a/test/test_meal_plan.py +++ b/test/test_meal_plan.py @@ -1,6 +1,6 @@ import pytest -from pygrocy.data_models.meal_items import MealPlanSection, RecipeItem +from pygrocy.data_models.meal_items import MealPlanItemType, MealPlanSection, RecipeItem class TestMealPlan: @@ -38,3 +38,21 @@ def test_get_meal_plan_with_details_valid(self, grocy): section_item = next(item for item in meal_plan if item.section_id == 1) assert isinstance(section_item.section, MealPlanSection) + + @pytest.mark.vcr + def test_get_meal_plan_with_note_and_details(self, grocy): + meal_plan = grocy.meal_plan(get_details=True) + + note_entry = next( + item for item in meal_plan if item.type == MealPlanItemType.NOTE + ) + assert note_entry.note == "This is a note" + + @pytest.mark.vcr + def test_get_meal_plan_with_product(self, grocy): + meal_plan = grocy.meal_plan(get_details=True) + + product_entry = next( + item for item in meal_plan if item.type == MealPlanItemType.PRODUCT + ) + assert product_entry.product_id == 4 From 88a74ab2d318bb32bed119f1eec80dd5f2140868 Mon Sep 17 00:00:00 2001 From: Sebastian Rutofski Date: Wed, 1 Sep 2021 13:10:59 +0200 Subject: [PATCH 3/8] Create black.yml --- .github/workflows/black.yml | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 .github/workflows/black.yml diff --git a/.github/workflows/black.yml b/.github/workflows/black.yml new file mode 100644 index 0000000..ff72390 --- /dev/null +++ b/.github/workflows/black.yml @@ -0,0 +1,9 @@ +name: black-action +on: [push, pull_request] +jobs: + linter_name: + name: runner / black formatter + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: rickstaa/action-black@v1 From 521fc6b741a30e84e8d455dac5e045ac069850bb Mon Sep 17 00:00:00 2001 From: Sebastian Rutofski Date: Wed, 1 Sep 2021 14:06:10 +0200 Subject: [PATCH 4/8] apply black to everything (#185) --- pygrocy/data_models/chore.py | 9 +++++++-- pygrocy/data_models/task.py | 3 ++- pygrocy/data_models/user.py | 2 +- test/test_LocationData.py | 1 - test/test_ShoppingListItem.py | 1 - test/test_currentChoreResponse.py | 1 - test/test_currentStockResponse.py | 2 +- test/test_utils.py | 4 ++-- 8 files changed, 13 insertions(+), 10 deletions(-) diff --git a/pygrocy/data_models/chore.py b/pygrocy/data_models/chore.py index 6e57df5..2dd71d1 100644 --- a/pygrocy/data_models/chore.py +++ b/pygrocy/data_models/chore.py @@ -1,9 +1,14 @@ from datetime import datetime from enum import Enum -from typing import List, Dict +from typing import Dict + from pygrocy.base import DataModel from pygrocy.data_models.user import User -from pygrocy.grocy_api_client import CurrentChoreResponse, ChoreDetailsResponse, GrocyApiClient +from pygrocy.grocy_api_client import ( + ChoreDetailsResponse, + CurrentChoreResponse, + GrocyApiClient, +) class PeriodType(str, Enum): diff --git a/pygrocy/data_models/task.py b/pygrocy/data_models/task.py index 466ec54..d99608f 100644 --- a/pygrocy/data_models/task.py +++ b/pygrocy/data_models/task.py @@ -1,5 +1,6 @@ from datetime import datetime from typing import Dict + from pygrocy.base import DataModel from pygrocy.grocy_api_client import TaskResponse @@ -51,4 +52,4 @@ def assigned_to_user_id(self) -> int: @property def userfields(self) -> Dict[str, str]: - return self._userfields \ No newline at end of file + return self._userfields diff --git a/pygrocy/data_models/user.py b/pygrocy/data_models/user.py index 40953ed..9bf256e 100644 --- a/pygrocy/data_models/user.py +++ b/pygrocy/data_models/user.py @@ -28,4 +28,4 @@ def last_name(self) -> str: @property def display_name(self) -> str: - return self._display_name \ No newline at end of file + return self._display_name diff --git a/test/test_LocationData.py b/test/test_LocationData.py index 07a5f8d..ba71ddf 100644 --- a/test/test_LocationData.py +++ b/test/test_LocationData.py @@ -18,4 +18,3 @@ def test_parse(self): assert response.id == 1 assert response.name == "string" assert response.description == "string" - \ No newline at end of file diff --git a/test/test_ShoppingListItem.py b/test/test_ShoppingListItem.py index 60e1650..83f55a9 100644 --- a/test/test_ShoppingListItem.py +++ b/test/test_ShoppingListItem.py @@ -22,4 +22,3 @@ def test_parse(self): assert response.id == 1 assert response.note == "string" assert response.amount == 2 - \ No newline at end of file diff --git a/test/test_currentChoreResponse.py b/test/test_currentChoreResponse.py index 9df8c4a..467a89f 100644 --- a/test/test_currentChoreResponse.py +++ b/test/test_currentChoreResponse.py @@ -11,4 +11,3 @@ def test_parse(self): response = CurrentChoreResponse(json.loads(input_json)) assert response.chore_id == 4 - diff --git a/test/test_currentStockResponse.py b/test/test_currentStockResponse.py index 4f25020..d7165ce 100644 --- a/test/test_currentStockResponse.py +++ b/test/test_currentStockResponse.py @@ -12,7 +12,7 @@ def test_parse(self): assert response.product_id == 0 assert response.amount == 12.53 assert response.amount_opened == 0 - + assert response.best_before_date.year == 2019 assert response.best_before_date.month == 4 assert response.best_before_date.day == 22 diff --git a/test/test_utils.py b/test/test_utils.py index 70d758a..171bf0c 100644 --- a/test/test_utils.py +++ b/test/test_utils.py @@ -16,13 +16,13 @@ def test_parse_date_no_data(self): date_obj = utils.parse_date(date_str) assert date_obj is None - + def test_parse_date_empty_string(self): date_str = "" date_obj = utils.parse_date(date_str) assert date_obj is None - + def test_parse_int_valid(self): int_str = "2" int_number = utils.parse_int(int_str) From 77ae69412825f657a38525a97ffdecb519b35311 Mon Sep 17 00:00:00 2001 From: Sebastian Rutofski Date: Wed, 1 Sep 2021 14:12:26 +0200 Subject: [PATCH 5/8] migrate to pydantic (#184) * add pydantic dependency * switch battery server comm to pydantic * migrate chores to pydantic * migrate meal plans to pydantic * migrate barcode to pydantic * migrate product related stuff to pydantic * migrate shoppinglist to pydantic * migrate stock stuff to pydantic * migrate tasks to pydantic * fix some types --- pygrocy/data_models/meal_items.py | 2 +- pygrocy/data_models/product.py | 6 +- pygrocy/data_models/task.py | 4 +- pygrocy/grocy.py | 2 +- pygrocy/grocy_api_client.py | 741 ++++++++-------------------- setup.py | 1 + test/test_LocationData.py | 20 - test/test_MealPlanResponse.py | 29 -- test/test_ShoppingListItem.py | 24 - test/test_UserDto.py | 24 - test/test_battery.py | 2 +- test/test_choreDetailsResponse.py | 68 --- test/test_currentChoreResponse.py | 13 - test/test_currentStockResponse.py | 20 - test/test_misc.py | 2 +- test/test_missingProductResponse.py | 24 - test/test_productDetailsResponse.py | 41 -- 17 files changed, 206 insertions(+), 817 deletions(-) delete mode 100644 test/test_LocationData.py delete mode 100644 test/test_MealPlanResponse.py delete mode 100644 test/test_ShoppingListItem.py delete mode 100644 test/test_UserDto.py delete mode 100644 test/test_choreDetailsResponse.py delete mode 100644 test/test_currentChoreResponse.py delete mode 100644 test/test_currentStockResponse.py delete mode 100644 test/test_missingProductResponse.py delete mode 100644 test/test_productDetailsResponse.py diff --git a/pygrocy/data_models/meal_items.py b/pygrocy/data_models/meal_items.py index 1437407..0fe9ecb 100644 --- a/pygrocy/data_models/meal_items.py +++ b/pygrocy/data_models/meal_items.py @@ -99,7 +99,7 @@ def id(self) -> int: return self._id @property - def day(self) -> datetime: + def day(self) -> datetime.date: return self._day @property diff --git a/pygrocy/data_models/product.py b/pygrocy/data_models/product.py index bb98038..8f6ede0 100644 --- a/pygrocy/data_models/product.py +++ b/pygrocy/data_models/product.py @@ -56,7 +56,7 @@ def _init_from_CurrentStockResponse(self, response: CurrentStockResponse): self._init_from_ProductData(response.product) def _init_from_MissingProductResponse(self, response: MissingProductResponse): - self._id = response.product_id + self._id = response.id self._name = response.name self._amount_missing = response.amount_missing self._is_partly_in_stock = response.is_partly_in_stock @@ -78,7 +78,7 @@ def get_details(self, api_client: GrocyApiClient): details = api_client.get_product(self.id) if details: self._name = details.product.name - self._barcodes = details.barcodes + self._barcodes = [ProductBarcode(barcode) for barcode in details.barcodes] self._product_group_id = details.product.product_group_id @property @@ -98,7 +98,7 @@ def available_amount(self) -> float: return self._available_amount @property - def best_before_date(self) -> datetime: + def best_before_date(self) -> datetime.date: return self._best_before_date @property diff --git a/pygrocy/data_models/task.py b/pygrocy/data_models/task.py index d99608f..8a8ac4b 100644 --- a/pygrocy/data_models/task.py +++ b/pygrocy/data_models/task.py @@ -19,7 +19,7 @@ def __init__(self, response: TaskResponse): self._userfields = response.userfields @property - def id(self) -> str: + def id(self) -> int: return self._id @property @@ -31,7 +31,7 @@ def description(self) -> str: return self._description @property - def due_date(self) -> datetime: + def due_date(self) -> datetime.date: return self._due_date @property diff --git a/pygrocy/grocy.py b/pygrocy/grocy.py index 51c6bb6..02a5339 100644 --- a/pygrocy/grocy.py +++ b/pygrocy/grocy.py @@ -98,7 +98,7 @@ def all_products(self) -> List[Product]: raw_products = self.get_generic_objects_for_type(EntityType.PRODUCTS) from pygrocy.grocy_api_client import ProductData - product_datas = [ProductData(product) for product in raw_products] + product_datas = [ProductData(**product) for product in raw_products] return [Product(product) for product in product_datas] def chores(self, get_details: bool = False) -> List[Chore]: diff --git a/pygrocy/grocy_api_client.py b/pygrocy/grocy_api_client.py index 5364042..295d265 100644 --- a/pygrocy/grocy_api_client.py +++ b/pygrocy/grocy_api_client.py @@ -3,19 +3,15 @@ import logging from datetime import datetime from enum import Enum -from typing import List +from typing import Dict, List, Optional from urllib.parse import urljoin import requests +from pydantic import BaseModel, Field +from pydantic.schema import date from pygrocy import EntityType -from pygrocy.utils import ( - localize_datetime, - parse_bool_int, - parse_date, - parse_float, - parse_int, -) +from pygrocy.utils import localize_datetime, parse_date from .errors import GrocyError @@ -25,482 +21,148 @@ _LOGGER.setLevel(logging.INFO) -class ShoppingListItem(object): - def __init__(self, parsed_json): - self._id = parse_int(parsed_json.get("id")) - self._product_id = parse_int(parsed_json.get("product_id", None)) - self._note = parsed_json.get("note", None) - self._amount = parse_float(parsed_json.get("amount"), 0) - self._row_created_timestamp = parse_date( - parsed_json.get("row_created_timestamp", None) - ) - self._shopping_list_id = parse_int(parsed_json.get("shopping_list_id")) - self._done = parse_int(parsed_json.get("done")) - - @property - def id(self) -> int: - return self._id - - @property - def product_id(self) -> int: - return self._product_id - - @property - def note(self) -> str: - return self._note - - @property - 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") - self._section_id = parse_int(parsed_json.get("section_id")) - - @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 section_id(self) -> int: - return self._section_id - - @property - def type(self) -> str: - return self._type - - @property - def product_id(self) -> int: - return self._product_id - - -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): - self._id = parse_int(parsed_json.get("id")) - self._name = parsed_json.get("name") - self._name_plural = parsed_json.get("name_plural") - self._description = parsed_json.get("description") - self._row_created_timestamp = parse_date( - parsed_json.get("row_created_timestamp") - ) - - -class LocationData(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._row_created_timestamp = parse_date( - parsed_json.get("row_created_timestamp") - ) - - @property - def id(self) -> int: - return self._id - - @property - def name(self) -> str: - return self._name - - @property - def description(self) -> str: - return self._description - - -class ProductData(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", None) - self._location_id = parse_int(parsed_json.get("location_id", None)) - self._product_group_id = parse_int(parsed_json.get("product_group_id", None)) - self._qu_id_stock = parse_int(parsed_json.get("qu_id_stock", None)) - self._qu_id_purchase = parse_int(parsed_json.get("qu_id_purchsase", None)) - self._qu_factor_purchase_to_stock = parse_float( - parsed_json.get("qu_factor_purchase_to_stock", None) - ) - self._picture_file_name = parsed_json.get("picture_file_name", None) - self._allow_partial_units_in_stock = bool( - parsed_json.get("allow_partial_units_in_stock", None) == "true" - ) - self._row_created_timestamp = parse_date( - parsed_json.get("row_created_timestamp", None) - ) - self._min_stock_amount = parse_int(parsed_json.get("min_stock_amount", None), 0) - self._default_best_before_days = parse_int( - parsed_json.get("default_best_before_days", None) - ) - - @property - def id(self) -> int: - return self._id - - @property - def product_group_id(self) -> int: - return self._product_group_id - - @property - def name(self) -> str: - return self._name - - -class ChoreData(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.period_type = parsed_json.get("period_type") - self.period_config = parsed_json.get("period_config") - self.period_days = parse_int(parsed_json.get("period_days")) - self.track_date_only = parse_bool_int(parsed_json.get("track_date_only")) - self.rollover = parse_bool_int(parsed_json.get("rollover")) - self.assignment_type = parsed_json.get("assignment_type") - self.assignment_config = parsed_json.get("assignment_config") - self.next_execution_assigned_to_user_id = parse_int( - parsed_json.get("next_execution_assigned_to_user_id") - ) - self.userfields = parsed_json.get("userfields") - - -class UserDto(object): - def __init__(self, parsed_json): - self._id = parse_int(parsed_json.get("id")) - - self._username = parsed_json.get("username") - self._first_name = parsed_json.get("first_name") - self._last_name = parsed_json.get("last_name") - self._display_name = parsed_json.get("display_name") - - @property - def id(self) -> int: - return self._id - - @property - def username(self) -> str: - return self._username - - @property - def first_name(self) -> str: - return self._first_name - - @property - def last_name(self) -> str: - return self._last_name - - @property - def display_name(self) -> str: - return self._display_name - - -class CurrentChoreResponse(object): - def __init__(self, parsed_json): - self._chore_id = parse_int(parsed_json.get("chore_id"), None) - self._last_tracked_time = parse_date(parsed_json.get("last_tracked_time")) - self._next_estimated_execution_time = parse_date( - parsed_json.get("next_estimated_execution_time") - ) - - @property - def chore_id(self) -> int: - return self._chore_id - - @property - def last_tracked_time(self) -> datetime: - return self._last_tracked_time - - @property - def next_estimated_execution_time(self) -> datetime: - return self._next_estimated_execution_time - - -class CurrentStockResponse(object): - def __init__(self, parsed_json): - self._product_id = parse_int(parsed_json.get("product_id")) - self._amount = parse_float(parsed_json.get("amount")) - self._best_before_date = parse_date(parsed_json.get("best_before_date")) - self._amount_opened = parse_float(parsed_json.get("amount_opened")) - self._product = ProductData(parsed_json.get("product")) - - @property - def product_id(self) -> int: - return self._product_id - - @property - def amount(self) -> float: - return self._amount - - @property - def best_before_date(self) -> datetime: - return self._best_before_date - - @property - def amount_opened(self) -> float: - return self._amount_opened - - @property - def product(self) -> ProductData: - return self._product - - -class MissingProductResponse(object): - def __init__(self, parsed_json): - self._product_id = parse_int(parsed_json.get("id")) - self._name = parsed_json.get("name") - self._amount_missing = parse_float(parsed_json.get("amount_missing")) - self._is_partly_in_stock = bool( - parse_int(parsed_json.get("is_partly_in_stock")) - ) - - @property - def product_id(self) -> int: - return self._product_id - - @property - def name(self) -> str: - return self._name - - @property - def amount_missing(self) -> float: - return self._amount_missing - - @property - def is_partly_in_stock(self) -> bool: - return self._is_partly_in_stock - - -class CurrentVolatilStockResponse(object): - def __init__(self, parsed_json): - self._due_products = [] - if "due_products" in parsed_json: - self._due_products = [ - CurrentStockResponse(product) - for product in parsed_json.get("due_products") - ] - - self._overdue_products = [] - if "overdue_products" in parsed_json: - self._overdue_products = [ - CurrentStockResponse(product) - for product in parsed_json.get("overdue_products") - ] - - self._expired_products = [] - if "expired_products" in parsed_json: - self._expired_products = [ - CurrentStockResponse(product) - for product in parsed_json.get("expired_products") - ] - - self._missing_products = [] - if "missing_products" in parsed_json: - self._missing_products = [ - MissingProductResponse(product) - for product in parsed_json.get("missing_products") - ] - - @property - def due_products(self) -> List[CurrentStockResponse]: - return self._due_products - - @property - def overdue_products(self) -> List[CurrentStockResponse]: - return self._overdue_products - - @property - def expired_products(self) -> List[CurrentStockResponse]: - return self._expired_products - - @property - def missing_products(self) -> List[MissingProductResponse]: - return self._missing_products - - -class ProductBarcodeData(object): - def __init__(self, parsed_json): - self._barcode = str(parsed_json.get("barcode")) - - @property - def barcode(self) -> str: - return self._barcode - - -class ProductDetailsResponse(object): - def __init__(self, parsed_json): - self._last_purchased = parse_date(parsed_json.get("last_purchased")) - self._last_used = parse_date(parsed_json.get("last_used")) - self._stock_amount = parse_int(parsed_json.get("stock_amount")) - self._stock_amount_opened = parse_int(parsed_json.get("stock_amount_opened")) - self._next_best_before_date = parse_date( - parsed_json.get("next_best_before_date") - ) - self._last_price = parse_float(parsed_json.get("last_price")) - - self._product = ProductData(parsed_json.get("product")) - - self._quantity_unit_stock = QuantityUnitData( - parsed_json.get("quantity_unit_stock") - ) - - self._parse_location(parsed_json) - - self._parse_barcodes(parsed_json) - - def _parse_barcodes(self, parsed_json): - barcodes_raw = parsed_json.get("product_barcodes", "") - if barcodes_raw is not None: - self._barcodes = [ProductBarcodeData(barcode) for barcode in barcodes_raw] - - def _parse_location(self, parsed_json): - raw_location = parsed_json.get("location") - if raw_location is None: - self._location = None - else: - self._location = LocationData(raw_location) - - @property - def last_purchased(self) -> datetime: - return self._last_purchased - - @property - def last_used(self) -> datetime: - return self._last_used - - @property - def stock_amount(self) -> int: - return self._stock_amount - - @property - def stock_amount_opened(self) -> int: - return self._stock_amount_opened - - @property - def next_best_before_date(self) -> datetime: - return self._next_best_before_date - - @property - def last_price(self) -> float: - return self._last_price - - @property - def barcodes(self) -> List[ProductBarcodeData]: - return self._barcodes - - @property - def product(self) -> ProductData: - return self._product - - -class ChoreDetailsResponse(object): - def __init__(self, parsed_json): - self._chore = ChoreData(parsed_json.get("chore")) - self._last_tracked = parse_date(parsed_json.get("last_tracked")) - self._next_estimated_execution_time = parse_date( - parsed_json.get("next_estimated_execution_time") - ) - self._track_count = parse_int(parsed_json.get("track_count")) - - next_user = parsed_json.get("next_execution_assigned_user") - if next_user is not None: - self._next_execution_assigned_user = UserDto(next_user) - else: - self._next_execution_assigned_user = None - - if self._last_tracked is None: - self._last_done_by = None - else: - self._last_done_by = UserDto(parsed_json.get("last_done_by")) - - @property - def chore(self) -> ChoreData: - return self._chore - - @property - def last_done_by(self) -> UserDto: - return self._last_done_by - - @property - def last_tracked(self) -> datetime: - return self._last_tracked - - @property - def next_estimated_execution_time(self) -> datetime: - return self._next_estimated_execution_time - - @property - def track_count(self) -> int: - return self._track_count - - @property - def next_execution_assigned_user(self) -> UserDto: - return self._next_execution_assigned_user +class ShoppingListItem(BaseModel): + id: int + product_id: Optional[int] = None + note: Optional[str] = None + amount: float + row_created_timestamp: datetime + shopping_list_id: int + done: int + + +class MealPlanResponse(BaseModel): + id: int + day: date + type: str + recipe_id: Optional[int] = None + recipe_servings: Optional[int] = None + note: Optional[str] = None + product_id: Optional[int] = None + product_amount: Optional[float] = None + product_qu_id: Optional[str] = None + row_created_timestamp: datetime + userfields: Optional[Dict] = None + section_id: Optional[int] = None + + +class RecipeDetailsResponse(BaseModel): + id: Optional[int] = None + name: str + description: Optional[str] = None + base_servings: int + desired_servings: int + picture_file_name: Optional[str] + row_created_timestamp: datetime + userfields: Optional[Dict] = None + + +class QuantityUnitData(BaseModel): + id: int + name: str + name_plural: str + description: Optional[str] = None + row_created_timestamp: datetime + + +class LocationData(BaseModel): + id: int + name: str + description: Optional[str] = None + row_created_timestamp: datetime + + +class ProductData(BaseModel): + id: int + name: str + description: Optional[str] = None + location_id: int + product_group_id: Optional[int] = None + qu_id_stock: int + qu_id_purchase: int + qu_factor_purchase_to_stock: float + picture_file_name: Optional[str] = None + allow_partial_units_in_stock: Optional[bool] = False + row_created_timestamp: datetime + min_stock_amount: Optional[int] + default_best_before_days: int + + +class ChoreData(BaseModel): + id: int + name: str + description: Optional[str] = None + period_type: str + period_config: Optional[str] = None + period_days: Optional[int] = 0 + track_date_only: bool + rollover: bool + assignment_type: Optional[str] = None + assignment_config: Optional[str] = None + next_execution_assigned_to_user_id: Optional[int] = None + userfields: Optional[Dict] + + +class UserDto(BaseModel): + id: int + username: str + first_name: Optional[str] = None + last_name: Optional[str] = None + display_name: Optional[str] = None + + +class CurrentChoreResponse(BaseModel): + chore_id: int + last_tracked_time: Optional[datetime] + next_estimated_execution_time: datetime + + +class CurrentStockResponse(BaseModel): + product_id: int + amount: float + best_before_date: date + amount_opened: float + product: ProductData + + +class MissingProductResponse(BaseModel): + id: int + name: str + amount_missing: float + is_partly_in_stock: bool + + +class CurrentVolatilStockResponse(BaseModel): + due_products: Optional[List[CurrentStockResponse]] = None + overdue_products: Optional[List[CurrentStockResponse]] = None + expired_products: Optional[List[CurrentStockResponse]] = None + missing_products: Optional[List[MissingProductResponse]] = None + + +class ProductBarcodeData(BaseModel): + barcode: str + + +class ProductDetailsResponse(BaseModel): + last_purchased: Optional[date] = None + last_used: Optional[datetime] = None + stock_amount: int + stock_amount_opened: int + next_best_before_date: Optional[date] = None + last_price: Optional[float] = None + product: ProductData + quantity_unit_stock: QuantityUnitData + barcodes: Optional[List[ProductBarcodeData]] = Field(alias="product_barcodes") + location: Optional[LocationData] = None + + +class ChoreDetailsResponse(BaseModel): + chore: ChoreData + last_tracked: Optional[datetime] = None + next_estimated_execution_time: datetime + track_count: int = 0 + next_execution_assigned_user: Optional[UserDto] = None + last_done_by: Optional[UserDto] = None class TransactionType(Enum): @@ -510,57 +172,46 @@ class TransactionType(Enum): PRODUCT_OPENED = "product-opened" -class TaskResponse(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.due_date = parse_date(parsed_json.get("due_date")) - self.done = parse_int(parsed_json.get("done")) - self.done_timestamp = parse_date(parsed_json.get("done_timestamp")) - self.category_id = parse_int(parsed_json.get("category_id")) - self.assigned_to_user_id = parse_int(parsed_json.get("assigned_to_user_id")) - self.userfields = parsed_json.get("userfields") - - -class CurrentBatteryResponse(object): - def __init__(self, parsed_json): - self.id = parse_int(parsed_json.get("battery_id")) - self.last_tracked_time = parse_date(parsed_json.get("last_tracked_time")) - self.next_estimated_charge_time = parse_date( - parsed_json.get("'next_estimated_charge_time") - ) +class TaskResponse(BaseModel): + id: int + name: str + description: Optional[str] = None + due_date: date + done: int + done_timestamp: Optional[datetime] = None + category_id: Optional[int] = None + assigned_to_user_id: int + userfields: Optional[Dict] = None -class BatteryData(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.used_in = parsed_json.get("used_in") - self.charge_interval_days = parse_int(parsed_json.get("charge_interval_days")) - self.created_timestamp = parse_date(parsed_json.get("row_created_timestamp")) - self.userfields = parsed_json.get("userfields") - - -class BatteryDetailsResponse(object): - def __init__(self, parsed_json): - self.battery = BatteryData(parsed_json.get("battery")) - self.charge_cycles_count = parse_int(parsed_json.get("charge_cycles_count")) - self.last_charged = parse_date(parsed_json.get("last_charged")) - self.next_estimated_charge_time = parse_date( - parsed_json.get("'next_estimated_charge_time") - ) +class CurrentBatteryResponse(BaseModel): + id: int + last_tracked_time: Optional[datetime] = None + next_estimated_charge_time: datetime -class MealPlanSectionResponse(object): - def __init__(self, parsed_json): - self.id = parse_int(parsed_json.get("id")) - self.name = parsed_json.get("name") - self.sort_number = parse_int(parsed_json.get("sort_number")) - self.row_created_timestamp = parse_date( - parsed_json.get("row_created_timestamp") - ) +class BatteryData(BaseModel): + id: int + name: str + description: str + used_in: str + charge_interval_days: int + created_timestamp: datetime = Field(alias="row_created_timestamp") + userfields: Optional[Dict] = None + + +class BatteryDetailsResponse(BaseModel): + battery: BatteryData + charge_cycles_count: int + last_charged: Optional[datetime] = None + next_estimated_charge_time: datetime + + +class MealPlanSectionResponse(BaseModel): + id: Optional[int] = None + name: str + sort_number: Optional[int] = None + row_created_timestamp: datetime def _enable_debug_mode(): @@ -659,27 +310,27 @@ def _do_delete_request(self, end_url: str): def get_stock(self) -> List[CurrentStockResponse]: parsed_json = self._do_get_request("stock") - return [CurrentStockResponse(response) for response in parsed_json] + return [CurrentStockResponse(**response) for response in parsed_json] def get_volatile_stock(self) -> CurrentVolatilStockResponse: parsed_json = self._do_get_request("stock/volatile") - return CurrentVolatilStockResponse(parsed_json) + return CurrentVolatilStockResponse(**parsed_json) def get_product(self, product_id) -> ProductDetailsResponse: url = f"stock/products/{product_id}" parsed_json = self._do_get_request(url) if parsed_json: - return ProductDetailsResponse(parsed_json) + return ProductDetailsResponse(**parsed_json) def get_chores(self) -> List[CurrentChoreResponse]: parsed_json = self._do_get_request("chores") - return [CurrentChoreResponse(chore) for chore in parsed_json] + return [CurrentChoreResponse(**chore) for chore in parsed_json] def get_chore(self, chore_id: int) -> ChoreDetailsResponse: url = f"chores/{chore_id}" parsed_json = self._do_get_request(url) if parsed_json: - return ChoreDetailsResponse(parsed_json) + return ChoreDetailsResponse(**parsed_json) def execute_chore( self, @@ -732,7 +383,7 @@ def consume_product( def get_shopping_list(self) -> List[ShoppingListItem]: parsed_json = self._do_get_request("objects/shopping_list") - return [ShoppingListItem(response) for response in parsed_json] + return [ShoppingListItem(**response) for response in parsed_json] def add_missing_product_to_shopping_list(self, shopping_list_id: int = None): data = None @@ -768,7 +419,7 @@ def remove_product_in_shopping_list( def get_product_groups(self) -> List[LocationData]: parsed_json = self._do_get_request("objects/product_groups") - return [LocationData(response) for response in parsed_json] + return [LocationData(**response) for response in parsed_json] def upload_product_picture(self, product_id: int, pic_path: str): b64fn = base64.b64encode("{}.jpg".format(product_id).encode("ascii")) @@ -796,7 +447,7 @@ def get_last_db_changed(self): def get_tasks(self) -> List[TaskResponse]: parsed_json = self._do_get_request("tasks") - return [TaskResponse(data) for data in parsed_json] + return [TaskResponse(**data) for data in parsed_json] def complete_task(self, task_id: int, done_time: datetime = datetime.now()): url = f"tasks/{task_id}/complete" @@ -808,22 +459,22 @@ def complete_task(self, task_id: int, done_time: datetime = datetime.now()): def get_meal_plan(self) -> List[MealPlanResponse]: parsed_json = self._do_get_request("objects/meal_plan") - return [MealPlanResponse(data) for data in parsed_json] + 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) + return RecipeDetailsResponse(**parsed_json) def get_batteries(self) -> List[CurrentBatteryResponse]: parsed_json = self._do_get_request(f"batteries") if parsed_json: - return [CurrentBatteryResponse(data) for data in parsed_json] + return [CurrentBatteryResponse(**data) for data in parsed_json] def get_battery(self, battery_id: int) -> BatteryDetailsResponse: parsed_json = self._do_get_request(f"batteries/{battery_id}") if parsed_json: - return BatteryDetailsResponse(parsed_json) + return BatteryDetailsResponse(**parsed_json) def charge_battery(self, battery_id: int, tracked_time: datetime = datetime.now()): localized_tracked_time = localize_datetime(tracked_time) @@ -846,11 +497,11 @@ def get_generic_objects_for_type(self, entity_type: str): def get_meal_plan_sections(self) -> List[MealPlanSectionResponse]: parsed_json = self.get_generic_objects_for_type(EntityType.MEAL_PLAN_SECTIONS) if parsed_json: - return [MealPlanSectionResponse(resp) for resp in parsed_json] + return [MealPlanSectionResponse(**resp) for resp in parsed_json] def get_meal_plan_section(self, meal_plan_section_id) -> MealPlanSectionResponse: parsed_json = self._do_get_request( f"objects/meal_plan_sections?query%5B%5D=id%3D{meal_plan_section_id}" ) if parsed_json and len(parsed_json) == 1: - return MealPlanSectionResponse(parsed_json[0]) + return MealPlanSectionResponse(**parsed_json[0]) diff --git a/setup.py b/setup.py index 2ab2e97..556fa9a 100644 --- a/setup.py +++ b/setup.py @@ -20,6 +20,7 @@ "pytz~=2021.1", "tzlocal>=2.1,<3.0", "deprecation~=2.1.0", + "pydantic~=1.8.2", ], classifiers=[ "Programming Language :: Python :: 3", diff --git a/test/test_LocationData.py b/test/test_LocationData.py deleted file mode 100644 index ba71ddf..0000000 --- a/test/test_LocationData.py +++ /dev/null @@ -1,20 +0,0 @@ -import json -from unittest import TestCase - -from pygrocy.grocy_api_client import LocationData - - -class TestLocationData(TestCase): - def test_parse(self): - input_json = """{ - "id": "1", - "name": "string", - "description": "string", - "row_created_timestamp": "2019-04-17 10:30:00" - }""" - - response = LocationData(json.loads(input_json)) - - assert response.id == 1 - assert response.name == "string" - assert response.description == "string" diff --git a/test/test_MealPlanResponse.py b/test/test_MealPlanResponse.py deleted file mode 100644 index 0743045..0000000 --- a/test/test_MealPlanResponse.py +++ /dev/null @@ -1,29 +0,0 @@ -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) diff --git a/test/test_ShoppingListItem.py b/test/test_ShoppingListItem.py deleted file mode 100644 index 83f55a9..0000000 --- a/test/test_ShoppingListItem.py +++ /dev/null @@ -1,24 +0,0 @@ -import json -from unittest import TestCase - -from pygrocy.grocy_api_client import ShoppingListItem - - -class TestShoppingListItem(TestCase): - def test_parse(self): - input_json = """{ - "id": "1", - "product_id": "6", - "note": "string", - "amount": "2", - "row_created_timestamp": "2019-04-17 10:30:00", - "shopping_list_id": "1", - "done": "0" - }""" - - response = ShoppingListItem(json.loads(input_json)) - - assert response.product_id == 6 - assert response.id == 1 - assert response.note == "string" - assert response.amount == 2 diff --git a/test/test_UserDto.py b/test/test_UserDto.py deleted file mode 100644 index 64529d3..0000000 --- a/test/test_UserDto.py +++ /dev/null @@ -1,24 +0,0 @@ -import json -from unittest import TestCase - -from pygrocy.grocy_api_client import UserDto - - -class TestUserDto(TestCase): - def test_parse(self): - input_json = """{ - "id": "1", - "username": "user", - "first_name": "Guz", - "last_name": "Userman", - "row_created_timestamp": "2019-04-17 10:30:00", - "display_name": "Guzzboy" - }""" - - response = UserDto(json.loads(input_json)) - - assert response.display_name == "Guzzboy" - assert response.id == 1 - assert response.first_name == "Guz" - assert response.last_name == "Userman" - assert response.username == "user" diff --git a/test/test_battery.py b/test/test_battery.py index 7dad178..0b9c2d9 100644 --- a/test/test_battery.py +++ b/test/test_battery.py @@ -22,7 +22,7 @@ def test_get_battery_details_valid(self, grocy): assert battery.charge_interval_days == 0 assert isinstance(battery.created_timestamp, datetime) assert isinstance(battery.last_charged, datetime) - assert battery.next_estimated_charge_time is None + assert isinstance(battery.next_estimated_charge_time, datetime) assert battery.userfields is None assert battery.charge_cycles_count == 6 diff --git a/test/test_choreDetailsResponse.py b/test/test_choreDetailsResponse.py deleted file mode 100644 index 61e4aa0..0000000 --- a/test/test_choreDetailsResponse.py +++ /dev/null @@ -1,68 +0,0 @@ -import json -from unittest import TestCase - -from pygrocy.grocy_api_client import ChoreDetailsResponse - - -class TestChoreDetailsResponse(TestCase): - def test_parse(self): - input_json = """{ - "chore": { - "id": 0, - "name": "string", - "description": "string", - "period_type": "manually", - "period_days": 0, - "row_created_timestamp": "2019-05-04T11:31:04.563Z" - }, - "last_tracked": "2019-05-04T11:31:04.563Z", - "track_count": 0, - "last_done_by": { - "id": 0, - "username": "string", - "first_name": "string", - "last_name": "string", - "display_name": "string", - "row_created_timestamp": "2019-05-04T11:31:04.564Z" - }, - "next_estimated_execution_time": "2019-05-04T11:31:04.564Z", - "next_execution_assigned_user": { - "id": 42, - "username": "string", - "first_name": "string", - "last_name": "string", - "display_name": "string", - "row_created_timestamp": "2019-05-04T11:31:04.564Z" - } - }""" - - response = ChoreDetailsResponse(json.loads(input_json)) - - assert response.chore.id == 0 - assert response.chore.name == "string" - - assert response.last_done_by.display_name == "string" - - assert response.next_execution_assigned_user.id == 42 - assert response.next_execution_assigned_user.display_name == "string" - - def test_no_last_tracked_data(self): - input_json = """{ - "chore": { - "id": 0, - "name": "string", - "description": "string", - "period_type": "manually", - "period_days": 0, - "row_created_timestamp": "2019-05-04T11:31:04.563Z" - }, - "last_tracked": null, - "track_count": 0, - "last_done_by": null, - "next_estimated_execution_time": "2019-05-04T11:31:04.564Z" - }""" - - response = ChoreDetailsResponse(json.loads(input_json)) - - assert response.last_tracked is None - assert response.last_done_by is None diff --git a/test/test_currentChoreResponse.py b/test/test_currentChoreResponse.py deleted file mode 100644 index 467a89f..0000000 --- a/test/test_currentChoreResponse.py +++ /dev/null @@ -1,13 +0,0 @@ -import json -from unittest import TestCase - -from pygrocy.grocy_api_client import CurrentChoreResponse - - -class TestCurrentChoreResponse(TestCase): - def test_parse(self): - input_json = """{ "chore_id": "4", "last_tracked_time": null, "next_estimated_execution_time": "2999-12-31 23:59:59", "track_date_only": "0", "next_execution_assigned_to_user_id": null }""" - - response = CurrentChoreResponse(json.loads(input_json)) - - assert response.chore_id == 4 diff --git a/test/test_currentStockResponse.py b/test/test_currentStockResponse.py deleted file mode 100644 index d7165ce..0000000 --- a/test/test_currentStockResponse.py +++ /dev/null @@ -1,20 +0,0 @@ -import json -from unittest import TestCase - -from pygrocy.grocy_api_client import CurrentStockResponse - - -class TestCurrentStockResponse(TestCase): - def test_parse(self): - input_json = """{"product_id": 0,"amount": "12.53","best_before_date": "2019-04-22","amount_opened": 0, "product": { "id": 0, "name": "test product"}}""" - response = CurrentStockResponse(json.loads(input_json)) - - assert response.product_id == 0 - assert response.amount == 12.53 - assert response.amount_opened == 0 - - assert response.best_before_date.year == 2019 - assert response.best_before_date.month == 4 - assert response.best_before_date.day == 22 - assert response.product.id == 0 - assert response.product.name == "test product" diff --git a/test/test_misc.py b/test/test_misc.py index 1e1ee8e..8162ed6 100644 --- a/test/test_misc.py +++ b/test/test_misc.py @@ -5,7 +5,7 @@ class TestMisc: def test_158_productbarcode_deserialization(self): parsed_data = {"barcode": "123"} - data = ProductBarcodeData(parsed_data) + data = ProductBarcodeData(**parsed_data) barcode = ProductBarcode(data) result = barcode.toJson() diff --git a/test/test_missingProductResponse.py b/test/test_missingProductResponse.py deleted file mode 100644 index 345009a..0000000 --- a/test/test_missingProductResponse.py +++ /dev/null @@ -1,24 +0,0 @@ -import json -from unittest import TestCase - -from pygrocy.grocy_api_client import MissingProductResponse - - -class TestMissingProductResponse(TestCase): - def test_parse_partly_in_stock_true(self): - input_json = """{ "id": "7", "name": "XXXX", "amount_missing": "2", "is_partly_in_stock": "1" }""" - response = MissingProductResponse(json.loads(input_json)) - - assert response.product_id == 7 - assert response.name == "XXXX" - assert response.amount_missing == 2 - assert response.is_partly_in_stock - - def test_parse_partly_in_stock_false(self): - input_json = """{ "id": "7", "name": "XXXX", "amount_missing": "2", "is_partly_in_stock": "0" }""" - response = MissingProductResponse(json.loads(input_json)) - - assert response.product_id == 7 - assert response.name == "XXXX" - assert response.amount_missing == 2 - assert not response.is_partly_in_stock diff --git a/test/test_productDetailsResponse.py b/test/test_productDetailsResponse.py deleted file mode 100644 index 4230ed7..0000000 --- a/test/test_productDetailsResponse.py +++ /dev/null @@ -1,41 +0,0 @@ -import json -from unittest import TestCase - -from pygrocy.grocy_api_client import ProductDetailsResponse - - -class TestProductDetailsResponse(TestCase): - def test_barcode_null(self): - input_json = """{ "product": { "id": 0, "name": "string", "description": "string", "location_id": 0, "qu_id_purchase": 0, "qu_id_stock": 0, "qu_factor_purchase_to_stock": 0, "barcode": null, "min_stock_amount": 0, "default_best_before_days": 0, "picture_file_name": "string", "allow_partial_units_in_stock": true, "row_created_timestamp": "2019-04-22T09:54:15.835Z" }, "quantity_unit_purchase": { "id": 0, "name": "string", "name_plural": "string", "description": "string", "row_created_timestamp": "2019-04-22T09:54:15.835Z" }, "quantity_unit_stock": { "id": 0, "name": "string", "name_plural": "string", "description": "string", "row_created_timestamp": "2019-04-22T09:54:15.835Z" }, "last_purchased": "2019-04-22", "last_used": "2019-04-22T09:54:15.835Z", "stock_amount": 10, "stock_amount_opened": 2, "next_best_before_date": "2019-04-22T09:54:15.835Z", "last_price": 0, "location": { "id": 0, "name": "string", "description": "string", "row_created_timestamp": "2019-04-22T09:54:15.835Z" } }""" - response = ProductDetailsResponse(json.loads(input_json)) - - assert len(response.barcodes) == 0 - - def test_parse(self): - input_json = """{ "product_barcodes": [{"barcode":"string"},{"barcode":"123"}], "product": { "id": 0, "name": "string", "description": "string", "location_id": 0, "product_group_id": 0, "qu_id_purchase": 0, "qu_id_stock": 0, "qu_factor_purchase_to_stock": 0, "barcode": "string,123", "min_stock_amount": 0, "default_best_before_days": 0, "picture_file_name": "string", "allow_partial_units_in_stock": true, "row_created_timestamp": "2019-04-22T09:54:15.835Z" }, "quantity_unit_purchase": { "id": 0, "name": "string", "name_plural": "string", "description": "string", "row_created_timestamp": "2019-04-22T09:54:15.835Z" }, "quantity_unit_stock": { "id": 0, "name": "string", "name_plural": "string", "description": "string", "row_created_timestamp": "2019-04-22T09:54:15.835Z" }, "last_purchased": "2019-04-22", "last_used": "2019-04-22T09:54:15.835Z", "stock_amount": 10, "stock_amount_opened": 2, "next_best_before_date": "2019-04-22T09:54:15.835Z", "last_price": 0, "location": { "id": 0, "name": "string", "description": "string", "row_created_timestamp": "2019-04-22T09:54:15.835Z" } }""" - response = ProductDetailsResponse(json.loads(input_json)) - - assert response.stock_amount == 10 - assert response.stock_amount_opened == 2 - assert response.last_price == 0 - assert response.product.product_group_id == 0 - - assert response.next_best_before_date.year == 2019 - assert response.next_best_before_date.month == 4 - assert response.next_best_before_date.day == 22 - assert response.next_best_before_date.hour == 9 - assert response.next_best_before_date.minute == 54 - assert response.next_best_before_date.second == 15 - - assert response.last_used.year == 2019 - assert response.last_used.month == 4 - assert response.last_used.day == 22 - assert response.last_used.hour == 9 - assert response.last_used.minute == 54 - assert response.last_used.second == 15 - - assert response.last_purchased.year == 2019 - assert response.last_purchased.month == 4 - assert response.last_purchased.day == 22 - - assert [barcode.barcode for barcode in response.barcodes] == ["string", "123"] From 5360efd057f989a25c6925d9e3cefd97342f2c66 Mon Sep 17 00:00:00 2001 From: Sebastian Rutofski Date: Fri, 10 Sep 2021 18:54:02 +0200 Subject: [PATCH 6/8] add users getter (#170) * add users getter * add get user by id * user tests * fix codefactor issues * remove mutmut cache --- pygrocy/grocy.py | 9 + pygrocy/grocy_api_client.py | 13 + ...Stock.test_get_expired_products_empty.yaml | 250 ++++++++++++++++++ .../TestUsers.test_get_user_by_id_valid.yaml | 46 ++++ .../TestUsers.test_get_users_valid.yaml | 46 ++++ test/test_users.py | 21 ++ 6 files changed, 385 insertions(+) create mode 100644 test/cassettes/test_stock/TestStock.test_get_expired_products_empty.yaml create mode 100644 test/cassettes/test_users/TestUsers.test_get_user_by_id_valid.yaml create mode 100644 test/cassettes/test_users/TestUsers.test_get_users_valid.yaml create mode 100644 test/test_users.py diff --git a/pygrocy/grocy.py b/pygrocy/grocy.py index 02a5339..ed5a5f9 100644 --- a/pygrocy/grocy.py +++ b/pygrocy/grocy.py @@ -247,3 +247,12 @@ def meal_plan_section(self, meal_plan_section_id: int) -> MealPlanSection: if section: return MealPlanSection(section) + + def users(self) -> List[User]: + user_dtos = self._api_client.get_users() + return [User(user) for user in user_dtos] + + def user(self, user_id: int = None) -> User: + user = self._api_client.get_user(user_id=user_id) + if user: + return User(user) diff --git a/pygrocy/grocy_api_client.py b/pygrocy/grocy_api_client.py index 295d265..eb13c23 100644 --- a/pygrocy/grocy_api_client.py +++ b/pygrocy/grocy_api_client.py @@ -505,3 +505,16 @@ def get_meal_plan_section(self, meal_plan_section_id) -> MealPlanSectionResponse ) if parsed_json and len(parsed_json) == 1: return MealPlanSectionResponse(**parsed_json[0]) + + def get_users(self) -> List[UserDto]: + parsed_json = self._do_get_request("users") + if parsed_json: + return [UserDto(**user) for user in parsed_json] + + def get_user(self, user_id: int) -> UserDto: + query_params = [] + if user_id: + query_params.append(f"id={user_id}") + parsed_json = self._do_get_request("users") + if parsed_json: + return UserDto(**parsed_json[0]) diff --git a/test/cassettes/test_stock/TestStock.test_get_expired_products_empty.yaml b/test/cassettes/test_stock/TestStock.test_get_expired_products_empty.yaml new file mode 100644 index 0000000..7092d22 --- /dev/null +++ b/test/cassettes/test_stock/TestStock.test_get_expired_products_empty.yaml @@ -0,0 +1,250 @@ +interactions: +- request: + body: null + headers: + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-requests/2.25.1 + accept: + - application/json + method: GET + uri: https://localhost/api/stock/volatile + response: + body: + string: !!binary | + H4sIAAAAAAAEA+1aS2/jNhD+K4TOjhDZSTbxrVgUvbRA0fZSFAuClsYy15Ko8OE0DfLfO0NRtuRX + vQvHSQEdfOAMNaTJ+fjNDPkSZQ54rVXmUmui6V9fRpFagd6SvkSiVK6y0TS6jUahwUWea8iFhYzk + 8TVqVqJwgK0kicfYnIGxfAZzpYFn2BE14+txcnU9ubruGFI1VN4ImWhG4o2sPwappenI+HpapKJJ + 2+faj4/N8K+49NPbCKLpS7SWVaKk/j/muSELYFItaytVFU0rVxQbK7lWrt7YEqmVq3akQqWCvmnU + 9MfNQtW1rHLeUzUWHx3247XT6UIYMjHBDxqhsSpd0iI1kjmOovS6K7eKtz2Sa7/epawa0dZKwFy4 + gpa+u/7PuMGRX6gDai7mFrRf+tN6zjXAP/g3T+ttF+Jp07mWqXXoF3NZAG+2oVkfqMQMRVag8glk + vrB8Iaqs2HzaUdHIfiUqZXm6gHQZ1mPuCrRclFBZjv7HNaSyhnYFajSOiq6LNIOnolBa+n7JmDYm + daUr0HX59lJzNefGzVobreUtJ3x0EqeUqsq4EjablPg5L2QGHN3Gbyon3K0kPIXF1OqJpxoIXtzK + EndSlDXqGgCNr5IJSx6mN8l0che9vo6+GaLju3iS4B/s+0gHo+Or8SfU9/AY5taTdfC43o3vROkD + jhf2ZI1SkgWU/qlypy22T8PpHc3+jDgle32cBskxnA4wJQSvHWOA6ZqyOgR4jElv44R6HoQpUql3 + dE/QgTbfGKYJkcg2Tr0wAPXzAgCp7b2AukuoQTIAdeDTb+fTG3TkwHg9oN74s70Nee/jhKjqIE6R + TskLe9T51jglft/BKQlbnKoiY6nDyH8LqtHPGLKXTNYYtrBMYUzEjLQMg2U7YhTNgAWM3pgRGfZJ + MTRjUEirR8xAxjIpSlapypXPDKQuVcYslDUakdVKZphIMGdZIWY4CAPbDACsFHklmCjko3tGA6AF + Dra2t1KFq60TMfvBMgyVFANl6GuRps5Qf8u+OgylWObwR3MGrwfBNMxcGbPfcdIsxXkKthQmY7mb + gcYcphrhbHEkwaxYylKg3ogKw1P83lh2eC3iI7phnTY+E2/8kHcSqXMHaMO5P+RRy0vnUclD/Ikc + +cjB7wO4ix78VEDYOfhJGA7+X0Wt5VKg4LRUyseqZ0ylQoED6yBtQeOEksd3plKUxGYftOIR1c1G + xF/rHKc5lD58ferNSx9JfH80p8LqCkVJF4UskdcOZEnYxmoOS1EYr6BkwOx7VimxJNjsxADaS9Yr + k9v4/sPxLKWGO6AlYQDtb5gfmcW7QZaor1+xDJI3KIR8ZJodLhYueLGAQL0j3joWEF+cXYnud4BK + wgDUPxSm/QrbA7e+K7davw8Ds56HWSmp2le5HPcql3dxco8dj+HV538XvGHwpdJtvHphwOsvslie + jNZz15n8VUefV1vRGxDrcGPvr5tOubFHj/if3NfjOxv4u5YaL/eDm4fXN6U0BsvpXWl4rtKr26sl + PVJYozt8hfcJhGS8fccHDrZ45u3zEH/RQI8E/MMXOhbarHKhUkXvG/bZmvhjYq+1JFpb6+SoP7kS + S/4zEHrv3Cgw/i9rneD5s5am3muIjqNDhr68/gsqXCVT1SQAAA== + headers: + Access-Control-Allow-Headers: + - '*' + Access-Control-Allow-Methods: + - GET, POST, PUT, DELETE, OPTIONS + Access-Control-Allow-Origin: + - '*' + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Thu, 27 May 2021 09:45:47 GMT + Server: + - nginx/1.18.0 + Transfer-Encoding: + - chunked + X-Powered-By: + - PHP/7.4.14 + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-requests/2.25.1 + accept: + - application/json + method: GET + uri: https://localhost/api/stock/volatile + response: + body: + string: !!binary | + H4sIAAAAAAAEA+1aS2/jNhD+K4TOjhDZSTbxrVgUvbRA0fZSFAuClsYy15Ko8OE0DfLfO0NRtuRX + vQvHSQEdfOAMNaTJ+fjNDPkSZQ54rVXmUmui6V9fRpFagd6SvkSiVK6y0TS6jUahwUWea8iFhYzk + 8TVqVqJwgK0kicfYnIGxfAZzpYFn2BE14+txcnU9ubruGFI1VN4ImWhG4o2sPwappenI+HpapKJJ + 2+faj4/N8K+49NPbCKLpS7SWVaKk/j/muSELYFItaytVFU0rVxQbK7lWrt7YEqmVq3akQqWCvmnU + 9MfNQtW1rHLeUzUWHx3247XT6UIYMjHBDxqhsSpd0iI1kjmOovS6K7eKtz2Sa7/epawa0dZKwFy4 + gpa+u/7PuMGRX6gDai7mFrRf+tN6zjXAP/g3T+ttF+Jp07mWqXXoF3NZAG+2oVkfqMQMRVag8glk + vrB8Iaqs2HzaUdHIfiUqZXm6gHQZ1mPuCrRclFBZjv7HNaSyhnYFajSOiq6LNIOnolBa+n7JmDYm + daUr0HX59lJzNefGzVobreUtJ3x0EqeUqsq4EjablPg5L2QGHN3Gbyon3K0kPIXF1OqJpxoIXtzK + EndSlDXqGgCNr5IJSx6mN8l0che9vo6+GaLju3iS4B/s+0gHo+Or8SfU9/AY5taTdfC43o3vROkD + jhf2ZI1SkgWU/qlypy22T8PpHc3+jDgle32cBskxnA4wJQSvHWOA6ZqyOgR4jElv44R6HoQpUql3 + dE/QgTbfGKYJkcg2Tr0wAPXzAgCp7b2AukuoQTIAdeDTb+fTG3TkwHg9oN74s70Nee/jhKjqIE6R + TskLe9T51jglft/BKQlbnKoiY6nDyH8LqtHPGLKXTNYYtrBMYUzEjLQMg2U7YhTNgAWM3pgRGfZJ + MTRjUEirR8xAxjIpSlapypXPDKQuVcYslDUakdVKZphIMGdZIWY4CAPbDACsFHklmCjko3tGA6AF + Dra2t1KFq60TMfvBMgyVFANl6GuRps5Qf8u+OgylWObwR3MGrwfBNMxcGbPfcdIsxXkKthQmY7mb + gcYcphrhbHEkwaxYylKg3ogKw1P83lh2eC3iI7phnTY+E2/8kHcSqXMHaMO5P+RRy0vnUclD/Ikc + +cjB7wO4ix78VEDYOfhJGA7+X0Wt5VKg4LRUyseqZ0ylQoED6yBtQeOEksd3plKUxGYftOIR1c1G + xF/rHKc5lD58ferNSx9JfH80p8LqCkVJF4UskdcOZEnYxmoOS1EYr6BkwOx7VimxJNjsxADaS9Yr + k9v4/sPxLKWGO6AlYQDtb5gfmcW7QZaor1+xDJI3KIR8ZJodLhYueLGAQL0j3joWEF+cXYnud4BK + wgDUPxSm/QrbA7e+K7davw8Ds56HWSmp2le5HPcql3dxco8dj+HV538XvGHwpdJtvHphwOsvslie + jNZz15n8VUefV1vRGxDrcGPvr5tOubFHj/if3NfjOxv4u5YaL/eDm4fXN6U0BsvpXWl4rtKr26sl + PVJYozt8hfcJhGS8fccHDrZ45u3zEH/RQI8E/MMXOhbarHKhUkXvG/bZmvhjYq+1JFpb6+SoP7kS + S/4zEHrv3Cgw/i9rneD5s5am3muIjqNDhr68/gsqXCVT1SQAAA== + headers: + Access-Control-Allow-Headers: + - '*' + Access-Control-Allow-Methods: + - GET, POST, PUT, DELETE, OPTIONS + Access-Control-Allow-Origin: + - '*' + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Thu, 27 May 2021 09:46:01 GMT + Server: + - nginx/1.18.0 + Transfer-Encoding: + - chunked + X-Powered-By: + - PHP/7.4.14 + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-requests/2.25.1 + accept: + - application/json + method: GET + uri: https://localhost/api/stock/volatile + response: + body: + string: !!binary | + H4sIAAAAAAAEA+1aS2/jNhD+K4TOjhDZSTbxrVgUvbRA0fZSFAuClsYy15Ko8OE0DfLfO0NRtuRX + vQvHSQEdfOAMNaTJ+fjNDPkSZQ54rVXmUmui6V9fRpFagd6SvkSiVK6y0TS6jUahwUWea8iFhYzk + 8TVqVqJwgK0kicfYnIGxfAZzpYFn2BE14+txcnU9ubruGFI1VN4ImWhG4o2sPwappenI+HpapKJJ + 2+faj4/N8K+49NPbCKLpS7SWVaKk/j/muSELYFItaytVFU0rVxQbK7lWrt7YEqmVq3akQqWCvmnU + 9MfNQtW1rHLeUzUWHx3247XT6UIYMjHBDxqhsSpd0iI1kjmOovS6K7eKtz2Sa7/epawa0dZKwFy4 + gpa+u/7PuMGRX6gDai7mFrRf+tN6zjXAP/g3T+ttF+Jp07mWqXXoF3NZAG+2oVkfqMQMRVag8glk + vrB8Iaqs2HzaUdHIfiUqZXm6gHQZ1mPuCrRclFBZjv7HNaSyhnYFajSOiq6LNIOnolBa+n7JmDYm + daUr0HX59lJzNefGzVobreUtJ3x0EqeUqsq4EjablPg5L2QGHN3Gbyon3K0kPIXF1OqJpxoIXtzK + EndSlDXqGgCNr5IJSx6mN8l0che9vo6+GaLju3iS4B/s+0gHo+Or8SfU9/AY5taTdfC43o3vROkD + jhf2ZI1SkgWU/qlypy22T8PpHc3+jDgle32cBskxnA4wJQSvHWOA6ZqyOgR4jElv44R6HoQpUql3 + dE/QgTbfGKYJkcg2Tr0wAPXzAgCp7b2AukuoQTIAdeDTb+fTG3TkwHg9oN74s70Nee/jhKjqIE6R + TskLe9T51jglft/BKQlbnKoiY6nDyH8LqtHPGLKXTNYYtrBMYUzEjLQMg2U7YhTNgAWM3pgRGfZJ + MTRjUEirR8xAxjIpSlapypXPDKQuVcYslDUakdVKZphIMGdZIWY4CAPbDACsFHklmCjko3tGA6AF + Dra2t1KFq60TMfvBMgyVFANl6GuRps5Qf8u+OgylWObwR3MGrwfBNMxcGbPfcdIsxXkKthQmY7mb + gcYcphrhbHEkwaxYylKg3ogKw1P83lh2eC3iI7phnTY+E2/8kHcSqXMHaMO5P+RRy0vnUclD/Ikc + +cjB7wO4ix78VEDYOfhJGA7+X0Wt5VKg4LRUyseqZ0ylQoED6yBtQeOEksd3plKUxGYftOIR1c1G + xF/rHKc5lD58ferNSx9JfH80p8LqCkVJF4UskdcOZEnYxmoOS1EYr6BkwOx7VimxJNjsxADaS9Yr + k9v4/sPxLKWGO6AlYQDtb5gfmcW7QZaor1+xDJI3KIR8ZJodLhYueLGAQL0j3joWEF+cXYnud4BK + wgDUPxSm/QrbA7e+K7davw8Ds56HWSmp2le5HPcql3dxco8dj+HV538XvGHwpdJtvHphwOsvslie + jNZz15n8VUefV1vRGxDrcGPvr5tOubFHj/if3NfjOxv4u5YaL/eDm4fXN6U0BsvpXWl4rtKr26sl + PVJYozt8hfcJhGS8fccHDrZ45u3zEH/RQI8E/MMXOhbarHKhUkXvG/bZmvhjYq+1JFpb6+SoP7kS + S/4zEHrv3Cgw/i9rneD5s5am3muIjqNDhr68/gsqXCVT1SQAAA== + headers: + Access-Control-Allow-Headers: + - '*' + Access-Control-Allow-Methods: + - GET, POST, PUT, DELETE, OPTIONS + Access-Control-Allow-Origin: + - '*' + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Thu, 27 May 2021 10:31:49 GMT + Server: + - nginx/1.18.0 + Transfer-Encoding: + - chunked + X-Powered-By: + - PHP/7.4.14 + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-requests/2.25.1 + accept: + - application/json + method: GET + uri: https://localhost/api/stock/volatile + response: + body: + string: !!binary | + H4sIAAAAAAAEA+1aS2/jNhD+K4TOjhDZSTbxrVgUvbRA0fZSFAuClsYy15Ko8OE0DfLfO0NRtuRX + vQvHSQEdfOAMNaTJ+fjNDPkSZQ54rVXmUmui6V9fRpFagd6SvkSiVK6y0TS6jUahwUWea8iFhYzk + 8TVqVqJwgK0kicfYnIGxfAZzpYFn2BE14+txcnU9ubruGFI1VN4ImWhG4o2sPwappenI+HpapKJJ + 2+faj4/N8K+49NPbCKLpS7SWVaKk/j/muSELYFItaytVFU0rVxQbK7lWrt7YEqmVq3akQqWCvmnU + 9MfNQtW1rHLeUzUWHx3247XT6UIYMjHBDxqhsSpd0iI1kjmOovS6K7eKtz2Sa7/epawa0dZKwFy4 + gpa+u/7PuMGRX6gDai7mFrRf+tN6zjXAP/g3T+ttF+Jp07mWqXXoF3NZAG+2oVkfqMQMRVag8glk + vrB8Iaqs2HzaUdHIfiUqZXm6gHQZ1mPuCrRclFBZjv7HNaSyhnYFajSOiq6LNIOnolBa+n7JmDYm + daUr0HX59lJzNefGzVobreUtJ3x0EqeUqsq4EjablPg5L2QGHN3Gbyon3K0kPIXF1OqJpxoIXtzK + EndSlDXqGgCNr5IJSx6mN8l0che9vo6+GaLju3iS4B/s+0gHo+Or8SfU9/AY5taTdfC43o3vROkD + jhf2ZI1SkgWU/qlypy22T8PpHc3+jDgle32cBskxnA4wJQSvHWOA6ZqyOgR4jElv44R6HoQpUql3 + dE/QgTbfGKYJkcg2Tr0wAPXzAgCp7b2AukuoQTIAdeDTb+fTG3TkwHg9oN74s70Nee/jhKjqIE6R + TskLe9T51jglft/BKQlbnKoiY6nDyH8LqtHPGLKXTNYYtrBMYUzEjLQMg2U7YhTNgAWM3pgRGfZJ + MTRjUEirR8xAxjIpSlapypXPDKQuVcYslDUakdVKZphIMGdZIWY4CAPbDACsFHklmCjko3tGA6AF + Dra2t1KFq60TMfvBMgyVFANl6GuRps5Qf8u+OgylWObwR3MGrwfBNMxcGbPfcdIsxXkKthQmY7mb + gcYcphrhbHEkwaxYylKg3ogKw1P83lh2eC3iI7phnTY+E2/8kHcSqXMHaMO5P+RRy0vnUclD/Ikc + +cjB7wO4ix78VEDYOfhJGA7+X0Wt5VKg4LRUyseqZ0ylQoED6yBtQeOEksd3plKUxGYftOIR1c1G + xF/rHKc5lD58ferNSx9JfH80p8LqCkVJF4UskdcOZEnYxmoOS1EYr6BkwOx7VimxJNjsxADaS9Yr + k9v4/sPxLKWGO6AlYQDtb5gfmcW7QZaor1+xDJI3KIR8ZJodLhYueLGAQL0j3joWEF+cXYnud4BK + wgDUPxSm/QrbA7e+K7davw8Ds56HWSmp2le5HPcql3dxco8dj+HV538XvGHwpdJtvHphwOsvslie + jNZz15n8VUefV1vRGxDrcGPvr5tOubFHj/if3NfjOxv4u5YaL/eDm4fXN6U0BsvpXWl4rtKr26sl + PVJYozt8hfcJhGS8fccHDrZ45u3zEH/RQI8E/MMXOhbarHKhUkXvG/bZmvhjYq+1JFpb6+SoP7kS + S/4zEHrv3Cgw/i9rneD5s5am3muIjqNDhr68/gsqXCVT1SQAAA== + headers: + Access-Control-Allow-Headers: + - '*' + Access-Control-Allow-Methods: + - GET, POST, PUT, DELETE, OPTIONS + Access-Control-Allow-Origin: + - '*' + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Thu, 27 May 2021 10:32:04 GMT + Server: + - nginx/1.18.0 + Transfer-Encoding: + - chunked + X-Powered-By: + - PHP/7.4.14 + status: + code: 200 + message: OK +version: 1 diff --git a/test/cassettes/test_users/TestUsers.test_get_user_by_id_valid.yaml b/test/cassettes/test_users/TestUsers.test_get_user_by_id_valid.yaml new file mode 100644 index 0000000..cae2453 --- /dev/null +++ b/test/cassettes/test_users/TestUsers.test_get_user_by_id_valid.yaml @@ -0,0 +1,46 @@ +interactions: +- request: + body: null + headers: + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-requests/2.25.1 + accept: + - application/json + method: GET + uri: https://localhost/api/users + response: + body: + string: !!binary | + H4sIAAAAAAAEA72RywrCMBBFfyVknUKT1oVd+wuuREJopzCQtCEPioj/bqJdqFi7kS7nXrjnwJyu + FDvaUE4ZjR7coAyk8wBmJMd0p7hH54N8FkPUmlGtPgI3TrJ1oAJ0MqABH5SxaUaUghelKHhF+L6p + eVPVabBDb7W6zJNvLIttiA5kjxpekDc2a4oFTZKLv4rufog+aGuq1ZJqLrZTzbQ11fyUb88nudhO + NdOWVM932fjmdqkCAAA= + headers: + Access-Control-Allow-Headers: + - '*' + Access-Control-Allow-Methods: + - GET, POST, PUT, DELETE, OPTIONS + Access-Control-Allow-Origin: + - '*' + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Wed, 28 Jul 2021 13:11:50 GMT + Server: + - nginx/1.18.0 + Transfer-Encoding: + - chunked + X-Powered-By: + - PHP/7.4.14 + status: + code: 200 + message: OK +version: 1 diff --git a/test/cassettes/test_users/TestUsers.test_get_users_valid.yaml b/test/cassettes/test_users/TestUsers.test_get_users_valid.yaml new file mode 100644 index 0000000..d005af2 --- /dev/null +++ b/test/cassettes/test_users/TestUsers.test_get_users_valid.yaml @@ -0,0 +1,46 @@ +interactions: +- request: + body: null + headers: + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-requests/2.25.1 + accept: + - application/json + method: GET + uri: https://localhost/api/users + response: + body: + string: !!binary | + H4sIAAAAAAAEA72RywrCMBBFfyVknUKT1oVd+wuuREJopzCQtCEPioj/bqJdqFi7kS7nXrjnwJyu + FDvaUE4ZjR7coAyk8wBmJMd0p7hH54N8FkPUmlGtPgI3TrJ1oAJ0MqABH5SxaUaUghelKHhF+L6p + eVPVabBDb7W6zJNvLIttiA5kjxpekDc2a4oFTZKLv4rufog+aGuq1ZJqLrZTzbQ11fyUb88nudhO + NdOWVM932fjmdqkCAAA= + headers: + Access-Control-Allow-Headers: + - '*' + Access-Control-Allow-Methods: + - GET, POST, PUT, DELETE, OPTIONS + Access-Control-Allow-Origin: + - '*' + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Fri, 28 May 2021 08:58:31 GMT + Server: + - nginx/1.18.0 + Transfer-Encoding: + - chunked + X-Powered-By: + - PHP/7.4.14 + status: + code: 200 + message: OK +version: 1 diff --git a/test/test_users.py b/test/test_users.py new file mode 100644 index 0000000..8ac0b97 --- /dev/null +++ b/test/test_users.py @@ -0,0 +1,21 @@ +import pytest + +from pygrocy.data_models.user import User + + +class TestUsers: + @pytest.mark.vcr + def test_get_user_by_id_valid(self, grocy): + user = grocy.user(user_id=1) + assert isinstance(user, User) + assert user.id == 1 + assert user.display_name == "Demo User" + assert user.username == "Demo User" + + @pytest.mark.vcr + def test_get_users_valid(self, grocy): + users = grocy.users() + + assert len(users) == 4 + user = users[0] + assert isinstance(user, User) From 689cd0f9d7d34b54914a5c250219c5d8b6ecd724 Mon Sep 17 00:00:00 2001 From: Sebastian Rutofski Date: Fri, 10 Sep 2021 18:57:07 +0200 Subject: [PATCH 7/8] add assigned_to_user field to Task (#190) * add assigned_to_user field to Task, fixes #188 * make assigned_to_user optional * add category field to Task, fixes #189 --- pygrocy/data_models/task.py | 39 ++++++++++++++++++++++++++++++++++++- pygrocy/grocy_api_client.py | 11 ++++++++++- test/test_tasks.py | 11 +++++++++-- 3 files changed, 57 insertions(+), 4 deletions(-) diff --git a/pygrocy/data_models/task.py b/pygrocy/data_models/task.py index 8a8ac4b..851d2a1 100644 --- a/pygrocy/data_models/task.py +++ b/pygrocy/data_models/task.py @@ -2,7 +2,32 @@ from typing import Dict from pygrocy.base import DataModel -from pygrocy.grocy_api_client import TaskResponse +from pygrocy.data_models.user import User +from pygrocy.grocy_api_client import TaskCategoryDto, TaskResponse + + +class TaskCategory(DataModel): + def __init__(self, data: TaskCategoryDto): + self._id = data.id + self._name = data.name + self._description = data.description + self._row_created_timestamp = data.row_created_timestamp + + @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 row_created_timestamp(self) -> datetime: + return self._row_created_timestamp class Task(DataModel): @@ -15,7 +40,11 @@ def __init__(self, response: TaskResponse): self._done = response.done self._done_timestamp = response.done_timestamp self._category_id = response.category_id + if response.category: + self._category = TaskCategory(response.category) self._assigned_to_user_id = response.assigned_to_user_id + if response.assigned_to_user: + self._assigned_to_user = User(response.assigned_to_user) self._userfields = response.userfields @property @@ -46,10 +75,18 @@ def done_timestamp(self) -> datetime: def category_id(self) -> int: return self._category_id + @property + def category(self) -> TaskCategory: + return self._category + @property def assigned_to_user_id(self) -> int: return self._assigned_to_user_id + @property + def assigned_to_user(self) -> User: + return self._assigned_to_user + @property def userfields(self) -> Dict[str, str]: return self._userfields diff --git a/pygrocy/grocy_api_client.py b/pygrocy/grocy_api_client.py index eb13c23..ddf4ddb 100644 --- a/pygrocy/grocy_api_client.py +++ b/pygrocy/grocy_api_client.py @@ -172,6 +172,13 @@ class TransactionType(Enum): PRODUCT_OPENED = "product-opened" +class TaskCategoryDto(BaseModel): + id: int + name: str + description: Optional[str] = None + row_created_timestamp: datetime + + class TaskResponse(BaseModel): id: int name: str @@ -180,7 +187,9 @@ class TaskResponse(BaseModel): done: int done_timestamp: Optional[datetime] = None category_id: Optional[int] = None - assigned_to_user_id: int + category: Optional[TaskCategoryDto] = None + assigned_to_user_id: Optional[int] = None + assigned_to_user: Optional[UserDto] = None userfields: Optional[Dict] = None diff --git a/test/test_tasks.py b/test/test_tasks.py index e1a1c6f..0ef0185 100644 --- a/test/test_tasks.py +++ b/test/test_tasks.py @@ -2,6 +2,8 @@ import pytest +from pygrocy.data_models.task import TaskCategory +from pygrocy.data_models.user import User from pygrocy.errors import GrocyError @@ -11,8 +13,13 @@ def test_get_tasks_valid(self, grocy): tasks = grocy.tasks() assert len(tasks) == 6 - assert tasks[0].id == 1 - assert tasks[0].name == "Repair the garage door" + task = tasks[0] + assert task.id == 1 + assert task.name == "Repair the garage door" + assert isinstance(task.assigned_to_user, User) + assert isinstance(task.category, TaskCategory) + assert task.category.id == 1 + assert task.category.name == "Home" @pytest.mark.vcr def test_complete_task_valid_with_defaults(self, grocy): From 9671b59aadc518c0f0539032458b88010d9853c9 Mon Sep 17 00:00:00 2001 From: Sebastian Rutofski Date: Fri, 10 Sep 2021 19:12:12 +0200 Subject: [PATCH 8/8] v1.0.0 prep --- CHANGELOG.md | 23 +++++++++++++++++++++++ setup.py | 2 +- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1fa0751..01953ab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,28 @@ # Changelog +## [v1.0.0](https://github.com/SebRut/pygrocy/tree/v1.0.0) (2021-09-10) + +[Full Changelog](https://github.com/SebRut/pygrocy/compare/v0.30.0...v1.0.0) + +**Implemented enhancements:** + +- Add assigned\_to\_user to task [\#188](https://github.com/SebRut/pygrocy/issues/188) +- Add support for meal plan sections in grocy v3.1.0 [\#172](https://github.com/SebRut/pygrocy/issues/172) + +**Closed issues:** + +- add category field to task [\#189](https://github.com/SebRut/pygrocy/issues/189) +- support consuming decimal amounts [\#187](https://github.com/SebRut/pygrocy/issues/187) + +**Merged pull requests:** + +- add assigned\_to\_user field to Task [\#190](https://github.com/SebRut/pygrocy/pull/190) ([SebRut](https://github.com/SebRut)) +- apply black to everything [\#185](https://github.com/SebRut/pygrocy/pull/185) ([SebRut](https://github.com/SebRut)) +- migrate to pydantic [\#184](https://github.com/SebRut/pygrocy/pull/184) ([SebRut](https://github.com/SebRut)) +- add basic support for product and note type [\#183](https://github.com/SebRut/pygrocy/pull/183) ([SebRut](https://github.com/SebRut)) +- add meal plan section support [\#182](https://github.com/SebRut/pygrocy/pull/182) ([SebRut](https://github.com/SebRut)) +- add users getter [\#170](https://github.com/SebRut/pygrocy/pull/170) ([SebRut](https://github.com/SebRut)) + ## [v0.30.0](https://github.com/SebRut/pygrocy/tree/v0.30.0) (2021-08-23) [Full Changelog](https://github.com/SebRut/pygrocy/compare/v0.29.0...v0.30.0) diff --git a/setup.py b/setup.py index 556fa9a..c0efc1e 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ setuptools.setup( name="pygrocy", - version="0.30.0", + version="1.0.0", author="Sebastian Rutofski", author_email="kontakt@sebastian-rutofski.de", description="",