things-py 0.0.4__py3.9.egg
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- EGG-INFO/PKG-INFO +137 -0
- EGG-INFO/SOURCES.txt +11 -0
- EGG-INFO/dependency_links.txt +1 -0
- EGG-INFO/top_level.txt +2 -0
- EGG-INFO/zip-safe +1 -0
- tests/__init__.py +0 -0
- tests/__pycache__/__init__.cpython-39.pyc +0 -0
- tests/__pycache__/test_things.cpython-39.pyc +0 -0
- tests/test_things.py +124 -0
- things/__init__.py +24 -0
- things/__pycache__/__init__.cpython-39.pyc +0 -0
- things/__pycache__/api.cpython-39.pyc +0 -0
- things/__pycache__/database.cpython-39.pyc +0 -0
- things/api.py +572 -0
- things/database.py +722 -0
things/api.py
ADDED
|
@@ -0,0 +1,572 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
Module implementing Things API.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
from shlex import quote
|
|
9
|
+
import sys
|
|
10
|
+
from typing import List, Dict
|
|
11
|
+
|
|
12
|
+
from .database import Database
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
# --------------------------------------------------
|
|
16
|
+
# Core functions
|
|
17
|
+
# --------------------------------------------------
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def tasks(uuid=None, include_items=False, **kwargs): # noqa: C901
|
|
21
|
+
"""
|
|
22
|
+
Read tasks into dicts.
|
|
23
|
+
|
|
24
|
+
Note: "task" is a technical term used in the database to refer to a
|
|
25
|
+
to-do, project, or heading. For details, check the "type"-parameter.
|
|
26
|
+
|
|
27
|
+
Per default, only tasks marked as incomplete are included. If you
|
|
28
|
+
want to include completed or canceled tasks in the result, check the
|
|
29
|
+
"status"-parameter.
|
|
30
|
+
|
|
31
|
+
Parameters
|
|
32
|
+
----------
|
|
33
|
+
uuid : str or None, optional
|
|
34
|
+
Any valid task uuid. If None, then return all tasks matched.
|
|
35
|
+
|
|
36
|
+
include_items : boolean, default False
|
|
37
|
+
Include items contained within a task. These might include
|
|
38
|
+
checklist items, headings, and to-dos.
|
|
39
|
+
|
|
40
|
+
type : {'to-do', 'heading', 'project', None}, optional
|
|
41
|
+
Only return a specific type of task:
|
|
42
|
+
|
|
43
|
+
'to-do': may have a checklist; may be in an area and have tags.
|
|
44
|
+
'project': may have to-dos and headings; may be in an area and
|
|
45
|
+
have tags.
|
|
46
|
+
'heading': part of a project; groups tasks.
|
|
47
|
+
None: return all types of tasks.
|
|
48
|
+
|
|
49
|
+
status : {'incomplete', 'completed', 'canceled', None}, optional, \
|
|
50
|
+
default 'incomplete'
|
|
51
|
+
|
|
52
|
+
Only include tasks matching that status. If the argument is `None`,
|
|
53
|
+
then include tasks with any status value.
|
|
54
|
+
|
|
55
|
+
start : {'Inbox', 'Anytime', 'Someday', None}, optional
|
|
56
|
+
Only include tasks matching that start value. If the argument is
|
|
57
|
+
`None` (default), then include tasks with any start value.
|
|
58
|
+
|
|
59
|
+
area : str, bool, or None, optional
|
|
60
|
+
Any valid uuid of an area. Only include tasks matching that area.
|
|
61
|
+
If the argument is `False`, only include tasks _without_ an area.
|
|
62
|
+
If the argument is `True`, only include tasks _with_ an area.
|
|
63
|
+
If the argument is `None` (default), then include all tasks.
|
|
64
|
+
|
|
65
|
+
project : str or None, optional
|
|
66
|
+
Any valid uuid of a project. Only include tasks matching that project.
|
|
67
|
+
If the argument is `False`, only include tasks _without_ a project.
|
|
68
|
+
If the argument is `True`, only include tasks _with_ a project.
|
|
69
|
+
If the argument is `None` (default), then include all tasks.
|
|
70
|
+
|
|
71
|
+
heading : str or None, optional
|
|
72
|
+
Any valid uuid of a heading. Only include tasks matching that heading.
|
|
73
|
+
If the argument is `False`, only include tasks _without_ a heading.
|
|
74
|
+
If the argument is `True`, only include tasks _with_ a heading.
|
|
75
|
+
If the argument is `None` (default), then include all tasks.
|
|
76
|
+
|
|
77
|
+
tag : str or None, optional
|
|
78
|
+
Any valid title of a tag. Only include tasks matching that tag.
|
|
79
|
+
If the argument is `False`, only include tasks _without_ tags.
|
|
80
|
+
If the argument is `True`, only include tasks _with_ tags.
|
|
81
|
+
If the argument is `None` (default), then include all tasks.
|
|
82
|
+
|
|
83
|
+
start_date : bool or None, optional
|
|
84
|
+
If the argument is `False`, only include tasks _without_ a start date.
|
|
85
|
+
If the argument is `True`, only include tasks _with_ a start date.
|
|
86
|
+
If the argument is `None` (default), then include all tasks.
|
|
87
|
+
|
|
88
|
+
deadline : bool or None, optional
|
|
89
|
+
If the argument is `False`, only include tasks _without_ a deadline.
|
|
90
|
+
If the argument is `True`, only include tasks _with_ a deadline.
|
|
91
|
+
If the argument is `None` (default), then include all tasks.
|
|
92
|
+
|
|
93
|
+
search_query : str, optional
|
|
94
|
+
The string value is passed to the SQL LIKE operator. It can thus
|
|
95
|
+
include placeholders such as '%' and '_'. Per default, it is
|
|
96
|
+
wrapped in '% ... %'.
|
|
97
|
+
|
|
98
|
+
Currently titles and notes of to-dos, projects, headings, and areas
|
|
99
|
+
are taken into account.
|
|
100
|
+
|
|
101
|
+
If the query is `False`, then return items where titles or notes
|
|
102
|
+
are empty.
|
|
103
|
+
|
|
104
|
+
If the query `None`, then return all items.
|
|
105
|
+
|
|
106
|
+
index : {'index', 'todayIndex'}, default 'index'
|
|
107
|
+
Database field to order result by.
|
|
108
|
+
|
|
109
|
+
count_only : boolean, default False
|
|
110
|
+
Only output length of result. This is done by a SQL COUNT query.
|
|
111
|
+
|
|
112
|
+
filepath : str, optional
|
|
113
|
+
Any valid path of a SQLite database file generated by the Things app.
|
|
114
|
+
If no path is provided, then access the default database path.
|
|
115
|
+
If environment variable THINGSDB is set, this path will be used.
|
|
116
|
+
|
|
117
|
+
database : things.database.Database, optional
|
|
118
|
+
Any valid `things.database.Database` object previously instantiated.
|
|
119
|
+
|
|
120
|
+
Returns
|
|
121
|
+
-------
|
|
122
|
+
list of dict (default)
|
|
123
|
+
Representing multiple tasks.
|
|
124
|
+
dict (if `uuid` is given)
|
|
125
|
+
Representing a single task.
|
|
126
|
+
int (count_only == True)
|
|
127
|
+
Count of matching Tasks.
|
|
128
|
+
|
|
129
|
+
Examples
|
|
130
|
+
--------
|
|
131
|
+
>>> things.tasks()
|
|
132
|
+
...
|
|
133
|
+
>>> things.tasks('2Ukg8I2nLukhyEM7wYiBeb')
|
|
134
|
+
...
|
|
135
|
+
>>> things.tasks(area='hIo1FJlAYGKt1Yj38vzKc3', include_items=True)
|
|
136
|
+
...
|
|
137
|
+
>>> things.tasks(status='completed', count_only=True)
|
|
138
|
+
10
|
|
139
|
+
|
|
140
|
+
"""
|
|
141
|
+
database = pop_database(kwargs)
|
|
142
|
+
result = database.get_tasks(
|
|
143
|
+
uuid=uuid, status=kwargs.pop("status", "incomplete"), **kwargs
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
if kwargs.get("count_only"):
|
|
147
|
+
return result
|
|
148
|
+
|
|
149
|
+
# overwrite `include_items` if fetching a single task.
|
|
150
|
+
if uuid:
|
|
151
|
+
include_items = True
|
|
152
|
+
|
|
153
|
+
for task in result:
|
|
154
|
+
# TK: How costly of an operation is it to do this for every task?
|
|
155
|
+
# IF costly, then can it be made significantly more efficient
|
|
156
|
+
# by optimizing SQL calls?
|
|
157
|
+
|
|
158
|
+
if task.get("tags"):
|
|
159
|
+
task["tags"] = database.get_tags(task=task["uuid"])
|
|
160
|
+
|
|
161
|
+
if not include_items:
|
|
162
|
+
continue
|
|
163
|
+
|
|
164
|
+
# include items
|
|
165
|
+
if task["type"] == "to-do":
|
|
166
|
+
if task.get("checklist"):
|
|
167
|
+
task["checklist"] = database.get_checklist_items(task_uuid=task["uuid"])
|
|
168
|
+
elif task["type"] == "project":
|
|
169
|
+
project = task
|
|
170
|
+
project["items"] = items = tasks(
|
|
171
|
+
project=project["uuid"],
|
|
172
|
+
include_items=True,
|
|
173
|
+
database=database,
|
|
174
|
+
)
|
|
175
|
+
# to-dos without headings appear before headings in app
|
|
176
|
+
items.sort(key=lambda item: item["type"], reverse=True)
|
|
177
|
+
elif task["type"] == "area":
|
|
178
|
+
area = task
|
|
179
|
+
area["items"] = items = tasks(
|
|
180
|
+
area=area["uuid"],
|
|
181
|
+
include_items=True,
|
|
182
|
+
database=database,
|
|
183
|
+
)
|
|
184
|
+
elif task["type"] == "heading":
|
|
185
|
+
heading = task
|
|
186
|
+
heading["items"] = tasks(
|
|
187
|
+
type="to-do",
|
|
188
|
+
heading=heading["uuid"],
|
|
189
|
+
include_items=True,
|
|
190
|
+
database=database,
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
if uuid:
|
|
194
|
+
result = result[0]
|
|
195
|
+
|
|
196
|
+
return result
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def areas(uuid=None, include_items=False, **kwargs):
|
|
200
|
+
"""
|
|
201
|
+
Read areas into dicts.
|
|
202
|
+
|
|
203
|
+
Parameters
|
|
204
|
+
----------
|
|
205
|
+
uuid : str or None, optional
|
|
206
|
+
Any valid uuid of an area. If None, then return all areas.
|
|
207
|
+
|
|
208
|
+
include_items : boolean, default False
|
|
209
|
+
Include tasks and projects in each area.
|
|
210
|
+
|
|
211
|
+
tag : str or None, optional
|
|
212
|
+
Any valid title of a tag. Only include areas matching that tag.
|
|
213
|
+
If the argument is `False`, only include areas _without_ tags.
|
|
214
|
+
If the argument is `True`, only include areas _with_ tags.
|
|
215
|
+
If the argument is `None`, then ignore any tags present, that is,
|
|
216
|
+
include areas both with and without tags.
|
|
217
|
+
|
|
218
|
+
count_only : boolean, default False
|
|
219
|
+
Only output length of result. This is done by a SQL COUNT query.
|
|
220
|
+
|
|
221
|
+
filepath : str, optional
|
|
222
|
+
Any valid path of a SQLite database file generated by the Things app.
|
|
223
|
+
If no path is provided, then access the default database path.
|
|
224
|
+
|
|
225
|
+
database : things.database.Database, optional
|
|
226
|
+
Any valid `things.database.Database` object previously instantiated.
|
|
227
|
+
|
|
228
|
+
Returns
|
|
229
|
+
-------
|
|
230
|
+
list of dict (default)
|
|
231
|
+
Representing Things areas.
|
|
232
|
+
dict (if `uuid` is given)
|
|
233
|
+
Representing a single Things area.
|
|
234
|
+
int (count_only == True)
|
|
235
|
+
Count of matching areas.
|
|
236
|
+
|
|
237
|
+
Examples
|
|
238
|
+
--------
|
|
239
|
+
>>> things.areas()
|
|
240
|
+
...
|
|
241
|
+
>>> things.areas(tag='Home')
|
|
242
|
+
...
|
|
243
|
+
>>> things.areas(uuid='Gw9QefIdgR6nPEoY5hBNSh')
|
|
244
|
+
...
|
|
245
|
+
>>> things.areas(include_items=True, status='completed')
|
|
246
|
+
...
|
|
247
|
+
"""
|
|
248
|
+
database = pop_database(kwargs)
|
|
249
|
+
result = database.get_areas(uuid=uuid, **kwargs)
|
|
250
|
+
|
|
251
|
+
if kwargs.get("count_only"):
|
|
252
|
+
return result
|
|
253
|
+
|
|
254
|
+
for area in result:
|
|
255
|
+
if area.get("tags"):
|
|
256
|
+
area["tags"] = database.get_tags(area=area["uuid"])
|
|
257
|
+
if include_items:
|
|
258
|
+
area["items"] = tasks(
|
|
259
|
+
area=area["uuid"], include_items=True, database=database
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
if uuid:
|
|
263
|
+
result = result[0]
|
|
264
|
+
|
|
265
|
+
return result
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
def tags(title=None, include_items=False, **kwargs):
|
|
269
|
+
"""
|
|
270
|
+
Read tags into dicts.
|
|
271
|
+
|
|
272
|
+
Parameters
|
|
273
|
+
----------
|
|
274
|
+
title : str, optional
|
|
275
|
+
Any valid title of a tag. Include all items of said tag.
|
|
276
|
+
If None, then return all tags.
|
|
277
|
+
|
|
278
|
+
include_items : boolean, default False
|
|
279
|
+
For each tag, include items tagged with that tag.
|
|
280
|
+
Items may include areas, tasks, and projects.
|
|
281
|
+
|
|
282
|
+
area : str, optional
|
|
283
|
+
Valid uuid of an area. Return tags of said area.
|
|
284
|
+
|
|
285
|
+
task : str, optional
|
|
286
|
+
Valid uuid of a task. Return tags of said task.
|
|
287
|
+
|
|
288
|
+
titles_only : bool, default False
|
|
289
|
+
If True, only return list of titles of tags.
|
|
290
|
+
|
|
291
|
+
filepath : str, optional
|
|
292
|
+
Any valid path of a SQLite database file generated by the Things app.
|
|
293
|
+
If no path is provided, then access the default database path.
|
|
294
|
+
|
|
295
|
+
database : things.database.Database, optional
|
|
296
|
+
Any valid `things.database.Database` object previously instantiated.
|
|
297
|
+
|
|
298
|
+
Returns
|
|
299
|
+
-------
|
|
300
|
+
list of dict (default)
|
|
301
|
+
Representing tags.
|
|
302
|
+
list of str (if `titles_only == True` or area / task is given)
|
|
303
|
+
Representing tag titles.
|
|
304
|
+
dict (if `title` is given)
|
|
305
|
+
Representing a single Things tag.
|
|
306
|
+
|
|
307
|
+
Examples
|
|
308
|
+
--------
|
|
309
|
+
>>> things.tags()
|
|
310
|
+
...
|
|
311
|
+
>>> things.tags('Home')
|
|
312
|
+
...
|
|
313
|
+
>>> things.tags(include_items=True)
|
|
314
|
+
...
|
|
315
|
+
>>> things.tags(task='2Ukg8I2nLukhyEM7wYiBeb')
|
|
316
|
+
...
|
|
317
|
+
"""
|
|
318
|
+
database = pop_database(kwargs)
|
|
319
|
+
result = database.get_tags(title=title, **kwargs)
|
|
320
|
+
|
|
321
|
+
if include_items:
|
|
322
|
+
for tag in result:
|
|
323
|
+
tag_title = tag["title"]
|
|
324
|
+
tag["items"] = [
|
|
325
|
+
*areas(tag=tag_title, database=database),
|
|
326
|
+
*tasks(tag=tag_title, database=database),
|
|
327
|
+
]
|
|
328
|
+
|
|
329
|
+
if title:
|
|
330
|
+
result = result[0]
|
|
331
|
+
|
|
332
|
+
return result
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
# --------------------------------------------------
|
|
336
|
+
# Utility API functions derived from above
|
|
337
|
+
# --------------------------------------------------
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
def search(query: str, **kwargs) -> List[Dict]:
|
|
341
|
+
"""
|
|
342
|
+
Search tasks in the database.
|
|
343
|
+
|
|
344
|
+
Currently any part of a title and note of a to-do, project,
|
|
345
|
+
heading, or area is matched.
|
|
346
|
+
|
|
347
|
+
See the `search_query` parameter of `things.api.tasks` for details.
|
|
348
|
+
|
|
349
|
+
Examples
|
|
350
|
+
--------
|
|
351
|
+
>>> things.search('book')
|
|
352
|
+
[{'uuid': 'YrOmUnEXASmpq8ch6RsyPt',
|
|
353
|
+
'type': 'to-do',
|
|
354
|
+
'title': 'Book a hotel room',
|
|
355
|
+
...},
|
|
356
|
+
{'uuid': 'KVHAxIIJ52a0h1RCbmg5D6',
|
|
357
|
+
'type': 'to-do',
|
|
358
|
+
'title': 'Book flights',
|
|
359
|
+
...}]
|
|
360
|
+
|
|
361
|
+
>>> things.search('book%room')
|
|
362
|
+
[{'uuid': 'YrOmUnEXASmpq8ch6RsyPt',
|
|
363
|
+
'type': 'to-do',
|
|
364
|
+
'title': 'Book a hotel room',
|
|
365
|
+
...}]
|
|
366
|
+
"""
|
|
367
|
+
return tasks(search_query=query, **kwargs)
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
def get(uuid, default=None, **kwargs):
|
|
371
|
+
"""
|
|
372
|
+
Find an object by uuid. If not found, return `default`.
|
|
373
|
+
|
|
374
|
+
Currently supports tasks, projects, headings, areas, and tags.
|
|
375
|
+
"""
|
|
376
|
+
try:
|
|
377
|
+
return tasks(uuid=uuid, **kwargs)
|
|
378
|
+
except ValueError:
|
|
379
|
+
pass
|
|
380
|
+
|
|
381
|
+
try:
|
|
382
|
+
return areas(uuid=uuid, **kwargs)
|
|
383
|
+
except ValueError:
|
|
384
|
+
pass
|
|
385
|
+
|
|
386
|
+
for tag in tags(**kwargs):
|
|
387
|
+
if tag["uuid"] == uuid:
|
|
388
|
+
return tag
|
|
389
|
+
|
|
390
|
+
return default
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
# Filter by object type
|
|
394
|
+
|
|
395
|
+
|
|
396
|
+
def todos(uuid=None, **kwargs):
|
|
397
|
+
"""
|
|
398
|
+
Read to-dos into dicts.
|
|
399
|
+
|
|
400
|
+
See `things.api.tasks` for details.
|
|
401
|
+
"""
|
|
402
|
+
return tasks(uuid=uuid, type="to-do", **kwargs)
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
def projects(uuid=None, **kwargs):
|
|
406
|
+
"""
|
|
407
|
+
Read projects into dicts.
|
|
408
|
+
|
|
409
|
+
See `things.api.tasks` for details.
|
|
410
|
+
"""
|
|
411
|
+
return tasks(uuid=uuid, type="project", **kwargs)
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
def checklist_items(uuid, **kwargs):
|
|
415
|
+
"""
|
|
416
|
+
Read checklist items into dicts.
|
|
417
|
+
See `things.api.tasks` for details.
|
|
418
|
+
|
|
419
|
+
Returns
|
|
420
|
+
-------
|
|
421
|
+
list of dict
|
|
422
|
+
Representing checklist items. Or [] if none found.
|
|
423
|
+
"""
|
|
424
|
+
return tasks(uuid=uuid, type="to-do", **kwargs).get('checklist', [])
|
|
425
|
+
|
|
426
|
+
# Filter by collections in the Things app sidebar.
|
|
427
|
+
|
|
428
|
+
|
|
429
|
+
def inbox(**kwargs):
|
|
430
|
+
"""
|
|
431
|
+
Read inbox into dicts.
|
|
432
|
+
|
|
433
|
+
See `things.api.tasks` for details.
|
|
434
|
+
"""
|
|
435
|
+
return tasks(start="Inbox", **kwargs)
|
|
436
|
+
|
|
437
|
+
|
|
438
|
+
def today(**kwargs):
|
|
439
|
+
"""
|
|
440
|
+
Note: This might not produce desired results if the Things app hasn't
|
|
441
|
+
been opened yet today and the yellow "OK" button clicked for new tasks.
|
|
442
|
+
In general, you can assume that whatever state the Things app was in
|
|
443
|
+
when you last opened it, that's the state reflected here by the API.
|
|
444
|
+
"""
|
|
445
|
+
database = pop_database(kwargs)
|
|
446
|
+
if not database.was_modified_today():
|
|
447
|
+
print(
|
|
448
|
+
"[NOTE] The results reflect the state of the Things app "
|
|
449
|
+
"when it was last run. If the results seem out of date, "
|
|
450
|
+
"then run the Things app and click the yellow 'OK' button "
|
|
451
|
+
"to update Today's to-dos and projects.",
|
|
452
|
+
file=sys.stderr,
|
|
453
|
+
)
|
|
454
|
+
return tasks(
|
|
455
|
+
start_date=True,
|
|
456
|
+
start="Anytime",
|
|
457
|
+
index="todayIndex",
|
|
458
|
+
database=database,
|
|
459
|
+
**kwargs,
|
|
460
|
+
)
|
|
461
|
+
|
|
462
|
+
|
|
463
|
+
def upcoming(**kwargs):
|
|
464
|
+
"""
|
|
465
|
+
Note: unscheduled tasks with a deadline are not included here.
|
|
466
|
+
See the `deadline` function instead.
|
|
467
|
+
"""
|
|
468
|
+
return tasks(start_date=True, start="Someday", **kwargs)
|
|
469
|
+
|
|
470
|
+
|
|
471
|
+
def anytime(**kwargs):
|
|
472
|
+
"""
|
|
473
|
+
Read anytime tasks into dicts.
|
|
474
|
+
|
|
475
|
+
See `things.api.tasks` for details.
|
|
476
|
+
"""
|
|
477
|
+
return tasks(start="Anytime", **kwargs)
|
|
478
|
+
|
|
479
|
+
|
|
480
|
+
def someday(**kwargs):
|
|
481
|
+
"""
|
|
482
|
+
Read someday tasks into dicts.
|
|
483
|
+
|
|
484
|
+
See `things.api.tasks` for details.
|
|
485
|
+
"""
|
|
486
|
+
return tasks(start_date=False, start="Someday", **kwargs)
|
|
487
|
+
|
|
488
|
+
|
|
489
|
+
def logbook(**kwargs):
|
|
490
|
+
"""
|
|
491
|
+
Read logbook tasks into dicts.
|
|
492
|
+
|
|
493
|
+
See `things.api.tasks` for details.
|
|
494
|
+
"""
|
|
495
|
+
result = [*canceled(**kwargs), *completed(**kwargs)]
|
|
496
|
+
result.sort(key=lambda task: task["stop_date"], reverse=True)
|
|
497
|
+
return result
|
|
498
|
+
|
|
499
|
+
|
|
500
|
+
# Filter by various task properties
|
|
501
|
+
|
|
502
|
+
|
|
503
|
+
def canceled(**kwargs):
|
|
504
|
+
"""
|
|
505
|
+
Read canceled tasks into dicts.
|
|
506
|
+
|
|
507
|
+
See `things.api.tasks` for details.
|
|
508
|
+
"""
|
|
509
|
+
return tasks(status="canceled", **kwargs)
|
|
510
|
+
|
|
511
|
+
|
|
512
|
+
def completed(**kwargs):
|
|
513
|
+
"""
|
|
514
|
+
Examples
|
|
515
|
+
--------
|
|
516
|
+
>>> things.completed(count_only=True)
|
|
517
|
+
14
|
|
518
|
+
>>> things.completed(type='project', count_only=True)
|
|
519
|
+
4
|
|
520
|
+
"""
|
|
521
|
+
return tasks(status="completed", **kwargs)
|
|
522
|
+
|
|
523
|
+
|
|
524
|
+
def deadlines(**kwargs):
|
|
525
|
+
"""
|
|
526
|
+
Read tasks with deadlines into dicts.
|
|
527
|
+
|
|
528
|
+
See `things.api.tasks` for details.
|
|
529
|
+
"""
|
|
530
|
+
result = tasks(deadline=True, **kwargs)
|
|
531
|
+
result.sort(key=lambda task: task["deadline"])
|
|
532
|
+
return result
|
|
533
|
+
|
|
534
|
+
|
|
535
|
+
# Interact with Things app
|
|
536
|
+
|
|
537
|
+
|
|
538
|
+
def link(uuid):
|
|
539
|
+
"""
|
|
540
|
+
Returns a things:///show?id=uuid link.
|
|
541
|
+
"""
|
|
542
|
+
|
|
543
|
+
return f"things:///show?id={uuid}"
|
|
544
|
+
|
|
545
|
+
|
|
546
|
+
def show(uuid):
|
|
547
|
+
"""
|
|
548
|
+
Show a certain uuid in the Things app.
|
|
549
|
+
|
|
550
|
+
Parameters
|
|
551
|
+
----------
|
|
552
|
+
uuid : str
|
|
553
|
+
A valid uuid of any Things object.
|
|
554
|
+
|
|
555
|
+
Examples
|
|
556
|
+
--------
|
|
557
|
+
>>> tag = things.tags('Home')
|
|
558
|
+
>>> things.show(tag['uuid'])
|
|
559
|
+
"""
|
|
560
|
+
uri = link(uuid)
|
|
561
|
+
os.system(f"open {quote(uri)}")
|
|
562
|
+
|
|
563
|
+
|
|
564
|
+
# Helper functions
|
|
565
|
+
|
|
566
|
+
|
|
567
|
+
def pop_database(kwargs):
|
|
568
|
+
"""instantiate non-default database from `kwargs` if provided"""
|
|
569
|
+
filepath, database = kwargs.pop("filepath", None), kwargs.pop("database", None)
|
|
570
|
+
if not database:
|
|
571
|
+
database = Database(filepath=filepath)
|
|
572
|
+
return database
|