Remember the Milk - updating and completing tasks ()

* Remember the Milk - updating and completing tasks

Added new feature so that tasks can be updated and completed.
For this feature a task id must be set when creating the task.

* fixed hould complaints

* fixed review comments by @MartinHjelmare

* removed unnecessary check as proposed by @MartinHjelmare
pull/11374/head
ChristianKuehnel 2017-12-29 19:20:36 +01:00 committed by Martin Hjelmare
parent 3fd620198e
commit 7759ab6919
3 changed files with 161 additions and 12 deletions
homeassistant/components/remember_the_milk

View File

@ -15,7 +15,8 @@ import json
import voluptuous as vol
from homeassistant.config import load_yaml_config_file
from homeassistant.const import (CONF_API_KEY, STATE_OK, CONF_TOKEN, CONF_NAME)
from homeassistant.const import (CONF_API_KEY, STATE_OK, CONF_TOKEN,
CONF_NAME, CONF_ID)
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.entity_component import EntityComponent
@ -31,6 +32,10 @@ DEFAULT_NAME = DOMAIN
GROUP_NAME_RTM = 'remember the milk accounts'
CONF_SHARED_SECRET = 'shared_secret'
CONF_ID_MAP = 'id_map'
CONF_LIST_ID = 'list_id'
CONF_TIMESERIES_ID = 'timeseries_id'
CONF_TASK_ID = 'task_id'
RTM_SCHEMA = vol.Schema({
vol.Required(CONF_NAME): cv.string,
@ -44,9 +49,15 @@ CONFIG_SCHEMA = vol.Schema({
CONFIG_FILE_NAME = '.remember_the_milk.conf'
SERVICE_CREATE_TASK = 'create_task'
SERVICE_COMPLETE_TASK = 'complete_task'
SERVICE_SCHEMA_CREATE_TASK = vol.Schema({
vol.Required(CONF_NAME): cv.string,
vol.Optional(CONF_ID): cv.string,
})
SERVICE_SCHEMA_COMPLETE_TASK = vol.Schema({
vol.Required(CONF_ID): cv.string,
})
@ -84,10 +95,14 @@ def _create_instance(hass, account_name, api_key, shared_secret,
entity = RememberTheMilk(account_name, api_key, shared_secret,
token, stored_rtm_config)
component.add_entity(entity)
hass.services.async_register(
hass.services.register(
DOMAIN, '{}_create_task'.format(account_name), entity.create_task,
description=descriptions.get(SERVICE_CREATE_TASK),
schema=SERVICE_SCHEMA_CREATE_TASK)
hass.services.register(
DOMAIN, '{}_complete_task'.format(account_name), entity.complete_task,
description=descriptions.get(SERVICE_COMPLETE_TASK),
schema=SERVICE_SCHEMA_COMPLETE_TASK)
def _register_new_account(hass, account_name, api_key, shared_secret,
@ -168,8 +183,7 @@ class RememberTheMilkConfiguration(object):
def set_token(self, profile_name, token):
"""Store a new server token for a profile."""
if profile_name not in self._config:
self._config[profile_name] = dict()
self._initialize_profile(profile_name)
self._config[profile_name][CONF_TOKEN] = token
self.save_config()
@ -181,6 +195,44 @@ class RememberTheMilkConfiguration(object):
self._config.pop(profile_name, None)
self.save_config()
def _initialize_profile(self, profile_name):
"""Initialize the data structures for a profile."""
if profile_name not in self._config:
self._config[profile_name] = dict()
if CONF_ID_MAP not in self._config[profile_name]:
self._config[profile_name][CONF_ID_MAP] = dict()
def get_rtm_id(self, profile_name, hass_id):
"""Get the rtm ids for a home assistant task id.
The id of a rtm tasks consists of the tuple:
list id, timeseries id and the task id.
"""
self._initialize_profile(profile_name)
ids = self._config[profile_name][CONF_ID_MAP].get(hass_id)
if ids is None:
return None
return ids[CONF_LIST_ID], ids[CONF_TIMESERIES_ID], ids[CONF_TASK_ID]
def set_rtm_id(self, profile_name, hass_id, list_id, time_series_id,
rtm_task_id):
"""Add/Update the rtm task id for a home assistant task id."""
self._initialize_profile(profile_name)
id_tuple = {
CONF_LIST_ID: list_id,
CONF_TIMESERIES_ID: time_series_id,
CONF_TASK_ID: rtm_task_id,
}
self._config[profile_name][CONF_ID_MAP][hass_id] = id_tuple
self.save_config()
def delete_rtm_id(self, profile_name, hass_id):
"""Delete a key mapping."""
self._initialize_profile(profile_name)
if hass_id in self._config[profile_name][CONF_ID_MAP]:
del self._config[profile_name][CONF_ID_MAP][hass_id]
self.save_config()
class RememberTheMilk(Entity):
"""MVP implementation of an interface to Remember The Milk."""
@ -225,19 +277,65 @@ class RememberTheMilk(Entity):
import rtmapi
try:
task_name = call.data.get('name')
task_name = call.data.get(CONF_NAME)
hass_id = call.data.get(CONF_ID)
rtm_id = None
if hass_id is not None:
rtm_id = self._rtm_config.get_rtm_id(self._name, hass_id)
result = self._rtm_api.rtm.timelines.create()
timeline = result.timeline.value
self._rtm_api.rtm.tasks.add(
timeline=timeline, name=task_name, parse='1')
_LOGGER.debug('created new task "%s" in account %s',
task_name, self.name)
if hass_id is None or rtm_id is None:
result = self._rtm_api.rtm.tasks.add(
timeline=timeline, name=task_name, parse='1')
_LOGGER.debug('created new task "%s" in account %s',
task_name, self.name)
self._rtm_config.set_rtm_id(self._name,
hass_id,
result.list.id,
result.list.taskseries.id,
result.list.taskseries.task.id)
else:
self._rtm_api.rtm.tasks.setName(name=task_name,
list_id=rtm_id[0],
taskseries_id=rtm_id[1],
task_id=rtm_id[2],
timeline=timeline)
_LOGGER.debug('updated task with id "%s" in account '
'%s to name %s',
hass_id, self.name, task_name)
except rtmapi.RtmRequestFailedException as rtm_exception:
_LOGGER.error('Error creating new Remember The Milk task for '
'account %s: %s', self._name, rtm_exception)
return False
return True
def complete_task(self, call):
"""Complete a task that was previously created by this component."""
import rtmapi
hass_id = call.data.get(CONF_ID)
rtm_id = self._rtm_config.get_rtm_id(self._name, hass_id)
if rtm_id is None:
_LOGGER.error('Could not find task with id %s in account %s. '
'So task could not be closed.',
hass_id, self._name)
return False
try:
result = self._rtm_api.rtm.timelines.create()
timeline = result.timeline.value
self._rtm_api.rtm.tasks.complete(list_id=rtm_id[0],
taskseries_id=rtm_id[1],
task_id=rtm_id[2],
timeline=timeline)
self._rtm_config.delete_rtm_id(self._name, hass_id)
_LOGGER.debug('Completed task with id %s in account %s',
hass_id, self._name)
except rtmapi.RtmRequestFailedException as rtm_exception:
_LOGGER.error('Error creating new Remember The Milk task for '
'account %s: %s', self._name, rtm_exception)
return True
@property
def name(self):
"""Return the name of the device."""

View File

@ -1,9 +1,24 @@
# Describes the format for available Remember The Milk services
create_task:
description: Create a new task in your Remember The Milk account
description: >
Create (or update) a new task in your Remember The Milk account. If you want to update a task
later on, you have to set an "id" when creating the task.
Note: Updating a tasks does not support the smart syntax.
fields:
name:
description: name of the new task, you can use the smart syntax here
example: 'do this ^today #from_hass'
example: 'do this ^today #from_hass'
id:
description: (optional) identifier for the task you're creating, can be used to update or complete the task later on
example: myid
complete_task:
description: Complete a tasks that was privously created.
fields:
id:
description: identifier that was defined when creating the task
example: myid

View File

@ -1,6 +1,7 @@
"""Tests for the Remember The Milk component."""
import logging
import json
import unittest
from unittest.mock import patch, mock_open, Mock
@ -19,7 +20,16 @@ class TestConfiguration(unittest.TestCase):
self.hass = get_test_home_assistant()
self.profile = "myprofile"
self.token = "mytoken"
self.json_string = '{"myprofile": {"token": "mytoken"}}'
self.json_string = json.dumps(
{"myprofile": {
"token": "mytoken",
"id_map": {"1234": {
"list_id": "0",
"timeseries_id": "1",
"task_id": "2"
}}
}
})
def tearDown(self):
"""Exit home assistant."""
@ -47,3 +57,29 @@ class TestConfiguration(unittest.TestCase):
patch("os.path.isfile", Mock(return_value=True)):
config = rtm.RememberTheMilkConfiguration(self.hass)
self.assertIsNotNone(config)
def test_id_map(self):
"""Test the hass to rtm task is mapping."""
hass_id = "hass-id-1234"
list_id = "mylist"
timeseries_id = "my_timeseries"
rtm_id = "rtm-id-4567"
with patch("builtins.open", mock_open()), \
patch("os.path.isfile", Mock(return_value=False)):
config = rtm.RememberTheMilkConfiguration(self.hass)
self.assertEqual(None, config.get_rtm_id(self.profile, hass_id))
config.set_rtm_id(self.profile, hass_id, list_id, timeseries_id,
rtm_id)
self.assertEqual((list_id, timeseries_id, rtm_id),
config.get_rtm_id(self.profile, hass_id))
config.delete_rtm_id(self.profile, hass_id)
self.assertEqual(None, config.get_rtm_id(self.profile, hass_id))
def test_load_key_map(self):
"""Test loading an existing key map from the file."""
with patch("builtins.open", mock_open(read_data=self.json_string)), \
patch("os.path.isfile", Mock(return_value=True)):
config = rtm.RememberTheMilkConfiguration(self.hass)
self.assertEqual(('0', '1', '2',),
config.get_rtm_id(self.profile, "1234"))