diff --git a/invoke/completion/complete.py b/invoke/completion/complete.py index 97e9a959..1fe8e07e 100644 --- a/invoke/completion/complete.py +++ b/invoke/completion/complete.py @@ -27,35 +27,35 @@ def complete( # Strip out program name (scripts give us full command line) # TODO: this may not handle path/to/script though? invocation = re.sub(r"^({}) ".format("|".join(names)), "", core.remainder) - debug("Completing for invocation: {!r}".format(invocation)) + debug("Completing for invocation: %r", invocation) # Tokenize (shlex will have to do) tokens = shlex.split(invocation) # Handle flags (partial or otherwise) if tokens and tokens[-1].startswith("-"): tail = tokens[-1] - debug("Invocation's tail {!r} is flag-like".format(tail)) + debug("Invocation's tail %r is flag-like", tail) # Gently parse invocation to obtain 'current' context. # Use last seen context in case of failure (required for # otherwise-invalid partial invocations being completed). contexts: List[ParserContext] try: - debug("Seeking context name in tokens: {!r}".format(tokens)) + debug("Seeking context name in tokens: %r", tokens) contexts = parser.parse_argv(tokens) except ParseError as e: - msg = "Got parser error ({!r}), grabbing its last-seen context {!r}" # noqa - debug(msg.format(e, e.context)) + msg = "Got parser error (%r), grabbing its last-seen context %r" # noqa + debug(msg, e, e.context) contexts = [e.context] if e.context is not None else [] # Fall back to core context if no context seen. - debug("Parsed invocation, contexts: {!r}".format(contexts)) + debug("Parsed invocation, contexts: %r", contexts) if not contexts or not contexts[-1]: context = initial_context else: context = contexts[-1] - debug("Selected context: {!r}".format(context)) + debug("Selected context: %r", context) # Unknown flags (could be e.g. only partially typed out; could be # wholly invalid; doesn't matter) complete with flags. - debug("Looking for {!r} in {!r}".format(tail, context.flags)) + debug("Looking for %r in %r", tail, context.flags) if tail not in context.flags: debug("Not found, completing with flag names") # Long flags - partial or just the dashes - complete w/ long flags @@ -119,7 +119,7 @@ def print_completion_script(shell: str, names: List[str]) -> None: except KeyError: err = 'Completion for shell "{}" not supported (options are: {}).' raise ParseError(err.format(shell, ", ".join(sorted(completions)))) - debug("Printing completion script from {}".format(path)) + debug("Printing completion script from %s", path) # Choose one arbitrary program name for script's own internal invocation # (also used to construct completion function names when necessary) binary = names[0] diff --git a/invoke/config.py b/invoke/config.py index c38afc67..6b5a2863 100644 --- a/invoke/config.py +++ b/invoke/config.py @@ -893,8 +893,7 @@ def _load_file( # Typically means 'no such file', so just note & skip past. except IOError as e: if e.errno == 2: - err = "Didn't see any {}, skipping." - debug(err.format(filepath)) + debug("Didn't see any %s, skipping.", filepath) else: raise # Still None -> no suffixed paths were found, record this fact @@ -941,21 +940,21 @@ def merge(self) -> None: """ debug("Merging config sources in order onto new empty _config...") self._set(_config={}) - debug("Defaults: {!r}".format(self._defaults)) + debug("Defaults: %r", self._defaults) merge_dicts(self._config, self._defaults) - debug("Collection-driven: {!r}".format(self._collection)) + debug("Collection-driven: %r", self._collection) merge_dicts(self._config, self._collection) self._merge_file("system", "System-wide") self._merge_file("user", "Per-user") self._merge_file("project", "Per-project") - debug("Environment variable config: {!r}".format(self._env)) + debug("Environment variable config: %r", self._env) merge_dicts(self._config, self._env) self._merge_file("runtime", "Runtime") - debug("Overrides: {!r}".format(self._overrides)) + debug("Overrides: %r", self._overrides) merge_dicts(self._config, self._overrides) - debug("Modifications: {!r}".format(self._modifications)) + debug("Modifications: %r", self._modifications) merge_dicts(self._config, self._modifications) - debug("Deletions: {!r}".format(self._deletions)) + debug("Deletions: %r", self._deletions) obliterate(self._config, self._deletions) def _merge_file(self, name: str, desc: str) -> None: @@ -966,16 +965,16 @@ def _merge_file(self, name: str, desc: str) -> None: data = getattr(self, "_{}".format(name)) # None -> no loading occurred yet if found is None: - debug("{} has not been loaded yet, skipping".format(desc)) + debug("%s has not been loaded yet, skipping", desc) # True -> hooray elif found: - debug("{} ({}): {!r}".format(desc, path, data)) + debug("%s (%s): %r", desc, path, data) merge_dicts(self._config, data) # False -> did try, did not succeed else: # TODO: how to preserve what was tried for each case but only for # the negative? Just a branch here based on 'name'? - debug("{} not found, skipping".format(desc)) + debug("%s not found, skipping", desc) def clone(self, into: Optional[Type["Config"]] = None) -> "Config": """ diff --git a/invoke/env.py b/invoke/env.py index 2c7aaa69..f57fb5e5 100644 --- a/invoke/env.py +++ b/invoke/env.py @@ -35,14 +35,14 @@ def load(self) -> Dict[str, Any]: """ # Obtain allowed env var -> existing value map env_vars = self._crawl(key_path=[], env_vars={}) - m = "Scanning for env vars according to prefix: {!r}, mapping: {!r}" - debug(m.format(self._prefix, env_vars)) + m = "Scanning for env vars according to prefix: %r, mapping: %r" + debug(m, self._prefix, env_vars) # Check for actual env var (honoring prefix) and try to set for env_var, key_path in env_vars.items(): real_var = (self._prefix or "") + env_var if real_var in os.environ: self._path_set(key_path, os.environ[real_var]) - debug("Obtained env var config: {!r}".format(self.data)) + debug("Obtained env var config: %r", self.data) return self.data def _crawl( diff --git a/invoke/executor.py b/invoke/executor.py index 08aa74e3..b4151c5e 100644 --- a/invoke/executor.py +++ b/invoke/executor.py @@ -96,9 +96,9 @@ def execute( .. versionadded:: 1.0 """ # Normalize input - debug("Examining top level tasks {!r}".format([x for x in tasks])) + debug("Examining top level tasks %r", [x for x in tasks]) calls = self.normalize(tasks) - debug("Tasks (now Calls) with kwargs: {!r}".format(calls)) + debug("Tasks (now Calls) with kwargs: %r", calls) # Obtain copy of directly-given tasks since they should sometimes # behave differently direct = list(calls) @@ -120,7 +120,7 @@ def execute( # moment... for call in calls: autoprint = call in direct and call.autoprint - debug("Executing {!r}".format(call)) + debug("Executing %r", call) # Hand in reference to our config, which will preserve user # modifications across the lifetime of the session. config = self.config @@ -189,10 +189,10 @@ def dedupe(self, calls: List["Call"]) -> List["Call"]: debug("Deduplicating tasks...") for call in calls: if call not in deduped: - debug("{!r}: no duplicates found, ok".format(call)) + debug("%r: no duplicates found, ok", call) deduped.append(call) else: - debug("{!r}: found in list already, skipping".format(call)) + debug("%r: found in list already, skipping", call) return deduped def expand_calls(self, calls: List["Call"]) -> List["Call"]: @@ -214,7 +214,7 @@ def expand_calls(self, calls: List["Call"]) -> List["Call"]: # task lists, which may contain 'raw' Task objects) if isinstance(call, Task): call = Call(call) - debug("Expanding task-call {!r}".format(call)) + debug("Expanding task-call %r", call) # TODO: this is where we _used_ to call Executor.config_for(call, # config)... # TODO: now we may need to preserve more info like where the call diff --git a/invoke/loader.py b/invoke/loader.py index 23bffdf0..f2063500 100644 --- a/invoke/loader.py +++ b/invoke/loader.py @@ -116,7 +116,7 @@ def start(self) -> str: def find(self, name: str) -> Tuple[IO, str, Tuple[str, str, int]]: # Accumulate all parent directories start = self.start - debug("FilesystemLoader find starting at {!r}".format(start)) + debug("FilesystemLoader find starting at %r", start) parents = [os.path.abspath(start)] parents.append(os.path.dirname(parents[-1])) while parents[-1] != parents[-2]: @@ -129,9 +129,9 @@ def find(self, name: str) -> Tuple[IO, str, Tuple[str, str, int]]: # we turn it into a more obvious exception class. try: tup = imp.find_module(name, parents) - debug("Found module: {!r}".format(tup[1])) + debug("Found module: %r", tup[1]) return tup except ImportError: - msg = "ImportError loading {!r}, raising CollectionNotFound" - debug(msg.format(name)) + msg = "ImportError loading %r, raising CollectionNotFound" + debug(msg, name) raise CollectionNotFound(name=name, start=start) diff --git a/invoke/parser/parser.py b/invoke/parser/parser.py index 43e95df0..68bf59f4 100644 --- a/invoke/parser/parser.py +++ b/invoke/parser/parser.py @@ -75,7 +75,7 @@ def __init__( self.contexts = Lexicon() self.ignore_unknown = ignore_unknown for context in contexts: - debug("Adding {}".format(context)) + debug("Adding %s", context) if not context.name: raise ValueError("Non-initial contexts must have names.") exists = "A context named/aliased {!r} is already in this parser!" @@ -120,7 +120,7 @@ def parse_argv(self, argv: List[str]) -> ParseResult: # FIXME: Why isn't there str.partition for lists? There must be a # better way to do this. Split argv around the double-dash remainder # sentinel. - debug("Starting argv: {!r}".format(argv)) + debug("Starting argv: %r", argv) try: ddash = argv.index("--") except ValueError: @@ -128,9 +128,7 @@ def parse_argv(self, argv: List[str]) -> ParseResult: body = argv[:ddash] remainder = argv[ddash:][1:] # [1:] to strip off remainder itself if remainder: - debug( - "Remainder: argv[{!r}:][1:] => {!r}".format(ddash, remainder) - ) + debug("Remainder: argv[%r:][1:] => %r", ddash, remainder) for index, token in enumerate(body): # Handle non-space-delimited forms, if not currently expecting a # flag value and still in valid parsing territory (i.e. not in @@ -144,15 +142,15 @@ def parse_argv(self, argv: List[str]) -> ParseResult: # Equals-sign-delimited flags, eg --foo=bar or -f=bar if "=" in token: token, _, value = token.partition("=") - msg = "Splitting x=y expr {!r} into tokens {!r} and {!r}" - debug(msg.format(orig, token, value)) + msg = "Splitting x=y expr %r into tokens %r and %r" + debug(msg, orig, token, value) mutations.append((index + 1, value)) # Contiguous boolean short flags, e.g. -qv elif not is_long_flag(token) and len(token) > 2: full_token = token[:] rest, token = token[2:], token[:2] - err = "Splitting {!r} into token {!r} and rest {!r}" - debug(err.format(full_token, token, rest)) + err = "Splitting %r into token %r and rest %r" + debug(err, full_token, token, rest) # Handle boolean flag block vs short-flag + value. Make # sure not to test the token as a context flag if we've # passed into 'storing unknown stuff' territory (e.g. on a @@ -162,13 +160,13 @@ def parse_argv(self, argv: List[str]) -> ParseResult: and machine.current_state != "unknown" ) if have_flag and machine.context.flags[token].takes_value: - msg = "{!r} is a flag for current context & it takes a value, giving it {!r}" # noqa - debug(msg.format(token, rest)) + msg = "%r is a flag for current context & it takes a value, giving it %r" # noqa + debug(msg, token, rest) mutations.append((index + 1, rest)) else: _rest = ["-{}".format(x) for x in rest] - msg = "Splitting multi-flag glob {!r} into {!r} and {!r}" # noqa - debug(msg.format(orig, token, _rest)) + msg = "Splitting multi-flag glob %r into %r and %r" # noqa + debug(msg, orig, token, _rest) for item in reversed(_rest): mutations.append((index + 1, item)) # Here, we've got some possible mutations queued up, and 'token' @@ -224,7 +222,7 @@ class ParseMachine(StateMachine): ) def changing_state(self, from_: str, to: str) -> None: - debug("ParseMachine: {!r} => {!r}".format(from_, to)) + debug("ParseMachine: %r => %r", from_, to) def __init__( self, @@ -235,12 +233,12 @@ def __init__( # Initialize self.ignore_unknown = ignore_unknown self.initial = self.context = copy.deepcopy(initial) - debug("Initialized with context: {!r}".format(self.context)) + debug("Initialized with context: %r", self.context) self.flag = None self.flag_got_value = False self.result = ParseResult() self.contexts = copy.deepcopy(contexts) - debug("Available contexts: {!r}".format(self.contexts)) + debug("Available contexts: %r", self.contexts) # In case StateMachine does anything in __init__ super().__init__() @@ -270,47 +268,46 @@ def waiting_for_flag_value(self) -> bool: return not has_value def handle(self, token: str) -> None: - debug("Handling token: {!r}".format(token)) + debug("Handling token: %r", token) # Handle unknown state at the top: we don't care about even # possibly-valid input if we've encountered unknown input. if self.current_state == "unknown": - debug("Top-of-handle() see_unknown({!r})".format(token)) + debug("Top-of-handle() see_unknown(%r)", token) self.see_unknown(token) return # Flag if self.context and token in self.context.flags: - debug("Saw flag {!r}".format(token)) + debug("Saw flag %r", token) self.switch_to_flag(token) elif self.context and token in self.context.inverse_flags: - debug("Saw inverse flag {!r}".format(token)) + debug("Saw inverse flag %r", token) self.switch_to_flag(token, inverse=True) # Value for current flag elif self.waiting_for_flag_value: debug( - "We're waiting for a flag value so {!r} must be it?".format( - token - ) + "We're waiting for a flag value so %r must be it?", + token, ) # noqa self.see_value(token) # Positional args (must come above context-name check in case we still # need a posarg and the user legitimately wants to give it a value that # just happens to be a valid context name.) elif self.context and self.context.missing_positional_args: - msg = "Context {!r} requires positional args, eating {!r}" - debug(msg.format(self.context, token)) + msg = "Context %r requires positional args, eating %r" + debug(msg, self.context, token) self.see_positional_arg(token) # New context elif token in self.contexts: self.see_context(token) # Initial-context flag being given as per-task flag (e.g. --help) elif self.initial and token in self.initial.flags: - debug("Saw (initial-context) flag {!r}".format(token)) + debug("Saw (initial-context) flag %r", token) flag = self.initial.flags[token] # Special-case for core --help flag: context name is used as value. if flag.name == "help": flag.value = self.context.name - msg = "Saw --help in a per-task context, setting task name ({!r}) as its value" # noqa - debug(msg.format(flag.value)) + msg = "Saw --help in a per-task context, setting task name (%r) as its value" # noqa + debug(msg, flag.value) # All others: just enter the 'switch to flag' parser state else: # TODO: handle inverse core flags too? There are none at the @@ -321,22 +318,21 @@ def handle(self, token: str) -> None: # Unknown else: if not self.ignore_unknown: - debug("Can't find context named {!r}, erroring".format(token)) + debug("Can't find context named %r, erroring", token) self.error("No idea what {!r} is!".format(token)) else: - debug("Bottom-of-handle() see_unknown({!r})".format(token)) + debug("Bottom-of-handle() see_unknown(%r)", token) self.see_unknown(token) def store_only(self, token: str) -> None: # Start off the unparsed list - debug("Storing unknown token {!r}".format(token)) + debug("Storing unknown token %r", token) self.result.unparsed.append(token) def complete_context(self) -> None: debug( - "Wrapping up context {!r}".format( - self.context.name if self.context else self.context - ) + "Wrapping up context %r", + self.context.name if self.context else self.context, ) # Ensure all of context's positional args have been given. if self.context and self.context.missing_positional_args: @@ -351,15 +347,15 @@ def complete_context(self) -> None: def switch_to_context(self, name: str) -> None: self.context = copy.deepcopy(self.contexts[name]) - debug("Moving to context {!r}".format(name)) - debug("Context args: {!r}".format(self.context.args)) - debug("Context flags: {!r}".format(self.context.flags)) - debug("Context inverse_flags: {!r}".format(self.context.inverse_flags)) + debug("Moving to context %r", name) + debug("Context args: %r", self.context.args) + debug("Context flags: %r", self.context.flags) + debug("Context inverse_flags: %r", self.context.inverse_flags) def complete_flag(self) -> None: if self.flag: - msg = "Completing current flag {} before moving on" - debug(msg.format(self.flag)) + msg = "Completing current flag %s before moving on" + debug(msg, self.flag) # Barf if we needed a value and didn't get one if ( self.flag @@ -373,8 +369,8 @@ def complete_flag(self) -> None: # explicit value, but they were seen, ergo they should get treated like # bools. if self.flag and self.flag.raw_value is None and self.flag.optional: - msg = "Saw optional flag {!r} go by w/ no value; setting to True" - debug(msg.format(self.flag.name)) + msg = "Saw optional flag %r go by w/ no value; setting to True" + debug(msg, self.flag.name) # Skip casting so the bool gets preserved self.flag.set_value(True, cast=False) @@ -425,7 +421,7 @@ def switch_to_flag(self, flag: str, inverse: bool = False) -> None: # If it wasn't in either, raise the original context's # exception, as that's more useful / correct. raise e - debug("Moving to flag {!r}".format(self.flag)) + debug("Moving to flag %r", self.flag) # Bookkeeping for iterable-type flags (where the typical 'value # non-empty/nondefault -> clearly it got its value already' test is # insufficient) @@ -433,13 +429,13 @@ def switch_to_flag(self, flag: str, inverse: bool = False) -> None: # Handle boolean flags (which can immediately be updated) if self.flag and not self.flag.takes_value: val = not inverse - debug("Marking seen flag {!r} as {}".format(self.flag, val)) + debug("Marking seen flag %r as %s", self.flag, val) self.flag.value = val def see_value(self, value: Any) -> None: self.check_ambiguity(value) if self.flag and self.flag.takes_value: - debug("Setting flag {!r} to value {!r}".format(self.flag, value)) + debug("Setting flag %r to value %r", self.flag, value) self.flag.value = value self.flag_got_value = True else: diff --git a/invoke/program.py b/invoke/program.py index c7e5cd00..1564f0bf 100644 --- a/invoke/program.py +++ b/invoke/program.py @@ -397,7 +397,7 @@ def run(self, argv: Optional[List[str]] = None, exit: bool = True) -> None: # steps, then tell it to execute the tasks. self.execute() except (UnexpectedExit, Exit, ParseError) as e: - debug("Received a possibly-skippable exception: {!r}".format(e)) + debug("Received a possibly-skippable exception: %r", e) # Print error messages from parser, runner, etc if necessary; # prevents messy traceback but still clues interactive user into # problems. @@ -422,7 +422,7 @@ def run(self, argv: Optional[List[str]] = None, exit: bool = True) -> None: sys.exit(1) # Same behavior as Python itself outside of REPL def parse_core(self, argv: Optional[List[str]]) -> None: - debug("argv given to Program.run: {!r}".format(argv)) + debug("argv given to Program.run: %r", argv) self.normalize_argv(argv) # Obtain core args (sets self.core) @@ -504,8 +504,9 @@ def parse_cleanup(self) -> None: # Print per-task help, if necessary if halp: if halp in self.parser.contexts: - msg = "Saw --help , printing per-task help & exiting" - debug(msg) + debug( + "Saw --help , printing per-task help & exiting" + ) self.print_task_help(halp) raise Exit else: @@ -599,10 +600,10 @@ def normalize_argv(self, argv: Optional[List[str]]) -> None: """ if argv is None: argv = sys.argv - debug("argv was None; using sys.argv: {!r}".format(argv)) + debug("argv was None; using sys.argv: %r", argv) elif isinstance(argv, str): argv = argv.split() - debug("argv was string-like; splitting: {!r}".format(argv)) + debug("argv was string-like; splitting: %r", argv) self.argv = argv @property @@ -696,8 +697,8 @@ def parse_core_args(self) -> None: debug("Parsing initial context (core args)") parser = Parser(initial=self.initial_context, ignore_unknown=True) self.core = parser.parse_argv(self.argv[1:]) - msg = "Core-args parse result: {!r} & unparsed: {!r}" - debug(msg.format(self.core, self.core.unparsed)) + msg = "Core-args parse result: %r & unparsed: %r" + debug(msg, self.core, self.core.unparsed) def load_collection(self) -> None: """ @@ -762,14 +763,14 @@ def parse_tasks(self) -> None: .. versionadded:: 1.0 """ self.parser = self._make_parser() - debug("Parsing tasks against {!r}".format(self.collection)) + ("Parsing tasks against {!r}".format(self.collection)) result = self.parser.parse_argv(self.core.unparsed) self.core_via_tasks = result.pop(0) self._update_core_context( context=self.core[0], new_args=self.core_via_tasks.args ) self.tasks = result - debug("Resulting task contexts: {!r}".format(self.tasks)) + debug("Resulting task contexts: %r", self.tasks) def print_task_help(self, name: str) -> None: """ diff --git a/invoke/util.py b/invoke/util.py index df29c841..6cfadc3f 100644 --- a/invoke/util.py +++ b/invoke/util.py @@ -214,13 +214,13 @@ def run(self) -> None: self.exc_info = sys.exc_info() # And log now, in case we never get to later (e.g. if executing # program is hung waiting for us to do something) - msg = "Encountered exception {!r} in thread for {!r}" + msg = "Encountered exception %r in thread for %r" # Name is either target function's dunder-name, or just "_run" if # we were run subclass-wise. name = "_run" if "target" in self.kwargs: name = self.kwargs["target"].__name__ - debug(msg.format(self.exc_info[1], name)) # noqa + debug(msg, self.exc_info[1], name) # noqa def exception(self) -> Optional["ExceptionWrapper"]: """ diff --git a/tests/config.py b/tests/config.py index 34a070c0..9452fd50 100644 --- a/tests/config.py +++ b/tests/config.py @@ -644,7 +644,9 @@ def nonexistent_files_are_skipped_and_logged(self, mock_debug): c._load_yml = Mock(side_effect=IOError(2, "aw nuts")) c.set_runtime_path("is-a.yml") # Triggers use of _load_yml c.load_runtime() - mock_debug.assert_any_call("Didn't see any is-a.yml, skipping.") + mock_debug.assert_any_call( + "Didn't see any %s, skipping.", "is-a.yml" + ) @raises(IOError) def non_missing_file_IOErrors_are_raised(self): diff --git a/tests/runners.py b/tests/runners.py index eed840d3..47d9e0e8 100644 --- a/tests/runners.py +++ b/tests/runners.py @@ -12,7 +12,7 @@ from pytest import raises, skip from pytest_relaxed import trap -from unittest.mock import patch, Mock, call +from unittest.mock import Mock, call, patch from invoke import ( CommandTimedOut, @@ -592,13 +592,11 @@ def exceptions_get_logged(self, mock_debug): # then make thread class configurable somewhere in Runner, and pass # in a customized ExceptionHandlingThread that has a Mock for that # method? - # NOTE: splitting into a few asserts to work around python 3.7 - # change re: trailing comma, which kills ability to just statically - # assert the entire string. Sigh. Also I'm too lazy to regex. - msg = mock_debug.call_args[0][0] - assert "Encountered exception OhNoz" in msg - assert "'oh god why'" in msg - assert "in thread for 'handle_stdin'" in msg + mock_debug.assert_any_call( + "Encountered exception %r in thread for %r", + klass.write_proc_stdin.side_effect, + "handle_stdin", + ) def EOF_triggers_closing_of_proc_stdin(self): class Fake(_Dummy):