From b195e71830256042a0a30ac8b7dc4a0dad178529 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Sep 2021 08:20:10 +0200 Subject: [PATCH 01/13] Update responses requirement from ~=0.13.4 to ~=0.14.0 (#191) Updates the requirements on [responses](https://github.com/getsentry/responses) to permit the latest version. - [Release notes](https://github.com/getsentry/responses/releases) - [Changelog](https://github.com/getsentry/responses/blob/master/CHANGES) - [Commits](https://github.com/getsentry/responses/compare/0.13.4...0.14.0) --- updated-dependencies: - dependency-name: responses dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 1f4c2a6..4a0501b 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,4 +1,4 @@ -responses~=0.13.4 +responses~=0.14.0 pre-commit isort pytest From 8b3a990ae21b09e4d02cbca95238b87f1c16288e Mon Sep 17 00:00:00 2001 From: Sebastian Rutofski Date: Sat, 18 Sep 2021 19:15:27 +0200 Subject: [PATCH 02/13] support path for url (#193) --- pygrocy/grocy.py | 5 ++++- pygrocy/grocy_api_client.py | 6 +++++- test/test_grocy_api_client.py | 23 +++++++++++++++++++++++ 3 files changed, 32 insertions(+), 2 deletions(-) create mode 100644 test/test_grocy_api_client.py diff --git a/pygrocy/grocy.py b/pygrocy/grocy.py index ed5a5f9..cd1a559 100644 --- a/pygrocy/grocy.py +++ b/pygrocy/grocy.py @@ -36,10 +36,13 @@ def __init__( base_url, api_key, port: int = DEFAULT_PORT_NUMBER, + path: str = None, verify_ssl=True, debug=False, ): - self._api_client = GrocyApiClient(base_url, api_key, port, verify_ssl, debug) + self._api_client = GrocyApiClient( + base_url, api_key, port, path, verify_ssl, debug + ) if debug: _LOGGER.setLevel(logging.DEBUG) diff --git a/pygrocy/grocy_api_client.py b/pygrocy/grocy_api_client.py index ddf4ddb..8999160 100644 --- a/pygrocy/grocy_api_client.py +++ b/pygrocy/grocy_api_client.py @@ -233,13 +233,17 @@ def __init__( base_url, api_key, port: int = DEFAULT_PORT_NUMBER, + path: str = None, verify_ssl=True, debug=False, ): if debug: _enable_debug_mode() - self._base_url = "{}:{}/api/".format(base_url, port) + if path: + self._base_url = "{}:{}/{}/api/".format(base_url, port, path) + else: + self._base_url = "{}:{}/api/".format(base_url, port) _LOGGER.debug(f"generated base url: {self._base_url}") self._api_key = api_key diff --git a/test/test_grocy_api_client.py b/test/test_grocy_api_client.py new file mode 100644 index 0000000..4b7f311 --- /dev/null +++ b/test/test_grocy_api_client.py @@ -0,0 +1,23 @@ +from pygrocy.grocy_api_client import GrocyApiClient + + +class TestGrocyApiClient: + def test_url_only(self): + client = GrocyApiClient(api_key="", base_url="http://grocy.de") + assert client._base_url == "http://grocy.de:9192/api/" + + def test_url_and_port(self): + client = GrocyApiClient(api_key="", base_url="http://grocy.de", port=1234) + assert client._base_url == "http://grocy.de:1234/api/" + + def test_url_and_port_and_path(self): + client = GrocyApiClient( + api_key="", base_url="http://grocy.de", port=1234, path="my/custom/path" + ) + assert client._base_url == "http://grocy.de:1234/my/custom/path/api/" + + def test_url_and_path(self): + client = GrocyApiClient( + api_key="", base_url="http://grocy.de", path="my/custom/path" + ) + assert client._base_url == "http://grocy.de:9192/my/custom/path/api/" From 5c1b4eae3067759f7bc9a0efddd3ecd30eaa0008 Mon Sep 17 00:00:00 2001 From: Sebastian Ecker Date: Sun, 19 Sep 2021 14:59:47 +0200 Subject: [PATCH 03/13] ProductDetailsResponse Type Date instead of Datetime (#194) When using datetime for last_used, the following validation error is returned. pydantic.error_wrappers.ValidationError: 1 validation error for ProductDetailsResponse last_used invalid datetime format (type=value_error.datetime) The used_date Column is set to Type DATE: used_date DATE, Co-authored-by: Sebastian Rutofski --- pygrocy/grocy_api_client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pygrocy/grocy_api_client.py b/pygrocy/grocy_api_client.py index 8999160..1348248 100644 --- a/pygrocy/grocy_api_client.py +++ b/pygrocy/grocy_api_client.py @@ -145,7 +145,7 @@ class ProductBarcodeData(BaseModel): class ProductDetailsResponse(BaseModel): last_purchased: Optional[date] = None - last_used: Optional[datetime] = None + last_used: Optional[date] = None stock_amount: int stock_amount_opened: int next_best_before_date: Optional[date] = None From 8024659df34fb259371fed89afe89312e07e9c86 Mon Sep 17 00:00:00 2001 From: George Gebbett Date: Tue, 23 Nov 2021 13:18:23 +0000 Subject: [PATCH 04/13] Product by barcode (#208) Adding barcode support done by @georgegebbett --- pygrocy/data_models/product.py | 6 + pygrocy/grocy.py | 38 +++++++ pygrocy/grocy_api_client.py | 62 +++++++++++ ...ocy.test_add_product_by_barcode_error.yaml | 44 ++++++++ ...ocy.test_add_product_by_barcode_valid.yaml | 105 ++++++++++++++++++ ...test_consume_product_by_barcode_valid.yaml | 105 ++++++++++++++++++ .../TestGrocy.test_consume_product_error.yaml | 44 ++++++++ ...stProduct.test_get_product_by_barcode.yaml | 55 +++++++++ ...t_by_barcode_get_details_non_existant.yaml | 40 +++++++ test/test_grocy.py | 38 +++++++ test/test_product.py | 23 ++++ 11 files changed, 560 insertions(+) create mode 100644 test/cassettes/test_grocy/TestGrocy.test_add_product_by_barcode_error.yaml create mode 100644 test/cassettes/test_grocy/TestGrocy.test_add_product_by_barcode_valid.yaml create mode 100644 test/cassettes/test_grocy/TestGrocy.test_consume_product_by_barcode_valid.yaml create mode 100644 test/cassettes/test_grocy/TestGrocy.test_consume_product_error.yaml create mode 100644 test/cassettes/test_product/TestProduct.test_get_product_by_barcode.yaml create mode 100644 test/cassettes/test_product/TestProduct.test_product_by_barcode_get_details_non_existant.yaml diff --git a/pygrocy/data_models/product.py b/pygrocy/data_models/product.py index 8f6ede0..9242fd6 100644 --- a/pygrocy/data_models/product.py +++ b/pygrocy/data_models/product.py @@ -11,6 +11,7 @@ ProductData, ProductDetailsResponse, ShoppingListItem, + StockLogResponse, ) @@ -34,6 +35,8 @@ def __init__(self, data): self._init_from_ProductDetailsResponse(data) elif isinstance(data, ProductData): self._init_from_ProductData(data) + elif isinstance(data, StockLogResponse): + self._init_from_StockLogResponse(data) def _init_empty(self): self._name = None @@ -74,6 +77,9 @@ def _init_from_ProductData(self, product: ProductData): self._product_group_id = product.product_group_id self._name = product.name + def _init_from_StockLogResponse(self, response: StockLogResponse): + self._id = response.product_id + 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 cd1a559..880a76a 100644 --- a/pygrocy/grocy.py +++ b/pygrocy/grocy.py @@ -97,6 +97,11 @@ def product(self, product_id: int) -> Product: if resp: return Product(resp) + def product_by_barcode(self, barcode: str) -> Product: + resp = self._api_client.get_product_by_barcode(barcode) + 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 @@ -148,6 +153,39 @@ def consume_product( product_id, amount, spoiled, transaction_type ) + def add_product_by_barcode( + self, + barcode: str, + amount: float, + price: float, + best_before_date: datetime = None, + get_details: bool = True, + ) -> Product: + product = Product( + self._api_client.add_product_by_barcode( + barcode, amount, price, best_before_date + ) + ) + + if get_details: + product.get_details(self._api_client) + return product + + def consume_product_by_barcode( + self, + barcode: str, + amount: float = 1, + spoiled: bool = False, + get_details: bool = True, + ) -> Product: + product = Product( + self._api_client.consume_product_by_barcode(barcode, amount, spoiled) + ) + + if get_details: + product.get_details(self._api_client) + return product + def shopping_list(self, get_details: bool = False) -> List[ShoppingListProduct]: raw_shoppinglist = self._api_client.get_shopping_list() shopping_list = [ShoppingListProduct(resp) for resp in raw_shoppinglist] diff --git a/pygrocy/grocy_api_client.py b/pygrocy/grocy_api_client.py index 1348248..326220c 100644 --- a/pygrocy/grocy_api_client.py +++ b/pygrocy/grocy_api_client.py @@ -223,6 +223,19 @@ class MealPlanSectionResponse(BaseModel): row_created_timestamp: datetime +class StockLogResponse(BaseModel): + id: int + product_id: int + amount: int + best_before_date: date + purchased_date: date + used_date: Optional[date] = None + spoiled: bool = False + stock_id: str + transaction_id: str + transaction_type: TransactionType + + def _enable_debug_mode(): _LOGGER.setLevel(logging.DEBUG) @@ -335,6 +348,12 @@ def get_product(self, product_id) -> ProductDetailsResponse: if parsed_json: return ProductDetailsResponse(**parsed_json) + def get_product_by_barcode(self, barcode) -> ProductDetailsResponse: + url = f"stock/products/by-barcode/{barcode}" + parsed_json = self._do_get_request(url) + if 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] @@ -394,6 +413,49 @@ def consume_product( self._do_post_request(f"stock/products/{product_id}/consume", data) + def add_product_by_barcode( + self, + barcode: str, + amount: float, + price: float, + best_before_date: datetime = None, + ) -> StockLogResponse: + data = { + "amount": amount, + "transaction_type": TransactionType.PURCHASE.value, + "price": price, + } + + if best_before_date is not None: + data["best_before_date"] = localize_datetime(best_before_date).strftime( + "%Y-%m-%d" + ) + + parsed_json = self._do_post_request( + f"stock/products/by-barcode/{barcode}/add", data + ) + + if parsed_json: + stockLog = [StockLogResponse(**response) for response in parsed_json] + return stockLog[0] + + def consume_product_by_barcode( + self, barcode: str, amount: float = 1, spoiled: bool = False + ): + data = { + "amount": amount, + "spoiled": spoiled, + "transaction_type": TransactionType.CONSUME.value, + } + + parsed_json = self._do_post_request( + f"stock/products/by-barcode/{barcode}/consume", data + ) + + if parsed_json: + stockLog = [StockLogResponse(**response) for response in parsed_json] + return stockLog[0] + def get_shopping_list(self) -> List[ShoppingListItem]: parsed_json = self._do_get_request("objects/shopping_list") return [ShoppingListItem(**response) for response in parsed_json] diff --git a/test/cassettes/test_grocy/TestGrocy.test_add_product_by_barcode_error.yaml b/test/cassettes/test_grocy/TestGrocy.test_add_product_by_barcode_error.yaml new file mode 100644 index 0000000..b69559e --- /dev/null +++ b/test/cassettes/test_grocy/TestGrocy.test_add_product_by_barcode_error.yaml @@ -0,0 +1,44 @@ +interactions: +- request: + body: '{"amount": 1, "transaction_type": "purchase", "price": 5}' + headers: + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '57' + Content-Type: + - application/json + User-Agent: + - python-requests/2.26.0 + accept: + - application/json + method: POST + uri: https://localhost/api/stock/products/by-barcode/555/add + response: + body: + string: '{"error_message":"No product with barcode 555 found"}' + 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: + - Tue, 23 Nov 2021 09:41:06 GMT + Server: + - nginx/1.20.1 + Transfer-Encoding: + - chunked + X-Powered-By: + - PHP/8.0.11 + status: + code: 400 + message: Bad Request +version: 1 diff --git a/test/cassettes/test_grocy/TestGrocy.test_add_product_by_barcode_valid.yaml b/test/cassettes/test_grocy/TestGrocy.test_add_product_by_barcode_valid.yaml new file mode 100644 index 0000000..ce06372 --- /dev/null +++ b/test/cassettes/test_grocy/TestGrocy.test_add_product_by_barcode_valid.yaml @@ -0,0 +1,105 @@ +interactions: +- request: + body: '{"amount": 1, "transaction_type": "purchase", "price": 5}' + headers: + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '57' + Content-Type: + - application/json + User-Agent: + - python-requests/2.26.0 + accept: + - application/json + method: POST + uri: https://localhost/api/stock/products/by-barcode/42141099/add + response: + body: + string: !!binary | + H4sIAAAAAAAEA21Q207EIBD9FcPzblJwrdpfMYbQYdYltgwZIMYY/12mxN2a+Macw8y5vHyp4NWk + 9Emrg0pMvkKxG3RqgFupxiJ8G2bMxc54JkbrXcEGm8Hoo9ZHcy/bleHiMvp/2XojYl2Wg8qJwoIi + PrTlXAjeu/Con2EecESHMDaqsIvZQQkUbflMovsrJaocQKCH9q7RU5RBLvbBlrA2325Nauq6lDBe + PXaI6cMCY8vk9/938e60mYyZ9FM7vBC4zcy1JkYICTf3/SAQMy63Xx3dB9l2JamHs9PuEeRyL0Hc + CN2X8oVSCvHN/pHtXOuUe2dafb/+AANkFP3MAQAA + 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, 23 Nov 2021 12:22:18 GMT + Server: + - nginx/1.20.1 + Transfer-Encoding: + - chunked + X-Powered-By: + - PHP/8.0.11 + 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/stock/products/4 + response: + body: + string: !!binary | + H4sIAAAAAAAEA81Vy27bMBD8lYDnOLAUG6l97Q/0XhQLmlpJRCiR4cOuE+Tfu0tKttMGhnvLxbD2 + Re7M7PJNOG+bpKLYvgndiK1YiXsxygHp73evgwv03WBQXruo7Si2YzLmfk6DztvkIGdWFClV1HvO + 5Q9jleSc4ubCobfO6bGDD65S8SVRHLjkVS8Dl3ikhGIM0arns6WlU6w/hUK0MEdUD0vKGvRYLCAH + m0ZqTlRsb7CVyUTYYeCf1nqERh4D+a+5QbYRPViH1P4tka1HfKU2b4uOvTycg51WMdG9Wm0QChEF + HxzljkxRkvOAuusj9HJszDn1wsUnZyhGG0H1qJ4nQNpkqLIZcIxA/YNHpR3OCDgqTo5JE5m3criS + xnqd46qaiVFpSEZGhL+xBttCSLu5xly5SXT1o5ul8ZI0XUnZMaQBL1jKd+51g0CyyaSC3aPfazxM + YHp7AOWRjm4g6oGYlIMjX72sq0VVLer6rnrc1sttXV8w7rymvkpBI3dopmrSGKqXLeCI4zRqVgur + gbSaTAMM4I7o8PZ1ov/9rP6d9MrSdIjtz2l8GJtL+PI8TWFUeFVXq2q52VBUlvY8TrNOC9rXxsRI + 0i61owjKEv1/iFA/U+b7LxrRXG2aOR7/E4zcSPamgP84CpDzpcWa+ym2vTSJSa6/VQ/rk7VE5gnK + xTK+PNQT9SC7zmPHpFLuepNlcHnIlPoxrih8HuqXJMeo4zFzeFoOp73G/Ux77YekbfLpVrsZyoo7 + 5nLgTPKS5cRVA1mLgYdrIF0wRSSYj5fLnX2Rm13qSTBjct/N+hKrh6cnMllDGj+pbrbmzGtSnZm5 + FjPi7wi8HBoiP+uv2iyWNMkM8PxKnKC6fJxo9x3vVHI7K31DwZ88UjfTWfNzpQNNOS1u9HSPpSDW + JK0e2SGEHk0LRrfzc7F4rEnwzmoDnpcgrQ5Fi1Nsn9a5zlnO593GmqeXjbaxNg0po5Um4PsfAAPk + yIEHAAA= + 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, 23 Nov 2021 12:22:18 GMT + Server: + - nginx/1.20.1 + Transfer-Encoding: + - chunked + X-Powered-By: + - PHP/8.0.11 + status: + code: 200 + message: OK +version: 1 diff --git a/test/cassettes/test_grocy/TestGrocy.test_consume_product_by_barcode_valid.yaml b/test/cassettes/test_grocy/TestGrocy.test_consume_product_by_barcode_valid.yaml new file mode 100644 index 0000000..2b0f9ec --- /dev/null +++ b/test/cassettes/test_grocy/TestGrocy.test_consume_product_by_barcode_valid.yaml @@ -0,0 +1,105 @@ +interactions: +- request: + body: '{"amount": 1, "spoiled": false, "transaction_type": "consume"}' + headers: + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '62' + Content-Type: + - application/json + User-Agent: + - python-requests/2.26.0 + accept: + - application/json + method: POST + uri: https://localhost/api/stock/products/by-barcode/42141099/consume + response: + body: + string: !!binary | + H4sIAAAAAAAEA3VQy07EMAz8FZTzFtWhLLS/glCUJl42oo2jPIQQ4t9xErG7HDh6xh7PzMuXcFYs + AiYpDiJEssVk1aCJAb1T8Zn5AXhaMWW14okiKqszMi5HmIcRBpjreYnmrBPaKyuZgkE+MFv+I1Ig + t2F1MfJaymTeu4MjzMYeR1gnsNVejtonbbIjr/JnqP8N+VR2rM+jMxWB+7l6Kd6Sr3PV7IPKbucE + eg9i8WXbDoIC+ovbDkX6UCYip7O3+xz0N8odyEXKBZ5ZeCOjm51LYxGNC9j8d0FDMeJ23erobZR2 + 27Kak36cYH5i5V5DdVPpfpTOFILzb+rP285xu7G3BuL79QeC7pDY1wEAAA== + 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, 23 Nov 2021 12:22:18 GMT + Server: + - nginx/1.20.1 + Transfer-Encoding: + - chunked + X-Powered-By: + - PHP/8.0.11 + 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/stock/products/4 + response: + body: + string: !!binary | + H4sIAAAAAAAEA81V227bMAz9lULPTRG7ydbkdT+w92EgFJm2hcqWqkuytOi/j5StJN2KIHvbS9Hw + ZvKcQ+pNOG+bpKLYvgndiK1YiXsxygHp329eBxfod4NBee2itqPYjsmY+5IGnbfJQc6sKFKqqPec + yz+MVZJzJjcXDr11To8dfHBNFV8SxYFLXvUycIlHSpiMIVr1fLa09BXrT6EQLZSI6mFJWYMeJwvI + waaRhhMV2xtsZTIRdhj4T2s9QiOPgfzX3CDbiB6sQxr/lsjWI77SmLdFx14ezsFOq5ior1YbhImI + CR8c5Y5MUZLzgLrrI/RybMw59cLFX85QjDaC6lE9z4C0yVBlM+AYgeYHj0o7LAg4Kk6OWROZt+nj + ShrrdY6raiZGpSEZGRH+xBpsCyHtSo1SuUnU+tEVabwkTS0pO4Y04AVLuedeNwgkm0wq2D36vcbD + DKa3B1Ae6dMNRD0Qk3Jw5KuXdbWoqkVd31WP23q5resLxp3XNNdU0MgdmrmaNIbqZQs44jiNmtXC + aiCtJtMAA7gjOrx9nel/P6t/J72ytB1i+2NeH8bmEr68T3MYFV7V1apabjYUlaVd1qnodEL72poY + SdqlcRRBOUX/GyI0z5z5/pNWNFebd47X/wQjD5K9KeBfjgnI0rRYPzFevKSwlyYxyfXXzcOaj8Bl + aF6hXC0DnBOmIiC7zmPHrFLy+inr4JPUj3GTxMtWvyQ5Rh2PmcTTdTgdNh5oPmzfJZ2TT8/azVhW + TCGXA2eSl6wnrhrIOhl4uwYSBnNEivnYXJ7sP+nsUlBiTf3LfVcEJlYPT7xG1pDIT7Ir1px5TauF + mWsxI/6KwNehIfKzAKvNYkmrzACXZ+IE1eXrRMfveKeS21npGwr+5JW6mc6apaoDrTldbvTUx1IQ + a5Juj+wQQo+mBaPb8l4sHmuStrPagOcrSLdD0eUU2y/LXOcs5/NxY83T00bnWJuGlNFKE/D9N9N4 + xRmCBwAA + 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, 23 Nov 2021 12:22:18 GMT + Server: + - nginx/1.20.1 + Transfer-Encoding: + - chunked + X-Powered-By: + - PHP/8.0.11 + status: + code: 200 + message: OK +version: 1 diff --git a/test/cassettes/test_grocy/TestGrocy.test_consume_product_error.yaml b/test/cassettes/test_grocy/TestGrocy.test_consume_product_error.yaml new file mode 100644 index 0000000..6483b29 --- /dev/null +++ b/test/cassettes/test_grocy/TestGrocy.test_consume_product_error.yaml @@ -0,0 +1,44 @@ +interactions: +- request: + body: '{"amount": 1, "spoiled": 5, "transaction_type": "consume"}' + headers: + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '58' + Content-Type: + - application/json + User-Agent: + - python-requests/2.26.0 + accept: + - application/json + method: POST + uri: https://localhost/api/stock/products/by-barcode/555/consume + response: + body: + string: '{"error_message":"No product with barcode 555 found"}' + 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: + - Tue, 23 Nov 2021 09:41:06 GMT + Server: + - nginx/1.20.1 + Transfer-Encoding: + - chunked + X-Powered-By: + - PHP/8.0.11 + status: + code: 400 + message: Bad Request +version: 1 diff --git a/test/cassettes/test_product/TestProduct.test_get_product_by_barcode.yaml b/test/cassettes/test_product/TestProduct.test_get_product_by_barcode.yaml new file mode 100644 index 0000000..a32d4b1 --- /dev/null +++ b/test/cassettes/test_product/TestProduct.test_get_product_by_barcode.yaml @@ -0,0 +1,55 @@ +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/stock/products/by-barcode/42141099 + response: + body: + string: !!binary | + H4sIAAAAAAAEA81Vy27bMBD8lUDnOLDkGEV87Q/0XhQLmqIkIpRIU6RdJ8i/d5aUbKUwAvfWi2Ht + i9yZ2eV74bytowzF7r3QdbErnovHYhC9wt/vXo9uxHetRum1C9oOxW6IxjzOadR6Gx2lzBKRQgZ9 + 5Fz+MFYKzsluLjx21jk9tPTJlSseIuLIRS87MXKJDRKycQxWvl4tDU6x/hJKwdIcUT6tkdXrIVtI + 9DYOaK4o2V6rRkQTaK9G/mmsV1SL8wj/V24STVCerFNo/57Ixiv1hjbviw6dOF2DnZYh4l6NNooy + ERkfNYg9TEHAeVK67QJ1YqjNNXXh4pMTFIMNJDslXydAmmhQ2fRqCIT+ySupnZoRcCgOx6SJxFs+ + XApjvU5xZcXEyNhHI4Kiv7Em29AY93ONuXIdcfWzm6VxiBpXknYYY68WLKU7d7pWBNkkUskelT9q + dZrA9PZE0iscXVPQPZgUvYOvWlflqixXVfVQbnbVeldVC8ad1+grFzRir8xUTRiDeslCDhzHQbNa + WA3QajQ1MYB70OHt20T/x1X9e+GlxXQUu5/T+DA2S/jSPE1hKPxclc/l+uUFUUna8zjNOs1ofzUm + RkC7aEcCyhz9b4ignynz4xdGNFWbZo7Hf4axZPSSN44KjuliPIhXurYMUzIdhYmJ229PW87M1txV + GhyukWFdlCDRtl61zCW820T+jczPYVnX8ygfohiCDufE3GUlXLYZ0zFtsx8CO+TmLrsbwJJ543Lk + TPSCRcRVR1izgUeqhxoYLcjk8+VSZ//JzZYqKjZP1YapFMd2VhZsbLEG4r7IbTKm3NsSLZYzdzsk + C2lQvwPxTqhBfpZdtVpvV9Xy3bhgtXyTsPLODzK6vRW+xhVvvE1385lO0yOGG/ta+aRQ0CawcUSr + aOyUacjoZn4lqnINaTurDXnefdgYEvuy2MGMMlc1X0eENwneM+xgbWoooxFmVB9/AA9z8G53BwAA + 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: + - Mon, 22 Nov 2021 13:29:27 GMT + Server: + - nginx/1.20.1 + Transfer-Encoding: + - chunked + X-Powered-By: + - PHP/8.0.11 + status: + code: 200 + message: OK +version: 1 diff --git a/test/cassettes/test_product/TestProduct.test_product_by_barcode_get_details_non_existant.yaml b/test/cassettes/test_product/TestProduct.test_product_by_barcode_get_details_non_existant.yaml new file mode 100644 index 0000000..a8fc62d --- /dev/null +++ b/test/cassettes/test_product/TestProduct.test_product_by_barcode_get_details_non_existant.yaml @@ -0,0 +1,40 @@ +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/stock/products/by-barcode/200 + response: + body: + string: '{"error_message":"No product with barcode 200 found"}' + 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: + - Mon, 22 Nov 2021 13:29:27 GMT + Server: + - nginx/1.20.1 + Transfer-Encoding: + - chunked + X-Powered-By: + - PHP/8.0.11 + status: + code: 400 + message: Bad Request +version: 1 diff --git a/test/test_grocy.py b/test/test_grocy.py index d1bec24..bb63f0a 100644 --- a/test/test_grocy.py +++ b/test/test_grocy.py @@ -1,5 +1,8 @@ import json from datetime import datetime + +import pytest + from test.test_const import CONST_BASE_URL, CONST_PORT, CONST_SSL from unittest import TestCase from unittest.mock import mock_open, patch @@ -9,6 +12,7 @@ from pygrocy import Grocy from pygrocy.errors import GrocyError from pygrocy.grocy_api_client import GrocyApiClient +from pygrocy.data_models.product import Product class TestGrocy(TestCase): @@ -216,6 +220,40 @@ def test_consume_product_error(self): ) self.assertRaises(GrocyError, self.grocy.consume_product, 1, 1.3) + @pytest.mark.vcr + def test_add_product_by_barcode_valid(self): + product = self.grocy.add_product_by_barcode( + "42141099", 1, 5, self.date_test, True + ) + + assert isinstance(product, Product) + assert product.id == 4 + assert product.name == "Crisps" + + @pytest.mark.vcr + def test_add_product_by_barcode_error(self): + with pytest.raises(GrocyError) as exc_info: + self.grocy.add_product_by_barcode("555", 1, 5, self.date_test, True) + + error = exc_info.value + assert error.status_code == 400 + + @pytest.mark.vcr + def test_consume_product_by_barcode_valid(self): + product = self.grocy.consume_product_by_barcode("42141099", 1, False, True) + + assert isinstance(product, Product) + assert product.id == 4 + assert product.name == "Crisps" + + @pytest.mark.vcr + def test_consume_product_error(self): + with pytest.raises(GrocyError) as exc_info: + self.grocy.consume_product_by_barcode("555", 1, False, True) + + error = exc_info.value + assert error.status_code == 400 + @responses.activate def test_execute_chore_valid(self): responses.add(responses.POST, f"{self.base_url}/chores/1/execute", status=200) diff --git a/test/test_product.py b/test/test_product.py index 44c52fc..1fcef03 100644 --- a/test/test_product.py +++ b/test/test_product.py @@ -56,3 +56,26 @@ def test_add_product_pic_valid(self, grocy, mocker): mocker.patch("builtins.open", mocker.mock_open()) assert grocy.add_product_pic(20, "/somepath/pic.jpg") is None + + @pytest.mark.vcr + def test_get_product_by_barcode(self, grocy): + product = grocy.product_by_barcode("42141099") + + assert isinstance(product, Product) + assert product.name == "Crisps" + assert product.available_amount == 5 + assert product.product_group_id == 1 + + assert len(product.product_barcodes) == 1 + barcode = product.product_barcodes[0] + assert isinstance(barcode, ProductBarcode) + assert barcode.barcode == "42141099" + + @pytest.mark.vcr + def test_product_by_barcode_get_details_non_existant(self, grocy): + with pytest.raises(GrocyError) as exc_info: + grocy.product_by_barcode(200) + + error = exc_info.value + assert error.status_code == 400 + assert error.message == "No product with barcode 200 found" From ffcb306a808ea4b202adc54985d33ad5d9eaa5c0 Mon Sep 17 00:00:00 2001 From: George Gebbett Date: Mon, 27 Dec 2021 06:52:46 +0000 Subject: [PATCH 05/13] Feature/edit stock (#209) * I am not a fan of the black formatter * Revert "I am not a fan of the black formatter" This reverts commit 1da0264970a797f77f86a540ea70478902a075e6. * I am not a fan of the black formatter * Inventory methods added * Sorted out my egregious slip up in variable naming --- pygrocy/data_models/product.py | 1 + pygrocy/grocy.py | 44 +++++ pygrocy/grocy_api_client.py | 65 +++++++ ...est_consume_product_by_barcode_error.yaml} | 6 +- ...st_inventory_product_by_barcode_error.yaml | 99 +++++++++++ ...st_inventory_product_by_barcode_valid.yaml | 160 ++++++++++++++++++ ...estGrocy.test_inventory_product_error.yaml | 99 +++++++++++ ...estGrocy.test_inventory_product_valid.yaml | 160 ++++++++++++++++++ test/test_grocy.py | 54 +++++- 9 files changed, 680 insertions(+), 8 deletions(-) rename test/cassettes/test_grocy/{TestGrocy.test_consume_product_error.yaml => TestGrocy.test_consume_product_by_barcode_error.yaml} (87%) create mode 100644 test/cassettes/test_grocy/TestGrocy.test_inventory_product_by_barcode_error.yaml create mode 100644 test/cassettes/test_grocy/TestGrocy.test_inventory_product_by_barcode_valid.yaml create mode 100644 test/cassettes/test_grocy/TestGrocy.test_inventory_product_error.yaml create mode 100644 test/cassettes/test_grocy/TestGrocy.test_inventory_product_valid.yaml diff --git a/pygrocy/data_models/product.py b/pygrocy/data_models/product.py index 9242fd6..65a61e1 100644 --- a/pygrocy/data_models/product.py +++ b/pygrocy/data_models/product.py @@ -86,6 +86,7 @@ def get_details(self, api_client: GrocyApiClient): self._name = details.product.name self._barcodes = [ProductBarcode(barcode) for barcode in details.barcodes] self._product_group_id = details.product.product_group_id + self._available_amount = details.stock_amount @property def name(self) -> str: diff --git a/pygrocy/grocy.py b/pygrocy/grocy.py index 880a76a..2ae2000 100644 --- a/pygrocy/grocy.py +++ b/pygrocy/grocy.py @@ -153,6 +153,31 @@ def consume_product( product_id, amount, spoiled, transaction_type ) + def inventory_product( + self, + product_id: int, + new_amount: int, + best_before_date: datetime = None, + shopping_location_id: int = None, + location_id: int = None, + price: int = None, + get_details: bool = True, + ) -> Product: + product = Product( + self._api_client.inventory_product( + product_id, + new_amount, + best_before_date, + shopping_location_id, + location_id, + price, + ) + ) + + if get_details: + product.get_details(self._api_client) + return product + def add_product_by_barcode( self, barcode: str, @@ -186,6 +211,25 @@ def consume_product_by_barcode( product.get_details(self._api_client) return product + def inventory_product_by_barcode( + self, + barcode: str, + new_amount: int, + best_before_date: datetime = None, + location_id: int = None, + price: int = None, + get_details: bool = True, + ) -> Product: + product = Product( + self._api_client.inventory_product_by_barcode( + barcode, new_amount, best_before_date, location_id, price + ) + ) + + if get_details: + product.get_details(self._api_client) + return product + def shopping_list(self, get_details: bool = False) -> List[ShoppingListProduct]: raw_shoppinglist = self._api_client.get_shopping_list() shopping_list = [ShoppingListProduct(resp) for resp in raw_shoppinglist] diff --git a/pygrocy/grocy_api_client.py b/pygrocy/grocy_api_client.py index 326220c..f0c864c 100644 --- a/pygrocy/grocy_api_client.py +++ b/pygrocy/grocy_api_client.py @@ -413,6 +413,40 @@ def consume_product( self._do_post_request(f"stock/products/{product_id}/consume", data) + def inventory_product( + self, + product_id: int, + new_amount: float, + best_before_date: datetime = None, + shopping_location_id: int = None, + location_id: int = None, + price: int = None, + ): + data = { + "new_amount": new_amount, + } + + if best_before_date is not None: + data["best_before_date"] = localize_datetime(best_before_date).strftime( + "%Y-%m-%d" + ) + if shopping_location_id is not None: + data["shopping_location_id"] = shopping_location_id + + if location_id is not None: + data["location_id"] = location_id + + if price is not None: + data["price"] = price + + parsed_json = self._do_post_request( + f"stock/products/{product_id}/inventory", data + ) + + if parsed_json: + stockLog = [StockLogResponse(**response) for response in parsed_json] + return stockLog[0] + def add_product_by_barcode( self, barcode: str, @@ -456,6 +490,37 @@ def consume_product_by_barcode( stockLog = [StockLogResponse(**response) for response in parsed_json] return stockLog[0] + def inventory_product_by_barcode( + self, + barcode: str, + new_amount: float, + best_before_date: datetime = None, + location_id: int = None, + price: int = None, + ): + data = { + "new_amount": new_amount, + } + + if best_before_date is not None: + data["best_before_date"] = localize_datetime(best_before_date).strftime( + "%Y-%m-%d" + ) + + if location_id is not None: + data["location_id"] = location_id + + if price is not None: + data["price"] = price + + parsed_json = self._do_post_request( + f"stock/products/by-barcode/{barcode}/inventory", data + ) + + if parsed_json: + stockLog = [StockLogResponse(**response) for response in parsed_json] + return stockLog[0] + def get_shopping_list(self) -> List[ShoppingListItem]: parsed_json = self._do_get_request("objects/shopping_list") return [ShoppingListItem(**response) for response in parsed_json] diff --git a/test/cassettes/test_grocy/TestGrocy.test_consume_product_error.yaml b/test/cassettes/test_grocy/TestGrocy.test_consume_product_by_barcode_error.yaml similarity index 87% rename from test/cassettes/test_grocy/TestGrocy.test_consume_product_error.yaml rename to test/cassettes/test_grocy/TestGrocy.test_consume_product_by_barcode_error.yaml index 6483b29..9ac898b 100644 --- a/test/cassettes/test_grocy/TestGrocy.test_consume_product_error.yaml +++ b/test/cassettes/test_grocy/TestGrocy.test_consume_product_by_barcode_error.yaml @@ -1,13 +1,13 @@ interactions: - request: - body: '{"amount": 1, "spoiled": 5, "transaction_type": "consume"}' + body: '{"amount": 1, "spoiled": false, "transaction_type": "consume"}' headers: Accept-Encoding: - gzip, deflate Connection: - keep-alive Content-Length: - - '58' + - '62' Content-Type: - application/json User-Agent: @@ -31,7 +31,7 @@ interactions: Content-Type: - application/json Date: - - Tue, 23 Nov 2021 09:41:06 GMT + - Tue, 23 Nov 2021 14:07:20 GMT Server: - nginx/1.20.1 Transfer-Encoding: diff --git a/test/cassettes/test_grocy/TestGrocy.test_inventory_product_by_barcode_error.yaml b/test/cassettes/test_grocy/TestGrocy.test_inventory_product_by_barcode_error.yaml new file mode 100644 index 0000000..93cf8f0 --- /dev/null +++ b/test/cassettes/test_grocy/TestGrocy.test_inventory_product_by_barcode_error.yaml @@ -0,0 +1,99 @@ +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/stock/products/by-barcode/42141099 + response: + body: + string: !!binary | + H4sIAAAAAAAEA81Vy27bMBD8lYDnOLBku0187Q/0XhQLmlpJRCiR4cOuE+Tfu0tKttMagXvrxbD2 + Re7M7PJNOG+bpKLYvgndiK1Yi3sxygHp7zevgwv03WBQXruo7Si2YzLmfk6DztvkIGdWFClV1HvO + 5Q9jleSc4ubCobfO6bGDD65S8SVRHLjkVS8Dl1hRQjGGaNXz2dLSKdafQiFamCOqhyVlDXosFpCD + TSM1Jyq2N9jKZCLsMPBPaz1CI4+B/J+5QbYRPViH1P4tka1HfKU2b4uOvTycg51WMdG9Wm0QChEF + HxzljkxRkvOAuusj9HJszDn1wsUnZyhGG0H1qJ4nQNpkqLIZcIxA/YNHpR3OCDgqTo5JE5m3criS + xnqd46qaiVFpSEZGhD+xBttCSLu5xly5SXT1o5ul8ZI0XUnZMaQBL1jKd+51g0CyyaSC3aPfazxM + YHp7AOWRjm4g6oGYlIMjX72sq0VVLer6rlpt6+W2ri8Yd15TX6WgkTs0UzVpDNXLFnDEcRo1q4XV + QFpNpgEGcEd0ePs60f9+Vv9OemVpOsT2xzQ+jM0lfHmepjAqvK6rdbV8eqKoLO15nGadFrQ/GxMj + SbvUjiIoS/S/IUL9TJnvP2lEc7Vp5nj8TzByI9mbAv7lKEDOlxbrDePFQwp7aRKTvHl8LAK8DM0j + lKtlgHNCKQKy6zx2zCqjtMk6uJL6Ma6cME/1S5Jj1PGYSTxth9Ni44amxfZd0jq5utZuxrJiCrkc + OJO8ZD1x1UDWYuDpGkgYzBEp5uPlcmf/yc0uBSWqDTMj990sMVGtCsrWkM5PyjuZc/Z1veY3YCbn + ekgR8Ii/IvCCaIj/rMHqabHcLJb8ZMwvxQmtyweK9t/xTiW3s9I3FHzlobqZ0ZqfLB1o0ml5o6d7 + LAURJ2n9yA4h9GhaMLqdn4xN9eWRVO+sNuB5E9L+ULQ9xXb19WGTS51FfV5xjC89cLSUtWlIH600 + Ad9/A5ls1FuIBwAA + 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, 23 Nov 2021 14:53:58 GMT + Server: + - nginx/1.20.1 + Transfer-Encoding: + - chunked + X-Powered-By: + - PHP/8.0.11 + status: + code: 200 + message: OK +- request: + body: '{"new_amount": 45, "best_before_date": "2019-05-04", "shopping_location_id": + 1, "location_id": 150, "price": true}' + headers: + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '114' + Content-Type: + - application/json + User-Agent: + - python-requests/2.26.0 + accept: + - application/json + method: POST + uri: https://localhost/api/stock/products/4/inventory + response: + body: + string: '{"error_message":"The new amount cannot equal the current stock amount"}' + 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: + - Tue, 23 Nov 2021 14:53:58 GMT + Server: + - nginx/1.20.1 + Transfer-Encoding: + - chunked + X-Powered-By: + - PHP/8.0.11 + status: + code: 400 + message: Bad Request +version: 1 diff --git a/test/cassettes/test_grocy/TestGrocy.test_inventory_product_by_barcode_valid.yaml b/test/cassettes/test_grocy/TestGrocy.test_inventory_product_by_barcode_valid.yaml new file mode 100644 index 0000000..711a403 --- /dev/null +++ b/test/cassettes/test_grocy/TestGrocy.test_inventory_product_by_barcode_valid.yaml @@ -0,0 +1,160 @@ +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/stock/products/by-barcode/42141099 + response: + body: + string: !!binary | + H4sIAAAAAAAEA81Vy27bMBD8lYDnOLBku0187Q/0XhQLmlpJRCiR4cOuE+Tfu0tKttMagXvrxbD2 + Re7M7PJNOG+bpKLYvgndiK1Yi3sxygHp7zevgwv03WBQXruo7Si2YzLmfk6DztvkIGdWFClV1HvO + 5Q9jleSc4ubCobfO6bGDD65S8SVRHLjkVS8Dl1hRQjGGaNXz2dLSKdafQiFamCOqhyVlDXosFpCD + TSM1Jyq2N9jKZCLsMPBPaz1CI4+B/J+5QbYRPViH1P4tka1HfKU2b4uOvTycg51WMdG9Wm0QChEF + HxzljkxRkvOAuusj9HJszDn1wsUnZyhGG0H1qJ4nQNpkqLIZcIxA/YNHpR3OCDgqTo5JE5m3criS + xnqd46qaiVFpSEZGhD+xBttCSLu5xly5SXT1o5ul8ZI0XUnZMaQBL1jKd+51g0CyyaSC3aPfazxM + YHp7AOWRjm4g6oGYlIMjX72sq0VVLer6rlpt6+W2ri8Yd15TX6WgkTs0UzVpDNXLFnDEcRo1q4XV + QFpNpgEGcEd0ePs60f9+Vv9OemVpOsT2xzQ+jM0lfHmepjAqvK6rdbV8eqKoLO15nGadFrQ/GxMj + SbvUjiIoS/S/IUL9TJnvP2lEc7Vp5nj8TzByI9mbAv7lKEDOlxbrDePFQwp7aRKTvHl8LAK8DM0j + lKtlgHNCKQKy6zx2zCqjtMk6uJL6Ma6cME/1S5Jj1PGYSTxth9Ni44amxfZd0jq5utZuxrJiCrkc + OJO8ZD1x1UDWYuDpGkgYzBEp5uPlcmf/yc0uBSWqDTMj990sMVGtCsrWkM5PyjuZc/Z1veY3YCbn + ekgR8Ii/IvCCaIj/rMHqabHcLJb8ZMwvxQmtyweK9t/xTiW3s9I3FHzlobqZ0ZqfLB1o0ml5o6d7 + LAURJ2n9yA4h9GhaMLqdn4xN9eWRVO+sNuB5E9L+ULQ9xXb19WGTS51FfV5xjC89cLSUtWlIH600 + Ad9/A5ls1FuIBwAA + 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, 23 Nov 2021 14:53:58 GMT + Server: + - nginx/1.20.1 + Transfer-Encoding: + - chunked + X-Powered-By: + - PHP/8.0.11 + status: + code: 200 + message: OK +- request: + body: '{"new_amount": 55, "best_before_date": "2019-05-04", "location_id": 1, + "price": 150}' + headers: + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '84' + Content-Type: + - application/json + User-Agent: + - python-requests/2.26.0 + accept: + - application/json + method: POST + uri: https://localhost/api/stock/products/by-barcode/42141099/inventory + response: + body: + string: !!binary | + H4sIAAAAAAAEA2VQ227DIAz9lYnnRoJc1ia/Mk2IgLOipRhx2VRN+/dh0JZKe/Q5ts/l5YtZwxYm + LpydmA9osk6yQmMB1A2zS8QTvUJMcoUNA0ijEhS852Lu+NRx2vY56KuKYA62F50QXT8UNh+Ey/t+ + YtGj3YHU6XlMqN+b8rOY9QabWodRE5WCclHpZNHJdPeka90HuITh3mkMASpHDoLVRIuJ7rIz6Gg8 + BpnsraRQN8+W5gI9uD/HDQr4KXWAktA87pewv3GexLhMwzLNRWVHraq1VmRBih/roWZpD6vH/dhq + 6GOseltyG84vZ6HUWZc/rRJyQ3Q7ilf03ro3+V+2FBxagYJ9v/4A9PKThtoBAAA= + 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, 23 Nov 2021 14:53:59 GMT + Server: + - nginx/1.20.1 + Transfer-Encoding: + - chunked + X-Powered-By: + - PHP/8.0.11 + 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/stock/products/4 + response: + body: + string: !!binary | + H4sIAAAAAAAEA81Vy27bMBD8lYLnODBlq0l07Q/0XhQLmqIkIpTI8GHXCfLv3SUl22mNwL31Ylj7 + IndmdvnGnLdtkpE1b0y3rGFbdscmMSr8+83r4AJ+typIr13UdmLNlIy5W9Kg9zY5yJkcI4WMek+5 + 9GGsFJRT3FQ4DNY5PfXwwVUqviSMA5e8HESgEhtMKMYQrXw+Wzo8xfpTKEQLSwS/X2PWqKdiATHa + NGFzjJO9VZ1IJsJOBfrprFfQimNA/2duEF1UHqxT2P4tkZ1X6hXbvC06DuJwDnZaxoT36rRRUIgo + +KhJ7NAUBToPSvdDhEFMrTmnXrjo5AzFZCPIQcnnGZAuGaxsRjVFwP7BK6mdWhBwWBwdsyYyb+Vw + KYz1OsfxioiRaUxGRAV/Yg22g5B2S42lcpvw6ke3SOMlabyStFNIo7pgKd950K0ClE0mFexe+b1W + hxlMbw8gvcKjW4h6RCbF6NBXrSu+4nxVVV/4pqnWTVVdMO68xr5KQSN2yszVhDFYL1vAIcdp0qQW + UgNqNZkWCMAd0uHt60z/+1n9O+GlxelgzY95fAibS/jyPM1hWHhb8S1fPz1hVJb2Mk6LTgvan42J + EahdbEcilCX63xDBfubM9584ornaPHM0/icYqZHsTUH95ShALpdmdU140ZDCXphEJD9sHosAL0Pz + COVqGeCcUIqA6HuvemIVk+s66+BK6se4csIy1S9JTFHHYybxtB1Oi40amhfbd4Hr5OpauxlLThRS + OXAmeUF6oqoBrcVA0zWiMIgjVMzHy+XO/pObXQqK8ZqYEft+kRjjm20mwxrU+Ul5J3POvq7X/AYs + 5FwPKQKe1K8ItCBa5D9rkD+t1vVqTU/G8lKc0Lp8oHD/Hb/I5HZW+BaDrzxUNzNa0ZOlA046Lm/l + 8R5rhsQJXD+iVxAGZTowuluejJp/fUTVO6sNeNqEuD8kbk/WbB7u61zqLOrziiN88YHDpaxNi/ro + hAnq/TcsMeFEiAcAAA== + 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, 23 Nov 2021 14:53:59 GMT + Server: + - nginx/1.20.1 + Transfer-Encoding: + - chunked + X-Powered-By: + - PHP/8.0.11 + status: + code: 200 + message: OK +version: 1 diff --git a/test/cassettes/test_grocy/TestGrocy.test_inventory_product_error.yaml b/test/cassettes/test_grocy/TestGrocy.test_inventory_product_error.yaml new file mode 100644 index 0000000..fb44351 --- /dev/null +++ b/test/cassettes/test_grocy/TestGrocy.test_inventory_product_error.yaml @@ -0,0 +1,99 @@ +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/stock/products/4 + response: + body: + string: !!binary | + H4sIAAAAAAAEA81Vy27bMBD8lYLnODBlq0l07Q/0XhQLmqIkIpTI8GHXCfLv3SUl22mNwL31Ylj7 + IndmdvnGnLdtkpE1b0y3rGFbdscmMSr8+83r4AJ+typIr13UdmLNlIy5W9Kg9zY5yJkcI4WMek+5 + 9GGsFJRT3FQ4DNY5PfXwwVUqviSMA5e8HESgEhtMKMYQrXw+Wzo8xfpTKEQLSwS/X2PWqKdiATHa + NGFzjJO9VZ1IJsJOBfrprFfQimNA/2duEF1UHqxT2P4tkZ1X6hXbvC06DuJwDnZaxoT36rRRUIgo + +KhJ7NAUBToPSvdDhEFMrTmnXrjo5AzFZCPIQcnnGZAuGaxsRjVFwP7BK6mdWhBwWBwdsyYyb+Vw + KYz1OsfxioiRaUxGRAV/Yg22g5B2S42lcpvw6ke3SOMlabyStFNIo7pgKd950K0ClE0mFexe+b1W + hxlMbw8gvcKjW4h6RCbF6NBXrSu+4nxVVV/4pqnWTVVdMO68xr5KQSN2yszVhDFYL1vAIcdp0qQW + UgNqNZkWCMAd0uHt60z/+1n9O+GlxelgzY95fAibS/jyPM1hWHhb8S1fPz1hVJb2Mk6LTgvan42J + EahdbEcilCX63xDBfubM9584ornaPHM0/icYqZHsTUH95ShALpdmdU140ZDCXphEJD9sHosAL0Pz + COVqGeCcUIqA6HuvemIVk+s66+BK6se4csIy1S9JTFHHYybxtB1Oi40amhfbd4Hr5OpauxlLThRS + OXAmeUF6oqoBrcVA0zWiMIgjVMzHy+XO/pObXQqK8ZqYEft+kRjjm20mwxrU+Ul5J3POvq7X/AYs + 5FwPKQKe1K8ItCBa5D9rkD+t1vVqTU/G8lKc0Lp8oHD/Hb/I5HZW+BaDrzxUNzNa0ZOlA046Lm/l + 8R5rhsQJXD+iVxAGZTowuluejJp/fUTVO6sNeNqEuD8kbk/WbB7u61zqLOrziiN88YHDpaxNi/ro + hAnq/TcsMeFEiAcAAA== + 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, 23 Nov 2021 14:53:59 GMT + Server: + - nginx/1.20.1 + Transfer-Encoding: + - chunked + X-Powered-By: + - PHP/8.0.11 + status: + code: 200 + message: OK +- request: + body: '{"new_amount": 55, "best_before_date": "2019-05-04", "shopping_location_id": + 1, "location_id": 1, "price": 150}' + headers: + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '111' + Content-Type: + - application/json + User-Agent: + - python-requests/2.26.0 + accept: + - application/json + method: POST + uri: https://localhost/api/stock/products/4/inventory + response: + body: + string: '{"error_message":"The new amount cannot equal the current stock amount"}' + 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: + - Tue, 23 Nov 2021 14:53:59 GMT + Server: + - nginx/1.20.1 + Transfer-Encoding: + - chunked + X-Powered-By: + - PHP/8.0.11 + status: + code: 400 + message: Bad Request +version: 1 diff --git a/test/cassettes/test_grocy/TestGrocy.test_inventory_product_valid.yaml b/test/cassettes/test_grocy/TestGrocy.test_inventory_product_valid.yaml new file mode 100644 index 0000000..ce841b7 --- /dev/null +++ b/test/cassettes/test_grocy/TestGrocy.test_inventory_product_valid.yaml @@ -0,0 +1,160 @@ +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/stock/products/4 + response: + body: + string: !!binary | + H4sIAAAAAAAEA81Vy27bMBD8lYLnODBlq0l07Q/0XhQLmqIkIpTI8GHXCfLv3SUl22mNwL31Ylj7 + IndmdvnGnLdtkpE1b0y3rGFbdscmMSr8+83r4AJ+typIr13UdmLNlIy5W9Kg9zY5yJkcI4WMek+5 + 9GGsFJRT3FQ4DNY5PfXwwVUqviSMA5e8HESgEhtMKMYQrXw+Wzo8xfpTKEQLSwS/X2PWqKdiATHa + NGFzjJO9VZ1IJsJOBfrprFfQimNA/2duEF1UHqxT2P4tkZ1X6hXbvC06DuJwDnZaxoT36rRRUIgo + +KhJ7NAUBToPSvdDhEFMrTmnXrjo5AzFZCPIQcnnGZAuGaxsRjVFwP7BK6mdWhBwWBwdsyYyb+Vw + KYz1OsfxioiRaUxGRAV/Yg22g5B2S42lcpvw6ke3SOMlabyStFNIo7pgKd950K0ClE0mFexe+b1W + hxlMbw8gvcKjW4h6RCbF6NBXrSu+4nxVVV/4pqnWTVVdMO68xr5KQSN2yszVhDFYL1vAIcdp0qQW + UgNqNZkWCMAd0uHt60z/+1n9O+GlxelgzY95fAibS/jyPM1hWHhb8S1fPz1hVJb2Mk6LTgvan42J + EahdbEcilCX63xDBfubM9584ornaPHM0/icYqZHsTUH95ShALpdmdU140ZDCXphEJD9sHosAL0Pz + COVqGeCcUIqA6HuvemIVk+s66+BK6se4csIy1S9JTFHHYybxtB1Oi40amhfbd4Hr5OpauxlLThRS + OXAmeUF6oqoBrcVA0zWiMIgjVMzHy+XO/pObXQqK8ZqYEft+kRjjm20mwxrU+Ul5J3POvq7X/AYs + 5FwPKQKe1K8ItCBa5D9rkD+t1vVqTU/G8lKc0Lp8oHD/Hb/I5HZW+BaDrzxUNzNa0ZOlA046Lm/l + 8R5rhsQJXD+iVxAGZTowuluejJp/fUTVO6sNeNqEuD8kbk/WbB7u61zqLOrziiN88YHDpaxNi/ro + hAnq/TcsMeFEiAcAAA== + 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, 23 Nov 2021 14:53:59 GMT + Server: + - nginx/1.20.1 + Transfer-Encoding: + - chunked + X-Powered-By: + - PHP/8.0.11 + status: + code: 200 + message: OK +- request: + body: '{"new_amount": 65, "best_before_date": "2019-05-04", "shopping_location_id": + 1, "location_id": 1, "price": 150}' + headers: + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '111' + Content-Type: + - application/json + User-Agent: + - python-requests/2.26.0 + accept: + - application/json + method: POST + uri: https://localhost/api/stock/products/4/inventory + response: + body: + string: !!binary | + H4sIAAAAAAAEA2VQ20rEMBD9FcnzFpJe3La/IhLSZOoG20zIRVnEf3fSoF3wcc6ZmXN5+WLWsJmJ + UbAL8wFN1kkeUE+A2jG7VHhO0wIxyQVWDCCNSkB4y8XU8KHhZdvnoG8qgjnZVjRCNG1HbD4Jl7ft + wqJHu0FRL89jQv1elZ/FpFdY1dL1ulApKBeVThadTHdfdK37AJcw3BuNIcDBFQfB6kKLodxlZ9CV + 8RxksjulULtnc3WBHtyf4woF/JQ6ACU0j/sU9jfOk+jnoZuHiVQ21OqwVoskhPxYD0eW+vDwuJ1b + FX2MddxSbsP5eB0V7670p1ZS3BS6HsUbem/dm/wvSwWHWqBg368/Isamy9oBAAA= + 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, 23 Nov 2021 14:53:59 GMT + Server: + - nginx/1.20.1 + Transfer-Encoding: + - chunked + X-Powered-By: + - PHP/8.0.11 + 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/stock/products/4 + response: + body: + string: !!binary | + H4sIAAAAAAAEA81Vy27bMBD8lYLnOLDk2E187Q/0XhQLmlpJRCiR4cOuE+Tfu0tKttMagXvrxbD2 + Re7M7PJNOG+bpKLYvgndiK14EHdilAPS329eBxfou8GgvHZR21Fsx2TM3ZwGnbfJQc6sKFKqqPec + yx/GKsk5xc2FQ2+d02MHH1yl4kuiOHDJq14GLrGihGIM0arns6WlU6w/hUK0MEdU90vKGvRYLCAH + m0ZqTlRsb7CVyUTYYeCf1nqERh4D+T9zg2wjerAOqf1bIluP+Ept3hYde3k4BzutYqJ7tdogFCIK + PjjKHZmiJOcBdddH6OXYmHPqhYtPzlCMNoLqUT1PgLTJUGUz4BiB+gePSjucEXBUnByTJjJv5XAl + jfU6x1U1E6PSkIyMCH9iDbaFkHZzjblyk+jqRzdL4yVpupKyY0gDXrCU79zrBoFkk0kFu0e/13iY + wPT2AMojHd1A1AMxKQdHvnpZV4uqWtT1l2q1rZfbur5g3HlNfZWCRu7QTNWkMVQvW8ARx2nUrBZW + A2k1mQYYwB3R4e3rRP/7Wf076ZWl6RDbH9P4MDaX8OV5msKo8ENdPVTLpyeKytKex2nWaUH7szEx + krRL7SiCskT/GyLUz5T5/pNGNFebZo7H/wQjN5K9KeBfjgLkfGmxWTNePKSwlyYxyY+Pj0WAl6F5 + hHK1DHBOKEVAdp3Hjlml5M066+BK6se4csI81S9JjlHHYybxtB1Oi40bmhbbd0nr5OpauxnLiink + cuBM8pL1xFUDWYuBp2sgYTBHpJiPl8ud/Sc3uxSUqNbMjNx3s8REtdpkMqwhnZ+UdzLn7Ot6zW/A + TM71kCLgEX9F4AXREP9Zg9XTYrleLPnJmF+KE1qXDxTtv+MXldzOSt9Q8JWH6mZGa36ydKBJp+WN + nu6xFEScpPUjO4TQo2nB6HZ+MtbV5pFU76w24HkT0v5QtD3FdvX1fp1LnUV9XnGMLz1wtJS1aUgf + rTQB338DsIaNHogHAAA= + 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, 23 Nov 2021 14:53:59 GMT + Server: + - nginx/1.20.1 + Transfer-Encoding: + - chunked + X-Powered-By: + - PHP/8.0.11 + status: + code: 200 + message: OK +version: 1 diff --git a/test/test_grocy.py b/test/test_grocy.py index bb63f0a..c758942 100644 --- a/test/test_grocy.py +++ b/test/test_grocy.py @@ -1,18 +1,16 @@ import json from datetime import datetime - -import pytest - from test.test_const import CONST_BASE_URL, CONST_PORT, CONST_SSL from unittest import TestCase from unittest.mock import mock_open, patch +import pytest import responses from pygrocy import Grocy +from pygrocy.data_models.product import Product from pygrocy.errors import GrocyError from pygrocy.grocy_api_client import GrocyApiClient -from pygrocy.data_models.product import Product class TestGrocy(TestCase): @@ -220,6 +218,30 @@ def test_consume_product_error(self): ) self.assertRaises(GrocyError, self.grocy.consume_product, 1, 1.3) + @pytest.mark.vcr + def test_inventory_product_valid(self): + current_inventory = int(self.grocy.product(4).available_amount) + new_amount = current_inventory + 10 + + product = self.grocy.inventory_product( + 4, new_amount, self.date_test, 1, 1, 150, True + ) + + assert product.id == 4 + assert product.name == "Crisps" + assert product.available_amount == new_amount + + @pytest.mark.vcr + def test_inventory_product_error(self): + with pytest.raises(GrocyError) as exc_info: + current_inventory = int(self.grocy.product(4).available_amount) + self.grocy.inventory_product( + 4, current_inventory, self.date_test, 1, 1, 150, True + ) + + error = exc_info.value + assert error.status_code == 400 + @pytest.mark.vcr def test_add_product_by_barcode_valid(self): product = self.grocy.add_product_by_barcode( @@ -247,13 +269,35 @@ def test_consume_product_by_barcode_valid(self): assert product.name == "Crisps" @pytest.mark.vcr - def test_consume_product_error(self): + def test_consume_product_by_barcode_error(self): with pytest.raises(GrocyError) as exc_info: self.grocy.consume_product_by_barcode("555", 1, False, True) error = exc_info.value assert error.status_code == 400 + @pytest.mark.vcr + def test_inventory_product_by_barcode_valid(self): + currentInv = int(self.grocy.product_by_barcode("42141099").available_amount) + newAmount = currentInv + 10 + + product = self.grocy.inventory_product_by_barcode( + "42141099", newAmount, self.date_test, 1, 150, True + ) + + assert product.id == 4 + assert product.name == "Crisps" + assert product.available_amount == newAmount + + @pytest.mark.vcr + def test_inventory_product_by_barcode_error(self): + with pytest.raises(GrocyError) as exc_info: + currentInv = int(self.grocy.product_by_barcode("42141099").available_amount) + self.grocy.inventory_product(4, currentInv, self.date_test, 1, 150, True) + + error = exc_info.value + assert error.status_code == 400 + @responses.activate def test_execute_chore_valid(self): responses.add(responses.POST, f"{self.base_url}/chores/1/execute", status=200) From cff125f6aab2fb95a5724d8856c11d6e753ad694 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 21 Feb 2022 15:58:34 +0000 Subject: [PATCH 06/13] Update pydantic requirement from ~=1.8.2 to >=1.8.2,<1.10.0 (#212) --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index c0efc1e..d6b6954 100644 --- a/setup.py +++ b/setup.py @@ -20,7 +20,7 @@ "pytz~=2021.1", "tzlocal>=2.1,<3.0", "deprecation~=2.1.0", - "pydantic~=1.8.2", + "pydantic>=1.8.2,<1.10.0", ], classifiers=[ "Programming Language :: Python :: 3", From 6f402eee07a6ba5f1af64dfd936b62fb215a2ddc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 21 Feb 2022 16:02:00 +0000 Subject: [PATCH 07/13] Update tzlocal requirement from <3.0,>=2.1 to >=2.1,<5.0 (#198) --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index d6b6954..5f2dc7f 100644 --- a/setup.py +++ b/setup.py @@ -18,7 +18,7 @@ "requests", "iso8601~=0.1.16", "pytz~=2021.1", - "tzlocal>=2.1,<3.0", + "tzlocal>=2.1,<5.0", "deprecation~=2.1.0", "pydantic>=1.8.2,<1.10.0", ], From 0bd1b2853471ad1878d36fbb23c958f06ff3d8a6 Mon Sep 17 00:00:00 2001 From: Harshitha Devi Ganajala <87297355+harshi1122@users.noreply.github.com> Date: Tue, 22 Feb 2022 10:54:16 +0300 Subject: [PATCH 08/13] change `min_stock_amount` from int to float (#218) --- pygrocy/grocy_api_client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pygrocy/grocy_api_client.py b/pygrocy/grocy_api_client.py index f0c864c..8d82cdc 100644 --- a/pygrocy/grocy_api_client.py +++ b/pygrocy/grocy_api_client.py @@ -84,7 +84,7 @@ class ProductData(BaseModel): picture_file_name: Optional[str] = None allow_partial_units_in_stock: Optional[bool] = False row_created_timestamp: datetime - min_stock_amount: Optional[int] + min_stock_amount: Optional[float] default_best_before_days: int From 75ea44dff20d5e75f1dfebe201b3f3742b1f47ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Heuer?= Date: Tue, 22 Feb 2022 09:00:51 +0100 Subject: [PATCH 09/13] Added support for product quantities (#214) * Added support for product quantities * Added basic assertions for default qu field * Reformatted using black Co-authored-by: Sebastian Rutofski --- pygrocy/data_models/product.py | 40 ++++++++++++++++++++++++++++++++++ pygrocy/grocy.py | 8 +++++-- pygrocy/grocy_api_client.py | 10 ++++++++- test/test_product.py | 5 +++++ 4 files changed, 60 insertions(+), 3 deletions(-) diff --git a/pygrocy/data_models/product.py b/pygrocy/data_models/product.py index 65a61e1..661155c 100644 --- a/pygrocy/data_models/product.py +++ b/pygrocy/data_models/product.py @@ -10,6 +10,7 @@ ProductBarcodeData, ProductData, ProductDetailsResponse, + QuantityUnitData, ShoppingListItem, StockLogResponse, ) @@ -24,6 +25,30 @@ def barcode(self) -> str: return self._barcode +class QuantityUnit(DataModel): + def __init__(self, data: QuantityUnitData): + self._id = data.id + self._name = data.name + self._name_plural = data.name_plural + self._description = data.description + + @property + def id(self) -> int: + return self._id + + @property + def name(self) -> str: + return self._name + + @property + def name_plural(self) -> str: + return self._name_plural + + @property + def description(self) -> str: + return self._description + + class Product(DataModel): def __init__(self, data): self._init_empty() @@ -47,6 +72,9 @@ def _init_empty(self): self._available_amount = None self._best_before_date = None + self._default_quantity_unit_purchase = None + self._qu_factor_purchase_to_stock = None + self._barcodes = [] self._product_group_id = None @@ -68,6 +96,9 @@ def _init_from_ProductDetailsResponse(self, response: ProductDetailsResponse): self._available_amount = response.stock_amount self._best_before_date = response.next_best_before_date self._barcodes = [ProductBarcode(data) for data in response.barcodes] + self._default_quantity_unit_purchase = QuantityUnit( + response.default_quantity_unit_purchase + ) if response.product: self._init_from_ProductData(response.product) @@ -76,6 +107,7 @@ def _init_from_ProductData(self, product: ProductData): self._id = product.id self._product_group_id = product.product_group_id self._name = product.name + self._qu_factor_purchase_to_stock = product.qu_factor_purchase_to_stock def _init_from_StockLogResponse(self, response: StockLogResponse): self._id = response.product_id @@ -124,6 +156,14 @@ def amount_missing(self) -> float: def is_partly_in_stock(self) -> int: return self._is_partly_in_stock + @property + def default_quantity_unit_purchase(self) -> QuantityUnit: + return self._default_quantity_unit_purchase + + @property + def qu_factor_purchase_to_stock(self) -> float: + return self._qu_factor_purchase_to_stock + class Group(DataModel): def __init__(self, raw_product_group: LocationData): diff --git a/pygrocy/grocy.py b/pygrocy/grocy.py index 2ae2000..e2af24e 100644 --- a/pygrocy/grocy.py +++ b/pygrocy/grocy.py @@ -243,10 +243,14 @@ def add_missing_product_to_shopping_list(self, shopping_list_id: int = 1): return self._api_client.add_missing_product_to_shopping_list(shopping_list_id) def add_product_to_shopping_list( - self, product_id: int, shopping_list_id: int = None, amount: int = None + self, + product_id: int, + shopping_list_id: int = None, + amount: int = None, + quantity_unit_id: int = None, ): return self._api_client.add_product_to_shopping_list( - product_id, shopping_list_id, amount + product_id, shopping_list_id, amount, quantity_unit_id ) def clear_shopping_list(self, shopping_list_id: int = 1): diff --git a/pygrocy/grocy_api_client.py b/pygrocy/grocy_api_client.py index 8d82cdc..edcea1c 100644 --- a/pygrocy/grocy_api_client.py +++ b/pygrocy/grocy_api_client.py @@ -152,6 +152,7 @@ class ProductDetailsResponse(BaseModel): last_price: Optional[float] = None product: ProductData quantity_unit_stock: QuantityUnitData + default_quantity_unit_purchase: QuantityUnitData barcodes: Optional[List[ProductBarcodeData]] = Field(alias="product_barcodes") location: Optional[LocationData] = None @@ -533,13 +534,20 @@ def add_missing_product_to_shopping_list(self, shopping_list_id: int = None): self._do_post_request("stock/shoppinglist/add-missing-products", data) def add_product_to_shopping_list( - self, product_id: int, shopping_list_id: int = 1, amount: int = 1 + self, + product_id: int, + shopping_list_id: int = 1, + amount: int = 1, + quantity_unit_id: int = None, ): data = { "product_id": product_id, "list_id": shopping_list_id, "product_amount": amount, } + if quantity_unit_id: + data["qu_id"] = quantity_unit_id + print(data) self._do_post_request("stock/shoppinglist/add-product", data) def clear_shopping_list(self, shopping_list_id: int = 1): diff --git a/test/test_product.py b/test/test_product.py index 1fcef03..cbe89d2 100644 --- a/test/test_product.py +++ b/test/test_product.py @@ -23,6 +23,11 @@ def test_product_get_details_valid(self, grocy): assert product.name == "Cheese" assert product.available_amount == 5 assert product.product_group_id == 6 + assert product.qu_factor_purchase_to_stock == 1.0 + assert product.default_quantity_unit_purchase.id == 3 + assert product.default_quantity_unit_purchase.name == "Pack" + assert product.default_quantity_unit_purchase.description == None + assert product.default_quantity_unit_purchase.name_plural == "Packs" assert len(product.product_barcodes) == 1 barcode = product.product_barcodes[0] From 573ec7c6a52b49a070e098df09e06582f44675b5 Mon Sep 17 00:00:00 2001 From: Sebastian Rutofski Date: Tue, 22 Feb 2022 14:38:42 +0100 Subject: [PATCH 10/13] update python (#219) --- .travis.yml | 2 +- README.md | 2 +- tox.ini | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index fe88549..72f7fd2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,9 +12,9 @@ addons: sources: - sourceline: 'ppa:deadsnakes/ppa' packages: - - python3.6 - python3.8 - python3.9 + - python3.10 - python3-pip - python3-setuptools - python3-wheel diff --git a/README.md b/README.md index e270dab..b61881e 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # pygrocy [![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) +![Python Version](https://img.shields.io/badge/python-3.8%20%7C%203.9%20%7C%203.10-blue) ![Grocy Version](https://img.shields.io/badge/grocy-3.1.0-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) diff --git a/tox.ini b/tox.ini index 67b3705..470bd4a 100644 --- a/tox.ini +++ b/tox.ini @@ -1,9 +1,9 @@ [tox] -envlist = py36,py38,py39 +envlist = py38,py39,py310 [testenv] passenv = TRAVIS TRAVIS_* deps = -rrequirements.txt -rrequirements-dev.txt -commands = pytest --cov=pygrocy --cov-append \ No newline at end of file +commands = pytest --cov=pygrocy --cov-append From 9e6152b9e3ee36ddfa02d3ddaa1f6372359c34f7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 22 Feb 2022 13:50:36 +0000 Subject: [PATCH 11/13] Update iso8601 requirement from ~=0.1.16 to >=0.1.16,<1.1.0 (#202) --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 5f2dc7f..5caad96 100644 --- a/setup.py +++ b/setup.py @@ -16,7 +16,7 @@ packages=setuptools.find_packages(), install_requires=[ "requests", - "iso8601~=0.1.16", + "iso8601>=0.1.16,<1.1.0", "pytz~=2021.1", "tzlocal>=2.1,<5.0", "deprecation~=2.1.0", From 8f5214cd1598c842fea005d1c81f16932e5bd228 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 22 Feb 2022 13:51:13 +0000 Subject: [PATCH 12/13] Update responses requirement from ~=0.14.0 to ~=0.18.0 Updates the requirements on [responses](https://github.com/getsentry/responses) to permit the latest version. - [Release notes](https://github.com/getsentry/responses/releases) - [Changelog](https://github.com/getsentry/responses/blob/master/CHANGES) - [Commits](https://github.com/getsentry/responses/compare/0.14.0...0.18.0) --- updated-dependencies: - dependency-name: responses dependency-type: direct:development ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 4a0501b..8b60ac4 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,4 +1,4 @@ -responses~=0.14.0 +responses~=0.18.0 pre-commit isort pytest From 3e817a881fe8cd2b447dce3fd2a6557238b13fc3 Mon Sep 17 00:00:00 2001 From: Sebastian Rutofski Date: Sat, 5 Mar 2022 15:07:52 +0100 Subject: [PATCH 13/13] 1.1.0 prep --- CHANGELOG.md | 53 +++++++++++++++++++++++++++++++++++++++++----------- setup.py | 2 +- 2 files changed, 43 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 01953ab..8bbcb49 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,39 @@ # Changelog +## [v1.1.0](https://github.com/SebRut/pygrocy/tree/v1.1.0) (2022-03-05) + +[Full Changelog](https://github.com/SebRut/pygrocy/compare/v1.0.0...v1.1.0) + +**Implemented enhancements:** + +- Feature: Edit Stock [\#201](https://github.com/SebRut/pygrocy/issues/201) +- Add path in request when creating base url [\#121](https://github.com/SebRut/pygrocy/issues/121) +- Get product by Barcode [\#85](https://github.com/SebRut/pygrocy/issues/85) + +**Fixed bugs:** + +- min\_stock\_amount should be float, not int [\#217](https://github.com/SebRut/pygrocy/issues/217) + +**Closed issues:** + +- execute\_chore is formatting time in UTC time. grocy server ignores the timezone, so it shifts the date of chore execution [\#222](https://github.com/SebRut/pygrocy/issues/222) +- Update Product Userfields [\#196](https://github.com/SebRut/pygrocy/issues/196) + +**Merged pull requests:** + +- update python [\#219](https://github.com/SebRut/pygrocy/pull/219) ([SebRut](https://github.com/SebRut)) +- Optional float [\#218](https://github.com/SebRut/pygrocy/pull/218) ([harshi1122](https://github.com/harshi1122)) +- Update responses requirement from ~=0.14.0 to ~=0.18.0 [\#216](https://github.com/SebRut/pygrocy/pull/216) ([dependabot[bot]](https://github.com/apps/dependabot)) +- Added support for product quantities [\#214](https://github.com/SebRut/pygrocy/pull/214) ([andreheuer](https://github.com/andreheuer)) +- Update pydantic requirement from ~=1.8.2 to \>=1.8.2,\<1.10.0 [\#212](https://github.com/SebRut/pygrocy/pull/212) ([dependabot[bot]](https://github.com/apps/dependabot)) +- Feature/edit stock [\#209](https://github.com/SebRut/pygrocy/pull/209) ([georgegebbett](https://github.com/georgegebbett)) +- Product by barcode [\#208](https://github.com/SebRut/pygrocy/pull/208) ([georgegebbett](https://github.com/georgegebbett)) +- Update iso8601 requirement from ~=0.1.16 to \>=0.1.16,\<1.1.0 [\#202](https://github.com/SebRut/pygrocy/pull/202) ([dependabot[bot]](https://github.com/apps/dependabot)) +- Update tzlocal requirement from \<3.0,\>=2.1 to \>=2.1,\<5.0 [\#198](https://github.com/SebRut/pygrocy/pull/198) ([dependabot[bot]](https://github.com/apps/dependabot)) +- ProductDetailsResponse last\_used Type Date instead of Datetime [\#194](https://github.com/SebRut/pygrocy/pull/194) ([sebiecker](https://github.com/sebiecker)) +- support path for url [\#193](https://github.com/SebRut/pygrocy/pull/193) ([SebRut](https://github.com/SebRut)) +- Update responses requirement from ~=0.13.4 to ~=0.14.0 [\#191](https://github.com/SebRut/pygrocy/pull/191) ([dependabot[bot]](https://github.com/apps/dependabot)) + ## [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) @@ -7,7 +41,15 @@ **Implemented enhancements:** - Add assigned\_to\_user to task [\#188](https://github.com/SebRut/pygrocy/issues/188) +- Done time should be optional, as it is for execute\_chore call [\#176](https://github.com/SebRut/pygrocy/issues/176) - Add support for meal plan sections in grocy v3.1.0 [\#172](https://github.com/SebRut/pygrocy/issues/172) +- Add debug mode [\#167](https://github.com/SebRut/pygrocy/issues/167) +- 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) + +**Fixed bugs:** + +- Sending no time when tracking chore gives an error [\#175](https://github.com/SebRut/pygrocy/issues/175) **Closed issues:** @@ -27,14 +69,8 @@ [Full Changelog](https://github.com/SebRut/pygrocy/compare/v0.29.0...v0.30.0) -**Implemented enhancements:** - -- Done time should be optional, as it is for execute\_chore call [\#176](https://github.com/SebRut/pygrocy/issues/176) -- Add debug mode [\#167](https://github.com/SebRut/pygrocy/issues/167) - **Fixed bugs:** -- Sending no time when tracking chore gives an error [\#175](https://github.com/SebRut/pygrocy/issues/175) - Lovelace not accessible [\#158](https://github.com/SebRut/pygrocy/issues/158) **Closed issues:** @@ -63,11 +99,6 @@ [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) diff --git a/setup.py b/setup.py index 5caad96..a9be9fa 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ setuptools.setup( name="pygrocy", - version="1.0.0", + version="1.1.0", author="Sebastian Rutofski", author_email="kontakt@sebastian-rutofski.de", description="",