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
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
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', ...
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.
406def search(query: str, **kwargs) -> List[Dict]: 407 """ 408 Search tasks in the database. 409 410 Currently any part of a title and note of a to-do, project, 411 heading, or area is matched. 412 413 See the `search_query` parameter of `things.api.tasks` for details. 414 415 Examples 416 -------- 417 >>> things.search('Today%yellow') 418 [{'uuid': '6Hf2qWBjWhq7B1xszwdo34', 419 'type': 'to-do', 420 'title': 'Upcoming To-Do in Today (yellow)', 421 'status': 'incomplete', 422 'notes': '', 423 ... 424 """ 425 return tasks(search_query=query, **kwargs)
Search tasks in the database.
Currently any part of a title and note of a to-do, project, heading, or area is matched.
See the search_query
parameter of things.api.tasks
for details.
Examples
>>> things.search('Today%yellow')
[{'uuid': '6Hf2qWBjWhq7B1xszwdo34',
'type': 'to-do',
'title': 'Upcoming To-Do in Today (yellow)',
'status': 'incomplete',
'notes': '',
...
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.
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.
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.
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.
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.
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
.
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.
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.
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.
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.
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.
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')
[]
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.
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 ofthings.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
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.
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:///
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 inquery_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'
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:///
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 inquery_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'
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
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
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.