Skip to content

Commit 273dee6

Browse files
Merge pull request #23 from mikez/main
add note for things.today; update tests; gitignore htmlcov
2 parents ed8b3cd + e88d36c commit 273dee6

File tree

4 files changed

+70
-23
lines changed

4 files changed

+70
-23
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
.DS_Store
22
dist
33
build
4+
htmlcov
45
__pycache__
56
*.pyc
67
*.egg-info

tests/test_things.py

Lines changed: 30 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -4,97 +4,105 @@
44
"""Module documentation goes here."""
55

66
import unittest
7-
from things import api
87

8+
import things
99

10-
DEMO_FILEPATH = "tests/main.sqlite"
10+
11+
FILEPATH = dict(filepath="tests/main.sqlite")
1112

1213

1314
class ThingsCase(unittest.TestCase):
1415
"""Class documentation goes here."""
1516

1617
def test_inbox(self):
1718
"""Test inbox."""
18-
tasks = api.inbox(filepath=DEMO_FILEPATH)
19+
tasks = things.inbox(**FILEPATH)
1920
self.assertEqual(1, len(tasks))
2021

2122
def test_upcoming(self):
2223
"""Test upcoming."""
23-
tasks = api.upcoming(filepath=DEMO_FILEPATH)
24+
tasks = things.upcoming(**FILEPATH)
2425
self.assertEqual(2, len(tasks))
2526

2627
def test_due(self):
2728
"""Test due."""
28-
tasks = api.due(filepath=DEMO_FILEPATH)
29+
tasks = things.due(**FILEPATH)
2930
self.assertEqual(1, len(tasks))
3031

3132
def test_today(self):
3233
"""Test today."""
33-
tasks = api.today(filepath=DEMO_FILEPATH)
34+
tasks = things.today(**FILEPATH)
3435
self.assertEqual(1, len(tasks))
3536

3637
def test_anytime(self):
3738
"""Test antime."""
38-
tasks = api.anytime(filepath=DEMO_FILEPATH)
39+
tasks = things.anytime(**FILEPATH)
3940
self.assertEqual(8, len(tasks))
4041

4142
def test_logbook(self):
4243
"""Test logbook."""
43-
tasks = api.logbook(filepath=DEMO_FILEPATH)
44+
tasks = things.logbook(**FILEPATH)
4445
self.assertEqual(21, len(tasks))
4546

4647
def test_canceled(self):
4748
"""Test canceled."""
48-
tasks = api.canceled(filepath=DEMO_FILEPATH)
49+
tasks = things.canceled(**FILEPATH)
4950
self.assertEqual(11, len(tasks))
5051

5152
def test_completed(self):
5253
"""Test completed."""
53-
tasks = api.completed(filepath=DEMO_FILEPATH)
54+
tasks = things.completed(**FILEPATH)
5455
self.assertEqual(10, len(tasks))
5556

5657
def test_someday(self):
5758
"""Test someday."""
58-
tasks = api.someday(filepath=DEMO_FILEPATH)
59+
tasks = things.someday(**FILEPATH)
5960
self.assertEqual(1, len(tasks))
6061

6162
def test_get(self):
6263
"""Test get."""
63-
tasks = api.get('wrong_uuid', filepath=DEMO_FILEPATH)
64+
tasks = things.get("wrong_uuid", **FILEPATH)
6465
self.assertEqual(None, tasks)
65-
tasks = api.get('Qt2AY87x2QDdowSn9HKTt1', filepath=DEMO_FILEPATH)
66+
tasks = things.get("wrong_uuid", "NOT FOUND", **FILEPATH)
67+
self.assertEqual("NOT FOUND", tasks)
68+
tasks = things.get("Qt2AY87x2QDdowSn9HKTt1", **FILEPATH)
6669
self.assertEqual(4, len(tasks))
6770

6871
def test_todos(self):
6972
"""Test all tasks."""
70-
tasks = api.todos(start="Anytime", filepath=DEMO_FILEPATH)
73+
tasks = things.todos(start="Anytime", **FILEPATH)
7174
self.assertEqual(5, len(tasks))
72-
tasks = api.todos(start="Anytime", status="completed", filepath=DEMO_FILEPATH)
75+
tasks = things.todos(start="Anytime", status="completed", **FILEPATH)
7376
self.assertEqual(6, len(tasks))
74-
tasks = api.todos(status="completed", filepath=DEMO_FILEPATH)
77+
tasks = things.todos(status="completed", **FILEPATH)
7578
self.assertEqual(10, len(tasks))
76-
tasks = api.todos(include_items=True, filepath=DEMO_FILEPATH)
79+
tasks = things.todos(include_items=True, **FILEPATH)
7780
self.assertEqual(9, len(tasks))
7881
with self.assertRaises(ValueError):
79-
api.todos(status="wrong_value", filepath=DEMO_FILEPATH)
80-
tasks = api.tasks('A2oPvtt4dXoypeoLc8uYzY', filepath=DEMO_FILEPATH)
82+
things.todos(status="wrong_value", **FILEPATH)
83+
tasks = things.tasks("A2oPvtt4dXoypeoLc8uYzY", **FILEPATH)
8184
self.assertEqual(13, len(tasks))
8285

8386
def test_tags(self):
8487
"""Test all tags."""
85-
tags = api.tags(filepath=DEMO_FILEPATH)
88+
tags = things.tags(**FILEPATH)
8689
self.assertEqual(5, len(tags))
8790

8891
def test_projects(self):
8992
"""Test all projects."""
90-
projects = api.projects(filepath=DEMO_FILEPATH)
93+
projects = things.projects(**FILEPATH)
9194
self.assertEqual(2, len(projects))
9295

9396
def test_areas(self):
9497
"""Test all test_areas."""
95-
test_areas = api.areas(filepath=DEMO_FILEPATH)
98+
test_areas = things.areas(**FILEPATH)
9699
self.assertEqual(1, len(test_areas))
97100

101+
def test_database_version(self):
102+
"""Test database version."""
103+
version = things.Database(**FILEPATH).get_version()
104+
self.assertEqual(18, version)
105+
98106

99107
if __name__ == "__main__":
100108
unittest.main()

things/api.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
import os
88
from shlex import quote
9+
import sys
910

1011
from .database import Database
1112

@@ -362,7 +363,21 @@ def today(**kwargs):
362363
the Things app was in when you last opened it, that's the state
363364
reflected here by the API.
364365
"""
365-
return tasks(start_date=True, start="Anytime", index="todayIndex", **kwargs)
366+
database = pop_database(kwargs)
367+
if not database.was_modified_today():
368+
print(
369+
"[NOTE] The results reflect the state of the Things app "
370+
"when it was last run. If the results seem out of date, "
371+
"then run the Things app to update the database.",
372+
file=sys.stderr,
373+
)
374+
return tasks(
375+
start_date=True,
376+
start="Anytime",
377+
index="todayIndex",
378+
database=database,
379+
**kwargs,
380+
)
366381

367382

368383
def upcoming(**kwargs):

things/database.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
__email__ = "[email protected]"
1515
__status__ = "Development"
1616

17+
import datetime
1718
import os
1819
import sqlite3
1920
import sys
@@ -63,6 +64,7 @@ class Database:
6364
TABLE_TASKTAG = "TMTaskTag"
6465
TABLE_AREATAG = "TMAreaTag"
6566
TABLE_CHECKLIST_ITEM = "TMChecklistItem"
67+
TABLE_META = "Meta"
6668
DATE_CREATE = "creationDate"
6769
DATE_MOD = "userModificationDate"
6870
DATE_DUE = "dueDate"
@@ -377,10 +379,20 @@ def get_tags_of_area(self, area_uuid):
377379
AREA_TAG.areas = ?
378380
ORDER BY AREA."index"
379381
"""
382+
380383
return self.execute_query(
381384
query, parameters=(area_uuid,), row_factory=list_factory
382385
)
383386

387+
def get_version(self):
388+
"""Get Things Database version."""
389+
import plistlib
390+
391+
query = f"SELECT value FROM {self.TABLE_META} WHERE key = 'databaseVersion'"
392+
result = self.execute_query(query, row_factory=list_factory)
393+
plist_bytes = result[0].encode()
394+
return plistlib.loads(plist_bytes)
395+
384396
def get_count(self, sql):
385397
sql = f"""SELECT COUNT(uuid) FROM ({sql})"""
386398
return self.execute_query(sql, row_factory=list_factory)[0]
@@ -406,6 +418,17 @@ def execute_query(self, sql, parameters=(), row_factory=None):
406418
print(f"Details: {error}.")
407419
sys.exit(2)
408420

421+
# -------- Utility methods --------
422+
423+
def last_modified(self):
424+
mtime_seconds = os.path.getmtime(self.filepath)
425+
return datetime.datetime.fromtimestamp(mtime_seconds)
426+
427+
def was_modified_today(self):
428+
last_modified_date = self.last_modified().date()
429+
todays_date = datetime.datetime.now().date()
430+
return last_modified_date >= todays_date
431+
409432
# -------- Historical methods (TK: transform) --------
410433

411434
def get_trashed(self):

0 commit comments

Comments
 (0)