Repository: SebRut/pygrocy
Pull request #243 (2022-07-08 21:24:59)

Add open_product API support
## Description

Adds the open_product API support to the library. Tested against my local Grocy instance.

**Related issue (if applicable):** No current issue

## 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



Patch:
diff --git a/pygrocy/grocy.py b/pygrocy/grocy.py
index 91fa624..24f0e90 100644
--- a/pygrocy/grocy.py
+++ b/pygrocy/grocy.py
@@ -163,6 +163,16 @@ class Grocy(object):
     ):
         return self._api_client.consume_recipe(recipe_id)
 
+    def open_product(
+        self,
+        product_id: int,
+        amount: float = 1,
+        allow_subproduct_substitution: bool = False,
+    ):
+        return self._api_client.open_product(
+            product_id, amount, allow_subproduct_substitution
+        )
+
     def inventory_product(
         self,
         product_id: int,
diff --git a/pygrocy/grocy_api_client.py b/pygrocy/grocy_api_client.py
index 7587721..5f7376b 100644
--- a/pygrocy/grocy_api_client.py
+++ b/pygrocy/grocy_api_client.py
@@ -449,6 +449,19 @@ class GrocyApiClient(object):
 
         self._do_post_request(f"stock/products/{product_id}/consume", data)
 
+    def open_product(
+        self,
+        product_id: int,
+        amount: float = 1,
+        allow_subproduct_substitution: bool = False,
+    ):
+        data = {
+            "amount": amount,
+            "allow_subproduct_substitution": allow_subproduct_substitution,
+        }
+
+        self._do_post_request(f"stock/products/{product_id}/open", data)
+
     def consume_recipe(
         self,
         recipe_id: int,


Test patch:
diff --git a/test/cassettes/test_stock/TestStock.test_open_product_error.yaml b/test/cassettes/test_stock/TestStock.test_open_product_error.yaml
new file mode 100644
index 0000000..7e90aa8
--- /dev/null
+++ b/test/cassettes/test_stock/TestStock.test_open_product_error.yaml
@@ -0,0 +1,44 @@
+interactions:
+- request:
+    body: '{"amount": 0}'
+    headers:
+      Accept-Encoding:
+      - gzip, deflate
+      Connection:
+      - keep-alive
+      Content-Length:
+      - '62'
+      Content-Type:
+      - application/json
+      User-Agent:
+      - python-requests/2.27.1
+      accept:
+      - application/json
+    method: POST
+    uri: https://localhost/api/stock/products/13/open
+  response:
+    body:
+      string: '{"error_message":"No transaction was found by the given transaction id"}'
+    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:
+      - Sat, 9 Jul 2022 15:40:16 GMT
+      Server:
+      - nginx/1.20.2
+      Transfer-Encoding:
+      - chunked
+      X-Powered-By:
+      - PHP/8.0.13
+    status:
+      code: 400
+      message: Bad Request
+version: 1
diff --git a/test/cassettes/test_stock/TestStock.test_open_product_valid.yaml b/test/cassettes/test_stock/TestStock.test_open_product_valid.yaml
new file mode 100644
index 0000000..ce3da0b
--- /dev/null
+++ b/test/cassettes/test_stock/TestStock.test_open_product_valid.yaml
@@ -0,0 +1,44 @@
+interactions:
+- request:
+    body: '{"amount": 1}'
+    headers:
+      Accept-Encoding:
+      - gzip, deflate
+      Connection:
+      - keep-alive
+      Content-Length:
+      - '62'
+      Content-Type:
+      - application/json
+      User-Agent:
+      - python-requests/2.27.1
+      accept:
+      - application/json
+    method: POST
+    uri: https://localhost/api/stock/products/13/open
+  response:
+    body:
+      string: '[{"id":"187","product_id":"13","amount":"1","best_before_date":"2022-09-27","purchased_date":"2022-07-09","used_date":null,"spoiled":"0","stock_id":"62c99c66aa8d8","transaction_type":"product-opened","price":null,"undone":"0","undone_timestamp":null,"opened_date":"2022-07-09","location_id":"10","recipe_id":null,"correlation_id":null,"transaction_id":"62c9a03984e51","stock_row_id":null,"shopping_location_id":null,"user_id":"2","row_created_timestamp":"2022-07-09 08:35:21","note":null},{"id":"188","product_id":"13","amount":"-1","best_before_date":"2022-07-23","purchased_date":"2022-07-09","used_date":null,"spoiled":"0","stock_id":"62c99c66aa8d8","transaction_type":"transfer_from","price":null,"undone":"0","undone_timestamp":null,"opened_date":"2022-07-09","location_id":"10","recipe_id":null,"correlation_id":"62c9a039a118f","transaction_id":"62c9a03984e51","stock_row_id":null,"shopping_location_id":null,"user_id":"2","row_created_timestamp":"2022-07-09 08:35:21","note":null},{"id":"189","product_id":"13","amount":"1","best_before_date":"2022-07-23","purchased_date":"2022-07-09","used_date":null,"spoiled":"0","stock_id":"62c99c66aa8d8","transaction_type":"transfer_to","price":null,"undone":"0","undone_timestamp":null,"opened_date":"2022-07-09","location_id":"2","recipe_id":null,"correlation_id":"62c9a039a118f","transaction_id":"62c9a03984e51","stock_row_id":null,"shopping_location_id":null,"user_id":"2","row_created_timestamp":"2022-07-09 08:35:21","note":null}]'
+    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:
+      - Sat, 9 Jul 2022 15:40:16 GMT
+      Server:
+      - nginx/1.20.2
+      Transfer-Encoding:
+      - chunked
+      X-Powered-By:
+      - PHP/8.0.13
+    status:
+      code: 200
+      message: OK
+version: 1
diff --git a/test/test_stock.py b/test/test_stock.py
index 4fe1ced..e8a23e1 100644
--- a/test/test_stock.py
+++ b/test/test_stock.py
@@ -1,6 +1,7 @@
 import pytest
 
 from pygrocy.data_models.product import Product
+from pygrocy.errors import GrocyError
 
 
 class TestStock:
@@ -54,3 +55,15 @@ class TestStock:
         assert len(overdue_products) == 4
         for prod in overdue_products:
             assert isinstance(prod, Product)
+
+    @pytest.mark.vcr
+    def test_open_product_valid(self, grocy):
+        grocy.open_product(13, 1)
+
+    @pytest.mark.vcr
+    def test_open_product_error(self, grocy):
+        with pytest.raises(GrocyError) as exc_info:
+            grocy.open_product(13, 0)
+
+        error = exc_info.value
+        assert error.status_code == 400