From 10fa2c8a07142cd149ef59bff22931ae0856c114 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20Bu=C3=9Fmann?= Date: Mon, 27 May 2024 19:08:21 +0200 Subject: [PATCH 01/14] Added complete function, reworked link function and added tests Added the complete function to programmatically complete a things task/project using the Things URL Scheme. Modified the link function to a more general version - Changed hardcoded "show" action to a variable - every link now contains the auth_token query argument - additional query arguments can now be supplied using **kwargs Modified the show function to account for the updated link function Added test for complete function and modified tests for link function to account for the updated link function --- tests/test_things.py | 24 +++++++++++++++++++++--- things/__init__.py | 1 + things/api.py | 43 +++++++++++++++++++++++++++++++++++++++---- 3 files changed, 61 insertions(+), 7 deletions(-) diff --git a/tests/test_things.py b/tests/test_things.py index f0882d8..4e4a129 100644 --- a/tests/test_things.py +++ b/tests/test_things.py @@ -232,8 +232,17 @@ def test_tags(self): self.assertEqual(1, len(tasks)) def test_get_link(self): - link = things.link("uuid") - self.assertEqual("things:///show?id=uuid", link) + link = things.link("show", id="uuid") + self.assertEqual( + "things:///show?auth-token=vKkylosuSuGwxrz7qcklOw&id=uuid", link + ) + + @unittest.mock.patch("things.api.token") + def test_get_link_no_token(self, token_mock): + token_mock.return_value = None + with self.assertRaises(ValueError): + things.link("show", id="uuid") + self.assertEqual(token_mock.call_count, 1) def test_projects(self): projects = things.projects() @@ -330,7 +339,16 @@ def test_database_details(self): @unittest.mock.patch("os.system") def test_api_show(self, os_system): things.show("invalid_uuid") - os_system.assert_called_once_with("open 'things:///show?id=invalid_uuid'") + os_system.assert_called_once_with( + "open 'things:///show?auth-token=vKkylosuSuGwxrz7qcklOw&id=invalid_uuid'" + ) + + @unittest.mock.patch("os.system") + def test_api_complete(self, os_system): + things.complete("invalid_uuid") + os_system.assert_called_once_with( + "open 'things:///update?auth-token=vKkylosuSuGwxrz7qcklOw&id=invalid_uuid&completed=True'" + ) def test_thingsdate(self): sqlfilter: str = things.database.make_thingsdate_filter( diff --git a/things/__init__.py b/things/__init__.py index fc5b228..8ce149d 100644 --- a/things/__init__.py +++ b/things/__init__.py @@ -14,6 +14,7 @@ areas, canceled, checklist_items, + complete, completed, deadlines, get, diff --git a/things/api.py b/things/api.py index 736e0fc..74ef2ef 100644 --- a/things/api.py +++ b/things/api.py @@ -677,9 +677,26 @@ def token(**kwargs) -> Union[str, None]: return database.get_url_scheme_auth_token() -def link(uuid): - """Return a things:///show?id=uuid link.""" - return f"things:///show?id={uuid}" +def link(action, **kwargs) -> str: + """ + Return a things:///?auth-token=(&) link. + + Additional query parameters can be provided in **kwargs + + Detailed information about the Things URL Scheme can be found + [here](https://culturedcode.com/things/help/url-scheme/). + + The returned link can be called with + >>> os.system(f"open {quote(link)}") # doctest: +SKIP + """ + auth_token = token() + if auth_token is None: + raise ValueError("Things URL scheme authentication token could not be read") + uri = f"things:///{action}?auth-token={auth_token}" + for key, value in kwargs.items(): + uri += f"&{key}={value}" + + return uri def show(uuid): # noqa @@ -696,7 +713,25 @@ def show(uuid): # noqa >>> tag = things.tags('Home') >>> things.show(tag['uuid']) # doctest: +SKIP """ - uri = link(uuid) + uri = link("show", id=uuid) + os.system(f"open {quote(uri)}") + + +def complete(uuid): # noqa + """ + Set the status of a certain uuid to complete + + Parameters + ---------- + uuid : str + A valid uuid of a project or todo. + + Examples + -------- + >>> task = things.todos()[0] # doctest: +SKIP + >>> things.complete(task['uuid']) # doctest: +SKIP + """ + uri = link("update", id=uuid, completed=True) os.system(f"open {quote(uri)}") From 0e069ee5212f80998412e1789c809610fb2c7797 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20Bu=C3=9Fmann?= Date: Sat, 8 Jun 2024 06:17:20 +0200 Subject: [PATCH 02/14] Added backwards compatiblity to link link implementation respects old usage of link now link function only adds authentication if needed added url as an alias for link as things documentation only mentions things urls tests updated accordingly --- tests/test_things.py | 19 ++++++++++--------- things/__init__.py | 1 + things/api.py | 40 +++++++++++++++++++++++----------------- 3 files changed, 34 insertions(+), 26 deletions(-) diff --git a/tests/test_things.py b/tests/test_things.py index 4e4a129..7c08743 100644 --- a/tests/test_things.py +++ b/tests/test_things.py @@ -232,16 +232,19 @@ def test_tags(self): self.assertEqual(1, len(tasks)) def test_get_link(self): - link = things.link("show", id="uuid") - self.assertEqual( - "things:///show?auth-token=vKkylosuSuGwxrz7qcklOw&id=uuid", link - ) + link = things.link("uuid") + self.assertEqual("things:///show?id=uuid", link) + + def test_get_url(self): + url = things.url("uuid") + self.assertEqual("things:///show?id=uuid", url) @unittest.mock.patch("things.api.token") def test_get_link_no_token(self, token_mock): token_mock.return_value = None with self.assertRaises(ValueError): - things.link("show", id="uuid") + # currently only update and update-project require authentication + things.link(uuid="uuid", action="update") self.assertEqual(token_mock.call_count, 1) def test_projects(self): @@ -339,15 +342,13 @@ def test_database_details(self): @unittest.mock.patch("os.system") def test_api_show(self, os_system): things.show("invalid_uuid") - os_system.assert_called_once_with( - "open 'things:///show?auth-token=vKkylosuSuGwxrz7qcklOw&id=invalid_uuid'" - ) + os_system.assert_called_once_with("open 'things:///show?id=invalid_uuid'") @unittest.mock.patch("os.system") def test_api_complete(self, os_system): things.complete("invalid_uuid") os_system.assert_called_once_with( - "open 'things:///update?auth-token=vKkylosuSuGwxrz7qcklOw&id=invalid_uuid&completed=True'" + "open 'things:///update?id=invalid_uuid&completed=True&auth-token=vKkylosuSuGwxrz7qcklOw'" ) def test_thingsdate(self): diff --git a/things/__init__.py b/things/__init__.py index 8ce149d..2b14097 100644 --- a/things/__init__.py +++ b/things/__init__.py @@ -33,6 +33,7 @@ token, trash, upcoming, + url, ) from things.database import Database # noqa diff --git a/things/api.py b/things/api.py index 74ef2ef..9ea22a4 100644 --- a/things/api.py +++ b/things/api.py @@ -677,26 +677,32 @@ def token(**kwargs) -> Union[str, None]: return database.get_url_scheme_auth_token() -def link(action, **kwargs) -> str: +def link(uuid, action="show", **kwargs) -> str: """ - Return a things:///?auth-token=(&) link. - + Return a things:///?id=uuid&… url. Additional query parameters can be provided in **kwargs - Detailed information about the Things URL Scheme can be found - [here](https://culturedcode.com/things/help/url-scheme/). - - The returned link can be called with - >>> os.system(f"open {quote(link)}") # doctest: +SKIP + uuid is required for backwards compatiblity. + It will be ignored if not needed. + … """ - auth_token = token() - if auth_token is None: - raise ValueError("Things URL scheme authentication token could not be read") - uri = f"things:///{action}?auth-token={auth_token}" - for key, value in kwargs.items(): - uri += f"&{key}={value}" + query_params = dict(id=uuid, **kwargs) + + # authenticate if needed + if action not in ("add", "add-project", "show", "search", "json"): + auth_token = query_params["auth-token"] = token() + if not auth_token: + raise ValueError("Things URL scheme authentication token could not be read") + + query_params_string = "&".join( + f"{key}={value}" for key, value in query_params.items() + ) + + return f"things:///{action}?{query_params_string}" + - return uri +# Alias since Things.app talks about urls and not links +url = link def show(uuid): # noqa @@ -713,7 +719,7 @@ def show(uuid): # noqa >>> tag = things.tags('Home') >>> things.show(tag['uuid']) # doctest: +SKIP """ - uri = link("show", id=uuid) + uri = link(uuid=uuid) os.system(f"open {quote(uri)}") @@ -731,7 +737,7 @@ def complete(uuid): # noqa >>> task = things.todos()[0] # doctest: +SKIP >>> things.complete(task['uuid']) # doctest: +SKIP """ - uri = link("update", id=uuid, completed=True) + uri = link(uuid=uuid, action="update", completed=True) os.system(f"open {quote(uri)}") From f0358a9303300717f7312bff660052dd9ea222a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20Bu=C3=9Fmann?= Date: Sat, 8 Jun 2024 17:15:00 +0200 Subject: [PATCH 03/14] url is now the main way to generate things urls, added urlencoding to url url is now the main way to generate things urls. link is kept as an alias for backward compatibility. things url generated by url is now url encoded to match with the things url scheme documentation. renamed action variable to command in url to better match with things url scheme documentation. added a test covering url encoding --- tests/test_things.py | 13 +++++++++++- things/api.py | 50 ++++++++++++++++++++++++++++++-------------- 2 files changed, 46 insertions(+), 17 deletions(-) diff --git a/tests/test_things.py b/tests/test_things.py index 7c08743..a82d903 100644 --- a/tests/test_things.py +++ b/tests/test_things.py @@ -239,12 +239,23 @@ def test_get_url(self): url = things.url("uuid") self.assertEqual("things:///show?id=uuid", url) + def test_get_url_encoding(self): + url = things.url( + command="add", + title="test task", + notes="really nice notes\nI really like notes", + ) + self.assertEqual( + "things:///add?title=test%20task¬es=really%20nice%20notes%0AI%20really%20like%20notes", + url, + ) + @unittest.mock.patch("things.api.token") def test_get_link_no_token(self, token_mock): token_mock.return_value = None with self.assertRaises(ValueError): # currently only update and update-project require authentication - things.link(uuid="uuid", action="update") + things.url(uuid="uuid", command="update") self.assertEqual(token_mock.call_count, 1) def test_projects(self): diff --git a/things/api.py b/things/api.py index 9ea22a4..7629951 100644 --- a/things/api.py +++ b/things/api.py @@ -10,7 +10,7 @@ import os from shlex import quote from typing import Dict, List, Union - +import urllib.parse from things.database import Database @@ -677,32 +677,50 @@ def token(**kwargs) -> Union[str, None]: return database.get_url_scheme_auth_token() -def link(uuid, action="show", **kwargs) -> str: +def url(uuid=None, command="show", **kwargs) -> str: """ - Return a things:///?id=uuid&… url. - Additional query parameters can be provided in **kwargs + Return a things:///?id=uuid&… url. + + Parameters + ---------- + uuid : str, optional + A valid uuid of any Things object + Defaults to None + + action : str, optional + A valid thingsURL command + Defaults to show - uuid is required for backwards compatiblity. - It will be ignored if not needed. - … + **kwargs: + Any aditional parameters needed. + + For details about availabel commands and their parameters + consult the Things URL Scheme documentation + [here](https://culturedcode.com/things/help/url-scheme/). + + Examples + -------- + >>> things_url_show = things.url("test_uuid") + >>> things_url_update = things.url(command="update", uuid="test_uuid", title="new title") + >>> things_url_add = things.url(command="add", title="new task", when="in 3 days", deadline="in 6 days") """ - query_params = dict(id=uuid, **kwargs) + query_params = dict(**kwargs) if uuid is None else dict(id=uuid, **kwargs) # authenticate if needed - if action not in ("add", "add-project", "show", "search", "json"): + if command not in ("add", "add-project", "show", "search", "json"): auth_token = query_params["auth-token"] = token() if not auth_token: raise ValueError("Things URL scheme authentication token could not be read") - query_params_string = "&".join( - f"{key}={value}" for key, value in query_params.items() + query_params_string = urllib.parse.urlencode( + query_params, quote_via=urllib.parse.quote ) - return f"things:///{action}?{query_params_string}" + return f"things:///{command}?{query_params_string}" -# Alias since Things.app talks about urls and not links -url = link +# Alias for backwards compatiblity +link = url def show(uuid): # noqa @@ -719,7 +737,7 @@ def show(uuid): # noqa >>> tag = things.tags('Home') >>> things.show(tag['uuid']) # doctest: +SKIP """ - uri = link(uuid=uuid) + uri = url(uuid=uuid) os.system(f"open {quote(uri)}") @@ -737,7 +755,7 @@ def complete(uuid): # noqa >>> task = things.todos()[0] # doctest: +SKIP >>> things.complete(task['uuid']) # doctest: +SKIP """ - uri = link(uuid=uuid, action="update", completed=True) + uri = url(uuid=uuid, command="update", completed=True) os.system(f"open {quote(uri)}") From 00280a662f00e5abce47bea5b82cccd2611e3ae5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20Bu=C3=9Fmann?= Date: Sat, 8 Jun 2024 17:22:03 +0200 Subject: [PATCH 04/14] added expected values to testdoc --- things/api.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/things/api.py b/things/api.py index 7629951..58b0ca0 100644 --- a/things/api.py +++ b/things/api.py @@ -700,9 +700,12 @@ def url(uuid=None, command="show", **kwargs) -> str: Examples -------- - >>> things_url_show = things.url("test_uuid") - >>> things_url_update = things.url(command="update", uuid="test_uuid", title="new title") - >>> things_url_add = things.url(command="add", title="new task", when="in 3 days", deadline="in 6 days") + >>> things.url("test_uuid") + 'things:///show?id=test_uuid' + >>> things.url(command="update", uuid="test_uuid", title="new title") + 'things:///update?id=test_uuid&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 = dict(**kwargs) if uuid is None else dict(id=uuid, **kwargs) From e23d088b434abcfd34f6f986e68759ec9047fa82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20Bu=C3=9Fmann?= Date: Sat, 8 Jun 2024 17:28:08 +0200 Subject: [PATCH 05/14] Changed docstring of url --- things/api.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/things/api.py b/things/api.py index 58b0ca0..e0f8ea5 100644 --- a/things/api.py +++ b/things/api.py @@ -681,6 +681,10 @@ def url(uuid=None, command="show", **kwargs) -> str: """ Return a things:///?id=uuid&… url. + For details about available commands and their parameters + consult the Things URL Scheme documentation + [here](https://culturedcode.com/things/help/url-scheme/). + Parameters ---------- uuid : str, optional @@ -694,10 +698,6 @@ def url(uuid=None, command="show", **kwargs) -> str: **kwargs: Any aditional parameters needed. - For details about availabel commands and their parameters - consult the Things URL Scheme documentation - [here](https://culturedcode.com/things/help/url-scheme/). - Examples -------- >>> things.url("test_uuid") From 431eaa30503354d115b8d91d31788b2e3a55b62c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20Bu=C3=9Fmann?= Date: Sat, 8 Jun 2024 23:35:58 +0200 Subject: [PATCH 06/14] Implemented requests from review, ran make auto-style, added tests Implemented requests from review (documentation improvements). Added test case to tests and doctest which covers passing a dictionary of parameters to url. This is needed if a parameter is in kebab-style which is not valid python syntax for a variable name. --- tests/test_things.py | 11 +++++++++++ things/api.py | 19 ++++++++++++------- things/database.py | 1 + 3 files changed, 24 insertions(+), 7 deletions(-) diff --git a/tests/test_things.py b/tests/test_things.py index a82d903..8601244 100644 --- a/tests/test_things.py +++ b/tests/test_things.py @@ -239,6 +239,17 @@ def test_get_url(self): url = things.url("uuid") self.assertEqual("things:///show?id=uuid", url) + def test_get_url_dict(self): + parameters = { + "title": "nice_title", + "list-id": "6c7e77b4-f4d7-44bc-8480-80c0bea585ea", + } + url = things.url(command="add", **parameters) + self.assertEqual( + "things:///add?title=nice_title&list-id=6c7e77b4-f4d7-44bc-8480-80c0bea585ea", + url, + ) + def test_get_url_encoding(self): url = things.url( command="add", diff --git a/things/api.py b/things/api.py index e0f8ea5..522127b 100644 --- a/things/api.py +++ b/things/api.py @@ -10,7 +10,9 @@ import os from shlex import quote from typing import Dict, List, Union + import urllib.parse + from things.database import Database @@ -687,16 +689,16 @@ def url(uuid=None, command="show", **kwargs) -> str: Parameters ---------- - uuid : str, optional - A valid uuid of any Things object - Defaults to None + uuid : str or None, optional + A valid uuid of any Things object. + If None id is not added as a parameter. - action : str, optional - A valid thingsURL command - Defaults to show + action : str, default show + A valid thingsURL command. **kwargs: Any aditional parameters needed. + Passing a dictionary is valid aswell. Examples -------- @@ -706,8 +708,11 @@ def url(uuid=None, command="show", **kwargs) -> str: 'things:///update?id=test_uuid&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' + >>> parameters = {"title" : "test title", "list-id" : "ba5d1237-1dfa-4ab8-826b-7c27b517f29d"} + >>> things.url(command="add", **parameters) + 'things:///add?title=test%20title&list-id=ba5d1237-1dfa-4ab8-826b-7c27b517f29d' """ - query_params = dict(**kwargs) if uuid is None else dict(id=uuid, **kwargs) + query_params = dict(kwargs) if uuid is None else dict(id=uuid, **kwargs) # authenticate if needed if command not in ("add", "add-project", "show", "search", "json"): diff --git a/things/database.py b/things/database.py index 2ea9022..eee2315 100755 --- a/things/database.py +++ b/things/database.py @@ -1,4 +1,5 @@ """Read from the Things SQLite database using SQL queries.""" + # pylint: disable=C0302 import datetime From 89e53cb6509288beed2977d059ff1e5289f07d5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20Bu=C3=9Fmann?= Date: Sat, 8 Jun 2024 23:40:23 +0200 Subject: [PATCH 07/14] adjusted name of test --- tests/test_things.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_things.py b/tests/test_things.py index 8601244..ac3035f 100644 --- a/tests/test_things.py +++ b/tests/test_things.py @@ -262,11 +262,11 @@ def test_get_url_encoding(self): ) @unittest.mock.patch("things.api.token") - def test_get_link_no_token(self, token_mock): + def test_get_url_no_token(self, token_mock): token_mock.return_value = None with self.assertRaises(ValueError): # currently only update and update-project require authentication - things.url(uuid="uuid", command="update") + things.url(command="update") self.assertEqual(token_mock.call_count, 1) def test_projects(self): From 461e450e4f732c343f5a4126b24c6e9fc948dbba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20Bu=C3=9Fmann?= Date: Sat, 8 Jun 2024 23:51:46 +0200 Subject: [PATCH 08/14] Added empty line by mistake in import section --- things/api.py | 1 - 1 file changed, 1 deletion(-) diff --git a/things/api.py b/things/api.py index 522127b..39df9f0 100644 --- a/things/api.py +++ b/things/api.py @@ -10,7 +10,6 @@ import os from shlex import quote from typing import Dict, List, Union - import urllib.parse from things.database import Database From d69ce21e7c55d5b258d51d1f8701e683f1c620b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20Bu=C3=9Fmann?= Date: Sun, 9 Jun 2024 00:00:03 +0200 Subject: [PATCH 09/14] Adjusted variable name in docstring --- things/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/things/api.py b/things/api.py index 39df9f0..89edde5 100644 --- a/things/api.py +++ b/things/api.py @@ -692,7 +692,7 @@ def url(uuid=None, command="show", **kwargs) -> str: A valid uuid of any Things object. If None id is not added as a parameter. - action : str, default show + command : str, default show A valid thingsURL command. **kwargs: From 884f13065f4ce05ed5d2c0d38a06def352f0f522 Mon Sep 17 00:00:00 2001 From: Michael <216956+mikez@users.noreply.github.com> Date: Sun, 9 Jun 2024 12:18:32 +0200 Subject: [PATCH 10/14] Update api.py Various minor tweaks. --- things/api.py | 47 ++++++++++++++++++++++++----------------------- 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/things/api.py b/things/api.py index 89edde5..3100769 100644 --- a/things/api.py +++ b/things/api.py @@ -678,9 +678,9 @@ def token(**kwargs) -> Union[str, None]: return database.get_url_scheme_auth_token() -def url(uuid=None, command="show", **kwargs) -> str: +def url(uuid=None, command="show", **query_parameters) -> str: """ - Return a things:///?id=uuid&… url. + Return a things:///? url. For details about available commands and their parameters consult the Things URL Scheme documentation @@ -690,40 +690,41 @@ def url(uuid=None, command="show", **kwargs) -> str: ---------- uuid : str or None, optional A valid uuid of any Things object. - If None id is not added as a parameter. + If `None`, then 'id' is not added as a parameter unless + specified in `query_parameters`. - command : str, default show - A valid thingsURL command. + command : str, default 'show' + A valid command name. - **kwargs: - Any aditional parameters needed. - Passing a dictionary is valid aswell. + **query_parameters: + Additional URL query parameters. Examples -------- - >>> things.url("test_uuid") - 'things:///show?id=test_uuid' - >>> things.url(command="update", uuid="test_uuid", title="new title") - 'things:///update?id=test_uuid&title=new%20title&auth-token=vKkylosuSuGwxrz7qcklOw' - >>> things.url(command="add", title="new task", when="in 3 days", deadline="in 6 days") + >>> 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' - >>> parameters = {"title" : "test title", "list-id" : "ba5d1237-1dfa-4ab8-826b-7c27b517f29d"} - >>> things.url(command="add", **parameters) + >>> query_parameters = {'title': 'test title', 'list-id': 'ba5d1237-1dfa-4ab8-826b-7c27b517f29d'} + >>> things.url(command="add", **query_parameters) 'things:///add?title=test%20title&list-id=ba5d1237-1dfa-4ab8-826b-7c27b517f29d' """ - query_params = dict(kwargs) if uuid is None else dict(id=uuid, **kwargs) + if uuid is not None: + query_parameters = dict(id=uuid, **query_parameters) # authenticate if needed - if command not in ("add", "add-project", "show", "search", "json"): - auth_token = query_params["auth-token"] = token() + if command in ("update", "update-project"): + auth_token = query_parameters["auth-token"] = token() if not auth_token: raise ValueError("Things URL scheme authentication token could not be read") - query_params_string = urllib.parse.urlencode( - query_params, quote_via=urllib.parse.quote + query_string = urllib.parse.urlencode( + query_parameters, quote_via=urllib.parse.quote ) - return f"things:///{command}?{query_params_string}" + return f"things:///{command}?{query_string}" # Alias for backwards compatiblity @@ -750,12 +751,12 @@ def show(uuid): # noqa def complete(uuid): # noqa """ - Set the status of a certain uuid to complete + Set the status of a certain uuid to complete. Parameters ---------- uuid : str - A valid uuid of a project or todo. + A valid uuid of a project or to-do. Examples -------- From ff540259e2c790cdef15f2d8adbc5b5b70af3ac1 Mon Sep 17 00:00:00 2001 From: Michael <216956+mikez@users.noreply.github.com> Date: Sun, 9 Jun 2024 12:25:15 +0200 Subject: [PATCH 11/14] Update api.py fixed suggestions via lint --- things/api.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/things/api.py b/things/api.py index 3100769..f6ebc4d 100644 --- a/things/api.py +++ b/things/api.py @@ -707,12 +707,12 @@ def url(uuid=None, command="show", **query_parameters) -> str: '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_parameters = {'title': 'test title', 'list-id': 'ba5d1237-1dfa-4ab8-826b-7c27b517f29d'} - >>> things.url(command="add", **query_parameters) + >>> 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' """ if uuid is not None: - query_parameters = dict(id=uuid, **query_parameters) + query_parameters = {"id": uuid, **query_parameters} # authenticate if needed if command in ("update", "update-project"): From 75a8fe2a28a1dcb4b4bd6faa3720dfeb5540bd59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20Bu=C3=9Fmann?= Date: Sun, 9 Jun 2024 13:07:32 +0200 Subject: [PATCH 12/14] fixed line to long in tests --- tests/test_things.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/test_things.py b/tests/test_things.py index ac3035f..3f7bdba 100644 --- a/tests/test_things.py +++ b/tests/test_things.py @@ -254,10 +254,10 @@ def test_get_url_encoding(self): url = things.url( command="add", title="test task", - notes="really nice notes\nI really like notes", + notes="nice notes\nI really like notes", ) self.assertEqual( - "things:///add?title=test%20task¬es=really%20nice%20notes%0AI%20really%20like%20notes", + "things:///add?title=test%20task¬es=nice%20notes%0AI%20really%20like%20notes", url, ) @@ -368,9 +368,9 @@ def test_api_show(self, os_system): @unittest.mock.patch("os.system") def test_api_complete(self, os_system): - things.complete("invalid_uuid") + things.complete("test_uuid") os_system.assert_called_once_with( - "open 'things:///update?id=invalid_uuid&completed=True&auth-token=vKkylosuSuGwxrz7qcklOw'" + "open 'things:///update?id=test_uuid&completed=True&auth-token=vKkylosuSuGwxrz7qcklOw'" ) def test_thingsdate(self): From 5bc118507f2d048a857f7fbc62c96140a22d4db8 Mon Sep 17 00:00:00 2001 From: Michael <216956+mikez@users.noreply.github.com> Date: Sun, 9 Jun 2024 13:53:36 +0200 Subject: [PATCH 13/14] Update database.py add pylint direction --- things/database.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/things/database.py b/things/database.py index eee2315..3ddda2d 100755 --- a/things/database.py +++ b/things/database.py @@ -1045,7 +1045,8 @@ def make_unixtime_range_filter(date_column: str, offset) -> str: modifier = f"-{number} years" column_datetime = f"datetime({date_column}, 'unixepoch')" - offset_datetime = f"datetime('now', '{modifier}')" # type: ignore + offset_datetime = f"datetime('now', '{modifier}')" \ + # pylint: disable=E0606; # type: ignore return f"AND {column_datetime} > {offset_datetime}" From b89f226a0b991a17712c3909f9c29e8bceb4079d Mon Sep 17 00:00:00 2001 From: Michael <216956+mikez@users.noreply.github.com> Date: Sun, 9 Jun 2024 14:08:42 +0200 Subject: [PATCH 14/14] Update database.py --- things/database.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/things/database.py b/things/database.py index 3ddda2d..a508b1a 100755 --- a/things/database.py +++ b/things/database.py @@ -1043,10 +1043,11 @@ def make_unixtime_range_filter(date_column: str, offset) -> str: modifier = f"-{number * 7} days" elif suffix == "y": modifier = f"-{number} years" + else: + raise ValueError() # for pylint; `validate_offset` already checks this column_datetime = f"datetime({date_column}, 'unixepoch')" - offset_datetime = f"datetime('now', '{modifier}')" \ - # pylint: disable=E0606; # type: ignore + offset_datetime = f"datetime('now', '{modifier}')" return f"AND {column_datetime} > {offset_datetime}"