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

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)
10
>>> things.tasks(status='completed', last='1w', count_only=True)
0
def areas(uuid=None, include_items=False, **kwargs):
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

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):
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

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):
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)

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):
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

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

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

def todos(uuid=None, **kwargs):
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)

Read to-dos into dicts.

See tasks for details on the optional parameters.

def projects(uuid=None, **kwargs):
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)

Read projects into dicts.

See tasks for details on the optional parameters.

def inbox(**kwargs):
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)

Read Inbox into dicts.

See tasks for details on the optional parameters.

def today(**kwargs):
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

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 tasks for details on the optional parameters.

def upcoming(**kwargs):
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)

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 tasks.

def anytime(**kwargs):
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)

Read Anytime tasks into dicts.

See tasks for details on the optional parameters.

def someday(**kwargs):
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)

Read Someday tasks into dicts.

See tasks for details on the optional parameters.

def logbook(**kwargs):
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

Read Logbook tasks into dicts.

See tasks for details on the optional parameters.

def trash(**kwargs):
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    )

Read Trash tasks into dicts.

See tasks for details on the optional parameters.

def canceled(**kwargs):
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)

Read canceled tasks into dicts.

See tasks for details on the optional parameters.

def completed(**kwargs):
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    10
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)

Read completed tasks into dicts.

See tasks for details on the optional parameters.

Examples
>>> things.completed(count_only=True)
10
>>> things.completed(type='project', count_only=True)
0
>>> things.completed(type='to-do', last='1w')
[]
def deadlines(**kwargs):
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

Read tasks with deadlines into dicts.

See tasks for details on the optional parameters.

def last(offset, **kwargs):
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

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

Per default, only incomplete tasks are included, but do see 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 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]:
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()

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 show(uuid):
686def show(uuid):  # noqa
687    """
688    Show a certain uuid in the Things app.
689
690    Parameters
691    ----------
692    uuid : str
693        A valid uuid of any Things object.
694
695    Examples
696    --------
697    >>> tag = things.tags('Home')
698    >>> things.show(tag['uuid'])  # doctest: +SKIP
699    """
700    uri = link(uuid)
701    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 pop_database(kwargs):
707def pop_database(kwargs):
708    """Instantiate non-default database from `kwargs` if provided."""
709    filepath = kwargs.pop("filepath", None)
710    database = kwargs.pop("database", None)
711    print_sql = kwargs.pop("print_sql", False)
712
713    if not database:
714        database = Database(filepath=filepath, print_sql=print_sql)
715    return database

Instantiate non-default database from kwargs if provided.