Repository: SebRut/pygrocy (python)

Issue:
implement chore details API interface
`GET /chores​/{choreId}`
returns more information for a single chore and should be implemented like the call for product details.


Pull request description:
add all fields from chore details api call
## Description

Adds missing fields for `GET /chores/{choreId}`

**Related issue (if applicable):** fixes #87

## Checklist
  - [ ] The code change is tested and works locally.
  - [ ] tests pass. **Your PR won't be merged unless tests pass**
  - [ ] There is no commented out code in this PR


Patch:
diff --git a/pygrocy/grocy.py b/pygrocy/grocy.py
index 7dd9575..271bfa4 100644
--- a/pygrocy/grocy.py
+++ b/pygrocy/grocy.py
@@ -1,5 +1,6 @@
 from datetime import datetime
-from typing import List
+from enum import Enum
+from typing import List, Dict
 
 from .grocy_api_client import (DEFAULT_PORT_NUMBER, ChoreDetailsResponse,
                                CurrentChoreResponse, CurrentStockResponse,
@@ -138,13 +139,56 @@ class ShoppingListProduct(object):
         return self._product
 
 
+class User(object):
+    def __init__(self, user_dto: UserDto):
+        self._id = user_dto.id
+        self._username = user_dto.username
+        self._first_name = user_dto.first_name
+        self._last_name = user_dto.last_name
+        self._display_name = user_dto.display_name
+
+    @property
+    def id(self) -> int:
+        return self._id
+
+    @property
+    def username(self) -> str:
+        return self._username
+
+    @property
+    def first_name(self) -> str:
+        return self._first_name
+
+    @property
+    def last_name(self) -> str:
+        return self._last_name
+
+    @property
+    def display_name(self) -> str:
+        return self._display_name
+
+
 class Chore(object):
+    class PeriodType(Enum):
+        MANUALLY = 'manually'
+        DYNAMIC_REGULAR = 'dynamic-regular'
+        DAILY = 'daily'
+        WEEKLY = 'weekly'
+        MONTHLY = 'monthly'
+
+    class AssignmentType(Enum):
+        NO_ASSIGNMENT = 'no-assignment'
+        WHO_DID_LEAST_DID_FIRST = 'who-did-least-did-first'
+        RANDOM = 'random'
+        IN_ALPHABETICAL_ORDER = 'in-alphabetical-order'
+
     def __init__(self, response):
         if isinstance(response, CurrentChoreResponse):
             self._init_from_CurrentChoreResponse(response)
         elif isinstance(response, ChoreDetailsResponse):
             self._init_from_ChoreDetailsResponse(response)
 
+    # noinspection PyPep8Naming
     def _init_from_CurrentChoreResponse(self, response: CurrentChoreResponse):
         self._id = response.chore_id
         self._last_tracked_time = response.last_tracked_time
@@ -152,23 +196,93 @@ class Chore(object):
         self._name = None
         self._last_done_by = None
 
-    def _init_from_ChoreDetailsResponse(self, response):
-        self._id = response.chore.id
+    # noinspection PyPep8Naming
+    def _init_from_ChoreDetailsResponse(self, response: ChoreDetailsResponse):
+        chore_data = response.chore
+        self._id = chore_data.id
+        self._name = chore_data.name
+        self._description = chore_data.description
+
+        if chore_data.period_type is not None:
+            self._period_type = Chore.PeriodType(chore_data.period_type)
+        else:
+            self._period_type = None
+
+        self._period_config = chore_data.period_config
+        self._period_days = chore_data.period_days
+        self._track_date_only = chore_data.track_date_only
+        self._rollover = chore_data.rollover
+
+        if chore_data.assignment_type is not None:
+            self._assignment_type = Chore.AssignmentType(chore_data.assignment_type)
+        else:
+            self._assignment_type = None
+
+        self._assignment_config = chore_data.assignment_config
+        self._next_execution_assigned_to_user_id = chore_data.next_execution_assigned_to_user_id
+        self._userfields = chore_data.userfields
+
         self._last_tracked_time = response.last_tracked
         self._next_estimated_execution_time = response.next_estimated_execution_time
-        self._name = response.chore.name
-        self._last_done_by = response.last_done_by
+        self._last_done_by = User(response.last_done_by)
+        self._track_count = response.track_count
+        if response.next_execution_assigned_user is not None:
+            self._next_execution_assigned_user = User(response.next_execution_assigned_user)
+        else:
+            self._next_execution_assigned_user = None
 
     def get_details(self, api_client: GrocyApiClient):
         details = api_client.get_chore(self.id)
-        self._name = details.chore.name
-        self._last_tracked_time = details.last_tracked
-        self._last_done_by = details.last_done_by
+        self._init_from_ChoreDetailsResponse(details)
 
     @property
     def id(self) -> int:
         return self._id
 
+    @property
+    def name(self) -> str:
+        return self._name
+
+    @property
+    def description(self) -> str:
+        return self._description
+
+    @property
+    def period_type(self) -> PeriodType:
+        return self._period_type
+
+    @property
+    def period_config(self) -> str:
+        return self._period_config
+
+    @property
+    def period_days(self) -> int:
+        return self._period_days
+
+    @property
+    def track_date_only(self) -> bool:
+        return self._track_date_only
+
+    @property
+    def rollover(self) -> bool:
+        return self._rollover
+
+    @property
+    def assignment_type(self) -> AssignmentType:
+        return self._assignment_type
+
+    @property
+    def assignment_config(self) -> str:
+        return self._assignment_config
+
+    @property
+    def next_execution_assigned_to_user_id(self) -> int:
+        return self._next_execution_assigned_to_user_id
+    
+    @property
+    def userfields(self) -> Dict[str,str]:
+        return self._userfields
+
     @property
     def last_tracked_time(self) -> datetime:
         return self._last_tracked_time
@@ -178,13 +292,16 @@ class Chore(object):
         return self._next_estimated_execution_time
 
     @property
-    def name(self) -> str:
-        return self._name
+    def last_done_by(self) -> User:
+        return self._last_done_by
 
     @property
-    def last_done_by(self) -> UserDto:
-        return self._last_done_by
+    def track_count(self) -> int:
+        return self._track_count
 
+    @property
+    def next_execution_assigned_user(self) -> User:
+        return self._next_execution_assigned_user
 
 class Grocy(object):
     def __init__(self, base_url, api_key, port: int = DEFAULT_PORT_NUMBER, verify_ssl=True):
diff --git a/pygrocy/grocy_api_client.py b/pygrocy/grocy_api_client.py
index f22cff1..f4f4e78 100644
--- a/pygrocy/grocy_api_client.py
+++ b/pygrocy/grocy_api_client.py
@@ -111,16 +111,18 @@ class ProductData(object):
 
 class ChoreData(object):
     def __init__(self, parsed_json):
-        self._id = parse_int(parsed_json.get('id'))
-        self._name = parsed_json.get('name')
-
-    @property
-    def id(self) -> int:
-        return self._id
-
-    @property
-    def name(self) -> str:
-        return self._name
+        self.id = parse_int(parsed_json.get('id'))
+        self.name = parsed_json.get('name')
+        self.description = parsed_json.get('description')
+        self.period_type = parsed_json.get('period_type')
+        self.period_config = parsed_json.get('period_config')
+        self.period_days = parse_int(parsed_json.get('period_days'))
+        self.track_date_only = parsed_json.get('track_date_only')
+        self.rollover = parsed_json.get('rollover')
+        self.assignment_type = parsed_json.get('assignment_type')
+        self.assignment_config = parsed_json.get('assignment_config')
+        self.next_execution_assigned_to_user_id = parse_int('next_execution_assigned_to_user_id')
+        self.userfields = parsed_json.get('userfields')
 
 
 class UserDto(object):
@@ -294,6 +296,13 @@ class ChoreDetailsResponse(object):
         self._chore = ChoreData(parsed_json.get('chore'))
         self._last_tracked = parse_date(parsed_json.get('last_tracked'))
         self._next_estimated_execution_time = parse_date(parsed_json.get('next_estimated_execution_time'))
+        self._track_count = parse_int(parsed_json.get('track_count'))
+
+        next_user = parsed_json.get('next_execution_assigned_user')
+        if next_user is not None:
+            self._next_execution_assigned_user = UserDto(next_user)
+        else:
+            self._next_execution_assigned_user = None
 
         if self._last_tracked is None:
             self._last_done_by = None
@@ -316,6 +325,14 @@ class ChoreDetailsResponse(object):
     def next_estimated_execution_time(self) -> datetime:
         return self._next_estimated_execution_time
 
+    @property
+    def track_count(self) -> int:
+        return self._track_count
+
+    @property
+    def next_execution_assigned_user(self) -> UserDto:
+        return self._next_execution_assigned_user
+
 
 class TransactionType(Enum):
     PURCHASE = "purchase"


Test patch:
diff --git a/test/test_choreDetailsResponse.py b/test/test_choreDetailsResponse.py
index 76e3392..61e4aa0 100644
--- a/test/test_choreDetailsResponse.py
+++ b/test/test_choreDetailsResponse.py
@@ -25,7 +25,15 @@ class TestChoreDetailsResponse(TestCase):
                 "display_name": "string",
                 "row_created_timestamp": "2019-05-04T11:31:04.564Z"
             },
-            "next_estimated_execution_time": "2019-05-04T11:31:04.564Z"
+            "next_estimated_execution_time": "2019-05-04T11:31:04.564Z",
+            "next_execution_assigned_user": {
+                "id": 42,
+                "username": "string",
+                "first_name": "string",
+                "last_name": "string",
+                "display_name": "string",
+                "row_created_timestamp": "2019-05-04T11:31:04.564Z"
+            }
         }"""
 
         response = ChoreDetailsResponse(json.loads(input_json))
@@ -35,6 +43,9 @@ class TestChoreDetailsResponse(TestCase):
 
         assert response.last_done_by.display_name == "string"
 
+        assert response.next_execution_assigned_user.id == 42
+        assert response.next_execution_assigned_user.display_name == "string"
+
     def test_no_last_tracked_data(self):
         input_json = """{
             "chore": {