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

Fix mypy error: Too few arguments to "makeTestCaseClasses" #1261

Merged
merged 6 commits into from
May 8, 2020

Conversation

rodrigc
Copy link
Contributor

@rodrigc rodrigc commented May 5, 2020

@rodrigc
Copy link
Contributor Author

rodrigc commented May 5, 2020

This helps address some of the mypy errors found here: https://travis-ci.com/github/twisted/twisted/jobs/327861205#L4888

such as:

src/twisted/internet/test/reactormixins.py:350:27: error: Incompatible types in
assignment (expression has type "classmethod", variable has type
"Callable[[ReactorBuilder], Any]")  [assignment]
        makeTestCaseClasses = classmethod(makeTestCaseClasses)
src/twisted/internet/test/test_win32events.py:199:18: error: Too few arguments
for "makeTestCaseClasses"  [call-arg]
    globals().update(Win32EventsTestsBuilder.makeTestCaseClasses())

I followed the suggestions here: https://stackoverflow.com/questions/44640479/mypy-annotation-for-classmethod-returning-instance
for using TypeVar

@rodrigc rodrigc force-pushed the 9811-rodrigc-mypy branch 5 times, most recently from 1139eb9 to 1efd196 Compare May 5, 2020 17:22
@rodrigc rodrigc requested a review from hawkowl May 7, 2020 20:53
- Fix some pyflakes/pylint errors.
- Add some type annotations to appease mypy
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.

Aside from needing to annotating the type: ignore comments before landing, this looks great, thank you!

from twisted.internet import process
except ImportError:
process = None # type: ignore
Copy link
Member

Choose a reason for hiding this comment

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

Could you file a ticket for a more mypy-friendly way of doing this, and link it here before landing? I am OK with adding type: ignore for an import hack like this (particularly in a test helper) but before the pattern replicates ad infinitum I would like to record the fact that we should clean it up somehow later.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

There already is an open issue python/mypy#1297 (comment) where this use-case was mentioned. I'll follow up there, and see what I can find out.

Copy link
Contributor Author

@rodrigc rodrigc May 8, 2020

Choose a reason for hiding this comment

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

So based on this: python/mypy#1297 (comment)
I think we can remove the #type: ignore, and do this:

try
    from twisted.internet import process as _process
except ImportError:
    process = None
else:
    process = _process

def makeTestCaseClasses(cls):
@classmethod
def makeTestCaseClasses(
cls: Type['ReactorBuilder']
Copy link
Member

Choose a reason for hiding this comment

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

Why are these all quoted? The names are imported, so they should be in scope?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ReactorBuilder is defined in the same file, so I had to use the syntax Class name forward references, as described here: https://mypy.readthedocs.io/en/stable/kinds_of_types.html#class-name-forward-references

otherwise mypy would complain.

Copy link
Contributor Author

@rodrigc rodrigc May 8, 2020

Choose a reason for hiding this comment

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

Actually mypy did not complain. If I didn't put ReactorBuilder in quotes, pyflakes complained with:

src/twisted/internet/test/reactormixins.py:331:19 undefined name 'ReactorBuilder'
src/twisted/internet/test/reactormixins.py:332:31 undefined name 'ReactorBuilder'

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've changed this so SynchronousTestCase is not in quotes, but I still need 'ReactorBuilder` to be in quotes for the annotation.

"""
Create a L{SynchronousTestCase} subclass which mixes in C{cls} for each
known reactor and return a dict mapping their names to them.
"""
classes = {}
classes = {} # type: Dict[str, Union[Type['ReactorBuilder'], Type['SynchronousTestCase']]] # noqa
Copy link
Member

Choose a reason for hiding this comment

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

Quoting in type comments seems doubly pointless, even if it works?

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'm not sure exactly how mypy parses the file and comments to do all the type checks. I just kept the use the same to be consistent.

Copy link
Contributor

@mthuurne mthuurne May 8, 2020

Choose a reason for hiding this comment

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

For mypy's sake you never need to quote, but the Python VM does require it when using a type name before it is defined, unless you use from __future__ import annotations, but that's a Python 3.7 feature.

Of course the Python VM doesn't process the contents of comments, so no quoting is necessary there.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@mthuurne Yes, for now I need to quote 'ReactorBuilder'. Since Twisted still supports Python 3.5 and 3.6, I cannot use from __future__ import annotations

for reactor in cls._reactors:
shortReactorName = reactor.split(".")[-1]
name = (cls.__name__ + "." + shortReactorName + "Tests").replace(".", "_")
class testcase(cls, SynchronousTestCase):
name = (cls.__name__ + "." + shortReactorName +
Copy link
Member

Choose a reason for hiding this comment

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

I guess twistedchecker complained at you?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, I made a lot of small changes like this to appease twistedchecker in terms of line spacing, line length, etc.

name = (cls.__name__ + "." + shortReactorName +
"Tests").replace(".", "_")

class testcase(cls, SynchronousTestCase): # type: ignore
Copy link
Member

Choose a reason for hiding this comment

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

What doesn't mypy like about this? Again, I would like type: ignores to be used sparingly and we should generally have a plan for how to get rid of them, at least file an upstream bug against mypy.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If I didn't put that #type: ignore there, mypy would give:

src/twisted/internet/test/reactormixins.py:343:28: error: Variable "cls" is not valid as a type  [valid-type]
                class testcase(cls, SynchronousTestCase):
                               ^
src/twisted/internet/test/reactormixins.py:343:28: note: See https://mypy.readthedocs.io/en/latest/common_issues.html#variables-vs-type-aliases
src/twisted/internet/test/reactormixins.py:343:28: error: Invalid base class "cls"  [misc]
                class testcase(cls, SynchronousTestCase):

The reason for this is that cls is a parameter, and the type of the parameter is only known at runtime. So mypy doesn't know what type it is, and can't check it.

Copy link
Contributor

Choose a reason for hiding this comment

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

Note that if you have to use type: ignore, you can specify the error category to ignore, which reduces the chance of accidentally silencing unexpected errors. In this case it would be # type: ignore[valid-type].

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@mthuurne thanks for the feedback. I've changed this line to specifically turn off two mypy errors on this line:

class testcase(cls, SynchronousTestCase):  # type: ignore[valid-type,misc]

__module__ = cls.__module__
if reactor in cls.skippedReactors:
skip = cls.skippedReactors[reactor]
try:
reactorFactory = namedAny(reactor)
except:
except Exception:
Copy link
Member

Choose a reason for hiding this comment

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

This should be BaseException to avoid a change to its semantics.

@rodrigc rodrigc merged commit 79a26b0 into trunk May 8, 2020
@rodrigc rodrigc deleted the 9811-rodrigc-mypy branch May 8, 2020 04:02
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.

None yet

3 participants