/usr/lib/python3/dist-packages/taskw/task.py is in python3-taskw 1.2.0-1.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 | import logging
import six
from taskw.fields import (
AnnotationArrayField,
ArrayField,
ChoiceField,
CommaSeparatedUUIDField,
DateField,
DurationField,
Field,
NumericField,
StringField,
UUIDField,
)
from taskw.fields.base import Dirtyable, DirtyableList, DirtyableDict
# Sentinel value for not specifying a default
UNSPECIFIED = object()
logger = logging.getLogger(__name__)
class Task(dict):
FIELDS = {
'annotations': AnnotationArrayField(label='Annotations'),
'depends': CommaSeparatedUUIDField(label='Depends Upon'),
'description': StringField(label='Description'),
'due': DateField(label='Due'),
'end': DateField(label='Ended'),
'entry': DateField(label='Entered'),
'id': NumericField(label='ID', read_only=True),
'imask': NumericField(label='Imask', read_only=True),
'mask': StringField(label='Mask', read_only=True),
'modified': DateField(label='Modified'),
'parent': StringField(label='Parent'),
'priority': ChoiceField(
choices=[None, 'H', 'M', 'L', ],
case_sensitive=False,
label='Priority'
),
'project': StringField(label='Project'),
'recur': DurationField(label='Recurrence'),
'scheduled': DateField(label='Scheduled'),
'start': DateField(label='Started'),
'status': ChoiceField(
choices=[
'pending',
'completed',
'deleted',
'waiting',
'recurring',
],
case_sensitive=False,
label='Status',
),
'tags': ArrayField(label='Tags'),
'until': DateField(label='Until'),
'urgency': NumericField(label='Urgency', read_only=True),
'uuid': UUIDField(label='UUID'),
'wait': DateField(label='Wait'),
}
def __init__(self, data, udas=None):
udas = udas or {}
self._fields = self.FIELDS.copy()
self._fields.update(udas)
self._changes = []
processed = {}
for k, v in six.iteritems(data):
processed[k] = self._deserialize(k, v, self._fields)
super(Task, self).__init__(processed)
@classmethod
def from_stub(cls, data, udas=None):
""" Create a Task from an already deserialized dict. """
udas = udas or {}
fields = cls.FIELDS.copy()
fields.update(udas)
processed = {}
for k, v in six.iteritems(data):
processed[k] = cls._serialize(k, v, fields)
return cls(processed, udas)
@classmethod
def _get_converter_for_field(cls, field, default=None, fields=None):
fields = fields or {}
converter = fields.get(field, None)
if not converter:
return default if default else Field()
return converter
@classmethod
def _deserialize(cls, key, value, fields):
""" Marshal incoming data into Python objects."""
converter = cls._get_converter_for_field(key, None, fields)
return converter.deserialize(value)
@classmethod
def _serialize(cls, key, value, fields):
""" Marshal outgoing data into Taskwarrior's JSON format."""
converter = cls._get_converter_for_field(key, None, fields)
return converter.serialize(value)
def _field_is_writable(self, key):
converter = self._get_converter_for_field(key, fields=self._fields)
if converter.read_only:
return False
return True
def get(self, key, default=None):
try:
return self[key]
except KeyError:
return default
def _record_change(self, key, from_, to):
self._changes.append((key, from_, to))
def get_changes(self, serialized=False, keep=False):
""" Get a journal of changes that have occurred
:param `serialized`:
Return changes in the serialized format used by TaskWarrior.
:param `keep_changes`:
By default, the list of changes is reset after running
``.get_changes``; set this to `True` if you would like to
keep the changes recorded following running this command.
:returns: A dictionary of 2-tuples of changes, where the key is the
name of the field that has changed, and the value is a 2-tuple
containing the original value and the final value respectively.
"""
results = {}
# Check for explicitly-registered changes
for k, f, t in self._changes:
if k not in results:
results[k] = [f, None]
results[k][1] = (
self._serialize(k, t, self._fields)
if serialized else t
)
# Check for changes on subordinate items
for k, v in six.iteritems(self):
if isinstance(v, Dirtyable):
result = v.get_changes(keep=keep)
if result:
if not k in results:
results[k] = [result[0], None]
results[k][1] = (
self._serialize(k, result[1], self._fields)
if serialized else result[1]
)
# Clear out recorded changes
if not keep:
self._changes = []
return results
def update(self, values, force=False):
""" Update this task dictionary
:returns: A dictionary mapping field names specified to be updated
and a boolean value indicating whether the field was changed.
"""
results = {}
for k, v in six.iteritems(values):
results[k] = self.__setitem__(k, v, force=force)
return results
def set(self, key, value):
""" Set a key's value regardless of whether a change is seen."""
return self.__setitem__(key, value, force=True)
def serialized(self):
""" Returns a serialized representation of this task."""
serialized = {}
for k, v in six.iteritems(self):
serialized[k] = self._serialize(k, v, self._fields)
return serialized
def serialized_changes(self, keep=False):
serialized = {}
for k, v in six.iteritems(self.get_changes(keep=keep)):
# Here, `v` is a 2-tuple of the field's original value
# and the field's new value.
_, to = v
serialized[k] = self._serialize(k, to, self._fields)
return serialized
def __setitem__(self, key, value, force=False):
if isinstance(value, dict) and not isinstance(value, DirtyableDict):
value = DirtyableDict(value)
elif isinstance(value, list) and not isinstance(value, DirtyableList):
value = DirtyableList(value)
existing_value = self.get(key)
if force or value != existing_value:
if force or existing_value or value:
# Do not attempt to record changes if both the existing
# and previous values are Falsy. We cannot distinguish
# between `''` and `None` for...reasons.
self._record_change(
key,
self.get(key),
value,
)
# Serialize just to make sure we can; it's better to throw
# this error early.
self._serialize(key, value, self._fields)
# Also make sure we raise an error if this field isn't
# writable at all.
if not self._field_is_writable(key):
raise ValueError("%s is a read-only field", key)
super(Task, self).__setitem__(key, value)
return True
return False
|