X-Git-Url: https://git.madduck.net/etc/taskwarrior.git/blobdiff_plain/9a7dac4599f198c52f268ae8bcc0267743ef934f:/docs/index.rst..8078ceed9b50dad2d319a74e2d3014b6ff649b47:/code/taskwarrior/tasklib/docs/static/gitweb.js diff --git a/docs/index.rst b/docs/index.rst deleted file mode 100644 index 17ea42a..0000000 --- a/docs/index.rst +++ /dev/null @@ -1,597 +0,0 @@ -Welcome to tasklib's documentation! -=================================== - -tasklib is a Python library for interacting with taskwarrior_ databases, using -a queryset API similar to that of Django's ORM. - -Supports Python 2.6, 2.7, 3.2, 3.3 and 3.4 with taskwarrior 2.1.x and above. -Older versions of taskwarrior are untested and may not work. - -Requirements ------------- - -* taskwarrior_ v2.1.x or above, although newest minor release is recommended. - -Installation ------------- - -Install via pip (recommended):: - - pip install tasklib - -Or clone from github:: - - git clone https://github.com/robgolding63/tasklib.git - cd tasklib - python setup.py install - -Initialization --------------- - -Optionally initialize the ``TaskWarrior`` instance with ``data_location`` (the -database directory). If it doesn't already exist, this will be created -automatically unless ``create=False``. - -The default location is the same as taskwarrior's:: - - >>> tw = TaskWarrior(data_location='~/.task', create=True) - -The ``TaskWarrior`` instance will also use your .taskrc configuration (so that -it recognizes the same UDAs as your task binary, uses the same configuration, -etc.). To override the location of the .taskrc, use -``taskrc_location=~/some/different/path``. - -Creating Tasks --------------- - -To create a task, simply create a new ``Task`` object:: - - >>> new_task = Task(tw, description="throw out the trash") - -This task is not yet saved to TaskWarrior (same as in Django), not until -you call ``.save()`` method:: - - >>> new_task.save() - -You can set any attribute as a keyword argument to the Task object:: - - >>> complex_task = Task(tw, description="finally fix the shower", due=datetime(2015,2,14,8,0,0), priority='H') - -or by setting the attributes one by one:: - - >>> complex_task = Task(tw) - >>> complex_task['description'] = "finally fix the shower" - >>> complex_task['due'] = datetime(2015,2,14,8,0,0) - >>> complex_task['priority'] = 'H' - -Modifying Task --------------- - -To modify a created or retrieved ``Task`` object, use dictionary-like access:: - - >>> homework = tw.tasks.get(tags=['chores']) - >>> homework['project'] = 'Home' - -The change is not propagated to the TaskWarrior until you run the ``save()`` method:: - - >>> homework.save() - -Attributes, which map to native Python objects are converted. See Task Attributes section. - -Task Attributes ---------------- - -Attributes of task objects are accessible through indices, like so:: - - >>> task = tw.tasks.pending().get(tags__contain='work') # There is only one pending task with 'work' tag - >>> task['description'] - 'Upgrade Ubuntu Server' - >>> task['id'] - 15 - >>> task['due'] - datetime.datetime(2015, 2, 5, 0, 0, tzinfo=) - >>> task['tags'] - ['work', 'servers'] - -The following fields are deserialized into Python objects: - -* ``due``, ``wait``, ``scheduled``, ``until``, ``entry``: deserialized to a ``datetime`` object -* ``annotations``: deserialized to a list of ``TaskAnnotation`` objects -* ``tags``: deserialized to a list of strings -* ``depends``: deserialized to a set of ``Task`` objects - -Attributes should be set using the correct Python representation, which will be -serialized into the correct format when the task is saved. - -Task properties ---------------- - -Tasklib defines several properties upon ``Task`` object, for convenience:: - - >>> t.save() - >>> t.saved - True - >>> t.pending - True - >>> t.active - False - >>> t.start() - >>> t.active - True - >>> t.done() - >>> t.completed - True - >>> t.pending - False - >>> t.delete() - >>> t.deleted - True - -Operations on Tasks -------------------- - -After modifying one or more attributes, simple call ``save()`` to write those -changes to the database:: - - >>> task = tw.tasks.pending().get(tags__contain='work') - >>> task['due'] = datetime(year=2014, month=1, day=5) - >>> task.save() - -To mark a task as complete, use ``done()``:: - - >>> task = tw.tasks.pending().get(tags__contain='work') - >>> task.done() - >>> len(tw.tasks.pending().filter(tags__contain='work')) - 0 - -To delete a task, use ``delete()``:: - - >>> task = tw.tasks.get(description="task added by mistake") - >>> task.delete() - -To update a task object with values from TaskWarrior database, use ``refresh()``. Example:: - - >>> task = Task(tw, description="learn to cook") - >>> task.save() - >>> task['id'] - 5 - >>> task['tags'] - [] - -Now, suppose the we modify the task using the TaskWarrior interface in another terminal:: - - $ task 5 modify +someday - Task 5 modified. - -Switching back to the open python process:: - - >>> task['tags'] - [] - >>> task.refresh() - >>> task['tags'] - ['someday'] - -Tasks can also be started and stopped. Use ``start()`` and ``stop()`` -respectively:: - - >>> task.start() - >>> task['start'] - datetime.datetime(2015, 7, 16, 18, 48, 28, tzinfo=) - >>> task.stop() - >>> task['start'] - >>> task.done() - >>> task['end'] - datetime.datetime(2015, 7, 16, 18, 49, 2, tzinfo=) - - -Retrieving Tasks ----------------- - -``tw.tasks`` is a ``TaskQuerySet`` object which emulates the Django QuerySet -API. To get all tasks (including completed ones):: - - >>> tw.tasks.all() - ['First task', 'Completed task', 'Deleted task', ...] - -Filtering ---------- - -Filter tasks using the same familiar syntax:: - - >>> tw.tasks.filter(status='pending', tags__contains=['work']) - ['Upgrade Ubuntu Server'] - -Filter arguments are passed to the ``task`` command (``__`` is replaced by -a period) so the above example is equivalent to the following command:: - - $ task status:pending tags.contain=work - -Tasks can also be filtered using raw commands, like so:: - - >>> tw.tasks.filter('status:pending +work') - ['Upgrade Ubuntu Server'] - -Although this practice is discouraged, as by using raw commands you may lose -some of the portablility of your commands over different TaskWarrior versions. - -However, you can mix raw commands with keyword filters, as in the given example:: - - >>> tw.tasks.filter('+BLOCKING', project='Home') # Gets all blocking tasks in project Home - ['Fix the toilette'] - -This can be a neat way how to use syntax not yet supported by tasklib. The above -is excellent example, since virtual tags do not work the same way as the ordinary ones, that is:: - - >>> tw.tasks.filter(tags=['BLOCKING']) - >>> [] - -will not work. - -There are built-in functions for retrieving pending & completed tasks:: - - >>> tw.tasks.pending().filter(tags__contain='work') - ['Upgrade Ubuntu Server'] - >>> len(tw.tasks.completed()) - 227 - -Use ``get()`` to return the only task in a ``TaskQuerySet``, or raise an -exception:: - - >>> tw.tasks.get(tags__contain='work')['status'] - 'pending' - >>> tw.tasks.get(status='completed', tags__contains='work') # Status of only task with the work tag is pending, so this should fail - Traceback (most recent call last): - File "", line 1, in - File "tasklib/task.py", line 224, in get - 'Lookup parameters were {0}'.format(kwargs)) - tasklib.task.DoesNotExist: Task matching query does not exist. Lookup parameters were {'status': 'completed', 'tags__contains': ['work']} - >>> tw.tasks.get(status='pending') - Traceback (most recent call last): - File "", line 1, in - File "tasklib/task.py", line 227, in get - 'Lookup parameters were {1}'.format(num, kwargs)) - ValueError: get() returned more than one Task -- it returned 23! Lookup parameters were {'status': 'pending'} - -Additionally, since filters return ``TaskQuerySets`` you can stack filters on top of each other:: - - >>> home_tasks = tw.tasks.filter(project='Wife') - >>> home_tasks.filter(due__before=datetime(2015,2,14,14,14,14)) # What I have to do until Valentine's day - ['Prepare surprise birthday party'] - -Equality of Task objects ------------------------- - -Two Tasks are considered equal if they have the same UUIDs:: - - >>> task1 = Task(tw, description="Pet the dog") - >>> task1.save() - >>> task2 = tw.tasks.get(description="Pet the dog") - >>> task1 == task2 - True - -If you compare the two unsaved tasks, they are considered equal only if it's the -same Python object:: - - >>> task1 = Task(tw, description="Pet the cat") - >>> task2 = Task(tw, description="Pet the cat") - >>> task1 == task2 - False - >>> task3 = task1 - >>> task3 == task1 - True - -Accessing original values -------------------------- - -To access the saved state of the Task, use dict-like access using the -``original`` attribute: - - >>> t = Task(tw, description="tidy up") - >>> t.save() - >>> t['description'] = "tidy up the kitchen and bathroom" - >>> t['description'] - "tidy up the kitchen and bathroom" - >>> t.original['description'] - "tidy up" - -When you save the task, original values are refreshed to reflect the -saved state of the task: - - >>> t.save() - >>> t.original['description'] - "tidy up the kitchen and bathroom" - -Dealing with dates and time ---------------------------- - -Any timestamp-like attributes of the tasks are converted to timezone-aware -datetime objects. To achieve this, Tasklib leverages ``pytz`` Python module, -which brings the Olsen timezone databaze to Python. - -This shields you from annoying details of Daylight Saving Time shifts -or conversion between different timezones. For example, to list all the -tasks which are due midnight if you're currently in Berlin: - - >>> myzone = pytz.timezone('Europe/Berlin') - >>> midnight = myzone.localize(datetime(2015,2,2,0,0,0)) - >>> tw.tasks.filter(due__before=midnight) - -However, this is still a little bit tedious. That's why TaskWarrior object -is capable of automatic timezone detection, using the ``tzlocal`` Python -module. If your system timezone is set to 'Europe/Berlin', following example -will work the same way as the previous one: - - >>> tw.tasks.filter(due__before=datetime(2015,2,2,0,0,0)) - -You can also use simple dates when filtering: - - >>> tw.tasks.filter(due__before=date(2015,2,2)) - -In such case, a 00:00:00 is used as the time component. - -Of course, you can use datetime naive objects when initializing Task object -or assigning values to datetime atrributes: - - >>> t = Task(tw, description="Buy new shoes", due=date(2015,2,5)) - >>> t['due'] - datetime.datetime(2015, 2, 5, 0, 0, tzinfo=) - >>> t['due'] = date(2015,2,6,15,15,15) - >>> t['due'] - datetime.datetime(2015, 2, 6, 15, 15, 15, tzinfo=) - -However, since timezone-aware and timezone-naive datetimes are not comparable -in Python, this can cause some unexpected behaviour: - - >>> from datetime import datetime - >>> now = datetime.now() - >>> t = Task(tw, description="take out the trash now") - >>> t['due'] = now - >>> now - datetime.datetime(2015, 2, 1, 19, 44, 4, 770001) - >>> t['due'] - datetime.datetime(2015, 2, 1, 19, 44, 4, 770001, tzinfo=) - >>> t['due'] == now - Traceback (most recent call last): - File "", line 1, in - TypeError: can't compare offset-naive and offset-aware datetimes - -If you want to compare datetime aware value with datetime naive value, you need -to localize the naive value first: - - >>> from datetime import datetime - >>> from tasklib.task import local_zone - >>> now = local_zone.localize(datetime.now()) - >>> t['due'] = now - >>> now - datetime.datetime(2015, 2, 1, 19, 44, 4, 770001, tzinfo=) - >>> t['due'] == now - True - -Also, note that it does not matter whether the timezone aware datetime objects -are set in the same timezone: - - >>> import pytz - >>> t['due'] - datetime.datetime(2015, 2, 1, 19, 44, 4, 770001, tzinfo=) - >>> now.astimezone(pytz.utc) - datetime.datetime(2015, 2, 1, 18, 44, 4, 770001, tzinfo=) - >>> t['due'] == now.astimezone(pytz.utc) - True - -*Note*: Following behaviour is available only for TaskWarrior >= 2.4.0. - -There is a third approach to setting up date time values, which leverages -the 'task calc' command. You can simply set any datetime attribute to -any string that contains an acceptable TaskWarrior-formatted time expression:: - - $ task calc now + 1d - 2015-07-17T21:17:54 - -This syntax can be leveraged in the python interpreter as follows:: - - >>> t['due'] = "now + 1d" - >>> t['due'] - datetime.datetime(2015, 7, 17, 21, 19, 31, tzinfo=) - -It can be easily seen that the string with TaskWarrior-formatted time expression -is automatically converted to native datetime in the local time zone. - -For the list of acceptable formats and keywords, please consult: - -* http://taskwarrior.org/docs/dates.html -* http://taskwarrior.org/docs/named_dates.html - -However, as each such assigment involves call to 'task calc' for conversion, -it might cause some performance issues when assigning strings to datetime -attributes repeatedly, in a automated manner. - -Working with annotations ------------------------- - -Annotations of the tasks are represented in tasklib by ``TaskAnnotation`` objects. These -are much like ``Task`` objects, albeit very simplified. - - >>> annotated_task = tw.tasks.get(description='Annotated task') - >>> annotated_task['annotations'] - [Yeah, I am annotated!] - -Annotations have only defined ``entry`` and ``description`` values:: - - >>> annotation = annotated_task['annotations'][0] - >>> annotation['entry'] - datetime.datetime(2015, 1, 3, 21, 13, 55, tzinfo=) - >>> annotation['description'] - u'Yeah, I am annotated!' - -To add a annotation to a Task, use ``add_annotation()``:: - - >>> task = Task(tw, description="new task") - >>> task.add_annotation("we can annotate any task") - Traceback (most recent call last): - File "", line 1, in - File "build/bdist.linux-x86_64/egg/tasklib/task.py", line 355, in add_annotation - tasklib.task.NotSaved: Task needs to be saved to add annotation - -However, Task needs to be saved before you can add a annotation to it:: - - >>> task.save() - >>> task.add_annotation("we can annotate saved tasks") - >>> task['annotations'] - [we can annotate saved tasks] - -To remove the annotation, pass its description to ``remove_annotation()`` method:: - - >>> task.remove_annotation("we can annotate saved tasks") - -Alternatively, you can pass the ``TaskAnnotation`` object itself:: - - >>> task.remove_annotation(task['annotations'][0]) - - -Running custom commands ------------------------ - -To run a custom commands, use ``execute_command()`` method of ``TaskWarrior`` object:: - - >>> tw = TaskWarrior() - >>> tw.execute_command(['log', 'Finish high school.']) - [u'Logged task.'] - -You can use ``config_override`` keyword argument to specify a dictionary of configuration overrides:: - - >>> tw.execute_command(['3', 'done'], config_override={'gc': 'off'}) # Will mark 3 as completed and it will retain its ID - - -Additionally, you can use ``return_all=True`` flag, which returns -``(stdout, sterr, return_code)`` triplet, and ``allow_failure=False``, which will -prevent tasklib from raising an exception if the task binary returned non-zero -return code:: - - >>> tw.execute_command(['invalidcommand'], allow_failure=False, return_all=True) - ([u''], - [u'Using alternate .taskrc file /home/tbabej/.taskrc', - u"[task next rc:/home/tbabej/.taskrc rc.recurrence.confirmation=no rc.json.array=off rc.confirmation=no rc.bulk=0 rc.dependency.confirmation=no description ~ 'invalidcommand']", - u'Configuration override rc.recurrence.confirmation:no', - u'Configuration override rc.json.array:off', - u'Configuration override rc.confirmation:no', - u'Configuration override rc.bulk:0', - u'Configuration override rc.dependency.confirmation:no', - u'No matches.', - u'There are local changes. Sync required.'], - 1) - - -Setting custom configuration values ------------------------------------ - -By default, TaskWarrior uses configuration values stored in your .taskrc. -To see what configuration value overrides are passed to each executed -task command, have a peek into ``overrides`` attribute of ``TaskWarrior`` object:: - - >>> tw.overrides - {'confirmation': 'no', 'data.location': '/home/tbabej/.task'} - -To pass your own configuration overrides, you just need to update this dictionary:: - - >>> tw.overrides.update({'hooks': 'off'}) # tasklib will not trigger hooks - -Creating hook scripts ---------------------- - -From version 2.4.0, TaskWarrior has support for hook scripts. Tasklib provides -some very useful helpers to write those. With tasklib, writing these becomes -a breeze:: - - #!/usr/bin/python - - from tasklib.task import Task - task = Task.from_input() - # ... - print task.export_data() - -For example, plugin which would assign the priority "H" to any task containing -three exclamation marks in the description, would go like this:: - - #!/usr/bin/python - - from tasklib.task import Task - task = Task.from_input() - - if "!!!" in task['description']: - task['priority'] = "H" - - print task.export_data() - -Tasklib can automatically detect whether it's running in the ``on-modify`` event, -which provides more input than ``on-add`` event and reads the data accordingly. - -This means the example above works both for ``on-add`` and ``on-modify`` events! - -Consenquently, you can create just one hook file for both ``on-add`` and -``on-modify`` events, and you just need to create a symlink for the other one. -This removes the need for maintaining two copies of the same code base and/or -boilerplate code. - -In ``on-modify`` events, tasklib loads both the original version and the modified -version of the task to the returned ``Task`` object. To access the original data -(in read-only manner), use ``original`` dict-like attribute: - - >>> t = Task.from_input() - >>> t['description'] - "Modified description" - >>> t.original['description'] - "Original description" - -Working with UDAs ------------------ - -Since TaskWarrior does read your .taskrc, you need not to define any UDAs -in the TaskWarrior's config dictionary, as described above. Suppose we have -a estimate UDA in the .taskrc:: - - uda.estimate.type = numeric - -We can simply filter and create tasks using the estimate UDA out of the box:: - - >>> tw = TaskWarrior() - >>> task = Task(tw, description="Long task", estimate=1000) - >>> task.save() - >>> task['id'] - 1 - -This is saved as UDA in the TaskWarrior:: - - $ task 1 export - {"id":1,"description":"Long task","estimate":1000, ...} - -We can also speficy UDAs as arguments in the TaskFilter:: - - >>> tw.tasks.filter(estimate=1000) - Long task - -Syncing -------- - -If you have configurated the needed config variables in your .taskrc, syncing -is as easy as:: - - >>> tw = TaskWarrior() - >>> tw.execute_command(['sync']) - -If you want to use non-standard server/credentials, you'll need to provide configuration -overrides to the ``TaskWarrior`` instance. Update the ``config`` dictionary with the -values you desire to override, and then we can run the sync command using -the ``execute_command()`` method:: - - >>> tw = TaskWarrior() - >>> sync_config = { - ... 'taskd.certificate': '/home/tbabej/.task/tbabej.cert.pem', - ... 'taskd.credentials': 'Public/tbabej/34af54de-3cb2-4d3d-82be-33ddb8fd3e66', - ... 'taskd.server': 'task.server.com:53589', - ... 'taskd.ca': '/home/tbabej/.task/ca.cert.pem', - ... 'taskd.trust': 'ignore hostname'} - >>> tw.config.update(sync_config) - >>> tw.execute_command(['sync']) - - -.. _taskwarrior: http://taskwarrior.org