Skip to content

Commit

Permalink
Merge pull request #121 from cato447/main
Browse files Browse the repository at this point in the history
Add complete function, rework link function (now also called `things.url`), and add tests
  • Loading branch information
mikez committed Jun 9, 2024
2 parents 8543067 + b89f226 commit 9ed1ddf
Show file tree
Hide file tree
Showing 4 changed files with 118 additions and 5 deletions.
41 changes: 41 additions & 0 deletions tests/test_things.py
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,40 @@ def test_get_link(self):
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)

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",
title="test task",
notes="nice notes\nI really like notes",
)
self.assertEqual(
"things:///add?title=test%20task&notes=nice%20notes%0AI%20really%20like%20notes",
url,
)

@unittest.mock.patch("things.api.token")
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(command="update")
self.assertEqual(token_mock.call_count, 1)

def test_projects(self):
projects = things.projects()
self.assertEqual(3, len(projects))
Expand Down Expand Up @@ -332,6 +366,13 @@ def test_api_show(self, os_system):
things.show("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("test_uuid")
os_system.assert_called_once_with(
"open 'things:///update?id=test_uuid&completed=True&auth-token=vKkylosuSuGwxrz7qcklOw'"
)

def test_thingsdate(self):
sqlfilter: str = things.database.make_thingsdate_filter(
"deadline", "2021-03-28"
Expand Down
2 changes: 2 additions & 0 deletions things/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
areas,
canceled,
checklist_items,
complete,
completed,
deadlines,
get,
Expand All @@ -32,6 +33,7 @@
token,
trash,
upcoming,
url,
)

from things.database import Database # noqa
75 changes: 71 additions & 4 deletions things/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import os
from shlex import quote
from typing import Dict, List, Union
import urllib.parse

from things.database import Database

Expand Down Expand Up @@ -677,9 +678,57 @@ 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 url(uuid=None, command="show", **query_parameters) -> str:
"""
Return a things:///<command>?<query> 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 or None, optional
A valid uuid of any Things object.
If `None`, then 'id' is not added as a parameter unless
specified in `query_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'
"""
if uuid is not None:
query_parameters = {"id": uuid, **query_parameters}

# authenticate if needed
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_string = urllib.parse.urlencode(
query_parameters, quote_via=urllib.parse.quote
)

return f"things:///{command}?{query_string}"


# Alias for backwards compatiblity
link = url


def show(uuid): # noqa
Expand All @@ -696,7 +745,25 @@ def show(uuid): # noqa
>>> tag = things.tags('Home')
>>> things.show(tag['uuid']) # doctest: +SKIP
"""
uri = link(uuid)
uri = url(uuid=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 to-do.
Examples
--------
>>> task = things.todos()[0] # doctest: +SKIP
>>> things.complete(task['uuid']) # doctest: +SKIP
"""
uri = url(uuid=uuid, command="update", completed=True)
os.system(f"open {quote(uri)}")


Expand Down
5 changes: 4 additions & 1 deletion things/database.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Read from the Things SQLite database using SQL queries."""

# pylint: disable=C0302

import datetime
Expand Down Expand Up @@ -1042,9 +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}')" # type: ignore
offset_datetime = f"datetime('now', '{modifier}')"

return f"AND {column_datetime} > {offset_datetime}"

Expand Down

0 comments on commit 9ed1ddf

Please sign in to comment.