Skip to content

mypy: Fix erroring files. #135

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

Merged
merged 19 commits into from
Oct 27, 2017
Merged

mypy: Fix erroring files. #135

merged 19 commits into from
Oct 27, 2017

Conversation

alenavolk
Copy link
Collaborator

Finally, I have a PR ready :) Things to keep in mind while reviewing:

  1. I annotated strings as Text in files that didn't have any annotations, and as str in files that already had str annotations.

  2. I made a couple of minor changes to the code in places where I thought it made sense. I tested these changes, but it would be great to have someone look over.

  3. I tried to minimize using type: ignore, but you'll still see it in a couple of places. Please let me know if you can suggest a way to avoid that.

Fixes #127.

@zulipbot
Copy link
Member

Hello @alenavolk, it seems like you have referenced an issue in your pull request, but you have not referenced that issue in your commit message(s). When you reference an issue in a commit message, it automatically closes the corresponding issue when the commit is merged.

Please run git commit --amend in your command line client to amend your commit message description with "Fixes #[issue number]”.

An example of a correctly-formatted commit:

commit fabd5e450374c8dde65ec35f02140383940fe146
Author: zulipbot
Date:   Sat Mar 18 13:42:40 2017 -0700

    pull requests: Check PR commits reference when issue is referenced.

    Fixes #51

Thank you for your contributions to Zulip!

@alenavolk
Copy link
Collaborator Author

alenavolk commented Oct 20, 2017

I've just realized that the changes I've made in zulip_bots/generate_manifest.py are causing a problem with /tools/provision. I'll think how to fix that.

@timabbott
Copy link
Member

@alenavolk it looks like Travis is failing on Python 3.7 and 3.4 because we need to add the typing package to our requirements.txt files.

BTW you should come to our integrations/bots meeting tomorrow at 9AM http://zulip.readthedocs.io/en/latest/chat-zulip-org.html#chat-meetings

Copy link
Collaborator

@roberthoenig roberthoenig left a comment

Choose a reason for hiding this comment

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

This looks good @alenavolk ! Left some comments. I find it surprising how many lines we have to exclude, all seeminglz because of mypy behaving incorrectly :-/

@@ -115,7 +115,7 @@ if __name__ == "__main__":
zulip_client = zulip.init_from_options(args)
try:
log_files = json.loads(open(args.control_path, "r").read())
except (json.JSONDecodeError, IOError):
except (json.JSONDecodeError, IOError): # type: ignore # error: Cannot determine type of 'IOError'
Copy link
Collaborator

Choose a reason for hiding this comment

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

Hmm, I wonder why this happens.

Copy link
Contributor

Choose a reason for hiding this comment

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

Have you tried marking this as a Union[] of the two types? I've not needed to annotate exception blocks before, I think. It seems strange it would not determine the Union from the tuple, in any case.

Copy link
Collaborator Author

@alenavolk alenavolk Oct 26, 2017

Choose a reason for hiding this comment

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

@neiljp In this line any annotation apart from type: ignore results in a misplaced type annotation error.

@@ -50,6 +51,7 @@
# * stream "depot_subdirectory-commits"
# * subject "change_root"
def commit_notice_destination(path, changelist):
Copy link
Collaborator

Choose a reason for hiding this comment

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

changelist doesn't seem to be used at all in this functio (but it is passed as an argument in zulip_change-commit.py. @timabbott do you know anything more about this?

@@ -1,2 +1,2 @@
import pkgutil
__path__ = pkgutil.extend_path(__path__, __name__)
__path__ = pkgutil.extend_path(__path__, __name__) # type: ignore # error: Cannot determine type of '__path__'
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can you do # type: Iterable[Text]? And from typing import Iterable, Text

Copy link
Contributor

Choose a reason for hiding this comment

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

In typeshed, this is defined as:
def extend_path(path: Iterable[str], name: str) -> Iterable[str]: ...
so I would agree with @derAnfaenger, at this point.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in both cases, thanks!

with self.assertRaises(SystemExit) as cm, patch('sys.stderr', new=six.StringIO()) as mock_stderr:
cm = self.assertRaises(SystemExit) # type: ignore # error: No variant of "assertRaises" matches argument types
mock_stderr = patch('sys.stderr', new=six.StringIO())
with cm as cm, mock_stderr as mock_stderr:
Copy link
Collaborator

Choose a reason for hiding this comment

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

Would with cm, mockstderr: suffice here? We already have bound them to variable names before.

Copy link
Contributor

Choose a reason for hiding this comment

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

Alternatively, if typing is fixed, can the original line just be tidied to something like:
with self.assertRaises(SystemExit) as cm, patch('sys.stderr', new=six.StringIO()) as mock_stderr: ?

For the typing, that assertion appears to be present in the main zulip repo, and presumably type checks ok there, so that might be something to work from or provide a test case for mypy if it comes to that?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@derAnfaenger Not really, it would make the test fail:

ERROR: test_invalid_arguments (tests.test_default_arguments.TestDefaultArguments)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/root/python-zulip-api/zulip/tests/test_default_arguments.py", line 27, in test_invalid_arguments
    self.assertTrue(mock_stderr.getvalue().startswith("""usage: lorem ipsum
AttributeError: '_patch' object has no attribute 'getvalue'

Copy link
Collaborator Author

@alenavolk alenavolk Oct 26, 2017

Choose a reason for hiding this comment

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

@neiljp Could you explain again how this line could be improved if typing is fixed? It looks exactly the same now :)

@@ -68,7 +72,7 @@ def get_docs():
return doc_paths

def get_assets():
# type: () -> List[str]
# type: () -> Iterator[str]
Copy link
Collaborator

Choose a reason for hiding this comment

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

Since map() returns a list, can we do from typing import List and keep the List types? Also for the other occurrences in this file.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

That's how it was originally. I changed it to avoid this error: Incompatible return value type (got Iterator[str], expected List[str]).

@neiljp FYI :)

@@ -72,7 +74,7 @@ def main():
# type: () -> None
current_dir = os.path.dirname(os.path.abspath(__file__))
bots_dir = os.path.join(current_dir, "bots")
bots_subdirs = map(os.path.abspath, glob.glob(bots_dir + '/*'))
bots_subdirs = map(lambda d: os.path.abspath(d), glob.glob(bots_dir + '/*'))
Copy link
Collaborator

Choose a reason for hiding this comment

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

map takes a function as the first argument, so the initial version should be fine here.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

It definitely should, but it results in a bunch of weird errors:

Argument 1 to "map" has incompatible type Callable[[AnyStr], AnyStr]; expected Callable[[str], AnyStr]
Argument 1 to "isdir" has incompatible type "AnyStr"; expected "Union[bytes, str, _PathLike[Any]]"
Argument 1 to "parse_args" has incompatible type Iterator[AnyStr]; expected Iterator[str]

@neiljp FYI :)

@@ -35,6 +36,7 @@ def provision_bot(path_to_bot, force):


def parse_args(available_bots):
# type: (Iterator[AnyStr]) -> argparse.Namespace
Copy link
Collaborator

Choose a reason for hiding this comment

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

What's the reason for using AnyStr instead of str here?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I was just trying to get rid of the errors below. You're right, str works fine here. Fixed.

@@ -1,2 +1,2 @@
import pkgutil
__path__ = pkgutil.extend_path(__path__, __name__)
__path__ = pkgutil.extend_path(__path__, __name__) # type: ignore # error: Cannot determine type of '__path__'
Copy link
Collaborator

Choose a reason for hiding this comment

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

Same as for zulip_bots

Copy link
Contributor

@neiljp neiljp left a comment

Choose a reason for hiding this comment

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

Thanks for this :) I always enjoy seeing more type annotations! There are small things to be changed, some in the PR and some outside in typeshed (and maybe mypy). I look forward to seeing this progress :)

@@ -1,6 +1,7 @@
import irc.bot
import irc.strings
from irc.client import ip_numstr_to_quad, ip_quad_to_numstr, Event, ServerConnection
from typing import Any, Dict
Copy link
Contributor

Choose a reason for hiding this comment

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

This line makes the file pass mypy, I assume, but is it possible to use a specific type instead of Any in __init__ ?

Copy link
Collaborator Author

@alenavolk alenavolk Oct 26, 2017

Choose a reason for hiding this comment

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

That's a good point, but I don't think it's possible in this case.

@@ -115,7 +115,7 @@ if __name__ == "__main__":
zulip_client = zulip.init_from_options(args)
try:
log_files = json.loads(open(args.control_path, "r").read())
except (json.JSONDecodeError, IOError):
except (json.JSONDecodeError, IOError): # type: ignore # error: Cannot determine type of 'IOError'
Copy link
Contributor

Choose a reason for hiding this comment

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

Have you tried marking this as a Union[] of the two types? I've not needed to annotate exception blocks before, I think. It seems strange it would not determine the Union from the tuple, in any case.

@@ -10,7 +10,7 @@ ccache_data_encoded = sys.argv[3]

# Update the Kerberos ticket cache file
program_name = "zmirror-%s" % (short_user,)
with open("/home/zulip/ccache/%s" % (program_name,), "w") as f:
with open("/home/zulip/ccache/%s" % (program_name,), "wb") as f:
Copy link
Contributor

Choose a reason for hiding this comment

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

Did mypy flag this due to the write on the following line?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yep, the error is Argument 1 to "write" of "IO" has incompatible type "bytes"; expected "str".

@@ -1,2 +1,2 @@
import pkgutil
__path__ = pkgutil.extend_path(__path__, __name__)
__path__ = pkgutil.extend_path(__path__, __name__) # type: ignore # error: Cannot determine type of '__path__'
Copy link
Contributor

Choose a reason for hiding this comment

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

In typeshed, this is defined as:
def extend_path(path: Iterable[str], name: str) -> Iterable[str]: ...
so I would agree with @derAnfaenger, at this point.

with self.assertRaises(SystemExit) as cm, patch('sys.stderr', new=six.StringIO()) as mock_stderr:
cm = self.assertRaises(SystemExit) # type: ignore # error: No variant of "assertRaises" matches argument types
mock_stderr = patch('sys.stderr', new=six.StringIO())
with cm as cm, mock_stderr as mock_stderr:
Copy link
Contributor

Choose a reason for hiding this comment

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

Alternatively, if typing is fixed, can the original line just be tidied to something like:
with self.assertRaises(SystemExit) as cm, patch('sys.stderr', new=six.StringIO()) as mock_stderr: ?

For the typing, that assertion appears to be present in the main zulip repo, and presumably type checks ok there, so that might be something to work from or provide a test case for mypy if it comes to that?

try:
module = import_module(module_name)
module = import_module(module_name) # type: Any
Copy link
Contributor

Choose a reason for hiding this comment

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

types.ModuleType ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

It looks very logical here, but for some reason it gives me the Module has no attribute "__version__" error two lines below.

@@ -35,7 +35,7 @@ def get_bot_message_handler(self):
# handler class. Eventually, we want bot's handler classes to
# inherit from a common prototype specifying the handle_message
# function.
lib_module = import_module('zulip_bots.bots.{bot}.{bot}'.format(bot=self.bot_name))
lib_module = import_module('zulip_bots.bots.{bot}.{bot}'.format(bot=self.bot_name)) # type: Any
Copy link
Contributor

Choose a reason for hiding this comment

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

types.ModuleType again here?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This one gives me the Module has no attribute "handler_class" error.

@@ -61,7 +61,7 @@ def check_expected_responses(self, expectations, expected_method='send_reply',
# To test send_message, Any would be a Dict type,
# to test send_reply, Any would be a str type.
if isinstance(expectations, dict):
expected = [(k, v) for k, v in expectations.items()]
expected = [(k, v) for k, v in expectations.items()] # type: Sequence[Tuple[str, Any]]
Copy link
Contributor

Choose a reason for hiding this comment

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

This is a list comprehension, in the implementation, so just type it as that?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This line was deleted in master.

@@ -75,7 +77,7 @@ def main():
sys.exit(1)

with patch('zulip.Client') as mock_client:
mock_bot_handler = ExternalBotHandler(mock_client, bot_dir)
mock_bot_handler = ExternalBotHandler(mock_client, bot_dir) # type: Any
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this necessary since this object gets 'monkey patched' later?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I think so, otherwise I get several Cannot assign to a method errors right below (and many others later).

try:
module = import_module(module_name)
module = import_module(module_name) # type: Any
Copy link
Contributor

Choose a reason for hiding this comment

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

types.ModuleType ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Same error as in zulip_bots/setup.py: Module has no attribute "__version__".

@alenavolk
Copy link
Collaborator Author

Regarding the failing Travis build. It looks like adding from typing ... imports to these files:

zulip_bots/setup.py
zulip_bots/generate_manifest.py
zulip_botserver/setup.py

causes the tools/provision script to fail with the No module named typing error.

Travis uses this script to recreate the environment every time it runs the tests, that's why the build fails even though all tests have passed locally.

I've tried adding the typing package to requirements.txt, but it doesn't really solve the problem, because these requirements:

-e ./zulip
-e ./zulip_bots
-e ./zulip_botserver

are installed from the same place, and pip looks at their setup.py files before the installation.
(Besides, typing is installed with the ./zulip package later.)

The only solution I can think of at the moment is to change the problematic imports as follows:

if False:
    from typing import Any, Dict, Optional

It fixes the problem locally, and I think it'll make the Travis build pass as well.

@timabbott
Copy link
Member

@alenavolk I think that means we need to add typing in the setup.py files.

@alenavolk
Copy link
Collaborator Author

alenavolk commented Oct 23, 2017

@timabbott I've added typing as a requirement to both files. For example, in zulip_bots/setup.py:

setuptools_info = dict(
    install_requires=[
        'zulip',
        'mock>=2.0.0',
        'html2text',  # for bots/define
        'typing>=3.5.2.2',
    ],
)

The same thing in zulip_botserver/setup.py. Is that what you had in mind?

Unfortunately, it looks like I'll still have to use the if False trick. Otherwise, the provision script fails (it fails before installing the requirements).


from zulip_bots.lib import run_message_handler_for_bot
from zulip_bots.provision import provision_bot

current_dir = os.path.dirname(os.path.abspath(__file__))

def import_module_from_source(path, name=None):
# type: (Text, Optional[Text]) -> Any
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@neiljp I think this is the only place where I can use types.ModuleType right now.

@alenavolk
Copy link
Collaborator Author

@neiljp Thank you so much for reviewing this! I really appreciate it.

Today I tried annotating the module instances with types.ModuleType instead of Any, but I got the following errors:

zulip_bots/setup.py:73: error: Module has no attribute "__version__"
zulip_bots/zulip_bots/test_lib.py:39: error: Module has no attribute "handler_class"
zulip_botserver/setup.py:61: error: Module has no attribute "__version__"

Does it mean I should keep the type: Any annotations in these files?

Also, I know I could use types.ModuleType in annotating zulip_bots/zulip_bots/run.py:

def import_module_from_source(path, name=None):
    # type: (Text, Optional[Text]) -> ModuleType
    ...

but I'm not sure if it makes sense. Wouldn't it be better to keep Any for consistency?

Looking forward to your reply!

@timabbott
Copy link
Member

@alenavolk if you have a subset of these commits that are fully reviewed and pass tests, it could be good to open those in a new PR so that we can start shrinking this.

Though .. I guess we need to figure out the Travis issue with typing first. @eeshangarg @derAnfaenger do either of you understand the problem?

@timabbott
Copy link
Member

Actually, looking at the error, I think it's just that generate_manifest.py, which is called from setup.py, is special, and does need the if False hack. We should only need that in a couple files though.

@alenavolk
Copy link
Collaborator Author

@timabbott Unfortunately, the error Travis is showing right now is not the only one, there are actually three files failing the Travis build. It's not a complicated problem or anything, but since we don't have a common understanding of how to fix it, I think the best approach would be to first shrink this PR as much as possible and then continue this discussion. I shouldn't have made a PR this big in the first place, but oh well... I'll work with what I've got now :)

@timabbott
Copy link
Member

@alenavolk which 3 files are failing? I think we might want to just do the if False thing for them...

@alenavolk
Copy link
Collaborator Author

@timabbott zulip_bots/setup.py, zulip_bots/generate_manifest.py, and zulip_botserver/setup.py. zulip/setup.py already has the if False hack, otherwise it would be on this list too.

The problem in a nutshell is that typing is included in the standard library only starting from Python 3.5. On the earlier versions we should either install it before installing our packages (zulip, zulip_bots, and zulip_botserver) the same way we install a modern version of pip:

subprocess.call([pip_path, 'install', 'pip>=9.0', 'typing>=3.5.2.2'])
if subprocess.call([pip_path, 'install', '--prefix', venv_dir, '-r',
                    os.path.join(base_dir, requirements_filename)]):
    raise OSError("The command `pip install -r {}` failed. Dependencies not installed!"
                  .format(os.path.join(base_dir, requirements_filename)))

or use the if False hack in our setup files.

We could also just leave these files on the exclude list for the time being. I honestly don't know which of these solutions is cleaner.

@zulipbot
Copy link
Member

Hello @alenavolk, it seems like you have referenced an issue in your pull request, but you have not referenced that issue in your commit message(s). When you reference an issue in a commit message, it automatically closes the corresponding issue when the commit is merged.

Please run git commit --amend in your command line client to amend your commit message description with "Fixes #[issue number]”.

An example of a correctly-formatted commit:

commit fabd5e450374c8dde65ec35f02140383940fe146
Author: zulipbot
Date:   Sat Mar 18 13:42:40 2017 -0700

    pull requests: Check PR commits reference when issue is referenced.

    Fixes #51

Thank you for your contributions to Zulip!

@zulipbot
Copy link
Member

Hello @alenavolk, it seems like you have referenced an issue in your pull request, but you have not referenced that issue in your commit message(s). When you reference an issue in a commit message, it automatically closes the corresponding issue when the commit is merged.

Please run git commit --amend in your command line client to amend your commit message description with "Fixes #[issue number]”.

An example of a correctly-formatted commit:

commit fabd5e450374c8dde65ec35f02140383940fe146
Author: zulipbot
Date:   Sat Mar 18 13:42:40 2017 -0700

    pull requests: Check PR commits reference when issue is referenced.

    Fixes #51

Thank you for your contributions to Zulip!

@timabbott timabbott merged commit 25d6c68 into zulip:master Oct 27, 2017
@timabbott
Copy link
Member

Merged, thanks @alenavolk! This cleaned up a lot of issues. I think we can probably deal with any remaining type: ignore items in follow-up commits.

@alenavolk alenavolk deleted the issue-127-mypy branch October 27, 2017 16:27
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.

5 participants