Giter Club home page Giter Club logo

taskw's Introduction

taskw - Python API for the taskwarrior DB

This is a python API for the taskwarrior command line tool.

It contains two implementations: taskw.TaskWarriorShellout and taskw.TaskWarriorDirect. The first implementation is the supported one recommended by the upstream taskwarrior core project. It uses the task export and task import commands to manipulate the task database. The second implementation opens the task db file itself and directly manipulates it. It exists for backwards compatibility, but should only be used when necessary.

Build Status

Branch Status
master Build Status - master branch
develop Build Status - develop branch

Getting taskw

Installing

Using taskw requires that you first install taskwarrior.

Installing it from http://pypi.python.org/pypi/taskw is easy with pip:

$ pip install taskw

The Source

You can find the source on github at http://github.com/ralphbean/taskw

Examples

Looking at tasks

>>> from taskw import TaskWarrior
>>> w = TaskWarrior()
>>> tasks = w.load_tasks()
>>> tasks.keys()
['completed', 'pending']
>>> type(tasks['pending'])
<type 'list'>
>>> type(tasks['pending'][0])
<type 'dict'>

Adding tasks

>>> from taskw import TaskWarrior
>>> w = TaskWarrior()
>>> w.task_add("Eat food")
>>> w.task_add("Take a nap", priority="H", project="life", due="1359090000")

Retrieving tasks

>>> from taskw import TaskWarrior
>>> w = TaskWarrior()
>>> w.get_task(id=5)

Updating tasks

>>> from taskw import TaskWarrior
>>> w = TaskWarrior()
>>> id, task = w.get_task(id=14)
>>> task['project'] = 'Updated project name'
>>> w.task_update(task)

Deleting tasks

>>> from taskw import TaskWarrior
>>> w = TaskWarrior()
>>> w.task_delete(id=3)

Completing tasks

>>> from taskw import TaskWarrior
>>> w = TaskWarrior()
>>> w.task_done(id=46)

Being Flexible

You can point taskw at different taskwarrior databases.

>>> from taskw import TaskWarrior
>>> w = TaskWarrior(config_filename="~/some_project/.taskrc")
>>> w.task_add("Use 'taskw'.")

Looking at the config

>>> from taskw import TaskWarrior
>>> w = TaskWarrior()
>>> config = w.load_config()
>>> config['data']['location']
'/home/threebean/.task'
>>> config['_forcecolor']
'yes'

Using Python-appropriate Types (Dates, UUIDs, etc)

>>> from taskw import TaskWarrior
>>> w = TaskWarrior(marshal=True)
>>> w.get_task(id=10)
(10,
 {
  'description': 'Hello there!',
  'entry': datetime.datetime(2014, 3, 14, 14, 18, 40, tzinfo=tzutc())
  'id': 10,
  'project': 'Saying Hello',
  'status': 'pending',
  'uuid': UUID('4882751a-3966-4439-9675-948b1152895c')
 }
)

taskw's People

Contributors

bergercookie avatar burnison avatar campbellr avatar coddingtonbear avatar countermeasure avatar dev-zero avatar djmitche avatar edwardbetts avatar eumiro avatar fdev31 avatar gdetrez avatar jayvdb avatar khaeru avatar kostajh avatar matt-snider avatar neingeist avatar nikoskoukis-slamcore avatar ralphbean avatar rossdylan avatar ryansb avatar ryneeverett avatar tbabej avatar tychoish avatar umonkey avatar vrusinov avatar yonk42 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

taskw's Issues

TaskWarrior.load_config() ignores TASKRC environment variable

I have set the TASKRC environment variable to set a custom location for the taskwarrior configuration file:

$ env | grep TASK
TASKRC=/home/khaeru/vc/dotfiles/taskwarrior

I installed the taskwarrior-time-tracking-hook by @kostajh, but it fails because taskw does not check this environment variable and instead looks for the configuration file at ~/.taskrc:

$ task 43 stop
Traceback (most recent call last):
  File "/home/khaeru/.local/share/taskwarrior/hooks/on-modify.timetracking", line 9, in <module>
    load_entry_point('taskwarrior-time-tracking-hook==0.1.4', 'console_scripts', 'taskwarrior_time_tracking_hook')()
  File "/usr/lib/python3/dist-packages/pkg_resources.py", line 356, in load_entry_point
    return get_distribution(dist).load_entry_point(group, name)
  File "/usr/lib/python3/dist-packages/pkg_resources.py", line 2476, in load_entry_point
    return ep.load()
  File "/usr/lib/python3/dist-packages/pkg_resources.py", line 2190, in load
    ['__name__'])
  File "/home/khaeru/vc/other/taskwarrior-time-tracking-hook/taskwarrior_time_tracking_hook/__init__.py", line 11, in <module>
    w = TaskWarrior()
  File "/home/khaeru/.local/lib/python3.4/site-packages/taskw/warrior.py", line 453, in __init__
    super(TaskWarriorShellout, self).__init__(config_filename)
  File "/home/khaeru/.local/lib/python3.4/site-packages/taskw/warrior.py", line 54, in __init__
    self.config = TaskWarriorBase.load_config(config_filename)
  File "/home/khaeru/.local/lib/python3.4/site-packages/taskw/warrior.py", line 173, in load_config
    with open(os.path.expanduser(config_filename), 'r') as f:
  File "/home/khaeru/.local/lib/python3.4/site-packages/taskw/warrior.py", line 37, in <lambda>
    open = lambda fname, mode: codecs.open(fname, mode, "utf-8")
  File "/usr/lib/python3.4/codecs.py", line 896, in open
    file = builtins.open(filename, mode, buffering)
FileNotFoundError: [Errno 2] No such file or directory: '/home/khaeru/.taskrc'
Hook Error: Expected feedback from a failing hook script.

taskw in marshaling mode doesn't work with pickle

I'm trying the following sample:

import pickle
from taskw import TaskWarrior

tw = TaskWarrior(marshal=True)
t = tw.get_task(id=...)[-1]
pickle.dump(t, open('f', 'wb'))
pickle.load(open('f', 'rb')

And I'm getting the following error:

In [33]: pickle.load(open('f', 'rb'))
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-33-99235821b593> in <module>
----> 1 pickle.load(open('f', 'rb'))
~/.local/lib/python3.6/site-packages/taskw/task.py in __setitem__(self, key, value, force)
    216                     key,
    217                     self.get(key),
--> 218                     value,
    219                 )
    220
~/.local/lib/python3.6/site-packages/taskw/task.py in _record_change(self, key, from_, to)
    123
    124     def _record_change(self, key, from_, to):
--> 125         self._changes.append((key, from_, to))
    126
    127     def get_changes(self, serialized=False, keep=False):
AttributeError: 'Task' object has no attribute '_changes

If I explicitly set the protocol for pickle.dump to 0 or not use the marshal mode, it works fine.

Unittest test_datas.TestDBShellout.test_add_datetime fails with task-2.3.0

with python 2.7 or python 3.3 and task 2.3.0 I get the following:

..............................F.F...............................................................................................
======================================================================
FAIL: taskw.test.test_datas.TestDBShellout.test_add_datetime
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/usr/lib64/python2.7/site-packages/nose/case.py", line 197, in runTest
    self.test(*self.arg)
  File "/home/tiziano/work/gentoo/scratch/taskw/taskw/test/test_datas.py", line 192, in test_add_datetime
    assert(tasks['pending'][0]['entry'].startswith("20110101T"))
AssertionError

======================================================================
FAIL: taskw.test.test_datas.TestDBShellout.test_add_with_uda_date
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/usr/lib64/python2.7/site-packages/nose/case.py", line 197, in runTest
    self.test(*self.arg)
  File "/home/tiziano/work/gentoo/scratch/taskw/taskw/test/test_datas.py", line 214, in test_add_with_uda_date
    assert(task['somedate'].startswith("20110101T"))
AssertionError

----------------------------------------------------------------------
Ran 128 tests in 4.036s

FAILED (failures=2)

Unable to update tasks

I have downloaded taskw through pip and my tasks are loaded normally. I am able to delete them, but I am unable to update their description through the update_task method. It incurs in the same error as #125 and I believe the two might be related.

The full output is below:

TaskwarriorError                        Traceback (most recent call last)
<ipython-input-13-e3038853540e> in <module>
----> 1 w.task_update(t)

~/.local/lib/python3.9/site-packages/taskw/warrior.py in task_update(self, task)
    804         # (changes *might* just be in annotations).
    805         if modification:
--> 806             self._execute(task_uuid, 'modify', *modification)
    807 
    808         # If there are no existing annotations, add the new ones

~/.local/lib/python3.9/site-packages/taskw/warrior.py in _execute(self, *args)
    482 
    483         if proc.returncode != 0:
--> 484             raise TaskwarriorError(command, stderr, stdout, proc.returncode)
    485 
    486         # We should get bytes from the outside world.  Turn those into unicode

<class 'str'>: (<class 'TypeError'>, TypeError('__str__ returned non-string (type bytes)'))

I don't quite understand if this error is supposed to be fixed or not. #126 seems to have passed, but has it actually been committed?

Documentation references missing taskw.load_tasks()

In a fresh virtualenv, performed a "pip install taskw". Then at a python prompt issued "from taskw import load_tasks()" as detailed in the README.rst here on GitHub. Importing the module itself with "import taskw" works fine. Issuing dir(taskw) produces

from taskw import load_tasks
Traceback (most recent call last):
File "", line 1, in
ImportError: cannot import name load_tasks
import taskw
dir(taskw)
['TaskWarrior', 'all', 'builtins', 'doc', 'file', 'name', 'package', 'path', 'clean_task', 'decode_task', 'encode_task', 'utils', 'warrior']

Test failures using taskwarrior 2.4.4

======================================================================
FAIL: taskw.test.test_datas.TestDBShellout.test_filtering_brace
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/nix/store/ybasldb42n05g7b7jsxz2f40jy3bwyj3-python2.7-nose-1.3.4/lib/python2.7/site-packages/nose/case.py", line 197, in runTest
    self.test(*self.arg)
  File "/tmp/nix-build-python2.7-taskw-1.0.3.drv-0/taskw-1.0.3/taskw/test/test_datas.py", line 374, in test_filtering_brace
    eq_(len(tasks), 1)
AssertionError: 2 != 1

======================================================================
FAIL: taskw.test.test_datas.TestDBShellout.test_filtering_plus
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/nix/store/ybasldb42n05g7b7jsxz2f40jy3bwyj3-python2.7-nose-1.3.4/lib/python2.7/site-packages/nose/case.py", line 197, in runTest
    self.test(*self.arg)
  File "/tmp/nix-build-python2.7-taskw-1.0.3.drv-0/taskw-1.0.3/taskw/test/test_datas.py", line 393, in test_filtering_plus
    eq_(len(tasks), 1)
AssertionError: 3 != 1

======================================================================
FAIL: taskw.test.test_datas.TestDBShellout.test_filtering_qmark
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/nix/store/ybasldb42n05g7b7jsxz2f40jy3bwyj3-python2.7-nose-1.3.4/lib/python2.7/site-packages/nose/case.py", line 197, in runTest
    self.test(*self.arg)
  File "/tmp/nix-build-python2.7-taskw-1.0.3.drv-0/taskw-1.0.3/taskw/test/test_datas.py", line 423, in test_filtering_qmark
    eq_(tasks[0]['id'], 2)
AssertionError: 1 != 2

======================================================================
FAIL: taskw.test.test_datas.TestDBShellout.test_filtering_question_mark
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/nix/store/ybasldb42n05g7b7jsxz2f40jy3bwyj3-python2.7-nose-1.3.4/lib/python2.7/site-packages/nose/case.py", line 197, in runTest
    self.test(*self.arg)
  File "/tmp/nix-build-python2.7-taskw-1.0.3.drv-0/taskw-1.0.3/taskw/test/test_datas.py", line 451, in test_filtering_question_mark
    eq_(len(tasks), 1)
AssertionError: 2 != 1

fail to create new TaskWarrior object

Just tried the example code and get a traceback:

Traceback (most recent call last):
  File "tw.py", line 4, in <module>
    w = TaskWarrior()
  File "/usr/local/lib/python2.7/dist-packages/taskw/warrior.py", line 444, in __init__
    self.config = TaskRc(config_filename, overrides=config_overrides)
  File "/usr/local/lib/python2.7/dist-packages/taskw/taskrc.py", line 39, in __init__
    config = self._read(self.path)
  File "/usr/local/lib/python2.7/dist-packages/taskw/taskrc.py", line 89, in _read
    TaskRc(right.strip())
  File "/usr/local/lib/python2.7/dist-packages/taskw/taskrc.py", line 71, in _merge_trees
    left[key] = self._merge_trees(left.get(key), value)
  File "/usr/local/lib/python2.7/dist-packages/taskw/taskrc.py", line 73, in _merge_trees
    left[key] = value
TypeError: 'str' object does not support item assignment

Test issue

Just testing. Apologies for the spam.

Filtering on a string containing ? doesn't work

This works fine:

task rc:/home/dustin/.taskrc rc.json.array=TRUE rc.confirmation=no rc.verbose=nothing export '(bugzillaurl:"https://bugzilla.mozilla.org/show_bug.cgi?id=1066854")'

But taskw generates

task rc:/home/dustin/.taskrc rc.json.array=TRUE rc.confirmation=no rc.verbose=nothing export '(bugzillaurl:"https://bugzilla.mozilla.org/show_bug.cgi\?id=1066854")'

instead, which doesn't. Note the backslashed ?.

Fetching a (periodic) task fails - Unable to find report that matches

I'm trying to fetch the contents of the following task:

ID              463
Description     Glass of lemon
Status          Pending
Recurrence      daily
Parent task     f2a34eee-9239-4d4a-9fc4-c3574abc9903
Mask Index      240
Recurrence type periodic
Entered         2021-11-29 08:22:46 (1d)
Due             2021-11-30 02:45:00
Last modified   2021-11-30 09:10:17 (12h)
Tags            remindme routine
Virtual tags    CHILD DUE DUETODAY INSTANCE MONTH OVERDUE PENDING QUARTER READY TAGGED TODAY UNBLOCKED WEEK YEAR
UUID            5fac74b4-dce7-4f4c-aa58-ce1f445269db
Urgency         5.071

    tags           0.9 *    1 =    0.9
    due          0.764 *   12 =   9.17
    age          0.003 *    2 =  0.005
    TAG routine      1 *   -5 =     -5
                                ------
                                 5.071

Date                Modification
2021-11-30 09:10:17 Due changed from '2021-11-30 02:45:03' to '2021-11-30 02:45:00'.

Doing so from taskw fails:

tw.get_task(uuid=UUID('5fac74b4-dce7-4f4c-aa58-ce1f445269db'))

~/.local/lib/python3.8/site-packages/taskw/warrior.py in _execute(self, *args)
    475
    476         if proc.returncode != 0:
--> 477             raise TaskwarriorError(command, stderr, stdout, proc.returncode)
    478
    479         # We should get bytes from the outside world.  Turn those into unicode

<class 'str'>: (<class 'TypeError'>, TypeError('__str__ returned non-string (type bytes)'))

it fails because it eventually tries to run the following command:

task rc.json.array=TRUE rc.verbose=nothing rc.confirmation=no rc.dependency.confirmation=no rc.recurrence.
confirmation=no  export 5fac74b4-dce7-4f4c-aa58-ce1f445269db

which fails with

Unable to find report that matches '5fac74b4-dce7-4f4c-aa58-ce1f445269db'.

If the UUID on the other hand is put right after task then it actually exports the contents of the task as expected.

  • TW Version: 2.6.1
  • I'm using version 1.3.0 of taskw.

Not sure if this is an issue with taskw or taskwarrior itself. Any pointers how can I solve it?
Why e.g. is taskw not assembling the get-task command with the task UUID always as the second argument?

Thanks

`task_add` raises an error, but creates the task

When trying to create a new task through taskw, an error is raised:

>>> from taskw import TaskWarrior
>>> w = TaskWarrior()
>>> w.task_add('test')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/myuser/.local/lib/python3.9/site-packages/taskw/warrior.py", line 697, in task_add
    task['uuid'] = re.search(UUID_REGEX, stdout).group(0)
AttributeError: 'NoneType' object has no attribute 'group'

It's not a breaking bug, though, the task is created just fine:

$ task list

ID Age Description Urg
 1 5s  test          0

The error is raised within the TaskWarriorShellout class, where stdout coming from TaskWarriorShellout._execute('add', *args) returns empty.

While the error seems to be a minor nuisance for the user when observed in isolation, this becomes a problem when paired with other packages that are built on top of taskw, like bugwarrior, because the worker stops after meeting the error and thus bugwarrior only pulls a single issue at a time.


I think this is a specific issue with either Python's or the dependecies' versions, as I ran taskw with errors in both Arch Linux and Manjaro, but Linux Mint worked without problems:

Package Arch-based Linux Mint
Python 3.9.2 3.8.5
six 1.15.0 1.14.0
python-dateutil 2.8.1 2.7.3
pytz 2021.1 2019.3
kitchen 1.2.6 1.2.6

If there is anything I can contribute with a pull request, please let me know.

get_task() only retrieves single task instead of all matches

I want to write a script that retrieves all tasks matching certain criteria (e.g. all tasks scheduled for a certain day). However, get_task() only seems to return the first matching task and ignores other matches. Is this by design? The name seems to imply so. Should I implement my own method to achieve what I want?

Python 3: str(TaskwarriorError(...)): TypeError: __str__ returned non-string (type bytes)

Python 3.6.8 (default, Jan 14 2019, 11:02:34) 
[GCC 8.0.1 20180414 (experimental) [trunk revision 259383]] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from taskw.exceptions import TaskwarriorError
>>> str(TaskwarriorError("X", "Y", "Z", 1))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: __str__ returned non-string (type bytes)
pip3 freeze | grep taskw==
taskw==1.2.0

task_add returns wrong task

Steps to reproduce:

$ task add test1
Created task 51.

Then:

>>> w.task_add('test')
{'description': 'test1',
 'entry': '20140301T111849Z',
 'id': 51,
 'status': 'pending',
 'urgency': '0',
 'uuid': '28c50cee-8fe8-427f-aa1c-f5aacc46fc06'}

Why don't you parse "Created task #" line?

Python 3k Test Failures

======================================================================
ERROR: taskw.test.test_datas.TestDBShellout.test_annotation_escaping
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/travis/virtualenv/python3.3.5/lib/python3.3/site-packages/nose/case.py", line 198, in runTest
    self.test(*self.arg)
  File "/home/travis/build/ralphbean/taskw/taskw/test/test_datas.py", line 504, in test_annotation_escaping
    self.tw.task_update(task)
  File "/home/travis/build/ralphbean/taskw/taskw/warrior.py", line 752, in task_update
    self._extract_annotations_from_task(task_to_modify)
  File "/home/travis/build/ralphbean/taskw/taskw/warrior.py", line 100, in _extract_annotations_from_task
    for key in task.keys():
RuntimeError: dictionary changed size during iteration
----------------------------------------------------------------------
Ran 139 tests in 9.863s

https://travis-ci.org/ralphbean/taskw/jobs/47386507

Task objects for dependencies instead of UUID

Hi guys,

A proposal for a future version as it breaks backwards compatibility...


Would it be possible to have taskw.task.Task objects for ["depends"] instead of raw UUIDs?

The container (currently taskw.fields.base.DirtyableList) could lazily evaluate the contained UUID(s) and obtain the task when items are retrieved.

This should make it easier to traverse long dependency chains:

# Given a task `a` that depends on `b` which in turn depends on `c`, one could do:
c = a["depends"][0]["depends"][0]
# or:
b = a["depends"][0]
c = b["depends"][0]

# currently we need to do:
c = w.get_task(uuid=w.get_task(uuid=a["depends"][0])[1]["depends"][0])[0]
# or
b = w.get_task(uuid=a["depends"][0])[1]
c = w.get_task(uuid=b["depends"][0])[1]

Cryptic error if task is not in PATH

INFO:bugwarrior|Service-defined UDAs exist: you can optionally use the `bugwarrior-uda` command to export a list of UDAs you can add to your ~/.taskrc file.
CRITICAL:command|oh noes
TRACE Traceback (most recent call last):
TRACE   File "/home/meka/.virtualenvs/v/local/lib/python2.7/site-packages/bugwarrior/command.py", line 62, in pull
TRACE     synchronize(issue_generator, config, main_section, dry_run)
TRACE   File "/home/meka/.virtualenvs/v/local/lib/python2.7/site-packages/bugwarrior/db.py", line 323, in synchronize
TRACE     marshal=True,
TRACE   File "/home/meka/.virtualenvs/v/local/lib/python2.7/site-packages/taskw/warrior.py", line 458, in __init__
TRACE     if self.get_version() >= LooseVersion('2.4'):
TRACE   File "/home/meka/.virtualenvs/v/local/lib/python2.7/site-packages/taskw/warrior.py", line 552, in get_version
TRACE     stdout=subprocess.PIPE
TRACE   File "/usr/lib/python2.7/subprocess.py", line 710, in __init__
TRACE     errread, errwrite)
TRACE   File "/usr/lib/python2.7/subprocess.py", line 1335, in _execute_child
TRACE     raise child_exception
TRACE OSError: [Errno 2] No such file or directory

The error is that task executable can not be found. More descriptive error would be nice.

fails when 1 task in pending and 1 in complete

failed here
https://github.com/ralphbean/taskw/blob/develop/taskw/warrior.py#L224

TRACE Traceback (most recent call last):
TRACE File "/Users/orzech/Library/Python/2.7/lib/python/site-packages/bugwarrior/command.py", line 17, in pull
TRACE synchronize(issues, config)
TRACE File "/Users/orzech/Library/Python/2.7/lib/python/site-packages/bugwarrior/db.py", line 74, in synchronize
TRACE id, task = tw.get_task(description=upstream_issue['description'])
TRACE File "/Users/orzech/Library/Python/2.7/lib/python/site-packages/taskw/warrior.py", line 183, in get_task
TRACE line, task = self._load_task(**kw)
TRACE File "/Users/orzech/Library/Python/2.7/lib/python/site-packages/taskw/warrior.py", line 224, in _load_task
TRACE line = tasks[_TaskStatus.to_file(task['status'])].index(task) + 1
TRACE ValueError: {u'status': u'completed', u'project': u'googlecl', u'end': u'1383786165', u'description': u'(bw)Is#1 - test1 .. groundnuty/googlecl#1', u'modified': u'1383786165', u'priority': u'H', u'entry': u'1383786049', u'uuid': u'9e64adf5-f2e2-44a7-bfb8-85ab5ff054a9'} is not in list

I had 1 task in pending and 1 in complete
when I added another task to pending, all went well.

load_config() does not allow a key to have multiple keys underneath it in the returned dictionary

The function load_config does not correctly parse the config file because it does not create a dictionary that can have more than one child key under a parent. It is easier to explain with an example.

For instance, I have multiple UDA's which are:

uda.timelog.type=string
uda.timelog.label=Logged Time
uda.worklog.type=string
uda.worklog.label=Work Log

When load_config is called, I only get this in the dictionary:

{'uda': {'worklog': {'label': 'Work Log'}

because everything under uda is replaced by the next line that is parsed, so the only line that stays from this is uda.worklog.label=Work Log.

What is really should look like is :

'uda': {'timelog': {'type': 'string', 'label': 'Logged Time'}, 'worklog': {'type': 'string', 'label': 'Work Log'}}

I just wanted you to know, here is how I fixed it:

def load_config(self, config_filename="~/.taskrc"):
    with open(os.path.expanduser(config_filename), 'r') as f:
        lines = f.readlines()

    _usable = lambda l: not(l.startswith('#') or l.strip() == '')
    lines = filter(_usable, lines)

    def _build_config(pieces, value, d):
        """ Called recursively to split up keys """
        if len(pieces) == 1:
            d[pieces[0]] = value.strip()
        else:
            if (pieces[0] not in d):
              d[pieces[0]] = {}
            _build_config(pieces[1:], value, d[pieces[0]]) 

    d = {}
    for line in lines:
        if '=' not in line:
            continue

        key, value = line.split('=', 1)
        pieces = key.split('.')
        _build_config(pieces, value, d)

    # Set a default data location if one is not specified.
    if d.get('data') is None:
        d['data'] = {}

    if d['data'].get('location') is None:
        d['data']['location'] = os.path.expanduser("~/.task/")

    return d

filter_tasks with multiple uuids

Looking through the code, it seems that filter_tasks is what is needed if I want to get back a list of tasks, but if I want to search for multiple uuids, there seems to be a problem.

The following two code samples are what I've tried, which both fail.

Attempt with a list of strings
#!/usr/bin/env python3

from taskw import TaskWarrior
from pprint import pprint

w = TaskWarrior()

uuids = set()
uuids.add('40f4028a-7771-4919-88cb-cfc503cd7fdc')
uuids.add('4c8f62e6-9ffc-439f-a3a6-1e85caa2c77e')

_filter = dict()
_filter['uuid'] = list(uuids)

tasks = w.filter_tasks(_filter)
pprint(tasks)
Attempt with a list of dicts
#!/usr/bin/env python3

from taskw import TaskWarrior
from pprint import pprint

w = TaskWarrior()

uuids = list()

uuid = dict()
uuid['uuid'] = '40f4028a-7771-4919-88cb-cfc503cd7fdc'
uuids.append(uuid)

uuid = dict()
uuid['uuid'] = '4c8f62e6-9ffc-439f-a3a6-1e85caa2c77e'
uuids.append(uuid)

_filter = dict()
_filter['uuid'] = uuids

tasks = w.filter_tasks(_filter)
pprint(tasks)

This sample (searching for a single result) succeeds, as expected.

Attempt with a single string - this works
#!/usr/bin/env python3

from taskw import TaskWarrior
from pprint import pprint

w = TaskWarrior()

_filter = dict()
_filter['uuid'] = '40f4028a-7771-4919-88cb-cfc503cd7fdc'

tasks = w.filter_tasks(_filter)
pprint(tasks)

I'm not sure what I might be doing wrong; debugging in utils.py:encode_query suggests that encode_query([item], version, query=False)[0] for item in v might be the culprit?

taskrc includes as relative paths cause FileNotFoundError in taskw

TaskWarrior config files can have include directives which are specified as paths relative to the location of the rcfile. taskw library always tries to load the path as-is without searching for it using the algorithm that TaskWarrior does. This means it can only work with taskw if it is specified as absolute, or, if relative, the program happens to have its cwd in the .taskrc location.

Example .taskrc:

...
include .taskrc.private

this will crash with FileNotFoundError exception in taskw:

>>> from taskw import TaskWarrior
>>> tw = TaskWarrior()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/scott/.local/lib/python3.9/site-packages/taskw/warrior.py", line 434, in __init__
    super(TaskWarriorShellout, self).__init__(config_filename)
  File "/home/scott/.local/lib/python3.9/site-packages/taskw/warrior.py", line 61, in __init__
    self.config = TaskWarriorBase.load_config(config_filename)
  File "/home/scott/.local/lib/python3.9/site-packages/taskw/warrior.py", line 179, in load_config
    return TaskRc(config_filename, overrides=overrides)
  File "/home/scott/.local/lib/python3.9/site-packages/taskw/taskrc.py", line 58, in __init__
    config = self._read(self.path)
  File "/home/scott/.local/lib/python3.9/site-packages/taskw/taskrc.py", line 106, in _read
    TaskRc(right.strip())
  File "/home/scott/.local/lib/python3.9/site-packages/taskw/taskrc.py", line 58, in __init__
    config = self._read(self.path)
  File "/home/scott/.local/lib/python3.9/site-packages/taskw/taskrc.py", line 96, in _read
    with codecs.open(path, 'r', 'utf8') as config_file:
  File "/usr/local/bin/../../../opt/python-3.9.5/lib/python3.9/codecs.py", line 905, in open
    file = builtins.open(filename, mode, buffering)
FileNotFoundError: [Errno 2] No such file or directory: '.taskrc.private'

Numeric marshaled values are rounded to the nearest integer

Today while triaging bug tickets on Inthe.AM, I found coddingtonbear/inthe.am#269 describing a problem in which tasks were mis-ordered because the urgency value returned by the API was unexpectedly rounded. A little digging revealed that this is actually some kind of problem in Taskw's marshalling:

Unmarshalled:

>>> tws = TaskWarriorShellout('./.taskrc', marshal=False)
>>> tws.filter_tasks({'description': 'Double'})[0]
{'id': 15, 'description': 'Double check that AIRSHIP-4Z1 fix worked: https://github.com/urbanairship/airship/pull/8526 worked?', 'entry': '20190419T173354Z', 'intheamtrelloboardid': '57fc50d53515d1ba1abe38a3', 'intheamtrelloid': '5cba067d2b11711351568815', 'intheamtrellolistid': '57fc51aa7b70585afcb3da79', 'intheamtrellolistname': 'This Week', 'intheamtrellourl': 'https://trello.com/c/QSTpkMCS/2220-double-check-that-airship-4z1-fix-worked-https-githubcom-urbanairship-airship-pull-8526-worked', 'modified': '20190419T173948Z', 'status': 'pending', 'tags': ['sky', 'work'], 'uuid': '54f30f75-1bbb-4023-9405-44383939f99c', 'urgency': 3.15}

Marshalled:

>>> tws = TaskWarriorShellout('./.taskrc', marshal=True)
>>> tws.filter_tasks({'description': 'Double'})[0]
{'id': 15, 'description': 'Double check that AIRSHIP-4Z1 fix worked: https://github.com/urbanairship/airship/pull/8526 worked?', 'entry': datetime.datetime(2019, 4, 19, 17, 33, 54, tzinfo=tzlocal()), 'intheamtrelloboardid': '57fc50d53515d1ba1abe38a3', 'intheamtrelloid': '5cba067d2b11711351568815', 'intheamtrellolistid': '57fc51aa7b70585afcb3da79', 'intheamtrellolistname': 'This Week', 'intheamtrellourl': 'https://trello.com/c/QSTpkMCS/2220-double-check-that-airship-4z1-fix-worked-https-githubcom-urbanairship-airship-pull-8526-worked', 'modified': datetime.datetime(2019, 4, 19, 17, 39, 48, tzinfo=tzlocal()), 'status': 'pending', 'tags': ['sky', 'work'], 'uuid': UUID('54f30f75-1bbb-4023-9405-44383939f99c'), 'urgency': 3}

This is undoubtedly somehow my fault, of course :-), whether this is filed under Inthe.AM or not.

Make TaskwarriorExperimental the default

Is there a timeline for this? I recently tested a program that used taskw but relied on the method for directly reading task data, which failed. IMO a more stable implementation would be to rely on task export/add/modify/delete for viewing and manipulating task data, as TaskwarriorExperimental does.

_load_task() assumes TaskWarriorExperimental is not used

As a result, anything using TaskWarriorExperimental() breaks.

I think the problem is here:

# If the key is an id, assume the task is pending (completed tasks don't have IDs).
if key == 'id':
tasks = self.load_tasks(command=_TaskStatus.PENDING)
line = kw[key]

    if len(tasks[_TaskStatus.PENDING]) >= line:
    task = tasks[_TaskStatus.PENDING][line - 1]

tasks is a JSON dictionary when using TaskWarriorExperimental, so the code above fails.

I'm not sure how you'd like to handle this. Should TaskWarriorExperimental have its own _load_task() method?

taskw has different escape style then taskwarrior itself

Creating a task using the command line

task add 'my new "task"'

results in a task with double quotes internally escaped with \". Loading the task with taskw and updating it results in the description being re-escaped with &dquot; which is hard to read e.g. in vit.

>>> import taskw
>>> tw = taskw.TaskWarriorShellout()
>>> _, task = tw.get_task(description='my new "task"')
>>> tw.task_update(task)

which gives

'description': 'my new &dquot;task&dquot;'

I would expect (A) taskw not to replace the quotes and (B) not to perform any update since the task was not changed at all.

A quick workaround is to append ('&dquot;', '\"') at the end of taskw.utils.encode_replacements_experimental. That way escaped and not escaped double quotes are temporarily replaced with the HTML entities and then replaced with the escaped version of the double quote. But I guess that replacing the double quotes back and forth is not the preferred solution.

Another solution is to remove replacements related to double quotes altogether. However, I was wondering what was the reason to introduce that in the first instance.

add_task fails if 'project' value contains "project" and a number

Very race condition.

tw.task_add(description='test1', project='life')
tw.task_add(description='test1', project='life1')
tw.task_add(description='test1', project='life_1')

works fine, while all the following give me a TaskwarriorError exception:

tw.task_add(description='test1', project='project1')
tw.task_add(description='test1', project='project_1')
tw.task_add(description='test1', project='project_2')

issue adding tasks "UUID has no attribute group"

Updating my local workflow, I've been setting up bugwarrior which uses taskw as a dependency.

Python version: 2.7.18
Task version 2.5.4
taskw version 1.3.0

Traceback (most recent call last):
  File "/usr/local/bin/bugwarrior-pull", line 11, in <module>
    sys.exit(pull())
  File "/Users/jwarren/.asdf/installs/python/2.7.18/lib/python2.7/site-packages/click/core.py", line 829, in __call__
    return self.main(*args, **kwargs)
  File "/Users/jwarren/.asdf/installs/python/2.7.18/lib/python2.7/site-packages/click/core.py", line 782, in main
    rv = self.invoke(ctx)
  File "/Users/jwarren/.asdf/installs/python/2.7.18/lib/python2.7/site-packages/click/core.py", line 1066, in invoke
    return ctx.invoke(self.callback, **ctx.params)
  File "/Users/jwarren/.asdf/installs/python/2.7.18/lib/python2.7/site-packages/click/core.py", line 610, in invoke
    return callback(*args, **kwargs)
  File "/Users/jwarren/.asdf/installs/python/2.7.18/lib/python2.7/site-packages/bugwarrior/command.py", line 73, in pull
    synchronize(issue_generator, config, main_section, dry_run)
  File "/Users/jwarren/.asdf/installs/python/2.7.18/lib/python2.7/site-packages/bugwarrior/db.py", line 438, in synchronize
    new_task = tw.task_add(**issue)
  File "/Users/jwarren/.asdf/installs/python/2.7.18/lib/python2.7/site-packages/taskw/warrior.py", line 697, in task_add
    task['uuid'] = re.search(UUID_REGEX, stdout).group(0)
AttributeError: 'NoneType' object has no attribute 'group'```

"Invalid range end" for a description.startswith containing `\\[..\\]`

CRITICAL:command:oh noes
TRACE Traceback (most recent call last):
TRACE   File "/home/dustin/code/taskwarrior/t/bugwarrior-prod/bugwarrior/command.py", line 62, in pull
TRACE     synchronize(issue_generator, config, main_section, dry_run)
TRACE   File "/home/dustin/code/taskwarrior/t/bugwarrior-prod/bugwarrior/db.py", line 342, in synchronize
TRACE     tw, key_list, issue, legacy_matching=legacy_matching
TRACE   File "/home/dustin/code/taskwarrior/t/bugwarrior-prod/bugwarrior/db.py", line 179, in find_local_uuid
TRACE     ('status', 'waiting'),
TRACE   File "/home/dustin/code/taskwarrior/t/bugwarrior-prod/sandbox/lib/python2.7/site-packages/taskw/warrior.py", line 606, in filter_tasks
TRACE     *query_args
TRACE   File "/home/dustin/code/taskwarrior/t/bugwarrior-prod/sandbox/lib/python2.7/site-packages/taskw/warrior.py", line 507, in _get_task_objects
TRACE     json = self._get_json(*args)
TRACE   File "/home/dustin/code/taskwarrior/t/bugwarrior-prod/sandbox/lib/python2.7/site-packages/taskw/warrior.py", line 504, in _get_json
TRACE     return json.loads(self._execute(*args)[0])
TRACE   File "/home/dustin/code/taskwarrior/t/bugwarrior-prod/sandbox/lib/python2.7/site-packages/taskw/warrior.py", line 494, in _execute
TRACE     raise TaskwarriorError(command, stderr, stdout, proc.returncode)
TRACE TaskwarriorError: ['task', 'rc:/home/dustin/.taskrc', 'rc.uda.bugzillasummary.type=string', 'rc.uda.bugzillasummary.label="Bugzilla Summary"', 'rc.uda.githubcreatedon.type=date', 'rc.uda.githubcreatedon.label="Github Created"', 'rc.uda.githubupdatedat.type=date', 'rc.uda.githubupdatedat.label="Github Updated"', 'rc.uda.githubnumber.type=numeric', 'rc.uda.githubnumber.label="Github Issue/PR #"', 'rc.uda.githubbody.type=string', 'rc.uda.githubbody.label="Github Body"', 'rc.uda.tracurl.type=string', 'rc.uda.tracurl.label="Trac URL"', 'rc.uda.githubrepo.type=string', 'rc.uda.githubrepo.label="Github Repo Slug"', 'rc.uda.githubtitle.type=string', 'rc.uda.githubtitle.label="Github Title"', 'rc.uda.tracnumber.type=numeric', 'rc.uda.tracnumber.label="Trac Number"', 'rc.uda.bugzillaurl.type=string', 'rc.uda.bugzillaurl.label="Bugzilla URL"', 'rc.uda.githuburl.type=string', 'rc.uda.githuburl.label="Github URL"', 'rc.uda.bugzillabugid.type=numeric', 'rc.uda.bugzillabugid.label="Bugzilla B
 ug ID"', 'rc.uda.githubtype.type=string', 'rc.uda.githubtype.label="Github Type"', 'rc.uda.githubmilestone.type=numeric', 'rc.uda.githubmilestone.label="Github Milestone"', 'rc.uda.tracsummary.type=string', 'rc.uda.tracsummary.label="Trac Summary"', 'rc.dependency.confirmation=no', 'rc.json.array=TRUE', 'rc.confirmation=no', 'rc.verbose=new-uuid', 'export', 'description.startswith:\\(bw\\)Is#1208031 - Android tc\\[Tier-2\\] Build problem ', '( status:pending or status:waiting )'] #2; stderr:"Invalid range end"; stdout:""

I admit that I'm guessing on the cause here, but this is probably the first time I've had a bug title with [..] in it. Is that being over- or under-escaped??

Seeing a diagnostic message each time (No handlers could be found for logger "taskw.taskrc")

Opened this original at taskwarrior time tracking hook who recommended that I file the bug here:

ENV:
Ubuntu 16.04
Task 2.5.1
Timewarrior: 1.1.1

Steps to reproduce

  1. Install latest taskw via pip
  2. install taskwarrior-time-tracking-hook
  3. task start x
  4. see message below:

[I] โœ˜ ๎‚ฐ ~/.t/hooks ๎‚ฐ task stop 120
No handlers could be found for logger "taskw.taskrc"
Total Time Tracked: 0:04:01.141180 (0:00:17.141180 in this instance)
The taskw.taskrc thing... I looked thru the /usr/local/lib/python2.7/dist-packages/taskwarrior_time_tracking_hook/init.py
;; file but didn't see any reference to it

anyways thanks for something thats very useful

bugwarrior runs (using taskw) are putting blocks of NUL bytes in my completed.data

I have been having my completed.data corrupted several times a day now, since using the most recent (develop) version of bugwarrior, which may have incidentally involved an ugprade to taskw -- I'm using 1.2.0.

I run bugwarrior hourly at :50, and every time I see the corruption, the datestamp is :50.

On the advice of #taskwarrior, I wrapped task:

#! /bin/bash

LOG=/home/dustin/.task/commands.log

ok=y
grep  -qPa '\x00' /home/dustin/.task/*.data && ok=n
echo `date` -- "start ok=$ok     " -- "${@}" >> $LOG
/usr/bin/task "${@}"
rv=$?
ok=y
grep  -qPa '\x00' /home/dustin/.task/*.data && ok=n
echo `date` -- "end   ok=$ok rv=$rv" -- "${@}" >> $LOG
exit $rv

This faithfully shows me the commands I run and, on most cron runs, the commands bugwarrior runs. For example:

Thu Mar 16 15:05:56 EDT 2017 -- start ok=y      -- 71
Thu Mar 16 15:05:57 EDT 2017 -- end   ok=y rv=0 -- 71
Thu Mar 16 15:50:03 EDT 2017 -- start ok=y      -- rc:/home/dustin/.taskrc rc.uda.bugzillasummary.type=string rc.uda.bugzillasummary.label="Bugzilla Summary" rc.uda.githubcreatedon.type=date rc.uda.githubcreatedon.label="Github Created" rc
.uda.githubupdatedat.type=date rc.uda.githubupdatedat.label="Github Updated" rc.uda.githubnumber.type=numeric rc.uda.githubnumber.label="Github Issue/PR #" rc.uda.githubbody.type=string rc.uda.githubbody.label="Github Body" rc.uda.githubre
po.type=string rc.uda.githubrepo.label="Github Repo Slug" rc.uda.githubtitle.type=string rc.uda.githubtitle.label="Github Title" rc.uda.bugzillaurl.type=string rc.uda.bugzillaurl.label="Bugzilla URL" rc.uda.githuburl.type=string rc.uda.git
huburl.label="Github URL" rc.uda.githubuser.type=string rc.uda.githubuser.label="Github User" rc.uda.bugzillastatus.type=string rc.uda.bugzillastatus.label="Bugzilla Status" rc.uda.bugzillabugid.type=numeric rc.uda.bugzillabugid.label="Bug
zilla Bug ID" rc.uda.githubtype.type=string rc.uda.githubtype.label="Github Type" rc.uda.bugzillaneedinfo.type=date rc.uda.bugzillaneedinfo.label="Bugzilla Needinfo" rc.uda.githubmilestone.type=string rc.uda.githubmilestone.label="Github M
ilestone" rc.dependency.confirmation=no rc.json.array=TRUE rc.confirmation=no rc.verbose=new-uuid export ( bugzillaurl.any: ) ( status:pending or status:waiting )
Thu Mar 16 15:50:04 EDT 2017 -- end   ok=y rv=0 -- rc:/home/dustin/.taskrc rc.uda.bugzillasummary.type=string rc.uda.bugzillasummary.label="Bugzilla Summary" rc.uda.githubcreatedon.type=date rc.uda.githubcreatedon.label="Github Created" rc
.uda.githubupdatedat.type=date rc.uda.githubupdatedat.label="Github Updated" rc.uda.githubnumber.type=numeric rc.uda.githubnumber.label="Github Issue/PR #" rc.uda.githubbody.type=string rc.uda.githubbody.label="Github Body" rc.uda.githubre
po.type=string rc.uda.githubrepo.label="Github Repo Slug" rc.uda.githubtitle.type=string rc.uda.githubtitle.label="Github Title" rc.uda.bugzillaurl.type=string rc.uda.bugzillaurl.label="Bugzilla URL" rc.uda.githuburl.type=string rc.uda.git
huburl.label="Github URL" rc.uda.githubuser.type=string rc.uda.githubuser.label="Github User" rc.uda.bugzillastatus.type=string rc.uda.bugzillastatus.label="Bugzilla Status" rc.uda.bugzillabugid.type=numeric rc.uda.bugzillabugid.label="Bugzilla Bug ID" rc.uda.githubtype.type=string rc.uda.githubtype.label="Github Type" rc.uda.bugzillaneedinfo.type=date rc.uda.bugzillaneedinfo.label="Bugzilla Needinfo" rc.uda.githubmilestone.type=string rc.uda.githubmilestone.label="Github Milestone" rc.dependency.confirmation=no rc.json.array=TRUE rc.confirmation=no rc.verbose=new-uuid export ( bugzillaurl.any: ) ( status:pending or status:waiting )
...
Thu Mar 16 15:50:24 EDT 2017 -- end   ok=y rv=0 -- rc:/home/dustin/.taskrc rc.uda.bugzillasummary.type=string rc.uda.bugzillasummary.label="Bugzilla Summary" rc.uda.githubcreatedon.type=date rc.uda.githubcreatedon.label="Github Created" rc.uda.githubupdatedat.type=date rc.uda.githubupdatedat.label="Github Updated" rc.uda.githubnumber.type=numeric rc.uda.githubnumber.label="Github Issue/PR #" rc.uda.githubbody.type=string rc.uda.githubbody.label="Github Body" rc.uda.githubrepo.type=string rc.uda.githubrepo.label="Github Repo Slug" rc.uda.githubtitle.type=string rc.uda.githubtitle.label="Github Title" rc.uda.bugzillaurl.type=string rc.uda.bugzillaurl.label="Bugzilla URL" rc.uda.githuburl.type=string rc.uda.githuburl.label="Github URL" rc.uda.githubuser.type=string rc.uda.githubuser.label="Github User" rc.uda.bugzillastatus.type=string rc.uda.bugzillastatus.label="Bugzilla Status" rc.uda.bugzillabugid.type=numeric rc.uda.bugzillabugid.label="Bugzilla Bug ID" rc.uda.githubtype.type=string rc.uda.githubtype.label="Github Type" rc.uda.bugzillaneedinfo.type=date rc.uda.bugzillaneedinfo.label="Bugzilla Needinfo" rc.uda.githubmilestone.type=string rc.uda.githubmilestone.label="Github Milestone" rc.dependency.confirmation=no rc.json.array=TRUE rc.confirmation=no rc.verbose=new-uuid export 309182af-ff3e-4509-987e-d4eed7877ec9
Thu Mar 16 16:38:13 EDT 2017 -- start ok=y      -- rc.gc=no rc.indent.report=4 rc.verbose= rc.report.next.columns=description.desc rc.report.next.labels= rc.defaultwidth=1000 next +ACTIVE
Thu Mar 16 16:38:14 EDT 2017 -- end   ok=y rv=0 -- rc.gc=no rc.indent.report=4 rc.verbose= rc.report.next.columns=description.desc rc.report.next.labels= rc.defaultwidth=1000 next +ACTIVE
Thu Mar 16 17:15:35 EDT 2017 -- start ok=n      -- moz
Thu Mar 16 17:15:35 EDT 2017 -- end   ok=n rv=2 -- moz

(it runs a lot!)

The thing to notice is at the end. At 16:38, I ran a query for active tasks, and at 17:15, I ran task moz, and before that task began the file was corrupted. The timestamp confirms this:

$ ls -al .task/completed.data
-rw-r--r-- 1 dustin dustin 5204490 Mar 16 16:50 .task/completed.data

Yet, there were no task executions logged at 16:50!

$ task
[/usr/bin/task next]
Unrecognized Taskwarrior file format. in /home/dustin/.task/completed.data at line 364

Here's some details on the contents of that "line"

>>> d[363].count('\x00')
2537034
>>> print `d[363][:10]`, `d[363][2537032:]`
'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' '\x00\x00[description:"email" end:"1486065883" entry:"1486065883" modified:"1486065883" priority:"M" status:"completed" uuid:"f6326142-be00-4ef8-a206-7cc0aaa6348b"]\n'

which is to say, it's about 2.4MB of NUL bytes followed by what seems to be the original data.

What could cause this? I don't think bugwarrior uses the deprecated direct-access functionality of taskw, right? Could taskw cause this? Halp?

Four test failures on openSUSE Tumbleweed

After adding patches #141 and #142, I am encountering four failures against taskwarrior-2.5.3:

[   54s] =================================== FAILURES ===================================
[   54s] ______________________ TestDBShellout.test_filtering_plus ______________________
[   54s] 
[   54s] self = <taskw.test.test_datas.TestDBShellout object at 0x7f8a8a572940>
[   54s] 
[   54s]     def test_filtering_plus(self):
[   54s]         self.tw.task_add("foobar1")
[   54s]         self.tw.task_add("foobar2")
[   54s]         self.tw.task_add("foobar+")
[   54s]         tasks = self.tw.filter_tasks({
[   54s] >           'description.contains': 'foobar+',
[   54s]         })
[   54s] 
[   54s] taskw/test/test_datas.py:386: 
[   54s] _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
[   54s] taskw/warrior.py:620: in filter_tasks
[   54s]     *query_args
[   54s] taskw/warrior.py:513: in _get_task_objects
[   54s]     json = self._get_json(*args)
[   54s] taskw/warrior.py:510: in _get_json
[   54s]     return json.loads(self._execute(*args)[0])
[   54s] _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
[   54s] 
[   54s] self = <taskw.warrior.TaskWarriorShellout object at 0x7f8a8a5724a8>
[   54s] args = ('export', 'description.contains:foobar\\+')
[   54s] command = [b'task', b'rc.verbose=new-uuid', b'rc.json.array=TRUE', b'rc.confirmation=no', b'rc.dependency.confirmation=no', b'rc.recurrence.confirmation=no', ...]
[   54s] env = {'COLORTERM': '1', 'CPU': 'x86_64', 'CSHEDIT': 'emacs', 'FORCE_SOURCE_DATE': '1', ...}
[   54s] i = 7
[   54s] 
[   54s]     def _execute(self, *args):
[   54s]         """ Execute a given taskwarrior command with arguments
[   54s]     
[   54s]         Returns a 2-tuple of stdout and stderr (respectively).
[   54s]     
[   54s]         """
[   54s]         command = (
[   54s]             [
[   54s]                 'task',
[   54s]             ]
[   54s]             + self.get_configuration_override_args()
[   54s]             + [six.text_type(arg) for arg in args]
[   54s]         )
[   54s]         env = os.environ.copy()
[   54s]         env['TASKRC'] = self.config_filename
[   54s]     
[   54s]         # subprocess is expecting bytestrings only, so nuke unicode if present
[   54s]         # and remove control characters
[   54s]         for i in range(len(command)):
[   54s]             if isinstance(command[i], six.text_type):
[   54s]                 command[i] = (
[   54s]                     taskw.utils.clean_ctrl_chars(command[i].encode('utf-8')))
[   54s]     
[   54s]         try:
[   54s]             proc = subprocess.Popen(
[   54s]                 command,
[   54s]                 env=env,
[   54s]                 stdout=subprocess.PIPE,
[   54s]                 stderr=subprocess.PIPE,
[   54s]             )
[   54s]             stdout, stderr = proc.communicate()
[   54s]         except FileNotFoundError:
[   54s]             raise FileNotFoundError(
[   54s]                 "Unable to find the 'task' command-line tool."
[   54s]             )
[   54s]     
[   54s]         if proc.returncode != 0:
[   54s] >           raise TaskwarriorError(command, stderr, stdout, proc.returncode)
[   54s] E           taskw.exceptions.TaskwarriorError: [b'task', b'rc.verbose=new-uuid', b'rc.json.array=TRUE', b'rc.confirmation=no', b'rc.dependency.confirmation=no', b'rc.recurrence.confirmation=no', b'export', b'description.contains:foobar\\+'] #2; stderr:"b'The expression could not be evaluated.'"; stdout:"b''"
[   54s] 
[   54s] taskw/warrior.py:484: TaskwarriorError
[   54s] _____________________ TestDBShellout.test_filtering_minus ______________________
[   54s] 
[   54s] self = <taskw.test.test_datas.TestDBShellout object at 0x7f8a8a4a4748>
[   54s] 
[   54s]     def test_filtering_minus(self):
[   54s]         self.tw.task_add("foobar1")
[   54s]         self.tw.task_add("foobar2")
[   54s]         self.tw.task_add("foobar-")
[   54s]         tasks = self.tw.filter_tasks({
[   54s] >           'description.contains': 'foobar-',
[   54s]         })
[   54s] 
[   54s] taskw/test/test_datas.py:396: 
[   54s] _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
[   54s] taskw/warrior.py:620: in filter_tasks
[   54s]     *query_args
[   54s] taskw/warrior.py:513: in _get_task_objects
[   54s]     json = self._get_json(*args)
[   54s] taskw/warrior.py:510: in _get_json
[   54s]     return json.loads(self._execute(*args)[0])
[   54s] _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
[   54s] 
[   54s] self = <taskw.warrior.TaskWarriorShellout object at 0x7f8a8a4a47f0>
[   54s] args = ('export', 'description.contains:foobar-')
[   54s] command = [b'task', b'rc.verbose=new-uuid', b'rc.json.array=TRUE', b'rc.confirmation=no', b'rc.dependency.confirmation=no', b'rc.recurrence.confirmation=no', ...]
[   54s] env = {'COLORTERM': '1', 'CPU': 'x86_64', 'CSHEDIT': 'emacs', 'FORCE_SOURCE_DATE': '1', ...}
[   54s] i = 7
[   54s] 
[   54s]     def _execute(self, *args):
[   54s]         """ Execute a given taskwarrior command with arguments
[   54s]     
[   54s]         Returns a 2-tuple of stdout and stderr (respectively).
[   54s]     
[   54s]         """
[   54s]         command = (
[   54s]             [
[   54s]                 'task',
[   54s]             ]
[   54s]             + self.get_configuration_override_args()
[   54s]             + [six.text_type(arg) for arg in args]
[   54s]         )
[   54s]         env = os.environ.copy()
[   54s]         env['TASKRC'] = self.config_filename
[   54s]     
[   54s]         # subprocess is expecting bytestrings only, so nuke unicode if present
[   54s]         # and remove control characters
[   54s]         for i in range(len(command)):
[   54s]             if isinstance(command[i], six.text_type):
[   54s]                 command[i] = (
[   54s]                     taskw.utils.clean_ctrl_chars(command[i].encode('utf-8')))
[   54s]     
[   54s]         try:
[   54s]             proc = subprocess.Popen(
[   54s]                 command,
[   54s]                 env=env,
[   54s]                 stdout=subprocess.PIPE,
[   54s]                 stderr=subprocess.PIPE,
[   54s]             )
[   54s]             stdout, stderr = proc.communicate()
[   54s]         except FileNotFoundError:
[   54s]             raise FileNotFoundError(
[   54s]                 "Unable to find the 'task' command-line tool."
[   54s]             )
[   54s]     
[   54s]         if proc.returncode != 0:
[   54s] >           raise TaskwarriorError(command, stderr, stdout, proc.returncode)
[   54s] E           taskw.exceptions.TaskwarriorError: [b'task', b'rc.verbose=new-uuid', b'rc.json.array=TRUE', b'rc.confirmation=no', b'rc.dependency.confirmation=no', b'rc.recurrence.confirmation=no', b'export', b'description.contains:foobar-'] #2; stderr:"b'The expression could not be evaluated.'"; stdout:"b''"
[   54s] 
[   54s] taskw/warrior.py:484: TaskwarriorError
[   54s] _____________________ TestDBShellout.test_filtering_slash ______________________
[   54s] 
[   54s] self = <taskw.test.test_datas.TestDBShellout object at 0x7f8a8a3b55f8>
[   54s] 
[   54s]     def test_filtering_slash(self):
[   54s]         self.tw.task_add("foobar1")
[   54s]         self.tw.task_add("foobar2")
[   54s]         self.tw.task_add("foo/bar")
[   54s]         tasks = self.tw.filter_tasks({
[   54s] >           'description.contains': 'foo/bar',
[   54s]         })
[   54s] 
[   54s] taskw/test/test_datas.py:454: 
[   54s] _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
[   54s] taskw/warrior.py:620: in filter_tasks
[   54s]     *query_args
[   54s] taskw/warrior.py:513: in _get_task_objects
[   54s]     json = self._get_json(*args)
[   54s] taskw/warrior.py:510: in _get_json
[   54s]     return json.loads(self._execute(*args)[0])
[   54s] _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
[   54s] 
[   54s] self = <taskw.warrior.TaskWarriorShellout object at 0x7f8a8a3b52e8>
[   54s] args = ('export', 'description.contains:foo/bar')
[   54s] command = [b'task', b'rc.verbose=new-uuid', b'rc.json.array=TRUE', b'rc.confirmation=no', b'rc.dependency.confirmation=no', b'rc.recurrence.confirmation=no', ...]
[   54s] env = {'COLORTERM': '1', 'CPU': 'x86_64', 'CSHEDIT': 'emacs', 'FORCE_SOURCE_DATE': '1', ...}
[   54s] i = 7
[   54s] 
[   54s]     def _execute(self, *args):
[   54s]         """ Execute a given taskwarrior command with arguments
[   54s]     
[   54s]         Returns a 2-tuple of stdout and stderr (respectively).
[   54s]     
[   54s]         """
[   54s]         command = (
[   54s]             [
[   54s]                 'task',
[   54s]             ]
[   54s]             + self.get_configuration_override_args()
[   54s]             + [six.text_type(arg) for arg in args]
[   54s]         )
[   54s]         env = os.environ.copy()
[   54s]         env['TASKRC'] = self.config_filename
[   54s]     
[   54s]         # subprocess is expecting bytestrings only, so nuke unicode if present
[   54s]         # and remove control characters
[   54s]         for i in range(len(command)):
[   54s]             if isinstance(command[i], six.text_type):
[   54s]                 command[i] = (
[   54s]                     taskw.utils.clean_ctrl_chars(command[i].encode('utf-8')))
[   54s]     
[   54s]         try:
[   54s]             proc = subprocess.Popen(
[   54s]                 command,
[   54s]                 env=env,
[   54s]                 stdout=subprocess.PIPE,
[   54s]                 stderr=subprocess.PIPE,
[   54s]             )
[   54s]             stdout, stderr = proc.communicate()
[   54s]         except FileNotFoundError:
[   54s]             raise FileNotFoundError(
[   54s]                 "Unable to find the 'task' command-line tool."
[   54s]             )
[   54s]     
[   54s]         if proc.returncode != 0:
[   54s] >           raise TaskwarriorError(command, stderr, stdout, proc.returncode)
[   54s] E           taskw.exceptions.TaskwarriorError: [b'task', b'rc.verbose=new-uuid', b'rc.json.array=TRUE', b'rc.confirmation=no', b'rc.dependency.confirmation=no', b'rc.recurrence.confirmation=no', b'export', b'description.contains:foo/bar'] #2; stderr:"b'Cannot divide real numbers by strings'"; stdout:"b''"
[   54s] 
[   54s] taskw/warrior.py:484: TaskwarriorError
[   54s] ___________________ TestDBShellout.test_annotation_escaping ____________________
[   54s] 
[   54s] self = <taskw.test.test_datas.TestDBShellout object at 0x7f8a8a56e358>
[   54s] 
[   54s]     def test_annotation_escaping(self):
[   54s]         original = {'description': 're-opening the issue'}
[   54s]     
[   54s]         self.tw.task_add('foobar')
[   54s]         task = self.tw.load_tasks()['pending'][0]
[   54s]         task['annotations'] = [original]
[   54s]         self.tw.task_update(task)
[   54s]     
[   54s]         task = self.tw.load_tasks()['pending'][0]
[   54s] >       self.tw.task_update(task)
[   54s] 
[   54s] taskw/test/test_datas.py:509: 
[   54s] _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
[   54s] taskw/warrior.py:809: in task_update
[   54s]     self._execute(task_uuid, 'modify', *modification)
[   54s] _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
[   54s] 
[   54s] self = <taskw.warrior.TaskWarriorShellout object at 0x7f8a8a56e908>
[   54s] args = ('093fbfd8-c3cd-42fc-8609-0977bb616551', 'modify', 'description:"foobar"', 'entry:"20210724T013854Z"', 'modified:"20210724T013854Z"', 'status:"pending"', ...)
[   54s] command = [b'task', b'rc.verbose=new-uuid', b'rc.json.array=TRUE', b'rc.confirmation=no', b'rc.dependency.confirmation=no', b'rc.recurrence.confirmation=no', ...]
[   54s] env = {'COLORTERM': '1', 'CPU': 'x86_64', 'CSHEDIT': 'emacs', 'FORCE_SOURCE_DATE': '1', ...}
[   54s] i = 12
[   54s] 
[   54s]     def _execute(self, *args):
[   54s]         """ Execute a given taskwarrior command with arguments
[   54s]     
[   54s]         Returns a 2-tuple of stdout and stderr (respectively).
[   54s]     
[   54s]         """
[   54s]         command = (
[   54s]             [
[   54s]                 'task',
[   54s]             ]
[   54s]             + self.get_configuration_override_args()
[   54s]             + [six.text_type(arg) for arg in args]
[   54s]         )
[   54s]         env = os.environ.copy()
[   54s]         env['TASKRC'] = self.config_filename
[   54s]     
[   54s]         # subprocess is expecting bytestrings only, so nuke unicode if present
[   54s]         # and remove control characters
[   54s]         for i in range(len(command)):
[   54s]             if isinstance(command[i], six.text_type):
[   54s]                 command[i] = (
[   54s]                     taskw.utils.clean_ctrl_chars(command[i].encode('utf-8')))
[   54s]     
[   54s]         try:
[   54s]             proc = subprocess.Popen(
[   54s]                 command,
[   54s]                 env=env,
[   54s]                 stdout=subprocess.PIPE,
[   54s]                 stderr=subprocess.PIPE,
[   54s]             )
[   54s]             stdout, stderr = proc.communicate()
[   54s]         except FileNotFoundError:
[   54s]             raise FileNotFoundError(
[   54s]                 "Unable to find the 'task' command-line tool."
[   54s]             )
[   54s]     
[   54s]         if proc.returncode != 0:
[   54s] >           raise TaskwarriorError(command, stderr, stdout, proc.returncode)
[   54s] E           taskw.exceptions.TaskwarriorError: [b'task', b'rc.verbose=new-uuid', b'rc.json.array=TRUE', b'rc.confirmation=no', b'rc.dependency.confirmation=no', b'rc.recurrence.confirmation=no', b'093fbfd8-c3cd-42fc-8609-0977bb616551', b'modify', b'description:"foobar"', b'entry:"20210724T013854Z"', b'modified:"20210724T013854Z"', b'status:"pending"', b'urgency:"0.8"'] #2; stderr:"b"The 'urgency' attribute does not allow a value of '0.8'.""; stdout:"b''"
[   54s] 
[   54s] taskw/warrior.py:484: TaskwarriorError
[   54s] =========================== short test summary info ============================
[   54s] FAILED taskw/test/test_datas.py::TestDBShellout::test_filtering_plus - taskw....
[   54s] FAILED taskw/test/test_datas.py::TestDBShellout::test_filtering_minus - taskw...
[   54s] FAILED taskw/test/test_datas.py::TestDBShellout::test_filtering_slash - taskw...
[   54s] FAILED taskw/test/test_datas.py::TestDBShellout::test_annotation_escaping - t...
[   54s] ======================== 4 failed, 147 passed in 9.55s =========================

Add a save_config() method

The idea would be you could do something like

tw = TaskWarrior()
config = tw.load_config()
config['data']['location'] = '/tmp/test'
tw.save_config(config)

This is not something I'll be adding any time soon but I bet someone could knock it out pretty quickly.

UUID parsing of created task fails with recur

There is a bug in the logic that parses UUIDs after a task is created.

The issue occurs when adding a task with recur set, since the output is different:

$ task add "Take out garbage" rc.verbose:new-uuid  due:sow recur:weekly
Created task 4febf3b5-379a-41d4-b1b1-c0160f0b801c (recurrence template).

The code in question is here, and doesn't expect the bit with recurrence template. We end up parsing uuid = 'template)' and the command fails when getting the task:

>>> try:
...    taskwarrior.task_add('Take out garbage', due='sow', recur='weekly')
... except TaskwarriorError as e:
...    print(b' '.join(e.command))
...    print(e.stderr)

b'task rc:~/.taskrc rc.json.array=TRUE rc.verbose=new-uuid rc.confirmation=no rc.dependency.confirmation=no export template)'

b'Configuration override rc.json.array:TRUE\nConfiguration override rc.verbose:new-uuid\nConfiguration override rc.confirmation:no\nConfiguration override rc.dependency.confirmation:no\nMismatched parentheses in expression'

Ability to obtain blocking (reverse dependencies) tasks

Hi guys,

Would it be possible to have a Task.blocks() function on taskw.task.Task objects that allows retrieving tasks that have a dependency on the current task?

This can be obtained using task dep.contains:<uuid of current task>.

Thanks

cannot create task

2013-12-17T09:00:00
2014-04-03T18:00:00
2013-12-17T09:00:00+00:00
problem with test create task
Traceback (most recent call last):
File "proj2task.py", line 42, in
w.task_add(Name.encode('UTF-8'), priority="N",project=projectname, start=startepoch)
File "/usr/lib/python2.7/site-packages/taskw-0.7.2-py2.7.egg/taskw/warrior.py", line 515, in task_add
raise KeyError('No uuid! uh oh.')
KeyError: 'No uuid! uh oh.'

thanks for your help

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.