Repository: SebRut/pygrocy
Pull request #244 (2022-07-17 20:46:05)

Get details for all batteries
## Description

- Add optional boolean to fetch details for all batteries. The default behavior remains unchanged.

- Add `last_tracked_time` to battery details response for compatibility (the API all batteries response `last_tracked_time` is called `last_charged` in the battery detail response).

## 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/data_models/battery.py b/pygrocy/data_models/battery.py
index ff2d0c5..56bf470 100644
--- a/pygrocy/data_models/battery.py
+++ b/pygrocy/data_models/battery.py
@@ -1,7 +1,11 @@
 from datetime import datetime
 
 from pygrocy.base import DataModel
-from pygrocy.grocy_api_client import BatteryDetailsResponse, CurrentBatteryResponse
+from pygrocy.grocy_api_client import (
+    BatteryDetailsResponse,
+    CurrentBatteryResponse,
+    GrocyApiClient,
+)
 
 
 class Battery(DataModel):
@@ -22,6 +26,7 @@ class Battery(DataModel):
     def _init_from_BatteryDetailsResponse(self, response: BatteryDetailsResponse):
         self._charge_cycles_count = response.charge_cycles_count
         self._last_charged = response.last_charged
+        self._last_tracked_time = response.last_charged  # For compatibility
         self._id = response.battery.id
         self._name = response.battery.name
         self._description = response.battery.description
@@ -41,6 +46,10 @@ class Battery(DataModel):
         self._created_timestamp = None
         self._userfields = None
 
+    def get_details(self, api_client: GrocyApiClient):
+        details = api_client.get_battery(self._id)
+        self._init_from_BatteryDetailsResponse(details)
+
     @property
     def id(self) -> int:
         return self._id
diff --git a/pygrocy/grocy.py b/pygrocy/grocy.py
index 24f0e90..9c134a0 100644
--- a/pygrocy/grocy.py
+++ b/pygrocy/grocy.py
@@ -329,9 +329,16 @@ class Grocy(object):
         if recipe:
             return RecipeItem(recipe)
 
-    def batteries(self, query_filters: List[str] = None) -> List[Battery]:
+    def batteries(
+        self, query_filters: List[str] = None, get_details: bool = False
+    ) -> List[Battery]:
         raw_batteries = self._api_client.get_batteries(query_filters)
-        return [Battery(bat) for bat in raw_batteries]
+        batteries = [Battery(bat) for bat in raw_batteries]
+
+        if get_details:
+            for item in batteries:
+                item.get_details(self._api_client)
+        return batteries
 
     def battery(self, battery_id: int) -> Battery:
         battery = self._api_client.get_battery(battery_id)
diff --git a/pygrocy/grocy_api_client.py b/pygrocy/grocy_api_client.py
index 5f7376b..4bb7f34 100644
--- a/pygrocy/grocy_api_client.py
+++ b/pygrocy/grocy_api_client.py
@@ -236,6 +236,7 @@ class BatteryDetailsResponse(BaseModel):
     battery: BatteryData
     charge_cycles_count: int
     last_charged: Optional[datetime] = None
+    last_tracked_time: Optional[datetime] = None
     next_estimated_charge_time: Optional[datetime] = None
 
 
@@ -680,6 +681,7 @@ class GrocyApiClient(object):
         parsed_json = self._do_get_request("batteries", query_filters)
         if parsed_json:
             return [CurrentBatteryResponse(**data) for data in parsed_json]
+        return []
 
     def get_battery(self, battery_id: int) -> BatteryDetailsResponse:
         parsed_json = self._do_get_request(f"batteries/{battery_id}")


Test patch:
diff --git a/test/cassettes/test_battery/TestBattery.test_get_batteries_with_details_valid.yaml b/test/cassettes/test_battery/TestBattery.test_get_batteries_with_details_valid.yaml
new file mode 100644
index 0000000..63a076f
--- /dev/null
+++ b/test/cassettes/test_battery/TestBattery.test_get_batteries_with_details_valid.yaml
@@ -0,0 +1,218 @@
+interactions:
+- request:
+    body: null
+    headers:
+      Accept-Encoding:
+      - gzip, deflate
+      Connection:
+      - keep-alive
+      User-Agent:
+      - python-requests/2.28.0
+      accept:
+      - application/json
+    method: GET
+    uri: https://localhost/api/batteries
+  response:
+    body:
+      string: '[{"id":"1","battery_id":"1","last_tracked_time":"2022-07-17 21:19:14","next_estimated_charge_time":"2023-01-13
+        21:19:14"},{"id":"2","battery_id":"2","last_tracked_time":"2022-04-28 19:33:45","next_estimated_charge_time":"2999-12-31
+        23:59:59"},{"id":"3","battery_id":"3","last_tracked_time":"2022-04-13 19:33:45","next_estimated_charge_time":"2022-06-12
+        19:33:45"},{"id":"4","battery_id":"4","last_tracked_time":"2022-04-22 19:33:45","next_estimated_charge_time":"2022-06-21
+        19:33:45"}]'
+    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:
+      - Sun, 17 Jul 2022 20:34:26 GMT
+      Server:
+      - nginx/1.22.0
+      Transfer-Encoding:
+      - chunked
+      X-Powered-By:
+      - PHP/8.0.20
+      content-length:
+      - '485'
+    status:
+      code: 200
+      message: OK
+- request:
+    body: null
+    headers:
+      Accept-Encoding:
+      - gzip, deflate
+      Connection:
+      - keep-alive
+      User-Agent:
+      - python-requests/2.28.0
+      accept:
+      - application/json
+    method: GET
+    uri: https://localhost/api/batteries/1
+  response:
+    body:
+      string: '{"battery":{"id":"1","name":"Battery1","description":"Warranty ends
+        2023","used_in":"TV remote control","charge_interval_days":"180","row_created_timestamp":"2022-06-17
+        19:33:37","active":"1"},"last_charged":"2022-07-17 21:19:14","charge_cycles_count":7,"next_estimated_charge_time":"2023-01-13
+        21:19:14"}'
+    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:
+      - Sun, 17 Jul 2022 20:34:26 GMT
+      Server:
+      - nginx/1.22.0
+      Transfer-Encoding:
+      - chunked
+      X-Powered-By:
+      - PHP/8.0.20
+      content-length:
+      - '305'
+    status:
+      code: 200
+      message: OK
+- request:
+    body: null
+    headers:
+      Accept-Encoding:
+      - gzip, deflate
+      Connection:
+      - keep-alive
+      User-Agent:
+      - python-requests/2.28.0
+      accept:
+      - application/json
+    method: GET
+    uri: https://localhost/api/batteries/2
+  response:
+    body:
+      string: '{"battery":{"id":"2","name":"Battery2","description":"Warranty ends
+        2022","used_in":"Alarm clock","charge_interval_days":"0","row_created_timestamp":"2022-06-17
+        19:33:37","active":"1"},"last_charged":"2022-04-28 19:33:45","charge_cycles_count":4,"next_estimated_charge_time":"2999-12-31
+        23:59:59"}'
+    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:
+      - Sun, 17 Jul 2022 20:34:26 GMT
+      Server:
+      - nginx/1.22.0
+      Transfer-Encoding:
+      - chunked
+      X-Powered-By:
+      - PHP/8.0.20
+      content-length:
+      - '297'
+    status:
+      code: 200
+      message: OK
+- request:
+    body: null
+    headers:
+      Accept-Encoding:
+      - gzip, deflate
+      Connection:
+      - keep-alive
+      User-Agent:
+      - python-requests/2.28.0
+      accept:
+      - application/json
+    method: GET
+    uri: https://localhost/api/batteries/3
+  response:
+    body:
+      string: '{"battery":{"id":"3","name":"Battery3","description":"Warranty ends
+        2022","used_in":"Heat remote control","charge_interval_days":"60","row_created_timestamp":"2022-06-17
+        19:33:37","active":"1"},"last_charged":"2022-04-13 19:33:45","charge_cycles_count":1,"next_estimated_charge_time":"2022-06-12
+        19:33:45"}'
+    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:
+      - Sun, 17 Jul 2022 20:34:26 GMT
+      Server:
+      - nginx/1.22.0
+      Transfer-Encoding:
+      - chunked
+      X-Powered-By:
+      - PHP/8.0.20
+      content-length:
+      - '306'
+    status:
+      code: 200
+      message: OK
+- request:
+    body: null
+    headers:
+      Accept-Encoding:
+      - gzip, deflate
+      Connection:
+      - keep-alive
+      User-Agent:
+      - python-requests/2.28.0
+      accept:
+      - application/json
+    method: GET
+    uri: https://localhost/api/batteries/4
+  response:
+    body:
+      string: '{"battery":{"id":"4","name":"Battery4","description":"Warranty ends
+        2028","used_in":"Heat remote control","charge_interval_days":"60","row_created_timestamp":"2022-06-17
+        19:33:37","active":"1"},"last_charged":"2022-04-22 19:33:45","charge_cycles_count":1,"next_estimated_charge_time":"2022-06-21
+        19:33:45"}'
+    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:
+      - Sun, 17 Jul 2022 20:34:26 GMT
+      Server:
+      - nginx/1.22.0
+      Transfer-Encoding:
+      - chunked
+      X-Powered-By:
+      - PHP/8.0.20
+      content-length:
+      - '306'
+    status:
+      code: 200
+      message: OK
+version: 1
diff --git a/test/test_battery.py b/test/test_battery.py
index 48c42a8..d32b534 100644
--- a/test/test_battery.py
+++ b/test/test_battery.py
@@ -8,11 +8,21 @@ from pygrocy.errors import GrocyError
 class TestBattery:
     @pytest.mark.vcr
     def test_get_batteries_valid(self, grocy):
-        batteries = grocy.batteries()
+        batteries = grocy.batteries(get_details=False)
 
         assert len(batteries) == 4
         assert isinstance(batteries[0].last_tracked_time, datetime)
 
+    @pytest.mark.vcr
+    def test_get_batteries_with_details_valid(self, grocy):
+        batteries = grocy.batteries(get_details=True)
+
+        assert len(batteries) == 4
+        assert isinstance(batteries[0].last_tracked_time, datetime)
+        assert batteries[0].last_charged == batteries[0].last_tracked_time
+        assert batteries[0].id == 1
+        assert batteries[0].name == "Battery1"
+
     @pytest.mark.vcr
     def test_get_battery_details_valid(self, grocy):
         battery = grocy.battery(1)