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

#10276 Correct type hints for IHostnameResolver.resolveHostName #11589

Merged
merged 8 commits into from Nov 22, 2022

Conversation

clokep
Copy link
Contributor

@clokep clokep commented Jul 26, 2022

Scope and purpose

Fixes #10276

Adds missing type hints to twisted.internet._resolver, as part of this it updates the type hints for IHostnameResolver.resolveHostName to be correct:

  • addressTypes is a sequence of IAddress types, not IAddress instances.
  • The return value should be IHostResolution, not IResolutionReceiver.

Ports matrix-org/synapse#11328.

Contributor Checklist:

This process applies to all pull requests - no matter how small.
Have a look at our developer documentation before submitting your Pull Request.

Below is a non-exaustive list (as a reminder):

  • The title of the PR should describe the changes and starts with the associated issue number (including the # character for GitHub auto-links).
  • A release notes news fragment file was create in src/twisted/newsfragments/ (see: Release notes fragments docs.)
  • The automated tests were updated.
  • Once all checks are green, request a review by leaving a comment that contains the please review words.
    Our bot will trigger the review process, by applying the pending review label
    and requesting a review from the Twisted dev team.

if TYPE_CHECKING:
from twisted.python.runtime import platform

if platform.supportsThreads():
Copy link
Member

Choose a reason for hiding this comment

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

What does it mean to have an executable condition like this inside an if TYPE_CHECKING block, where the code will definitely never run?

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 think it works fine, I believe anything inside of TYPE_CHECKING is just ignored when CPython executes the code (hence why ThreadPool is quoted below). mypy seems to be OK with it, but I'm not sure if that actually executes the module (or how it picks up on what this is doing).

Note that this conditional is copied from twisted.internet.interfaces:

https://github.com/twisted/twisted/blob/release-21.2.0-10091/src/twisted/internet/interfaces.py#L56-L59


# Make sure it's passed down as a native str, to maintain the interface
hostName = nativeString(hostName)
hostName = nativeString(hostName_bytes)

resolution = HostResolution(hostName)
resolutionReceiver.resolutionBegan(resolution)
onAddress = self._simpleResolver.getHostByName(hostName)
Copy link
Contributor Author

@clokep clokep Sep 1, 2022

Choose a reason for hiding this comment

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

This line now doesn't match the interface for IResolverSimple which states that the timeout parameter is required. I'm not sure if I should:

  1. Pass the default timeout value here.
  2. Update the interface to reflect that a default value exists.

Any thoughts on what to do here?

(There's a similar error related to IHostnameResolver.resolveHostName on line 348.)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We chatted a bit about this in #twisted:

12:04:11 PM - clokep: Not sure how to get feedback on an ongoing PR, but I left a question at https://github.com/twisted/twisted/pull/11589#discussion_r961057512 -- the tl;dr is Twisted internally seems to assume that implementations of an interface have a default value which the interface doesn't show. What's the proper solution to this?
12:04:37 PM - clokep: (To add the default to the interface or the provide the defaults in the caller or something else?)
12:07:49 PM - jean-paul[m]: The goal should usually be to fix the mismatch without breaking any code.  Sometimes that's hard, maybe even hard enough that it's not worth doing, but that's the goal that should guide choices.
12:08:35 PM - jean-paul[m]: Since it's basically a point of extension, we should expect developers are implementing the interface as currently defined.
12:09:48 PM - jean-paul[m]: Buuuut if they did that, their implementation wouldn't actually work with Twisted, right?  Because Twisted calls the method without a value for that parameter.
12:10:28 PM - jean-paul[m]: So from that perspective, the interface is wrong and should be updated to have a default - reflecting real usage by Twisted.
12:11:26 PM - jean-paul[m]: You could also look at it from the opposite perspective - someone wrote code that expects implementations of the interface and then uses it according to what the interface says.  Fortunately, this means those implementations would already be passing a value for `timeout` since the interface says you have to.
12:11:40 PM - jean-paul[m]: So this code also wouldn't be broken by adding a default to the interface.
12:12:08 PM - jean-paul[m]: I don't think that's a complete case analysis but it's probably the major points.  And I don't think this interface is an extremely popular extension point anyway.
12:12:21 PM - clokep: Right, my inclination was that adding a default to the interface is the "least" breaking thing to do.
12:12:40 PM - jean-paul[m]: That seems right to me.
12:13:01 PM - clokep: Awesome. Thanks for the help! Will try to finish that up "soon"!
12:13:23 PM - clokep: (I think connectTCP has a similar issue with parameters, will need to check the Synapse codebase again for ignores.)
12:15:00 PM - graingert[m]: I think the policy on type annotations is that they may break at any time right now
12:15:16 PM - graingert[m]: I can't find the docs on that
12:15:17 PM - graingert[m]: But they're currently provisional
12:15:42 PM - clokep: Yeah, I'm not really talking about the type annotations breaking -- I'm talking about type annotations finding bugs in old code. 😁
12:17:13 PM - graingert[m]: Ah right

The tl;dr is that option 2 from above seems more reasonable in this case (to avoid breaking code).

@@ -747,7 +751,7 @@ def ebLog(error):
d.addCallback(cbDeliver)
d.addErrback(ebLog)
d.addBoth(lambda ignored: resolutionReceiver.resolutionComplete())
return resolutionReceiver
return resolution
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 changes the return value here, but it is never used:

self._nameResolver.resolveHostName(
EndpointReceiver, self._hostText, portNumber=self._port
)

Copy link
Member

Choose a reason for hiding this comment

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

Hm... might not be used by Twisted own code, but this looks like public API, so it might be used by someone else.

But I agree that the change makes more sense... since the old code just returns an input argument.

But this needs to follow the deprecations process... or ask for an exception


Do we still need @rtype: L{IResolutionReceiver} in the docstring ?

same for @type resolutionReceiver: L{IResolutionReceiver} ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hm... might not be used by Twisted own code, but this looks like public API, so it might be used by someone else.

But I agree that the change makes more sense... since the old code just returns an input argument.

I don't think I was clear above -- this implementation (i.e. _nameResolver which gets set from a private class also in this file) returns the wrong type, which is never used. I don't see anyway for this to leak out to the public API. This is an internal-only change.

@clokep clokep changed the title #11328: Correct type hints for IHostnameResolver.resolveHostName #11328 Correct type hints for IHostnameResolver.resolveHostName Sep 9, 2022
@clokep clokep changed the title #11328 Correct type hints for IHostnameResolver.resolveHostName #10237 Correct type hints for IHostnameResolver.resolveHostName Sep 9, 2022
@clokep clokep changed the title #10237 Correct type hints for IHostnameResolver.resolveHostName #10276 Correct type hints for IHostnameResolver.resolveHostName Sep 9, 2022
This corrects the type hints on IHostnameResolver.resolveHostName:

* addressTypes is a sequence of IAddress types, not IAddress instances.
* The return value should be IHostResolution, not IResolutionReceiver.

Ports matrix-org/synapse#11328.
@clokep clokep marked this pull request as ready for review September 9, 2022 17:22
@chevah-robot chevah-robot requested a review from a team September 9, 2022 17:22
@clokep
Copy link
Contributor Author

clokep commented Sep 9, 2022

please review

@adiroiban
Copy link
Member

The return value should be IHostResolution, not IResolutionReceiver.

Why? Just asking.

@clokep
Copy link
Contributor Author

clokep commented Sep 13, 2022

The return value should be IHostResolution, not IResolutionReceiver.

Why? Just asking.

Because the type hint was incorrect when added.

The implementation of GAIResolver, which was the initial implementation of IHostNameResolver has always returned (since 2016) a IHostResolution instance, see #548. Note that but the documentation of the return type was incorrect in that pull request (but the actual description was correct):

https://github.com/twisted/twisted/pull/548/files#diff-79b2f1f9960a400f335be8682ed57a4cf5379e47c067c9a320cad9e7c91890e6R186

https://github.com/twisted/twisted/pull/548/files#diff-575bab6dffb171ffba2feff510a95f0aa1105a17f3d7100961bcaeda7b7c5cd1R185-R186

The incorrect documentation than got ported into an incorrect type hint in #1430. (I can't seem to deep link easily into that PR unfortunately.)

When the endpoint resolver was added it seems like it was added by following the (incorrect) documentation:

https://github.com/twisted/twisted/pull/740/files#diff-5f22d09685cbd043da78599cb70caa39bf60b73abf2f94fe1ec72d020ad5ccc5R718

Luckily the _SimpleHostnameResolver from the endpoints code is not public and the result of calling resolveHostName is never used:

self._nameResolver.resolveHostName(
EndpointReceiver, self._hostText, portNumber=self._port
)


In summary, what we have is:

  1. Multiple implementations of IHostNameResolver which differ in their implementation (GAIResolver and SimpleResolverComplexifier return IHostResolution; _SimpleHostnameResolver returns IResolutionReceiver).
  2. IHostNameResolver.resolveHostName is documented as returning IResolutionReceiver.
  3. IHostNameResolver.resolveHostName has a type hint as returning IResolverReceiver (but it has been mentioned that type hints are considered provisional at the moment).

I'm asserting that the best way to not break things is to:

  1. Consistently implement IHostNameResolver between the three implementations, choosing the implementations which are public (yes, this changes _SimpleHostnameResolver, but that shouldn't leak outside the APIs at all).
  2. Update the type hint on IHostNameResolver.resolveHostName since those can change at any point and are not subject to the deprecation policy (that's my understanding -- I might be wrong).

Honestly: this is the only way I can see us not breaking code. But it might (honestly it likely will) break type hints for anyone that is implementing IHostNameResolver. I don't see many folks as having a separate implementation of that though!

As a final thought, I'm fairly certain the initial docstring was wrong (and the implementations are correct) since IResolutionReceiver is passed into resolverHostName, so returning it is useless. Without returning IHostResolution, however, than the caller has no access to it. 🤷

Copy link
Member

@glyph glyph left a comment

Choose a reason for hiding this comment

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

OK. I don't love the TYPE_CHECKING block but it you wouldn't mind hitting that in a follow-up, I'll just merge this now.

src/twisted/internet/_resolver.py Outdated Show resolved Hide resolved
"""
Create a L{HostResolution} with the given name.
"""
self.name = name

def cancel(self):
def cancel(self) -> NoReturn:
Copy link
Member

Choose a reason for hiding this comment

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

Hmm. IMHO this should really be -> None, to match the interface.

(And this is not this change's problem, but putting "@implementer on a superclass that does not in fact implement the interface, and just promotes a static type-check error into a dynamic NotImplementedError is an antipattern, let's do less of this in the future.)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good catch. I'm surprised mypy allowed this in the first place.

src/twisted/internet/base.py Show resolved Hide resolved
[mypy-twisted.internet._resolver]
allow_untyped_defs = True
check_untyped_defs = False

[mypy-twisted.internet._signals]
Copy link
Member

Choose a reason for hiding this comment

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

❤️

@glyph glyph enabled auto-merge November 22, 2022 19:47
@glyph
Copy link
Member

glyph commented Nov 22, 2022

Hopefully that trivial import conflict resolution will not make CI mad, but if I flubbed it anybody else can feel free to fix it up for merge.

auto-merge was automatically disabled November 22, 2022 20:06

Head branch was pushed to by a user without write access

@glyph glyph enabled auto-merge November 22, 2022 20:36
@glyph glyph merged commit 4f9f584 into twisted:trunk Nov 22, 2022
@clokep clokep deleted the resolver-types branch November 22, 2022 21:03
@clokep
Copy link
Contributor Author

clokep commented Nov 22, 2022

Thanks for the review! 🎉

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

Successfully merging this pull request may close these issues.

Type hints on IHostnameResolver.resolveHostName seem incorrect
5 participants