Repository: SebRut/pygrocy
Pull request #25 (2019-09-04 12:57:29)

Product Groups
## Description

Product Groups list

## Checklist
  - [x] The code change is tested and works locally.
  - [x] tests pass. **Your PR won't be merged unless tests pass**
  - [x] There is no commented out code in this PR


BlueBlueBlob: Just wait before validation, a commit is missing : to expose groups in product class.

Patch:
diff --git a/pygrocy/grocy.py b/pygrocy/grocy.py
index ef02bea..a45046c 100644
--- a/pygrocy/grocy.py
+++ b/pygrocy/grocy.py
@@ -4,6 +4,7 @@ from typing import List
 from .grocy_api_client import (ChoreDetailsResponse, CurrentChoreResponse,
                                CurrentStockResponse,
                                ShoppingListItem,
+                               LocationData,
                                CurrentVolatilStockResponse, GrocyApiClient,
                                ProductData, ProductDetailsResponse,
                                TransactionType, UserDto)
@@ -17,6 +18,7 @@ class Product(object):
 
         self._name = None
         self._barcodes = None
+        self._product_group_id = None
 
     def get_details(self, api_client: GrocyApiClient):
         details = api_client.get_product(self.product_id)
@@ -24,6 +26,7 @@ class Product(object):
             return
         self._name = details.product.name
         self._barcodes = details.product.barcodes
+        self._product_group_id = details.product.product_group_id
 
     @property
     def name(self) -> str:
@@ -32,6 +35,10 @@ class Product(object):
     @property
     def product_id(self) -> int:
         return self._product_id
+        
+    @property
+    def product_group_id(self) -> int:
+        return self._product_group_id
 
     @property
     def available_amount(self) -> float:
@@ -45,6 +52,24 @@ class Product(object):
     def barcodes(self) -> List[str]:
         return self._barcodes
 
+class Group(object):
+    def __init__(self, raw_product_group: LocationData):
+        self._id = raw_product_group.id
+        self._name = raw_product_group.name
+        self._description = raw_product_group.description
+        
+    @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 ShoppingListProduct(object):
     def __init__(self, raw_shopping_list: ShoppingListItem):
         self._id = raw_shopping_list.id
@@ -186,4 +211,11 @@ class Grocy(object):
 
     def remove_product_in_shopping_list(self, shopping_list_product_id: int):
         return self._api_client.remove_product_in_sl(shopping_list_product_id)
+        
+    def product_groups(self) -> List[Group]:
+        raw_groups = self._api_client.get_product_groups()
+        if raw_groups is None:
+            return
+        return [Group(resp) for resp in raw_groups]
+        
         
\ No newline at end of file
diff --git a/pygrocy/grocy_api_client.py b/pygrocy/grocy_api_client.py
index faa88d9..a49b40b 100644
--- a/pygrocy/grocy_api_client.py
+++ b/pygrocy/grocy_api_client.py
@@ -49,6 +49,18 @@ class LocationData(object):
         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):
@@ -57,6 +69,7 @@ class ProductData(object):
         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))
@@ -76,6 +89,10 @@ class ProductData(object):
     @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:
@@ -375,3 +392,12 @@ class GrocyApiClient(object):
         resp = requests.delete(req_url, verify=self._verify_ssl, headers=self._headers)
         return resp
         
+    def get_product_groups(self) -> List[LocationData]:
+        req_url = urljoin(self._base_url, "objects/product_groups")
+        resp = requests.get(req_url, verify=self._verify_ssl, headers=self._headers)
+        if resp.status_code != 200 or not resp.text:
+            return
+        parsed_json = resp.json()
+        return [LocationData(response) for response in parsed_json]
+        
+        
\ No newline at end of file


Test patch:
diff --git a/test/test_LocationData.py b/test/test_LocationData.py
new file mode 100644
index 0000000..07a5f8d
--- /dev/null
+++ b/test/test_LocationData.py
@@ -0,0 +1,21 @@
+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"
+        
\ No newline at end of file
diff --git a/test/test_grocy.py b/test/test_grocy.py
index 0636b2a..3e7cd06 100644
--- a/test/test_grocy.py
+++ b/test/test_grocy.py
@@ -3,6 +3,7 @@ from unittest import TestCase
 import responses
 from pygrocy import Grocy
 from pygrocy.grocy import Product
+from pygrocy.grocy import Group
 from pygrocy.grocy import ShoppingListProduct
 from pygrocy.grocy_api_client import CurrentStockResponse, GrocyApiClient
 
@@ -35,6 +36,7 @@ class TestGrocy(TestCase):
                 "qu_id_stock": 0,
                 "qu_factor_purchase_to_stock": 0,
                 "barcode": "string",
+                "product_group_id": 0,
                 "min_stock_amount": 0,
                 "default_best_before_days": 0,
                 "picture_file_name": "string",
@@ -73,6 +75,7 @@ class TestGrocy(TestCase):
         product.get_details(api_client)
 
         assert product.name == "string"
+        assert product.product_group_id == 0
 
     @responses.activate
     def test_product_get_details_invalid_no_data(self):
@@ -188,5 +191,36 @@ class TestGrocy(TestCase):
         responses.add(responses.DELETE, "https://example.com/api/objects/shopping_list/1", status=400)
         assert self.grocy.remove_product_in_shopping_list(1).status_code != 204
         
+    @responses.activate
+    def test_get_product_groups_valid(self):
+        resp = [
+            {
+                "id": 1,
+                "name": "string",
+                "description": "string",
+                "row_created_timestamp": "2019-04-17 10:30:00",
+            }
+        ]
+        responses.add(responses.GET, "https://example.com/api/objects/product_groups", json=resp, status=200)
+        product_groups_list = self.grocy.product_groups()
+        
+        assert isinstance(product_groups_list, list)
+        assert len(product_groups_list) == 1
+        for item in product_groups_list:
+            assert isinstance(item, Group)
+            
+    @responses.activate
+    def test_get_product_groups_invalid_no_data(self):
+        responses.add(responses.GET, "https://example.com/api/objects/product_groups", status=400)
+        assert self.grocy.product_groups() is None
+        
+    @responses.activate
+    def test_get_product_groups_invalid_missing_data(self):
+        resp = [
+            {
+            }
+        ]
+        responses.add(responses.GET, "https://example.com/api/objects/product_groups", json=resp, status=200)
+        
         
         
\ No newline at end of file
diff --git a/test/test_productDetailsResponse.py b/test/test_productDetailsResponse.py
index d77b15c..c871cfd 100644
--- a/test/test_productDetailsResponse.py
+++ b/test/test_productDetailsResponse.py
@@ -12,12 +12,13 @@ class TestProductDetailsResponse(TestCase):
         assert response.product.barcodes is None
 
     def test_parse(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": "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" } }"""
+        input_json = """{ "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