From fa9fd3c71a18675c465e84150baea71530d6d7b2 Mon Sep 17 00:00:00 2001 From: Albert Tugushev Date: Mon, 20 Apr 2020 03:17:30 +0700 Subject: [PATCH] Indent logs with a context manager Introduces new context manager `LogContext.intentation()`. Usage: ```python from piptools.loggin import log log.debug("Hello") with log.indentation(): log.debug("World!") ``` Output: ``` Hello World! ``` --- piptools/logging.py | 27 ++++++++++++++++++++++++--- piptools/repositories/pypi.py | 18 ++++++++++++------ piptools/resolver.py | 33 ++++++++++++++++++--------------- piptools/scripts/compile.py | 10 ++++++---- tests/test_logging.py | 25 +++++++++++++++++++++++++ 5 files changed, 85 insertions(+), 28 deletions(-) create mode 100644 tests/test_logging.py diff --git a/piptools/logging.py b/piptools/logging.py index 8b379b8d7..eea6772af 100644 --- a/piptools/logging.py +++ b/piptools/logging.py @@ -1,6 +1,7 @@ # coding: utf-8 from __future__ import absolute_import, division, print_function, unicode_literals +import contextlib import logging from . import click @@ -11,12 +12,15 @@ class LogContext(object): - def __init__(self, verbosity=0): + def __init__(self, verbosity=0, indent_width=2): self.verbosity = verbosity + self.current_indent = 0 + self._indent_width = indent_width - def log(self, *args, **kwargs): + def log(self, message, *args, **kwargs): kwargs.setdefault("err", True) - click.secho(*args, **kwargs) + prefix = " " * self.current_indent + click.secho(prefix + message, *args, **kwargs) def debug(self, *args, **kwargs): if self.verbosity >= 1: @@ -34,5 +38,22 @@ def error(self, *args, **kwargs): kwargs.setdefault("fg", "red") self.log(*args, **kwargs) + def _indent(self): + self.current_indent += self._indent_width + + def _dedent(self): + self.current_indent -= self._indent_width + + @contextlib.contextmanager + def indentation(self): + """ + Increase indentation. + """ + self._indent() + try: + yield + finally: + self._dedent() + log = LogContext() diff --git a/piptools/repositories/pypi.py b/piptools/repositories/pypi.py index ef5ba4ecb..72081f7b8 100644 --- a/piptools/repositories/pypi.py +++ b/piptools/repositories/pypi.py @@ -267,14 +267,15 @@ def get_hashes(self, ireq): ) matching_candidates = candidates_by_version[matching_versions[0]] - log.debug(" {}".format(ireq.name)) + log.debug(ireq.name) - return { - self._get_file_hash(candidate.link) for candidate in matching_candidates - } + with log.indentation(): + return { + self._get_file_hash(candidate.link) for candidate in matching_candidates + } def _get_file_hash(self, link): - log.debug(" Hashing {}".format(link.url_without_fragment)) + log.debug("Hashing {}".format(link.url_without_fragment)) h = hashlib.new(FAVORITE_HASH) with open_local_or_remote_file(link, self.session) as f: # Chunks to iterate @@ -283,7 +284,12 @@ def _get_file_hash(self, link): # Choose a context manager depending on verbosity if log.verbosity >= 1: iter_length = f.size / FILE_CHUNK_SIZE if f.size else None - context_manager = progressbar(chunks, length=iter_length, label=" ") + bar_template = "{prefix}[%(bar)s] %(info)s".format( + prefix=" " * log.current_indent + ) + context_manager = progressbar( + chunks, length=iter_length, bar_template=bar_template + ) else: context_manager = contextlib.nullcontext(chunks) diff --git a/piptools/resolver.py b/piptools/resolver.py index 01169926b..37bc21ee5 100644 --- a/piptools/resolver.py +++ b/piptools/resolver.py @@ -131,7 +131,7 @@ def resolve_hashes(self, ireqs): """ log.debug("") log.debug("Generating hashes:") - with self.repository.allow_all_wheels(): + with self.repository.allow_all_wheels(), log.indentation(): return {ireq: self.repository.get_hashes(ireq) for ireq in ireqs} def resolve(self, max_rounds=10): @@ -238,20 +238,23 @@ def _resolve_one_round(self): constraints = sorted(self.constraints, key=key_from_ireq) log.debug("Current constraints:") - for constraint in constraints: - log.debug(" {}".format(constraint)) + with log.indentation(): + for constraint in constraints: + log.debug(str(constraint)) log.debug("") log.debug("Finding the best candidates:") - best_matches = {self.get_best_match(ireq) for ireq in constraints} + with log.indentation(): + best_matches = {self.get_best_match(ireq) for ireq in constraints} # Find the new set of secondary dependencies log.debug("") log.debug("Finding secondary dependencies:") their_constraints = [] - for best_match in best_matches: - their_constraints.extend(self._iter_dependencies(best_match)) + with log.indentation(): + for best_match in best_matches: + their_constraints.extend(self._iter_dependencies(best_match)) # Grouping constraints to make clean diff between rounds theirs = set(self._group_constraints(sorted(their_constraints, key=str))) @@ -268,11 +271,13 @@ def _resolve_one_round(self): if has_changed: log.debug("") log.debug("New dependencies found in this round:") - for new_dependency in sorted(diff, key=key_from_ireq): - log.debug(" adding {}".format(new_dependency)) + with log.indentation(): + for new_dependency in sorted(diff, key=key_from_ireq): + log.debug("adding {}".format(new_dependency)) log.debug("Removed dependencies in this round:") - for removed_dependency in sorted(removed, key=key_from_ireq): - log.debug(" removing {}".format(removed_dependency)) + with log.indentation(): + for removed_dependency in sorted(removed, key=key_from_ireq): + log.debug("removing {}".format(removed_dependency)) # Store the last round's results in the their_constraints self.their_constraints = theirs @@ -308,7 +313,7 @@ def get_best_match(self, ireq): # Format the best match log.debug( - " found candidate {} (constraint was {})".format( + "found candidate {} (constraint was {})".format( format_requirement(best_match), format_specifier(ireq) ) ) @@ -352,9 +357,7 @@ def _iter_dependencies(self, ireq): # from there if ireq not in self.dependency_cache: log.debug( - " {} not in cache, need to check index".format( - format_requirement(ireq) - ), + "{} not in cache, need to check index".format(format_requirement(ireq)), fg="yellow", ) dependencies = self.repository.get_dependencies(ireq) @@ -363,7 +366,7 @@ def _iter_dependencies(self, ireq): # Example: ['Werkzeug>=0.9', 'Jinja2>=2.4'] dependency_strings = self.dependency_cache[ireq] log.debug( - " {:25} requires {}".format( + "{:25} requires {}".format( format_requirement(ireq), ", ".join(sorted(dependency_strings, key=lambda s: s.lower())) or "-", ) diff --git a/piptools/scripts/compile.py b/piptools/scripts/compile.py index 03232a849..7bd6e0822 100755 --- a/piptools/scripts/compile.py +++ b/piptools/scripts/compile.py @@ -369,14 +369,16 @@ def cli( ] log.debug("Using indexes:") - for index_url in dedup(repository.finder.index_urls): - log.debug(" {}".format(index_url)) + with log.indentation(): + for index_url in dedup(repository.finder.index_urls): + log.debug(index_url) if repository.finder.find_links: log.debug("") log.debug("Configuration:") - for find_link in dedup(repository.finder.find_links): - log.debug(" -f {}".format(find_link)) + with log.indentation(): + for find_link in dedup(repository.finder.find_links): + log.debug("-f {}".format(find_link)) try: resolver = Resolver( diff --git a/tests/test_logging.py b/tests/test_logging.py new file mode 100644 index 000000000..a19a74179 --- /dev/null +++ b/tests/test_logging.py @@ -0,0 +1,25 @@ +from piptools.logging import LogContext + + +def test_indentation(runner): + """ + Test LogContext.indentation() context manager increases indentation. + """ + log = LogContext(indent_width=2) + + with runner.isolation() as (_, stderr): + log.log("Test message 1") + with log.indentation(): + log.log("Test message 2") + with log.indentation(): + log.log("Test message 3") + log.log("Test message 4") + log.log("Test message 5") + + assert stderr.getvalue().decode().splitlines() == [ + "Test message 1", + " Test message 2", + " Test message 3", + " Test message 4", + "Test message 5", + ]