Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added complete function, reworked link function and added tests #121

Merged
merged 14 commits into from
Jun 9, 2024

Conversation

cato447
Copy link
Contributor

@cato447 cato447 commented May 27, 2024

Motivation

I am working on a project that syncs Things3 with reclaim.ai.
For this I needed a way to programmatically complete a things task.

Changes made:

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

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
@AlexanderWillner
Copy link
Contributor

For this I needed a way to programmatically complete a things task.

That's nice! I always wanted to add support for the URL Scheme. Thank you for the patch. Couldn't find time to review, yet.

@mikez
Copy link
Contributor

mikez commented Jun 7, 2024

@cato447 Thank you! Two suggestions, otherwise looks good to me:

  1. Keep the API backwards-compatible (otherwise it might break for others who're using link);
  2. No need to check the auth-token when you do non-writeable links (the user may not have set a token).

Here's some suggested code (you might find other actions that don't require a token):

def link(uid, action="show", **kwargs) -> str:
    """
    Return a things:///<action>?id=uid&… url.
    Additional query parameters can be provided in **kwargs
    …
    """
    query_params = dict(id=uid, **kwargs)

    # authenticate if needed
    if action not in ("show", "search"):
        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}"

Also, I wonder if we could create an alias url() for the function link()... it seems the Things.app talks about Things URLs and not links. I believe this was my mistake in naming it link().

@cato447
Copy link
Contributor Author

cato447 commented Jun 8, 2024

I changed the signature of link because add, add-project, search and json do not require an id parameter.
I will test if setting the id parameter anyways causes problems.

Aliases in python3 can be done by url = link
image

@cato447
Copy link
Contributor Author

cato447 commented Jun 8, 2024

My results from testing:
The added id parameter is not a problem when using actions that don't require it so it can stay.
This makes it kind of awkward to use since building a link always requires setting the id parameter.

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
@mikez
Copy link
Contributor

mikez commented Jun 8, 2024

This makes it kind of awkward to use since building a link always requires setting the id parameter.

Good point, I see what you're saying. How about doing it like this:

link(uid=None, action="show", **kwargs)
    query_params = {} if uid is None else dict(id=uid)
    query_params.update(kwargs)
    …

That way you can do link(action="update", uid="123") if you wish, but also link(123).

Documentation: you might want to specify the parameters in the documentation (and that kwargs are the other query params). See the show() or complete() function for how such a documentation might look like.

Also, I don't know what you think about this, but I'm tempted for the main method to be called url and the link to be a backwards-compatible alias.

@cato447
Copy link
Contributor Author

cato447 commented Jun 8, 2024

Calling the main method url instead of link is a good idea.

I tested a little bit a more and found another problem.
The Things URL Scheme parameters are in kebab-case (words separated by hyphens).
Our current implementation breaks if a parameter in kebab-case is used in kwargs.
This is because variables are not allowed to be written in kebab-case in python.

My first ideas would be to pass the parameters as a dictionary and not as variables in kwargs

… 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
@mikez
Copy link
Contributor

mikez commented Jun 8, 2024

My first ideas would be to pass the parameters as a dictionary and not as variables in kwargs

👍 This sounds good to me. However, keeping kwargs might work as well.

def f(**kwargs):
    print(kwargs)

d = {"what-is-this": 10}

Now you can do this:

>>> f(**d)
{'what-is-this': 10}

things/api.py Show resolved Hide resolved
things/api.py Outdated Show resolved Hide resolved
things/api.py Outdated Show resolved Hide resolved
things/api.py Outdated Show resolved Hide resolved
things/api.py Outdated Show resolved Hide resolved
things/api.py Outdated
>>> 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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you don't want to mutate kwargs, then you can do dict(kwargs) as well. (Since kwargs is created afresh on every call, I wouldn't think there's any danger in mutating it directly either... or is it?)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not that experienced using kwargs. I just copied the statement from the else case to get it to work.
There is no deeper though behind using dict(**kwargs)

things/api.py Show resolved Hide resolved
@mikez
Copy link
Contributor

mikez commented Jun 8, 2024

@cato447 Great work! This looks good to me besides the comments. Thank you for your work here and sorry about the thorough review.

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.
@cato447
Copy link
Contributor Author

cato447 commented Jun 8, 2024

sorry about the thorough review.

No problem! This is currently part of my procrastination project and I am very thankful for your work and am happy to see this project being well maintained :D

@mikez
Copy link
Contributor

mikez commented Jun 9, 2024

@cato447 I made some minor tweaks to api.py; let me know if this looks ok to you. If so, then I will merge this.

@cato447
Copy link
Contributor Author

cato447 commented Jun 9, 2024

@mikez looks good to me. Thank you for your time!

fixed suggestions via lint
@codecov-commenter
Copy link

codecov-commenter commented Jun 9, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 99.34%. Comparing base (41f0112) to head (ff54025).
Report is 36 commits behind head on main.

Current head ff54025 differs from pull request most recent head b89f226

Please upload reports for the commit b89f226 to get more accurate results.

❗ Your organization needs to install the Codecov GitHub app to enable full functionality.

Additional details and impacted files
@@             Coverage Diff             @@
##              main     #121      +/-   ##
===========================================
- Coverage   100.00%   99.34%   -0.66%     
===========================================
  Files            2        3       +1     
  Lines          363      455      +92     
===========================================
+ Hits           363      452      +89     
- Misses           0        3       +3     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@mikez
Copy link
Contributor

mikez commented Jun 9, 2024

@cato447 Lint is reporting two "line too long" errors:

 ************* Module tests.test_things
tests/test_things.py:260:0: C0301: Line too long (102/100) (line-too-long)
tests/test_things.py:373:0: C0301: Line too long (102/100) (line-too-long)

Sorry about this, that lint is very strict. Can you fix those? I'll merge in later today.

@cato447
Copy link
Contributor Author

cato447 commented Jun 9, 2024

@cato447 Lint is reporting two "line too long" errors:


 ************* Module tests.test_things

tests/test_things.py:260:0: C0301: Line too long (102/100) (line-too-long)

tests/test_things.py:373:0: C0301: Line too long (102/100) (line-too-long)

Sorry about this, that lint is very strict. Can you fix those? I'll merge in later today.

What should I do about the other errors in the lint report?
Fix them as well?

@mikez
Copy link
Contributor

mikez commented Jun 9, 2024

@cato447 Anything that doesn't involve code that you wrote, I will fix.

Copy link

sonarcloud bot commented Jun 9, 2024

Quality Gate Passed Quality Gate passed

Issues
0 New issues
0 Accepted issues

Measures
0 Security Hotspots
No data about Coverage
No data about Duplication

See analysis details on SonarCloud

@mikez
Copy link
Contributor

mikez commented Jun 9, 2024

FWIW, I opened up an issue on the pylint error here: pylint-dev/pylint#9715 and pyright error here: microsoft/pyright#8107

@mikez mikez merged commit 9ed1ddf into thingsapi:main Jun 9, 2024
6 checks passed
@mikez
Copy link
Contributor

mikez commented Jun 9, 2024

Thanks @cato447. Congrats on the merge! 🥳🎉

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

4 participants