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

[Core] Document all exported core methods with type hints #359

Closed
wants to merge 4 commits into from

Conversation

gazpachoking
Copy link
Contributor

Started adding some type hints. This one does all the exported methods from core.

@cas--
Copy link
Member

cas-- commented Feb 3, 2022

Unless we are going to include a type checker, I am not sure the value here? Also the type is included in the docstring and there should be only one source of truth

@gazpachoking
Copy link
Contributor Author

Hmm, that's a good point and I hadn't fully thought it out. Here were my goals:

  1. Have the IDE know what stuff was when I'm refactoring. (I have several branches where I'm trying different stuff, and the IDE rarely has good type info at the moment.)
  2. Define the contract a bit better for exported methods, so it's more apparent if they get changed. (Once again, I was thinking more how pycharm automatically highlights wrong usage/returns more than running an explicit type checker.)
  3. I don't think it would be terrible to enable mypy checking in the loosest mode, where it only cares about the things we have already documented, such that type hints could be added bits at a time.

That being said, point 1 is probably not so applicable to the exported methods, because they normally get used via client, or via component.get(), both of which the IDE loses track of the type hints.
And 2 is probably better served by the docstrings, so it gets exported to the RPC docs. I do like the expressiveness of the type hints compared to just a simple type in the docstring, (e.g. List[Tuple[int, str]]) but in cases where the requirement is more complex it needs to be spelled out in words for the generated documentation anyway. If we did want to move to this style type hints, we could make it the single source of truth, and just make the docstrings for people https://pypi.org/project/sphinx-autodoc-typehints/ (this would be my vote.)

I still think the type hints are quite useful in the IDE for autocompletion, and I'd be inclined to add them to any bits I touch when doing any fixing/refactoring, unless you are against them completely. For more internal methods the disadvantages above are moot.

@cas--
Copy link
Member

cas-- commented Feb 3, 2022

I had forgotten about IDE usefulness so happy to try to accommodate there, just didn't want to have the types to become outdated or spend a lot of effort for little gain.

Personally I find type hints can reduce code readability and unless constantly encountering type issues not worth the added overhead, especially if never fully verified by mypy.

Since we already have a form of typing in the code, I am happy if we experiment with moving types from docstrings. As you suggest starting with core exported methods and use sphinx-autodoc-typehints.

@DjLegolas
Copy link
Contributor

I think that we should also start converting the docstrings to googles's format, probably in another PR.

@cas--
Copy link
Member

cas-- commented Feb 3, 2022

Can use https://github.com/dadadel/pyment/ to convert them

@gazpachoking
Copy link
Contributor Author

I did some work in core.py standardizing the docstrings, removing the types, and enabling sphinx generating them from the function signatures. Seems to be working pretty well.
image
The docstring updates were sorta tedious. I tried pyment, couldn't get it to work very well. I'd be more inclined to fix stuff as it gets touched going forward rather than do a big dump.

@aresch
Copy link
Contributor

aresch commented Feb 4, 2022

Can we add a mypy check as a github action? We really should have typing everywhere that's feasible.

@gazpachoking
Copy link
Contributor Author

Can we add a mypy check as a github action? We really should have typing everywhere that's feasible.

Yeah, I was just experimenting with mypy. Trying to figure the easiest way to introduce it without a shitload of comments on stuff to ignore before we get it all in.

deluge/core/core.py Outdated Show resolved Hide resolved
deluge/core/core.py Outdated Show resolved Hide resolved
Copy link
Contributor Author

@gazpachoking gazpachoking left a comment

Choose a reason for hiding this comment

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

I haven't used mypy a lot, but I think it could be a good idea to enable it. My only hesitation would be if we start having to add a lot of extra cruft to make mypy understand stuff. (by cruft, I mean stuff that is useless to a human reader, not just making things slightly more verbose, which could be useful to humans) I added a few notes on stuff I changed to get mypy to pass on core.py. My biggest concern is how it complains about twisted reactor. If there was some way we could solve that for the whole project at once it would be great.

deluge/core/core.py Outdated Show resolved Hide resolved
deluge/core/core.py Outdated Show resolved Hide resolved
@@ -673,7 +679,7 @@ def pause_torrent(self, torrent_id: str) -> None:
@export
def pause_torrents(self, torrent_ids: List[str] = None) -> None:
"""Pauses a list of torrents"""
if not torrent_ids:
if torrent_ids is None:
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This change was made so that mypy type narrowing worked and knew that torrent_ids couldn't be None any longer. For some reason not torrent_ids doesn't have the same result as far as mypy is concerned.

Copy link
Contributor

Choose a reason for hiding this comment

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

You have to make torrent_ids an Optional[List[str]] since the default is None.

https://docs.python.org/3/library/typing.html#typing.Optional

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Optional is implied when the default is none.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hmm, I guess current recommendations are to make the Optional explicit, but the default mypy behavior still allows inferring it. https://mypy.readthedocs.io/en/stable/config_file.html#confval-no_implicit_optional

Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe it only makes it Optional if you provide no typing?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It for sure was getting the Optional type. The issue I was actually trying to solve was: Item "None" of "Optional[List[str]]" has no attribute "__iter__" (not iterable). When the if was just if not torrent_ids it didn't narrow the type down to List[str], but when the check changed to if torrent_ids is not None it did narrow the type down, and allow iterating on it without complaint.

Copy link
Contributor

Choose a reason for hiding this comment

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

Huh, I guess it's because of the for loop below the conditional. Mypy doesn't know that you're changing the None into a List[str] with the call to get the torrents list. I suppose there are a few ways to change the code to work without reassigning torrent_ids which would likely fix the issue. I also wonder if setting the return value of TorrentManager.get_torrent_list() to List[str] would help?

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 actually figured it out. It wasn't the not torrent_ids that was messing up, it was that I hadn't typed self.torrentmanager, so it didn't actually know the return type when torrent_ids got reassigned. Did some more testing in another branch and got it working fine.

deluge/core/core.py Outdated Show resolved Hide resolved
@cas--
Copy link
Member

cas-- commented Feb 4, 2022

Can we leave the mypy to another PR and get this one merged

@gazpachoking
Copy link
Contributor Author

Can we leave the mypy to another PR and get this one merged

Sure can. I'll back that stuff out, was just doing some experiments.

@gazpachoking
Copy link
Contributor Author

Restored this to just the doc changes and type hints.

@cas-- cas-- closed this in 4096cdf Feb 5, 2022
@cas--
Copy link
Member

cas-- commented Feb 5, 2022

@gazpachoking There are breaking compatibility changes with both older Twisted and Python versions in the PR, I have reverted the code from develop: 1089adb

Perhaps we concentrate on fixing other areas of the code and Windows release as I don't see the expenditure just now worth it?

@gazpachoking
Copy link
Contributor Author

gazpachoking commented Feb 5, 2022

There are breaking compatibility changes with both older Twisted and Python versions in the PR

Ahh. Didn't consider older versions of twisted not having type hinting capability. Shouldn't be any issues with older python though, I didn't use the stdlib types as generics, so that should be fine. The twisted issue would also be resolved if we drop python 3.6 support, was questioning that in my mypy PR. #362

Perhaps we concentrate on fixing other areas of the code and Windows release as I don't see the expenditure just now worth it?

Yeah, that's fine. I just prefer adding the type hints as I go fixing other stuff, so it's a real bummer.

@cas--
Copy link
Member

cas-- commented Feb 5, 2022

Shouldn't be any issues with older python though, I didn't use the stdlib types as generics, so that should be fine.

I thought I saw usage but seems I was mistaken

re python 3.6

Certainly can consider dropping it for 2.1.x since it would simplify our support 🤔

If you can see a quick way to get typing in without too much overhead certainly update this PR. I just have limited time to investigate any resulting errors

I should also see about running a CI that has our minimum requirements and current requirements

@gazpachoking
Copy link
Contributor Author

If you can see a quick way to get typing in without too much overhead certainly update this PR. I just have limited time to investigate any resulting errors

We could do comment type hints, or, probably nicer, just type Deferreds as Deferreds without typing their return value. If we do maybe_coroutine branch that won't be bad, since we don't return a Deferred if the function is wrapped in that.

@gazpachoking
Copy link
Contributor Author

I think there shouldn't be problems without the use of generic Deferred return types...

I should also see about running a CI that has our minimum requirements and current requirements

Yeah, that would be useful.

@gazpachoking
Copy link
Contributor Author

Totally forgot type hints could just be strings. Made that change, it's a much better option.

Remove type hints in docstrings in favor of the ones in method signatures.
Older versions of twisted (<21.7) don't
support generic Deferred type hinting,
this prevents crashes on those versions.
@gazpachoking
Copy link
Contributor Author

Rebased to resolve the conflicts, should be good again.

@cas-- cas-- closed this in dabb505 Feb 11, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants