From 79c48ea4bbb98323c45b9d3a378edf103bd83cbb Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 1 Sep 2019 12:00:31 +0100 Subject: [PATCH 1/5] Colorize daemon output and add summary line --- mypy/dmypy/client.py | 11 ++++++--- mypy/dmypy_server.py | 47 +++++++++++++++++++++++++++++------- mypy/main.py | 6 +++-- mypy/test/testfinegrained.py | 2 +- mypy/util.py | 15 ++++++++---- 5 files changed, 60 insertions(+), 21 deletions(-) diff --git a/mypy/dmypy/client.py b/mypy/dmypy/client.py index 7ee816f55aabb..af6d15b2e56bb 100644 --- a/mypy/dmypy/client.py +++ b/mypy/dmypy/client.py @@ -265,7 +265,8 @@ def do_run(args: argparse.Namespace) -> None: # Bad or missing status file or dead process; good to start. start_server(args, allow_sources=True) t0 = time.time() - response = request(args.status_file, 'run', version=__version__, args=args.flags) + response = request(args.status_file, 'run', version=__version__, args=args.flags, + no_tty=not sys.stdout.isatty()) # If the daemon signals that a restart is necessary, do it if 'restart' in response: print('Restarting: {}'.format(response['restart'])) @@ -327,7 +328,8 @@ def do_kill(args: argparse.Namespace) -> None: def do_check(args: argparse.Namespace) -> None: """Ask the daemon to check a list of files.""" t0 = time.time() - response = request(args.status_file, 'check', files=args.files) + response = request(args.status_file, 'check', files=args.files, + no_tty=not sys.stdout.isatty()) t1 = time.time() response['roundtrip_time'] = t1 - t0 check_output(response, args.verbose, args.junit_xml, args.perf_stats_file) @@ -350,9 +352,10 @@ def do_recheck(args: argparse.Namespace) -> None: """ t0 = time.time() if args.remove is not None or args.update is not None: - response = request(args.status_file, 'recheck', remove=args.remove, update=args.update) + response = request(args.status_file, 'recheck', remove=args.remove, update=args.update, + no_tty=not sys.stdout.isatty()) else: - response = request(args.status_file, 'recheck') + response = request(args.status_file, 'recheck', no_tty=not sys.stdout.isatty()) t1 = time.time() response['roundtrip_time'] = t1 - t0 check_output(response, args.verbose, args.junit_xml, args.perf_stats_file) diff --git a/mypy/dmypy_server.py b/mypy/dmypy_server.py index 3495be7e4bdd2..e46b7326fa7de 100644 --- a/mypy/dmypy_server.py +++ b/mypy/dmypy_server.py @@ -33,6 +33,7 @@ from mypy.suggestions import SuggestionFailure, SuggestionEngine from mypy.typestate import reset_global_state from mypy.version import __version__ +from mypy.util import FancyFormatter, count_stats MEM_PROFILE = False # type: Final # If True, dump memory profile after initialization @@ -188,6 +189,10 @@ def __init__(self, options: Options, options.local_partial_types = True self.status_file = status_file + # Since the object is created in the parent process we can check + # the output terminal options here. + self.formatter = FancyFormatter(sys.stdout, sys.stderr, options.show_error_codes) + def _response_metadata(self) -> Dict[str, str]: py_version = '{}_{}'.format(self.options.python_version[0], self.options.python_version[1]) return { @@ -280,7 +285,7 @@ def cmd_stop(self) -> Dict[str, object]: os.unlink(self.status_file) return {} - def cmd_run(self, version: str, args: Sequence[str]) -> Dict[str, object]: + def cmd_run(self, version: str, args: Sequence[str], no_tty: bool) -> Dict[str, object]: """Check a list of files, triggering a restart if needed.""" try: # Process options can exit on improper arguments, so we need to catch that and @@ -313,17 +318,18 @@ def cmd_run(self, version: str, args: Sequence[str]) -> Dict[str, object]: 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) + return self.check(sources, no_tty) - def cmd_check(self, files: Sequence[str]) -> Dict[str, object]: + def cmd_check(self, files: Sequence[str], no_tty: bool) -> Dict[str, object]: """Check a list of files.""" try: sources = create_source_list(files, self.options, self.fscache) except InvalidSourceList as err: return {'out': '', 'err': str(err), 'status': 2} - return self.check(sources) + return self.check(sources, no_tty) def cmd_recheck(self, + no_tty: bool, remove: Optional[List[str]] = None, update: Optional[List[str]] = None) -> Dict[str, object]: """Check the same list of files we checked most recently. @@ -349,17 +355,17 @@ def cmd_recheck(self, t1 = time.time() manager = self.fine_grained_manager.manager manager.log("fine-grained increment: cmd_recheck: {:.3f}s".format(t1 - t0)) - res = self.fine_grained_increment(sources, remove, update) + res = self.fine_grained_increment(sources, no_tty, remove, update) self.fscache.flush() self.update_stats(res) return res - def check(self, sources: List[BuildSource]) -> Dict[str, Any]: + def check(self, sources: List[BuildSource], no_tty: bool) -> Dict[str, Any]: """Check using fine-grained incremental mode.""" if not self.fine_grained_manager: - res = self.initialize_fine_grained(sources) + res = self.initialize_fine_grained(sources, no_tty) else: - res = self.fine_grained_increment(sources) + res = self.fine_grained_increment(sources, no_tty) self.fscache.flush() self.update_stats(res) return res @@ -371,7 +377,8 @@ def update_stats(self, res: Dict[str, Any]) -> None: res['stats'] = manager.stats manager.stats = {} - def initialize_fine_grained(self, sources: List[BuildSource]) -> Dict[str, Any]: + def initialize_fine_grained(self, sources: List[BuildSource], + no_tty: bool) -> Dict[str, Any]: self.fswatcher = FileSystemWatcher(self.fscache) t0 = time.time() self.update_sources(sources) @@ -433,10 +440,12 @@ def initialize_fine_grained(self, sources: List[BuildSource]) -> Dict[str, Any]: print_memory_profile(run_gc=False) status = 1 if messages else 0 + messages = self.pretty_messages(messages, len(sources), no_tty) return {'out': ''.join(s + '\n' for s in messages), 'err': '', 'status': status} def fine_grained_increment(self, sources: List[BuildSource], + no_tty: bool, remove: Optional[List[str]] = None, update: Optional[List[str]] = None, ) -> Dict[str, Any]: @@ -466,8 +475,28 @@ def fine_grained_increment(self, status = 1 if messages else 0 self.previous_sources = sources + messages = self.pretty_messages(messages, len(sources), no_tty) return {'out': ''.join(s + '\n' for s in messages), 'err': '', 'status': status} + def pretty_messages(self, messages: List[str], n_sources: int, + no_tty: bool = False) -> List[str]: + if self.options.error_summary: + summary = None # type: Optional[str] + if messages: + n_errors, n_files = count_stats(messages) + if n_errors: + summary = self.formatter.format_error(n_errors, n_files, n_sources, + not self.options.color_output or no_tty) + else: + summary = self.formatter.format_success(n_sources, + not self.options.color_output or no_tty) + if summary: + # Create new list to avoid appending multiple summaries on successive runs. + messages = messages + [summary] + if self.options.color_output and not no_tty: + messages = [self.formatter.colorize(m) for m in messages] + return messages + def update_sources(self, sources: List[BuildSource]) -> None: paths = [source.path for source in sources if source.path is not None] self.fswatcher.add_watched_paths(paths) diff --git a/mypy/main.py b/mypy/main.py index bcc165a8bc4ce..b1889ebad5d38 100644 --- a/mypy/main.py +++ b/mypy/main.py @@ -112,9 +112,11 @@ def flush_errors(new_messages: List[str], serious: bool) -> None: if messages: n_errors, n_files = util.count_stats(messages) if n_errors: - stdout.write(formatter.format_error(n_errors, n_files, len(sources)) + '\n') + stdout.write(formatter.format_error(n_errors, n_files, len(sources), + not options.color_output) + '\n') else: - stdout.write(formatter.format_success(len(sources)) + '\n') + stdout.write(formatter.format_success(len(sources), + not options.color_output) + '\n') stdout.flush() if options.fast_exit: # Exit without freeing objects -- it's faster. diff --git a/mypy/test/testfinegrained.py b/mypy/test/testfinegrained.py index 00b8536acaf5c..e0c443cde8970 100644 --- a/mypy/test/testfinegrained.py +++ b/mypy/test/testfinegrained.py @@ -196,7 +196,7 @@ def get_options(self, return options def run_check(self, server: Server, sources: List[BuildSource]) -> List[str]: - response = server.check(sources) + response = server.check(sources, no_tty=True) out = cast(str, response['out'] or response['err']) return out.splitlines() diff --git a/mypy/util.py b/mypy/util.py index 6fb297c94262a..92e0f11659ea5 100644 --- a/mypy/util.py +++ b/mypy/util.py @@ -427,14 +427,19 @@ def underline_link(self, note: str) -> str: self.style(note[start:end], 'none', underline=True) + note[end:]) - def format_success(self, n_sources: int) -> str: - return self.style('Success: no issues found in {}' - ' source file{}'.format(n_sources, 's' if n_sources != 1 else ''), - 'green', bold=True) + def format_success(self, n_sources: int, no_color: bool = False) -> str: + msg = 'Success: no issues found in {}' \ + ' source file{}'.format(n_sources, 's' if n_sources != 1 else '') + if no_color: + return msg + return self.style(msg, 'green', bold=True) - def format_error(self, n_errors: int, n_files: int, n_sources: int) -> str: + def format_error(self, n_errors: int, n_files: int, n_sources: int, + no_color: bool = False) -> str: msg = 'Found {} error{} in {} file{}' \ ' (checked {} source file{})'.format(n_errors, 's' if n_errors != 1 else '', n_files, 's' if n_files != 1 else '', n_sources, 's' if n_sources != 1 else '') + if no_color: + return msg return self.style(msg, 'red', bold=True) From ebf59e90480d251e4f99de5c38924bf76fb67a81 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 1 Sep 2019 12:52:51 +0100 Subject: [PATCH 2/5] Fix tests --- mypy/dmypy/client.py | 3 ++- mypy/test/testfinegrained.py | 1 + test-data/unit/daemon.test | 23 ++++++++++++++++------- 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/mypy/dmypy/client.py b/mypy/dmypy/client.py index af6d15b2e56bb..ff149077bfe04 100644 --- a/mypy/dmypy/client.py +++ b/mypy/dmypy/client.py @@ -271,7 +271,8 @@ def do_run(args: argparse.Namespace) -> None: if 'restart' in response: print('Restarting: {}'.format(response['restart'])) restart_server(args, allow_sources=True) - response = request(args.status_file, 'run', version=__version__, args=args.flags) + response = request(args.status_file, 'run', version=__version__, args=args.flags, + no_tty=not sys.stdout.isatty()) t1 = time.time() response['roundtrip_time'] = t1 - t0 diff --git a/mypy/test/testfinegrained.py b/mypy/test/testfinegrained.py index e0c443cde8970..d1b2e39df943e 100644 --- a/mypy/test/testfinegrained.py +++ b/mypy/test/testfinegrained.py @@ -181,6 +181,7 @@ def get_options(self, options.incremental = True options.use_builtins_fixtures = True options.show_traceback = True + options.error_summary = False options.fine_grained_incremental = not build_cache options.use_fine_grained_cache = self.use_cache and not build_cache options.cache_fine_grained = self.use_cache diff --git a/test-data/unit/daemon.test b/test-data/unit/daemon.test index 84db6b9cf4cc0..70ce344bc175a 100644 --- a/test-data/unit/daemon.test +++ b/test-data/unit/daemon.test @@ -11,7 +11,9 @@ Daemon stopped $ dmypy start -- --follow-imports=error Daemon started $ dmypy check -- foo.py +Success: no issues found in 1 source file $ dmypy recheck +Success: no issues found in 1 source file $ dmypy stop Daemon stopped [file foo.py] @@ -20,6 +22,7 @@ def f(): pass [case testDaemonRun] $ dmypy run -- foo.py --follow-imports=error Daemon started +Success: no issues found in 1 source file $ dmypy stop Daemon stopped [file foo.py] @@ -28,7 +31,9 @@ def f(): pass [case testDaemonRunRestart] $ dmypy run -- foo.py --follow-imports=error Daemon started +Success: no issues found in 1 source file $ dmypy run -- foo.py --follow-imports=error +Success: no issues found in 1 source file $ {python} -c "print('[mypy]')" >mypy.ini $ {python} -c "print('disallow_untyped_defs = True')" >>mypy.ini $ dmypy run -- foo.py --follow-imports=error @@ -37,19 +42,21 @@ Daemon stopped Daemon started foo.py:1: error: Function is missing a return type annotation foo.py:1: note: Use "-> None" if function does not return a value +Found 1 error in 1 file (checked 1 source file) == Return code: 1 $ {python} -c "print('def f() -> None: pass')" >foo.py $ dmypy run -- foo.py --follow-imports=error +Success: no issues found in 1 source file $ dmypy stop Daemon stopped [file foo.py] def f(): pass [case testDaemonRunRestartPluginVersion] -$ dmypy run -- foo.py +$ dmypy run -- foo.py --no-error-summary Daemon started $ {python} -c "print(' ')" >> plug.py -$ dmypy run -- foo.py +$ dmypy run -- foo.py --no-error-summary Restarting: plugins changed Daemon stopped Daemon started @@ -79,14 +86,14 @@ No status file found $ dmypy recheck No status file found == Return code: 2 -$ dmypy start -- --follow-imports=error +$ dmypy start -- --follow-imports=error --no-error-summary Daemon started $ dmypy status Daemon is up and running $ dmypy start Daemon is still alive == Return code: 2 -$ dmypy restart -- --follow-imports=error +$ dmypy restart -- --follow-imports=error --no-error-summary Daemon stopped Daemon started $ dmypy stop @@ -94,7 +101,7 @@ Daemon stopped $ dmypy status No status file found == Return code: 2 -$ dmypy restart -- --follow-imports=error +$ dmypy restart -- --follow-imports=error --no-error-summary Daemon started $ dmypy recheck Command 'recheck' is only valid after a 'check' command @@ -106,7 +113,7 @@ Daemon has died == Return code: 2 [case testDaemonRecheck] -$ dmypy start -- --follow-imports=error +$ dmypy start -- --follow-imports=error --no-error-summary Daemon started $ dmypy check foo.py bar.py $ dmypy recheck @@ -153,6 +160,7 @@ $ {python} -c "import time;time.sleep(1)" $ {python} -c "print('x=1')" >bar.py $ dmypy run -- foo.py bar.py --follow-imports=error --use-fine-grained-cache --no-sqlite-cache --python-version=3.6 Daemon started +Success: no issues found in 2 source files $ dmypy status --fswatcher-dump-file test.json Daemon is up and running $ dmypy stop @@ -165,6 +173,7 @@ $ {python} -c "print('lol')" >foo.py $ dmypy run --log-file=log -- foo.py bar.py --follow-imports=error --use-fine-grained-cache --no-sqlite-cache --python-version=3.6 --quickstart-file test.json Daemon started foo.py:1: error: Name 'lol' is not defined +Found 1 error in 1 file (checked 2 source files) == Return code: 1 -- make sure no errors made it to the log file $ {python} -c "import sys; sys.stdout.write(open('log').read())" @@ -173,7 +182,7 @@ $ {python} -c "import sys; sys.stdout.write(open('log').read())" $ {python} -c "x = open('.mypy_cache/3.6/bar.meta.json').read(); y = open('asdf.json').read(); assert x == y" [case testDaemonSuggest] -$ dmypy start --log-file log.txt -- --follow-imports=error +$ dmypy start --log-file log.txt -- --follow-imports=error --no-error-summary Daemon started $ dmypy suggest foo:foo Command 'suggest' is only valid after a 'check' command From e8f46218d7526f89411a5f9593d1316aacdd0606 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Mon, 2 Sep 2019 20:17:49 +0100 Subject: [PATCH 3/5] Address CR --- mypy/dmypy/client.py | 17 +++++++-------- mypy/dmypy_server.py | 42 ++++++++++++++++++++---------------- mypy/main.py | 4 ++-- mypy/test/testfinegrained.py | 2 +- mypy/util.py | 8 +++---- 5 files changed, 38 insertions(+), 35 deletions(-) diff --git a/mypy/dmypy/client.py b/mypy/dmypy/client.py index ff149077bfe04..3dc73ecdf22b6 100644 --- a/mypy/dmypy/client.py +++ b/mypy/dmypy/client.py @@ -265,14 +265,12 @@ def do_run(args: argparse.Namespace) -> None: # Bad or missing status file or dead process; good to start. start_server(args, allow_sources=True) t0 = time.time() - response = request(args.status_file, 'run', version=__version__, args=args.flags, - no_tty=not sys.stdout.isatty()) + response = request(args.status_file, 'run', version=__version__, args=args.flags) # If the daemon signals that a restart is necessary, do it if 'restart' in response: print('Restarting: {}'.format(response['restart'])) restart_server(args, allow_sources=True) - response = request(args.status_file, 'run', version=__version__, args=args.flags, - no_tty=not sys.stdout.isatty()) + response = request(args.status_file, 'run', version=__version__, args=args.flags) t1 = time.time() response['roundtrip_time'] = t1 - t0 @@ -329,8 +327,7 @@ def do_kill(args: argparse.Namespace) -> None: def do_check(args: argparse.Namespace) -> None: """Ask the daemon to check a list of files.""" t0 = time.time() - response = request(args.status_file, 'check', files=args.files, - no_tty=not sys.stdout.isatty()) + response = request(args.status_file, 'check', files=args.files) t1 = time.time() response['roundtrip_time'] = t1 - t0 check_output(response, args.verbose, args.junit_xml, args.perf_stats_file) @@ -353,10 +350,9 @@ def do_recheck(args: argparse.Namespace) -> None: """ t0 = time.time() if args.remove is not None or args.update is not None: - response = request(args.status_file, 'recheck', remove=args.remove, update=args.update, - no_tty=not sys.stdout.isatty()) + response = request(args.status_file, 'recheck', remove=args.remove, update=args.update) else: - response = request(args.status_file, 'recheck', no_tty=not sys.stdout.isatty()) + response = request(args.status_file, 'recheck') t1 = time.time() response['roundtrip_time'] = t1 - t0 check_output(response, args.verbose, args.junit_xml, args.perf_stats_file) @@ -470,6 +466,9 @@ def request(status_file: str, command: str, *, timeout: Optional[int] = None, response = {} # type: Dict[str, str] args = dict(kwds) args['command'] = command + # Tell the server whether this request was initiated from a human-facing terminal, + # so that it can format the type checking output accordingly. + args['is_tty'] = sys.stdout.isatty() bdata = json.dumps(args).encode('utf8') _, name = get_status(status_file) try: diff --git a/mypy/dmypy_server.py b/mypy/dmypy_server.py index e46b7326fa7de..fc157cf010ccb 100644 --- a/mypy/dmypy_server.py +++ b/mypy/dmypy_server.py @@ -285,7 +285,7 @@ def cmd_stop(self) -> Dict[str, object]: os.unlink(self.status_file) return {} - def cmd_run(self, version: str, args: Sequence[str], no_tty: bool) -> Dict[str, object]: + def cmd_run(self, version: str, args: Sequence[str], is_tty: bool) -> Dict[str, object]: """Check a list of files, triggering a restart if needed.""" try: # Process options can exit on improper arguments, so we need to catch that and @@ -318,18 +318,18 @@ def cmd_run(self, version: str, args: Sequence[str], no_tty: bool) -> Dict[str, 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, no_tty) + return self.check(sources, is_tty) - def cmd_check(self, files: Sequence[str], no_tty: bool) -> Dict[str, object]: + def cmd_check(self, files: Sequence[str], is_tty: bool) -> Dict[str, object]: """Check a list of files.""" try: sources = create_source_list(files, self.options, self.fscache) except InvalidSourceList as err: return {'out': '', 'err': str(err), 'status': 2} - return self.check(sources, no_tty) + return self.check(sources, is_tty) def cmd_recheck(self, - no_tty: bool, + is_tty: bool, remove: Optional[List[str]] = None, update: Optional[List[str]] = None) -> Dict[str, object]: """Check the same list of files we checked most recently. @@ -355,17 +355,21 @@ def cmd_recheck(self, t1 = time.time() manager = self.fine_grained_manager.manager manager.log("fine-grained increment: cmd_recheck: {:.3f}s".format(t1 - t0)) - res = self.fine_grained_increment(sources, no_tty, remove, update) + res = self.fine_grained_increment(sources, is_tty, remove, update) self.fscache.flush() self.update_stats(res) return res - def check(self, sources: List[BuildSource], no_tty: bool) -> Dict[str, Any]: - """Check using fine-grained incremental mode.""" + def check(self, sources: List[BuildSource], is_tty: bool) -> Dict[str, Any]: + """Check using fine-grained incremental mode. + + If is_tty is True format the output nicely with colors and summary line + (unless disabled in self.options). + """ if not self.fine_grained_manager: - res = self.initialize_fine_grained(sources, no_tty) + res = self.initialize_fine_grained(sources, is_tty) else: - res = self.fine_grained_increment(sources, no_tty) + res = self.fine_grained_increment(sources, is_tty) self.fscache.flush() self.update_stats(res) return res @@ -378,7 +382,7 @@ def update_stats(self, res: Dict[str, Any]) -> None: manager.stats = {} def initialize_fine_grained(self, sources: List[BuildSource], - no_tty: bool) -> Dict[str, Any]: + is_tty: bool) -> Dict[str, Any]: self.fswatcher = FileSystemWatcher(self.fscache) t0 = time.time() self.update_sources(sources) @@ -440,12 +444,12 @@ def initialize_fine_grained(self, sources: List[BuildSource], print_memory_profile(run_gc=False) status = 1 if messages else 0 - messages = self.pretty_messages(messages, len(sources), no_tty) + messages = self.pretty_messages(messages, len(sources), is_tty) return {'out': ''.join(s + '\n' for s in messages), 'err': '', 'status': status} def fine_grained_increment(self, sources: List[BuildSource], - no_tty: bool, + is_tty: bool, remove: Optional[List[str]] = None, update: Optional[List[str]] = None, ) -> Dict[str, Any]: @@ -475,25 +479,25 @@ def fine_grained_increment(self, status = 1 if messages else 0 self.previous_sources = sources - messages = self.pretty_messages(messages, len(sources), no_tty) + messages = self.pretty_messages(messages, len(sources), is_tty) return {'out': ''.join(s + '\n' for s in messages), 'err': '', 'status': status} def pretty_messages(self, messages: List[str], n_sources: int, - no_tty: bool = False) -> List[str]: + is_tty: bool = False) -> List[str]: + use_color = self.options.color_output and is_tty if self.options.error_summary: summary = None # type: Optional[str] if messages: n_errors, n_files = count_stats(messages) if n_errors: summary = self.formatter.format_error(n_errors, n_files, n_sources, - not self.options.color_output or no_tty) + use_color) else: - summary = self.formatter.format_success(n_sources, - not self.options.color_output or no_tty) + summary = self.formatter.format_success(n_sources, use_color) if summary: # Create new list to avoid appending multiple summaries on successive runs. messages = messages + [summary] - if self.options.color_output and not no_tty: + if use_color: messages = [self.formatter.colorize(m) for m in messages] return messages diff --git a/mypy/main.py b/mypy/main.py index b1889ebad5d38..16df2b2f5ac41 100644 --- a/mypy/main.py +++ b/mypy/main.py @@ -113,10 +113,10 @@ def flush_errors(new_messages: List[str], serious: bool) -> None: n_errors, n_files = util.count_stats(messages) if n_errors: stdout.write(formatter.format_error(n_errors, n_files, len(sources), - not options.color_output) + '\n') + options.color_output) + '\n') else: stdout.write(formatter.format_success(len(sources), - not options.color_output) + '\n') + options.color_output) + '\n') stdout.flush() if options.fast_exit: # Exit without freeing objects -- it's faster. diff --git a/mypy/test/testfinegrained.py b/mypy/test/testfinegrained.py index d1b2e39df943e..464f2d837e5c3 100644 --- a/mypy/test/testfinegrained.py +++ b/mypy/test/testfinegrained.py @@ -197,7 +197,7 @@ def get_options(self, return options def run_check(self, server: Server, sources: List[BuildSource]) -> List[str]: - response = server.check(sources, no_tty=True) + response = server.check(sources, is_tty=False) out = cast(str, response['out'] or response['err']) return out.splitlines() diff --git a/mypy/util.py b/mypy/util.py index 92e0f11659ea5..075da8d049bac 100644 --- a/mypy/util.py +++ b/mypy/util.py @@ -427,19 +427,19 @@ def underline_link(self, note: str) -> str: self.style(note[start:end], 'none', underline=True) + note[end:]) - def format_success(self, n_sources: int, no_color: bool = False) -> str: + def format_success(self, n_sources: int, use_color: bool = True) -> str: msg = 'Success: no issues found in {}' \ ' source file{}'.format(n_sources, 's' if n_sources != 1 else '') - if no_color: + if not use_color: return msg return self.style(msg, 'green', bold=True) def format_error(self, n_errors: int, n_files: int, n_sources: int, - no_color: bool = False) -> str: + use_color: bool = True) -> str: msg = 'Found {} error{} in {} file{}' \ ' (checked {} source file{})'.format(n_errors, 's' if n_errors != 1 else '', n_files, 's' if n_files != 1 else '', n_sources, 's' if n_sources != 1 else '') - if no_color: + if use_color: return msg return self.style(msg, 'red', bold=True) From 2e5ecaada858f8259ff83a621ae16873f00a49dc Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Mon, 2 Sep 2019 20:29:13 +0100 Subject: [PATCH 4/5] Fix bugs --- mypy/dmypy_server.py | 7 +++++-- mypy/util.py | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/mypy/dmypy_server.py b/mypy/dmypy_server.py index fc157cf010ccb..774ce18e66123 100644 --- a/mypy/dmypy_server.py +++ b/mypy/dmypy_server.py @@ -16,7 +16,7 @@ import traceback from contextlib import redirect_stderr, redirect_stdout -from typing import AbstractSet, Any, Callable, Dict, List, Mapping, Optional, Sequence, Tuple +from typing import AbstractSet, Any, Callable, Dict, List, Optional, Sequence, Tuple from typing_extensions import Final import mypy.build @@ -253,13 +253,16 @@ def serve(self) -> None: if exc_info[0] and exc_info[0] is not SystemExit: traceback.print_exception(*exc_info) - def run_command(self, command: str, data: Mapping[str, object]) -> Dict[str, object]: + def run_command(self, command: str, data: Dict[str, object]) -> Dict[str, object]: """Run a specific command from the registry.""" key = 'cmd_' + command method = getattr(self.__class__, key, None) if method is None: return {'error': "Unrecognized command '%s'" % command} else: + if command not in {'check', 'recheck', 'run'}: + # Only the above commands use some error formatting. + del data['is_tty'] return method(self, **data) # Command functions (run in the server via RPC). diff --git a/mypy/util.py b/mypy/util.py index 075da8d049bac..7284d0639ca26 100644 --- a/mypy/util.py +++ b/mypy/util.py @@ -440,6 +440,6 @@ def format_error(self, n_errors: int, n_files: int, n_sources: int, ' (checked {} source file{})'.format(n_errors, 's' if n_errors != 1 else '', n_files, 's' if n_files != 1 else '', n_sources, 's' if n_sources != 1 else '') - if use_color: + if not use_color: return msg return self.style(msg, 'red', bold=True) From c6bccb3f698a1069d08190a31ac5efb308460d6a Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Mon, 2 Sep 2019 20:39:22 +0100 Subject: [PATCH 5/5] Add override support also for daemon --- mypy/dmypy_server.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mypy/dmypy_server.py b/mypy/dmypy_server.py index 774ce18e66123..9349ddc781a8b 100644 --- a/mypy/dmypy_server.py +++ b/mypy/dmypy_server.py @@ -263,6 +263,8 @@ def run_command(self, command: str, data: Dict[str, object]) -> Dict[str, object if command not in {'check', 'recheck', 'run'}: # Only the above commands use some error formatting. del data['is_tty'] + elif int(os.getenv('MYPY_FORCE_COLOR', '0')): + data['is_tty'] = True return method(self, **data) # Command functions (run in the server via RPC).