things.api

Module implementing the Things API.

The terms of the API aim to match those used in the Things app and Things URL Scheme. In some specific cases we instead choose to use terms that occur in the Things SQL database to closer reflect its underlying data structures. Whenever that happens, we define the new term here.

  1"""
  2Module implementing the Things API.
  3
  4The terms of the API aim to match those used in the Things app and
  5Things URL Scheme. In some specific cases we instead choose to use terms
  6that occur in the Things SQL database to closer reflect its underlying
  7data structures. Whenever that happens, we define the new term here.
  8"""
  9
 10import os
 11from shlex import quote
 12from typing import Dict, List, Union
 13import urllib.parse
 14
 15from things.database import Database
 16
 17
 18# --------------------------------------------------
 19# Core functions
 20# --------------------------------------------------
 21
 22
 23def tasks(uuid=None, include_items=False, **kwargs):  # noqa: C901
 24    """
 25    Read tasks into dicts.
 26
 27    Note: "task" is a technical term used in the database to refer to a
 28    to-do, project, or heading. For details, check the "type"-parameter.
 29
 30    Per default, only tasks marked as incomplete are included. If you
 31    want to include completed or canceled tasks in the result, check the
 32    "status"-parameter.
 33
 34    Parameters
 35    ----------
 36    uuid : str or None, optional
 37        Any valid task uuid. If None, then return all tasks matched.
 38
 39    include_items : bool, default False
 40        Include items contained within a task. These might include
 41        checklist items, headings, and to-dos.
 42
 43    type : {'to-do', 'heading', 'project', None}, optional
 44        Only return a specific type of task:
 45        - `'to-do'`:    may have a checklist; may be in an area and have tags.
 46        - `'project'`:  may have to-dos and headings; may be in an area and
 47                        have tags.
 48        - `'heading'`:  part of a project; groups tasks.
 49        - `None` (default): return all types of tasks.
 50
 51    status : {'incomplete', 'completed', 'canceled', None}, optional, \
 52        default 'incomplete'
 53
 54        Only include tasks matching that status. If `status == None`,
 55        then include tasks with any status value.
 56
 57    start : {'Inbox', 'Anytime', 'Someday', None}, optional
 58        Only include tasks matching that start value. If the argument is
 59        `None` (default), then include tasks with any start value.
 60
 61    area : str or bool or None, optional
 62        Any valid uuid of an area. Only include tasks matching that area.
 63        Special cases:
 64        - `area == False`, only include tasks _without_ an area.
 65        - `area == True`, only include tasks _with_ an area.
 66        - `area == None` (default), then include all tasks.
 67
 68    project : str or bool or None, optional
 69        Any valid uuid of a project. Only include tasks matching that project.
 70        Special cases:
 71        - `project == False`, only include tasks _without_ a project.
 72        - `project == True`, only include tasks _with_ a project.
 73        - `project == None` (default), then include all tasks.
 74
 75    heading : str or None, optional
 76        Any valid uuid of a heading. Only include tasks matching that heading.
 77        Special cases:
 78        - `heading == False`, only include tasks _without_ a heading.
 79        - `heading == True`, only include tasks _with_ a heading.
 80        - `heading == None` (default), then include all tasks.
 81
 82    tag : str or bool or None, optional
 83        Any valid title of a tag. Only include tasks matching that tag.
 84        Special cases:
 85        - `tag == False`, only include tasks _without_ tags.
 86        - `tag == True`, only include tasks _with_ tags.
 87        - `tag == None` (default), then include all tasks.
 88
 89    start_date : bool, str or None, optional
 90        - `start_date == False`, only include tasks _without_ a start date.
 91        - `start_date == True`, only include tasks with a start date.
 92        - `start_date == 'future'`, only include tasks with a future start date.
 93        - `start_date == 'past'`, only include tasks with a past start date.
 94          Note: this includes today's date.
 95        - `start_date == '[operator]YYYY-MM-DD'`; match with optional operator,
 96          for example: '2023-05-22', '<=2023-05-22', or '>2023-05-22'.
 97        - `start_date == None` (default), then include all tasks.
 98
 99    stop_date : same options as start_date, signifies the date of completion
100
101    deadline : same options as start_date, signifies the deadline
102
103    deadline_suppressed : bool or None, optional
104        "deadline suppressed" is a technical term used in the database.
105        When tasks have an overdue deadline they show up in Today.
106        Some of us "suppress" some tasks, that is, move them out of
107        Today and into Inbox, Anytime, or Someday. For those moved tasks
108        the deadline is said to be suppressed.
109        - `deadline_suppressed == True`, only include tasks with deadlines
110          and whose deadlines have been suppressed.
111        - `deadline_suppressed == False`, include any task _except_ those
112          with suppressed deadlines.
113        - `deadline_suppressed == None` (default), include any task.
114
115    trashed : bool or None, optional, default False
116        - `trashed == False` (default), only include non-trashed tasks.
117        - `trashed == True`, only include trashed tasks.
118        - `trashed == None`, include both kind of tasks.
119
120    context_trashed : bool or None, optional, default False
121        Some tasks may be within a context of a project or a heading.
122        This manages how to handle such tasks. The other tasks are not
123        affected by this setting.
124
125        - `context_trashed == False` (default), for tasks within a context,
126           only return tasks whose context has _not_ been trashed.
127        - `context_trashed == True`, for tasks within a context,
128           only return tasks whose context has been trashed.
129        - `context_trashed == None`, include both kind of tasks.
130
131    last : str, optional
132        Limit returned tasks to tasks created within the last X days,
133        weeks, or years. For example: '3d', '5w', or '1y'.
134
135        Takes as input an offset string of the format 'X[d/w/y]' where X
136        is a non-negative  integer that is followed by 'd', 'w', or 'y'
137        which stand for 'days', 'weeks', and 'years', respectively.
138
139    search_query : str, optional
140        The string value is passed to the SQL LIKE operator. It can thus
141        include placeholders such as '%' and '_'. Per default, it is
142        wrapped in '% ... %'.
143
144        Currently titles and notes of to-dos, projects, headings, and areas
145        are taken into account.
146
147    index : {'index', 'todayIndex'}, default 'index'
148        Database field to order result by.
149
150    count_only : bool, default False
151        Only output length of result. This is done by a SQL COUNT query.
152
153    print_sql : bool, default False
154        Print every SQL query performed. Some may contain '?' and ':'
155        characters, which correspond to SQLite parameter tokens.
156        See https://www.sqlite.org/lang_expr.html#varparam
157
158    filepath : str, optional
159        Any valid path of a SQLite database file generated by the Things app.
160        If the environment variable `THINGSDB` is set, then use that path.
161        Otherwise, access the default database path.
162
163    database : things.database.Database, optional
164        Any valid database object previously instantiated.
165
166    Returns
167    -------
168    list of dict (default)
169        Representing multiple tasks.
170    dict (if `uuid` is given)
171        Representing a single task.
172    int (`count_only == True`)
173        Count of matching Tasks.
174
175    Examples
176    --------
177    >>> things.tasks()
178    [{'uuid': '6Hf2qWBjWhq7B1xszwdo34', 'type': 'to-do', 'title':...
179    >>> things.tasks('DfYoiXcNLQssk9DkSoJV3Y')
180    {'uuid': 'DfYoiXcNLQssk9DkSoJV3Y', 'type': 'to-do', 'title': ...
181    >>> things.tasks(area='hIo1FJlAYGKt1Yj38vzKc3', include_items=True)
182    []
183    >>> things.tasks(status='completed', count_only=True)
184    12
185    >>> things.tasks(status='completed', last='1w', count_only=True)
186    0
187
188    """
189    database = pop_database(kwargs)
190    result = database.get_tasks(
191        uuid=uuid, status=kwargs.pop("status", "incomplete"), **kwargs
192    )
193
194    if kwargs.get("count_only"):
195        return result
196
197    # overwrite `include_items` if fetching a single task.
198    if uuid:
199        include_items = True
200
201    for task in result:
202        # TK: How costly of an operation is it to do this for every task?
203        # IF costly, then can it be made significantly more efficient
204        # by optimizing SQL calls?
205
206        if task.get("tags"):
207            task["tags"] = database.get_tags(task=task["uuid"])
208
209        if not include_items:
210            continue
211
212        # include items
213        if task["type"] == "to-do":
214            if task.get("checklist"):
215                task["checklist"] = database.get_checklist_items(task["uuid"])
216        elif task["type"] == "project":
217            project = task
218            project["items"] = items = tasks(
219                project=project["uuid"],
220                context_trashed=None,
221                include_items=True,
222                database=database,
223            )
224            # to-dos without headings appear before headings in app
225            items.sort(key=lambda item: item["type"], reverse=True)
226        elif task["type"] == "heading":
227            heading = task
228            heading["items"] = tasks(
229                type="to-do",
230                heading=heading["uuid"],
231                context_trashed=None,
232                include_items=True,
233                database=database,
234            )
235
236    if uuid:
237        result = result[0]
238
239    return result
240
241
242def areas(uuid=None, include_items=False, **kwargs):
243    """
244    Read areas into dicts.
245
246    Parameters
247    ----------
248    uuid : str or None, optional
249        Any valid uuid of an area. If None, then return all areas.
250
251    include_items : bool, default False
252        Include tasks and projects in each area.
253
254    tag : str or bool or None, optional
255        Any valid title of a tag. Only include areas matching that tag.
256        Special cases:
257        - `tag == False`, only include areas _without_ tags.
258        - `tag == True`, only include areas _with_ tags.
259        - `tag == None`, then ignore any tags present, that is,
260           include areas both with and without tags.
261
262    count_only : bool, default False
263        Only output length of result. This is done by a SQL COUNT query.
264
265    filepath : str, optional
266        Any valid path of a SQLite database file generated by the Things app.
267        If no path is provided, then access the default database path.
268
269    database : things.database.Database, optional
270        Any valid `things.database.Database` object previously instantiated.
271
272    Returns
273    -------
274    list of dict (default)
275        Representing Things areas.
276    dict (if `uuid` is given)
277        Representing a single Things area.
278    int (`count_only == True`)
279        Count of matching areas.
280
281    Examples
282    --------
283    >>> things.areas()
284    [{'uuid': 'Y3JC4XeyGWxzDocQL4aobo', 'type': 'area', 'title': 'Area 3'}, ...
285    >>> things.areas(tag='Home')
286    []
287    >>> things.areas(uuid='DciSFacytdrNG1nRaMJPgY')
288    {'uuid': 'DciSFacytdrNG1nRaMJPgY', 'type': 'area', 'title': 'Area 1', ...
289    >>> things.areas(include_items=True, tag='Errand')
290    [{'uuid': 'DciSFacytdrNG1nRaMJPgY', 'type': 'area', 'title': 'Area 1', ...
291    """
292    database = pop_database(kwargs)
293    result = database.get_areas(uuid=uuid, **kwargs)
294
295    if kwargs.get("count_only"):
296        return result
297
298    for area in result:
299        if area.get("tags"):
300            area["tags"] = database.get_tags(area=area["uuid"])
301        if include_items:
302            area["items"] = tasks(
303                area=area["uuid"], include_items=True, database=database
304            )
305
306    if uuid:
307        result = result[0]
308
309    return result
310
311
312def tags(title=None, include_items=False, **kwargs):
313    """
314    Read tags into dicts.
315
316    Parameters
317    ----------
318    title : str, optional
319        Any valid title of a tag. Include all items of said tag.
320        If None, then return all tags.
321
322    include_items : bool, default False
323        For each tag, include items tagged with that tag.
324        Items may include areas, tasks, and projects.
325
326    area : str, optional
327        Valid uuid of an area. Return tags of said area.
328
329    task : str, optional
330        Valid uuid of a task. Return tags of said task.
331
332    titles_only : bool, default False
333        If True, only return list of titles of tags.
334
335    filepath : str, optional
336        Any valid path of a SQLite database file generated by the Things app.
337        If no path is provided, then access the default database path.
338
339    database : things.database.Database, optional
340        Any valid database object previously instantiated.
341
342    Returns
343    -------
344    list of dict (default)
345        Representing tags.
346    list of str (if `titles_only == True` or area / task is given)
347        Representing tag titles.
348    dict (if `title` is given)
349        Representing a single Things tag.
350
351    Examples
352    --------
353    >>> things.tags()
354    [{'uuid': 'H96sVJwE7VJveAnv7itmux', 'type': 'tag', 'title': 'Errand', ...
355    >>> things.tags('Home')
356    {'uuid': 'CK9dARrf2ezbFvrVUUxkHE', 'type': 'tag', 'title': 'Home', ...
357    >>> things.tags(include_items=True)
358    [{'uuid': 'H96sVJwE7VJveAnv7itmux', 'type': 'tag', 'title': 'Errand', ...
359    >>> things.tags(task='2Ukg8I2nLukhyEM7wYiBeb')
360    []
361    """
362    database = pop_database(kwargs)
363    result = database.get_tags(title=title, **kwargs)
364
365    if include_items:
366        for tag in result:
367            tag_title = tag["title"]
368            tag["items"] = [
369                *areas(tag=tag_title, database=database),
370                *tasks(tag=tag_title, database=database),
371            ]
372
373    if title:
374        result = result[0]
375
376    return result
377
378
379def checklist_items(todo_uuid, **kwargs):
380    """
381    Read checklist items of to-dos into dicts.
382
383    Note: checklists are contained in the return value of
384    `things.todos(todo_uuid)` and `things.tasks(todo_uuid)`.
385
386    Parameters
387    ----------
388    todo_uuid : str, optional
389        A valid to-do uuid.
390
391    Returns
392    -------
393    list of dict
394        Checklist items.
395    """
396    database = pop_database(kwargs)
397    return database.get_checklist_items(todo_uuid=todo_uuid)
398
399
400# --------------------------------------------------
401# Utility API functions derived from above
402# --------------------------------------------------
403
404
405def search(query: str, **kwargs) -> List[Dict]:
406    """
407    Search tasks in the database.
408
409    Currently any part of a title and note of a to-do, project,
410    heading, or area is matched.
411
412    See the `search_query` parameter of `things.api.tasks` for details.
413
414    Examples
415    --------
416    >>> things.search('Today%yellow')
417    [{'uuid': '6Hf2qWBjWhq7B1xszwdo34',
418      'type': 'to-do',
419      'title': 'Upcoming To-Do in Today (yellow)',
420      'status': 'incomplete',
421      'notes': '',
422      ...
423    """
424    return tasks(search_query=query, **kwargs)
425
426
427def get(uuid, default=None, **kwargs):
428    """
429    Find an object by uuid. If not found, return `default`.
430
431    Currently supports tasks, projects, headings, areas, and tags.
432    """
433    try:
434        return tasks(uuid=uuid, **kwargs)
435    except ValueError:
436        pass
437
438    try:
439        return areas(uuid=uuid, **kwargs)
440    except ValueError:
441        pass
442
443    for tag in tags(**kwargs):
444        if tag["uuid"] == uuid:
445            return tag
446
447    return default
448
449
450# Filter by object type
451
452
453def todos(uuid=None, **kwargs):
454    """
455    Read to-dos into dicts.
456
457    See `things.api.tasks` for details on the optional parameters.
458    """
459    return tasks(uuid=uuid, type="to-do", **kwargs)
460
461
462def projects(uuid=None, **kwargs):
463    """
464    Read projects into dicts.
465
466    See `things.api.tasks` for details on the optional parameters.
467    """
468    return tasks(uuid=uuid, type="project", **kwargs)
469
470
471# Filter by collections in the Things app sidebar.
472
473
474def inbox(**kwargs):
475    """
476    Read Inbox into dicts.
477
478    See `things.api.tasks` for details on the optional parameters.
479    """
480    return tasks(start="Inbox", **kwargs)
481
482
483def today(**kwargs):
484    """
485    Read Today's tasks into dicts.
486
487    Note: The Things database reflects the state of the Things app when
488    it was last opened. For the Today tasks that means the database
489    might not be up to date anymore if you didn't open the app recently.
490    To get around this limitation, we here make a prediction of what
491    tasks would show up in Today if you were to open the app right now.
492    This prediction does not include repeating tasks at this time.
493
494    See `things.api.tasks` for details on the optional parameters.
495    """
496    database = pop_database(kwargs)
497    kwargs["database"] = database
498
499    regular_today_tasks = tasks(
500        start_date=True,
501        start="Anytime",
502        index="todayIndex",
503        **kwargs,
504    )
505
506    # Predictions of new to-dos indicated by a yellow dot in the app.
507
508    unconfirmed_scheduled_tasks = tasks(
509        start_date="past",
510        start="Someday",
511        index="todayIndex",
512        **kwargs,
513    )
514
515    unconfirmed_overdue_tasks = tasks(
516        start_date=False,
517        deadline="past",
518        deadline_suppressed=False,
519        **kwargs,
520    )
521
522    result = [
523        *regular_today_tasks,
524        *unconfirmed_scheduled_tasks,
525        *unconfirmed_overdue_tasks,
526    ]
527    result.sort(key=lambda task: (task["today_index"], task["start_date"]))
528
529    return result
530
531
532def upcoming(**kwargs):
533    """
534    Read Upcoming tasks into dicts.
535
536    Note: unscheduled tasks with a deadline are not included here.
537    See the `things.api.deadline` function instead.
538
539    For details on parameters, see `things.api.tasks`.
540    """
541    return tasks(start_date="future", start="Someday", **kwargs)
542
543
544def anytime(**kwargs):
545    """
546    Read Anytime tasks into dicts.
547
548    See `things.api.tasks` for details on the optional parameters.
549    """
550    return tasks(start="Anytime", **kwargs)
551
552
553def someday(**kwargs):
554    """
555    Read Someday tasks into dicts.
556
557    See `things.api.tasks` for details on the optional parameters.
558    """
559    return tasks(start_date=False, start="Someday", **kwargs)
560
561
562def logbook(**kwargs):
563    """
564    Read Logbook tasks into dicts.
565
566    See `things.api.tasks` for details on the optional parameters.
567    """
568    result = [*canceled(**kwargs), *completed(**kwargs)]
569    result.sort(key=lambda task: task["stop_date"], reverse=True)
570    return result
571
572
573def trash(**kwargs):
574    """
575    Read Trash tasks into dicts.
576
577    See `things.api.tasks` for details on the optional parameters.
578    """
579    return tasks(
580        trashed=True,
581        context_trashed=kwargs.pop("context_trashed", None),
582        status=kwargs.pop("status", None),
583        **kwargs,
584    )
585
586
587# Filter by various task properties
588
589
590def canceled(**kwargs):
591    """
592    Read canceled tasks into dicts.
593
594    See `things.api.tasks` for details on the optional parameters.
595    """
596    return tasks(status="canceled", **kwargs)
597
598
599def completed(**kwargs):
600    """
601    Read completed tasks into dicts.
602
603    See `things.api.tasks` for details on the optional parameters.
604
605    Examples
606    --------
607    >>> things.completed(count_only=True)
608    12
609    >>> things.completed(type='project', count_only=True)
610    0
611    >>> things.completed(type='to-do', last='1w')
612    []
613    """
614    return tasks(status="completed", **kwargs)
615
616
617def deadlines(**kwargs):
618    """
619    Read tasks with deadlines into dicts.
620
621    See `things.api.tasks` for details on the optional parameters.
622    """
623    result = tasks(deadline=True, **kwargs)
624    result.sort(key=lambda task: task["deadline"])
625    return result
626
627
628def last(offset, **kwargs):
629    """
630    Read tasks created within last X days, weeks, or years into dicts.
631
632    Per default, only incomplete tasks are included, but do see
633    `things.api.tasks` for details on the optional parameters.
634
635    Parameters
636    ----------
637    offset : str
638        A valid date offset such as '3d', '5w', or '1y'.
639        For details, see the `last` parameter of `things.api.tasks`.
640
641    Returns
642    -------
643    list of dict
644        Tasks within offset ordered by creation date. Newest first.
645
646    Examples
647    --------
648    >>> things.last('3d')
649    []
650    >>> things.last('1w', status='completed')
651    []
652    >>> things.last('1y', tag='Important', status='completed', count_only=True)
653    0
654
655    """
656    if offset is None:
657        raise ValueError(f"Invalid offset type: {offset!r}")
658
659    result = tasks(last=offset, **kwargs)
660    if result:
661        result.sort(key=lambda task: task["created"], reverse=True)
662
663    return result
664
665
666# Interact with Things app
667
668
669def token(**kwargs) -> Union[str, None]:
670    """
671    Read the Things URL scheme authentication token.
672
673    You can make good use of this token to modify existing Things data
674    using the Things URL Scheme. For details, see
675    [here](https://culturedcode.com/things/help/url-scheme/).
676    """
677    database = pop_database(kwargs)
678    return database.get_url_scheme_auth_token()
679
680
681def url(uuid=None, command="show", **query_parameters) -> str:
682    """
683    Return a things:///<command>?<query> url.
684
685    For details about available commands and their parameters
686    consult the Things URL Scheme documentation
687    [here](https://culturedcode.com/things/help/url-scheme/).
688
689    Parameters
690    ----------
691    uuid : str or None, optional
692        A valid uuid of any Things object.
693        If `None`, then 'id' is not added as a parameter unless
694        specified in `query_parameters`.
695
696    command : str, default 'show'
697        A valid command name.
698
699    **query_parameters:
700        Additional URL query parameters.
701
702    Examples
703    --------
704    >>> things.url('6Hf2qWBjWhq7B1xszwdo34')
705    'things:///show?id=6Hf2qWBjWhq7B1xszwdo34'
706    >>> things.url(command='update', uuid='6Hf2qWBjWhq7B1xszwdo34', title='new title')
707    'things:///update?id=6Hf2qWBjWhq7B1xszwdo34&title=new%20title&auth-token=vKkylosuSuGwxrz7qcklOw'
708    >>> things.url(command='add', title='new task', when='in 3 days', deadline='in 6 days')
709    'things:///add?title=new%20task&when=in%203%20days&deadline=in%206%20days'
710    >>> query_params = {'title': 'test title', 'list-id': 'ba5d1237-1dfa-4ab8-826b-7c27b517f29d'}
711    >>> things.url(command="add", **query_params)
712    'things:///add?title=test%20title&list-id=ba5d1237-1dfa-4ab8-826b-7c27b517f29d'
713    """
714    if uuid is not None:
715        query_parameters = {"id": uuid, **query_parameters}
716
717    # authenticate if needed
718    if command in ("update", "update-project"):
719        auth_token = query_parameters["auth-token"] = token()
720        if not auth_token:
721            raise ValueError("Things URL scheme authentication token could not be read")
722
723    query_string = urllib.parse.urlencode(
724        query_parameters, quote_via=urllib.parse.quote
725    )
726
727    return f"things:///{command}?{query_string}"
728
729
730# Alias for backwards compatiblity
731link = url
732
733
734def show(uuid):  # noqa
735    """
736    Show a certain uuid in the Things app.
737
738    Parameters
739    ----------
740    uuid : str
741        A valid uuid of any Things object.
742
743    Examples
744    --------
745    >>> tag = things.tags('Home')
746    >>> things.show(tag['uuid'])  # doctest: +SKIP
747    """
748    uri = url(uuid=uuid)
749    os.system(f"open {quote(uri)}")
750
751
752def complete(uuid):  # noqa
753    """
754    Set the status of a certain uuid to complete.
755
756    Parameters
757    ----------
758    uuid : str
759        A valid uuid of a project or to-do.
760
761    Examples
762    --------
763    >>> task = things.todos()[0]       # doctest: +SKIP
764    >>> things.complete(task['uuid'])  # doctest: +SKIP
765    """
766    uri = url(uuid=uuid, command="update", completed=True)
767    os.system(f"open {quote(uri)}")
768
769
770# Helper functions
771
772
773def pop_database(kwargs):
774    """Instantiate non-default database from `kwargs` if provided."""
775    filepath = kwargs.pop("filepath", None)
776    database = kwargs.pop("database", None)
777    print_sql = kwargs.pop("print_sql", False)
778
779    if not database:
780        database = Database(filepath=filepath, print_sql=print_sql)
781    return database
def tasks(uuid=None, include_items=False, **kwargs):
 24def tasks(uuid=None, include_items=False, **kwargs):  # noqa: C901
 25    """
 26    Read tasks into dicts.
 27
 28    Note: "task" is a technical term used in the database to refer to a
 29    to-do, project, or heading. For details, check the "type"-parameter.
 30
 31    Per default, only tasks marked as incomplete are included. If you
 32    want to include completed or canceled tasks in the result, check the
 33    "status"-parameter.
 34
 35    Parameters
 36    ----------
 37    uuid : str or None, optional
 38        Any valid task uuid. If None, then return all tasks matched.
 39
 40    include_items : bool, default False
 41        Include items contained within a task. These might include
 42        checklist items, headings, and to-dos.
 43
 44    type : {'to-do', 'heading', 'project', None}, optional
 45        Only return a specific type of task:
 46        - `'to-do'`:    may have a checklist; may be in an area and have tags.
 47        - `'project'`:  may have to-dos and headings; may be in an area and
 48                        have tags.
 49        - `'heading'`:  part of a project; groups tasks.
 50        - `None` (default): return all types of tasks.
 51
 52    status : {'incomplete', 'completed', 'canceled', None}, optional, \
 53        default 'incomplete'
 54
 55        Only include tasks matching that status. If `status == None`,
 56        then include tasks with any status value.
 57
 58    start : {'Inbox', 'Anytime', 'Someday', None}, optional
 59        Only include tasks matching that start value. If the argument is
 60        `None` (default), then include tasks with any start value.
 61
 62    area : str or bool or None, optional
 63        Any valid uuid of an area. Only include tasks matching that area.
 64        Special cases:
 65        - `area == False`, only include tasks _without_ an area.
 66        - `area == True`, only include tasks _with_ an area.
 67        - `area == None` (default), then include all tasks.
 68
 69    project : str or bool or None, optional
 70        Any valid uuid of a project. Only include tasks matching that project.
 71        Special cases:
 72        - `project == False`, only include tasks _without_ a project.
 73        - `project == True`, only include tasks _with_ a project.
 74        - `project == None` (default), then include all tasks.
 75
 76    heading : str or None, optional
 77        Any valid uuid of a heading. Only include tasks matching that heading.
 78        Special cases:
 79        - `heading == False`, only include tasks _without_ a heading.
 80        - `heading == True`, only include tasks _with_ a heading.
 81        - `heading == None` (default), then include all tasks.
 82
 83    tag : str or bool or None, optional
 84        Any valid title of a tag. Only include tasks matching that tag.
 85        Special cases:
 86        - `tag == False`, only include tasks _without_ tags.
 87        - `tag == True`, only include tasks _with_ tags.
 88        - `tag == None` (default), then include all tasks.
 89
 90    start_date : bool, str or None, optional
 91        - `start_date == False`, only include tasks _without_ a start date.
 92        - `start_date == True`, only include tasks with a start date.
 93        - `start_date == 'future'`, only include tasks with a future start date.
 94        - `start_date == 'past'`, only include tasks with a past start date.
 95          Note: this includes today's date.
 96        - `start_date == '[operator]YYYY-MM-DD'`; match with optional operator,
 97          for example: '2023-05-22', '<=2023-05-22', or '>2023-05-22'.
 98        - `start_date == None` (default), then include all tasks.
 99
100    stop_date : same options as start_date, signifies the date of completion
101
102    deadline : same options as start_date, signifies the deadline
103
104    deadline_suppressed : bool or None, optional
105        "deadline suppressed" is a technical term used in the database.
106        When tasks have an overdue deadline they show up in Today.
107        Some of us "suppress" some tasks, that is, move them out of
108        Today and into Inbox, Anytime, or Someday. For those moved tasks
109        the deadline is said to be suppressed.
110        - `deadline_suppressed == True`, only include tasks with deadlines
111          and whose deadlines have been suppressed.
112        - `deadline_suppressed == False`, include any task _except_ those
113          with suppressed deadlines.
114        - `deadline_suppressed == None` (default), include any task.
115
116    trashed : bool or None, optional, default False
117        - `trashed == False` (default), only include non-trashed tasks.
118        - `trashed == True`, only include trashed tasks.
119        - `trashed == None`, include both kind of tasks.
120
121    context_trashed : bool or None, optional, default False
122        Some tasks may be within a context of a project or a heading.
123        This manages how to handle such tasks. The other tasks are not
124        affected by this setting.
125
126        - `context_trashed == False` (default), for tasks within a context,
127           only return tasks whose context has _not_ been trashed.
128        - `context_trashed == True`, for tasks within a context,
129           only return tasks whose context has been trashed.
130        - `context_trashed == None`, include both kind of tasks.
131
132    last : str, optional
133        Limit returned tasks to tasks created within the last X days,
134        weeks, or years. For example: '3d', '5w', or '1y'.
135
136        Takes as input an offset string of the format 'X[d/w/y]' where X
137        is a non-negative  integer that is followed by 'd', 'w', or 'y'
138        which stand for 'days', 'weeks', and 'years', respectively.
139
140    search_query : str, optional
141        The string value is passed to the SQL LIKE operator. It can thus
142        include placeholders such as '%' and '_'. Per default, it is
143        wrapped in '% ... %'.
144
145        Currently titles and notes of to-dos, projects, headings, and areas
146        are taken into account.
147
148    index : {'index', 'todayIndex'}, default 'index'
149        Database field to order result by.
150
151    count_only : bool, default False
152        Only output length of result. This is done by a SQL COUNT query.
153
154    print_sql : bool, default False
155        Print every SQL query performed. Some may contain '?' and ':'
156        characters, which correspond to SQLite parameter tokens.
157        See https://www.sqlite.org/lang_expr.html#varparam
158
159    filepath : str, optional
160        Any valid path of a SQLite database file generated by the Things app.
161        If the environment variable `THINGSDB` is set, then use that path.
162        Otherwise, access the default database path.
163
164    database : things.database.Database, optional
165        Any valid database object previously instantiated.
166
167    Returns
168    -------
169    list of dict (default)
170        Representing multiple tasks.
171    dict (if `uuid` is given)
172        Representing a single task.
173    int (`count_only == True`)
174        Count of matching Tasks.
175
176    Examples
177    --------
178    >>> things.tasks()
179    [{'uuid': '6Hf2qWBjWhq7B1xszwdo34', 'type': 'to-do', 'title':...
180    >>> things.tasks('DfYoiXcNLQssk9DkSoJV3Y')
181    {'uuid': 'DfYoiXcNLQssk9DkSoJV3Y', 'type': 'to-do', 'title': ...
182    >>> things.tasks(area='hIo1FJlAYGKt1Yj38vzKc3', include_items=True)
183    []
184    >>> things.tasks(status='completed', count_only=True)
185    12
186    >>> things.tasks(status='completed', last='1w', count_only=True)
187    0
188
189    """
190    database = pop_database(kwargs)
191    result = database.get_tasks(
192        uuid=uuid, status=kwargs.pop("status", "incomplete"), **kwargs
193    )
194
195    if kwargs.get("count_only"):
196        return result
197
198    # overwrite `include_items` if fetching a single task.
199    if uuid:
200        include_items = True
201
202    for task in result:
203        # TK: How costly of an operation is it to do this for every task?
204        # IF costly, then can it be made significantly more efficient
205        # by optimizing SQL calls?
206
207        if task.get("tags"):
208            task["tags"] = database.get_tags(task=task["uuid"])
209
210        if not include_items:
211            continue
212
213        # include items
214        if task["type"] == "to-do":
215            if task.get("checklist"):
216                task["checklist"] = database.get_checklist_items(task["uuid"])
217        elif task["type"] == "project":
218            project = task
219            project["items"] = items = tasks(
220                project=project["uuid"],
221                context_trashed=None,
222                include_items=True,
223                database=database,
224            )
225            # to-dos without headings appear before headings in app
226            items.sort(key=lambda item: item["type"], reverse=True)
227        elif task["type"] == "heading":
228            heading = task
229            heading["items"] = tasks(
230                type="to-do",
231                heading=heading["uuid"],
232                context_trashed=None,
233                include_items=True,
234                database=database,
235            )
236
237    if uuid:
238        result = result[0]
239
240    return result

Read tasks into dicts.

Note: "task" is a technical term used in the database to refer to a to-do, project, or heading. For details, check the "type"-parameter.

Per default, only tasks marked as incomplete are included. If you want to include completed or canceled tasks in the result, check the "status"-parameter.

Parameters
  • uuid (str or None, optional): Any valid task uuid. If None, then return all tasks matched.
  • include_items (bool, default False): Include items contained within a task. These might include checklist items, headings, and to-dos.
  • type ({'to-do', 'heading', 'project', None}, optional): Only return a specific type of task:
    • 'to-do': may have a checklist; may be in an area and have tags.
    • 'project': may have to-dos and headings; may be in an area and have tags.
    • 'heading': part of a project; groups tasks.
    • None (default): return all types of tasks.
  • status ({'incomplete', 'completed', 'canceled', None}, optional, default 'incomplete'): Only include tasks matching that status. If status == None, then include tasks with any status value.
  • start ({'Inbox', 'Anytime', 'Someday', None}, optional): Only include tasks matching that start value. If the argument is None (default), then include tasks with any start value.
  • area (str or bool or None, optional): Any valid uuid of an area. Only include tasks matching that area. Special cases:
    • area == False, only include tasks _without_ an area.
    • area == True, only include tasks _with_ an area.
    • area == None (default), then include all tasks.
  • project (str or bool or None, optional): Any valid uuid of a project. Only include tasks matching that project. Special cases:
    • project == False, only include tasks _without_ a project.
    • project == True, only include tasks _with_ a project.
    • project == None (default), then include all tasks.
  • heading (str or None, optional): Any valid uuid of a heading. Only include tasks matching that heading. Special cases:
    • heading == False, only include tasks _without_ a heading.
    • heading == True, only include tasks _with_ a heading.
    • heading == None (default), then include all tasks.
  • tag (str or bool or None, optional): Any valid title of a tag. Only include tasks matching that tag. Special cases:
    • tag == False, only include tasks _without_ tags.
    • tag == True, only include tasks _with_ tags.
    • tag == None (default), then include all tasks.
  • start_date (bool, str or None, optional):
    • start_date == False, only include tasks _without_ a start date.
    • start_date == True, only include tasks with a start date.
    • start_date == 'future', only include tasks with a future start date.
    • start_date == 'past', only include tasks with a past start date. Note: this includes today's date.
    • start_date == '[operator]YYYY-MM-DD'; match with optional operator, for example: '2023-05-22', '<=2023-05-22', or '>2023-05-22'.
    • start_date == None (default), then include all tasks.
  • stop_date (same options as start_date, signifies the date of completion):

  • deadline (same options as start_date, signifies the deadline):

  • deadline_suppressed (bool or None, optional): "deadline suppressed" is a technical term used in the database. When tasks have an overdue deadline they show up in Today. Some of us "suppress" some tasks, that is, move them out of Today and into Inbox, Anytime, or Someday. For those moved tasks the deadline is said to be suppressed.

    • deadline_suppressed == True, only include tasks with deadlines and whose deadlines have been suppressed.
    • deadline_suppressed == False, include any task _except_ those with suppressed deadlines.
    • deadline_suppressed == None (default), include any task.
  • trashed (bool or None, optional, default False):
    • trashed == False (default), only include non-trashed tasks.
    • trashed == True, only include trashed tasks.
    • trashed == None, include both kind of tasks.
  • context_trashed (bool or None, optional, default False): Some tasks may be within a context of a project or a heading. This manages how to handle such tasks. The other tasks are not affected by this setting.

    • context_trashed == False (default), for tasks within a context, only return tasks whose context has _not_ been trashed.
    • context_trashed == True, for tasks within a context, only return tasks whose context has been trashed.
    • context_trashed == None, include both kind of tasks.
  • last (str, optional): Limit returned tasks to tasks created within the last X days, weeks, or years. For example: '3d', '5w', or '1y'.

    Takes as input an offset string of the format 'X[d/w/y]' where X is a non-negative integer that is followed by 'd', 'w', or 'y' which stand for 'days', 'weeks', and 'years', respectively.

  • search_query (str, optional): The string value is passed to the SQL LIKE operator. It can thus include placeholders such as '%' and '_'. Per default, it is wrapped in '% ... %'.

    Currently titles and notes of to-dos, projects, headings, and areas are taken into account.

  • index ({'index', 'todayIndex'}, default 'index'): Database field to order result by.
  • count_only (bool, default False): Only output length of result. This is done by a SQL COUNT query.
  • print_sql (bool, default False): Print every SQL query performed. Some may contain '?' and ':' characters, which correspond to SQLite parameter tokens. See https://www.sqlite.org/lang_expr.html#varparam
  • filepath (str, optional): Any valid path of a SQLite database file generated by the Things app. If the environment variable THINGSDB is set, then use that path. Otherwise, access the default database path.
  • database (things.database.Database, optional): Any valid database object previously instantiated.
Returns
  • list of dict (default): Representing multiple tasks.
  • dict (if uuid is given): Representing a single task.
  • int (count_only == True): Count of matching Tasks.
Examples
>>> things.tasks()
[{'uuid': '6Hf2qWBjWhq7B1xszwdo34', 'type': 'to-do', 'title':...
>>> things.tasks('DfYoiXcNLQssk9DkSoJV3Y')
{'uuid': 'DfYoiXcNLQssk9DkSoJV3Y', 'type': 'to-do', 'title': ...
>>> things.tasks(area='hIo1FJlAYGKt1Yj38vzKc3', include_items=True)
[]
>>> things.tasks(status='completed', count_only=True)
12
>>> things.tasks(status='completed', last='1w', count_only=True)
0
def areas(uuid=None, include_items=False, **kwargs):
243def areas(uuid=None, include_items=False, **kwargs):
244    """
245    Read areas into dicts.
246
247    Parameters
248    ----------
249    uuid : str or None, optional
250        Any valid uuid of an area. If None, then return all areas.
251
252    include_items : bool, default False
253        Include tasks and projects in each area.
254
255    tag : str or bool or None, optional
256        Any valid title of a tag. Only include areas matching that tag.
257        Special cases:
258        - `tag == False`, only include areas _without_ tags.
259        - `tag == True`, only include areas _with_ tags.
260        - `tag == None`, then ignore any tags present, that is,
261           include areas both with and without tags.
262
263    count_only : bool, default False
264        Only output length of result. This is done by a SQL COUNT query.
265
266    filepath : str, optional
267        Any valid path of a SQLite database file generated by the Things app.
268        If no path is provided, then access the default database path.
269
270    database : things.database.Database, optional
271        Any valid `things.database.Database` object previously instantiated.
272
273    Returns
274    -------
275    list of dict (default)
276        Representing Things areas.
277    dict (if `uuid` is given)
278        Representing a single Things area.
279    int (`count_only == True`)
280        Count of matching areas.
281
282    Examples
283    --------
284    >>> things.areas()
285    [{'uuid': 'Y3JC4XeyGWxzDocQL4aobo', 'type': 'area', 'title': 'Area 3'}, ...
286    >>> things.areas(tag='Home')
287    []
288    >>> things.areas(uuid='DciSFacytdrNG1nRaMJPgY')
289    {'uuid': 'DciSFacytdrNG1nRaMJPgY', 'type': 'area', 'title': 'Area 1', ...
290    >>> things.areas(include_items=True, tag='Errand')
291    [{'uuid': 'DciSFacytdrNG1nRaMJPgY', 'type': 'area', 'title': 'Area 1', ...
292    """
293    database = pop_database(kwargs)
294    result = database.get_areas(uuid=uuid, **kwargs)
295
296    if kwargs.get("count_only"):
297        return result
298
299    for area in result:
300        if area.get("tags"):
301            area["tags"] = database.get_tags(area=area["uuid"])
302        if include_items:
303            area["items"] = tasks(
304                area=area["uuid"], include_items=True, database=database
305            )
306
307    if uuid:
308        result = result[0]
309
310    return result

Read areas into dicts.

Parameters
  • uuid (str or None, optional): Any valid uuid of an area. If None, then return all areas.
  • include_items (bool, default False): Include tasks and projects in each area.
  • tag (str or bool or None, optional): Any valid title of a tag. Only include areas matching that tag. Special cases:
    • tag == False, only include areas _without_ tags.
    • tag == True, only include areas _with_ tags.
    • tag == None, then ignore any tags present, that is, include areas both with and without tags.
  • count_only (bool, default False): Only output length of result. This is done by a SQL COUNT query.
  • filepath (str, optional): Any valid path of a SQLite database file generated by the Things app. If no path is provided, then access the default database path.
  • database (things.database.Database, optional): Any valid things.database.Database object previously instantiated.
Returns
  • list of dict (default): Representing Things areas.
  • dict (if uuid is given): Representing a single Things area.
  • int (count_only == True): Count of matching areas.
Examples
>>> things.areas()
[{'uuid': 'Y3JC4XeyGWxzDocQL4aobo', 'type': 'area', 'title': 'Area 3'}, ...
>>> things.areas(tag='Home')
[]
>>> things.areas(uuid='DciSFacytdrNG1nRaMJPgY')
{'uuid': 'DciSFacytdrNG1nRaMJPgY', 'type': 'area', 'title': 'Area 1', ...
>>> things.areas(include_items=True, tag='Errand')
[{'uuid': 'DciSFacytdrNG1nRaMJPgY', 'type': 'area', 'title': 'Area 1', ...
def tags(title=None, include_items=False, **kwargs):
313def tags(title=None, include_items=False, **kwargs):
314    """
315    Read tags into dicts.
316
317    Parameters
318    ----------
319    title : str, optional
320        Any valid title of a tag. Include all items of said tag.
321        If None, then return all tags.
322
323    include_items : bool, default False
324        For each tag, include items tagged with that tag.
325        Items may include areas, tasks, and projects.
326
327    area : str, optional
328        Valid uuid of an area. Return tags of said area.
329
330    task : str, optional
331        Valid uuid of a task. Return tags of said task.
332
333    titles_only : bool, default False
334        If True, only return list of titles of tags.
335
336    filepath : str, optional
337        Any valid path of a SQLite database file generated by the Things app.
338        If no path is provided, then access the default database path.
339
340    database : things.database.Database, optional
341        Any valid database object previously instantiated.
342
343    Returns
344    -------
345    list of dict (default)
346        Representing tags.
347    list of str (if `titles_only == True` or area / task is given)
348        Representing tag titles.
349    dict (if `title` is given)
350        Representing a single Things tag.
351
352    Examples
353    --------
354    >>> things.tags()
355    [{'uuid': 'H96sVJwE7VJveAnv7itmux', 'type': 'tag', 'title': 'Errand', ...
356    >>> things.tags('Home')
357    {'uuid': 'CK9dARrf2ezbFvrVUUxkHE', 'type': 'tag', 'title': 'Home', ...
358    >>> things.tags(include_items=True)
359    [{'uuid': 'H96sVJwE7VJveAnv7itmux', 'type': 'tag', 'title': 'Errand', ...
360    >>> things.tags(task='2Ukg8I2nLukhyEM7wYiBeb')
361    []
362    """
363    database = pop_database(kwargs)
364    result = database.get_tags(title=title, **kwargs)
365
366    if include_items:
367        for tag in result:
368            tag_title = tag["title"]
369            tag["items"] = [
370                *areas(tag=tag_title, database=database),
371                *tasks(tag=tag_title, database=database),
372            ]
373
374    if title:
375        result = result[0]
376
377    return result

Read tags into dicts.

Parameters
  • title (str, optional): Any valid title of a tag. Include all items of said tag. If None, then return all tags.
  • include_items (bool, default False): For each tag, include items tagged with that tag. Items may include areas, tasks, and projects.
  • area (str, optional): Valid uuid of an area. Return tags of said area.
  • task (str, optional): Valid uuid of a task. Return tags of said task.
  • titles_only (bool, default False): If True, only return list of titles of tags.
  • filepath (str, optional): Any valid path of a SQLite database file generated by the Things app. If no path is provided, then access the default database path.
  • database (things.database.Database, optional): Any valid database object previously instantiated.
Returns
  • list of dict (default): Representing tags.
  • list of str (if titles_only == True or area / task is given): Representing tag titles.
  • dict (if title is given): Representing a single Things tag.
Examples
>>> things.tags()
[{'uuid': 'H96sVJwE7VJveAnv7itmux', 'type': 'tag', 'title': 'Errand', ...
>>> things.tags('Home')
{'uuid': 'CK9dARrf2ezbFvrVUUxkHE', 'type': 'tag', 'title': 'Home', ...
>>> things.tags(include_items=True)
[{'uuid': 'H96sVJwE7VJveAnv7itmux', 'type': 'tag', 'title': 'Errand', ...
>>> things.tags(task='2Ukg8I2nLukhyEM7wYiBeb')
[]
def checklist_items(todo_uuid, **kwargs):
380def checklist_items(todo_uuid, **kwargs):
381    """
382    Read checklist items of to-dos into dicts.
383
384    Note: checklists are contained in the return value of
385    `things.todos(todo_uuid)` and `things.tasks(todo_uuid)`.
386
387    Parameters
388    ----------
389    todo_uuid : str, optional
390        A valid to-do uuid.
391
392    Returns
393    -------
394    list of dict
395        Checklist items.
396    """
397    database = pop_database(kwargs)
398    return database.get_checklist_items(todo_uuid=todo_uuid)

Read checklist items of to-dos into dicts.

Note: checklists are contained in the return value of things.todos(todo_uuid) and things.tasks(todo_uuid).

Parameters
  • todo_uuid (str, optional): A valid to-do uuid.
Returns
  • list of dict: Checklist items.
def get(uuid, default=None, **kwargs):
428def get(uuid, default=None, **kwargs):
429    """
430    Find an object by uuid. If not found, return `default`.
431
432    Currently supports tasks, projects, headings, areas, and tags.
433    """
434    try:
435        return tasks(uuid=uuid, **kwargs)
436    except ValueError:
437        pass
438
439    try:
440        return areas(uuid=uuid, **kwargs)
441    except ValueError:
442        pass
443
444    for tag in tags(**kwargs):
445        if tag["uuid"] == uuid:
446            return tag
447
448    return default

Find an object by uuid. If not found, return default.

Currently supports tasks, projects, headings, areas, and tags.

def todos(uuid=None, **kwargs):
454def todos(uuid=None, **kwargs):
455    """
456    Read to-dos into dicts.
457
458    See `things.api.tasks` for details on the optional parameters.
459    """
460    return tasks(uuid=uuid, type="to-do", **kwargs)

Read to-dos into dicts.

See things.api.tasks for details on the optional parameters.

def projects(uuid=None, **kwargs):
463def projects(uuid=None, **kwargs):
464    """
465    Read projects into dicts.
466
467    See `things.api.tasks` for details on the optional parameters.
468    """
469    return tasks(uuid=uuid, type="project", **kwargs)

Read projects into dicts.

See things.api.tasks for details on the optional parameters.

def inbox(**kwargs):
475def inbox(**kwargs):
476    """
477    Read Inbox into dicts.
478
479    See `things.api.tasks` for details on the optional parameters.
480    """
481    return tasks(start="Inbox", **kwargs)

Read Inbox into dicts.

See things.api.tasks for details on the optional parameters.

def today(**kwargs):
484def today(**kwargs):
485    """
486    Read Today's tasks into dicts.
487
488    Note: The Things database reflects the state of the Things app when
489    it was last opened. For the Today tasks that means the database
490    might not be up to date anymore if you didn't open the app recently.
491    To get around this limitation, we here make a prediction of what
492    tasks would show up in Today if you were to open the app right now.
493    This prediction does not include repeating tasks at this time.
494
495    See `things.api.tasks` for details on the optional parameters.
496    """
497    database = pop_database(kwargs)
498    kwargs["database"] = database
499
500    regular_today_tasks = tasks(
501        start_date=True,
502        start="Anytime",
503        index="todayIndex",
504        **kwargs,
505    )
506
507    # Predictions of new to-dos indicated by a yellow dot in the app.
508
509    unconfirmed_scheduled_tasks = tasks(
510        start_date="past",
511        start="Someday",
512        index="todayIndex",
513        **kwargs,
514    )
515
516    unconfirmed_overdue_tasks = tasks(
517        start_date=False,
518        deadline="past",
519        deadline_suppressed=False,
520        **kwargs,
521    )
522
523    result = [
524        *regular_today_tasks,
525        *unconfirmed_scheduled_tasks,
526        *unconfirmed_overdue_tasks,
527    ]
528    result.sort(key=lambda task: (task["today_index"], task["start_date"]))
529
530    return result

Read Today's tasks into dicts.

Note: The Things database reflects the state of the Things app when it was last opened. For the Today tasks that means the database might not be up to date anymore if you didn't open the app recently. To get around this limitation, we here make a prediction of what tasks would show up in Today if you were to open the app right now. This prediction does not include repeating tasks at this time.

See things.api.tasks for details on the optional parameters.

def upcoming(**kwargs):
533def upcoming(**kwargs):
534    """
535    Read Upcoming tasks into dicts.
536
537    Note: unscheduled tasks with a deadline are not included here.
538    See the `things.api.deadline` function instead.
539
540    For details on parameters, see `things.api.tasks`.
541    """
542    return tasks(start_date="future", start="Someday", **kwargs)

Read Upcoming tasks into dicts.

Note: unscheduled tasks with a deadline are not included here. See the things.api.deadline function instead.

For details on parameters, see things.api.tasks.

def anytime(**kwargs):
545def anytime(**kwargs):
546    """
547    Read Anytime tasks into dicts.
548
549    See `things.api.tasks` for details on the optional parameters.
550    """
551    return tasks(start="Anytime", **kwargs)

Read Anytime tasks into dicts.

See things.api.tasks for details on the optional parameters.

def someday(**kwargs):
554def someday(**kwargs):
555    """
556    Read Someday tasks into dicts.
557
558    See `things.api.tasks` for details on the optional parameters.
559    """
560    return tasks(start_date=False, start="Someday", **kwargs)

Read Someday tasks into dicts.

See things.api.tasks for details on the optional parameters.

def logbook(**kwargs):
563def logbook(**kwargs):
564    """
565    Read Logbook tasks into dicts.
566
567    See `things.api.tasks` for details on the optional parameters.
568    """
569    result = [*canceled(**kwargs), *completed(**kwargs)]
570    result.sort(key=lambda task: task["stop_date"], reverse=True)
571    return result

Read Logbook tasks into dicts.

See things.api.tasks for details on the optional parameters.

def trash(**kwargs):
574def trash(**kwargs):
575    """
576    Read Trash tasks into dicts.
577
578    See `things.api.tasks` for details on the optional parameters.
579    """
580    return tasks(
581        trashed=True,
582        context_trashed=kwargs.pop("context_trashed", None),
583        status=kwargs.pop("status", None),
584        **kwargs,
585    )

Read Trash tasks into dicts.

See things.api.tasks for details on the optional parameters.

def canceled(**kwargs):
591def canceled(**kwargs):
592    """
593    Read canceled tasks into dicts.
594
595    See `things.api.tasks` for details on the optional parameters.
596    """
597    return tasks(status="canceled", **kwargs)

Read canceled tasks into dicts.

See things.api.tasks for details on the optional parameters.

def completed(**kwargs):
600def completed(**kwargs):
601    """
602    Read completed tasks into dicts.
603
604    See `things.api.tasks` for details on the optional parameters.
605
606    Examples
607    --------
608    >>> things.completed(count_only=True)
609    12
610    >>> things.completed(type='project', count_only=True)
611    0
612    >>> things.completed(type='to-do', last='1w')
613    []
614    """
615    return tasks(status="completed", **kwargs)

Read completed tasks into dicts.

See things.api.tasks for details on the optional parameters.

Examples
>>> things.completed(count_only=True)
12
>>> things.completed(type='project', count_only=True)
0
>>> things.completed(type='to-do', last='1w')
[]
def deadlines(**kwargs):
618def deadlines(**kwargs):
619    """
620    Read tasks with deadlines into dicts.
621
622    See `things.api.tasks` for details on the optional parameters.
623    """
624    result = tasks(deadline=True, **kwargs)
625    result.sort(key=lambda task: task["deadline"])
626    return result

Read tasks with deadlines into dicts.

See things.api.tasks for details on the optional parameters.

def last(offset, **kwargs):
629def last(offset, **kwargs):
630    """
631    Read tasks created within last X days, weeks, or years into dicts.
632
633    Per default, only incomplete tasks are included, but do see
634    `things.api.tasks` for details on the optional parameters.
635
636    Parameters
637    ----------
638    offset : str
639        A valid date offset such as '3d', '5w', or '1y'.
640        For details, see the `last` parameter of `things.api.tasks`.
641
642    Returns
643    -------
644    list of dict
645        Tasks within offset ordered by creation date. Newest first.
646
647    Examples
648    --------
649    >>> things.last('3d')
650    []
651    >>> things.last('1w', status='completed')
652    []
653    >>> things.last('1y', tag='Important', status='completed', count_only=True)
654    0
655
656    """
657    if offset is None:
658        raise ValueError(f"Invalid offset type: {offset!r}")
659
660    result = tasks(last=offset, **kwargs)
661    if result:
662        result.sort(key=lambda task: task["created"], reverse=True)
663
664    return result

Read tasks created within last X days, weeks, or years into dicts.

Per default, only incomplete tasks are included, but do see things.api.tasks for details on the optional parameters.

Parameters
  • offset (str): A valid date offset such as '3d', '5w', or '1y'. For details, see the last parameter of things.api.tasks.
Returns
  • list of dict: Tasks within offset ordered by creation date. Newest first.
Examples
>>> things.last('3d')
[]
>>> things.last('1w', status='completed')
[]
>>> things.last('1y', tag='Important', status='completed', count_only=True)
0
def token(**kwargs) -> Optional[str]:
670def token(**kwargs) -> Union[str, None]:
671    """
672    Read the Things URL scheme authentication token.
673
674    You can make good use of this token to modify existing Things data
675    using the Things URL Scheme. For details, see
676    [here](https://culturedcode.com/things/help/url-scheme/).
677    """
678    database = pop_database(kwargs)
679    return database.get_url_scheme_auth_token()

Read the Things URL scheme authentication token.

You can make good use of this token to modify existing Things data using the Things URL Scheme. For details, see here.

def url(uuid=None, command='show', **query_parameters) -> str:
682def url(uuid=None, command="show", **query_parameters) -> str:
683    """
684    Return a things:///<command>?<query> url.
685
686    For details about available commands and their parameters
687    consult the Things URL Scheme documentation
688    [here](https://culturedcode.com/things/help/url-scheme/).
689
690    Parameters
691    ----------
692    uuid : str or None, optional
693        A valid uuid of any Things object.
694        If `None`, then 'id' is not added as a parameter unless
695        specified in `query_parameters`.
696
697    command : str, default 'show'
698        A valid command name.
699
700    **query_parameters:
701        Additional URL query parameters.
702
703    Examples
704    --------
705    >>> things.url('6Hf2qWBjWhq7B1xszwdo34')
706    'things:///show?id=6Hf2qWBjWhq7B1xszwdo34'
707    >>> things.url(command='update', uuid='6Hf2qWBjWhq7B1xszwdo34', title='new title')
708    'things:///update?id=6Hf2qWBjWhq7B1xszwdo34&title=new%20title&auth-token=vKkylosuSuGwxrz7qcklOw'
709    >>> things.url(command='add', title='new task', when='in 3 days', deadline='in 6 days')
710    'things:///add?title=new%20task&when=in%203%20days&deadline=in%206%20days'
711    >>> query_params = {'title': 'test title', 'list-id': 'ba5d1237-1dfa-4ab8-826b-7c27b517f29d'}
712    >>> things.url(command="add", **query_params)
713    'things:///add?title=test%20title&list-id=ba5d1237-1dfa-4ab8-826b-7c27b517f29d'
714    """
715    if uuid is not None:
716        query_parameters = {"id": uuid, **query_parameters}
717
718    # authenticate if needed
719    if command in ("update", "update-project"):
720        auth_token = query_parameters["auth-token"] = token()
721        if not auth_token:
722            raise ValueError("Things URL scheme authentication token could not be read")
723
724    query_string = urllib.parse.urlencode(
725        query_parameters, quote_via=urllib.parse.quote
726    )
727
728    return f"things:///{command}?{query_string}"

Return a things:///? url.

For details about available commands and their parameters consult the Things URL Scheme documentation here.

Parameters
  • uuid (str or None, optional): A valid uuid of any Things object. If None, then 'id' is not added as a parameter unless specified in query_parameters.
  • command (str, default 'show'): A valid command name.
  • **query_parameters:: Additional URL query parameters.
Examples
>>> things.url('6Hf2qWBjWhq7B1xszwdo34')
'things:///show?id=6Hf2qWBjWhq7B1xszwdo34'
>>> things.url(command='update', uuid='6Hf2qWBjWhq7B1xszwdo34', title='new title')
'things:///update?id=6Hf2qWBjWhq7B1xszwdo34&title=new%20title&auth-token=vKkylosuSuGwxrz7qcklOw'
>>> things.url(command='add', title='new task', when='in 3 days', deadline='in 6 days')
'things:///add?title=new%20task&when=in%203%20days&deadline=in%206%20days'
>>> query_params = {'title': 'test title', 'list-id': 'ba5d1237-1dfa-4ab8-826b-7c27b517f29d'}
>>> things.url(command="add", **query_params)
'things:///add?title=test%20title&list-id=ba5d1237-1dfa-4ab8-826b-7c27b517f29d'
def show(uuid):
735def show(uuid):  # noqa
736    """
737    Show a certain uuid in the Things app.
738
739    Parameters
740    ----------
741    uuid : str
742        A valid uuid of any Things object.
743
744    Examples
745    --------
746    >>> tag = things.tags('Home')
747    >>> things.show(tag['uuid'])  # doctest: +SKIP
748    """
749    uri = url(uuid=uuid)
750    os.system(f"open {quote(uri)}")

Show a certain uuid in the Things app.

Parameters
  • uuid (str): A valid uuid of any Things object.
Examples
>>> tag = things.tags('Home')
>>> things.show(tag['uuid'])  # doctest: +SKIP
def complete(uuid):
753def complete(uuid):  # noqa
754    """
755    Set the status of a certain uuid to complete.
756
757    Parameters
758    ----------
759    uuid : str
760        A valid uuid of a project or to-do.
761
762    Examples
763    --------
764    >>> task = things.todos()[0]       # doctest: +SKIP
765    >>> things.complete(task['uuid'])  # doctest: +SKIP
766    """
767    uri = url(uuid=uuid, command="update", completed=True)
768    os.system(f"open {quote(uri)}")

Set the status of a certain uuid to complete.

Parameters
  • uuid (str): A valid uuid of a project or to-do.
Examples
>>> task = things.todos()[0]       # doctest: +SKIP
>>> things.complete(task['uuid'])  # doctest: +SKIP
def pop_database(kwargs):
774def pop_database(kwargs):
775    """Instantiate non-default database from `kwargs` if provided."""
776    filepath = kwargs.pop("filepath", None)
777    database = kwargs.pop("database", None)
778    print_sql = kwargs.pop("print_sql", False)
779
780    if not database:
781        database = Database(filepath=filepath, print_sql=print_sql)
782    return database

Instantiate non-default database from kwargs if provided.