diff --git a/CHANGELOG.md b/CHANGELOG.md index 6ea1576..d726b89 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,23 @@ # Changelog +## [v0.29.0](https://github.com/SebRut/pygrocy/tree/v0.29.0) (2021-03-03) + +[Full Changelog](https://github.com/SebRut/pygrocy/compare/v0.28.0...v0.29.0) + +**Implemented enhancements:** + +- Missing generic put and get option [\#155](https://github.com/SebRut/pygrocy/issues/155) +- add "get all products" method [\#97](https://github.com/SebRut/pygrocy/issues/97) + +**Closed issues:** + +- 404 errors and unresponsive sensors after configuring integration in HA [\#154](https://github.com/SebRut/pygrocy/issues/154) + +**Merged pull requests:** + +- add all\_products method [\#157](https://github.com/SebRut/pygrocy/pull/157) ([SebRut](https://github.com/SebRut)) +- add support for generic objcts [\#156](https://github.com/SebRut/pygrocy/pull/156) ([SebRut](https://github.com/SebRut)) + ## [v0.28.0](https://github.com/SebRut/pygrocy/tree/v0.28.0) (2021-02-20) [Full Changelog](https://github.com/SebRut/pygrocy/compare/v0.27.0...v0.28.0) @@ -196,6 +214,7 @@ - Update coveralls requirement from ~=2.0.0 to ~=2.1.1 [\#104](https://github.com/SebRut/pygrocy/pull/104) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview)) - Update pdoc3 requirement from ~=0.8.1 to ~=0.8.3 [\#102](https://github.com/SebRut/pygrocy/pull/102) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview)) - Update responses requirement from ~=0.10.14 to ~=0.10.15 [\#101](https://github.com/SebRut/pygrocy/pull/101) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview)) +- add all fields from chore details api call [\#98](https://github.com/SebRut/pygrocy/pull/98) ([SebRut](https://github.com/SebRut)) ## [v0.15.0](https://github.com/SebRut/pygrocy/tree/v0.15.0) (2020-05-25) @@ -219,7 +238,6 @@ - upgrade used grocy version [\#100](https://github.com/SebRut/pygrocy/pull/100) ([SebRut](https://github.com/SebRut)) - Feature/91 task api [\#99](https://github.com/SebRut/pygrocy/pull/99) ([SebRut](https://github.com/SebRut)) -- add all fields from chore details api call [\#98](https://github.com/SebRut/pygrocy/pull/98) ([SebRut](https://github.com/SebRut)) - finish abstraction from api [\#96](https://github.com/SebRut/pygrocy/pull/96) ([SebRut](https://github.com/SebRut)) - fix travis build [\#93](https://github.com/SebRut/pygrocy/pull/93) ([SebRut](https://github.com/SebRut)) - Update responses requirement from ~=0.10.12 to ~=0.10.14 [\#89](https://github.com/SebRut/pygrocy/pull/89) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview)) diff --git a/README.md b/README.md index aa358bd..6defed5 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,7 @@ [![Development Build Status](https://api.travis-ci.com/SebRut/pygrocy.svg?branch=develop)](https://travis-ci.com/SebRut/pygrocy) [![PyPI](https://img.shields.io/pypi/v/pygrocy.svg)](https://pypi.org/project/pygrocy/) ![Python Version](https://img.shields.io/badge/python-3.6%20%7C%203.8%20%7C%203.9-blue) +![Grocy Version](https://img.shields.io/badge/grocy-3.0.1-yellow) [![Coverage Status](https://coveralls.io/repos/github/SebRut/pygrocy/badge.svg?branch=master)](https://coveralls.io/github/SebRut/pygrocy?branch=master) [![CodeFactor](https://www.codefactor.io/repository/github/sebrut/pygrocy/badge)](https://www.codefactor.io/repository/github/sebrut/pygrocy) [![pre-commit](https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit&logoColor=white)](https://github.com/pre-commit/pre-commit) diff --git a/pygrocy/__init__.py b/pygrocy/__init__.py index 9a787d3..392c4c8 100644 --- a/pygrocy/__init__.py +++ b/pygrocy/__init__.py @@ -1,6 +1,7 @@ """ The pygrocy module """ +from .data_models.generic import EntityType # noqa: F401 from .grocy import Grocy # noqa: F401 from .grocy_api_client import TransactionType # noqa: F401 diff --git a/pygrocy/data_models/generic.py b/pygrocy/data_models/generic.py new file mode 100644 index 0000000..d85af48 --- /dev/null +++ b/pygrocy/data_models/generic.py @@ -0,0 +1,25 @@ +from enum import Enum + + +class EntityType(str, Enum): + PRODUCTS = "products" + CHORES = "chores" + PRODUCT_BARCODES = "product_barcodes" + BATTERIES = "batteries" + LOCATIONS = "locations" + QUANTITY_UNITS = "quantity_units" + QUANTITY_UNIT_CONVERSIONS = "quantity_unit_conversions" + SHOPPING_LIST = "shopping_list" + SHOPPING_LISTS = "shopping_lists" + SHOPPING_LOCATIONS = "shopping_locations" + RECIPES = "recipes" + RECIPES_POS = "recipes_pos" + RECIPES_NESTINGS = "recipes_nestings" + TASKS = "tasks" + TASK_CATEGORIES = "task_categories" + PRODUCT_GROUPS = "product_groups" + EQUIPMENT = "equipment" + USER_FIELDS = "userfields" + USER_ENTITIES = "userentities" + USER_OBJECTS = "userobjects" + MEAL_PLAN = "meal_plan" diff --git a/pygrocy/data_models/product.py b/pygrocy/data_models/product.py index 18634b5..1fae201 100644 --- a/pygrocy/data_models/product.py +++ b/pygrocy/data_models/product.py @@ -8,20 +8,23 @@ LocationData, MissingProductResponse, ProductBarcode, + ProductData, ProductDetailsResponse, ShoppingListItem, ) class Product(DataModel): - def __init__(self, response): + def __init__(self, data): self._init_empty() - if isinstance(response, CurrentStockResponse): - self._init_from_CurrentStockResponse(response) - elif isinstance(response, MissingProductResponse): - self._init_from_MissingProductResponse(response) - elif isinstance(response, ProductDetailsResponse): - self._init_from_ProductDetailsResponse(response) + if isinstance(data, CurrentStockResponse): + self._init_from_CurrentStockResponse(data) + elif isinstance(data, MissingProductResponse): + self._init_from_MissingProductResponse(data) + elif isinstance(data, ProductDetailsResponse): + self._init_from_ProductDetailsResponse(data) + elif isinstance(data, ProductData): + self._init_from_ProductData(data) def _init_empty(self): self._name = None @@ -39,9 +42,9 @@ def _init_from_CurrentStockResponse(self, response: CurrentStockResponse): self._id = response.product_id self._available_amount = response.amount self._best_before_date = response.best_before_date + if response.product: - self._name = response.product.name - self._product_group_id = response.product.product_group_id + self._init_from_ProductData(response.product) def _init_from_MissingProductResponse(self, response: MissingProductResponse): self._id = response.product_id @@ -50,13 +53,18 @@ def _init_from_MissingProductResponse(self, response: MissingProductResponse): self._is_partly_in_stock = response.is_partly_in_stock def _init_from_ProductDetailsResponse(self, response: ProductDetailsResponse): - self._id = response.product.id - self._product_group_id = response.product.product_group_id self._available_amount = response.stock_amount self._best_before_date = response.next_best_before_date - self._name = response.product.name self._barcodes = response.barcodes + if response.product: + self._init_from_ProductData(response.product) + + def _init_from_ProductData(self, product: ProductData): + self._id = product.id + self._product_group_id = product.product_group_id + self._name = product.name + def get_details(self, api_client: GrocyApiClient): details = api_client.get_product(self.id) if details: diff --git a/pygrocy/grocy.py b/pygrocy/grocy.py index cab71af..83f22f9 100644 --- a/pygrocy/grocy.py +++ b/pygrocy/grocy.py @@ -6,6 +6,7 @@ from .base import DataModel # noqa: F401 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.product import Group, Product, ShoppingListProduct from .data_models.task import Task @@ -81,6 +82,13 @@ def product(self, product_id: int) -> Product: if resp: return Product(resp) + 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] + return [Product(product) for product in product_datas] + def chores(self, get_details: bool = False) -> List[Chore]: raw_chores = self._api_client.get_chores() chores = [Chore(chore) for chore in raw_chores] @@ -192,9 +200,6 @@ def recipe(self, recipe_id: int) -> RecipeItem: if recipe: return RecipeItem(recipe) - def add_generic(self, entity_type, data): - return self._api_client.add_generic(entity_type, data) - def batteries(self) -> List[Battery]: raw_batteries = self._api_client.get_batteries() return [Battery(bat) for bat in raw_batteries] @@ -206,3 +211,17 @@ def battery(self, battery_id: int) -> Battery: def charge_battery(self, battery_id: int, tracked_time: datetime = datetime.now()): return self._api_client.charge_battery(battery_id, tracked_time) + + def add_generic(self, entity_type: EntityType, data): + return self._api_client.add_generic(entity_type.value, data) + + def update_generic(self, entity_type: EntityType, object_id: int, updated_data): + return self._api_client.update_generic( + entity_type.value, object_id, updated_data + ) + + def delete_generic(self, entity_type: EntityType, object_id: int): + return self._api_client.delete_generic(entity_type, object_id) + + def get_generic_objects_for_type(self, entity_type: EntityType): + return self._api_client.get_generic_objects_for_type(entity_type.value) diff --git a/pygrocy/grocy_api_client.py b/pygrocy/grocy_api_client.py index 09824d0..e283ce9 100644 --- a/pygrocy/grocy_api_client.py +++ b/pygrocy/grocy_api_client.py @@ -584,6 +584,15 @@ def _do_put_request(self, end_url: str, data): if len(resp.content) > 0: return resp.json() + def _do_delete_request(self, end_url: str): + req_url = urljoin(self._base_url, end_url) + resp = requests.get(req_url, verify=self._verify_ssl, headers=self._headers) + if resp.status_code >= 400: + raise GrocyError(resp) + + if len(resp.content) > 0: + return resp.json() + def get_stock(self) -> List[CurrentStockResponse]: parsed_json = self._do_get_request("stock") return [CurrentStockResponse(response) for response in parsed_json] @@ -742,9 +751,6 @@ def get_recipe(self, object_id: int) -> RecipeDetailsResponse: if parsed_json: return RecipeDetailsResponse(parsed_json) - def add_generic(self, entity_type: str, data: object): - self._do_post_request(f"objects/{entity_type}", data) - def get_batteries(self) -> List[CurrentBatteryResponse]: parsed_json = self._do_get_request(f"batteries") if parsed_json: @@ -760,3 +766,15 @@ def charge_battery(self, battery_id: int, tracked_time: datetime = datetime.now( data = {"tracked_time": localized_tracked_time.isoformat()} return self._do_post_request(f"batteries/{battery_id}/charge", data) + + def add_generic(self, entity_type: str, data): + return self._do_post_request(f"objects/{entity_type}", data) + + def update_generic(self, entity_type: str, object_id: int, data): + return self._do_put_request(f"objects/{entity_type}/{object_id}", data) + + def delete_generic(self, entity_type: str, object_id: int): + return self._do_delete_request(f"objects/{entity_type}/{object_id}") + + def get_generic_objects_for_type(self, entity_type: str): + return self._do_get_request(f"objects/{entity_type}") diff --git a/setup.py b/setup.py index c1ca23f..b98bffc 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ setuptools.setup( name="pygrocy", - version="0.28.0", + version="0.29.0", author="Sebastian Rutofski", author_email="kontakt@sebastian-rutofski.de", description="", diff --git a/test/cassettes/test_generic/TestGeneric.test_delete_generic_error.yaml b/test/cassettes/test_generic/TestGeneric.test_delete_generic_error.yaml new file mode 100644 index 0000000..c56a6ae --- /dev/null +++ b/test/cassettes/test_generic/TestGeneric.test_delete_generic_error.yaml @@ -0,0 +1,43 @@ +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/objects/tasks/30000 + response: + body: + string: !!binary | + H4sIAAAAAAAEA6tWSi0qyi+Kz00tLk5MT1WyUvJPykpNLlHIyy9RSMsvzUtRqgUAcMoJfiQAAAA= + 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, 03 Mar 2021 13:12:37 GMT + Server: + - nginx/1.18.0 + Transfer-Encoding: + - chunked + X-Powered-By: + - PHP/7.4.14 + status: + code: 404 + message: Not Found +version: 1 diff --git a/test/cassettes/test_generic/TestGeneric.test_delete_generic_success.yaml b/test/cassettes/test_generic/TestGeneric.test_delete_generic_success.yaml new file mode 100644 index 0000000..da02ff5 --- /dev/null +++ b/test/cassettes/test_generic/TestGeneric.test_delete_generic_success.yaml @@ -0,0 +1,45 @@ +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/objects/tasks/3 + response: + body: + string: !!binary | + H4sIAAAAAAAEA0WNSwoCMRBEryJZO5CPCOYc7kNI2iGYz5DOICLe3W5HmV11Vb2ul0hRWGHEUVRf + gOTV413RGQFDT8tIrQpb15zJWsFFP7ilpVaT1JPSXG2VPfmTbqQCOHxZ/mAgaG796b5jjHjENFeI + bjS3IvQt4d3eHi50IILC/dG+aA7qYk/KmjO1mb0lyBG3rfcHvUz3sNEAAAA= + 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, 03 Mar 2021 13:12:37 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_generic/TestGeneric.test_generic_add_invalid.yaml b/test/cassettes/test_generic/TestGeneric.test_generic_add_invalid.yaml new file mode 100644 index 0000000..781ed43 --- /dev/null +++ b/test/cassettes/test_generic/TestGeneric.test_generic_add_invalid.yaml @@ -0,0 +1,45 @@ +interactions: +- request: + body: '{"eman": "Testbattery"}' + headers: + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '23' + Content-Type: + - application/json + User-Agent: + - python-requests/2.25.1 + accept: + - application/json + method: POST + uri: https://localhost/api/objects/batteries + response: + body: + string: '{"error_message":"SQLSTATE[HY000]: General error: 1 table batteries + has no column named eman"}' + headers: + Access-Control-Allow-Headers: + - '*' + Access-Control-Allow-Methods: + - GET, POST, PUT, DELETE, OPTIONS + Access-Control-Allow-Origin: + - '*' + Connection: + - keep-alive + Content-Type: + - application/json + Date: + - Wed, 03 Mar 2021 13:07:14 GMT + Server: + - nginx/1.18.0 + Transfer-Encoding: + - chunked + X-Powered-By: + - PHP/7.4.14 + status: + code: 400 + message: Bad Request +version: 1 diff --git a/test/cassettes/test_generic/TestGeneric.test_generic_add_valid.yaml b/test/cassettes/test_generic/TestGeneric.test_generic_add_valid.yaml new file mode 100644 index 0000000..87097cb --- /dev/null +++ b/test/cassettes/test_generic/TestGeneric.test_generic_add_valid.yaml @@ -0,0 +1,47 @@ +interactions: +- request: + body: '{"name": "Testbattery"}' + headers: + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '23' + Content-Type: + - application/json + User-Agent: + - python-requests/2.25.1 + accept: + - application/json + method: POST + uri: https://localhost/api/objects/batteries + response: + body: + string: !!binary | + H4sIAAAAAAAEA6tWSi5KTSxJTYnPT8pKTS6Jz0xRslIyVaoFAFvm3pQZAAAA + 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, 03 Mar 2021 13:06:48 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_generic/TestGeneric.test_generic_update_invalid_data.yaml b/test/cassettes/test_generic/TestGeneric.test_generic_update_invalid_data.yaml new file mode 100644 index 0000000..36c6279 --- /dev/null +++ b/test/cassettes/test_generic/TestGeneric.test_generic_update_invalid_data.yaml @@ -0,0 +1,45 @@ +interactions: +- request: + body: null + headers: + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '0' + Content-Type: + - application/octet-stream + User-Agent: + - python-requests/2.25.1 + accept: + - '*/*' + method: PUT + uri: https://localhost/api/objects/batteries/1 + response: + body: + string: '{"error_message":"Request body could not be parsed (probably invalid + JSON format or missing\/wrong Content-Type header)"}' + headers: + Access-Control-Allow-Headers: + - '*' + Access-Control-Allow-Methods: + - GET, POST, PUT, DELETE, OPTIONS + Access-Control-Allow-Origin: + - '*' + Connection: + - keep-alive + Content-Type: + - application/json + Date: + - Wed, 03 Mar 2021 11:40:26 GMT + Server: + - nginx/1.18.0 + Transfer-Encoding: + - chunked + X-Powered-By: + - PHP/7.4.14 + status: + code: 400 + message: Bad Request +version: 1 diff --git a/test/cassettes/test_generic/TestGeneric.test_generic_update_invalid_id.yaml b/test/cassettes/test_generic/TestGeneric.test_generic_update_invalid_id.yaml new file mode 100644 index 0000000..8051a70 --- /dev/null +++ b/test/cassettes/test_generic/TestGeneric.test_generic_update_invalid_id.yaml @@ -0,0 +1,65 @@ +interactions: +- request: + body: '{"name": "Le new battery"}' + headers: + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '26' + Content-Type: + - application/json + User-Agent: + - python-requests/2.25.1 + accept: + - '*/*' + method: PUT + uri: https://localhost/api/objects/batteries/1000 + response: + body: + string: '{"error_message":"Call to a member function update() on null","error_details":{"stack_trace":"#0 + \/var\/www\/vendor\/slim\/slim\/Slim\/Handlers\/Strategies\/RequestResponse.php(43): + Grocy\\Controllers\\GenericEntityApiController->EditObject()\n#1 \/var\/www\/vendor\/slim\/slim\/Slim\/Routing\/Route.php(384): + Slim\\Handlers\\Strategies\\RequestResponse->__invoke()\n#2 \/var\/www\/vendor\/slim\/slim\/Slim\/MiddlewareDispatcher.php(81): + Slim\\Routing\\Route->handle()\n#3 \/var\/www\/middleware\/JsonMiddleware.php(13): + Slim\\MiddlewareDispatcher->handle()\n#4 \/var\/www\/vendor\/slim\/slim\/Slim\/MiddlewareDispatcher.php(209): + Grocy\\Middleware\\JsonMiddleware->__invoke()\n#5 \/var\/www\/vendor\/slim\/slim\/Slim\/MiddlewareDispatcher.php(81): + class@anonymous->handle()\n#6 \/var\/www\/vendor\/slim\/slim\/Slim\/Routing\/Route.php(341): + Slim\\MiddlewareDispatcher->handle()\n#7 \/var\/www\/vendor\/slim\/slim\/Slim\/Routing\/RouteRunner.php(84): + Slim\\Routing\\Route->run()\n#8 \/var\/www\/middleware\/AuthMiddleware.php(48): + Slim\\Routing\\RouteRunner->handle()\n#9 \/var\/www\/vendor\/slim\/slim\/Slim\/MiddlewareDispatcher.php(313): + Grocy\\Middleware\\AuthMiddleware->__invoke()\n#10 \/var\/www\/vendor\/slim\/slim\/Slim\/Middleware\/RoutingMiddleware.php(59): + class@anonymous->handle()\n#11 \/var\/www\/vendor\/slim\/slim\/Slim\/MiddlewareDispatcher.php(147): + Slim\\Middleware\\RoutingMiddleware->process()\n#12 \/var\/www\/vendor\/slim\/slim\/Slim\/Middleware\/ErrorMiddleware.php(107): + class@anonymous->handle()\n#13 \/var\/www\/vendor\/slim\/slim\/Slim\/MiddlewareDispatcher.php(147): + Slim\\Middleware\\ErrorMiddleware->process()\n#14 \/var\/www\/middleware\/CorsMiddleware.php(30): + class@anonymous->handle()\n#15 \/var\/www\/vendor\/slim\/slim\/Slim\/MiddlewareDispatcher.php(313): + Grocy\\Middleware\\CorsMiddleware->__invoke()\n#16 \/var\/www\/vendor\/slim\/slim\/Slim\/MiddlewareDispatcher.php(81): + class@anonymous->handle()\n#17 \/var\/www\/vendor\/slim\/slim\/Slim\/App.php(215): + Slim\\MiddlewareDispatcher->handle()\n#18 \/var\/www\/vendor\/slim\/slim\/Slim\/App.php(199): + Slim\\App->handle()\n#19 \/var\/www\/app.php(94): Slim\\App->run()\n#20 \/var\/www\/public\/index.php(45): + require_once(''\/var\/www\/app.ph...'')\n#21 {main}","file":"\/var\/www\/controllers\/GenericEntityApiController.php","line":93}}' + headers: + Access-Control-Allow-Headers: + - '*' + Access-Control-Allow-Methods: + - GET, POST, PUT, DELETE, OPTIONS + Access-Control-Allow-Origin: + - '*' + Connection: + - keep-alive + Content-Type: + - application/json + Date: + - Wed, 03 Mar 2021 11:35:43 GMT + Server: + - nginx/1.18.0 + Transfer-Encoding: + - chunked + X-Powered-By: + - PHP/7.4.14 + status: + code: 500 + message: Internal Server Error +version: 1 diff --git a/test/cassettes/test_generic/TestGeneric.test_generic_update_valid.yaml b/test/cassettes/test_generic/TestGeneric.test_generic_update_valid.yaml new file mode 100644 index 0000000..52629d9 --- /dev/null +++ b/test/cassettes/test_generic/TestGeneric.test_generic_update_valid.yaml @@ -0,0 +1,42 @@ +interactions: +- request: + body: '{"name": "Le new battery"}' + headers: + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '26' + Content-Type: + - application/json + User-Agent: + - python-requests/2.25.1 + accept: + - '*/*' + method: PUT + uri: https://localhost/api/objects/batteries/1 + response: + body: + string: '' + headers: + Access-Control-Allow-Headers: + - '*' + Access-Control-Allow-Methods: + - GET, POST, PUT, DELETE, OPTIONS + Access-Control-Allow-Origin: + - '*' + Connection: + - keep-alive + Content-Type: + - application/json + Date: + - Wed, 03 Mar 2021 11:25:56 GMT + Server: + - nginx/1.18.0 + X-Powered-By: + - PHP/7.4.14 + status: + code: 204 + message: No Content +version: 1 diff --git a/test/cassettes/test_product/TestProduct.test_get_all_products.yaml b/test/cassettes/test_product/TestProduct.test_get_all_products.yaml new file mode 100644 index 0000000..2ddccad --- /dev/null +++ b/test/cassettes/test_product/TestProduct.test_get_all_products.yaml @@ -0,0 +1,68 @@ +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/objects/products + response: + body: + string: !!binary | + H4sIAAAAAAAEA+1cXW/bNhT9K4SfXSNy2q3N29B1e9mGYCkwDMNA0BQtsaZEhR8OkmH/ffdalG0F + iSLXVh17fEiQ8Fu8R0fn3kvpr39GMh1djZLReFSyQsCfH7VeSGGhIBWWG1k5qcvRVemVGo8qo1PP + Hc2M9hVdd2XcySV2xnGU5gz71NVvocTmuqpkmdFWVT3irYd2tPKG58ziEJfQoS60TvPFpmQOs2iz + bkqdpk2LZHIBvQpZ1iWUFdqXDrq+h+JUzJlXjs6ExV9zbQRN2b2Fauz1XDVlcycM1ZWAq+/Tcm6E + eICr7Nfa5exu07iS3HlY11wqQYMheG2IyZcqg2WKks2gzjFodSdkljuaszJVmzG2qnAJqy0ptaM8 + F3wRNmbuFUyhClE6ChtBjeCyAmPXF1jB4FDRGBnNWxuJM6UNggIsPEUDcV94xZygj/ec6jm1ftaM + 0Yycelj6fdVA5NZLWBLXpfWF2FirNmMuU0EBPivjUr0UZinFXVii0XeUGwFTp9TJAkzKigrqphfT + 5M3F9E1ySZIPV2+Tq8vvYJneCjOXQqWwDrySf8cB71OobPCea67xWqAoIv6YiK+xdrpAR/J7bUDH + mzUA/WdfFPdkJpiJ5H50cs/QGCtbRH4fgN9RdTT8bqStzgTxSZdgCU/I16pnTp3dV1v/ymTMuw3M + P2VZf5Bjvw7NjupoT82OQ4CK79LstUJ9LCBfQPEL1cfm9Qjyw2t1FPKBy3/TOlU7+KbYtQPnKI72 + xDkO8QLOn/NNI5ejf0+jS1qHYL7fwPwavONdYI4g7IA5sv1OMEf11A7BhJIuOo8wx6AMPp9i5KUj + 8oIRubVDqpjNiYVQIhT2i70cGup4b7ShHkoi1GOQcc8g44cN1P/UmTeuN8pf0C0763Mcr43yUBJR + HlG+J8pXMYkm1pILAUmcvmR+aJgHMQ75pCYxFOV5zBgdJmOUtFKkKiXcO/sY6aNfILlYEFlBXouk + GpJmxEpHILHqxgTTXcIJyPMRy1JowyF3R4SSzoyJFSlJJStIqUsPYXohTaFT4kRRwSCyXMoUMprE + O6LYDCYhwtUTCFKwrGSEKXnr72EAYRhMth5vqZWvnGcT8oMjkEvTRGiLvRnn3mJ7R754uF9I6uEH + 1yxW9YIRI2a+mJAbWDThsE5GFsymJPMzYTJIFY5htTATI44tZMGg3rISEpnQ3zry/F5MOuriPm0w + MwF8NcnYrYx7pM0Q5jtwxj0G7w4fvEu2Mu3XrDJywR6TZr3tT+D80FHqEJPekgc9otRfGdbAaM5r + zcSMqtoQMfM4QOYxQcnZyGEP52TgWRkRf/SzVMESEfJDQB6jwwHyv4OytfnRAI+02w50hJIBAh2v + meKjlBlAyqAeCTD/rMHd0UeDeVQywQ5uZYfI6kOwOrqZYZuv5cMDg+CAz/pTe/RSo5fqTuQ4eILP + 8oD1GymWEIyrmWWHsyao+w+ZhI+ZydogUcoMIGW2svA3TLFC9pYykdYjrZ8MrW9l4D9rZvsn4FFg + d5A5cv1OJ6oimUcyXwz1LhseJA3i5VdZchAvBbwg15vRMXrTgXW8FXbCOt4c7RhMKBkgBtN1hnaf + s+DJ+x4jb17hxD166hXO6cW5uaZ15OHYb29u5eJ/Utob2P6DHB/cmddRR7WxHkpOCOs7IR0bP4H0 + PiIdjdZ+R/ndxVBnZRGmJ/+OMl5E45T6jB0P5efA6N8K5ThPG+VDYRyf2yePcYTWWruoBfzXj8gP + 7YauTjG2mbwpilR+zDOy5wByvFO3QE4+ntcnJ7qoNb6vGXbgiW+rjFYq5ew+rbKVJv2RmUVE+/qL + Q/t4pF03WRh3449i469U6YPplzNF+1aW9A82nytBjFbK9hYy6BV1RF/w0fG/i76cPNZrd/hsvpr1 + 93+ZCZeaJ04AAA== + 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, 03 Mar 2021 13:35:56 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_generic.py b/test/test_generic.py new file mode 100644 index 0000000..18706ed --- /dev/null +++ b/test/test_generic.py @@ -0,0 +1,60 @@ +import pytest + +from pygrocy.data_models.generic import EntityType +from pygrocy.errors.grocy_error import GrocyError + + +class TestGeneric: + @pytest.mark.vcr + def test_generic_add_valid(self, grocy): + data = {"name": "Testbattery"} + + grocy.add_generic(EntityType.BATTERIES, data) + + @pytest.mark.vcr + def test_generic_add_invalid(self, grocy): + data = {"eman": "Testbattery"} + + with pytest.raises(GrocyError) as exc_info: + grocy.add_generic(EntityType.BATTERIES, data) + + error = exc_info.value + assert error.status_code == 400 + + @pytest.mark.vcr + def test_generic_update_valid(self, grocy): + updated_data = {"name": "Le new battery"} + + grocy.update_generic(EntityType.BATTERIES, 1, updated_data) + + @pytest.mark.vcr + def test_generic_update_invalid_id(self, grocy): + updated_data = {"name": "Le new battery"} + + with pytest.raises(GrocyError) as exc_info: + grocy.update_generic(EntityType.BATTERIES, 1000, updated_data) + + error = exc_info.value + assert error.status_code == 500 + assert error.message[:7] == "Call to" + + @pytest.mark.vcr + def test_generic_update_invalid_data(self, grocy): + with pytest.raises(GrocyError) as exc_info: + grocy.update_generic(EntityType.BATTERIES, 1, None) + + error = exc_info.value + assert error.status_code == 400 + assert error.message[:7] == "Request" + + @pytest.mark.vcr + def test_delete_generic_success(self, grocy): + grocy.delete_generic(EntityType.TASKS, 3) + + @pytest.mark.vcr + def test_delete_generic_error(self, grocy): + with pytest.raises(GrocyError) as exc_info: + grocy.delete_generic(EntityType.TASKS, 30000) + + error = exc_info.value + assert error.status_code == 404 diff --git a/test/test_grocy.py b/test/test_grocy.py index 4696fd7..d1bec24 100644 --- a/test/test_grocy.py +++ b/test/test_grocy.py @@ -202,18 +202,6 @@ def test_add_product_error(self): GrocyError, self.grocy.add_product, 1, 1.3, 2.44, self.date_test ) - @responses.activate - def test_add_generic_valid(self): - responses.add(responses.POST, f"{self.base_url}/objects/tasks", status=200) - self.assertIsNone(self.grocy.add_generic("tasks", self.add_generic_data)) - - @responses.activate - def test_add_generic_error(self): - responses.add(responses.POST, f"{self.base_url}/objects/tasks", status=400) - self.assertRaises( - GrocyError, self.grocy.add_generic, "tasks", self.add_generic_data - ) - @responses.activate def test_consume_product_valid(self): responses.add( diff --git a/test/test_product.py b/test/test_product.py index 5137cb1..a28a03c 100644 --- a/test/test_product.py +++ b/test/test_product.py @@ -5,6 +5,16 @@ class TestProduct: + @pytest.mark.vcr + def test_get_all_products(self, grocy): + products = grocy.all_products() + + assert len(products) == 26 + + product = products[0] + assert product.id == 1 + assert product.name == "Cookies" + @pytest.mark.vcr def test_product_get_details_valid(self, grocy): product = grocy.product(10)