X-Git-Url: https://git.madduck.net/etc/taskwarrior.git/blobdiff_plain/8a2ba91cb471ad97094311172b86b9bf3afed725..9c20e0ae392ba353109d7bea344032c56cff1136:/tasklib/task.py diff --git a/tasklib/task.py b/tasklib/task.py index cbc1192..8374eb7 100644 --- a/tasklib/task.py +++ b/tasklib/task.py @@ -24,6 +24,8 @@ VERSION_2_4_0 = six.u('2.4.0') VERSION_2_4_1 = six.u('2.4.1') VERSION_2_4_2 = six.u('2.4.2') VERSION_2_4_3 = six.u('2.4.3') +VERSION_2_4_4 = six.u('2.4.4') +VERSION_2_4_5 = six.u('2.4.5') logger = logging.getLogger(__name__) local_zone = tzlocal.get_localzone() @@ -392,9 +394,9 @@ class TaskResource(SerializingObject): class TaskAnnotation(TaskResource): read_only_fields = ['entry', 'description'] - def __init__(self, task, data={}): + def __init__(self, task, data=None): self.task = task - self._load_data(data) + self._load_data(data or dict()) super(TaskAnnotation, self).__init__(task.warrior) def remove(self): @@ -688,7 +690,7 @@ class Task(TaskResource): # Refreshing is very important here, as not only modification time # is updated, but arbitrary attribute may have changed due hooks # altering the data before saving - self.refresh() + self.refresh(after_save=True) def add_annotation(self, annotation): if not self.saved: @@ -744,7 +746,7 @@ class Task(TaskResource): return args - def refresh(self, only_fields=[]): + def refresh(self, only_fields=None, after_save=False): # Raise error when trying to refresh a task that has not been saved if not self.saved: raise Task.NotSaved("Task needs to be saved to be refreshed") @@ -755,13 +757,37 @@ class Task(TaskResource): args = [self['uuid'] or self['id'], 'export'] output = self.warrior.execute_command(args) - # If more than 1 task has been matched, raise exception - if len(output) > 1: + def valid(output): + return len(output) == 1 and output[0].startswith('{') + + # For older TW versions attempt to uniquely locate the task + # using the data we have if it has been just saved. + # This can happen when adding a completed task on older TW versions. + if (not valid(output) and self.warrior.version < VERSION_2_4_5 + and after_save): + + # Make a copy, removing ID and UUID. It's most likely invalid + # (ID 0) if it failed to match a unique task. + data = copy.deepcopy(self._data) + data.pop('id', None) + data.pop('uuid', None) + + taskfilter = TaskFilter(self.warrior) + for key, value in data.items(): + taskfilter.add_filter_param(key, value) + + output = self.warrior.execute_command(['export', '--'] + + taskfilter.get_filter_params()) + + # If more than 1 task has been matched still, raise an exception + if not valid(output): raise TaskWarriorException( - "Unique identifier {0} matches multiple tasks: {1}".format( - self['uuid'] or self['id'], output)) + "Unique identifiers {0} with description: {1} matches " + "multiple tasks: {2}".format( + self['uuid'] or self['id'], self['description'], output) + ) - new_data = json.loads(self.warrior.execute_command(args)[0]) + new_data = json.loads(output[0]) if only_fields: to_update = dict( [(k, new_data.get(k)) for k in only_fields]) @@ -774,8 +800,8 @@ class TaskFilter(SerializingObject): A set of parameters to filter the task list with. """ - def __init__(self, warrior, filter_params=[]): - self.filter_params = filter_params + def __init__(self, warrior, filter_params=None): + self.filter_params = filter_params or [] super(TaskFilter, self).__init__(warrior) def add_filter(self, filter_str): @@ -802,8 +828,10 @@ class TaskFilter(SerializingObject): # We enforce equality match by using 'is' (or 'none') modifier # Without using this syntax, filter fails due to TW-1479 - modifier = '.is' if value else '.none' - key = key + modifier if '.' not in key else key + # which is, however, fixed in 2.4.5 + if self.warrior.version < VERSION_2_4_5: + modifier = '.is' if value else '.none' + key = key + modifier if '.' not in key else key self.filter_params.append(six.u("{0}:{1}").format(key, value)) @@ -958,10 +986,10 @@ class TaskWarrior(object): self.tasks = TaskQuerySet(self) - def _get_command_args(self, args, config_override={}): + def _get_command_args(self, args, config_override=None): command_args = ['task', 'rc:{0}'.format(self.taskrc_location)] config = self.config.copy() - config.update(config_override) + config.update(config_override or dict()) for item in config.items(): command_args.append('rc.{0}={1}'.format(*item)) command_args.extend(map(six.text_type, args)) @@ -991,7 +1019,7 @@ class TaskWarrior(object): return config - def execute_command(self, args, config_override={}, allow_failure=True, + def execute_command(self, args, config_override=None, allow_failure=True, return_all=False): command_args = self._get_command_args( args, config_override=config_override)