Skip to content

Commit

Permalink
Fix dmypy run when bad options passed to mypy (#6153)
Browse files Browse the repository at this point in the history
  • Loading branch information
emmatyping authored Jan 8, 2019
1 parent 94fe11c commit 41d6aea
Show file tree
Hide file tree
Showing 5 changed files with 80 additions and 12 deletions.
2 changes: 0 additions & 2 deletions mypy/dmypy.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
"""Client for mypy daemon mode.
Highly experimental! Only supports UNIX-like systems.
This manages a daemon process which keeps useful state in memory
rather than having to read it back from disk on each run.
"""
Expand Down
25 changes: 18 additions & 7 deletions mypy/dmypy_server.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
"""Server for mypy daemon mode.
Only supports UNIX-like systems.
This implements a daemon process which keeps useful state in memory
to enable fine-grained incremental reprocessing of changes.
"""

import argparse
import base64
import io
import json
import os
import pickle
Expand All @@ -29,6 +29,7 @@
from mypy.modulefinder import BuildSource, compute_search_paths
from mypy.options import Options
from mypy.typestate import reset_global_state
from mypy.util import redirect_stderr, redirect_stdout
from mypy.version import __version__


Expand Down Expand Up @@ -270,11 +271,19 @@ def cmd_stop(self) -> Dict[str, object]:
def cmd_run(self, version: str, args: Sequence[str]) -> Dict[str, object]:
"""Check a list of files, triggering a restart if needed."""
try:
sources, options = mypy.main.process_options(
['-i'] + list(args),
require_targets=True,
server_options=True,
fscache=self.fscache)
# Process options can exit on improper arguments, so we need to catch that and
# capture stderr so the client can report it
stderr = io.StringIO()
stdout = io.StringIO()
with redirect_stderr(stderr):
with redirect_stdout(stdout):
sources, options = mypy.main.process_options(
['-i'] + list(args),
require_targets=True,
server_options=True,
fscache=self.fscache,
program='mypy-daemon',
header=argparse.SUPPRESS)
# Signal that we need to restart if the options have changed
if self.options_snapshot != options.snapshot():
return {'restart': 'configuration changed'}
Expand All @@ -288,6 +297,8 @@ def cmd_run(self, version: str, args: Sequence[str]) -> Dict[str, object]:
return {'restart': 'plugins changed'}
except InvalidSourceList as err:
return {'out': '', 'err': str(err), 'status': 2}
except SystemExit as e:
return {'out': stdout.getvalue(), 'err': stderr.getvalue(), 'status': e.code}
return self.check(sources)

def cmd_check(self, files: Sequence[str]) -> Dict[str, object]:
Expand Down
6 changes: 4 additions & 2 deletions mypy/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -297,15 +297,17 @@ def process_options(args: List[str],
require_targets: bool = True,
server_options: bool = False,
fscache: Optional[FileSystemCache] = None,
program: str = 'mypy',
header: str = HEADER,
) -> Tuple[List[BuildSource], Options]:
"""Parse command line arguments.
If a FileSystemCache is passed in, and package_root options are given,
call fscache.set_package_root() to set the cache's package root.
"""

parser = argparse.ArgumentParser(prog='mypy',
usage=HEADER,
parser = argparse.ArgumentParser(prog=program,
usage=header,
description=DESCRIPTION,
epilog=FOOTER,
fromfile_prefix_chars='@',
Expand Down
51 changes: 50 additions & 1 deletion mypy/util.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
"""Utility functions with no non-trivial dependencies."""
import contextlib
import os
import pathlib
import re
import subprocess
import sys
from typing import TypeVar, List, Tuple, Optional, Dict, Sequence
from types import TracebackType
from typing import TypeVar, List, Tuple, Optional, Dict, Sequence, TextIO

MYPY = False
if MYPY:
Expand Down Expand Up @@ -255,3 +257,50 @@ def hard_exit(status: int = 0) -> None:
sys.stdout.flush()
sys.stderr.flush()
os._exit(status)


# The following is a backport of stream redirect utilities from Lib/contextlib.py
# We need this for 3.4 support. They can be removed in March 2019!


class _RedirectStream:

_stream = None # type: str

def __init__(self, new_target: TextIO) -> None:
self._new_target = new_target
# We use a list of old targets to make this CM re-entrant
self._old_targets = [] # type: List[TextIO]

def __enter__(self) -> TextIO:
self._old_targets.append(getattr(sys, self._stream))
setattr(sys, self._stream, self._new_target)
return self._new_target

def __exit__(self,
exc_ty: 'Optional[Type[BaseException]]' = None,
exc_val: Optional[BaseException] = None,
exc_tb: Optional[TracebackType] = None,
) -> bool:
setattr(sys, self._stream, self._old_targets.pop())
return False


class redirect_stdout(_RedirectStream):
"""Context manager for temporarily redirecting stdout to another file.
# How to send help() to stderr
with redirect_stdout(sys.stderr):
help(dir)
# How to write help() to a file
with open('help.txt', 'w') as f:
with redirect_stdout(f):
help(pow)
"""

_stream = "stdout"


class redirect_stderr(_RedirectStream):
"""Context manager for temporarily redirecting stderr to another file."""

_stream = "stderr"
8 changes: 8 additions & 0 deletions test-data/unit/daemon.test
Original file line number Diff line number Diff line change
Expand Up @@ -122,3 +122,11 @@ Daemon stopped
import bar
[file bar.py]
pass

[case testDaemonRunNoTarget]
$ dmypy run -- --follow-imports=error
Daemon started
mypy-daemon: error: Missing target module, package, files, or command.
== Return code: 2
$ dmypy stop
Daemon stopped

0 comments on commit 41d6aea

Please sign in to comment.