things-py 0.0.4__tar.gz → 0.0.6__tar.gz
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.
- {things.py-0.0.4 → things.py-0.0.6}/PKG-INFO +1 -1
- {things.py-0.0.4 → things.py-0.0.6}/setup.py +1 -1
- {things.py-0.0.4 → things.py-0.0.6}/tests/test_things.py +20 -9
- {things.py-0.0.4 → things.py-0.0.6}/things/__init__.py +1 -0
- {things.py-0.0.4 → things.py-0.0.6}/things/api.py +32 -16
- {things.py-0.0.4 → things.py-0.0.6}/things/database.py +63 -52
- {things.py-0.0.4 → things.py-0.0.6}/things.py.egg-info/PKG-INFO +1 -1
- {things.py-0.0.4 → things.py-0.0.6}/README.md +0 -0
- {things.py-0.0.4 → things.py-0.0.6}/setup.cfg +0 -0
- {things.py-0.0.4 → things.py-0.0.6}/tests/__init__.py +0 -0
- {things.py-0.0.4 → things.py-0.0.6}/things.py.egg-info/SOURCES.txt +0 -0
- {things.py-0.0.4 → things.py-0.0.6}/things.py.egg-info/dependency_links.txt +0 -0
- {things.py-0.0.4 → things.py-0.0.6}/things.py.egg-info/top_level.txt +0 -0
|
@@ -20,7 +20,7 @@ AUTHOR = "Alexander Willner, Michael B."
|
|
|
20
20
|
AUTHOR_MAIL = "alex@willner.ws"
|
|
21
21
|
DESCRIPTON = "A simple Python 3 library to read your Things app data."
|
|
22
22
|
URL = "https://github.com/thingsapi/things.py"
|
|
23
|
-
VERSION = "0.0.
|
|
23
|
+
VERSION = "0.0.6"
|
|
24
24
|
DATA_FILES = package_files("")
|
|
25
25
|
OPTIONS = {
|
|
26
26
|
"argv_emulation": False,
|
|
@@ -24,12 +24,12 @@ class ThingsCase(unittest.TestCase):
|
|
|
24
24
|
def test_inbox(self):
|
|
25
25
|
"""Test inbox."""
|
|
26
26
|
tasks = things.inbox(**FILEPATH)
|
|
27
|
-
self.assertEqual(
|
|
27
|
+
self.assertEqual(2, len(tasks))
|
|
28
28
|
|
|
29
29
|
def test_upcoming(self):
|
|
30
30
|
"""Test upcoming."""
|
|
31
31
|
tasks = things.upcoming(**FILEPATH)
|
|
32
|
-
self.assertEqual(
|
|
32
|
+
self.assertEqual(1, len(tasks))
|
|
33
33
|
|
|
34
34
|
def test_deadlines(self):
|
|
35
35
|
"""Test deadlines."""
|
|
@@ -39,12 +39,20 @@ class ThingsCase(unittest.TestCase):
|
|
|
39
39
|
def test_today(self):
|
|
40
40
|
"""Test today."""
|
|
41
41
|
tasks = things.today(**FILEPATH)
|
|
42
|
-
self.assertEqual(
|
|
42
|
+
self.assertEqual(2, len(tasks))
|
|
43
|
+
|
|
44
|
+
def test_checklist(self):
|
|
45
|
+
"""Test checklist."""
|
|
46
|
+
tasks = things.checklist_items('3Eva4XFof6zWb9iSfYy4ej', **FILEPATH)
|
|
47
|
+
self.assertEqual(3, len(tasks))
|
|
48
|
+
tasks = things.checklist_items('K9bx7h1xCJdevvyWardZDq', **FILEPATH)
|
|
49
|
+
self.assertEqual(0, len(tasks))
|
|
43
50
|
|
|
44
51
|
def test_anytime(self):
|
|
45
|
-
"""Test
|
|
52
|
+
"""Test anytime."""
|
|
46
53
|
tasks = things.anytime(**FILEPATH)
|
|
47
|
-
self.assertEqual(
|
|
54
|
+
self.assertEqual(11, len(tasks))
|
|
55
|
+
self.assertTrue(any(task.pop('area_title', '') == 'Area 1' for task in tasks))
|
|
48
56
|
|
|
49
57
|
def test_logbook(self):
|
|
50
58
|
"""Test logbook."""
|
|
@@ -78,22 +86,25 @@ class ThingsCase(unittest.TestCase):
|
|
|
78
86
|
def test_todos(self):
|
|
79
87
|
"""Test all tasks."""
|
|
80
88
|
tasks = things.todos(start="Anytime", **FILEPATH)
|
|
81
|
-
self.assertEqual(
|
|
89
|
+
self.assertEqual(8, len(tasks))
|
|
82
90
|
tasks = things.todos(start="Anytime", status="completed", **FILEPATH)
|
|
83
91
|
self.assertEqual(6, len(tasks))
|
|
84
92
|
tasks = things.todos(status="completed", **FILEPATH)
|
|
85
93
|
self.assertEqual(10, len(tasks))
|
|
86
94
|
tasks = things.todos(include_items=True, **FILEPATH)
|
|
87
|
-
self.assertEqual(
|
|
95
|
+
self.assertEqual(12, len(tasks))
|
|
88
96
|
with self.assertRaises(ValueError):
|
|
89
97
|
things.todos(status="wrong_value", **FILEPATH)
|
|
90
98
|
tasks = things.tasks("A2oPvtt4dXoypeoLc8uYzY", **FILEPATH)
|
|
91
|
-
self.assertEqual(
|
|
99
|
+
self.assertEqual(16, len(tasks))
|
|
92
100
|
|
|
93
101
|
def test_tags(self):
|
|
94
102
|
"""Test all tags."""
|
|
95
103
|
tags = things.tags(**FILEPATH)
|
|
96
104
|
self.assertEqual(5, len(tags))
|
|
105
|
+
tasks = things.tasks(tag='Errand', **FILEPATH)
|
|
106
|
+
self.assertEqual(1, len(tasks))
|
|
107
|
+
|
|
97
108
|
|
|
98
109
|
def test_projects(self):
|
|
99
110
|
"""Test all projects."""
|
|
@@ -103,7 +114,7 @@ class ThingsCase(unittest.TestCase):
|
|
|
103
114
|
def test_areas(self):
|
|
104
115
|
"""Test all test_areas."""
|
|
105
116
|
test_areas = things.areas(**FILEPATH)
|
|
106
|
-
self.assertEqual(
|
|
117
|
+
self.assertEqual(3, len(test_areas))
|
|
107
118
|
|
|
108
119
|
def test_database_version(self):
|
|
109
120
|
"""Test database version."""
|
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
Module implementing Things API.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
+
__version__ = "0.0.6"
|
|
8
|
+
|
|
7
9
|
import os
|
|
8
10
|
from shlex import quote
|
|
9
11
|
import sys
|
|
@@ -111,10 +113,11 @@ def tasks(uuid=None, include_items=False, **kwargs): # noqa: C901
|
|
|
111
113
|
|
|
112
114
|
filepath : str, optional
|
|
113
115
|
Any valid path of a SQLite database file generated by the Things app.
|
|
114
|
-
If
|
|
116
|
+
If the environment variable THINGSDB is set, then that path will be used.
|
|
117
|
+
Otherwise, access the default database path.
|
|
115
118
|
|
|
116
|
-
database : things.Database, optional
|
|
117
|
-
Any valid `things.Database` object previously instantiated.
|
|
119
|
+
database : things.database.Database, optional
|
|
120
|
+
Any valid `things.database.Database` object previously instantiated.
|
|
118
121
|
|
|
119
122
|
Returns
|
|
120
123
|
-------
|
|
@@ -214,8 +217,8 @@ def areas(uuid=None, include_items=False, **kwargs):
|
|
|
214
217
|
Any valid path of a SQLite database file generated by the Things app.
|
|
215
218
|
If no path is provided, then access the default database path.
|
|
216
219
|
|
|
217
|
-
database : things.Database, optional
|
|
218
|
-
Any valid `things.Database` object previously instantiated.
|
|
220
|
+
database : things.database.Database, optional
|
|
221
|
+
Any valid `things.database.Database` object previously instantiated.
|
|
219
222
|
|
|
220
223
|
Returns
|
|
221
224
|
-------
|
|
@@ -284,8 +287,8 @@ def tags(title=None, include_items=False, **kwargs):
|
|
|
284
287
|
Any valid path of a SQLite database file generated by the Things app.
|
|
285
288
|
If no path is provided, then access the default database path.
|
|
286
289
|
|
|
287
|
-
database : things.Database, optional
|
|
288
|
-
Any valid `things.Database` object previously instantiated.
|
|
290
|
+
database : things.database.Database, optional
|
|
291
|
+
Any valid `things.database.Database` object previously instantiated.
|
|
289
292
|
|
|
290
293
|
Returns
|
|
291
294
|
-------
|
|
@@ -336,7 +339,7 @@ def search(query: str, **kwargs) -> List[Dict]:
|
|
|
336
339
|
Currently any part of a title and note of a to-do, project,
|
|
337
340
|
heading, or area is matched.
|
|
338
341
|
|
|
339
|
-
See the `search_query` parameter of `api.tasks` for details.
|
|
342
|
+
See the `search_query` parameter of `things.api.tasks` for details.
|
|
340
343
|
|
|
341
344
|
Examples
|
|
342
345
|
--------
|
|
@@ -389,7 +392,7 @@ def todos(uuid=None, **kwargs):
|
|
|
389
392
|
"""
|
|
390
393
|
Read to-dos into dicts.
|
|
391
394
|
|
|
392
|
-
See `things.tasks` for details.
|
|
395
|
+
See `things.api.tasks` for details.
|
|
393
396
|
"""
|
|
394
397
|
return tasks(uuid=uuid, type="to-do", **kwargs)
|
|
395
398
|
|
|
@@ -398,11 +401,24 @@ def projects(uuid=None, **kwargs):
|
|
|
398
401
|
"""
|
|
399
402
|
Read projects into dicts.
|
|
400
403
|
|
|
401
|
-
See `things.tasks` for details.
|
|
404
|
+
See `things.api.tasks` for details.
|
|
402
405
|
"""
|
|
403
406
|
return tasks(uuid=uuid, type="project", **kwargs)
|
|
404
407
|
|
|
405
408
|
|
|
409
|
+
def checklist_items(uuid, **kwargs):
|
|
410
|
+
"""
|
|
411
|
+
Read checklist items into dicts.
|
|
412
|
+
See `things.api.tasks` for details.
|
|
413
|
+
|
|
414
|
+
Returns
|
|
415
|
+
-------
|
|
416
|
+
list of dict
|
|
417
|
+
Representing checklist items. Or [] if none found.
|
|
418
|
+
"""
|
|
419
|
+
return tasks(uuid=uuid, type="to-do", **kwargs).get("checklist", []) # type: ignore
|
|
420
|
+
|
|
421
|
+
|
|
406
422
|
# Filter by collections in the Things app sidebar.
|
|
407
423
|
|
|
408
424
|
|
|
@@ -410,7 +426,7 @@ def inbox(**kwargs):
|
|
|
410
426
|
"""
|
|
411
427
|
Read inbox into dicts.
|
|
412
428
|
|
|
413
|
-
See `things.tasks` for details.
|
|
429
|
+
See `things.api.tasks` for details.
|
|
414
430
|
"""
|
|
415
431
|
return tasks(start="Inbox", **kwargs)
|
|
416
432
|
|
|
@@ -452,7 +468,7 @@ def anytime(**kwargs):
|
|
|
452
468
|
"""
|
|
453
469
|
Read anytime tasks into dicts.
|
|
454
470
|
|
|
455
|
-
See `things.tasks` for details.
|
|
471
|
+
See `things.api.tasks` for details.
|
|
456
472
|
"""
|
|
457
473
|
return tasks(start="Anytime", **kwargs)
|
|
458
474
|
|
|
@@ -461,7 +477,7 @@ def someday(**kwargs):
|
|
|
461
477
|
"""
|
|
462
478
|
Read someday tasks into dicts.
|
|
463
479
|
|
|
464
|
-
See `things.tasks` for details.
|
|
480
|
+
See `things.api.tasks` for details.
|
|
465
481
|
"""
|
|
466
482
|
return tasks(start_date=False, start="Someday", **kwargs)
|
|
467
483
|
|
|
@@ -470,7 +486,7 @@ def logbook(**kwargs):
|
|
|
470
486
|
"""
|
|
471
487
|
Read logbook tasks into dicts.
|
|
472
488
|
|
|
473
|
-
See `things.tasks` for details.
|
|
489
|
+
See `things.api.tasks` for details.
|
|
474
490
|
"""
|
|
475
491
|
result = [*canceled(**kwargs), *completed(**kwargs)]
|
|
476
492
|
result.sort(key=lambda task: task["stop_date"], reverse=True)
|
|
@@ -484,7 +500,7 @@ def canceled(**kwargs):
|
|
|
484
500
|
"""
|
|
485
501
|
Read canceled tasks into dicts.
|
|
486
502
|
|
|
487
|
-
See `things.tasks` for details.
|
|
503
|
+
See `things.api.tasks` for details.
|
|
488
504
|
"""
|
|
489
505
|
return tasks(status="canceled", **kwargs)
|
|
490
506
|
|
|
@@ -505,7 +521,7 @@ def deadlines(**kwargs):
|
|
|
505
521
|
"""
|
|
506
522
|
Read tasks with deadlines into dicts.
|
|
507
523
|
|
|
508
|
-
See `things.tasks` for details.
|
|
524
|
+
See `things.api.tasks` for details.
|
|
509
525
|
"""
|
|
510
526
|
result = tasks(deadline=True, **kwargs)
|
|
511
527
|
result.sort(key=lambda task: task["deadline"])
|
|
@@ -13,13 +13,14 @@ __author__ = "Alexander Willner"
|
|
|
13
13
|
__copyright__ = "2020 Alexander Willner"
|
|
14
14
|
__credits__ = ["Alexander Willner"]
|
|
15
15
|
__license__ = "Apache License 2.0"
|
|
16
|
-
__version__ = "
|
|
16
|
+
__version__ = "0.0.6"
|
|
17
17
|
__maintainer__ = "Alexander Willner"
|
|
18
18
|
__email__ = "alex@willner.ws"
|
|
19
19
|
__status__ = "Development"
|
|
20
20
|
|
|
21
21
|
import datetime
|
|
22
22
|
import os
|
|
23
|
+
import plistlib
|
|
23
24
|
import sqlite3
|
|
24
25
|
import sys
|
|
25
26
|
|
|
@@ -45,7 +46,8 @@ TYPE_TO_FILTER = {"to-do": "type = 0", "project": "type = 1", "heading": "type =
|
|
|
45
46
|
|
|
46
47
|
INDICES = ("index", "todayIndex")
|
|
47
48
|
|
|
48
|
-
COLUMNS_TO_OMIT_IF_NONE = ("area", "
|
|
49
|
+
COLUMNS_TO_OMIT_IF_NONE = ("area", "area_title", "project", "project_title",
|
|
50
|
+
"heading", "heading_title", "trashed", "checklist", "tags")
|
|
49
51
|
COLUMNS_TO_TRANSFORM_TO_BOOL = ("trashed", "checklist", "tags")
|
|
50
52
|
|
|
51
53
|
|
|
@@ -99,7 +101,7 @@ class Database:
|
|
|
99
101
|
|
|
100
102
|
# pylint: disable=R0913
|
|
101
103
|
def __init__(self, filepath=None):
|
|
102
|
-
self.filepath = filepath or DEFAULT_DATABASE_FILEPATH
|
|
104
|
+
self.filepath = filepath or os.environ.get("THINGSDB") or DEFAULT_DATABASE_FILEPATH
|
|
103
105
|
|
|
104
106
|
# Automated migration to new database location in Things 3.12.6/3.13.1
|
|
105
107
|
# --------------------------------
|
|
@@ -113,31 +115,29 @@ class Database:
|
|
|
113
115
|
|
|
114
116
|
# Core methods
|
|
115
117
|
|
|
116
|
-
def get_tasks(
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
search_query=None,
|
|
131
|
-
):
|
|
118
|
+
def get_tasks(self, # pylint: disable=R0914
|
|
119
|
+
uuid=None,
|
|
120
|
+
type=None, # pylint: disable=W0622
|
|
121
|
+
status=None,
|
|
122
|
+
start=None,
|
|
123
|
+
area=None,
|
|
124
|
+
project=None,
|
|
125
|
+
heading=None,
|
|
126
|
+
tag=None,
|
|
127
|
+
start_date=None,
|
|
128
|
+
deadline=None,
|
|
129
|
+
index="index",
|
|
130
|
+
count_only=False,
|
|
131
|
+
search_query=None):
|
|
132
132
|
"""Get tasks. See `api.tasks` for details on parameters."""
|
|
133
133
|
|
|
134
134
|
# Overwrites
|
|
135
135
|
start = start and start.title()
|
|
136
136
|
|
|
137
137
|
# Validation
|
|
138
|
-
validate("type", type, [None] + list(TYPE_TO_FILTER))
|
|
139
|
-
validate("status", status, [None] + list(STATUS_TO_FILTER))
|
|
140
|
-
validate("start", start, [None] + list(START_TO_FILTER))
|
|
138
|
+
validate("type", type, [None] + list(TYPE_TO_FILTER)) # type: ignore
|
|
139
|
+
validate("status", status, [None] + list(STATUS_TO_FILTER)) # type: ignore
|
|
140
|
+
validate("start", start, [None] + list(START_TO_FILTER)) # type: ignore
|
|
141
141
|
validate("index", index, list(INDICES))
|
|
142
142
|
|
|
143
143
|
if tag is not None:
|
|
@@ -181,8 +181,8 @@ class Database:
|
|
|
181
181
|
sql_query = self.make_task_sql_query(where_predicate)
|
|
182
182
|
if count_only:
|
|
183
183
|
return self.get_count(sql_query)
|
|
184
|
-
|
|
185
|
-
|
|
184
|
+
|
|
185
|
+
return self.execute_query(sql_query)
|
|
186
186
|
|
|
187
187
|
def make_task_sql_query(self, where_predicate):
|
|
188
188
|
"""Make SQL query for Task table"""
|
|
@@ -206,12 +206,21 @@ class Database:
|
|
|
206
206
|
CASE
|
|
207
207
|
WHEN AREA.uuid IS NOT NULL THEN AREA.uuid
|
|
208
208
|
END AS area,
|
|
209
|
+
CASE
|
|
210
|
+
WHEN AREA.uuid IS NOT NULL THEN AREA.title
|
|
211
|
+
END AS area_title,
|
|
209
212
|
CASE
|
|
210
213
|
WHEN PROJECT.uuid IS NOT NULL THEN PROJECT.uuid
|
|
211
214
|
END AS project,
|
|
215
|
+
CASE
|
|
216
|
+
WHEN PROJECT.uuid IS NOT NULL THEN PROJECT.title
|
|
217
|
+
END AS project_title,
|
|
212
218
|
CASE
|
|
213
219
|
WHEN HEADING.uuid IS NOT NULL THEN HEADING.uuid
|
|
214
220
|
END AS heading,
|
|
221
|
+
CASE
|
|
222
|
+
WHEN HEADING.uuid IS NOT NULL THEN HEADING.title
|
|
223
|
+
END AS heading_title,
|
|
215
224
|
TASK.notes,
|
|
216
225
|
CASE
|
|
217
226
|
WHEN TAG.uuid IS NOT NULL THEN 1
|
|
@@ -228,7 +237,9 @@ class Database:
|
|
|
228
237
|
date(TASK.{self.DATE_DEADLINE}, "unixepoch") AS deadline,
|
|
229
238
|
date(TASK.stopDate, "unixepoch") AS "stop_date",
|
|
230
239
|
datetime(TASK.{self.DATE_CREATE}, "unixepoch", "localtime") AS created,
|
|
231
|
-
datetime(TASK.{self.DATE_MOD}, "unixepoch", "localtime") AS modified
|
|
240
|
+
datetime(TASK.{self.DATE_MOD}, "unixepoch", "localtime") AS modified,
|
|
241
|
+
TASK.'index',
|
|
242
|
+
TASK.todayIndex
|
|
232
243
|
FROM
|
|
233
244
|
{self.TABLE_TASK} AS TASK
|
|
234
245
|
LEFT OUTER JOIN
|
|
@@ -252,6 +263,7 @@ class Database:
|
|
|
252
263
|
"""
|
|
253
264
|
|
|
254
265
|
def get_task_rows(self, where_predicate):
|
|
266
|
+
"""Executes SQL query with given WHERE clauses."""
|
|
255
267
|
return self.execute_query(self.make_task_sql_query(where_predicate))
|
|
256
268
|
|
|
257
269
|
def get_areas(self, uuid=None, tag=None, count_only=False):
|
|
@@ -262,17 +274,13 @@ class Database:
|
|
|
262
274
|
valid_tags = self.get_tags(titles_only=True)
|
|
263
275
|
validate("tag", tag, [None] + list(valid_tags))
|
|
264
276
|
|
|
265
|
-
if (
|
|
266
|
-
uuid
|
|
267
|
-
and count_only is False
|
|
268
|
-
and not self.get_areas(uuid=uuid, count_only=True)
|
|
269
|
-
):
|
|
277
|
+
if (uuid and count_only is False and not self.get_areas(uuid=uuid, count_only=True)):
|
|
270
278
|
raise ValueError(f"No such area uuid found: {uuid!r}")
|
|
271
279
|
|
|
272
280
|
# Query
|
|
273
281
|
sql_query = f"""
|
|
274
282
|
SELECT
|
|
275
|
-
AREA.uuid,
|
|
283
|
+
DISTINCT AREA.uuid,
|
|
276
284
|
'area' as type,
|
|
277
285
|
AREA.title,
|
|
278
286
|
CASE
|
|
@@ -292,8 +300,8 @@ class Database:
|
|
|
292
300
|
"""
|
|
293
301
|
if count_only:
|
|
294
302
|
return self.get_count(sql_query)
|
|
295
|
-
|
|
296
|
-
|
|
303
|
+
|
|
304
|
+
return self.execute_query(sql_query)
|
|
297
305
|
|
|
298
306
|
def get_checklist_items(self, task_uuid=None):
|
|
299
307
|
"""Get checklist items."""
|
|
@@ -339,21 +347,21 @@ class Database:
|
|
|
339
347
|
if titles_only:
|
|
340
348
|
sql_query = f'SELECT title FROM {self.TABLE_TAG} ORDER BY "index"'
|
|
341
349
|
return self.execute_query(sql_query, row_factory=list_factory)
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
350
|
+
|
|
351
|
+
sql_query = f"""
|
|
352
|
+
SELECT
|
|
353
|
+
uuid,
|
|
354
|
+
'tag' AS type,
|
|
355
|
+
title,
|
|
356
|
+
shortcut
|
|
357
|
+
FROM
|
|
358
|
+
{self.TABLE_TAG}
|
|
359
|
+
WHERE
|
|
360
|
+
TRUE
|
|
361
|
+
{make_filter('title', title)}
|
|
362
|
+
ORDER BY "index"
|
|
363
|
+
"""
|
|
364
|
+
return self.execute_query(sql_query)
|
|
357
365
|
|
|
358
366
|
def get_tags_of_task(self, task_uuid):
|
|
359
367
|
"""Get tag titles of task"""
|
|
@@ -391,7 +399,6 @@ class Database:
|
|
|
391
399
|
|
|
392
400
|
def get_version(self):
|
|
393
401
|
"""Get Things Database version."""
|
|
394
|
-
import plistlib
|
|
395
402
|
|
|
396
403
|
sql_query = f"SELECT value FROM {self.TABLE_META} WHERE key = 'databaseVersion'"
|
|
397
404
|
result = self.execute_query(sql_query, row_factory=list_factory)
|
|
@@ -399,6 +406,7 @@ class Database:
|
|
|
399
406
|
return plistlib.loads(plist_bytes)
|
|
400
407
|
|
|
401
408
|
def get_count(self, sql):
|
|
409
|
+
"""Count number of results."""
|
|
402
410
|
sql_query = f"""SELECT COUNT(uuid) FROM ({sql})"""
|
|
403
411
|
return self.execute_query(sql_query, row_factory=list_factory)[0]
|
|
404
412
|
|
|
@@ -410,7 +418,7 @@ class Database:
|
|
|
410
418
|
print(sql_query)
|
|
411
419
|
try:
|
|
412
420
|
uri = f"file:{self.filepath}?mode=ro" # "ro" means read-only
|
|
413
|
-
connection = sqlite3.connect(uri, uri=True)
|
|
421
|
+
connection = sqlite3.connect(uri, uri=True) # pylint: disable=E1101
|
|
414
422
|
connection.row_factory = row_factory or dict_factory
|
|
415
423
|
cursor = connection.cursor()
|
|
416
424
|
cursor.execute(sql_query, parameters)
|
|
@@ -419,7 +427,7 @@ class Database:
|
|
|
419
427
|
for task in tasks:
|
|
420
428
|
print(task)
|
|
421
429
|
return tasks
|
|
422
|
-
except sqlite3.OperationalError as error:
|
|
430
|
+
except sqlite3.OperationalError as error: # pylint: disable=E1101
|
|
423
431
|
print(f"Could not query the database at: {self.filepath}.")
|
|
424
432
|
print(f"Details: {error}.")
|
|
425
433
|
sys.exit(2)
|
|
@@ -427,10 +435,12 @@ class Database:
|
|
|
427
435
|
# -------- Utility methods --------
|
|
428
436
|
|
|
429
437
|
def last_modified(self):
|
|
438
|
+
"""Get last modified time."""
|
|
430
439
|
mtime_seconds = os.path.getmtime(self.filepath)
|
|
431
440
|
return datetime.datetime.fromtimestamp(mtime_seconds)
|
|
432
441
|
|
|
433
442
|
def was_modified_today(self):
|
|
443
|
+
"""Was task modified today?"""
|
|
434
444
|
last_modified_date = self.last_modified().date()
|
|
435
445
|
todays_date = datetime.datetime.now().date()
|
|
436
446
|
return last_modified_date >= todays_date
|
|
@@ -653,7 +663,7 @@ def dict_factory(cursor, row):
|
|
|
653
663
|
return result
|
|
654
664
|
|
|
655
665
|
|
|
656
|
-
def list_factory(
|
|
666
|
+
def list_factory(_cursor, row):
|
|
657
667
|
"""
|
|
658
668
|
Convert SQL selects of one column into a list.
|
|
659
669
|
"""
|
|
@@ -661,6 +671,7 @@ def list_factory(cursor, row):
|
|
|
661
671
|
|
|
662
672
|
|
|
663
673
|
def make_filter(column, value):
|
|
674
|
+
"""Filter SQL query by AND {column} = "{value}"."""
|
|
664
675
|
default = f'AND {column} = "{value}"'
|
|
665
676
|
# special options
|
|
666
677
|
return {
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|