tests package

Subpackages

Submodules

tests.conftest module

Fixtures, configuration and helpers for tests.

tests.conftest.httpserver_listen_address()[source]

Bind the local HTTP server to the loopback address, not to a name.

Overrides pytest-httpserver’s default of ("localhost", 0). Resolving that name is the one thing the tests/test_config.py cases serving a configuration file over HTTP need from the host, and a build sandbox is exactly where it is unavailable: the Nix one on macOS denies the lookup, so every one of them errors out with socket.gaierror: [Errno 8] nodename nor servname provided, or not known.

Binding the literal address asks nothing of the resolver. Whether a sandbox additionally gates the loopback socket is its own policy, and a separate question: this only removes the lookup that failed first.

tests.conftest.TRANSIENT_STATUS_CODES = frozenset({408, 425, 429, 500, 502, 503, 504})

Status codes a host answers with when it is refusing this request rather than reporting something about the resource: a rate limit, a proxy hiccup, or a service that is briefly down.

tests.conftest.fetch_or_skip(url, timeout=60)[source]

Fetch url, skipping the test when the failure says nothing about it.

A network-dependent test asserts something about what a host serves. It cannot assert it while the host is rate-limiting, timing out, or down, and a bare assert response.ok there reports assert False: a red run that looks like the finding the test exists to make, with nothing naming the cause. GitHub throttles anonymous archive downloads, so a full-suite run hits this on its own schedule and reads as an order-dependent flake.

A response that is about the resource still fails, loudly and with its status: a 404 means the URL these tests build no longer resolves, which is the finding, not the weather.

Return type:

Response

tests.conftest.walk_commands(command, ctx=None, path=())[source]

Yield every (path, command) pair under command, itself included.

path holds the subcommand names leading to the command, the root’s own name excluded, so joining it names the invocation a user would type.

Parameters:
  • command (Command) – the command to walk, a group or a leaf.

  • ctx (Context | None) – the context command is looked up in. Built from the command itself when omitted, which is what a walk starting at the root wants.

  • path (tuple[str, ...]) – the names already walked through, for the recursion.

Return type:

Iterator[tuple[tuple[str, ...], Command]]

tests.test_accessibility module

tests.test_accessibility.report_cli()[source]

An extra command that echoes the resolved color flag and table format, then renders a table with a styled cell.

tests.test_accessibility.test_default_params_include_accessible()[source]
tests.test_accessibility.test_accessible_precedence(invoke, report_cli, extra_args, expected_color, expected_format)[source]
tests.test_accessibility.test_accessible_via_envvar(invoke, report_cli)[source]
tests.test_accessibility.test_explicit_flag_overrides_accessible_envvar(invoke, report_cli)[source]
tests.test_accessibility.test_default_renders_box_drawing_and_ansi(invoke, report_cli)[source]
tests.test_accessibility.test_accessible_strips_box_drawing_and_ansi(invoke, report_cli)[source]
tests.test_accessibility.test_standalone_decorator_on_plain_click_command(invoke)[source]

--accessible lowers the defaults of the sibling color and table options, so they must be composed alongside it on a plain click.command.

tests.test_accessibility.test_accessible_flag_published_to_context(invoke, args, expected)[source]

set_accessible publishes the resolved intent at ctx.meta[ACCESSIBLE].

tests.test_accessibility.test_clear_is_noop_under_accessible(invoke, monkeypatch)[source]

clear() defers to click.clear normally, but no-ops under –accessible.

tests.test_accessibility.test_echo_via_pager_streams_plainly_under_accessible(invoke, monkeypatch)[source]

echo_via_pager pages normally, but writes straight to stdout under –accessible.

tests.test_accessibility.test_pager_and_clear_defer_without_accessible_option(invoke, monkeypatch)[source]

With no –accessible option wired, the wrappers defer to Click unchanged.

tests.test_cli_wrapper module

Tests for the CLI wrapper feature.

tests.test_cli_wrapper.GREET_SCRIPT = 'import click\n\n@click.command()\n@click.option("--name", default="World", help="Name to greet.")\ndef hello(name):\n    """Greet someone."""\n    click.echo(f"Hello, {name}")\n\nif __name__ == "__main__":\n    hello()\n'

Plain @click.command() script: patched via decorator defaults.

tests.test_cli_wrapper.CUSTOM_CLS_SCRIPT = 'import click\n\nclass RecipeGroup(click.Group):\n    """Custom group like Flask\'s FlaskGroup."""\n\n@click.command(cls=RecipeGroup)\ndef kitchen():\n    """Manage recipes and ingredients."""\n\n@kitchen.command()\n@click.option("--servings", default=4, help="Number of servings.")\ndef bake(servings):\n    """Bake a cake."""\n    click.echo(f"Baking for {servings}")\n\nif __name__ == "__main__":\n    kitchen()\n'

Script with explicit cls=RecipeGroup: patched via method patching.

tests.test_cli_wrapper.MULTI_OPTION_SCRIPT = 'import click\n\n@click.command()\n@click.option("--city", default="Paris", help="City name.")\n@click.option("--unit", default="celsius", help="Temperature unit.")\n@click.option("--verbose", is_flag=True, help="Show details.")\ndef weather(city, unit, verbose):\n    """Check the weather."""\n    msg = f"{city}: 22 {unit}"\n    if verbose:\n        msg += " (detailed)"\n    click.echo(msg)\n\nif __name__ == "__main__":\n    weather()\n'

Script with multiple options for config passthrough tests.

tests.test_cli_wrapper.PACKAGE_GROUP_SRC = 'import click\n\n@click.group()\ndef cli():\n    """Manage a produce stand."""\n\n@cli.command()\ndef restock():\n    """Restock the shelves."""\n    click.echo("Restocked")\n'

Module-level Click group, exposed by a package inside a project directory.

tests.test_cli_wrapper.runner()[source]

CLI runner for wrapper tests.

tests.test_cli_wrapper.greet_script(tmp_path)[source]

A minimal Click CLI script for wrapping tests.

tests.test_cli_wrapper.custom_cls_script(tmp_path)[source]

A Click CLI with explicit cls=CustomGroup (like Flask’s FlaskGroup).

tests.test_cli_wrapper.weather_script(tmp_path)[source]

A Click CLI with multiple options for config tests.

tests.test_cli_wrapper.make_project(tmp_path)[source]

Build a local project directory exposing a Click group at module level.

The returned factory writes packaging metadata (pyproject.toml or setup.cfg) plus a package, in either the flat or src layout. On teardown it undoes the sys.path and sys.modules mutations that resolving a project directory triggers, keeping the tests isolated.

tests.test_cli_wrapper.create_config(tmp_path)[source]

Produce a temporary configuration file.

tests.test_cli_wrapper.test_patched_class_inherits_click(cls, base)[source]
tests.test_cli_wrapper.test_patched_class_has_mixin(cls)[source]
tests.test_cli_wrapper.test_patched_class_context(cls)[source]
tests.test_cli_wrapper.test_patched_command_no_extra_params()[source]

Patched commands carry no default_params.

tests.test_cli_wrapper.test_resolve_target(script, expected_module, expected_func)[source]
tests.test_cli_wrapper.test_resolve_py_file(tmp_path)[source]
tests.test_cli_wrapper.test_resolve_py_file_missing(tmp_path)[source]

A .py path that doesn’t exist falls through to module resolution.

tests.test_cli_wrapper.test_resolve_not_found(script)[source]
tests.test_cli_wrapper.test_resolve_directory_flat_layout(make_project)[source]

A project directory resolves via its pyproject.toml entry point.

tests.test_cli_wrapper.test_resolve_directory_src_layout(make_project)[source]

A src-layout project puts its src/ directory on sys.path.

tests.test_cli_wrapper.test_resolve_directory_setup_cfg(make_project)[source]

console_scripts in setup.cfg resolve when no pyproject.toml is present.

tests.test_cli_wrapper.test_resolve_directory_duplicate_scripts(make_project)[source]

Multiple script names pointing to one target resolve unambiguously.

tests.test_cli_wrapper.test_resolve_directory_run(runner, make_project)[source]

The default run mode imports and executes a project directory’s CLI.

tests.test_cli_wrapper.test_resolve_directory_introspect(make_project)[source]

Introspection discovers the command tree of a project directory.

tests.test_cli_wrapper.test_resolve_directory_no_scripts(tmp_path)[source]

A project without console scripts raises an actionable error.

tests.test_cli_wrapper.test_resolve_directory_multiple_scripts(make_project)[source]

Distinct entry-point targets cannot be disambiguated and raise.

tests.test_cli_wrapper.test_resolve_directory_package_not_found(tmp_path)[source]

An entry point whose package is absent on disk raises.

tests.test_cli_wrapper.test_wrap_self(runner, args, expected)[source]
tests.test_cli_wrapper.test_run_invokes_target(runner, script_fixture, target_args, expected_text, request)[source]

The run subcommand forwards arguments to the target CLI.

tests.test_cli_wrapper.test_run_colorizes(runner, script_fixture, target_args, request)[source]

Help output contains ANSI escape codes.

tests.test_cli_wrapper.test_run_highlights_keywords_with_custom_cls(runner, custom_cls_script)[source]

Options and subcommands are individually styled, not just headings.

tests.test_cli_wrapper.test_run_unresolvable_target(runner)[source]
tests.test_cli_wrapper.test_run_usage_line_ignores_the_launcher(runner, make_project, outer_package, monkeypatch)[source]

The wrapped target’s usage line names the script, not click-extra’s launcher.

A target whose Click command detects its own program name reads __main__.__package__, which states how click-extra itself was launched. Left alone, a documentation build running python -m sphinx paints a wrapped usage line as python -m sphinx.flask. Pinning the detection on the script name keeps it identical whatever the launcher.

tests.test_cli_wrapper.test_resolve_target_command_returns_command_and_context(greet_script)[source]

The shared resolver returns the target’s command object and a context.

tests.test_cli_wrapper.test_resolve_target_command_drills_subcommand(custom_cls_script)[source]

Extra subcommands navigate into nested groups.

tests.test_cli_wrapper.test_wrap_man_reads_manual(runner, greet_script)[source]

click-extra wrap --man SCRIPT reads the target’s manual and exits.

A CLI shipping no man page of its own still gets one to read, typeset from its own command tree.

tests.test_cli_wrapper.test_wrap_help_format_man_emits_source(runner, greet_script)[source]

The roff a packager installs comes from the format, not from –man.

tests.test_cli_wrapper.test_wrap_man_custom_class_group(runner, custom_cls_script)[source]

--man resolves a custom-class group target via the shared scanner.

tests.test_cli_wrapper.test_wrap_man_drills_into_subcommand(runner, custom_cls_script)[source]

Extra arguments after SCRIPT render the nested subcommand’s page.

tests.test_cli_wrapper.test_wrap_man_unresolvable_target(runner)[source]
tests.test_cli_wrapper.test_wrap_man_output_dir_writes_tree(runner, custom_cls_script, tmp_path)[source]

wrap --help-format man --output-dir writes one .1 per (sub)command.

--output-dir must appear before SCRIPT, because wrap runs with allow_interspersed_args=False so that anything after SCRIPT is treated as a sub-command path rather than a click-extra flag.

tests.test_cli_wrapper.test_wrap_man_output_dir_creates_missing_directory(runner, greet_script, tmp_path)[source]

--output-dir creates the target dir when it does not exist yet.

tests.test_cli_wrapper.test_wrap_man_output_dir_rejects_subcommand(runner, custom_cls_script, tmp_path)[source]

--output-dir always emits the full tree; mixing in a SUBCOMMAND arg is rejected so the user cannot accidentally produce a tree of pages named after a partial path.

tests.test_cli_wrapper.test_group_dispatches_to_wrap(runner, greet_script, args, expected)[source]

All invocation forms reach the target CLI.

tests.test_cli_wrapper.test_group_options_work_with_wrap(runner, greet_script, group_opts)[source]

Default Group options are accepted alongside the wrap subcommand.

tests.test_cli_wrapper.test_wrap_honors_group_theme(runner, greet_script, theme, styled_heading)[source]

A group-level --theme reaches the wrapped CLI’s help screen.

ThemeOption records the pick on the shared context meta, but the wrapped target runs under its own fresh context; wrap must bridge the pick to the process default (through patch_click) for the target’s help to pick it up.

tests.test_cli_wrapper.test_wrap_honors_theme_envvar(runner, greet_script, monkeypatch)[source]

CLICK_EXTRA_THEME reaches a wrapped CLI’s help screen too.

The group resolves the machine-wide variable like any other source of a palette, and wrap bridges whatever it settled on to the process default the wrapped target renders under.

tests.test_cli_wrapper.test_wrap_pins_group_color_on_every_target_kind(runner, request, script_fixture, color_opt, colored)[source]

The group’s tri-state color decision reaches the wrapped target.

Both kinds of target must obey it: one built by the patched @click.command() decorator (so it is a _HelpColorsMixin) and one carrying an explicit cls= (so it stays plain Click). They travel different code paths inside patch_click, and the forcing direction used to reach the second only.

The runner is left on its own auto-detection here: forcing colors at the stream level would paint the output whatever the flag decided.

tests.test_cli_wrapper.test_group_known_subcommands_not_wrapped(runner, subcommand)[source]

Known demo subcommands are dispatched directly, not to wrap.

tests.test_cli_wrapper.test_config_verbosity(runner, greet_script, create_config)[source]

verbosity = "DEBUG" in pyproject.toml activates debug logging.

tests.test_cli_wrapper.test_config_group_theme(runner, greet_script, create_config)[source]

A [tool.click-extra] theme key sets the help-screen theme.

tests.test_cli_wrapper.test_config_target_string(runner, greet_script, create_config)[source]

A string config value is forwarded as --key value.

tests.test_cli_wrapper.test_config_target_bool_true(runner, weather_script, create_config)[source]

A true config value is forwarded as --flag.

tests.test_cli_wrapper.test_config_target_bool_false_is_noop(runner, weather_script, create_config)[source]

A false config value is skipped: the flag is simply not passed.

tests.test_cli_wrapper.test_config_target_multiple_keys(runner, weather_script, create_config)[source]

Multiple config keys are all forwarded.

tests.test_cli_wrapper.test_config_target_cli_overrides(runner, greet_script, create_config)[source]

Explicit CLI args override config target defaults.

tests.test_cli_wrapper.test_config_target_wrong_section_ignored(runner, greet_script, create_config)[source]

Config for a different script name has no effect.

tests.test_cli_wrapper.test_config_target_empty_section(runner, greet_script, create_config)[source]

An empty target section produces no extra args.

tests.test_cli_wrapper.test_config_target_no_config(runner, greet_script)[source]

No config file at all: target runs with its own defaults.

tests.test_cli_wrapper.test_config_target_invalid_option(runner, greet_script, create_config)[source]

An invalid config key is caught by the target CLI.

tests.test_cli_wrapper.test_config_args_for_target(section, script, expected)[source]
tests.test_cli_wrapper.test_config_args_no_config()[source]

No config loaded: returns empty tuple.

tests.test_cli_wrapper.test_config_args_no_wrap_section()[source]

Config exists but has no wrap section.

tests.test_cli_wrapper.test_wrap_help_format_describes_a_foreign_cli(runner, greet_script, help_format, expected)[source]

A CLI that never heard of Click Extra is rendered from the outside.

Same posture as --params and --man: the target is loaded and walked, never asked to cooperate, so machine-readable help needs no opt-in from the author of the wrapped CLI.

tests.test_cli_wrapper.test_wrap_help_format_json_is_parseable(runner, greet_script)[source]

The JSON rendering of a foreign CLI parses, and carries its options.

tests.test_cli_wrapper.test_wrap_help_format_stays_colorless(runner, greet_script)[source]

Machine-readable output carries no ANSI, whatever the group asked for.

Every format here is meant to be piped into a parser, which has no use for escape codes: --color=always paints the help screen, not the export.

tests.test_cli_wrapper.test_wrap_install_writes_where_the_consumer_looks(runner, greet_script, tmp_path, monkeypatch, help_format, artifact)[source]

--install puts each rendering where the tool reading it looks.

One destination flag across both formats, each module resolving its own canonical directory: Carapace’s spec directory, and the user’s man directory. Both honor their XDG variable at call time.

tests.test_cli_wrapper.test_wrap_destination_refused_for_documents(runner, greet_script, help_format)[source]

A document has nowhere canonical to be installed, and says so.

tests.test_cli_wrapper.test_target_prog_name_is_what_a_user_would_type(script, expected)[source]

Every rendering is titled with the name the target runs under.

tests.test_cli_wrapper.test_every_rendering_agrees_on_the_target_name(runner, tmp_path)[source]

One rule, so the man page, spec, Markdown and tree cannot disagree.

The script file is named after neither its command object nor the directory holding it, so a renderer picking the wrong source shows it.

tests.test_color module

tests.test_color.test_standalone_color_option(invoke, option_decorator, param, expecting_colors, assert_output_regex)[source]

Check color option values, defaults and effects on all things colored, including verbosity option.

tests.test_color.test_no_color_env_convention(invoke, env, env_expect_colors, param, param_expect_colors)[source]
tests.test_color.test_resolve_color_env_term(monkeypatch, term, expected)[source]

A dumb/unknown TERM votes color-off, while any other value stays neutral.

tests.test_color.test_resolve_color_env_force_color_beats_dumb_term(monkeypatch, term)[source]

An explicit FORCE_COLOR stays authoritative over a dumb/unknown TERM.

tests.test_color.test_color_synonym_cli_resolution(invoke, when, ctx_color)[source]

Every canonical value and GNU synonym resolves –color to the right state.

tests.test_color.test_color_synonym_invalid_value(invoke, when)[source]

Unknown values, including the git-style true/false, still error. The message lists only the canonical choices, never the hidden synonyms.

tests.test_color.test_color_synonym_hidden_in_help(invoke)[source]

The GNU synonyms are accepted but never advertised: –help shows only the canonical metavar.

tests.test_color.test_color_synonym_cli_beats_env(invoke, when, env_var, ctx_color)[source]

A synonym on the command line keeps the same env precedence as its canonical twin: a command-line choice outranks the color environment variables.

tests.test_color.test_color_synonym_config_string(invoke, create_config, when, ctx_color)[source]

A configuration file accepts the GNU synonyms as strings, normalized exactly like on the command line.

tests.test_color.test_color_synonym_config_boolean(invoke, create_config, filename, raw, ctx_color)[source]

A configuration boolean maps true -> always and false -> never, including YAML’s coercion of yes/no/on/off, so a value means the same across formats.

tests.test_color.test_color_synonym_config_beats_env(invoke, create_config, rhs, env_var, ctx_color)[source]

A config synonym or boolean beats the color environment variables, matching the documented precedence of canonical config values.

tests.test_color.test_integrated_color_option(invoke, param, expecting_colors, ctx_color, assert_output_regex)[source]

Check effect of color option on all things colored, including verbosity option.

Also checks the color option in subcommands is inherited from parent context.

tests.test_color.test_color_settles_before_eager_help_and_version(invoke, args, expecting_colors)[source]

–color / –no-color colorize the eager –help and –version screens whatever their position on the command line.

Click processes eager options in command-line order, so a –color sitting after –help or –version would otherwise pin ctx.color only once the screen had already printed and exited. Command.parse_args settles the color options in a pre-pass to close that gap. See Command._resolve_presentation_eagerly.

tests.test_color.test_forced_color_sets_and_restores_env(monkeypatch)[source]

forced_color forces FORCE_COLOR and clears Click Extra’s disabling vars.

Inside the context the capture sees FORCE_COLOR=1 with every flag that would disable color (NO_COLOR, LLM, …) removed; on exit the prior environment, including any pre-existing values, is restored untouched.

tests.test_color.test_resolve_background(monkeypatch, env, expected)[source]

Environment-variable precedence for the dark/light background decision.

tests.test_color.test_resolve_background_query_is_opt_in(monkeypatch)[source]

The OSC 11 query runs only when allow_query is set, below the env vars.

tests.test_color.test_resolve_background_query_below_explicit_env(monkeypatch)[source]

An explicit env signal outranks the live query even when querying is allowed.

tests.test_color.test_parse_osc_rgb(response, expected)[source]

An OSC 11 reply is parsed into an 8-bit RGB tuple, or None when malformed.

tests.test_color.test_is_dark_rgb(rgb, is_dark)[source]

Perceived-lightness classification of background colors.

tests.test_color.test_is_a_tty()[source]

The probe reads isatty, guarding streams that do not expose it.

Return type:

None

tests.test_color.test_query_osc_background_without_tty(monkeypatch)[source]

The OSC query is a no-op when stdin or stdout is not a terminal.

tests.test_color.test_query_osc_background_without_streams(monkeypatch)[source]

A detached process (no __stdin__/__stdout__) cannot query the terminal.

tests.test_color.test_query_osc_background_pty(monkeypatch)[source]

A full OSC 11 round-trip over a pseudo-terminal yields the parsed color.

tests.test_color.test_invocation_color_reaches_background_threads(invoke, args, expected)[source]

The resolved color tri-state is mirrored process-wide, so a background thread (which has no reachable Click context) can still honor it; the mirror resets once the invocation closes.

tests.test_color.test_resilient_context_does_not_publish_invocation_color(monkeypatch)[source]

Building an introspection context must leave the color mirror untouched.

Module-level completion-spec building (like the Carapace samples of tests/test_carapace.py) runs at pytest collection, ahead of every test. A resilient context is never closed, so publishing from one pinned the process-wide mirror to the build environment’s NO_COLOR, randomly stripping ANSI from the log output of any CLI carrying no color option of its own, depending on the test order the random seed drew.

tests.test_color.test_invocation_color_never_outlives_its_invocation(invoke, args)[source]

However an invocation ends, it must leave the color mirror on auto.

publish_invocation_color() queues its reset as a context-close callback, so an option callback raising during parameter processing aborts before the context is entered and the reset never fires. The mirror then stays pinned to that run’s --no-color for the rest of the process, stripping ANSI from any later CLI carrying no color option of its own.

tests.test_color.test_invocation_color_expires_with_a_plain_click_command(invoke)[source]

A plain click.command borrowing the color options must not leak either.

It has no ExtraCommand.main() to reset the mirror in a finally, so the cleanup rides the context finalizer. Click’s UsageError holds that context inside a reference cycle, so the collection is forced here rather than waited on: in a running program ordinary allocation churn reaches it on its own.

tests.test_color.test_a_stale_finalizer_does_not_clobber_a_live_invocation()[source]

The finalizer of a dead context must leave a newer invocation’s value alone.

Tying the reset to the context’s lifetime introduces this hazard: a context collected after the next invocation published would otherwise wipe a value that is still in use. The token comparison is what prevents it.

tests.test_command_doc module

tests.test_command_doc.test_render_manpage_header_and_sections()[source]
tests.test_command_doc.test_option_names_are_escaped_and_bold()[source]
tests.test_command_doc.test_choice_metavar_rendered()[source]
tests.test_command_doc.test_optional_value_metavar_attached()[source]
tests.test_command_doc.test_count_option_has_no_metavar()[source]
tests.test_command_doc.test_no_rewrap_marker_becomes_no_fill()[source]

Click’s \b marker must produce a roff .nf / .fi block (click-man #9).

tests.test_command_doc.test_no_rewrap_marker_does_not_leak_into_surrounding_paragraphs()[source]

A \b paragraph must not switch the whole DESCRIPTION to preformatted mode: prose before and after stays filled, only the marked paragraph lands between .nf / .fi.

tests.test_command_doc.test_name_line_keeps_full_short_help()[source]

The NAME .SH line carries the canonical description without Click’s 45-char terminal truncation.

tests.test_command_doc.test_inline_literal_in_short_help_renders_as_bold()[source]

Inline reST literals (..) in the first docstring line land on the NAME .SH line as \fB..\fR, not as raw backticks (which mandoc renders as quote characters).

tests.test_command_doc.test_inline_literal_in_description_renders_as_bold()[source]

Inline reST literals in the description body become \fB..\fR so mandoc’s HTML output shows them in bold rather than wrapped in Unicode quote characters.

tests.test_command_doc.test_boolean_flag_renders_both_spellings()[source]

--ascii / --no-ascii must both appear (click-man #41).

tests.test_command_doc.test_hidden_option_skipped()[source]
tests.test_command_doc.test_option_groups_become_subsections()[source]

An explicit option group renders as a roff .SS subsection of OPTIONS, with the ungrouped remainder gathered under Other options.

tests.test_command_doc.test_default_option_groups_close_the_options_section(subject, own_section)[source]

The sections of DEFAULT_OPTION_GROUPS trail a command’s own options.

They land past the ungrouped remainder, in the order the taxonomy declares, matching what --help draws.

tests.test_command_doc.test_ungrouped_command_has_no_subsections()[source]

A command carrying no option group at all keeps a flat OPTIONS list.

tests.test_command_doc.test_operand_documented()[source]
tests.test_command_doc.test_render_manpages_tree_filenames()[source]
tests.test_command_doc.test_subcommand_page_uses_full_path()[source]
tests.test_command_doc.test_hidden_command_skipped()[source]
tests.test_command_doc.test_dynamic_subcommand_discovered()[source]

Dynamically-resolved subcommands must be generated (click-man #14 / #56).

tests.test_command_doc.test_environment_section_deduplicated()[source]

A shared env var (--config / --no-config) appears only once.

tests.test_command_doc.test_files_section_from_config_option()[source]
tests.test_command_doc.test_version_and_authors_overrides()[source]
tests.test_command_doc.test_authors_omitted_without_metadata()[source]

No distribution matches the command name, so AUTHORS is dropped rather than synthesized from a fallback.

tests.test_command_doc.test_source_date_epoch_is_honored(monkeypatch)[source]
tests.test_command_doc.test_write_manpages(tmp_path)[source]
tests.test_command_doc.test_man_option_reads_the_manual()[source]

--man typesets the page for reading, the way man itself does.

tests.test_command_doc.test_man_option_falls_back_to_the_source(monkeypatch)[source]

With no typesetter installed, the source beats an error.

tests.test_command_doc.test_accessible_manual_drops_overstrike()[source]

Accessible mode strips the emphasis a screen reader would voice as noise.

tests.test_command_doc.test_generated_roff_passes_groff_lint(cli, prog_name)[source]

Every generated page must parse cleanly under groff (no warnings).

forecast exercises the .SS option-group subsections.

tests.test_command_doc.test_render_help_rejects_unknown_format()[source]

An unknown format names the ones that exist instead of failing blankly.

tests.test_command_doc.test_json_carries_every_section()[source]

The JSON document covers what the man page covers, as native types.

tests.test_command_doc.test_json_carries_choices_a_short_metavar_hides()[source]

A consumer reads the accepted values off choices, never off the metavar.

An option overriding the metavar with a short placeholder leaves nothing to parse in metavar, which is the whole reason the key is separate.

tests.test_command_doc.test_json_reports_no_choices_for_an_open_type()[source]

choices is None for a type that enumerates nothing.

tests.test_command_doc.test_a_short_metavar_lists_its_values_in_the_render(help_format)[source]

Shortening the metavar moves the values below the help, never drops them.

tests.test_command_doc.test_a_default_choice_metavar_does_not_repeat_its_values(help_format)[source]

A [a|b] metavar already shows them, so nothing is appended below it.

tests.test_command_doc.test_every_enumerated_default_option_reaches_the_render(help_format)[source]

No default option hides its accepted values behind a short metavar.

--table-format and --export-config both trade the [a|b|c] metavar for a one-word placeholder to keep the help screen readable. That is a help-screen decision, and it must not reach the man page or the Markdown render, which are what a reader consults precisely to learn what a value may be.

tests.test_command_doc.test_json_lists_subcommands_by_name_only()[source]

Progressive disclosure: children are named, never recursively expanded.

tests.test_command_doc.test_subcommand_aliases_reach_every_backend()[source]

A generated page names the short spelling the help screen advertises.

An alias is invocable, so a reader who only ever sees the man page or the JSON export would otherwise never learn it exists.

tests.test_command_doc.test_json_full_walks_the_whole_tree()[source]

The -full variant is the one that expands every command of the tree.

tests.test_command_doc.test_markdown_renders_every_section()[source]

The Markdown document carries the same sections as headings.

tests.test_command_doc.test_markdown_full_walks_the_whole_tree()[source]

Every command of the tree gets its own title in one document.

tests.test_command_doc.test_no_rewrap_marker_keeps_its_shape_in_markdown()[source]

A \b region is fenced rather than reflowed into a paragraph.

tests.test_command_doc.test_examples_render_in_every_backend()[source]

One examples= declaration feeds the help screen, roff, Markdown and JSON.

tests.test_command_doc.test_command_without_examples_grows_no_section()[source]

A command declaring none renders exactly as it did before the feature.

tests.test_command_doc.test_malformed_examples_fail_at_construction(malformed)[source]

A bad pair surfaces on import, not on the first –help a user runs.

tests.test_command_doc.test_dynamic_help_is_extracted()[source]

Options computing their help from the context are not left blank.

-v / -q leave Option.help at None and build their sentence in get_help_record(). Every backend here reads the extracted model, so the text has to be resolved once, at extraction.

tests.test_commands module

Test defaults of our custom commands, as well as their customizations and attached options, and how they interact with each others.

tests.test_commands.test_module_root_declarations()[source]

Verify click_extra.__all__ is a superset of click and cloup.

Sort order is enforced by ruff (RUF022).

tests.test_commands.test_public_namespace_integrity()[source]

The package namespace and __all__ agree: nothing missing or extra.

click ships no __all__, so its star import would otherwise leak every submodule bound by click’s __init__ (click.core, click.globals, …) into the package namespace, handing out click’s un-enhanced classes and shadowing the globals builtin. Those bindings are scrubbed at the end of click_extra/__init__.py: verify the scrub held, that every public binding is declared in __all__, and that every declared name resolves (eagerly, lazily, or as one of our own submodules).

tests.test_commands.test_no_debugger_ballast_on_import()[source]

Importing the package must not load the test tooling nor the debugger stack.

click_extra.testing imports click.testing, whose module-level import pdb cascades into asyncio and the _pyrepl machinery on Python 3.13+. That chain used to load eagerly with import click_extra, and ended up bundled into every Nuitka-compiled CLI binary. The test tooling is exported lazily now: check from a pristine interpreter that none of it leaks at import time.

tests.test_commands.test_lazy_test_tooling_exports()[source]

The lazy test-tooling names resolve, cache, and show up in dir().

tests.test_commands.all_command_cli()[source]

A CLI that is mixing all variations and flavors of subcommands.

tests.test_commands.test_unknown_option(invoke, all_command_cli)[source]
tests.test_commands.test_short_option_error_enhancement(invoke, cli_options, args, exit_code, expected_fragment)[source]

Command.parse_args improves error messages for single-dash multi-character tokens whose first character is not a registered short option. Vanilla Click would split -dbgwrong character by character and report “No such option: -d”; we re-raise with the full token and close-match suggestions instead.

The enhancement must not interfere with valid -abc-style combining or with the per-character diagnostic when a later character is unknown.

Upstream context: https://github.com/pallets/click/issues/2779

tests.test_commands.test_unknown_command(invoke, all_command_cli)[source]
tests.test_commands.test_required_command(invoke, all_command_cli, assert_output_regex)[source]
tests.test_commands.test_group_help(invoke, all_command_cli, param, exit_code, assert_output_regex)[source]
tests.test_commands.test_help_eagerness(invoke, all_command_cli, params, exit_code, expect_help, expect_empty_stderr, assert_output_regex)[source]

See: https://click.palletsprojects.com/en/stable/click-concepts/#callback-evaluation-order

tests.test_commands.test_help_custom_name(invoke)[source]

Removes the -h short option as we reserve it for a custom -h/--header option.

See: https://github.com/kdeldycke/mail-deduplicate/issues/762

tests.test_commands.test_subcommand_help(invoke, all_command_cli, cmd_id, param, assert_output_regex)[source]
tests.test_commands.test_subcommand_execution(invoke, all_command_cli, cmd_id)[source]
tests.test_commands.test_integrated_version_value(invoke, all_command_cli)[source]
tests.test_commands.test_colored_bare_help(invoke, cmd_decorator, param)[source]

Extra decorators are always colored.

Even when stripped of their default parameters, as reported in: https://github.com/kdeldycke/click-extra/issues/534 https://github.com/kdeldycke/click-extra/pull/543

tests.test_commands.test_duplicate_option(invoke)[source]

See: - https://kdeldycke.github.io/click-extra/commands.html#change-default-options - https://github.com/kdeldycke/click-extra/issues/232

tests.test_commands.test_no_option_leaks_between_subcommands(invoke, assert_output_regex)[source]

As reported in https://github.com/kdeldycke/click-extra/issues/489.

tests.test_commands.test_option_group_integration(invoke, assert_output_regex)[source]
tests.test_commands.test_show_envvar_parameter(invoke, cmd_decorator, ctx_settings, expected_help)[source]
tests.test_commands.test_show_choices_parameter(ctx_settings, expected)[source]

The show_choices context setting is forced on every option when set.

tests.test_commands.test_raw_args(invoke)[source]

Raw args are expected to be scoped in subcommands.

tests.test_commands.test_lazy_group(invoke, tmp_path, lazy_cmd_decorator, lazy_group_decorator)[source]

Test extends the snippet from Click documentation.

tests.test_commands.write_produce_modules(tmp_path)[source]

Write the command modules the sectioned lazy-group tests import.

tests.test_commands.test_lazy_group_sections(invoke, tmp_path)[source]

A LazySubcommand files its command under the section it declares.

tests.test_commands.test_lazy_group_section_shared_with_eager_subcommand(invoke, tmp_path)[source]

A Section can hold both eagerly and lazily registered subcommands.

tests.test_commands.test_lazy_group_no_default_section(invoke, tmp_path)[source]

fallback_to_default_section=False hides a subcommand but keeps it invocable.

tests.test_commands.test_lazy_subcommand_normalizes_bare_import_paths(tmp_path)[source]

A bare import path is normalized into a LazySubcommand.

tests.test_commands.test_decorator_overrides()[source]

Ensure our decorators are not just alias of Click and Cloup ones.

tests.test_commands.test_decorator_cls_parameter(klass, should_raise)[source]

Decorators accept custom cls parameters.

tests.test_commands.test_help_shows_group_help(invoke)[source]

mycli help produces the same output as mycli --help.

tests.test_commands.test_help_shows_subcommand_help(invoke)[source]

mycli help greet matches mycli greet --help.

tests.test_commands.test_help_nested_group(invoke)[source]

mycli help sub leaf resolves through nested groups.

tests.test_commands.test_help_nonexistent_subcommand(invoke)[source]

mycli help nosuch reports an error.

tests.test_commands.test_help_subcommand_of_non_group(invoke)[source]

mycli help leaf deeper errors when leaf is not a group.

tests.test_commands.test_help_disabled(invoke)[source]

help_command=False suppresses auto-injection.

tests.test_commands.test_help_user_override(invoke)[source]

User-defined help subcommand replaces the auto-injected one.

tests.test_commands.test_help_appears_in_listing(invoke)[source]

The help subcommand is visible in the group’s command list.

mycli help --search term finds matching subcommands.

tests.test_commands.test_help_search_no_match(invoke)[source]

mycli help --search term with no matches.

tests.test_commands.test_help_in_all_command_cli(invoke, all_command_cli)[source]

The help subcommand works on the fixture CLI.

tests.test_commands.test_help_for_subcommand_in_all_command_cli(invoke, all_command_cli)[source]

help default-subcommand works on the fixture CLI.

tests.test_commands.kitchen_group(**kwargs)[source]

A group whose subcommands are declared out of alphabetical order.

tests.test_commands.listed_subcommands(help_screen)[source]

Names listed under the Commands: heading of a help screen.

tests.test_commands.test_subcommand_order_in_help_screen(invoke, group_kwargs, expected)[source]

Cloup renders the help screen from sections, never from list_commands().

tests.test_commands.test_subcommand_order_in_list_commands(group_kwargs, expected)[source]

The flat listing feeding --tree, man pages, specs and completion.

tests.test_commands.test_subcommand_order_agrees_across_renderers(invoke)[source]

Every rendering of a command tree lists subcommands in the same order.

Each renderer used to reach for its own accessor, so a group that ordered its subcommands could have its help screen disagree with its man page or its completion spec. They all go through list_commands() now, and this pins that down for the whole population rather than one renderer at a time.

tests.test_commands.test_option_order_agrees_across_renderers(invoke)[source]

Every rendering of a command lists options in the same order.

Cloup draws the ungrouped section last, and Click Extra sends its own groups past it, so a renderer reaching for its own accessor would let a help screen disagree with its man page or its completion spec. They all resolve the section order through split_option_groups() now.

Pins the taxonomy too: each rendering is filtered down to the flags DEFAULT_OPTION_GROUPS declares, so the order they are declared in is the order a reader sees.

tests.test_commands.ARGUMENT_HELP_CLICK_VERSION = (8, 5)

Click release that gave click.Argument a help parameter of its own.

Below it a plain click.argument rejects the keyword outright, so the matrix cells pinning an older Click inside the supported range have nothing to render. Cloup and Click Extra carry their own Argument, which accepted a description all along and is checked on every cell.

tests.test_commands.test_argument_help_agrees_across_renderers(invoke, argument_decorator)[source]

An argument’s help reaches every rendering, whatever its Argument class.

Click 8.5.0 gave click.Argument a help parameter of its own. Cloup reads a description off a cloup.Argument alone, so a plain click.argument drew a blank Positional arguments entry while the man page and the JSON export carried its text. See https://github.com/janluke/cloup/issues/210.

tests.test_commands.test_deprecated_command_label_matches_click(invoke, deprecated, label)[source]

A deprecated command carries Click’s own marker, reason string included.

Cloup prefixes (Deprecated) to the description, a form Click left behind in 8.2.0 and which has nowhere to put the reason a deprecated string carries. See https://github.com/janluke/cloup/issues/211.

The marker is checked on the help screen and on full_short_help(), which feeds --tree, the man page, the JSON export and the completion specs.

tests.test_commands.test_every_default_option_lands_in_a_section()[source]

No option default_params() returns escapes DEFAULT_OPTION_GROUPS.

An option no section claims stays ungrouped, which draws it in the command’s own Options block where it reads as one the CLI author declared. Nothing else reports that: the help screen renders, and every cross-renderer check still agrees, because they all read the same wrong layout.

tests.test_commands.test_default_option_groups_name_no_stale_flag()[source]

Every flag DEFAULT_OPTION_GROUPS declares is one default_params() returns.

A renamed or dropped option otherwise leaves a dead entry behind. And a flag landing in two sections is silently taken by the later one, since _assign_option_groups writes them in order.

tests.test_commands.test_option_swapped_by_a_params_hook_keeps_its_section()[source]

An option a params hook replaces stays in the section of the original.

default_params files the instances it builds, so a replacement built after that call carries none, and cloup draws it in the command’s own Options block where it reads as one the CLI author declared. Each default option is swapped in turn, sections being declared one flag at a time: an option losing its own would pass unseen while its siblings still hold theirs.

tests.test_commands.test_lazy_group_subcommand_order_is_stable_across_loading(tmp_path, monkeypatch)[source]

A lazy subcommand holds its slot before and after it is imported.

Importing appends the command to self.commands, so registration order read off that dictionary alone would reshuffle mid-run.

tests.test_commands.test_lazy_group_defaults_to_alphabetical_order()[source]

Sorting moved from __init__ to listing time, with the same result.

tests.test_commands.test_option_priorities_leave_processing_order_alone(invoke)[source]

The help screen reorders while params and the callbacks do not.

click.core.iter_params_for_processing breaks eager-option ties on declaration order, which is why --time measures everything and --accessible lowers the --color default before it resolves. Reordering the help screen must not disturb any of that.

tests.test_commands.test_option_priorities_match_flags_then_destination()[source]

A flag pair sharing one destination stays addressable one flag at a time.

tests.test_commands.test_option_priorities_never_reorder_positional_arguments()[source]

Argument order is part of the command’s grammar, not of its presentation.

tests.test_commands.test_command_listing_is_not_cut_by_an_abbreviation()[source]

A subcommand’s line in its parent’s list holds its whole first sentence.

Click ends the listing at the first word closing on a period, so an abbreviation mid-sentence (vs., e.g., etc.) cuts it into a fragment: gradient once read “Render 24-bit RGB gradients vs.”, which says nothing.

The guard is narrower than “the listing reads well”: it only catches a cut landing before the first sentence ends, which is the one failure a docstring can cause without anyone noticing.

tests.test_commands.test_no_help_screen_leaks_a_no_rewrap_marker()[source]

A \b marker never reaches the rendered help of any command.

Click strips the marker only where it opens a paragraph, and paragraphs are split on blank lines. An epilog writing one between two indented blocks without a blank line around it therefore ships the raw backspace character: a terminal hides it, an HTML page does not.

tests.test_config module

tests.test_config.DOCS_CONFIG_PAGE = PosixPath('/home/runner/work/click-extra/click-extra/docs/config-discovery.md')

The documentation page transcribing part of ConfigFormat.

tests.test_config.FULL_SEARCH_FLAGS = 285504

All search flags ConfigOption forces on, used as the baseline in tests.

tests.test_config.NO_DOTGLOB_FLAGS = 285440

FULL_SEARCH_FLAGS minus DOTGLOB, to exercise the dotfile warnings.

tests.test_config.SQLITE_DATA = {'config-cli1': {'default': {'int_param': 3, 'random_stuff': 'will be ignored'}, 'dummy_flag': True, 'my_list': ['pip', 'npm', 'gem']}}

The shared reference configuration, as SQLITE_CONFIG_TABLE rows.

Keys are dotted parameter paths, values are JSON-encoded. This mirrors TOML_DATA, minus the verbosity bump and the sections the other formats use to exercise their own quirks.

tests.test_config.flatten_sqlite_keys(data, prefix='')[source]

Flatten a nested mapping into dotted keys, the SQLite config layout.

Return type:

dict

tests.test_config.make_sqlite_config(path, data=None, *, create_table=True)[source]

Write a nested mapping into a SQLite configuration database.

Skips the calling test on a Python whose SQLite bindings are missing, the same interpreter on which ConfigFormat.SQLITE reports itself disabled.

Return type:

Path

tests.test_config.simple_config_cli()[source]
tests.test_config.test_unset_conf(invoke, simple_config_cli)[source]
tests.test_config.test_unset_conf_debug_message(invoke, simple_config_cli, assert_output_regex)[source]
tests.test_config.test_conf_default_path(invoke, simple_config_cli)[source]
tests.test_config.test_conf_chosen_formats_displayed(invoke, file_format_patterns, expected_pattern)[source]

A format set chosen by the developer is displayed in full.

Only an inherited set collapses to its folder, so the help screen keeps showing the effect of file_format_patterns, which is what docs/config-discovery.md demonstrates.

tests.test_config.test_conf_show_file_patterns(invoke, kwargs, shows_patterns)[source]

show_file_patterns overrides the display in both directions.

tests.test_config.test_conf_default_pathlib_type(invoke, create_config)[source]

Refs https://github.com/kdeldycke/click-extra/issues/1356

tests.test_config.test_conf_not_found(invoke, simple_config_cli, conf_path)[source]
tests.test_config.test_conf_unparsable(invoke, simple_config_cli, create_config)[source]

Explicit –config pointing to a file with garbage content.

tests.test_config.test_conf_empty_file(invoke, simple_config_cli, create_config)[source]

Explicit –config pointing to an empty file.

tests.test_config.test_no_config_option(invoke, simple_config_cli, create_config)[source]
tests.test_config.test_standalone_no_config_option(invoke)[source]

@no_config_option cannot work without @config_option.

tests.test_config.test_strict_conf(invoke, create_config, conf_text, expect_error)[source]

Strict mode rejects unknown params but accepts clean configs.

tests.test_config.test_kebab_case_keys(invoke, create_config)[source]

Kebab-case config keys reach the snake_case-named CLI parameters.

tests.test_config.test_kebab_case_spelling_collision(invoke, create_config)[source]

Both spellings of the same key: last one wins, a warning names both.

tests.test_config.test_strict_conf_ignores_foreign_sections(invoke, create_config)[source]

Other tools’ sections in a shared config file do not trip strict mode.

tests.test_config.test_command_forwards_config_strict(invoke, create_config)[source]

@command(config_strict=True) activates strict mode on the default option.

tests.test_config.test_command_excluded_params_additive(invoke, create_config)[source]

@command(excluded_params=…) extends the default blocklist.

The forwarded exclusion applies on top of the built-in ones, and a blocked parameter found in a config file is reported as blocked, not unknown.

tests.test_config.test_export_config_includes_unset_params(invoke)[source]

Parameters without a default are exported instead of silently dropped.

TOML has no null type so unset parameters are commented out; multi-value parameters read as empty lists; JSON renders unset parameters as null.

tests.test_config.test_export_config_reads_a_repeatable_flag_as_a_toggle(invoke)[source]

A repeatable boolean flag exports as the boolean a configuration spells.

It collects occurrences of a toggle rather than values, so dumping an unset one as the empty list of a multi-value parameter advertises a shape no user writes. A repeatable option taking values still reads as a list.

tests.test_config.test_export_config_skips_an_opaque_subtree(invoke)[source]

A subcommand sharing a name with an opaque schema field is not exported.

The loader hands the whole sub-tree to the app’s own validator, so the subcommand’s options cannot be read back from there. Exporting them writes a file the same loader refuses.

tests.test_config.test_export_config_kebab_case_keys(invoke, tmp_path)[source]

Exported keys use the kebab-case spelling, the canonical form for files.

Either spelling loads back to the same parameter, so the kebab-cased export still round-trips through –config.

tests.test_config.test_introspection_flags_load_config_first(invoke, create_config)[source]

–params and –export-config reflect the config file regardless of the order in which Click processes the eager options.

Click processes eager parameters given on the command line ahead of eager parameters left at their defaults, so these flags used to render before the configuration file was discovered and loaded.

tests.test_config.test_conf_file_overrides_defaults(invoke, simple_config_cli, create_config, httpserver, conf_name, conf_text, conf_data, assert_output_regex)[source]
tests.test_config.test_auto_envvar_conf(invoke, simple_config_cli, create_config, httpserver, conf_name, conf_text, conf_data)[source]
tests.test_config.test_conf_file_overridden_by_cli_param(invoke, simple_config_cli, create_config, httpserver, conf_name, conf_text, conf_data)[source]
tests.test_config.test_conf_metadata(invoke, create_config, httpserver, conf_name, conf_text, conf_data)[source]
tests.test_config.test_conf_metadata_no_config(invoke)[source]

ctx.meta entries are not set when –no-config skips loading.

tests.test_config.test_format_from_mime(media_type, expected)[source]
tests.test_config.test_format_from_mime_restricted_to_candidates()[source]

formats narrows the resolution, like format_from_path does.

tests.test_config.test_jwcc_resolves_to_the_json5_parser(tmp_path)[source]

A *.jwcc file is read by the JSON5 parser, which is a superset of it.

tests.test_config.test_jwcc_conf(invoke, simple_config_cli, tmp_path)[source]

A JWCC document loads: JSON plus comments and trailing commas.

tests.test_config.test_conf_key_reaches_a_case_preserving_param_name(invoke, create_config)[source]

A parameter whose name kept its case is still addressed by that case.

Click takes an identifier declaration verbatim, so this parameter is named Explicit_Name. No fold produces that spelling, so the template has to stay the authority on it.

tests.test_config.test_conf_key_case_folds_onto_the_param_name(invoke, create_config, spelling)[source]

Every spelling Click could have derived foo_bar from reaches it.

tests.test_config.test_conf_key_folding_onto_two_params_is_skipped(invoke, create_config, caplog)[source]

A key folding onto two parameter names picks neither, and warns.

Click allows foo_bar and Foo_Bar on one command, and nothing in the folded spelling says which was meant.

tests.test_config.test_strict_conf_accepts_a_folded_key(invoke, create_config)[source]

Strict mode no longer rejects a spelling the fold resolves.

tests.test_config.test_mime_types_are_unambiguous()[source]

No media type is claimed by two formats.

A duplicate would leave format_from_mime resolving on ConfigFormat declaration order alone, silently handing the media type to whichever format happens to be declared first.

tests.test_config.test_docs_media_types_table_matches_formats()[source]

The media-type table in the docs is ConfigFormat.mime_types, transcribed.

A format gaining or losing a media type otherwise leaves the table stale, and that table is the only place a user reads the mapping from.

tests.test_config.test_remote_conf_typed_by_content_type(invoke, simple_config_cli, httpserver, media_type, conf_text)[source]

An extension-less URL is typed by the media type its server advertises.

tests.test_config.test_remote_conf_falls_back_to_the_url_name(invoke, simple_config_cli, httpserver, media_type)[source]

A generic or plain wrong media type still leaves the URL name to match on.

tests.test_config.test_remote_conf_content_type_never_widens_the_format_set(invoke, httpserver, file_format_patterns, exit_code, stdout)[source]

A media type is resolved against file_format_patterns alone.

The same application/json download feeds the CLI when JSON is accepted, and is rejected outright when the option only declares TOML.

tests.test_config.test_unparsable_conf_message_enumerates_formats(invoke, create_config, caplog, file_format_patterns, expected)[source]

The “error parsing” message never dangles its conjunction.

tests.test_config.test_argfile_conf_file_overrides_defaults(invoke, simple_config_cli, create_config, assert_output_regex)[source]

An argfile feeds CLI tokens into the same default_map pipeline.

tests.test_config.test_argfile_conf_metadata(invoke, create_config)[source]
tests.test_config.test_argfile_cli_overrides_conf(invoke, create_config)[source]

Command-line parameters take precedence over argfile values.

tests.test_config.test_argfile_secondary_flag_and_inline_value(invoke, create_config)[source]
tests.test_config.test_argfile_strict_conf(invoke, create_config, conf_text, expected_key)[source]

Strict mode rejects unknown options with the standard error.

An unmatched declaration is named the way Click names a parameter it derives from one, case fold included, so --Unknown-Option is reported as unknown_option.

tests.test_config.test_argfile_unknown_option_ignored_when_not_strict(invoke, create_config)[source]
tests.test_config.test_argfile_unparsable_conf(invoke, create_config, conf_text)[source]

An argfile that produces no option is skipped like any other format.

tests.test_config.test_argfile_positional_tokens_skipped(invoke, create_config)[source]
tests.test_config.test_argfile_export_config_rejected(invoke)[source]

Argfile has no serializer, so –export-config rejects it.

tests.test_config.test_sqlite_conf_file_overrides_defaults(invoke, simple_config_cli, tmp_path, ext)[source]
tests.test_config.test_sqlite_conf_metadata(invoke, tmp_path)[source]
tests.test_config.test_sqlite_read_and_parse_conf(tmp_path)[source]

The default format patterns discover SQLite databases by extension.

tests.test_config.test_sqlite_conf_unparsable(invoke, simple_config_cli, tmp_path, make_db)[source]

A SQLite file that cannot yield a configuration is rejected.

tests.test_config.SPLITTABLE_STDLIB_MODULES = frozenset({'curses', 'dbm', 'readline', 'sqlite3', 'tkinter'})

Standard library modules a distribution can ship apart from its base Python.

Each wraps a system library, so a packager can leave it out of the interpreter: FreeBSD serves sqlite3 as a separate pyXXX-sqlite3 package, and Debian serves tkinter as python3-tk. A module-level import of any of them kills every CLI built on click_extra at import time, on an interpreter that is otherwise complete, so each one is probed and imported at its point of use.

tests.test_config.eager_imports(tree)[source]

Top-level packages a module imports as soon as it is loaded.

Skips function bodies, which import at call time, and try blocks, which guard against a missing module. Those are the two shapes that survive an interpreter without the module.

Return type:

set[str]

tests.test_config.test_no_splittable_stdlib_module_imported_at_load_time()[source]

No module of the package imports a splittable module unconditionally.

tests.test_config.test_sqlite3_not_imported_by_the_package()[source]

Importing the package leaves sqlite3 out of sys.modules.

Covers the whole import graph, dependencies included, where test_no_splittable_stdlib_module_imported_at_load_time only reads the package’s own sources.

tests.test_config.test_sqlite_support_gates_the_format()[source]

SQLITE is enabled exactly when the standard library ships its bindings.

tests.test_config.test_plist_conf_file_overrides_defaults(invoke, simple_config_cli, assert_output_regex, tmp_path, plist_variant)[source]

Both the XML and the binary plist variants load through –config.

tests.test_config.test_plist_read_and_parse_conf(tmp_path)[source]

The default format patterns discover plist files by extension.

tests.test_config.test_validate_config_sqlite_valid(invoke, tmp_path)[source]

–validate-config accepts a valid SQLite configuration database.

tests.test_config.test_default_map_populated(invoke, create_config)[source]

Verify default_map structure when config values match CLI parameters.

Complements test_conf_metadata which only checks the empty default_map case (where no config values match the CLI’s parameter structure).

tests.test_config.test_merge_default_map_standalone(invoke)[source]

merge_default_map filters a config into default_map on its own.

load_conf bypasses this method by installing the merged config the validation pipeline already produced, so it is exercised here directly to cover the standalone entry point external callers rely on.

tests.test_config.test_default_map_none_without_config(invoke)[source]

Verify default_map is left alone when –no-config is used.

tests.test_config.test_nested_subcommand_config(invoke, create_config)[source]

Config propagates through group -> subgroup -> leaf command.

tests.test_config.test_multiple_cli_shared_conf(invoke, create_config)[source]

Two CLIs sharing the same configuration file.

Refs: https://github.com/kdeldycke/click-extra/issues/1277

tests.test_config.test_params_template_not_mutated_across_invocations(invoke, create_config)[source]

Back-to-back invocations of the same CLI must not cross-contaminate via ConfigOption.params_template.

params_template is a @cached_property of the ConfigOption instance bound at decoration time, so it lives for the lifetime of the CLI object. _merge_into_template mutates its first argument in place; without a defensive copy, the cached template would accumulate values from earlier --config loads and leak them into default_map on subsequent invocations.

tests.test_config.test_lazy_group_config(invoke, create_config, tmp_path)[source]

Test that lazy groups work with config files.

Refs: https://github.com/kdeldycke/click-extra/issues/1332

tests.test_config.test_lazy_group_config_no_config_flag(invoke, create_config, tmp_path)[source]

Test that –no-config works with lazy groups.

tests.test_config.test_file_pattern(file_format_patterns, expected_pattern)[source]

Test the file_pattern property with different file format configurations.

tests.test_config.test_default_pattern_roaming_force_posix(roaming, force_posix, current_platform, expected_path, monkeypatch)[source]

Test that roaming and force_posix affect the default pattern generation.

tests.test_config.test_default_pattern_xdg_config_home(force_posix, tmp_path, monkeypatch)[source]

Test that default_pattern respects XDG_CONFIG_HOME on Linux.

tests.test_config.test_parent_patterns(tmp_path, search_parents, subdirs, create_file, expected_start)[source]
tests.test_config.test_parent_patterns_with_magic_pattern(tmp_path, pattern_factory, expected_factory)[source]

Test parent_patterns with glob patterns containing magic characters.

Magic pattern with search_parents=False yields only the original.

tests.test_config.test_parent_patterns_relative_path(tmp_path)[source]

Test parent_patterns resolves relative paths to absolute.

tests.test_config.test_parent_patterns_stop_at_path(tmp_path)[source]

stop_at as a path limits the parent directory walk.

tests.test_config.test_parent_patterns_stop_at_vcs(tmp_path, has_vcs, expected_bounded)[source]

stop_at=VCS stops at VCS root, or walks to filesystem root if none.

tests.test_config.test_parent_patterns_inaccessible_directory(tmp_path)[source]

Walk stops at an inaccessible directory.

tests.test_config.cascade_tree(tmp_path, monkeypatch)[source]

Point auto-discovery at a temporary app dir nested inside tmp_path.

Returns (tmp_path, app_dir): a config file dropped in app_dir is the most local source, one in tmp_path sits one level up the parent walk.

tests.test_config.test_cascade_merges_files_local_wins(invoke, cascade_tree)[source]

With cascade=True, local values win and parent values fill the gaps.

tests.test_config.test_no_cascade_first_file_wins(invoke, cascade_tree)[source]

Without cascade, the first parseable file wins entirely.

tests.test_config.test_cascade_single_layer_from_parent(invoke, cascade_tree)[source]

A lone file found up the walk is applied as-is.

tests.test_config.test_cascade_explicit_config_does_not_cascade(invoke, cascade_tree)[source]

An explicit –config pins a single source, even with cascade=True.

tests.test_config.test_cascade_conf_sources_metadata(invoke, cascade_tree)[source]

ctx.meta[CONF_SOURCES] lists every loaded file, highest precedence first.

tests.test_config.test_cascade_conf_full_is_merged_view(invoke, cascade_tree)[source]

ctx.meta[CONF_FULL] exposes the deep-merged document.

tests.test_config.test_cascade_validation_error_names_file(invoke, cascade_tree, caplog)[source]

A strict-check failure in one layer names that file and exits 1.

tests.test_config.test_read_and_parse_all_conf_orders_local_first(tmp_path)[source]

All parseable files are yielded, deepest first; unparsable ones skip.

tests.test_config.test_read_and_parse_conf_returns_first(tmp_path)[source]

read_and_parse_conf keeps its first-match contract on top of the generator.

tests.test_config.test_find_vcs_root(tmp_path, vcs_dir, expected)[source]

Test _find_vcs_root with .git, .hg, and no VCS markers.

tests.test_config.test_config_option_default_no_config(invoke, create_config)[source]

ConfigOption with default=NO_CONFIG disables autodiscovery.

tests.test_config.test_no_config_explicit_with_default_no_config(invoke)[source]

–no-config still prints the skip message even when NO_CONFIG is the default.

tests.test_config.test_excluded_params(invoke, create_config)[source]

Custom excluded_params prevents config values from being applied.

tests.test_config.test_included_params(invoke, create_config)[source]

Only parameters in included_params are loaded from config.

tests.test_config.test_included_params_empty(invoke, create_config)[source]

An empty included_params excludes all params from config.

tests.test_config.test_included_and_excluded_params_conflict()[source]

Providing both included_params and excluded_params raises ValueError.

tests.test_config.test_multiple_files_matching_glob(invoke, create_config, tmp_path)[source]

When multiple files match a glob, only the first parseable one is used.

tests.test_config.test_forced_flags_warnings(caplog)[source]

Warnings fire when SPLIT, BRACE or NODIR flags are missing.

All format extensions are found in the search directory.

Regression test: before BRACE expansion, only the first format in the default pattern got the directory prefix: others were searched in CWD.

tests.test_config.test_root_dir_parent_search_finds_non_toml(invoke, tmp_path)[source]

Parent search with root_dir correctly finds non-TOML config in parents.

Before the root_dir refactoring, SPLIT patterns like *.toml|*.yaml only applied the directory prefix to the first sub-pattern. Now with root_dir, all sub-patterns are scoped to the correct directory.

tests.test_config.test_no_enabled_formats_raises()[source]

ValueError raised when all formats are disabled.

tests.test_config.test_pyproject_toml_in_defaults()[source]

ConfigOption() with default file_format_patterns includes PYPROJECT_TOML.

tests.test_config.test_pyproject_toml_tool_extraction(simple_config_cli)[source]

parse_conf with PYPROJECT_TOML returns the [tool] subsection.

tests.test_config.test_pyproject_toml_no_tool_section(simple_config_cli)[source]

pyproject.toml without [tool] returns empty dict.

tests.test_config.test_file_pattern_with_pyproject_toml()[source]

Explicit file_format_patterns with PYPROJECT_TOML works.

tests.test_config.test_pyproject_toml_overrides_defaults(invoke, create_config)[source]

End-to-end: a CLI with default formats reads from pyproject.toml.

tests.test_config.test_validate_config_valid(invoke, create_config)[source]

–validate-config with a valid config file exits 0.

tests.test_config.test_validate_config_accepts_a_glob(invoke, create_config)[source]

–validate-config takes every location --config takes, glob included.

tests.test_config.test_validate_config_accepts_a_url(invoke, httpserver)[source]

A configuration a CLI can load from a URL is one it can also validate.

tests.test_config.test_validate_config_invalid_keys(invoke, create_config)[source]

–validate-config with unrecognized keys exits 1.

tests.test_config.test_extensionless_config(invoke, create_config, default_pattern, expected_help_default)[source]

Both broad and exact default patterns resolve the same .commandrc file.

The default parameter is printed as-is on the help screen, so an exact path is more informative than a broad glob, but both locate the same file.

tests.test_config.test_validate_config_unparsable(invoke, create_config)[source]

–validate-config with garbage content exits 2.

tests.test_config.test_validate_config_missing_file(invoke, tmp_path)[source]

–validate-config reports a nonexistent location from its own callback.

tests.test_config.test_validate_config_requires_config_option(invoke, tmp_path)[source]

–validate-config without @config_option raises RuntimeError.

tests.test_config.test_validate_config_pyproject_toml(invoke, create_config)[source]

–validate-config works with pyproject.toml [tool.*] sections.

tests.test_config.test_export_config_to_stdout(invoke)[source]

–export-config writes the resolved configuration and exits 0.

tests.test_config.test_export_config_captures_overrides(invoke)[source]

Command-line values are reflected in the dump.

tests.test_config.test_export_config_captures_environment(invoke)[source]

Values resolved from environment variables are reflected in the dump.

tests.test_config.test_export_config_numeric_values_keep_their_type(invoke)[source]

A command-line numeric scalar is dumped as a number, not a quoted string.

tests.test_config.test_export_config_round_trip(invoke, tmp_path, fmt)[source]

A dumped configuration reloads to the same values through –config.

tests.test_config.test_export_config_invalid_format(invoke)[source]

An unsupported format token is rejected by the Choice.

tests.test_config.test_export_config_requires_config_option(invoke)[source]

–export-config without @config_option raises RuntimeError.

tests.test_config.test_export_config_standalone_falls_back_to_defaults(invoke)[source]

Without a captured command line (vanilla Command), defaults are dumped.

tests.test_config.test_default_subcommand_selection(invoke, create_config, cli_subcmd, expected, unexpected)[source]

Config default is used when no subcommand given; CLI wins otherwise.

tests.test_config.test_default_subcommand_chained(invoke, create_config)[source]

chain=True group runs multiple config-listed subcommands in order.

tests.test_config.test_default_subcommand_config_errors(invoke, create_config, conf_value, error_fragment)[source]

Bad _default_subcommands values produce clear errors.

tests.test_config.test_default_subcommand_strict_mode_tolerance(invoke, create_config)[source]

strict=True config with _default_subcommands doesn’t raise.

tests.test_config.test_default_subcommand_validate_config_tolerance(invoke, create_config)[source]

–validate-config with _default_subcommands reports valid.

tests.test_config.test_default_subcommand_with_options(invoke, create_config)[source]

Default subcommand receives its config-provided options.

tests.test_config.test_default_subcommand_no_config(invoke)[source]

Normal behavior when no config file is loaded.

tests.test_config.test_default_subcommand_duplicates_warning(invoke, create_config)[source]

Duplicate entries in _default_subcommands are deduplicated with a warning.

tests.test_config.test_default_subcommand_cli_override_debug_log(invoke, create_config)[source]

Debug log emitted when CLI subcommands override config defaults.

tests.test_config.test_prepend_subcommand_selection(invoke, create_config, cli_subcmd, expected, unexpected)[source]

Prepend fires regardless of whether a CLI subcommand is given.

tests.test_config.test_prepend_subcommand_with_defaults(invoke, create_config, cli_subcmd, expect_backup)[source]

Prepend always applies; defaults only fire when no CLI subcommand given.

tests.test_config.test_prepend_subcommand_non_chained_error(invoke, create_config)[source]

Error on non-chained group.

tests.test_config.test_prepend_subcommand_config_errors(invoke, create_config, conf_value, error_fragment)[source]

Bad _prepend_subcommands values produce clear errors.

tests.test_config.test_prepend_subcommand_strict_mode_tolerance(invoke, create_config)[source]

strict=True config with _prepend_subcommands doesn’t raise.

tests.test_config.test_prepend_subcommand_validate_config_tolerance(invoke, create_config)[source]

–validate-config with _prepend_subcommands reports valid.

tests.test_config.test_prepend_subcommand_duplicates_warning(invoke, create_config)[source]

Duplicate entries in _prepend_subcommands are deduplicated with a warning.

tests.test_config.test_prepend_subcommand_info_log(invoke, create_config)[source]

INFO log emitted when _prepend_subcommands are injected.

tests.test_config.test_prepend_subcommand_multiple(invoke, create_config)[source]

Multiple prepend subcommands run in order.

tests.test_config.test_sanity_checks(caplog, default, file_format_patterns, flags, present, absent)[source]

_check_pattern_sanity emits (or suppresses) debug logs per pattern config.

tests.test_config.test_expand_dotted_keys(input_conf, expected)[source]
tests.test_config.test_dotted_keys_in_config(invoke, simple_config_cli, create_config, conf_name, conf_text)[source]

Dotted keys in config files are expanded into nested structures.

tests.test_config.test_expand_dotted_keys_conflict_warning(caplog, input_conf, warning_fragment)[source]

Scalar/dict conflicts on the same key emit a warning.

tests.test_config.test_expand_dotted_keys_empty_segments(caplog, input_conf)[source]

Dotted keys with empty segments are skipped with a warning.

tests.test_config.test_expand_dotted_keys_edge_cases(input_conf, expected)[source]
tests.test_config.test_expand_dotted_keys_strict_conflict(input_conf, error_fragment)[source]

Strict mode raises ValueError on type conflicts.

tests.test_config.test_expand_dotted_keys_strict_empty_segments(input_conf)[source]

Strict mode raises ValueError on dotted keys with empty segments.

tests.test_config.test_strict_conf_dotted_key_conflict(invoke, create_config)[source]

Strict mode rejects configs with dotted-key type conflicts.

tests.test_config_schema module

Tests for typed configuration schemas, validation, and extension points.

tests.test_config_schema.test_normalize_config_keys()[source]
tests.test_config_schema.test_config_schema_dataclass(invoke, create_config)[source]

Dataclass schemas are auto-detected and instantiated with normalized keys.

tests.test_config_schema.test_config_schema_cascade(invoke, tmp_path, monkeypatch)[source]

With cascade=True, the schema is built from the merged view.

tests.test_config_schema.test_config_schema_callable(invoke, create_config)[source]

A plain callable can be used as config_schema.

tests.test_config_schema.test_config_schema_no_config_file(invoke)[source]

When no config file is found, schema defaults are used.

tests.test_config_schema.test_config_schema_dataclass_defaults(invoke, create_config)[source]

Dataclass defaults are used for fields not present in the config file.

tests.test_config_schema.test_fallback_sections(invoke, create_config)[source]

Legacy section names are recognized with a deprecation warning.

tests.test_config_schema.test_fallback_sections_prefers_current(invoke, create_config)[source]

When both current and legacy sections exist, current wins.

tests.test_config_schema.test_config_schema_multiple_formats(invoke, create_config, conf_name, conf_text)[source]

Config schema works with YAML and JSON, not just TOML.

tests.test_config_schema.test_config_schema_on_config_option_directly(invoke, create_config)[source]

Config schema can be set directly on ConfigOption via the decorator.

tests.test_config_schema.test_get_tool_config_defaults_to_current_context(invoke, create_config)[source]

get_tool_config() works without passing ctx explicitly.

tests.test_config_schema.test_flatten_config_keys()[source]
tests.test_config_schema.test_flatten_config_keys_with_normalize()[source]

flatten + normalize maps nested kebab-case config to flat snake_case fields.

tests.test_config_schema.test_config_schema_nested_toml(invoke, create_config)[source]

Nested TOML sub-tables map to flat dataclass fields via flattening.

tests.test_config_schema.test_config_schema_strict_rejects_unknown(invoke, create_config)[source]

schema_strict=True raises ValueError on unrecognized config keys.

tests.test_config_schema.test_config_schema_strict_passes_when_valid(invoke, create_config)[source]

schema_strict=True does not raise when all config keys are known.

tests.test_config_schema.test_config_schema_strict_with_nested(invoke, create_config)[source]

schema_strict=True validates flattened keys from nested sub-tables.

tests.test_config_schema.test_config_schema_lax_warns_unknown_when_schema_only(invoke, create_config)[source]

A schema-only section warns on unknown keys instead of dropping them silently.

included_params=() means no CLI parameter is merged from the app’s section, so any key the schema does not know can only be a typo: lax mode then logs a warning while still loading the known fields.

tests.test_config_schema.test_config_schema_lax_silent_when_params_merged(invoke, create_config)[source]

Without included_params=(), lax mode stays silent on unknown keys.

The section may legitimately mix CLI parameter keys with schema fields, so a key unknown to the schema is not necessarily a typo.

tests.test_config_schema.test_make_schema_callable_warn_unknown(caplog)[source]

warn_unknown=True logs unknown keys, recursing into nested dataclasses.

tests.test_config_schema.test_make_schema_callable_lax_default_is_silent(caplog)[source]

Without warn_unknown, lax mode keeps dropping unknown keys silently.

tests.test_config_schema.test_pyproject_toml_cwd_discovery(invoke, tmp_path, monkeypatch)[source]

pyproject.toml in CWD is discovered automatically without –config.

tests.test_config_schema.test_pyproject_toml_cwd_discovery_walks_up(invoke, tmp_path, monkeypatch)[source]

pyproject.toml discovery walks up from CWD to parent directories.

tests.test_config_schema.test_pyproject_toml_explicit_config_skips_cwd(invoke, create_config, tmp_path, monkeypatch)[source]

Explicit –config skips CWD pyproject.toml discovery.

tests.test_config_schema.test_pyproject_toml_cwd_skips_unrelated_tool_section(invoke, tmp_path, monkeypatch)[source]

A pyproject.toml without [tool.<cli_name>] is skipped.

Regression: a pyproject.toml carrying only unrelated [tool.X] sections (like a dotfiles repo’s [tool.ruff]) used to shadow the user’s app-dir config. It must now be ignored so the CLI falls back to its defaults instead of inheriting an unrelated project’s settings.

tests.test_config_schema.test_pyproject_toml_cwd_walks_past_unrelated_tool_section(invoke, tmp_path, monkeypatch)[source]

CWD walk continues past a pyproject.toml lacking [tool.<cli_name>].

A nearer pyproject.toml that only carries unrelated [tool.X] sections must not stop the upward walk: a parent pyproject.toml with a matching [tool.<cli_name>] section should still be discovered.

tests.test_config_schema.test_pyproject_toml_cwd_mixed_tool_sections(invoke, tmp_path, monkeypatch)[source]

[tool.<cli_name>] is picked from a pyproject.toml that also has others.

tests.test_config_schema.test_pyproject_toml_cwd_unrelated_does_not_shadow_app_dir(invoke, tmp_path, monkeypatch)[source]

An unrelated pyproject.toml falls through to the app-dir config.

Directly exercises the documented intent of the fix: when CWD contains a pyproject.toml whose only [tool.X] sections are unrelated to the CLI, the walk must give up so the standard app-dir search can find the user’s actual config and apply it.

tests.test_config_schema.test_flatten_config_keys_opaque()[source]

opaque_keys stops flattening at matching key boundaries.

tests.test_config_schema.test_flatten_config_keys_opaque_nested()[source]

opaque_keys works at deeper nesting levels.

tests.test_config_schema.test_schema_type_aware_flattening(invoke, create_config)[source]

dict-typed dataclass fields stop flattening automatically.

tests.test_config_schema.test_schema_field_metadata_config_path(invoke, create_config)[source]

click_extra.config_path extracts a value at a dotted TOML path.

tests.test_config_schema.test_schema_field_metadata_normalize_keys_true(invoke, create_config)[source]

click_extra.normalize_keys defaults to True: keys are normalized.

tests.test_config_schema.test_schema_nested_dataclass(invoke, create_config)[source]

Nested dataclass fields are recursively instantiated.

tests.test_config_schema.test_schema_nested_dataclass_with_opaque_fields(invoke, create_config)[source]

Nested dataclass with dict-typed fields preserves opaque keys.

tests.test_config_schema.test_schema_nested_dataclass_defaults(invoke, create_config)[source]

Nested dataclass uses defaults when config section is absent.

tests.test_config_schema.test_strict_skips_opaque_dict_field(invoke, create_config)[source]

Strict mode does not reject keys inside a dict[str, X] schema field.

A field typed as dict[str, dict] is user-controlled: the keys are data, not CLI flag names. Click-extra strips that sub-tree before running its unknown-key check so app extensions don’t trip strict mode.

tests.test_config_schema.test_strict_skips_opaque_metadata_field(invoke, create_config)[source]

Strict mode also honors the EXTENSION_METADATA_KEY marker on a field whose Python type is not a mapping.

tests.test_config_schema.test_validate_config_skips_opaque_field(invoke, create_config)[source]

–validate-config also skips opaque sub-trees, so a config that the runtime accepts also passes validation.

tests.test_config_schema.test_config_validator_runs_and_fails_under_validate_config(invoke, create_config)[source]

A registered ConfigValidator runs during --validate-config and surfaces its ValidationError with a path rooted at the config file.

tests.test_config_schema.test_config_validator_runs_during_normal_load(invoke, create_config)[source]

A misconfigured opaque sub-tree fails fast during normal config loading, not only under --validate-config.

tests.test_config_schema.test_config_validator_extension_path_strips_strict_check(invoke, create_config)[source]

A ConfigValidator(extension_path=…) registration alone is enough to skip strict-check on that path, even when the schema doesn’t have the field.

tests.test_config_schema.test_config_validator_collects_all_errors(invoke, create_config)[source]

--validate-config reports every detected error in one pass.

A config with both an unknown CLI flag key and a validator-flagged field should surface both messages before the run exits non-zero, so the user sees the full punch list.

tests.test_config_schema.test_collect_opaque_paths_from_schema()[source]

Schema introspection picks up dict-typed fields, metadata-marked fields, and nested-dataclass opaque fields with dotted prefixes.

tests.test_config_schema.test_schema_strict_honors_extension_metadata_on_non_mapping_field(invoke, create_config)[source]

schema_strict must not descend into an EXTENSION_METADATA_KEY-marked field whose Python type is not a mapping.

Before the opaque-path unification, the outer strip honored the marker but the dataclass adapter’s own flatten boundary inspected only the type hint, so the marked sub-tree was flattened into dotted keys and rejected as unknown.

tests.test_config_schema.test_run_config_validation_valid_document()[source]

A clean document yields an ok report with the schema instance built and every opaque sub-tree extracted.

tests.test_config_schema.test_run_config_validation_exposes_merged_conf()[source]

A passing strict check carries the template-filtered config as merged_conf, with recognized values merged in and unknown keys dropped.

tests.test_config_schema.test_run_config_validation_collects_all_then_short_circuits()[source]

collect_all=True gathers errors from every stage in order; collect_all=False stops after the first.

tests.test_config_schema.test_run_config_validation_wraps_schema_errors()[source]

A schema_strict failure is recorded as a ValidationError with the schema_error code, and the message is preserved verbatim.

tests.test_config_schema.test_run_config_validation_no_schema_no_template()[source]

With neither a template nor a schema, the report is ok and carries no schema instance.

tests.test_config_schema.test_make_schema_callable_coerces_dict_to_dataclass()[source]

The public make_schema_callable turns a raw config dict into a dataclass.

tests.test_config_schema.test_field_docstrings_returns_full_text()[source]

Attribute docstrings are recovered whole, paragraph breaks preserved.

tests.test_config_schema.test_field_docstrings_degrades_without_source()[source]

A class defined through exec has no source: the mapping is empty.

tests.test_config_schema.test_schema_field_infos_flat_schema()[source]

Keys are kebab-cased and sorted; defaults, types, and summaries surface.

tests.test_config_schema.test_schema_field_infos_nested_and_config_path()[source]

Nested dataclasses expand to dotted keys; config_path metadata wins.

tests.test_config_schema.test_schema_field_infos_sorts_segment_wise()[source]

A sub-table’s options stay contiguous when a sibling shares their prefix.

Plain string sort would interleave pear-cellar between pear.crates and pear.pickers (in ASCII - sorts before .); segment-wise sort keeps the pear table’s options together.

tests.test_config_schema.test_schema_field_infos_rejects_non_dataclass()[source]

A non-dataclass schema is refused with a clear error.

tests.test_context module

Tests for click_extra.context.

Covers four surfaces the module exposes:

  • The registry of ctx.meta key constants.

  • The get() / set() helpers that read and write them.

  • Context, Click Extra’s cloup.Context subclass.

  • _LazyMetaDict, the lazy ctx._meta proxy used by VersionOption.

tests.test_context.KEY_CONSTANTS: tuple[tuple[str, str], ...] = (('RAW_ARGS', 'click_extra.raw_args'), ('INVOCATION_NAME', 'click_extra.invocation_name'), ('CONF_SOURCE', 'click_extra.conf_source'), ('CONF_FULL', 'click_extra.conf_full'), ('CONF_SOURCES', 'click_extra.conf_sources'), ('TOOL_CONFIG', 'click_extra.tool_config'), ('VERBOSITY_LEVEL', 'click_extra.verbosity_level'), ('VERBOSITY', 'click_extra.verbosity'), ('DEBUG', 'click_extra.debug'), ('VERBOSE', 'click_extra.verbose'), ('QUIET', 'click_extra.quiet'), ('START_TIME', 'click_extra.start_time'), ('JOBS', 'click_extra.jobs'), ('TABLE_FORMAT', 'click_extra.table_format'), ('SORT_BY', 'click_extra.sort_by'), ('TABLE_SORT_KEY', 'click_extra.table_sort_key'), ('COLUMNS', 'click_extra.columns'), ('THEME', 'click_extra.theme.active'), ('THEME_OVERRIDES', 'click_extra.theme.overrides'), ('TELEMETRY', 'click_extra.telemetry'), ('PROGRESS', 'click_extra.progress'), ('ACCESSIBLE', 'click_extra.accessible'), ('ZERO_EXIT', 'click_extra.zero_exit'))

Pairs of (attribute name, raw string key) for every registered ctx.meta entry. Single source of truth used by every parametrized registry test below.

tests.test_context.test_key_constant_value(attr, expected)[source]

Each registered constant binds to its documented string value.

Pins the spelling of every key so a rename of the literal does not silently break downstream code that reads ctx.meta["click_extra.X"].

Return type:

None

tests.test_context.test_key_uses_namespace_prefix(key)[source]

Every registered key sits under META_NAMESPACE.

Return type:

None

tests.test_context.test_keys_are_unique()[source]

No two registry constants share a string value.

Return type:

None

tests.test_context.test_registry_covers_all_module_constants()[source]

KEY_CONSTANTS lists every public namespace-prefixed constant.

Drift detector: when a new ctx.meta key is added to click_extra.context but not to KEY_CONSTANTS, this test fails so the maintainer remembers to update both sides.

Return type:

None

tests.test_context.test_get_returns_default_for_missing_key()[source]

context.get() mirrors dict.get semantics.

Return type:

None

tests.test_context.test_set_then_get_round_trip()[source]

context.set() is observable through context.get().

Return type:

None

tests.test_context.test_context_uses_help_formatter()[source]

Context installs Click Extra’s colorized formatter.

Return type:

None

tests.test_context.test_context_meta_kwarg_seeds_ctx_meta()[source]

The meta= kwarg populates ctx.meta at construction time.

Return type:

None

tests.test_context.test_context_meta_kwarg_omitted_leaves_meta_empty()[source]

Without meta=, ctx.meta is an empty dict, not None.

Return type:

None

tests.test_context.test_context_color(monkeypatch, parent_color, child_color, expected)[source]

Context color resolution covers every parent/child path.

Root contexts without an explicit color= resolve the GNU auto default: with no color environment variable they stay at None (TTY detection). Child contexts inherit the parent’s color unless they override it explicitly.

Return type:

None

tests.test_context.test_context_posixly_correct(monkeypatch, env_present, expected)[source]

POSIXLY_CORRECT flips allow_interspersed_args off when present.

A plain click.Command defaults the flag to True, so the unset case leaves interspersing enabled and the set case disables it.

Return type:

None

tests.test_context.test_posixly_correct_presence_overrides_explicit_true(monkeypatch)[source]

An empty POSIXLY_CORRECT still wins over an explicit True.

Presence alone triggers POSIX parsing (matching GNU getopt), regardless of value, and it takes precedence over a developer-supplied allow_interspersed_args=True.

Return type:

None

tests.test_context.test_posixly_correct_stops_option_parsing_at_first_argument()[source]

End-to-end: parsing stops at the first positional under POSIXLY_CORRECT.

Without the variable, the option interleaves with arguments (GNU style). With it set, the first positional ends option parsing, so the option keeps its default and the remaining tokens fall into the variadic argument.

Return type:

None

tests.test_context.test_lazy_meta_dict_resolves_on_first_access()[source]

Reading a lazy key triggers exactly one source attribute access.

Return type:

None

tests.test_context.test_lazy_meta_dict_caches_after_resolution()[source]

Subsequent reads of the same lazy key do not re-hit the source.

Return type:

None

tests.test_context.test_lazy_meta_dict_get_resolves_lazy_key()[source]

.get(lazy_key) triggers resolution like __getitem__.

Return type:

None

tests.test_context.test_lazy_meta_dict_get_returns_default_for_unknown_key()[source]

.get(unknown, default) returns the default without touching source.

Return type:

None

tests.test_context.test_lazy_meta_dict_contains_includes_lazy_keys_without_resolving()[source]

in returns True for declared lazy keys before any resolution.

Return type:

None

tests.test_context.test_lazy_meta_dict_preserves_base_entries()[source]

Entries from the wrapped base dict remain accessible.

Return type:

None

tests.test_context.test_lazy_meta_dict_independent_keys_resolve_independently()[source]

Resolving one lazy key does not resolve sibling lazy keys.

Return type:

None

tests.test_context.test_pass_context_typed_for_enhanced_context(invoke)[source]

@pass_context accepts a handler typed with the enhanced Context.

Click’s own pass_context is typed for the base click.Context, so annotating the handler with click-extra’s Context (to reach its extra helpers) would fail static type checks by parameter contravariance. This must both type-check (mypy covers this file) and forward the active enhanced Context at runtime.

tests.test_envvar module

tests.test_envvar.test_merge_envvar_ids(envvars, result)[source]
tests.test_envvar.test_clean_envvar_id(env_name, clean_name)[source]
tests.test_envvar.test_show_auto_envvar_help(invoke, cmd_decorator, option_help)[source]

Check that the auto-generated envvar appears in the help screen with the extra variants.

Checks that https://github.com/pallets/click/issues/2483 is addressed.

tests.test_envvar.envvars_test_cases()[source]
tests.test_envvar.test_auto_envvar_parsing(invoke, cmd_decorator, envvars, expected_flag)[source]

This test highlights the way Click recognize and parse envvars.

It shows that the default behavior is not ideal, and covers how command improves the situation by normalizing the envvar name.

tests.test_envvar.test_empty_envvar_falls_back_to_the_default(invoke, cmd_decorator, default)[source]

An empty variable is read as unset, so the flag keeps its own default.

Pins the rule the "" row of envvars_test_cases() relies on: Parameter.resolve_envvar_value guards its lookup with a bare if rv, so an empty value never reaches the flag’s type. The False that row expects is the option’s default showing through, which is why flipping the default flips the outcome with it.

tests.test_envvar.test_env_copy()[source]
tests.test_envvar.test_env_copy_removes_on_none(monkeypatch)[source]

A None value drops its variable, the way Click’s CliRunner reads it.

The only way to hide an inherited variable from a child: assigning the empty string leaves it set, which parse_envvar_flag() counts as activation.

tests.test_execution module

Tests for the execution-control options (–jobs, –time, -0/–zero-exit) and the subprocess-execution primitives (run_cli and the interrupt machinery).

tests.test_execution.test_standalone_jobs_option(invoke, cmd_decorator, option_decorator)[source]
tests.test_execution.test_default_value(invoke)[source]

Default reserves one core, except on hosts with fewer than three CPUs.

tests.test_execution.test_keyword_resolution(invoke, keyword, expected)[source]

‘auto’ resolves to the reserved-core default, ‘max’ to all logical CPUs.

On a host with at least two logical CPUs the resolution is silent; on a single-CPU host the keyword collapses to a single (sequential) job with a warning, so the assertion adapts to the host running the suite.

tests.test_execution.test_parallel_keyword_collapses_to_sequential_warns(invoke, keyword, cpu_count, default_jobs, cpu_phrase)[source]

‘auto’/’max’ warn when too few logical CPUs force a single (sequential) job.

tests.test_execution.test_explicit_single_job_is_silent(invoke)[source]

An explicit ‘–jobs 1’ is a deliberate sequential choice: no warning.

tests.test_execution.test_default_collapse_to_sequential_is_quiet(invoke)[source]

The bare default (‘auto’) collapsing to a single job does not warn.

The user never asked for parallelism: warning on the option’s own default would fire on every bare invocation on a 1-CPU host, polluting captured runner streams and the CLI output rendered in Sphinx docs.

tests.test_execution.test_default_collapse_to_sequential_logged_at_info(invoke)[source]

The default’s collapse to a single job stays discoverable at info level.

This is the silent trap on a single-CPU host: no flag is passed, yet execution runs sequentially. The trace lives at info level, next to the resolved-jobs line, instead of a default-verbosity warning.

tests.test_execution.test_resolved_job_count_logged_at_info(invoke)[source]

The resolved job count and os.cpu_count() are logged at info level.

tests.test_execution.test_run_jobs_preserves_order(jobs)[source]

Results come back in submission order, sequential or parallel.

tests.test_execution.test_run_jobs_sequential_is_lazy()[source]

With one worker, items run lazily so a caller can stop early.

tests.test_execution.test_run_jobs_preserves_order_past_its_window(jobs)[source]

Order holds when the stream is longer than the in-flight window.

tests.test_execution.test_run_jobs_handles_empty_and_single_streams(jobs)[source]

The peek that sizes the run does not lose the items it looked at.

tests.test_execution.test_run_jobs_parallel_reads_no_further_than_its_window()[source]

The parallel path pulls a bounded window instead of draining the stream.

tests.test_execution.test_run_jobs_parallel_stops_scheduling_on_early_exit()[source]

Breaking out of a parallel run leaves the rest of the stream unread.

tests.test_execution.test_run_jobs_reads_jobs_from_context(invoke)[source]

Without an explicit count, run_jobs reads the resolved –jobs value.

tests.test_execution.test_run_jobs_without_context_runs_sequential()[source]

Outside any Click context and with no count, run_jobs falls back to 1.

tests.test_execution.test_run_jobs_interrupt_aborts_without_blocking()[source]

A KeyboardInterrupt returns at once, without waiting on in-flight tasks.

Results yield in submission order, so the interrupting item (index 0) is pulled first: the abort fires while a second, still-running item is parked on an event that stays unset for the run. The old with-block teardown would shutdown(wait=True) and hang on that parked task; the hardened path drops queued work and returns immediately.

tests.test_execution.test_resolve_jobs_without_context_is_sequential()[source]

No context means nothing to read a job count from: stay sequential.

tests.test_execution.test_resolve_jobs_single_item_is_sequential()[source]

A single item has nothing to parallelize.

tests.test_execution.test_resolve_jobs_reads_context(jobs, count, expected)[source]

The resolved –jobs value drives the count, capped at the item count.

tests.test_execution.test_resolve_jobs_serial_at_debug()[source]

serial_at_debug collapses to sequential only at DEBUG verbosity.

tests.test_execution.test_run_lanes_preserves_order(jobs)[source]

Results come back in lane-submission order, items within a lane in order.

tests.test_execution.test_run_lanes_preserves_order_past_its_window(jobs)[source]

Lane order holds when there are more lanes than the in-flight window.

tests.test_execution.test_run_lanes_materializes_lanes_lazily()[source]

A lane becomes a list only when it is about to be scheduled.

tests.test_execution.test_run_lanes_is_run_jobs_with_singleton_lanes()[source]

run_jobs is the degenerate case of run_lanes: one item per lane.

tests.test_execution.test_run_lanes_serializes_within_a_lane()[source]

Within a lane, items run one at a time even when lanes run in parallel.

tests.test_execution.test_run_lanes_runs_lanes_concurrently()[source]

Distinct lanes overlap: a barrier only releases if all lanes run at once.

tests.test_execution.test_run_lanes_sequential_is_lazy()[source]

With one worker, items run lazily so a caller can stop early.

tests.test_execution.test_run_lanes_reads_jobs_from_context(invoke)[source]

Without an explicit count, run_lanes reads the resolved –jobs value.

tests.test_execution.test_run_lanes_without_context_runs_sequential()[source]

Outside any Click context and with no count, run_lanes falls back to 1.

tests.test_execution.test_run_lanes_empty_yields_nothing()[source]

No lanes, or only empty lanes, yields nothing and raises nothing.

tests.test_execution.test_run_lanes_interrupt_aborts_without_blocking()[source]

A KeyboardInterrupt returns at once, without waiting on in-flight lanes.

Mirror of test_run_jobs_interrupt_aborts_without_blocking(): see it for the rationale.

tests.test_execution.test_invalid_value(invoke)[source]

Values that are neither an integer nor a known keyword are rejected.

tests.test_execution.test_jobs_shell_complete(incomplete, expected)[source]

–jobs completion suggests the auto/max keywords and never an integer.

tests.test_execution.test_clamp_to_one(invoke, value, warning)[source]

0 disables parallelism and negatives clamp: both run 1 job with a warning.

tests.test_execution.test_exceeds_cpu_count(invoke)[source]

A count above the core count is honored, with an I/O-bound caveat.

tests.test_execution.test_no_warning_within_bounds(invoke)[source]

No warning when the value is within the valid range.

tests.test_execution.test_single_core_default()[source]

DEFAULT_JOBS is 1 when the logical CPU count is 1.

tests.test_execution.test_two_core_default_uses_both_cpus()[source]

DEFAULT_JOBS drops the core reservation on a two-CPU host.

Reserving one of two logical CPUs would collapse the pool to a single (sequential) worker, and threads waiting on subprocesses and I/O cost nothing there, so the whole machine is used instead.

tests.test_execution.test_none_cpu_count_default()[source]

DEFAULT_JOBS is 1 when cpu_count returns None.

tests.test_execution.test_logical_cpu_count_prefers_process_count(process_count, fallback, expected)[source]

os.process_cpu_count() is preferred, os.cpu_count() is the fallback.

tests.test_execution.test_logical_cpu_count_fallback_without_process_count()[source]

On runtimes lacking os.process_cpu_count(), os.cpu_count() answers.

tests.test_execution.test_integrated_time_option(invoke, subcommand_id, time_min)[source]
tests.test_execution.test_integrated_notime_option(invoke, subcommand_id)[source]
tests.test_execution.test_standalone_timer_option(invoke, cmd_decorator, option_decorator, assert_output_regex)[source]
tests.test_execution.test_time_with_short_circuit_sibling_still_prints(invoke)[source]

--time --version still emits a duration.

--version is an eager option that calls ctx.exit() before the user command body runs, but --time is intentionally measured even on short-circuit paths so it can probe the cost of Click Extra’s own machinery (eager callbacks, config loading, option parsing).

tests.test_execution.test_standalone_zero_exit_option(invoke, cmd_decorator, option_decorator)[source]
tests.test_execution.test_zero_exit_auto_envvar(invoke)[source]
tests.test_execution.test_run_cli_returns_completed_process(caplog)[source]

run_cli mirrors subprocess.run’s result shape, with separate streams.

tests.test_execution.test_run_cli_cwd_moves_the_child(tmp_path)[source]

cwd runs the child elsewhere; omitted, it inherits the caller’s.

tests.test_execution.test_run_cli_flattens_nested_args(caplog)[source]

Nested iterables are flattened, None dropped, and elements stringified.

tests.test_execution.test_run_cli_discloses_command_at_info(caplog)[source]

The invocation is logged up front at INFO, with its forced env vars, and the output stays out of the INFO records.

tests.test_execution.test_run_cli_command_level_override(caplog)[source]

A caller can lower the disclosure line to DEBUG for internal probes.

tests.test_execution.test_run_cli_streams_output_at_debug_with_label(caplog)[source]

Every output line is forwarded to the logger, tagged with the label.

The tag rides the record’s label attribute, not the message text: the default click_extra.logging.Formatter renders it glued to the level name (debug:probe: line1).

tests.test_execution.test_highlight_bin_name()[source]

Only the binary’s own name is styled; its directory stays plain, whichever separator convention the path uses.

tests.test_execution.test_format_cli_prompt_styles_token_families()[source]

Each token family gets the theme slot it holds elsewhere in a CLI’s output: dim sigil, envvar/default assignment pairs, the binary name as an invoked command (directory plain), option-styled flags, plain arguments.

tests.test_execution.test_format_cli_prompt_arguments_survive_a_shell_round_trip(argv)[source]

A drawn command line parses back to the arguments that produced it.

The line advertises itself as copy-pasteable, so every argument carrying a space or a shell metacharacter has to reach the shell as the single argument it started as, instead of spilling into the line as several.

tests.test_execution.test_format_cli_prompt_environment_values_survive_a_shell_round_trip()[source]

An assignment prefixing the command is quoted like an argument.

A value holding a space ends the assignment early otherwise, and the rest of it reads as the command to run.

tests.test_execution.test_format_cli_prompt_honors_an_explicit_theme()[source]

A caller drawing the line onto a surface of its own picks the palette.

Every slot follows, the binary name included: that one is styled a level down, in highlight_bin_name(), which used to read the active theme on its own and leave a light capture’s prompt in the dark theme’s near-white.

tests.test_execution.test_run_cli_merged_streams()[source]

merge_streams interleaves stderr into stdout and nulls the stderr field.

tests.test_execution.test_run_cli_timeout_kills_child_and_attaches_partial_output()[source]

An overrun raises TimeoutExpired carrying what was captured so far, and leaves no zombie in the live registry.

tests.test_execution.test_run_cli_default_shares_process_group()[source]

By default the child stays in the caller’s process group: it keeps the controlling terminal (an interactive sudo raised from inside the child must be able to prompt on /dev/tty) and receives the terminal’s signals with the rest of the foreground group.

tests.test_execution.test_run_cli_new_session_makes_child_group_leader()[source]

With start_new_session the child leads its own session and process group, whose ID is its own PID: the property every group-kill path relies on.

tests.test_execution.test_run_cli_timeout_new_session_kills_grandchildren()[source]

A timed-out start_new_session child takes its whole process group down: the grandchild is reaped along with it instead of surviving as an orphan holding the inherited output pipe open.

tests.test_execution.test_run_cli_registers_live_process_then_discards_it()[source]

run_cli tracks its subprocess while it runs, and drops it once done.

A background call parks in a real subprocess. Once it is registered, terminate_live_processes() unblocks it, and run_cli’s finally clears the registry: this is the exact path the SIGINT handler drives on Ctrl+C.

tests.test_execution.test_terminate_live_processes_signals_whole_group(tmp_path)[source]

Interrupting a start_new_session child reaps its grandchild too: the group never received the terminal’s SIGINT (it left the foreground group), so terminate_live_processes() is its only kill path and must cover the descendants.

The child reports its grandchild’s PID through a sentinel file, polled before tearing down, so the group is never signalled mid-spawn. The sentinel is published by rename: a bare open(..., "w") creates the file before the write is flushed, so polling for existence could tear the group down between creation and flush, leaving an empty file behind for int() to choke on.

tests.test_execution.test_terminate_live_processes_ignores_already_reaped()[source]

A process gone between snapshot and signal is skipped, not raised on.

tests.test_execution.test_install_interrupt_handler_terminates_children_and_reraises()[source]

The installed SIGINT handler SIGTERMs live children, then raises to abort.

tests.test_execution.test_install_interrupt_handler_restored_on_context_close()[source]

Closing the context restores the handler in place before the install.

tests.test_execution.test_install_interrupt_handler_skips_off_main_thread()[source]

signal.signal() only works in the main thread: off-thread install is a no-op.

tests.test_highlight module

tests.test_highlight.test_theme_definition()[source]

Ensure we do not leave any property we would have inherited from cloup and logging primitives.

tests.test_highlight.test_extra_theme()[source]
class tests.test_highlight.HashType(*values)[source]

Bases: Enum

MD5 = 1
SHA1 = 2
BCRYPT = 3
class tests.test_highlight.Priority(*values)[source]

Bases: Enum

LOW = 'low-priority'
HIGH = 'high-priority'
class tests.test_highlight.Port(*values)[source]

Bases: IntEnum

HTTP = 80
HTTPS = 443
tests.test_highlight.test_option_highlight(opt, expected_outputs)[source]

Test highlighting of all option variations: types, defaults, ranges, envvars, choices, flags, metavars, deprecated messages.

tests.test_highlight.test_extra_deprecated_markers_are_painted()[source]

A CLI’s own marker takes the deprecated slot, beside Click’s spelling.

DEPRECATED_RE is the word deprecated and nothing else, so a project marking a parameter in its own vocabulary gets no color for it until it declares the marker.

tests.test_highlight.test_extra_deprecated_marker_overlapping_click_is_painted_once()[source]

A declared marker Click also writes is styled by one pass, not two.

tests.test_highlight.test_deprecation_marker_stays_painted_around_a_keyword(category, keyword)[source]

A keyword quoted in a deprecation reason keeps the marker painted.

Cross-reference passes run over text the deprecation pass already painted, and the style each one applies closes with a reset. Unless the surrounding style is re-opened after it, the marker renders plain from the keyword to its closing parenthesis. One case per keyword category: the passes run in sequence, so a hole one of them punches says nothing about the others.

tests.test_highlight.test_bracket_field_full_combination_styling()[source]

Document the runtime styling of a fully-loaded bracket field.

Covers the canonical [env var: NAME; default: VAL; required] block that combines all three trailing-field labels. The bracket slot styles only the structural tokens: the outer [ / ], the env var: `` / ``default: `` labels, and the ``; `` separators, while the value tokens (``NAME, VAL) and the required label get their own slot styling layered on top. This is the contract referenced in bracket’s docstring.

tests.test_highlight.test_range_field_does_not_leak_into_following_choice()[source]

A range field must not bleed its bracket styling into the next option.

Regression guard: the [x>=1] field emitted for an IntRange(min=1) option is immediately followed by a Choice option whose [apple|mango] metavar supplies a closing ]. When the range bound matched \S+ it absorbed the field’s own ], letting the bracket regex run past the field and dim everything up to the choice metavar’s ]. Assert the range field is self-contained and closed, and the following choice keeps its own styling.

tests.test_highlight.test_bracket_field_inner_slot_fallback_to_bracket()[source]

Inner bracket-field slots fall back to bracket when at identity.

Contract: a theme that styles only bracket and leaves envvar / default / required / range_label at identity should still render the whole bracket field with the bracket styling: value tokens inherit it rather than rendering plain. Specific inner slots override piecemeal.

tests.test_highlight.test_bracket_field_inner_slot_override_takes_precedence()[source]

When a theme sets one inner slot, only that token uses it.

Complements test_bracket_field_inner_slot_fallback_to_bracket(): a theme that styles bracket + only required (leaving envvar and default at identity) should render the required label with the dedicated style and fall back to bracket for the envvar and default values.

tests.test_highlight.test_skip_hidden_option()[source]

Ensure hidden options are not highlighted.

tests.test_highlight.test_cross_ref_highlight_disabled()[source]

When cross_ref_highlight is False, only structural elements are styled (bracket fields, deprecated messages, subcommands, choice metavars). Options, choices in free-form text, metavars, arguments, and CLI names are left plain.

tests.test_highlight.test_choice_does_not_override_default_style()[source]

Choice cross-ref highlighting must not restyle text inside bracket fields.

When a default value contains a substring that matches a choice keyword (like outline from rounded-outline), the choice style must not override the default value style. Regression test for the case where line-wrapping splits a hyphenated default so the second word starts a new line and passes the lookbehind.

tests.test_highlight.test_choice_collection_case(params, expected, forbidden)[source]

Choice keywords must use the original-case strings from the type definition, not the normalized (lowercased) forms produced by normalize_choice().

tests.test_highlight.test_argument_highlight(params, expected, forbidden)[source]

Argument metavars get the argument style, distinct from option metavars.

tests.test_highlight.test_no_false_positive_highlight(params, help_text, expected_present, expected_absent)[source]

Verify that highlighting does not leak into compound words, URLs, dotted names, already-styled regions, or partial-word matches.

tests.test_highlight.test_parent_keywords_highlighted_in_subcommand_help()[source]

Parent group names, options, and choices must be highlighted in subcommand help text.

tests.test_highlight.test_parent_choice_case_with_custom_metavar()[source]

Parent choices with custom metavar must use original-case strings in subcommand help, not normalized (lowercased) forms.

tests.test_highlight.test_command_aliases_collected()[source]

Command aliases are collected as keywords for highlighting.

tests.test_highlight.test_command_aliases_highlighted(invoke)[source]

Aliases inside parenthetical groups take the alias slot, and the parentheses and separators the alias_secondary slot.

tests.test_highlight.test_single_alias_highlighted(invoke)[source]

A command with exactly one alias still gets highlighted.

tests.test_highlight.test_alias_no_false_positive_in_description(invoke)[source]

An alias name appearing in a description must not be highlighted when it does not sit inside alias parentheses.

tests.test_highlight.test_alias_substring_not_highlighted(invoke)[source]

An alias that is a substring of the subcommand name must not cause double-highlighting or partial matches.

tests.test_highlight.test_help_keywords_merge(base_kwargs, other_kwargs, checks)[source]

HelpKeywords.merge() unions every field.

tests.test_highlight.test_help_keywords_subtract(base_kwargs, removals_kwargs, checks)[source]

HelpKeywords.subtract() removes matching entries per field.

tests.test_highlight.test_extra_keywords_merged()[source]

extra_keywords injects additional strings into the collected set.

tests.test_highlight.test_excluded_keywords_preserved_in_collection()[source]

excluded_keywords does not remove from collect_keywords().

Exclusion is deferred to highlight_extra_keywords() so that choice metavars can be styled with the full choices set before the excluded choices are removed for cross-ref passes.

tests.test_highlight.test_excluded_keywords_via_constructor()[source]

excluded_keywords can be passed through the Command constructor.

tests.test_highlight.test_excluded_keywords_suppresses_highlighting()[source]

Excluded keywords do not appear styled in the rendered help text.

tests.test_highlight.test_style_choice_metavar(metavar, choices, expected)[source]

_style_choice_metavar styles known choices inside bracket-delimited metavar strings, styles non-choice parts (type placeholders) as metavars, and returns None for non-bracket strings.

tests.test_highlight.test_multiple_choice_options_metavar_styled()[source]

Each choice option gets its metavar individually styled.

tests.test_highlight.test_jobs_hybrid_metavar_highlights_keywords_and_placeholder()[source]

The hybrid --jobs [auto|max|INTEGER] metavar highlights every part.

Regression guard: JobCount is a custom ParamType, not a click.Choice, so the keyword collector learns its auto/max keywords only through the choices attribute it exposes. With no other option contributing these tokens (a bare JobsOption command pulls in no --color), auto/max can only be styled via JobCount, and the INTEGER placeholder renders as a metavar rather than staying plain.

tests.test_highlight.test_excluded_multiple_choices_styled_in_metavar_only()[source]

Multiple excluded choices appear styled in their own metavar but not in free-text descriptions.

tests.test_highlight.test_excluded_keywords_inheritance(parent_excluded, child_excluded, word, expect_styled)[source]

excluded_keywords propagate from parent groups to subcommands.

Parent choices are collected for subcommand help screens (cross-ref highlighting). The parent’s excluded_keywords must follow, otherwise excluded choices bleed into subcommand descriptions.

tests.test_highlight.test_excluded_keywords_grandparent_propagation()[source]

excluded_keywords propagate through multiple nesting levels.

tests.test_highlight.test_excluded_keywords_plain_click_group_parent()[source]

A plain click.Group parent without excluded_keywords does not crash.

tests.test_highlight.test_excluded_keywords_not_mutated()[source]

Calling format_help must not mutate the command’s excluded_keywords.

tests.test_highlight.test_keyword_collection(invoke, assert_output_regex)[source]
tests.test_highlight.test_substring_highlighting(content, patterns, expected, ignore_case)[source]
tests.test_highlight.test_standalone_help_option(invoke, cmd_decorator, cmd_type, option_decorator)[source]
tests.test_highlight.test_invoked_subcommand_is_highlighted_and_prose_is_not()[source]

An example line running the CLI paints the subcommands it names.

The same word in the prose describing that subcommand stays unpainted: a subcommand often carries the name of what it does, and reading a whole help screen for it colors plain English.

tests.test_highlight.test_subcommand_options_are_highlighted_on_the_group_screen()[source]

A group screen knows the options of the subcommands it lists.

An example invoking one names options the group itself never declares.

tests.test_highlight.test_end_of_options_separator_is_styled()[source]

A lone -- takes the separator slot; an option name keeps its own.

tests.test_highlight.test_bracket_field_closes_on_a_bracketed_default()[source]

A default value that is itself bracketed closes its own field.

The content pattern used to stop at the first ] on screen, which left the field’s real closing bracket outside the styled run.

tests.test_highlight.test_enumerated_metavar_is_styled_part_by_part()[source]

A hand-written metavar that enumerates its parts is painted as one.

Click renders nothing structured for a hybrid type, so INTEGER|auto is a plain string: the value takes the choice slot and the type placeholder beside it the metavar slot.

tests.test_logging module

tests.test_logging.test_level_default_order()[source]
tests.test_logging.test_root_logger_defaults()[source]

Check our internal default is aligned to Python’s root logger.

tests.test_logging.test_integrated_verbosity_options(invoke, cmd_decorator, cmd_type, args, expected_level, assert_output_regex)[source]
tests.test_logging.test_custom_verbosity_option_name(invoke, args, assert_output_regex)[source]
tests.test_logging.test_custom_verbose_option_name(invoke, args, assert_output_regex)[source]
tests.test_logging.test_custom_quiet_option_name(invoke, args)[source]
tests.test_logging.test_unrecognized_verbosity_level(invoke, cmd_decorator, cmd_type)[source]
tests.test_logging.test_standalone_option_default_logger(invoke, cmd_decorator, option_decorator, args, expected_level, assert_output_regex)[source]

Checks: - option affect log level - the default logger is root - the default logger message format - level names are colored - log level is propagated to all other loggers

tests.test_logging.test_default_logger_param(invoke, logger_param, params)[source]

Passing a logger instance or name to the default_logger parameter works.

tests.test_logging.test_new_logger_name_passing(invoke)[source]

Test extra logger with custom format, passed to the option by its name.

tests.test_logging.test_new_logger_object_passing(invoke)[source]

Test extra logger with custom format, passed as an object to the option.

tests.test_logging.restored_root_logger()[source]

Put the root logger back the way the test found it.

new_logger() naming no logger reconfigures the process-wide root one through basicConfig(force=True), which drops whatever handlers were already attached. Nothing in logging undoes that, so a test doing it once decides how every later test in the same worker renders: leaving the handler behind is what made test_logger_propagation fail whenever this module’s root-logger test ran before it.

tests.test_logging.test_new_logger_root_config(invoke, restored_root_logger)[source]

Modify the root logger via new_logger()

tests.test_logging.test_logger_propagation(invoke)[source]
tests.test_logging.test_stream_handler_honors_no_color_from_background_threads(capsys, monkeypatch)[source]

A record emitted with no reachable Click context (as from run_cli’s stream reader threads) still honors –no-color, through the process-wide mirror published by the color options.

tests.test_logging.test_formatter_renders_label_glued_to_level_name()[source]

A record tagged with a label attribute (as run_cli’s streamed output lines are) renders it inside the level prefix: debug:mas: message.

tests.test_logging.test_stream_handler_routes_through_active_spinner(capsys)[source]

A record emitted while a spinner animates on the same stream is printed through Spinner.echo(), on its own line, instead of over the frame.

tests.test_parameters module

tests.test_parameters.CLICK_HELP_PARAM_NAME = '_click_default_help'

Click’s internal .name for its built-in --help option.

Detected at runtime instead of hardcoded as "help": Click’s development branch renamed it to "_click_default_help", an internal identifier click-extra does not control and a version comparison alone cannot reliably predict.

class tests.test_parameters.Custom[source]

Bases: ParamType

A dummy custom type.

name: str = 'Custom'

the descriptive name of this type

convert(value, param, ctx)[source]

Convert the value to the correct type. This is not called if the value is None (the missing value).

This must accept string values from the command line, as well as values that are already the correct type. It may also convert other compatible types.

The param and ctx arguments may be None in certain situations, such as when converting prompt input.

If the value cannot be converted, call fail() with a descriptive message.

Parameters:
  • value – The value to convert.

  • param – The parameter that is using this type to convert its value. May be None.

  • ctx – The current context that arrived at this value. May be None.

tests.test_parameters.test_canonical_param_name_matches_click(decl)[source]

The fold answers what Click names a parameter declared that way.

Click derives the name in Option._parse_decls, which splits the prefix then applies this same fold. Pinning the two together is what keeps the helper honest when Click moves.

tests.test_parameters.test_canonical_param_name_is_many_to_one()[source]

The fold identifies a name, and cannot reconstruct a spelling.

tests.test_parameters.test_canonical_param_name_never_answers_an_identifier_decl()[source]

Click takes an identifier declaration verbatim, so no fold produces it.

This is why a resolved spelling has to come back from the names a CLI declares, rather than from what the fold returns.

tests.test_parameters.test_factory_decorators_expose_option_signature()[source]

Factory-built option decorators expose their option class’s real signature.

Guards the signature propagation in click_extra.decorators.decorator_factory(), which lets editors, help() and Sphinx see the actual parameters instead of an opaque (*args, **kwargs).

tests.test_parameters.test_params_auto_types(invoke, option_decorator)[source]

Check parameters types and structure are properly derived from CLI.

tests.test_parameters.assert_table_content(output, expected_table, table_format=None)[source]

Helper to assert the content of a rendered table in the output.

Return type:

None

tests.test_parameters.test_standalone_show_params_option(invoke, cmd_decorator, option_decorator, assert_output_regex)[source]
tests.test_parameters.test_integrated_show_params_option(invoke, create_config)[source]
tests.test_parameters.test_show_params_table_format_ordering(invoke, args_order)[source]

--params respects --table-format regardless of CLI order.

tests.test_parameters.test_show_params_native_types(invoke, table_format)[source]

Serialization formats emit native types instead of styled glyphs.

tests.test_parameters.test_show_params_no_default_renders_none(invoke)[source]

A parameter with no default renders as None, not the UNSET sentinel.

Regression guard for Click 8.4’s UNSET sentinel leaking into the value and default columns through the --params re-parse path. See the RAW_ARGS dossier in click_extra.context.

tests.test_parameters.test_show_params_config_file_cascade(invoke, tmp_path, monkeypatch)[source]

The config_file column names the cascade layer a value resolved from.

The local file wins the root-level parameter; the parent file owns the [sub] section the local file does not define. A parameter overridden on the command line keeps an empty column: the file did not supply the effective value.

tests.test_parameters.test_show_params_config_file_single_file(invoke, create_config)[source]

A single loaded file is attributed to every config-sourced parameter.

tests.test_parameters.test_show_params_config_file_column_is_opt_in(invoke)[source]

config_file is addressable by ID but stays out of the default table.

tests.test_parameters.test_show_params_subclass_widens_the_default_columns(invoke)[source]

A subclass widening default_columns() draws the column it added.

That hook is the only way to reach an opt-in column on a CLI exposing no --columns option of its own, or one whose --columns belongs to its subcommands and carries a different vocabulary.

tests.test_parameters.test_column_registry_is_consistent()[source]

TABLE_HEADERS exposes parallel column_labels() / column_ids().

tests.test_parameters.test_find_column_known_and_unknown()[source]
tests.test_parameters.test_render_doc_table_emits_markdown()[source]

render_doc_table returns a 2-column Markdown table covering every column.

tests.test_parameters.test_columns_option_projects_and_orders(invoke)[source]

--columns keeps only selected columns and preserves the user order.

tests.test_parameters.test_columns_option_rejects_unknown_id(invoke)[source]

An unknown column ID raises a UsageError with available IDs listed.

tests.test_parameters.test_recurse_subcommands(invoke)[source]
tests.test_parameters.test_subcommand_conflicts_with_parent_param(invoke)[source]

A subcommand whose name matches its direct parent’s param is skipped in the parameter tree (the config key would be ambiguous), but does not crash the CLI.

[root.alpha]
# "foo" is ambiguous: is it the --foo param or the [root.alpha.foo] subcommand?
foo = ???

See: https://github.com/kdeldycke/click-extra/pull/1286

tests.test_parameters.test_nested_subcommand_no_false_conflict_with_root_param(invoke)[source]

A nested subcommand can share a name with a root-level param without conflict.

The config paths are distinct (root.verbose vs root.alpha.verbose), so there is no ambiguity.

See: https://github.com/kdeldycke/click-extra/pull/1286

tests.test_parameters.test_standalone_table_rendering(invoke, opt1, opt2, table_format)[source]

Check all rendering styles of the table with standalone --params and --table-format option.

tests.test_parameters.test_standalone_no_color_rendering(invoke, opt1, opt2, opt3, table_format)[source]

Check that all rendering styles are responding to the --color option.

tests.test_parameters.test_option_value_kind(opt, expected)[source]
tests.test_parameters.test_iter_subcommands_hidden_handling()[source]
tests.test_parameters.test_iter_subcommands_empty_for_non_group()[source]
tests.test_parameters.test_iter_params_for_display_follows_the_help_screen()[source]

Presentation order, not the processing order carried by params.

tests.test_parameters.test_iter_params_for_display_keeps_option_groups_together()[source]

Options declared in a Cloup group are yielded under that group.

tests.test_parameters.test_iter_params_for_display_falls_back_to_get_params()[source]

A plain Click command holds a single order, and it is the one to yield.

tests.test_parameters.test_iter_params_for_display_yields_late_additions_last()[source]

A parameter attached after construction is missing from the cached groups.

tests.test_parameters.test_help_column_is_opt_in(invoke)[source]

The Help column exists, stays out of the way, and answers to its ID.

tests.test_parameters.test_help_column_paints_the_deprecation_marker(invoke)[source]

The marker takes the slot the help screen paints it with.

Every other column of the rendered table is themed, and the marker is the one part of this one Click Extra writes rather than the CLI author.

tests.test_parameters.test_option_deprecation_notice_names_every_spelling(invoke)[source]

The notice names the flags a user types, not the identifier Click derived.

Click reports human_readable_name, which for an option is self.name: the one spelling that appears nowhere on the command line.

tests.test_parameters.test_deprecation_notice_reaches_a_configuration_file(invoke, create_config)[source]

A file switching a deprecated parameter on gets the notice too.

Click stops one rank short of the configuration, so the one place a selection outlives the project it names said nothing.

tests.test_parameters.test_deprecation_notice_stays_silent_on_the_default(invoke)[source]

Nobody asked for the parameter, so nothing announces it.

tests.test_parameters.test_deprecation_notice_is_emitted_once(invoke, create_config)[source]

A parameter named on both the command line and in a file warns once.

tests.test_parameters.test_help_column_is_documented()[source]

The auto-generated column reference covers the opt-in column too.

tests.test_pygments module

tests.test_pygments.lex(text)[source]

Shorthand: lex text and return (token_type, value) pairs.

Return type:

list[tuple]

tests.test_pygments.collect_classes(klass, prefix='Ansi')[source]

Returns all classes defined in click_extra.pygments that are a subclass of klass, and whose name starts with the provided prefix.

tests.test_pygments.get_pyproject_section(*section_path)[source]

Descends into the TOML tree of pyproject.toml to reach the value specified by section_path.

Return type:

dict[str, str]

tests.test_pygments.check_entry_points(entry_points, *section_path)[source]
Return type:

None

tests.test_pygments.test_ansi_lexers_candidates(tmp_path)[source]

Look into Pygments test suite to find all ANSI lexers candidates.

Good candidates for ANSI colorization are lexers that are producing Generic.Output tokens, which are often used by REPL-like and scripting terminal to render text in a console.

The list is manually maintained in Click Extra code, and this test is here to detect new candidates from new releases of Pygments.

Attention

The Pygments source code is downloaded from GitHub in the form of an archive, and extracted in a temporary folder.

The version of Pygments used for this test is the one installed in the current environment.

Danger

Security check While extracting the archive, we double check we are not fed an archive exploiting relative .. or . path attacks.

tests.test_pygments.test_formatter_entry_points()[source]
tests.test_pygments.test_filter_entry_points()[source]
tests.test_pygments.test_lexer_entry_points()[source]
tests.test_pygments.test_registered_formatters()[source]
tests.test_pygments.test_registered_filters()[source]
tests.test_pygments.test_registered_lexers()[source]
tests.test_pygments.test_plain_text(text, expected)[source]

Plain text without escape sequences passes through unchanged.

tests.test_pygments.test_sgr_fg_standard(code, color)[source]

SGR 30-37 set standard foreground colors.

tests.test_pygments.test_sgr_bg_standard(code, color)[source]

SGR 40-47 set standard background colors.

tests.test_pygments.test_sgr_fg_bright(code, color)[source]

SGR 90-97 set bright foreground colors.

tests.test_pygments.test_sgr_bg_bright(code, color)[source]

SGR 100-107 set bright background colors.

tests.test_pygments.test_sgr_text_attribute(code, attr)[source]

SGR attribute codes set the corresponding text styling.

tests.test_pygments.test_sgr_attribute_reset(set_code, reset_code, attr)[source]

Each attribute can be individually reset by its specific SGR code.

tests.test_pygments.test_sgr22_resets_bold_and_faint()[source]

SGR 22 (normal intensity) resets both bold and faint simultaneously.

tests.test_pygments.test_sgr0_resets_all()[source]

SGR 0 resets all attributes and colors.

tests.test_pygments.test_empty_sgr_is_reset()[source]

An empty SGR sequence (ESC [ m) is equivalent to SGR 0.

tests.test_pygments.test_sgr39_resets_foreground()[source]

SGR 39 resets foreground color to default.

tests.test_pygments.test_sgr49_resets_background()[source]

SGR 49 resets background color to default.

tests.test_pygments.test_sgr39_keeps_other_attributes()[source]

SGR 39 only resets foreground; other attributes persist.

tests.test_pygments.test_sgr_combined(params, expected_token)[source]

Multiple SGR codes in a single escape sequence are applied together.

tests.test_pygments.test_256color_fg(index)[source]

SGR 38;5;n sets foreground to 256-color index.

tests.test_pygments.test_256color_bg(index)[source]

SGR 48;5;n sets background to 256-color index.

tests.test_pygments.lex_quantized(text)[source]

Shorthand: lex text with explicit 256-color quantization opt-in.

Return type:

list[tuple]

tests.test_pygments.test_24bit_rgb_fg_quantized(r, g, b, expected_idx)[source]

SGR 38;2;r;g;b quantizes to nearest 256-color index when true_color=False.

tests.test_pygments.test_24bit_rgb_bg_quantized(r, g, b, expected_idx)[source]

SGR 48;2;r;g;b quantizes background to nearest 256-color index when true_color=False.

tests.test_pygments.lex_truecolor(text)[source]

Shorthand: lex text with true-color enabled.

Return type:

list[tuple]

tests.test_pygments.test_truecolor_fg_preserves_hex(r, g, b, expected_hex)[source]

With true_color=True, SGR 38;2;r;g;b emits Token.Ansi.FG_{hex}.

tests.test_pygments.test_truecolor_bg_preserves_hex(r, g, b, expected_hex)[source]

With true_color=True, SGR 48;2;r;g;b emits Token.Ansi.BG_{hex}.

tests.test_pygments.test_truecolor_combined_fg_and_bg()[source]

Foreground and background 24-bit RGB combine into a single compound token.

tests.test_pygments.test_truecolor_with_attribute()[source]

Bold + 24-bit RGB combines into a single compound token.

tests.test_pygments.test_truecolor_default_enabled()[source]

Default AnsiColorLexer() preserves 24-bit RGB as FG_{rrggbb} tokens.

tests.test_pygments.test_truecolor_explicit_disable_quantizes()[source]

AnsiColorLexer(true_color=False) quantizes RGB to the 256-color palette.

tests.test_pygments.test_truecolor_invalid_range_ignored()[source]

Out-of-range RGB values are skipped in true-color mode too.

tests.test_pygments.test_truecolor_truncated_params_ignored()[source]

Truncated RGB params are skipped in true-color mode too.

tests.test_pygments.test_truecolor_filter_forwards_flag()[source]

AnsiFilter(true_color=True) forwards the flag to its inner lexer.

tests.test_pygments.test_truecolor_session_lexer_forwards_flag()[source]

Lexer kwarg true_color=True flows through _AnsiFilterMixin.

tests.test_pygments.test_formatter_renders_truecolor_inline_style()[source]

AnsiHtmlFormatter emits inline style for FG_/BG_ tokens.

tests.test_pygments.test_formatter_renders_truecolor_background()[source]

Background 24-bit RGB renders as background-color inline style.

tests.test_pygments.test_formatter_truecolor_combined_with_class_styling()[source]

Bold + RGB renders both a CSS class for Bold and an inline style for the color.

tests.test_pygments.test_formatter_truecolor_fg_and_bg_nested_spans()[source]

Both fg and bg RGB on the same token produce two nested inline-style spans.

tests.test_pygments.test_formatter_quantize_path_no_inline_styles()[source]

When true_color=False is opted into, no inline styles are emitted.

tests.test_pygments.test_formatter_osc8_with_truecolor_coexist()[source]

OSC 8 hyperlink and 24-bit RGB tokens cooperate in the same span.

Both mechanisms inject Private Use Area markers into the token stream (_LINK_* for hyperlinks, _RGB_* for RGB colors) and rely on independent post-processing passes in format_unencoded. This test pins the contract that both rewrites happen and don’t interfere.

tests.test_pygments.test_nearest_256_quantization(r, g, b, expected)[source]

Verify RGB-to-256 quantization for representative values.

tests.test_pygments.test_extra_css_matches_sgr_attributes()[source]

EXTRA_ANSI_CSS keys match the attribute names in _SGR_ATTR_ON.

tests.test_pygments.test_non_sgr_csi_stripped()[source]

Non-SGR CSI sequences (cursor movement, etc.) are stripped.

tests.test_pygments.test_vt100_charset_stripped()[source]

VT100 charset selection escapes are stripped.

tests.test_pygments.test_vt100_charset_g1_stripped()[source]

ESC ) designator for G1 charset is stripped.

tests.test_pygments.test_unknown_escape_stripped()[source]

Unknown single-byte escape sequences are stripped.

tests.test_pygments.test_bare_escape_at_end()[source]

A lone ESC at the end of input is consumed without error.

tests.test_pygments.test_osc_sequence_stripped()[source]

Non-hyperlink OSC sequences are fully consumed and stripped.

tests.test_pygments.test_osc_st_terminated_stripped()[source]

OSC sequences terminated by ST (ESC ) are fully stripped.

OSC 8 hyperlinks emit link start/end tokens around the visible text.

tests.test_pygments.test_osc8_with_sgr()[source]

OSC 8 hyperlink combined with SGR color preserves both.

tests.test_pygments.test_osc8_unsafe_scheme_stripped(url)[source]

OSC 8 with unsafe or missing URL scheme is stripped silently.

tests.test_pygments.test_osc8_implicit_close()[source]

A new OSC 8 link implicitly closes the previous one.

tests.test_pygments.test_osc8_unclosed()[source]

An unclosed OSC 8 link is automatically closed at end of input.

tests.test_pygments.test_osc8_close_without_open()[source]

An OSC 8 close without a preceding open is silently ignored.

tests.test_pygments.test_color_persists_until_changed()[source]

A foreground color set in one sequence persists until explicitly changed.

tests.test_pygments.test_multiple_color_changes()[source]

Color can be changed multiple times without resetting.

tests.test_pygments.test_attribute_stacking()[source]

Attributes accumulate: bold then italic produces bold+italic.

tests.test_pygments.test_independent_fg_bg()[source]

Foreground and background colors are independent of each other.

tests.test_pygments.test_sgr_with_trailing_semicolons()[source]

Trailing semicolons in SGR parameters produce zero codes, which are resets.

tests.test_pygments.test_sgr_leading_semicolons()[source]

Leading semicolons produce 0 values (resets).

tests.test_pygments.test_sgr_double_semicolons()[source]

Double semicolons produce empty strings that cause the sequence to be skipped.

tests.test_pygments.test_sgr_unknown_codes_ignored()[source]

Unknown SGR codes are silently ignored; known codes still apply.

tests.test_pygments.test_256color_truncated_params()[source]

Truncated 256-color sequence (missing color index) leaves leftover codes.

38;5: code 38 triggers extended color handling but needs at least 2 more values (mode + index). Only 1 remains (5), so 38 skips. Then 5 is processed as SGR 5 (blink).

tests.test_pygments.test_256color_out_of_range()[source]

256-color index outside 0-255 is ignored.

tests.test_pygments.test_24bit_truncated_params()[source]

Truncated 24-bit RGB sequence (missing channels) is ignored.

tests.test_pygments.test_24bit_out_of_range()[source]

24-bit RGB values outside 0-255 are ignored.

tests.test_pygments.test_extended_color_unknown_mode()[source]

Extended color with unknown mode (not 5 or 2) skips the mode byte.

38;3;100: code 38 triggers extended color handling, mode 3 is unknown so mode and nothing else are consumed, then 100 is processed as SGR 100 (bright black background).

tests.test_pygments.test_non_numeric_sgr_params()[source]

Non-numeric characters in CSI params cause partial consumption.

ESC[abc;31m is not a valid SGR sequence. The regex consumes ESC[a as a CSI with a as the final byte, leaving bc;31mtext as plain text.

tests.test_pygments.test_consecutive_resets()[source]

Multiple consecutive resets are harmless.

tests.test_pygments.test_empty_text_between_sequences()[source]

Sequences with no text between them produce no empty tokens.

tests.test_pygments.test_newline_in_colored_text()[source]

Newlines within colored text are preserved in the token value.

tests.test_pygments.test_interleaved_text_and_escapes()[source]

Complex interleaving of plain text and escape sequences.

tests.test_pygments.test_lexer_resets_between_calls()[source]

Each call to get_tokens starts from a clean state.

tests.test_pygments.test_ansi_styles_has_all_named_colors()[source]

Style dict contains entries for all 16 named foreground and background colors.

tests.test_pygments.test_ansi_styles_has_256_palette()[source]

Style dict contains entries for all 256 foreground and background indices.

tests.test_pygments.test_ansi_styles_excludes_all_attributes()[source]

All text attribute tokens are absent from the style dict.

Furo’s dark-mode CSS generator adds color: #D0D0D0 to every token in the style dict. For attribute tokens, this overrides actual foreground colors on compound tokens when the attribute rule appears later in the CSS cascade. All attribute styling is handled by EXTRA_ANSI_CSS / custom.css instead.

tests.test_pygments.test_ansi_styles_count()[source]

Style dict has only color entries: 32 named + 512 indexed.

tests.test_pygments.test_formatter_no_color_on_attribute_css()[source]

CSS rules for attribute-only tokens must not set a color property.

When an attribute token (like -Ansi-Strikethrough) has a color property in its CSS rule, it can override the foreground color of a sibling color token (like -Ansi-Red) if the attribute rule appears later in the CSS cascade. This regression test catches the issue that Furo’s dark-mode generator exposed.

tests.test_pygments.test_palette_256_completeness()[source]

256-color palette has exactly 256 entries.

tests.test_pygments.test_palette_256_hex_format()[source]

All palette values are 7-character hex strings.

tests.test_pygments.test_lexer_map_completeness()[source]

LEXER_MAP has one entry per session lexer.

tests.test_pygments.test_ansi_filter_transforms_output_tokens()[source]

AnsiFilter converts Generic.Output tokens containing ANSI codes.

tests.test_pygments.test_ansi_filter_passes_through_other_tokens()[source]

AnsiFilter does not modify tokens that are not Generic.Output.

tests.test_pygments.test_formatter_css_classes_single_color()[source]

Single-color token gets the expected CSS classes.

tests.test_pygments.test_formatter_css_classes_compound()[source]

Compound token gets decomposed CSS classes.

tests.test_pygments.test_formatter_css_classes_256color()[source]

256-color tokens get the correct CSS class.

tests.test_pygments.test_formatter_style_defs_contain_ansi_colors()[source]

get_style_defs() includes CSS rules for ANSI color tokens.

OSC 8 hyperlink is rendered as an HTML <a> tag.

tests.test_pygments.test_formatter_osc8_with_color()[source]

OSC 8 hyperlink combined with SGR color renders both link and color.

tests.test_pygments.test_formatter_osc8_url_escaping()[source]

URLs with special HTML characters are properly escaped in href.

get_style_defs() includes CSS for hyperlink styling.

Unsafe URL schemes produce no <a> tag in formatted output.

tests.test_pygments.test_formatter_osc8_quotes_escaped_in_href()[source]

Quotes in a hyperlink URL cannot break out of the href attribute.

Pygments stopped escaping quotes in token text in 2.21.0, so the formatter escapes them itself, and emits the same entities on both sides of that release. Ampersands are left to Pygments, and are escaped once.

tests.test_pygments.test_real_world_ansi(text, expected_tokens)[source]

Real-world ANSI patterns from terminal tools and documentation references.

tests.test_pygments.test_ansi_session_decodes_a_styled_input_line()[source]

An input line arriving styled keeps its colors instead of its escape bytes.

A session lexer reads any line opening on a prompt as a typed command, and a --help epilog writing $ my-cli pick --ripe matches. Its escapes used to reach the inner shell lexer, which tokenized \x1b[36m as an operator between two runs of text and left the raw bytes in the page.

tests.test_pygments.test_ansi_session_leaves_a_plain_input_line_to_the_shell_lexer()[source]

Input carrying no escape still gets its shell highlighting.

tests.test_pygments.test_no_click_extra_help_screen_renders_raw_escapes()[source]

No help screen this package ships leaks an escape byte into its HTML.

The whole CLI tree is rendered the way click:run does it, then lexed and formatted. Whatever the lexer fails to decode surfaces as a literal \x1b in the page, so the assertion reads the formatted output rather than the token stream.

tests.test_pyproject module

Consistency checks tying the test matrix to the declared dependencies.

The click version axis of the test matrix is the one dependency whose whole supported range is exercised release-by-release (a patch can change behavior mid-stream). These tests keep that axis in sync with the click dependency specifier, so a drift, like a new Click release or a raised floor, fails CI instead of silently rotting.

tests.test_pyproject.PYPROJECT = PosixPath('/home/runner/work/click-extra/click-extra/pyproject.toml')

Path to the project’s pyproject.toml, relative to this test file.

tests.test_pyproject.SENTINELS = ('released', 'stable', 'main')

Moving-reference values of the click-version axis: the lockfile-resolved release, and Click’s stable and main development branches. Everything else in the axis is a pinned release number.

tests.test_pyproject.load_click_matrix()[source]

Read the Click setup from pyproject.toml.

The click-version axis is spread across the forward-looking test-matrix.full-include rows (the pinned releases and the stable / main sentinels) and the released default declared in test-matrix.include.

Return type:

tuple[SpecifierSet, list[str], set[str]]

Returns:

the click dependency specifier, the collected click-version axis values, and the subset that are pinned release numbers (sentinels start with a letter, pinned versions with a digit).

tests.test_pyproject.stable_pypi_versions(package)[source]

Return all non-yanked, non-prerelease versions of package on PyPI.

Return type:

set[Version]

tests.test_pyproject.test_click_floor_is_pinned_and_sentinels_present()[source]

The matrix floor stays in sync with the dependency floor (hermetic).

The lowest pinned click-version must equal the lower bound of the click specifier, so raising or lowering the dependency floor without updating the matrix (or vice versa) fails here. Runs offline, unlike the PyPI cross-check below.

tests.test_pyproject.test_click_matrix_matches_authorized_releases()[source]

The pinned releases match exactly the Click releases the specifier allows.

Every release allowed by the click specifier must be referenced in the matrix: the newest through the released sentinel, every earlier one by an explicit pin. This asserts the set equality in both directions:

  • a missing pin means Click published a release the matrix has not caught up to (the previous newest is no longer covered by released);

  • a stale pin means a pinned version is no longer an authorized release (the floor was raised past it, or the release was yanked).

Either way, the matrix needs an edit, and this is the signal. There is no exemption list: a release the suite cannot run on gets its own answer at the moment it appears, whether that is a floor bump past it or a hole opened here on purpose.

tests.test_pytest module

Test the Pytest helpers.

tests.test_pytest.test_aligned_colored_fixtures(uncolored, colored)[source]
tests.test_pytest.test_isolated_app_dir(invoke, isolated_app_dir)[source]

Config discovery is repointed at the isolated directory.

A CLI invoked in-process must not see the host’s real configuration folder, and a configuration file planted in the isolated directory must be picked up by the default --config search pattern.

tests.test_spinner module

class tests.test_spinner.TTYStringIO(initial_value='', newline='\n')[source]

Bases: StringIO

An in-memory text buffer that claims to be an interactive terminal.

isatty()[source]

Return whether this is an ‘interactive’ stream.

Return False if it can’t be determined.

Return type:

bool

tests.test_spinner.wait_until(predicate, timeout=3.0)[source]

Poll predicate until it is true or timeout seconds elapse.

Lets thread-driven assertions wait for an outcome instead of sleeping a fixed (and racy) amount.

Return type:

bool

tests.test_spinner.test_spinner_exported_from_root()[source]
tests.test_spinner.test_default_stream_is_stderr()[source]
tests.test_spinner.test_explicit_stream_is_honored()[source]
tests.test_spinner.test_resolve_enabled(enabled, stream, expected)[source]
tests.test_spinner.test_noop_on_non_tty_stream()[source]

A non-interactive stream produces no output and spawns no thread.

tests.test_spinner.test_delay_suppresses_quick_calls()[source]

A call shorter than the delay never draws anything.

tests.test_spinner.test_draws_and_cleans_up_when_enabled()[source]
tests.test_spinner.test_shown_false_when_not_drawn()[source]

shown stays False whenever no frame reaches the terminal.

tests.test_spinner.test_shown_true_after_drawing()[source]

shown flips to True once a frame is drawn, and stays True after stop.

tests.test_spinner.test_label_can_change_mid_spin()[source]
tests.test_spinner.test_hide_cursor_disabled()[source]
tests.test_spinner.test_ascii_frames()[source]
tests.test_spinner.test_stop_is_idempotent_and_safe_before_start()[source]
tests.test_spinner.test_suspend_and_resume()[source]

A spinner restarts cleanly after a stop, without re-using a dead thread.

tests.test_spinner.test_rotation_direction(reverse)[source]

Frames cycle forwards by default and backwards when reverse=True.

tests.test_spinner.test_beep_rings_bell_on_stop_when_enabled()[source]
tests.test_spinner.test_beep_silent_when_disabled()[source]

A disabled spinner never beeps, even with beep=True.

tests.test_spinner.test_echo_prints_above_running_spinner()[source]
tests.test_spinner.test_echo_degrades_to_plain_write_when_disabled()[source]

Off a TTY the message is still emitted, just without control codes.

tests.test_spinner.test_progress_option_is_a_default_option()[source]

ProgressOption ships in the default option set of every extra command.

tests.test_spinner.test_progress_option_resolution(invoke, args, expected)[source]

ctx.meta[PROGRESS] follows –progress and –accessible, never color.

tests.test_spinner.test_progressbar_follows_progress_flag(invoke, args, expected_hidden)[source]

click_extra.progressbar gates hidden on the resolved –progress flag.

tests.test_spinner.test_progressbar_explicit_hidden_overrides_flag(invoke, forced)[source]

An explicit hidden= wins over –no-progress, like echo(color=…).

tests.test_spinner.test_progressbar_shown_without_active_context()[source]

Outside a Click command the bar defaults to shown, like click.progressbar.

tests.test_spinner.test_progressbar_label_emission_off_tty(invoke, args, label_shown)[source]

Off a TTY a shown bar still emits its label once; a hidden one emits nothing.

tests.test_spinner.test_progressbar_shows_final_position_with_update_min_steps()[source]

Work around pallets/click#3571: with show_pos and an update_min_steps that does not divide the length, the bar must still land on total/total instead of freezing at the last multiple (14/20 for length 20, update_min_steps 7).

tests.test_spinner.test_progressbar_show_eta_follows_time_flag()[source]

The bar’s ETA follows –time by default; an explicit show_eta wins.

tests.test_spinner.test_dumb_terminal_disables_spinner(monkeypatch, term)[source]

A cursor-less terminal self-disables the spinner even on a TTY.

tests.test_spinner.test_explicit_enabled_overrides_dumb_terminal(monkeypatch)[source]

An explicit enabled=True wins over the TERM=dumb auto-detection.

tests.test_spinner.test_decorator_runs_function_inside_spinner()[source]

@spinner animates while the wrapped function runs and returns its result.

tests.test_spinner.test_bare_decorator_without_parentheses()[source]

@Spinner with no parentheses wraps the function with default settings.

tests.test_spinner.test_resolve_color_enabled(monkeypatch, env, stream_factory, expected)[source]

Color follows FORCE_COLOR / dumb TERM / NO_COLOR then TTY, with no context.

tests.test_spinner.test_color_applied_on_tty(monkeypatch)[source]
tests.test_spinner.test_color_stripped_but_spinner_still_spins_when_disabled(monkeypatch)[source]

NO_COLOR strips the spinner’s color but never stops it spinning.

tests.test_spinner.test_style_applied_to_spinner(monkeypatch)[source]
tests.test_spinner.test_invalid_style_raises()[source]
tests.test_spinner.test_frame_lines_are_what_the_animation_draws(monkeypatch)[source]

A picture of a spinner shows the lines the spinner really writes.

Spins for real, recovers every frame written to the stream, and asserts each one is a line frame_lines() offers. Composing the picture separately from the animation is what lets the two drift the first time the glyph, the label or the timer change places; comparing them is what stops that.

tests.test_spinner.test_frame_lines_honors_reverse()[source]

A spinner cycling backwards is pictured spinning the way it animates.

tests.test_spinner.test_frame_lines_colors_on_request(color)[source]

A capture asks for color whatever stream the spinner would have drawn on.

A spinner that never started resolved no color for itself, so the picture has to state its own answer rather than inherit that one.

tests.test_spinner.test_outcome_leaves_persistent_line(monkeypatch, outcome, glyph, color)[source]
tests.test_spinner.test_ok_degrades_to_plain_line_when_disabled(monkeypatch)[source]

Off a TTY the outcome is still recorded, without symbol color.

tests.test_spinner.test_timer_appended_to_frames_and_final_line()[source]
tests.test_spinner.test_timer_accepts_custom_formatter()[source]

A callable timer formats the elapsed seconds itself (yaspin #236).

tests.test_spinner.test_enable_windows_ansi_is_a_safe_noop()[source]

The Windows VT-enable never raises: off Windows, or on a stream with no usable console handle (the path the spinner exercises on every platform).

tests.test_spinner.test_elapsed_time_freezes_after_stop()[source]
tests.test_spinner.test_catalog_is_complete()[source]

The cli-spinners / ora catalog is present and well-formed.

tests.test_spinner.test_catalog_frames_share_one_width(name)[source]

Every frame of a preset occupies the same number of terminal cells.

A ragged preset moves its label a cell in and out as the animation turns, and a capture laid out on the frames reserves a column for the widest one.

tests.test_spinner.test_catalog_frames_carry_no_upstream_padding(name)[source]

No preset pads every one of its frames with a trailing space.

Upstream writes its emoji frames that way. Kept, the space lands next to the one the spinner writes before its label and reads as a double gap.

tests.test_spinner.test_spinner_preset_supplies_frames_and_interval()[source]
tests.test_spinner.test_explicit_frames_and_interval_override_preset()[source]
tests.test_spinner.test_defaults_without_frames_or_preset()[source]
tests.test_spinner.test_multichar_preset_renders()[source]

A multi-character animation (which upstream \b renderers drop) draws.

tests.test_spinner.test_demo_spinner_table_lists_selection(invoke)[source]

--table prints the catalog table; the tour stays TTY-only.

tests.test_spinner.test_demo_spinner_without_table_flag_shows_no_table(invoke)[source]

Off a TTY and without –table, the command renders no table.

tests.test_spinner.test_demo_spinner_tour_column_shows_three_cycle_time(invoke)[source]

The Tour column reports 3 × frames × interval (dots = 2.4s).

tests.test_spinner.test_demo_spinner_all_lists_full_catalog(invoke)[source]
tests.test_spinner.test_demo_spinner_select_filters_by_name(invoke)[source]
tests.test_spinner.test_demo_spinner_select_rejects_unknown(invoke)[source]
tests.test_spinner.test_demo_spinner_random_limits_count(invoke)[source]
tests.test_spinner.test_demo_spinner_options_are_mutually_exclusive(invoke)[source]
tests.test_spinner.test_demo_trail_help_lists_renderings(invoke)[source]

trail --help documents its purpose and the rendering-selecting options.

tests.test_spinner.test_demo_trail_rejects_unknown_spinner(invoke)[source]

An unknown –spinner name is rejected by the Choice type before any work.

tests.test_spinner.test_demo_trail_runs_silently_off_tty(invoke, monkeypatch, extra_args)[source]

Every rendering runs the batch to completion and, off a TTY, stays silent.

tests.test_spinner.test_tour_duration_bounds_dwell()[source]

The tour dwell aims for three cycles, clamped to [_TOUR_MIN, _TOUR_CAP], and never trims a huge spinner below one full cycle.

tests.test_spinner.test_active_spinner_registry_lifecycle()[source]

A started spinner advertises itself, keyed by stream, until stopped.

tests.test_spinner.test_active_spinner_ignores_disabled_spinner()[source]

A disabled spinner never animates, so it never registers either.

tests.test_spinner.test_operation_trail_exported_from_root()[source]
tests.test_spinner.test_trail_line_carries_themed_glyphs()[source]
tests.test_spinner.test_sequential_trail_echoes_lines_and_finisher()[source]

A sequential batch on a TTY echoes each outcome, then a timed finisher.

tests.test_spinner.test_sequential_trail_silent_off_tty()[source]

A non-interactive stream gets no trail at all by default.

tests.test_spinner.test_sequential_trail_forced_on_pipe()[source]

enabled=True forces the sequential echo onto a non-interactive stream.

tests.test_spinner.test_sequential_trail_echo_opt_out()[source]

echo_sequential=False silences a sequential batch, even on a TTY.

tests.test_spinner.test_concurrent_trail_buffers_until_spinner_draws()[source]

Outcomes marked before the aggregate spinner first draws are buffered, then flushed above it; the finisher becomes the spinner’s kept line.

The draw delay guarantees the first mark lands before the first frame, making the buffering deterministic instead of racing the animation thread.

tests.test_spinner.test_concurrent_trail_disabled_stays_silent()[source]

enabled=False keeps the concurrent spinner and its buffer off screen.

tests.test_spinner.test_concurrent_trail_marks_are_thread_safe()[source]

Concurrent mark() calls from worker threads all land in the tally.

tests.test_spinner.test_concurrent_trail_uses_chosen_spinner_preset()[source]

spinner= picks the concurrent aggregate spinner’s animation.

tests.test_spinner.test_concurrent_spinner_eta_mode()[source]

clock=’eta’ drives the concurrent spinner’s estimate from a hidden Click bar (stepped per outcome), and the finisher still shows the elapsed total.

tests.test_spinner.test_operation_trail_appends_per_operation_timing()[source]

With timer on (the default), a seconds value appends each operation’s own duration to its trail line, independent of the others.

tests.test_spinner.test_operation_handle_times_from_its_creation()[source]

An operation() handle marks its outcome with the elapsed since it began.

tests.test_spinner.test_operation_trail_timer_false_drops_all_timing()[source]

timer=False silences both the per-item and the finisher clock.

tests.test_spinner.test_operation_trail_timer_callable_formats_durations()[source]

A callable timer formats the per-item durations it is handed.

tests.test_spinner.test_resolve_timer_follows_time_flag()[source]

timer=None follows the –time flag; explicit settings pass through.

tests.test_spinner.test_progress_bar_trail_renders_bar_and_finisher()[source]

progress_bar=True drives a determinate bar with outcomes above it.

tests.test_spinner.test_progress_bar_trail_shows_empty_bar_on_entry()[source]

The bar’s 0/total state draws on entry, not only once the first outcome advances it, matching the spinner indicator that animates its tally at once.

tests.test_spinner.test_progress_bar_clock_defaults_to_elapsed()[source]

clock=’elapsed’ (the default) draws a stopwatch from the start via item_show_func, with Click’s ETA off and a ticker to keep it moving.

tests.test_spinner.test_progress_bar_clock_eta_uses_click_eta()[source]

clock=’eta’ keeps Click’s estimated-time display and runs no ticker.

tests.test_spinner.test_progress_bar_elapsed_clock_ticks_between_marks()[source]

The elapsed clock advances on its own between outcomes, with no mark.

tests.test_spinner.test_operation_trail_rejects_invalid_clock()[source]

clock must be ‘elapsed’ or ‘eta’.

tests.test_spinner.test_progress_bar_trail_works_concurrently()[source]

progress_bar=True also drives a concurrent batch from worker threads.

tests.test_spinner.test_progress_bar_trail_disabled_stays_silent()[source]

Off a TTY, the progress-bar trail renders nothing but keeps its tally.

tests.test_spinner.test_progress_bar_requires_positive_total()[source]

A determinate bar needs a length, so total must be positive.

tests.test_spinner.test_progress_bar_and_spinner_are_mutually_exclusive()[source]

progress_bar and spinner select different indicators; only one may win.

tests.test_spinner.test_progress_bar_registers_as_active_line_not_spinner()[source]

A drawing bar owns the active line (so logs cooperate), but is no spinner.

tests.test_spinner.CUSTOM_CSS = PosixPath('/home/runner/work/click-extra/click-extra/docs/_static/custom.css')

Stylesheet carrying the documentation’s unicode-range declaration.

tests.test_spinner.GRIDLESS_BLOCKS = ((9472, 9599), (9600, 9631), (9632, 9727), (10240, 10495))

Ranges no mainstream monospace font carries a glyph for.

A browser substitutes one that knows nothing of the character grid, so these come out narrow or wide and a captured table’s columns slide apart along the row. docs/_static/custom.css binds a subset font to exactly these.

tests.test_spinner.declared_unicode_ranges()[source]

Read the codepoint spans the documentation’s stylesheet claims.

Return type:

list[tuple[int, int]]

tests.test_spinner.test_catalog_gridless_glyphs_are_covered_by_the_docs_font(invoke)[source]

Every glyph a browser cannot size is one the documentation ships a font for.

The catalog table is laid out on a character grid, and a browser only reproduces that if every glyph advances by one cell. custom.css binds a subset font to the ranges no ordinary monospace font carries; this checks the two have not drifted apart, which they would the moment the declaration is narrowed or a spinner starts drawing from a range it omits.

Note

Narrower than the problem it guards: it checks the ranges already known to need a font, not that a newly used range has been noticed. A spinner drawn from some other gridless block would pass here and still misalign.

tests.test_spinner.SPINNER_ASSETS = PosixPath('/home/runner/work/click-extra/click-extra/docs/assets')

Directory the inventory gallery writes one animation per preset into.

tests.test_spinner.test_every_spinner_has_a_committed_animation()[source]

The inventory gallery covers the catalog exactly, one asset per preset.

The gallery is generated from SPINNERS at build time, so a new preset grows an asset on the next build and this only fails on a committed tree that has fallen behind. The stray half matters more: a renamed or dropped preset leaves its old animation behind, where nothing references it and nothing would notice.

tests.test_styling module

Tests for click_extra.styling.Style extras.

tests.test_styling.CLICK_VERSION = (8, 5)

Major and minor version of the installed Click package.

Click 8.5.0 started validating fg / bg color arguments: the 256-color index 0 is no longer dropped, and falsy non-None values raise TypeError instead of being silently ignored. See pallets/click#3666.

tests.test_styling.CLICK_HAS_PALETTE_ZERO_FIX = True

True when Click emits the 256-color escape sequence for palette index 0.

Click 8.5.0 fixed a bug where fg=0 / bg=0 were silently dropped because the integer 0 is falsy. Development snapshots with version numbers >= 8.5 may not yet carry this fix, so a version comparison alone is unreliable.

tests.test_styling.CLOUP_STYLE_HAS_KWARGS_CACHE = True

True when cloup.Style still carries its lazy _style_kwargs cache.

Cloup builds that cache on the first __call__ and declares it without compare=False, so a called style stops comparing equal to its twin and hash() raises. Cloup removed the cache to fix janluke/cloup#224, so the field is absent from development snapshots. The invariant the cache threatened is checked on both, and only the probes reading the field itself are gated on this flag.

tests.test_styling.test_hex_fg_converts_to_rgb_tuple(hex_str, expected_rgb)[source]
tests.test_styling.test_hex_bg_converts_to_rgb_tuple()[source]
tests.test_styling.test_hex_invalid_raises()[source]
tests.test_styling.test_named_color_string_passes_through()[source]

Plain named-color strings must not be touched by the hex shorthand.

tests.test_styling.test_or_right_operand_wins_on_conflicts()[source]
tests.test_styling.test_or_returns_subclass_instance()[source]
tests.test_styling.test_or_with_cloup_style_promotes_to_subclass()[source]
tests.test_styling.test_ror_with_cloup_left_operand()[source]

cloup_style | my_style is reached via __ror__.

tests.test_styling.test_or_with_non_style_returns_notimplemented()[source]

Style | int falls through to int.__ror__ and raises TypeError.

tests.test_styling.test_cascade_fills_unset_fields_from_base()[source]
tests.test_styling.test_cascade_keeps_subclass_identity()[source]
tests.test_styling.test_cascade_with_non_style_raises()[source]
tests.test_styling.test_to_dict_omits_unset_fields()[source]
tests.test_styling.test_to_dict_serializes_rgb_as_hex()[source]
tests.test_styling.test_to_dict_omits_none_only()[source]

False boolean attributes are kept; only None is filtered out.

tests.test_styling.test_from_dict_round_trip()[source]
tests.test_styling.test_from_dict_accepts_hex_or_rgb()[source]

Both hex strings and RGB tuples work as input.

tests.test_styling.test_str_returns_styled_sample()[source]
tests.test_styling.test_str_no_styling_has_no_color_codes()[source]

Style with no fields set produces only click’s bare reset suffix.

tests.test_styling.test_repr_compact_named_color()[source]
tests.test_styling.test_repr_compact_rgb_to_hex()[source]
tests.test_styling.test_repr_palette_index_zero()[source]

Index 0 is falsy but set: it must survive the is not None guards.

tests.test_styling.test_repr_empty_style()[source]
tests.test_styling.test_repr_multiple_attrs()[source]
tests.test_styling.test_to_css_basic()[source]
tests.test_styling.test_to_css_named_color_passes_through()[source]
tests.test_styling.test_to_css_palette_index_zero()[source]

Palette index 0 is falsy but set, and resolves to ANSI black.

tests.test_styling.test_to_css_bright_named_color_resolves_to_rgb()[source]

Bright ANSI colors aren’t valid CSS keywords: convert to RGB.

tests.test_styling.test_to_css_text_decorations_combine()[source]
tests.test_styling.test_to_css_dim_emits_opacity()[source]
tests.test_styling.test_to_css_empty_style_returns_empty_string()[source]
tests.test_styling.test_from_ansi_parses_codes(escape, expected)[source]
tests.test_styling.test_from_ansi_full_styled_string_round_trip()[source]

Parsing a style’s complete output (trailing reset included) recovers it.

tests.test_styling.test_from_ansi_round_trip_through_call()[source]

A Style can be parsed back from its own ANSI output.

tests.test_styling.test_from_ansi_invalid_raises()[source]
tests.test_styling.test_contrast_ratio_white_on_black_is_max()[source]
tests.test_styling.test_contrast_ratio_identical_colors_is_one()[source]
tests.test_styling.test_contrast_ratio_is_symmetric()[source]
tests.test_styling.test_contrast_ratio_meets_wcag_aa_for_dracula_default()[source]

Dracula’s default fg/bg pair clears WCAG AA (4.5).

tests.test_styling.test_contrast_ratio_requires_both_fgs()[source]
tests.test_styling.test_eq_ignores_style_kwargs_cache()[source]

Equality must not depend on cloup’s lazy _style_kwargs cache.

tests.test_styling.test_eq_with_cloup_style()[source]

A click-extra Style equals an equivalent cloup.Style.

tests.test_styling.test_supports_truecolor(monkeypatch, colorterm, term, expected)[source]
tests.test_styling.test_style_call_keeps_24bit_on_truecolor(monkeypatch)[source]

An RGB color emits a 24-bit sequence when the terminal supports truecolor.

tests.test_styling.test_style_call_quantizes_without_truecolor(monkeypatch)[source]

An RGB color downsamples to the nearest 256-index without truecolor.

tests.test_styling.test_style_call_leaves_named_and_indexed_colors(monkeypatch, colorterm)[source]

Named and palette-index colors never quantize, regardless of depth.

tests.test_styling.test_style_call_cache_survives_depth_flip(monkeypatch)[source]

The same style renders correctly when truecolor flips between calls.

Quantizing on a transient copy must not poison cloup’s lazy _style_kwargs cache on the shared (often singleton) style instance.

tests.test_styling.test_style_call_palette_index_zero(style, expected)[source]

Rendering palette index 0 must emit its escape code, not drop it.

tests.test_styling.test_style_call_empty_string_color_ignored(style)[source]

Click < 8.5 silently ignores falsy colors at render time.

tests.test_styling.test_style_call_empty_string_color_rejected(style)[source]

Click >= 8.5 validates colors at render time and rejects empty strings.

tests.test_styling.test_split_ansi(text, expected)[source]
tests.test_styling.test_split_ansi_empty_string()[source]
tests.test_styling.test_split_ansi_preserves_text()[source]

Concatenated run texts equal the ANSI-stripped input.

tests.test_styling.test_open_ansi(text, expected)[source]
tests.test_styling.test_open_ansi_resumes_from_an_opened_state(text, expected)[source]
tests.test_styling.test_open_ansi_chunked_matches_one_shot()[source]

Reading a string in two chunks answers like reading it whole.

Every cut is tried except the ones splitting an escape in half, which no parser can carry across a chunk boundary.

tests.test_styling.test_render_ansi_passthrough_unstyled()[source]
tests.test_styling.test_render_ansi_wraps_styled_runs()[source]
tests.test_styling.test_render_ansi_splits_runs_at_newlines()[source]

No markup wrapper ever crosses a line boundary.

tests.test_styling.test_wrap_ansi_matches_textwrap_on_plain_text()[source]

Unstyled input defers entirely to textwrap.

tests.test_styling.test_wrap_ansi_breaks_on_visible_width()[source]

A styled string breaks exactly where its plain counterpart does.

textwrap alone counts the escape bytes toward the line length, which would break a styled string several words early.

tests.test_styling.test_wrap_ansi_closes_styling_on_every_line()[source]

No escape sequence crosses a line boundary.

tests.test_styling.test_wrap_ansi_keeps_styling_on_its_own_words()[source]

Each run keeps its style once the text is split across lines.

tests.test_styling.test_wrap_ansi_edge_cases(text, width, expected)[source]
tests.test_styling.test_ansi_converters(converter, text, expected)[source]

tests.test_table module

tests.test_table.test_table_formats_definition()[source]

Check all table formats are accounted for and properly named.

tests.test_table.test_unrecognized_format(invoke, cmd_decorator, cmd_type)[source]
tests.test_table.test_all_table_formats_have_test_rendering()[source]

Check all table formats have a rendering test fixture defined.

tests.test_table.test_all_table_rendering(invoke, cmd_decorator, option_decorator, format_name, expected)[source]
tests.test_table.test_emoji_presentation_width_follows_the_terminal(monkeypatch, term_program, expected)[source]

An emoji-presentation sequence measures as its terminal advances it.

tests.test_table.test_emoji_presentation_gains_the_column_it_paints_into(monkeypatch)[source]

A terminal painting the glyph wider than it advances gets a column for it.

tests.test_table.test_emoji_presentation_padding_stays_out_of_markup(monkeypatch)[source]

A markup rendering outlives this terminal, so its cells keep their text.

tests.test_table.test_table_rows_line_up_on_every_terminal_wcwidth_knows(monkeypatch, term_program)[source]

Rows advance the same width on each terminal, not just the default one.

Measured with wcwidth’s own per-terminal tables rather than with the measure the layout used, so a terminal whose correction Click Extra misses shows up as a ragged table instead of agreeing with itself.

tests.test_table.test_table_rows_are_uniform_under_a_narrow_emoji_terminal(monkeypatch)[source]

Every row of a rendered table advances the same width in that terminal.

Measured with the terminal’s own rule, which is the one deciding whether the table’s vertical rules line up on screen.

tests.test_table.test_markup_strips_ansi_by_default(invoke, format_id)[source]

Markup formats without native styling strip ANSI codes by default.

tests.test_table.test_markup_preserves_ansi_with_color_flag(invoke, format_id)[source]

--color overrides ANSI stripping for non-styled markup formats.

tests.test_table.test_color_flag_from_parent_group_preserves_ansi(invoke)[source]

A --color forced on a parent group reaches the subcommand’s table.

Mirrors the layout of the click-extra demo CLI: the color and table options live on the group, while the table is printed from a subcommand whose context does not own those parameters.

tests.test_table.STYLED_MARKUP_SAMPLES = {TableFormat.HTML: '<span style="color: red">hello</span>', TableFormat.JIRA: '{color:red}hello{color}', TableFormat.LATEX: '\\textcolor{red}{hello}', TableFormat.LATEX_BOOKTABS: '\\textcolor{red}{hello}', TableFormat.LATEX_LONGTABLE: '\\textcolor{red}{hello}', TableFormat.LATEX_RAW: '\\textcolor{red}{hello}', TableFormat.MEDIAWIKI: '<span style="color: red">hello</span>', TableFormat.TEXTILE: '%{color: red}hello%', TableFormat.UNSAFEHTML: '<span style="color: red">hello</span>'}

Expected native styling of a red hello cell, per styled format.

tests.test_table.test_styled_formats_all_have_samples()[source]
tests.test_table.test_styled_formats_translate_ansi(invoke, format_id, styled_cell, flags)[source]

Styled formats translate ANSI codes to native markup, by default and under a forced --color alike.

tests.test_table.test_styled_formats_strip_ansi_with_no_color(invoke, format_id)[source]

Disabling colors renders styled formats plain, with no translation.

tests.test_table.test_serialize_json_compatible(table_format, data)[source]
tests.test_table.test_serialize_roundtrip(table_format, data, loader)[source]
tests.test_table.test_serialize_toml_list_wrapping()[source]

Top-level lists are wrapped under a record key for TOML.

tests.test_table.test_serialize_strips_none(table_format)[source]

TOML and XML have no null type. None values are omitted.

tests.test_table.test_serialize_xml()[source]
tests.test_table.test_serialize_xml_list_wrapping()[source]

Top-level lists are wrapped under a record key for XML.

tests.test_table.test_serialize_xml_custom_root_element()[source]
tests.test_table.test_serialize_default_callback()[source]

Custom types are converted via the default callback.

tests.test_table.test_serialize_unsupported_format_raises()[source]
tests.test_table.test_strip_none(data, expected)[source]
tests.test_table.test_apply_default_native_types_unchanged()[source]
tests.test_table.test_apply_default_custom_type_converted(data, expected)[source]
tests.test_table.test_missing_dependency_clean_error(monkeypatch, func, args, kwargs, match)[source]

Missing optional dependency produces a clean error, no traceback.

tests.test_table.test_render_table_sort(data, headers, sort_key, expected_fruits)[source]
tests.test_table.test_render_table_header_edge_cases(headers, data, expected)[source]

Edge cases for header handling in structured format rendering.

tests.test_table.test_wrappable_formats_definition()[source]

Wrapping is claimed by text layouts only, never by a data interchange.

tests.test_table.test_max_column_widths_never_breaks_a_format(format_id)[source]

Every format accepts a width: it either wraps on it or drops it.

Locks the invariant across the whole enum, so a format added later cannot silently raise or corrupt its output when handed a width.

tests.test_table.test_max_column_widths_keeps_data_formats_intact(format_id)[source]

A width never leaks a line break into a serialized cell.

tests.test_table.test_max_column_widths_scalar_applies_to_every_column()[source]

A single value stands in for a per-column list.

tests.test_table.test_max_column_widths_shorter_than_the_table()[source]

A list shorter than the table leaves the trailing columns unlimited.

tests.test_table.test_column_spec_max_width_is_the_default_source()[source]

A width declared on a ColumnSpec applies without any argument.

tests.test_table.test_max_column_widths_overrides_column_spec()[source]

An explicit argument wins over the ColumnSpec declaration.

tests.test_table.test_column_spec_max_width_follows_a_projection()[source]

A ColumnSpec width stays on its column when others are dropped.

tests.test_table.test_auto_width_fits_the_terminal(monkeypatch)[source]

An auto column absorbs the width left by the others.

tests.test_table.test_auto_width_shares_the_remainder(monkeypatch)[source]

Several auto columns split what is left evenly.

tests.test_table.test_auto_width_floors_at_min_column_width(monkeypatch)[source]

A terminal too narrow to fit the table still renders a usable column.

tests.test_table.test_auto_width_honors_context_width(invoke)[source]

auto reads the width a help screen would use, not the raw terminal.

tests.test_table.test_vertical_wrapping_keeps_the_label_gutter()[source]

Continuation lines of a wrapped cell align under the first one.

tests.test_table.test_column_sort_key(header_defs, rows, sort_columns, cell_key, expected_first_col)[source]
tests.test_table.test_sort_by_option_choices_and_default(header_defs, expected_choices, expected_default)[source]

SortByOption choices and default are derived from column definitions.

tests.test_table.test_sort_by_option_wires_context(invoke)[source]

SortByOption publishes the sort key that ctx.print_table applies.

tests.test_table.test_print_table_from_subcommand_context(invoke)[source]

A group-level –table-format reaches ctx.print_table in subcommands.

ctx.meta is shared along the context chain, so the print_table context method works from a subcommand without reaching for the root context.

tests.test_table.test_print_table_without_table_option(invoke)[source]

ctx.print_table works with no –table-format in the chain.

Falls back to the default rendering format, like the module-level print_table() invoked without an explicit format.

tests.test_table.test_render_table_context_method_honors_sort_by(invoke)[source]

ctx.render_table applies the –sort-by selection, like ctx.print_table.

tests.test_table.test_sort_by_option_multi_column(invoke)[source]

Multiple –sort-by options define sort priority.

tests.test_table.test_sort_by_option_decorator(invoke)[source]

The sort_by_option decorator wires sorting like a direct SortByOption.

tests.test_table.test_sort_by_option_decorator_in_option_group(invoke)[source]

The sort_by_option decorator composes with @option_group.

tests.test_table.test_sort_by_option_columns_registry(invoke)[source]

A ColumnSpec registry passed via columns= drives choices and ordering.

tests.test_table.test_sort_by_option_accepts_column_spec_varargs()[source]

ColumnSpec instances also work positionally, normalized to (label, id).

tests.test_table.test_sort_by_option_rejects_positional_and_columns()[source]

Column definitions cannot be passed both positionally and via columns=.

tests.test_table.test_sort_by_and_columns_share_registry()[source]

The same ColumnSpec registry configures both –columns and –sort-by.

tests.test_table.test_column_sort_key_field_mapping(header_defs, sort_columns, rows, expected_first_col)[source]

The public key builder maps requested fields onto the carried columns.

tests.test_table.test_column_sort_key_none_when_not_carried(sort_columns)[source]

No requested field carried by the table: rows must keep their order.

tests.test_table.test_render_table_rich_headers_render_labels()[source]

ColumnSpec and (label, column_id) header entries render their labels.

tests.test_table.test_sort_by_option_field_vocabulary()[source]

Bare column IDs declare a table-less field vocabulary.

tests.test_table.test_sort_by_option_labeled_defs_not_vocabulary()[source]

Labeled definitions keep the declaration-time baked-sort behavior.

tests.test_table.test_sort_by_option_rejects_mixed_defs()[source]

Bare IDs and labeled definitions cannot be mixed.

tests.test_table.test_sort_by_group_heterogeneous_tables(invoke)[source]

A group-level field vocabulary sorts each subcommand’s table independently.

Each table sorts by the selected fields it carries; a table carrying none of them keeps its original row order.

tests.test_table.test_print_table_explicit_sort_key_wins(invoke)[source]

An explicit sort_key bypasses the context --sort-by resolution.

tests.test_telemetry module

tests.test_telemetry.test_standalone_telemetry_option(invoke, cmd_decorator, telemetry_help, option_decorator)[source]
tests.test_telemetry.test_multiple_envvars(invoke, cmd_decorator, telemetry_help)[source]

tests.test_test_suite module

Tests for the declarative CLI test-suite engine and its test-suite command.

Covers three surfaces:

The host Python interpreter stands in for the command under test, so cases stay fast and platform-neutral.

tests.test_test_suite.test_parse_returns_cases()[source]

A well-formed YAML suite yields one CLITestCase per entry.

tests.test_test_suite.test_parse_returns_cases_per_format(suite_string, fmt)[source]

TOML (cases under [[cases]]) and JSON (bare array) yield the same cases.

tests.test_test_suite.test_parse_toml_requires_cases_key()[source]

A TOML mapping without a top-level ‘cases’ array of tables is rejected.

tests.test_test_suite.test_parse_rejects_non_suite_format()[source]

A format that cannot represent a list of cases is rejected.

tests.test_test_suite.test_parse_rejects_malformed(suite, exception)[source]

Empty, mapping-without-cases, and unknown-directive suites raise.

tests.test_test_suite.test_load_detects_format_from_extension(tmp_path, filename, content)[source]

The file extension selects the parser, so each format yields the case.

tests.test_test_suite.test_load_rejects_unknown_extension(tmp_path)[source]

An extension matching no suite format is rejected.

tests.test_test_suite.test_cases_from_data_builds_cases()[source]

A list of directive mappings becomes CLITestCase instances.

tests.test_test_suite.test_cases_from_data_rejects_unknown_directive()[source]

An unknown directive in a mapping is rejected.

tests.test_test_suite.test_split_args_honors_quotes(cli, expected)[source]

Quoting survives tokenization identically on POSIX and Windows.

shlex and CommandLineToArgvW are two different parsers, so the cases above are the subset of syntax on which they must agree. Windows used to reach a bare str.split() here, which broke every quoted case into pieces.

Two corners stay out on purpose, because the parsers answer differently and each answer is right for its platform: a backslash escapes the next character for shlex but stands for itself on Windows (which is what keeps C:\Users intact), and a doubled quote inside a quoted run closes and reopens it for shlex while Windows folds it into one literal quote.

tests.test_test_suite.test_case_normalizes_scalars()[source]

String scalars are coerced: exit_code to int, cli_parameters to a tuple.

tests.test_test_suite.test_case_normalizes_timeout(value, expected)[source]

A timeout given as int, float, or numeric string becomes a float.

Plain integers are what every config format produces for a bare number, so they must be accepted (a timeout: 5 in any suite).

tests.test_test_suite.test_case_rejects_non_numeric_timeout(value)[source]

A boolean or non-scalar timeout is rejected as not-a-float.

tests.test_test_suite.test_output_contains_sees_merged_stream()[source]

output_contains matches substrings from both stdout and stderr.

tests.test_test_suite.test_output_regex_preserves_cross_stream_order()[source]

output_regex_matches sees stdout and stderr interleaved in write order.

tests.test_test_suite.test_output_directive_mismatch_fails()[source]

A substring absent from the merged stream fails the case.

tests.test_test_suite.test_only_platforms_runs_on_member_platform()[source]

A case restricted to the current platform’s own id is not skipped.

tests.test_test_suite.test_only_platforms_skip_names_required_platforms(monkeypatch)[source]

The skip message names the case’s required platforms, not the current one.

The message used to interpolate current_platform(), producing the contradictory “only runs on platform: macOS” for a case skipped on macOS.

tests.test_test_suite.test_non_utf8_output_does_not_crash_the_harness()[source]

Bytes that are not valid UTF-8 are escaped, not a reader-thread crash.

A binary emitting its platform’s legacy encoding (cp1252 on Windows) used to kill the capture with a bare UnicodeDecodeError, surfacing as a “got ‘NoneType’” case failure with no hint of the cause. The escaped bytes now flow into the captured stream where assertions can see them.

tests.test_test_suite.test_child_inherits_utf8_io_encoding()[source]

The subprocess emits UTF-8 regardless of the platform’s default.

PYTHONIOENCODING is injected into the child environment so CPython-based binaries write UTF-8 on piped stdout, where Windows would pick cp1252 and desynchronize from the harness’s UTF-8 decoding.

tests.test_test_suite.test_output_and_stream_directives_are_mutually_exclusive()[source]

Combining output_* with stdout_*/stderr_* is rejected at construction.

tests.test_test_suite.ECHO_ENV = ('-c', "import os; print(os.environ.get('PROBE_VAR', '<absent>'))")

Command line printing one variable, or a marker when it is not set.

tests.test_test_suite.test_env_sets_a_variable_on_the_child()[source]

A variable a case declares reaches the command it runs.

tests.test_test_suite.test_unset_env_hides_an_inherited_variable(monkeypatch)[source]

A variable exported around the suite can be taken away for one case.

The half env cannot cover: assigning the empty string leaves the variable set, which a flag read by bare presence counts as activation.

tests.test_test_suite.test_env_directives_leave_the_runner_environment_alone(monkeypatch)[source]

Cases stay independent under --jobs: only the child is touched.

tests.test_test_suite.test_env_overrides_the_injected_io_encoding_default()[source]

A case pinning PYTHONIOENCODING wins over the harness’s own default.

The harness injects utf8 so a child’s piped stdout stays decodable (see test_child_inherits_utf8_io_encoding); a case declaring the variable itself must land after that. cp1252 is picked over latin-1 because Python reports the latter back under its iso8859-1 alias.

tests.test_test_suite.test_env_rejects_what_is_not_a_string_mapping(value, exception, match)[source]

An environment holds strings only, so anything else is refused loudly.

tests.test_test_suite.test_unset_env_normalizes_like_the_envvar_helpers()[source]

A single name is wrapped, duplicates collapse, blanks are dropped.

tests.test_test_suite.LIST_CWD = ('-c', "import os; print(sorted(os.listdir('.')))")

Command line printing the names the working directory holds.

tests.test_test_suite.test_work_directory_moves_the_command(tmp_path)[source]

The command runs where asked, not where the runner sits.

tests.test_test_suite.test_work_directory_defaults_to_the_runner_directory(tmp_path)[source]

Left unset, a case sees what it always saw.

tests.test_test_suite.test_work_directory_leaves_the_command_resolution_alone(tmp_path)[source]

The target is resolved before the move, so it is never looked for there.

run_cli_test resolves a PATH name (and .absolute()``s a path) against the runner's own directory first, which is what lets a relative target survive a ``work_directory pointing somewhere that does not hold it.

tests.test_test_suite.test_run_suite_applies_the_work_directory_to_every_case(tmp_path)[source]

The orchestrator hands it down, so a whole suite moves at once.

tests.test_test_suite.test_env_and_unset_env_are_valid_suite_directives()[source]

Both reach a case through a serialized suite, not just the Python API.

tests.test_test_suite.test_run_counts_pass_and_fail(jobs)[source]

Pass/fail tallies match regardless of the worker count.

tests.test_test_suite.test_run_select_test_skips_others()[source]

select_test runs only the chosen 1-based cases; the rest count as skipped.

tests.test_test_suite.test_run_exit_on_error_bails_sequentially()[source]

With one worker, exit_on_error stops before later cases run.

tests.test_test_suite.test_run_stats_echoes_summary(capsys)[source]

stats prints the worker line up front and the result tally at the end.

tests.test_test_suite.test_run_no_stats_is_quiet(capsys)[source]

Without stats, neither the worker line nor the tally is printed.

tests.test_test_suite.test_cli_runs_default_suite(invoke)[source]

With no suite source, the subcommand runs the built-in default suite.

tests.test_test_suite.test_cli_runs_suite_file(invoke, tmp_path)[source]

A –suite-file is parsed and run against the target command.

tests.test_test_suite.test_cli_runs_toml_suite_file(invoke, tmp_path)[source]

A TOML –suite-file runs without the yaml extra, since TOML is built in.

tests.test_test_suite.test_cli_reports_failure_exit_code(invoke, tmp_path)[source]

A failing case makes the subcommand exit non-zero.

tests.test_test_suite.test_cli_rejects_non_integer_jobs(invoke)[source]

–jobs is click-extra’s JobsOption, so a non-numeric value is refused.

tests.test_test_suite.test_cli_requires_command(invoke)[source]

Without –command/–binary, the subcommand errors with a usage message.

tests.test_test_suite.test_cli_resolves_suite_from_config(invoke, tmp_path, monkeypatch)[source]

With no –suite-file, the suite comes from [tool.click-extra.test-suite].

tests.test_test_suite.test_cli_resolves_native_cases_from_config(invoke, tmp_path, monkeypatch)[source]

Cases can be declared natively under [[tool.click-extra.test-suite.cases]].

tests.test_testing module

Test the testing utilities and the simulation of CLI execution.

tests.test_testing.test_real_fs()[source]

Check a simple test is not caught into the CLI runner fixture which is encapsulating all filesystem access into temporary directory structure.

tests.test_testing.test_temporary_fs(runner)[source]

Check the CLI runner fixture properly encapsulated the filesystem in temporary directory.

tests.test_testing.test_runner_output()[source]
tests.test_testing.check_default_colored_rendering(result)[source]
tests.test_testing.check_default_uncolored_rendering(result)[source]
tests.test_testing.check_forced_uncolored_rendering(result)[source]
tests.test_testing.test_invoke_optional_color(invoke)[source]
tests.test_testing.test_invoke_default_color(invoke)[source]
tests.test_testing.test_invoke_forced_color_stripping(invoke)[source]
tests.test_testing.test_invoke_color_keep(invoke)[source]
tests.test_testing.test_invoke_color_forced(invoke)[source]

Test colors are preserved while invoking, and forced to be rendered on Windows.

tests.test_testing.test_command_default_color()[source]

With @command and no color env var, Context and ColorOption resolve the GNU auto default (ctx.color=None), yet a forced runner still renders ANSI codes.

tests.test_testing.test_command_no_color_flag()[source]

Invoke with –no-color. Verify ctx.color=False and ANSI stripped from echo output.

tests.test_testing.test_force_color_attribute()[source]

CliRunner.force_color=True overrides color parameter.

tests.test_testing.test_no_color_envvar()[source]

NO_COLOR=1 env var causes ctx.color=False via ColorOption.

tests.test_testing.test_force_color_envvar()[source]

FORCE_COLOR=1 env var keeps ctx.color=True via ColorOption.

tests.test_testing.test_should_strip_ansi_non_tty()[source]

In a test runner (non-TTY), should_strip_ansi behaves based on color arg.

tests.test_testing.test_resolve_color_default_no_context()[source]

Outside any Click context, resolve_color_default returns None or passed value.

tests.test_theme module

Tests for click_extra.theme: in-process isolation, built-in TOML themes and [tool.<cli>.themes.<name>] config integration.

tests.test_theme.test_theme_does_not_leak_across_invocations()[source]

A --theme light invocation must not bleed into a later --help render.

Two back-to-back invocations of the same CLI in the same process:

  1. --theme light --help – selects the light palette for this call only.

  2. --help – no --theme argument, must fall back to the dark default.

The dark theme renders headings with \x1b[94m (bright blue); the light theme uses \x1b[35m (magenta, chosen to stay distinct from its blue options). If the second invocation picks up the first’s choice via process-wide state, it leaks the light palette and the assertion below fails.

tests.test_theme.test_theme_default_unchanged_after_invocation()[source]

An invocation with --theme light must not mutate the process-wide default.

tests.test_theme.test_demo_themes_renders_each_builtin_palette()[source]

click-extra themes renders the sample help once per built-in theme, each in its own palette.

Guards against the whole gallery collapsing onto the default palette: the themes command drives get_current_theme through the context THEME meta, so a regression there would render all seven screens identically.

tests.test_theme.test_demo_themes_renders_only_the_named_palettes()[source]

Named theme IDs narrow the gallery down, and keep the order they were given.

tests.test_theme.test_demo_themes_resolves_auto_to_a_palette_name()[source]

auto is a directive, so the gallery labels the palette it lands on.

Which palette that is depends on the background detected from the environment, so the expectation is read back from the same resolver rather than pinned to dark.

tests.test_theme.test_demo_themes_rejects_an_unknown_palette()[source]

An unknown ID fails, and the error lists every name that would have worked.

tests.test_theme.test_theme_meta_key_matches_registry()[source]

get_current_theme() reads from the same key ThemeOption writes.

tests.test_theme.test_font_role_slots_are_known_and_disjoint()[source]

LITERAL_STYLES / REPLACEABLE_STYLES must classify real, distinct slots.

The two frozensets encode the man-pages(7) bold/italic font roles by slot name and are maintained by hand, so guard against drift: every name must be a real HelpTheme field, the two roles must not overlap, and the representative slots must keep their expected role.

tests.test_theme.test_themes_toml_tables_alphabetical()[source]

Top-level tables in themes.toml are declared alphabetically.

tests.test_theme.test_builtin_themes_alphabetical()[source]

BUILTIN_THEMES keys are alphabetical (matters for --theme choices).

tests.test_theme.test_builtin_themes_match_toml()[source]

Every TOML table maps to a BUILTIN_THEMES entry, and vice versa.

tests.test_theme.test_builtin_themes_are_helpextratheme_instances()[source]

Every BUILTIN_THEMES entry is a HelpTheme instance.

tests.test_theme.test_cloup_constructors_keep_the_subclass(palette)[source]

dark() and light() are shadowed to return a click-extra theme.

Cloup declares both as static methods naming its own class, so the inherited ones hand back a bare cloup.HelpTheme. Reported at https://github.com/janluke/cloup/issues/225

tests.test_theme.test_builtin_themes_follow_manpage_font_convention(theme_name)[source]

Every built-in theme bolds literal slots and italicizes replaceable ones.

Encodes the man-pages(7) typographic convention (LITERAL_STYLES bold, REPLACEABLE_STYLES italic) as an invariant across all palettes, so adding a theme or tweaking a slot can’t silently drop the literal/replaceable distinction. The manpage theme renders it with no color at all.

tests.test_theme.test_theme_round_trips_through_dict(theme_name)[source]

HelpTheme.to_dict/from_dict round-trips every built-in theme.

tests.test_theme.test_themes_toml_payload_matches_to_dict(theme_name)[source]

The TOML payload for each theme equals what to_dict would emit.

tests.test_theme.test_to_dict_omits_identity_slots()[source]

Slots left at the identity default do not appear in to_dict output.

tests.test_theme.test_to_dict_emits_cross_ref_highlight_only_when_overridden()[source]

cross_ref_highlight is emitted only when it differs from the default.

tests.test_theme.test_from_dict_rejects_unknown_keys()[source]

Typos like optoin raise TypeError instead of being silently dropped.

tests.test_theme.test_cascade_overrides_only_set_slots()[source]

cascade keeps base’s slots wherever the overlay leaves them at default.

tests.test_theme.test_cascade_returns_new_instance_when_overlay_changes_anything()[source]

Even a single-slot overlay produces a distinct theme instance.

tests.test_theme.test_cascade_round_trips_through_dict()[source]

self.to_dict() wins over base.to_dict() slot-by-slot.

tests.test_theme.test_cascade_rejects_non_theme_base()[source]

cascade rejects anything that is not a HelpTheme.

tests.test_theme.test_themechoice_choices_track_global_registry()[source]

Outside any context, choices reflects the module-level registry.

Plus the reserved auto directive, which every CLI takes and no registry holds.

tests.test_theme.test_themechoice_choices_pick_up_context_overrides()[source]

A theme stashed under THEME_OVERRIDES shows up in choices.

tests.test_theme.test_branded_themes_meet_wcag_aa_large(theme_name, slot)[source]

Branded themes’ readable-text slots clear WCAG AA Large (3.0+).

Branded palettes are deliberate 24-bit RGB choices, so we can hold them to a real WCAG threshold. AA Large is the realistic floor: full AA (4.5+) is unattainable for some published themes (like Solarized’s accent blue on its base03 background sits at ~4.08).

A regression that drops one of these slots below 3.0 means the theme is less readable than what currently ships and warrants a deliberate palette tweak rather than a silent slip.

tests.test_theme.test_themes_meet_legibility_floor(theme_name)[source]

Every styled slot in every theme stays above the legibility floor.

Subdued slots (debug, bracket, default, …) are allowed to fall below WCAG AA Large by design, but no slot should be effectively invisible against the theme’s assumed background. Catches accidental palette tweaks like setting an attribute to nearly the background color.

tests.test_theme.test_load_builtin_themes_tolerates_missing_file(caplog, monkeypatch)[source]

A dropped themes.toml degrades to an empty mapping plus a warning.

tests.test_theme.test_themechoice_inert_when_registry_empty(monkeypatch)[source]

With no themes available, ThemeChoice ignores any value instead of failing.

Mirrors the runtime state once themes.toml is dropped: an empty registry means even the built-in dark default cannot resolve, so convert returns None rather than aborting the invocation.

tests.test_theme.test_cli_runs_with_empty_theme_registry(invoke, monkeypatch)[source]

A CLI still runs when no themes are available (themes.toml dropped).

The built-in –theme option defaults to dark; with an empty registry that default must stay inert instead of crashing the whole command.

tests.test_theme.test_themes_from_config_overrides_existing_theme()[source]

Known theme names cascade on top of the matching built-in palette.

tests.test_theme.test_themes_from_config_creates_standalone_theme()[source]

Unknown theme names build a stand-alone theme with unset slots at default.

tests.test_theme.test_themes_from_config_does_not_mutate_global_registry()[source]

themes_from_config is pure: the module-level registry is untouched.

tests.test_theme.test_get_theme_registry_falls_back_to_global_without_ctx()[source]

get_theme_registry(None) returns a copy of the module-level registry.

tests.test_theme.test_config_loads_new_theme(invoke, create_config)[source]

A [tool.<cli>.themes.<name>] table registers a new theme for the invocation.

tests.test_theme.test_config_overrides_existing_theme_palette(invoke, create_config)[source]

A [tool.<cli>.themes.dark] table cascades onto the built-in dark palette.

tests.test_theme.test_config_theme_appears_in_help_metavar(invoke, create_config)[source]

--help lists config-defined themes alongside the built-ins.

tests.test_theme.test_config_theme_validation_error(invoke, create_config)[source]

A malformed [tool.<cli>.themes.<name>] table is rejected with a rooted path.

tests.test_theme.test_config_theme_does_not_leak_across_invocations(invoke, create_config)[source]

Themes defined in invocation N are not visible in invocation N+1.

tests.test_theme.test_validate_config_catches_bad_theme(invoke, create_config)[source]

--validate-config surfaces the same ValidationError as the runtime path.

tests.test_theme.test_theme_auto_resolves_to_detected_palette(invoke, monkeypatch, env, expected_name)[source]

--theme=auto picks the built-in palette implied by the terminal background.

tests.test_theme.test_theme_auto_from_config(invoke, create_config, monkeypatch)[source]

theme = "auto" in the config file makes detection the effective default.

tests.test_theme.test_theme_auto_advertised_in_help_metavar(invoke)[source]

The reserved ‘auto’ directive is listed among the values –theme takes.

It resolves to a palette rather than being one, but it works on every Click Extra CLI, and a value named nowhere is a value nobody finds.

tests.test_theme.test_themechoice_accepts_auto_directive()[source]

‘auto’ converts to itself though it is not a registered palette.

tests.test_theme.test_theme_option_query_background_opt_in()[source]

The live OSC 11 query is off unless a CLI explicitly opts in.

tests.test_theme.test_resolve_auto_theme_forwards_query_flag(monkeypatch)[source]

query_background gates the live query that env-var detection ignores.

tests.test_theme.test_theme_envvar_picks_palette(invoke, monkeypatch, env_value, expected_name)[source]

CLICK_EXTRA_THEME themes a CLI invoked without --theme.

tests.test_theme.test_theme_envvar_accepts_auto_directive(invoke, monkeypatch)[source]

The reserved ‘auto’ directive resolves from the variable too.

tests.test_theme.test_theme_flag_outranks_envvar(invoke, monkeypatch)[source]

An explicit --theme beats the machine-wide variable.

tests.test_theme.test_cli_envvar_outranks_theme_envvar(invoke, monkeypatch)[source]

The <CLI>_THEME variable is more specific than the machine-wide one.

tests.test_theme.test_config_outranks_theme_envvar(invoke, create_config, monkeypatch)[source]

A palette pinned in the CLI’s own config file is more specific.

tests.test_theme.test_unknown_theme_envvar_warns_and_keeps_default(invoke, monkeypatch)[source]

A typo in a shell profile must not break every Click Extra CLI at once.

tests.test_theme.test_theme_from_env_reaches_the_help_screen(invoke, monkeypatch, envvar)[source]

A palette named by the environment paints the screen it exists to paint.

Click processes a typed --help before an untyped --theme, so the palette only reaches the help screen through the pre-pass in Command._resolve_presentation_eagerly.

tests.test_theme.test_theme_flag_after_help_still_paints_it(invoke)[source]

--help --theme nord renders under nord, like the reverse order.

Both are typed, so Click sorts --help first and would render before the palette settles: the same pre-pass covers it.

tests.test_types module

tests.test_types.test_click_choice_behavior()[source]

Lockdown the behavior of method inherited from Click’s Choice type.

Return type:

None

tests.test_types.test_enum_string_choices(enum_definition, choice_source, result)[source]
Return type:

None

tests.test_types.test_enum_choice_show_aliases(enum_definition, choice_source, show_aliases, result)[source]

Test that EnumChoice correctly handles Enum with aliases.

Return type:

None

tests.test_types.kebab_case(choice)[source]

Reshape a Python identifier into a CLI-friendly choice string.

Return type:

str

class tests.test_types.AliasedStrategy(*values)[source]

Bases: Enum

Aliases declared in the class body, which every supported Python handles.

Unlike _add_alias_(), this needs no Python 3.13.

SELECT_OLDER = 1
SELECT_NEWEST = 2
DISCARD_NEWEST = 1
DISCARD_OLDER = 2
class tests.test_types.TransformedFormat(*values)[source]

Bases: Enum

PLAIN_TEXT = 'plain_text'
RICH_HTML = 'rich_html'
tests.test_types.test_enum_choice_transform(choice_source, transform, expected)[source]

transform reshapes the choice string produced by any source.

Return type:

None

tests.test_types.test_enum_choice_transform_with_aliases(choice_source)[source]

transform is what makes show_aliases usable with non-identifier choices.

Return type:

None

tests.test_types.test_enum_choice_transform_collision(show_aliases)[source]

A transform collapsing two spellings into one is rejected.

Return type:

None

tests.test_types.test_enum_choice_transform_non_string()[source]

A transform returning a non-string blames itself, not the choice source.

Return type:

None

tests.test_types.test_enum_choice_transform_raising()[source]

A transform blowing up is reported with the string it choked on.

Return type:

None

class tests.test_types.MyEnum(*values)[source]

Bases: Enum

Produce different strings for keys/names, values and str().

FIRST_VALUE = 'first-value'
SECOND_VALUE = 'second-value'
tests.test_types.test_enum_choice_internals(source, expected_choices)[source]
Return type:

None

tests.test_types.test_enum_choice_case_sensitivity(case_sensitive)[source]
Return type:

None

tests.test_types.test_enum_choice_shell_complete(source, expected)[source]

Completion offers normalized choice strings, never the Enum.member form.

Regression guard for pallets/click#3015, fixed upstream in pallets/click#3471: completion routes through Choice.normalize_choice(), so an EnumChoice suggests parseable strings (case-folded, as it is case-insensitive by default) instead of MyEnum.FIRST_VALUE.

Return type:

None

tests.test_types.test_enum_choice_duplicate_string()[source]
Return type:

None

tests.test_types.test_enum_choice_command(invoke, cmd_decorator, opt_decorator, case_sensitive, valid_args, invalid_args)[source]

Test EnumChoice used within an option.

Return type:

None

tests.test_types.test_enum_choice_default_value(invoke, cmd_decorator, opt_decorator, opt_type, default_value)[source]

Test EnumChoice used within an option with a default value.

Return type:

None

tests.test_types.test_enum_choice_multiple_default_value(invoke, cmd_decorator, opt_decorator, default_value, expected)[source]

A multiple=True EnumChoice resolves each member of its tuple default.

Regression test for the get_default() override stringifying the whole default tuple (str((MyEnum.FOO,))) instead of mapping each member, which made the default trip Click’s Value must be an iterable check.

Return type:

None

tests.test_types.test_enum_choice_variadic_default_value(invoke, cmd_decorator, opt_decorator, default_value, expected)[source]

A variadic (nargs=-1) EnumChoice argument resolves each member of its default.

Companion to test_enum_choice_multiple_default_value covering the other branch of the get_default() override: the nargs == -1 path taken by arguments rather than the multiple path taken by options. Both map get_choice_string() over the members of a tuple default instead of stringifying the whole tuple.

Return type:

None

tests.test_types.test_enum_choice_callback(invoke, cmd_decorator, opt_decorator, choice_source, cli_value)[source]

Test that option callbacks receive properly converted Enum members.

Return type:

None

tests.test_types.test_multi_choice_parses_input(raw, expected)[source]

convert() splits on the separator, strips whitespace, drops empties.

Return type:

None

tests.test_types.test_multi_choice_validates_against_choices()[source]

Unknown tokens raise BadParameter via self.fail().

Return type:

None

tests.test_types.test_multi_choice_validates_an_already_parsed_sequence()[source]

A list is checked like a raw token, not waved through as pre-parsed.

A configuration file hands the option a list where the command line hands it one string, so skipping the check on a sequence lets an unknown value travel to whatever consumes the selection.

Return type:

None

tests.test_types.test_multi_choice_case_insensitive_normalizes()[source]

case_sensitive=False matches case-insensitively and returns the canonical case.

Return type:

None

tests.test_types.test_multi_choice_metavar(choices, separator, expected)[source]
Return type:

None

tests.test_types.test_multi_choice_in_click_option()[source]

End-to-end: MultiChoice plugs into a Click option like Choice does.

Return type:

None

tests.test_types.test_duration_parsing(value, expected)[source]
Return type:

None

tests.test_types.test_duration_passthrough_timedelta()[source]

An already-parsed timedelta is returned unchanged (idempotent conversion).

Return type:

None

tests.test_types.test_duration_invalid(value)[source]
Return type:

None

tests.test_types.test_duration_rejects_calendar_units(value)[source]

Months and years are explicitly rejected because their length is ambiguous.

Return type:

None

tests.test_types.test_duration_absolute_timestamp(value)[source]

An RFC 3339 timestamp is converted to now - timestamp at parse time.

Return type:

None

tests.test_types.test_duration_future_timestamp_parses_to_none()[source]

A timestamp in the future parses to None, read as “no cutoff”.

Return type:

None

tests.test_types.test_duration_in_click_option()[source]

End-to-end: Duration plugs into a Click option.

Return type:

None

tests.test_types.test_parse_duration(value, expected)[source]
Return type:

None

tests.test_types.test_parse_duration_passthrough_timedelta()[source]

An existing timedelta is returned unchanged, like Duration.convert.

Return type:

None

tests.test_types.test_parse_duration_never_raises(value)[source]

The umbrella parser mirrors Duration but returns None instead of failing.

Return type:

None

tests.test_types.test_parse_friendly_duration(value, expected)[source]
Return type:

None

tests.test_types.test_parse_iso8601_duration(value, expected)[source]
Return type:

None

tests.test_version module

Test the --version option.

tests.test_version.test_standalone_version_option(invoke, cmd_decorator, option_decorator)[source]
tests.test_version.test_debug_output(invoke, cmd_decorator, option_decorator, assert_output_regex)[source]
tests.test_version.test_set_version(invoke)[source]
tests.test_version.test_set_version_positional(invoke)[source]

Click drop-in: an explicit version may be the first positional argument.

@version_option("1.2.3.4") is equivalent to the fields={"version": "1.2.3.4"} form used by test_set_version().

tests.test_version.test_version_option_dashed_positional_is_flag()[source]

A leading-dash positional names the option flag, not the version.

tests.test_version.test_version_option_conflicting_version()[source]

The version cannot be supplied both positionally and via fields=.

tests.test_version.test_custom_message(invoke, cmd_decorator, message, regex_stdout, assert_output_regex)[source]
tests.test_version.test_style_reset(invoke, cmd_decorator)[source]
tests.test_version.test_custom_message_style(invoke, cmd_decorator)[source]
tests.test_version.test_build_resolver_answers_and_is_a_template_field(field_id)[source]

Every build resolver answers on the host running it, and has a field.

A build fact is unlike a git one on both counts: nothing can fail to resolve, since the host is right here, and nothing can fall back later, so a resolver whose field is missing from the template would bake a value no message could ever print.

tests.test_version.test_build_time_honors_source_date_epoch(monkeypatch)[source]

A reproducible build pins the stamp instead of reading the clock.

tests.test_version.test_context_meta(invoke, cmd_decorator, assert_output_regex)[source]
tests.test_version.test_env_info_resolves_no_hostname(monkeypatch)[source]

The environment profile is built without resolving the host’s name.

boltons.ecoutils.get_profile(scrub=True) skips every lookup whose value it then replaces with -, since boltons 26.2.0. One of them is a reverse DNS query: a host whose resolver does not answer paid that timeout for a value already discarded. It cost ~35 s per call on a GitHub macOS runner, which is what made --verbosity DEBUG runs there take over an hour. This test guards the version floor holding that fix.

Asserting on the calls rather than on a duration keeps the guard away from a timing threshold, which a loaded runner would flake on.

tests.test_version.test_context_meta_laziness(invoke, cmd_decorator)[source]

Accessing a single field from ctx.meta must not evaluate unrelated fields.

Ensures that the _LazyVersionDict defers property evaluation: reading click_extra.version should not trigger expensive properties like env_info or git fields.

tests.test_version.test_module_version_parent_package_fallback(monkeypatch)[source]

module_version falls back to parent package’s __version__.

Simulates the Nuitka use-case: a CLI whose module is myapp.__main__ (no __version__), with the parent package myapp providing it.

tests.test_version.test_is_main_module(module_name, expected)[source]
tests.test_version.test_main_entry_point_survives_absent_distribution_metadata(monkeypatch)[source]

A compiled binary reads its version off the package it started in.

A Nuitka standalone binary ships no distribution metadata, so distribution_of() answers None for the CLI’s own package just as it does for ecosystem plumbing, and the walk lands on click_extra.__main__. That entry point must survive: traded for the root command’s callback module, it loses the __main__ exemption and --version renders nothing at all.

tests.test_version.test_package_version_resolves_import_name_to_distribution(monkeypatch)[source]

When package_name is an import name that differs from its installed distribution name (PIL vs Pillow), package_version resolves it via packages_distributions() instead of returning None.

tests.test_version.test_package_version_ambiguous_import_name_returns_none(monkeypatch)[source]

When an import name maps to several installed distributions, package_version returns None rather than guessing.

tests.test_version.test_package_version_unknown_returns_none(monkeypatch)[source]

When the name resolves to no distribution, package_version returns None, the existing graceful behavior.

tests.test_version.test_cli_frame_fallback(monkeypatch)[source]

cli_frame() falls back to the outermost frame when all frames are from the Click ecosystem.

tests.test_version.STANDALONE_SCRIPT = 'import click\nfrom click_extra import echo, version_option\n\n\n@click.command\n@version_option(\n    message=(\n        "{prog_name} | {exec_name} | {package_name}"\n        " | {module_version} | {version}"\n    )\n)\ndef weather():\n    echo("Sunny.")\n\n\nif __name__ == "__main__":\n    weather()\n'

A CLI run straight from a file as __main__, with no package around it.

Its version message renders every field the unpackaged case resolves on its own, so a single invocation pins them all.

tests.test_version.test_standalone_script(tmp_path, dunder, expected_version)[source]

A standalone script is named after its file, and versioned by __version__.

With no package to read metadata from, exec_name falls back to the script’s file name and package_name to None. The version is read from a __version__ variable defined alongside the CLI, and stays None when the script defines none.

Only a real interpreter reaches that code path: a CLI declared inside a test function belongs to the test module, and that module has a package.

tests.test_version.test_integrated_version_option_precedence(invoke, params)[source]
tests.test_version.test_version_fields_forwarded_to_version_option(invoke)[source]

version_fields on @command forwards to VersionOption.

tests.test_version.test_version_fields_forwarded_on_group(invoke)[source]

version_fields works on @group too.

tests.test_version.test_version_fields_multiple(invoke)[source]

Multiple fields can be overridden at once.

tests.test_version.test_version_fields_rejects_unknown(invoke)[source]

Unknown field names raise TypeError.

tests.test_version.test_color_option_precedence(invoke)[source]

A plain click.command only decolors --version when --no-color precedes it on the command line.

Click evaluates eager parameters in the order the user typed them (callback evaluation order), so a --no-color placed after --version lands too late: the version screen has already rendered in color and exited.

click-extra’s own @command settles the color options in a pre-pass before any eager screen renders, so the color choice is honored whatever its position. See Command._resolve_presentation_eagerly and the order-independent test_color_settles_before_eager_help_and_version in test_color.py. This test pins the residual behavior of the plain-Click path, which that pre-pass does not reach.

tests.test_version.test_dev_version_appends_git_hash(invoke, cmd_decorator)[source]

A .dev version gets a +hash suffix appended (or not, if git is unavailable).

tests.test_version.test_prebaked_dev_version_not_double_suffixed(invoke, cmd_decorator)[source]

A version with an existing + is returned as-is: no second hash appended.

tests.test_version.test_release_version_unchanged(invoke, cmd_decorator)[source]

A non-dev version is never modified.

tests.test_version.init_file(tmp_path)[source]

Helper that creates a temporary __init__.py with the given content.

tests.test_version.test_prebake_cli_resolves_module_from_config(invoke, tmp_path, monkeypatch)[source]

click-extra prebake resolves its target from [tool.click-extra.prebake].

tests.test_version.test_prebake_dev_version(init_file)[source]

A .dev version gets +hash appended in the file.

tests.test_version.test_prebake_single_quotes(init_file)[source]

Single-quoted __version__ is also handled.

tests.test_version.test_prebake_already_baked_skipped(init_file)[source]

A version with existing + is left untouched.

tests.test_version.test_prebake_release_skipped(init_file)[source]

A release version (no .dev) is not modified.

tests.test_version.test_prebake_no_version_in_file(init_file)[source]

A file without __version__ returns None.

tests.test_version.test_prebake_missing_local_version_raises(init_file)[source]

Calling without local_version raises TypeError.

tests.test_version.test_prebake_idempotent(init_file)[source]

Running prebake twice does not double-suffix.

tests.test_version.test_prebake_preserves_surrounding_content(init_file)[source]

Content around __version__ is not disturbed.

tests.test_version.test_prebake_dunder_empty_replaced(init_file)[source]

An empty dunder variable gets replaced.

tests.test_version.test_prebake_dunder_single_quotes(init_file)[source]

Single-quoted empty dunder is also handled.

tests.test_version.test_prebake_dunder_nonempty_skipped(init_file)[source]

A dunder with an existing non-empty value is left untouched.

tests.test_version.test_prebake_dunder_not_found(init_file)[source]

A file without the target dunder returns None.

tests.test_version.test_prebake_dunder_idempotent(init_file)[source]

Running prebake_dunder twice does not overwrite.

tests.test_version.test_prebake_dunder_preserves_surrounding_content(init_file)[source]

Content around the target dunder is not disturbed.

tests.test_version.test_prebake_dunder_full_sha(init_file)[source]

A full 40-character SHA is handled correctly.

tests.test_version.test_discover_finds_init(tmp_path, monkeypatch)[source]

Discovers __init__.py from [project.scripts].

tests.test_version.test_discover_no_pyproject(tmp_path, monkeypatch)[source]

Returns empty list when pyproject.toml is missing.

tests.test_version.test_discover_no_scripts(tmp_path, monkeypatch)[source]

Returns empty list when [project.scripts] is absent.

tests.test_version.test_discover_deduplicates(tmp_path, monkeypatch)[source]

Multiple scripts from the same package yield one path.

tests.test_version.test_prebaked_git_branch()[source]

A pre-baked __git_branch__ dunder is used over subprocess.

tests.test_version.test_prebaked_git_long_hash()[source]

A pre-baked __git_long_hash__ dunder is used over subprocess.

tests.test_version.test_prebaked_git_tag_sha()[source]

A pre-baked __git_tag_sha__ dunder is resolved.

tests.test_version.test_prebaked_empty_dunder_ignored()[source]

An empty dunder is not treated as a pre-baked value.

tests.test_version.test_prebaked_non_string_ignored()[source]

A non-string dunder is not treated as a pre-baked value.

tests.test_version.test_prebaked_git_distance()[source]

A pre-baked __git_distance__ dunder is used over subprocess.

tests.test_version.test_prebaked_git_dirty()[source]

A pre-baked __git_dirty__ dunder is used over subprocess.

tests.test_version.test_resolve_git_distance(monkeypatch, describe_output, expected)[source]
tests.test_version.test_resolve_git_dirty(monkeypatch, status_output, expected)[source]
tests.test_version.test_archival_field_substituted(field_id, expected)[source]
tests.test_version.test_archival_field_unsubstituted_ignored(field_id)[source]

A raw checkout keeps the $Format placeholders, which must be ignored.

tests.test_version.test_archival_field_exact_tag_distance_zero()[source]

A bare describe-name (no -N-g suffix) means distance zero.

tests.test_version.test_read_archival_roundtrip(tmp_path)[source]
tests.test_version.test_read_archival_invalid_json(tmp_path)[source]
tests.test_version.test_find_archival_file_walks_up(tmp_path)[source]
tests.test_version.test_find_archival_file_absent(tmp_path)[source]
tests.test_version.test_archival_resolves_git_fields(tmp_path)[source]

Git fields resolve from .git_archival.json when there is no live git.

tests.test_version.test_version_dev_hash_assembly(module_version, expected)[source]

The git short hash is appended to dev releases only.

tests.test_version.terminal_width(monkeypatch)[source]

Pin the width VersionScreen.render measures itself against.

The function is patched, and COLUMNS deliberately left alone. shutil.get_terminal_size reads that variable before it measures the terminal, so exporting it would pin the width just as well. It would also resize pytest’s own reports for as long as a narrow pin is held.

The replacement accepts keyword arguments because pytest measures its progress line with shutil.get_terminal_size(fallback=(80, 24)). A positional-only stand-in raises TypeError there and aborts the run with an INTERNALERROR. See #1934.

A deliberately ragged mark: no caller should have to square its own artwork.

The same, carrying color, to prove the measurement discounts escape sequences.

The width comes from the artwork, never from the caller.

Every line is padded out to the widest, so the facts seat against a block.

Padding here rather than demanding it is what lets a caller hand over whatever its renderer produced. It cannot repair this afterwards even if it wanted to: str.ljust counts the escape sequences it cannot see, so on a styled line it silently does nothing.

tests.test_version.test_screen_padding_closes_an_open_style()[source]

Padding after an unterminated style cannot inherit it.

A mark whose last run is left open would otherwise paint its own trailing blanks, and the gutter with them, dragging a background across the facts.

tests.test_version.test_screen_facts_may_be_deferred()[source]

A callable is not called until the screen is drawn.

The point of accepting one: a CLI reporting something costly should pay for it when --version is asked for, not on every invocation that might have been.

tests.test_version.test_screen_sizes_its_label_column_to_the_longest_label()[source]

No declared width to outgrow: the column is measured, then gutter-separated.

tests.test_version.test_screen_facts_replace_a_default_row_in_place()[source]

| over the defaults swaps a row where it sits, and appends a new one.

tests.test_version.test_screen_declines_when_too_narrow(terminal_width)[source]
tests.test_version.test_screen_opens_on_a_blank_line_and_never_trails_whitespace(terminal_width)[source]
tests.test_version.test_screen_seats_every_fact_at_one_column(terminal_width)[source]

The facts start at the same column on every line that carries one.

tests.test_version.test_screen_uses_the_options_own_styles(terminal_width)[source]

The header is painted like the plain message, not like something new.

tests.test_version.screen_cli()[source]
tests.test_version.test_version_option_without_a_screen_is_unchanged(invoke)[source]

A CLI that never asks for a screen keeps the one-line message.

tests.test_version.test_screen_skipped_without_color(invoke, screen_cli)[source]
tests.test_version.test_screen_drawn_with_color(invoke, screen_cli, terminal_width)[source]
tests.test_version.test_screen_skipped_when_accessible(invoke, screen_cli, terminal_width)[source]
tests.test_version.test_version_message_follows_the_theme(invoke, theme, expected)[source]

The message is painted from the active palette, not from a frozen copy.

Capturing the dark palette at import left --version printing the program name bright white under every theme, which on a light terminal is white on white. The dark row is unchanged from that era on purpose: this made the other two work without moving what anyone already had.

tests.test_version.test_colors_reach_output(monkeypatch, color, stdout_is_a_tty, expected)[source]

auto reads sys.stdout, an explicit tri-state never looks at it.

The stream half is the part worth pinning: click.echo reaches stdout through a wrapper whose public alias Click deprecated in 8.5.0, and this resolution has to keep answering what echo answers without it.

tests.test_version.test_sphinx_runner_resets_version_resolution()[source]

Each documented invocation resolves --version for itself.

The resolution chain opens on a stack walk and memoizes, which suits a CLI: one invocation, one process. A documentation build renders many commands in one process, and a render carrying no CLI frame (writing roff for a command tree, say) resolves the chain from a stack the walk cannot read. That answer used to stand for the rest of the build, so a later --version example published a screen with no version on it.

This guards the mechanism, not the build: it plants a stale resolution by hand rather than reproducing the frame shape a real sphinx-build has, so it holds ClickRunner.invoke to clearing the memo and says nothing about which renders would otherwise poison it.

tests.test_version.test_reset_resolution_clears_every_memoized_field()[source]

reset_resolution() drops the whole chain, not a chosen few fields.

tests.test_version.test_reset_resolution_keeps_a_pinned_field()[source]

A field pinned through fields= is not a resolution, so it survives.

An override lands in the instance dict under the name of the property it shadows, so a reset that goes by name alone erases it. That silently turns a pinned version into None on every documented invocation.