def update_task(task_id, attribute, value): # TODO Create Docstring logger_obj.info(task_id) logger_obj.info("attribute = " + attribute) logger_obj.info(value) tw = TaskWarrior() task_id, task = tw.get_task(id=task_id) logger_obj.info(task) for attempt in range(9): try: if attribute not in task or task[attribute] != value: task[attribute] = value logger_obj.debug("Setting " + attribute + " to " + str(value)) tw.task_update(task) except: logger_obj.exception('Retry update - ' + str(attempt)) else: break else: logger_obj.debug('Failed to Update task') # try: # if task[attribute] != value: # task[attribute] = value # logger_obj.debug("Setting " + attribute + " to " + value) # tw.task_update(task) # except KeyError: # logger_obj.info("Attribute has not been set so we are adding it") # task[attribute] = value # tw.task_update(task) logger_obj.info(task)
def main(task_id, action): w = TaskWarrior() # First, make sure that the task id is valid. if action == 'start': task = w.get_task(id=task_id) if task[0] is None: print "Invalid task ID." return task = task[1] print task name = task['description'].replace(",","") # get rid of commas name = "%s %s" % (task_id, name) # store task_id in the name so we can find it again later when we want to mark a task as 'done'... category = "" if 'tags' in task: # preserve tags from TaskWarrior name = "%s, %s " % (name, " ".join(task['tags'])) if 'project' in task: # if there is a project assigned in TW, make it a Project Hamster category name = "%s@%s" % (name, task['project']) subprocess.check_output(['hamster', action, name]) return if action == 'done': if task_id == 0: cur_hamster = subprocess.check_output(['hamster', 'current']).strip().split() task_id = int(cur_hamster[2]) subprocess.check_output(['hamster', 'stop']) w.task_done(id=task_id) return
def update_task(tw: TaskWarrior, task: Dict[str, Any]) -> None: logger.debug('Maybe updating task %s', task['webdesk_key']) id_, twt = tw.get_task(webdesk_key=task['webdesk_key']) _push_properties(task, twt) r = twt.update(task) if True in r.values(): tw.task_update(twt) logger.log(logging.INFO + 5, 'Updated task %d (%s)', id_, ', '.join(k for k, v in r.items() if v is True))
def synchronize(issues): tw = TaskWarrior() # Load info about the task database tasks = tw.load_tasks() is_bugwarrior_task = lambda task: task.get('description', '').startswith(MARKUP) # Prune down to only tasks managed by bugwarrior for key in tasks.keys(): tasks[key] = filter(is_bugwarrior_task, tasks[key]) # Build a list of only the descriptions of those local bugwarrior tasks local_descs = [t['description'] for t in sum(tasks.values(), []) \ if t['status'] not in ('deleted')] # Now for the remote data. # Build a list of only the descriptions of those remote issues remote_descs = [i['description'] for i in issues] # Build the list of tasks that need to be added is_new = lambda issue: issue['description'] not in local_descs new_issues = filter(is_new, issues) old_issues = filter(lambda i: not is_new(i), issues) # Build the list of local tasks that need to be completed is_done = lambda task: task['description'] not in remote_descs done_tasks = filter(is_done, tasks['pending']) log.struct(new=len(new_issues), completed=len(done_tasks)) # Add new issues for issue in new_issues: log.info("Adding task {0}", issue['description'].encode("utf-8")) tw.task_add(**issue) # Update any issues that may have had new properties added. These are # usually annotations that come from comments in the issue thread. pending_descriptions = [t['description'] for t in tasks['pending']] for upstream_issue in old_issues: if upstream_issue['description'] not in pending_descriptions: continue id, task = tw.get_task(description=upstream_issue['description']) for key in upstream_issue: if key not in task: log.info("Updating {0} on {1}", key, upstream_issue['description'].encode("utf-8")) task[key] = upstream_issue[key] id, task = tw.task_update(task) # Delete old issues for task in done_tasks: log.info("Completing task {0}", task['description'].encode("utf-8")) tw.task_done(uuid=task['uuid'])
def main(args): w = TaskWarrior() task_id = 0 try: create_or_connect() pomodoro_counter = args.pomodoro_counter task_id, task_body = w.get_task(id=args.taskw_id) print("Starting our pomodoro break period: %d on task: %s" % (pomodoro_counter, task_body['description'])) task_uuid = task_body['uuid'] while True: do_task_work(task_uuid, task_id=task_id) pomodoro_counter += 1 do_task_break(pomodoro_counter, task_uuid, task_id=task_id) except KeyboardInterrupt: pass finally: exit_gracefully() subprocess.call(["task", str(task_id), "stop"])
class TaskWarriorSide(GenericSide): """Handles interaction with the TaskWarrior client.""" def __init__(self, **kargs): super(TaskWarriorSide, self).__init__() # Tags are used to filter the tasks for both *push* and *pull*. self.config = { "tags": [], "config_filename": "~/.taskrc", "enable_caching": True } self.config.update(**kargs) assert isinstance(self.config["tags"], list), "Expected a list of tags" # TaskWarrior instance as a class memeber - initialize only once self.tw = TaskWarrior(marshal=True, config_filename=self.config["config_filename"]) # All TW tasks self.items: Dict[str, List[dict]] = [] # Whether to refresh the cached list of items self.reload_items = True def _load_all_items(self): """Load all tasks to memory. May return already loaded list of items, depending on the validity of the cache. """ if not self.config["enable_caching"]: self.items = self.tw.load_tasks() return if self.reload_items: self.items = self.tw.load_tasks() self.reload_items = False @overrides def get_all_items(self, **kargs): """Fetch the tasks off the local taskw db. :param kargs: Extra options for the call. * Use the `order_by` arg to specify the order by which to return the items. * Use the `use_ascending_order` boolean flag to specify ascending/descending order * `include_completed` to also include completed tasks [Default: True] :return: list of tasks that exist locally :raises: ValueError in case the order_by key is invalid """ self._load_all_items() tasks = [] if kargs.get("include_completed", True): tasks.extend(self.items["completed"]) tasks.extend(self.items["pending"]) tags = set(self.config["tags"]) tasks = [t for t in tasks if tags.issubset(t.get("tags", []))] if "order_by" in kargs and kargs["order_by"] is not None: if "use_ascending_order" in kargs: assert isinstance(kargs["use_ascending_order"], bool) use_ascending_order = kargs["use_ascending_order"] else: use_ascending_order = True assert (kargs["order_by"] in [ "description", "end", "entry", "id", "modified", "status", "urgency" ] and "Invalid 'order_by' value") tasks.sort(key=lambda t: t[kargs["order_by"]], reverse=not use_ascending_order) return tasks @overrides def get_single_item(self, item_id: str) -> Union[dict, None]: t = self.tw.get_task(id=item_id)[-1] or None assert "status" in t.keys() # type: ignore return t if t["status"] != "deleted" else None # type: ignore @overrides def update_item(self, item_id: str, **changes): """Update an already added item. :raises ValaueError: In case the item is not present in the db """ changes.pop("id", False) t = self.tw.get_task(uuid=UUID(item_id))[-1] # task CLI doesn't allow `imask` unwanted_keys = ["imask", "recur", "rtype", "parent"] for i in unwanted_keys: t.pop(i, False) # taskwarrior doesn't let you explicitly set the update time. # even if you set it it will revert to the time that you call # `tw.task_update` d = dict(t) d.update(changes) self.tw.task_update(d) @overrides def add_item(self, item) -> dict: """Add a new Item as a TW task. :param item: This should contain only keys that exist in standard TW tasks (e.g., proj, tag, due). It is mandatory that it contains the 'description' key for the task title """ assert "description" in item.keys(), "Item doesn't have a description." assert ("uuid" not in item.keys( )), "Item already has a UUID, try updating it instead of adding it" curr_status = item.get("status", None) if curr_status not in ["pending", "done"]: self.logger.info( 'Invalid status of task: "%s", setting it to pending', item["status"]) item["status"] = "pending" item.setdefault("tags", []) item["tags"] += self.config["tags"] description = item.pop("description") new_item = self.tw.task_add(description=description, **item) len_print = min(20, len(description)) self.logger.info('Task "{}" created - "{}"...'.format( new_item["id"], description[0:len_print])) return new_item @overrides def delete_single_item(self, item_id) -> None: self.tw.task_delete(uuid=item_id) @staticmethod def items_are_identical(item1, item2, ignore_keys=[]) -> bool: keys = [ k for k in [ "annotations", "description", "due", "modified", "status", "uuid" ] if k not in ignore_keys ] # special care for the annotations key if "annotations" in item1 and "annotations" in item2: if item1["annotations"] != item2["annotations"]: return False item1.pop("annotations") item2.pop("annotations") # one may contain empty list elif "annotations" in item1 and "annotations" not in item2: if item1["annotations"] != []: return False item1.pop("annotations") # one may contain empty list elif "annotations" in item2 and "annotations" not in item1: if item2["annotations"] != []: return False item2.pop("annotations") else: pass return GenericSide._items_are_identical(item1, item2, keys) @staticmethod def get_task_id(item: dict) -> str: """Get the ID of a task in string form""" return str(item["uuid"])
class TestDB(object): def setup(self): # Create some temporary config stuff fd, fname = tempfile.mkstemp(prefix='taskw-testsrc') dname = tempfile.mkdtemp(prefix='taskw-tests-data') with open(fname, 'w') as f: f.writelines(['data.location=%s' % dname]) # Create empty .data files for piece in ['completed', 'pending', 'undo']: with open(os.path.sep.join([dname, piece + '.data']), 'w'): pass # Save names for .tearDown() self.fname, self.dname = fname, dname # Create the taskwarrior db object that each test will use. self.tw = TaskWarrior(config_filename=fname) def tearDown(self): os.remove(self.fname) shutil.rmtree(self.dname) def test_has_two_categories(self): tasks = self.tw.load_tasks() eq_(len(tasks), 2) def test_empty_db(self): tasks = self.tw.load_tasks() eq_(len(sum(tasks.values(), [])), 0) def test_add(self): self.tw.task_add("foobar") tasks = self.tw.load_tasks() eq_(len(tasks['pending']), 1) def test_unchanging_load_tasks(self): tasks = self.tw.load_tasks() eq_(len(tasks['pending']), 0) tasks = self.tw.load_tasks() eq_(len(tasks['pending']), 0) @raises(KeyError) def test_completion_raising_unspecified(self): self.tw.task_done() def test_completing_task_by_id_unspecified(self): self.tw.task_add("foobar") self.tw.task_done(id=1) tasks = self.tw.load_tasks() eq_(len(tasks['pending']), 0) eq_(len(tasks['completed']), 1) eq_(len(sum(tasks.values(), [])), 1) ok_(tasks['completed'][0]['end'] != None) def test_completing_task_with_date(self): self.tw.task_add("foobar") uuid = self.tw.load_tasks()['pending'][0]['uuid'] self.tw.task_done(uuid=uuid, end="1234567890") tasks = self.tw.load_tasks() eq_(len(tasks['pending']), 0) eq_(len(tasks['completed']), 1) eq_(tasks['completed'][0]['end'], '1234567890') def test_completing_task_by_id_specified(self): self.tw.task_add("foobar") self.tw.task_done(id=1) tasks = self.tw.load_tasks() eq_(len(tasks['pending']), 0) eq_(len(tasks['completed']), 1) eq_(len(sum(tasks.values(), [])), 1) def test_completing_task_by_id_retrieved(self): task = self.tw.task_add("foobar") self.tw.task_done(id=task['id']) tasks = self.tw.load_tasks() eq_(len(tasks['pending']), 0) eq_(len(tasks['completed']), 1) eq_(len(sum(tasks.values(), [])), 1) def test_completing_task_by_uuid(self): self.tw.task_add("foobar") uuid = self.tw.load_tasks()['pending'][0]['uuid'] self.tw.task_done(uuid=uuid) tasks = self.tw.load_tasks() eq_(len(tasks['pending']), 0) eq_(len(tasks['completed']), 1) eq_(len(sum(tasks.values(), [])), 1) @raises(KeyError) def test_get_task_mismatch(self): self.tw.task_add("foobar") self.tw.task_add("bazbar") uuid = self.tw.load_tasks()['pending'][0]['uuid'] self.tw.get_task(id=2, uuid=uuid) # which one? def test_updating_task(self): self.tw.task_add("foobar") tasks = self.tw.load_tasks() eq_(len(tasks['pending']), 1) task = tasks['pending'][0] task["priority"] = "L" self.tw.task_update(task) tasks = self.tw.load_tasks() eq_(len(tasks['pending']), 1) eq_(tasks['pending'][0], task) @raises(KeyError) def test_update_exc(self): task = dict(description="lol") self.tw.task_update(task)