CLI testing and execution#

Todo

Write example and tutorial.

click_extra.run API#

classDiagram BytesIO <|-- BytesIOCopy CliRunner <|-- ExtraCliRunner Result <|-- ExtraResult

CLI testing and simulation of their execution.

click_extra.testing.INDENT = '  '#

Constants for rendering of CLI execution.

click_extra.testing.EnvVars#

Type for dict-like environment variables.

alias of Mapping[str, Optional[str]]

click_extra.testing.NestedArgs#

Types for arbitrary nested CLI arguments.

Arguments can be str, pathlib.Path objects or None values.

alias of Iterable[Union[str, Path, None, Iterable[NestedArgs]]]

click_extra.testing.args_cleanup(*args)[source]#

Flatten recursive iterables, remove all None, and cast each element to strings.

Helps serialize pathlib.Path and other objects.

It also allows for nested iterables and None values as CLI arguments for convenience. We just need to flatten and filters them out.

Return type:

tuple[str, ...]

click_extra.testing.format_cli_prompt(cmd_args, extra_env=None)[source]#

Simulate the console prompt used to invoke the CLI.

Return type:

str

click_extra.testing.print_cli_run(args, result, env=None)[source]#

Prints the full simulation of CLI execution, including output.

Mostly used to print debug traces to user or in test results.

Return type:

None

click_extra.testing.env_copy(extend=None)[source]#

Returns a copy of the current environment variables and eventually extend it.

Mimics Python’s original implementation by returning None if no extend content are provided.

Environment variables are expected to be a dict of str:str.

Return type:

Optional[Mapping[str, str | None]]

click_extra.testing.run_cmd(*args, extra_env=None, print_output=True)[source]#

Run a system command, print output and return results.

Return type:

tuple[int, str, str]

click_extra.testing.INVOKE_ARGS = {'args', 'catch_exceptions', 'cli', 'color', 'env', 'input', 'self'}#

Parameter IDs of click.testing.CliRunner.invoke().

We need to collect them to help us identify which extra parameters passed to invoke() collides with its original signature.

Warning

This has been reported upstream to Click project but has been rejected and not considered an issue worth fixing.

class click_extra.testing.BytesIOCopy(copy_to)[source]#

Bases: BytesIO

Patch io.BytesIO to let the written stream be copied to another.

Caution

This has been proposed upstream to Click project but has not been merged yet.

flush()[source]#

Does nothing.

Return type:

None

write(b)[source]#

Write bytes to file.

Return the number of bytes written.

Return type:

int

class click_extra.testing.StreamMixer(mix_stderr)[source]#

Bases: object

Mixes <stdout> and <stderr> streams if mix_stderr=True.

The result is available in the output attribute.

If mix_stderr=False, the <stdout> and <stderr> streams are kept independent and the output is the same as the <stdout> stream.

Caution

This has been proposed upstream to Click project but has not been merged yet.

class click_extra.testing.ExtraResult(runner, stdout_bytes, stderr_bytes, output_bytes, return_value, exit_code, exception, exc_info=None)[source]#

Bases: Result

Like click.testing.Result, with finer <stdout> and <stderr> streams.

Caution

This has been proposed upstream to Click project but has not been merged yet.

Same as original but adds output_bytes parameter.

Also makes stderr_bytes mandatory.

stderr_bytes: bytes#

Makes stderr_bytes mandatory.

property output: str#

The terminal output as unicode string, as the user would see it.

Caution

Contrary to original click.testing.Result.output, it is not a proxy for self.stdout. It now possess its own stream to mix <stdout> and <stderr> depending on the mix_stderr value.

property stderr: str#

The standard error as unicode string.

Caution

Contrary to original click.testing.Result.stderr, it no longer raise an exception, and always returns the <stderr> string.

class click_extra.testing.ExtraCliRunner(charset='utf-8', env=None, echo_stdin=False, mix_stderr=True)[source]#

Bases: CliRunner

Augment click.testing.CliRunner with extra features and bug fixes.

env: t.Mapping[str, t.Optional[str]]#
force_color: bool = False#

Global class attribute to override the color parameter in invoke.

Note

This was initially developed to force the initialization of the runner during the setup of Sphinx new directives. This was the only way we found, as to patch some code we had to operate at the class level.

isolation(input=None, env=None, color=False)[source]#

Copy of click.testing.CliRunner.isolation() with extra features. :rtype: Iterator[tuple[BytesIO, BytesIO, BytesIO]]

  • An additional output stream is returned, which is a mix of <stdout> and <stderr> streams if mix_stderr=True.

  • Always returns the <stderr> stream.

Caution

This is a hard-copy of the modified isolation() method from click#2523 PR which has not been merged upstream yet.

Todo

Reduce the code duplication here by using clever monkeypatching?

invoke2(cli, args=None, input=None, env=None, catch_exceptions=True, color=False, **extra)[source]#

Copy of click.testing.CliRunner.invoke() with extra <output> stream. :rtype: ExtraResult

Caution

This is a hard-copy of the modified invoke() method from click#2523 PR which has not been merged upstream yet.

Todo

Reduce the code duplication here by using clever monkeypatching?

invoke(cli, *args, input=None, env=None, catch_exceptions=True, color=None, **extra)[source]#

Same as click.testing.CliRunner.invoke() with extra features.

  • The first positional parameter is the CLI to invoke. The remaining positional parameters of the function are the CLI arguments. All other parameters are required to be named.

  • The CLI arguments can be nested iterables of arbitrary depth. This is useful for argument composition of test cases with @pytest.mark.parametrize.

  • Allow forcing of the color property at the class-level via force_color attribute.

  • Adds a special case in the form of color="forced" parameter, which allows colored output to be kept, while forcing the initialization of Context.color = True. This is not allowed in current implementation of click.testing.CliRunner.invoke() because of colliding parameters.

  • Strips all ANSI codes from results if color was explicirely set to False.

  • Always prints a simulation of the CLI execution as the user would see it in its terminal. Including colors.

  • Pretty-prints a formatted exception traceback if the command fails.

Parameters:
  • cli (BaseCommand) – CLI to invoke.

  • *args –

    can be nested iterables composed of str, pathlib.Path objects and None values. The nested structure will be flattened and None values will be filtered out. Then all elements will be casted to str. See args_cleanup() for details.

  • input (str | bytes | IO | None) – same as click.testing.CliRunner.invoke().

  • env (Optional[Mapping[str, str | None]]) – same as click.testing.CliRunner.invoke().

  • catch_exceptions (bool) – same as click.testing.CliRunner.invoke().

  • color (Union[bool, Literal['forced'], None]) – If a boolean, the parameter will be passed as-is to click.testing.CliRunner.isolation(). If "forced", the parameter will be passed as True to click.testing.CliRunner.isolation() and an extra color=True parameter will be passed to the invoked CLI.

  • **extra –

    same as click.testing.CliRunner.invoke(), but colliding parameters are allowed and properly passed on to the invoked CLI.

Return type:

Result