Upstream

Click Extra was born as a collection of patches for unmaintained or slow-moving click-contrib addons. Over time, many of these fixes were contributed back upstream, and the project grew into a significant contributor to the Click ecosystem.

This page tracks click-extra’s relationship with its upstream dependencies: code contributed back, issues reported, workarounds provided, and features that upstream declined.

Code contributed upstream

PRs authored by the click-extra maintainer and merged into upstream projects.

click

Default and sentinel handling:

Help system:

Enum and Choice support:

Testing:

Documentation:

python-tabulate

pygments

furo

cloup

pallets-sphinx-themes

click-contrib/sphinx-click

click-contrib/click-log

boltons

Upstreamed from click-extra

Issues that click-extra solved with local workarounds first. The fix was later contributed upstream and the workaround removed from click-extra.

boltons

  • #461 - ecoutils.get_profile(scrub=True) resolved the user, host and working directory before replacing all four values with -. The host lookups stall on a machine with no reverse DNS, and os.getcwd() raises once the working directory is gone. click-extra patched the two host lookups out; the fix shipped in boltons 26.2.0, whose floor retired that patch.

click

  • #2811 / #2563 - Help option eagerness and deduplication. click-extra had custom HelpOption fixes; contributed upstream and removed locally.

  • #2680 - Callbacks not properly closed on CLI exit. click-extra patched Option to force-close; contributed the fix upstream.

  • #2523 / #2522 - CliRunner conflating stderr and stdout. click-extra backported the fix before the Click release.

  • #2516 / #2517 - Help extra items (default, envvar, required) were computed and rendered together. click-extra needed them split to inject colorization; contributed the refactor upstream.

cloup

  • #160 / #162 - cloup.Group ignored command_class for subcommands. click-extra worked around it; Cloup fixed it in a later release.

  • #177 - cloup.Color could not be rendered by Sphinx. click-extra had an exception handler; Cloup fixed the import.

Addressed by click-extra

Issues that remain open or unfixed upstream. click-extra provides the solution.

Color and terminal support

click-extra implements NO_COLOR, FORCE_COLOR, and full help colorization with a theme system, addressing long-standing Click gaps:

Terminal captures as SVG

click-extra’s screenshot command renders a captured terminal to SVG with its own click_extra.screenshot.render_svg(). It was built on Rich’s Console.export_svg() until 9.0.0, which is where these came from: everything below is a defect the exported image carried, reported upstream and still unfixed.

Open upstream, and fixed here:

  • rich#3576 - Underscores on the last captured line are clipped when exporting to SVG: the terminal’s clip path is computed as (rows + 1) × line_height - 1, one pixel shorter than the text it contains, which cuts the descenders off the last line. Reported in 2024 with the fix named in the report; rich#3611 and rich#3991 both propose it and neither is merged, so rich 15.0.0 still ships it. render_svg sizes the clip to the text block.

  • rich#2742 - SVG font rendering too narrow for Asian language: a run’s textLength is computed from its character count while its column advance uses terminal cells, so the two disagree for every glyph that is not exactly one cell. The error runs both ways. Chinese, Japanese, Hangul, fullwidth Latin and emoji are drawn at half their width and stack on each other; a decomposed éàü is drawn at twice its width, and a zero-width-joiner family emoji at two and a half times. Open since 2023. render_svg sizes both from click_extra.layout.cell_width().

  • rich#2536 - Can we fix this render glitch in SVG output?: box-drawing characters do not meet cleanly, which the maintainer answers would need replacing them with drawn shapes. Open since 2022, and not addressed here either: click-extra’s tables and command tree draw the same characters.

  • rich#2279 - Misaligned box boundaries in SVG generated from a rich-click help dialogue: closed as not planned in 2022, the maintainer answering that a font whose glyphs are not all one width is the font’s problem and a browser rendering difference is not Rich’s to fix. Distinct from rich#2536 above: that one is glyphs failing to meet at their seams, this one is column drift accumulating across a whole line wherever a renderer resolves font metrics differently, which is why the same file looked right in one browser and broken in another. Starting every run on its own column with a cell-sized textLength leaves drift nowhere to accumulate, whatever font a viewer substitutes.

The same defect reaches the tools built on that exporter:

  • rich-codex#34 - emojis break alignments: an emoji-heavy capture comes out misaligned, the reporter guessing at “two different widths of emojis used during placement and then rendering”. The maintainer answered that it would be difficult or impossible to fix and suspected an upstream cause, without naming one. It is rich#2742 above, reported a year later and still open. ewels/rich-codex draws its images with Rich’s Console.save_svg(), so it inherits the whole list; its own captures are otherwise actively maintained, and it solved the committed-image churn below on its own terms, deriving a stable id from the output path rather than leaving it to the caller.

Committing an exported image to a repository is its own problem, and Rich answers it with a parameter where render_svg answers it by construction:

  • rich#2537, merged in 2022, adds an opt-in unique_id=, filed because “As part of Airflow’s CI we save SVG images of our command’s help output and commit them to git. Without this change a single character change in the help text results in every single class and id in the SVG changing, which makes the diffs unreadable.” It is a parameter a caller has to remember and keep unique per image by hand; forget it and the diff is noise again, reuse it and two images collide on one page.

  • rich#3928 - Add @generated comment to HTML and SVG exports: the same “I commit this output” need from another angle, open since January 2026 with no reply.

Also carried by every image Rich exports, and dropped here:

  • The exported document references one clipPath per line but defines them for every line but the last, so the final row of every capture carries a dangling reference. A browser ignores it, a strict SVG 1.1 renderer drops the element it is attached to. render_svg clips the text block as a whole, which removes the per-line paths and the failure mode with them.

  • The stylesheet carries two @font-face rules pointing at a CDN, so a committed image reaches the network to render as intended, and falls back silently offline or behind a content-security policy. render_svg names a font stack and embeds nothing, which is also what rich#2153 and rich#2526 run into from the other end.

  • A run’s inter-column padding is written inside its <text> element, so a column only lands correctly where the renderer both honors textLength and resolves the font: librsvg, and through it rsvg-convert and ImageMagick, does neither (rich#2153, rich#3034). render_svg starts each run on its own column.

  • Every line ends with a <text> element containing only a newline, drawing nothing. Removing those, the per-line clip paths and the webfont rules together took this documentation’s 21 committed captures from 451 KB to 247 KB.

Source code as SVG

click-extra’s snippet command highlights source code with Pygments, then draws it with the same renderer the captures above use rather than with Pygments’ own SvgFormatter. That formatter has called itself “still experimental” in its own docstring since version 0.9, released in 2007, and its last functional change was pygments@3097984 in December 2019; every commit to the module since is a copyright bump, a lint fix or a pyupgrade pass. Three gaps make its output unusable on the surfaces a snippet is meant for, and none of the three is reported upstream:

  • It never reads style.background_color, so it paints no background at all, and a dark style draws near-white text on whatever surface the viewer supplies. Of the formatters Pygments ships, only html and img read the value. The same gap is filed against the RTF formatter, and the answer there explains the silence: pygments#1208 - RTF formatter does not set background color from style, open since 2019, where the maintainer replies that this is “by design”, a token’s background being the background of that token. Read it as a design position rather than an oversight, and expect the SVG behavior to stand.

  • Its root element carries no viewBox, no width and no height, so the image states no size for a page to embed it at.

  • It emits no textLength and opens every line at x="0", so nothing pins the character grid and the alignment holds only where the renderer resolves the exact font. This is the same failure render_svg was written around for terminal captures.

The pattern behind all three is that a capability lands on the HTML and image formatters and never travels: line highlighting was asked of img in 2009 (pygments#236) and is there today, background color reached it in 2021 (pygments#1374), and neither has reached SVG. hl_lines today works in html, img and rtf and nowhere else, which is what pygments#894 - Add line highlighting to latex formatter has asked about since 2019. No terminal formatter has ever had it and nobody has asked, so the bands snippet draws behind emphasized rows in terminal output answer no upstream request at all.

Two things click-extra routes around without fixing, so a Pygments user gains nothing from either: pygments#3236, an open attribute-injection hole in SvgFormatter’s own fontfamily and fontsize options; and pygments#2667 with pygments#1430, which ask a terminal formatter to detect whether it draws on a light or a dark terminal. snippet paints its own window, so the picture never faces that question, and its terminal output still lands on whatever surface the user’s terminal supplies.

Note

The nearest prior art is ewels/rich-codex, by rich-click’s maintainer, which highlights a snippet with Rich’s Syntax and writes it with Console.save_svg(). It reaches the same goal through the exporter whose defects the previous section lists, and inherits them.

Color on a pipe

--output - prints a capture’s escape sequences through click.echo, so they are dropped when the destination turns out to be a pipe and kept when --color=always says so. Pygments settled on that same contract and has not shipped it:

  • pygments#2491 - Pygmentize ansi escape sequences are stripped from Windows pipes: open since 2023, pygmentize | less losing every color on Windows.

  • pygments#2492: closed in 2024 as too breaking a change of default, but not before the maintainer settled the design: “Add an option to strip escapes (off/auto/on), make it work on all platforms, and default it to off everywhere. (Where ‘auto’ means the usual isatty check).”

  • pygments#2802: a contributor built that flag in November 2024. Still open, still unreviewed, no comments.

NO_COLOR has never been asked of Pygments at all, which makes click-extra’s support for it a first rather than a fix.

Screenshots in a project’s own documentation

  • click#3081 - Add Screenshot workflow: open since 2025, asking for a way to picture an annotated help screen in Click’s own documentation. Four requirements settled in the thread: it runs locally for doc generation, it runs in CI, it adds only pip-installable dependencies (no ImageMagick, no external service), and the image is not blurry. The screenshot command and the :screenshot: directive option meet all four, drawing vector SVG from a live invocation at build time. The labelled boxes the report also asks for are not covered: click-extra bands whole rows and draws no callouts. Click settled for hand-annotating one screenshot in click#3472, merged in 2026, and the general request stays open.

Themes and palettes

click-extra ships seven built-in themes (dark, light, dracula, monokai, nord, solarized-dark, plus a monochrome manpage) and covers every surface an end user reaches for: the --theme flag, a machine-wide CLICK_EXTRA_THEME variable next to the per-CLI <CLI>_THEME, and — alone in the Click ecosystem — new themes and overrides declared in the CLI’s own --config file ([tool.<cli>.themes.<name>]). Click itself has no theme system (formatter-level customization is still WIP upstream), and the competing rich help layers stop at Python and environment variables:

Configuration validation

click-extra’s config pipeline exposes an extension hook (ConfigValidator) so apps can validate data-keyed sub-tables ([tool.<cli>.managers.<id>], [tool.<cli>.plugins], …) inside the same strict-check pipeline that polices CLI-flag-bound keys. --validate-config collects every error before exiting and surfaces all failures with the same rooted ValidationError shape.

This is click-extra’s own design rather than an answer to a documented upstream issue: Click maintainers have closed every config-file proposal as out of scope (#1753, #386, #42, #971) and deferred to external packages, so validation of those external config files lives wherever each package chose to put it. The closest related upstream gap is click_config_file#11 (listed under Configuration files above): “warn when providing unsupported options in the config file?”: open since 2018 in an unmaintained package.

Configuration files

click-extra’s config module replaces the functionality of several unmaintained packages, with multi-format support (TOML, YAML, JSON, INI, XML), pyproject.toml integration, and a precedence chain:

Version option

click-extra’s VersionOption adds template variables for git metadata (branch, hash, date, tag), environment info, and Python/OS details, with pre-baking for compiled binaries:

Environment variables

click-extra auto-generates environment variables for all options and adds show_envvar as a global context setting:

Logging

click-extra’s logging module replaces the unmaintained click-log package:

Progress spinner

click-extra’s Spinner is a thread-animated, indeterminate progress spinner for blocking work of unknown duration. It supersedes the click-spinner package, last released in 2020 and openly looking for a maintainer since 2022. By design it resolves the issues and pull requests still open or rejected against that package:

Progress bar

click-extra’s progressbar is a drop-in for click.progressbar that also repairs a rendering bug in Click’s own bar:

Option parsing

Multi-value options

click-extra’s MultiChoice type parses a single comma-separated token into a tuple of validated values: the pick-many counterpart to click.Choice, with a rendered [a,b,c] metavar that mirrors click.Choice’s [a|b|c] and per-value highlighting in the help colorizer. The canonical Click idiom for this is multiple=True + Choice, which requires the flag to be repeated (--tag a --tag b --tag c); SQL SELECT a, b, c-style syntax has been requested upstream multiple times and not shipped:

The --columns option on --params is the headline consumer; the type is also exposed at the package root for arbitrary tags / categories / modes use cases in downstream CLIs.

Normalized arguments

Command tree

click-extra ships a --tree flag on every command, the standalone @tree_option decorator, and a wrap --tree mode that prints the subcommand hierarchy of any Click CLI without running it. That covers the whole checklist of the tree view rich-click has planned for its 1.10 release:

  • rich-click#269 - --tree integration: open since August 2025, filed by rich-click’s maintainer after the author of the treeclick plugin reached out. It proposes a @click.tree_option() decorator, a rich-click --tree [cmd] mode working on any Click CLI, and opt-in wiring through context_settings: the same three surfaces click-extra exposes (default option, standalone decorator, CLI wrapper).

  • rich-click#270 - Subcommand tree view: first-concept PR from treeclick’s author, self-described as rough and stalled since September 2025.

  • rich-click#275 - 1.10 roadmap: tracks the --tree option and @click.tree_option() among the planned features.

One design difference: treeclick embeds the tree inside --help output through custom TreeGroup/TreeCommand classes, and the older click-command-tree package registers a tree subcommand on the user’s group, walking the static commands mapping. click-extra renders the view behind a dedicated eager flag, like --man and --params: the plain help screen stays untouched, a flag cannot collide with the user’s own subcommand namespace, and the live-context walk includes lazily-registered commands.

Machine-readable help

click-extra renders any command as json, json-full, markdown, markdown-full, man or carapace through --help-format, and wrap --help-format applies the same renderings to a foreign Click CLI without running it. rich-click is designing that surface now, and arrived at nearly the same one:

  • rich-click#337 - Machine-readable CLI help: v2: a draft PR by rich-click’s maintainer, open since August 2026, proposing --help with an optional format argument taking markdown, markdown-full, json, json-full and carapace. That is click-extra’s list minus man, reached independently: the PR describes finding Carapace through its own research, and adds a command-examples syntax taking a description and a command, which is the shape of click-extra’s examples=[("description", "command")] argument.

  • rich-click#335 - rich-click CLI: --output json for machine-readable help: the same idea one layer out, putting the JSON rendering on rich-click’s own wrapper CLI so any Click CLI reaches it, which is what wrap --help-format does. Draft, stacked on a PR since superseded by #337.

The two answer different questions, and only one of them is click-extra’s. wrap --help-format renders a rich-click CLI today (verified against rich-click 1.9.8), but it is a tool installed beside the target, so it serves whoever wants the output now. A flag on rich-click’s own commands is what reaches the end users of a CLI already shipped, and no external renderer can supply that.

Shell completion

click-extra exports any Click command tree to a Carapace completion spec, so a CLI gets native completion across every shell Carapace supports (Bash, Zsh, Fish, Nushell, PowerShell, Elvish, and more) from a single generated file. Click’s built-in completion is wired per shell and installed by hand; one spec covers them all:

ANSI rendering in documentation

click-extra provides ANSI-capable session lexers, an HTML formatter, and Sphinx integration for rendering ANSI-colored CLI output in documentation, with 24-bit true-color rendering enabled by default:

Sphinx click:source and click:run directives

click-extra maintains and extends the click:source/click:run directives originally from pallets-sphinx-themes:

Sphinx python:* directives and live document rendering

The python:source, python:run, python:render, python:render-myst, and python:render-rst directives extend the click:* family to arbitrary Python (no Click CLI required). The render* variants parse the captured stdout as live document content: generated tables, headings, admonitions, and cross-references become first-class document nodes rather than a code block. This replaces the docs_update.py regenerator + marker-region pattern that many downstream click-extra-consuming projects use (Meta Package Manager, Mail Deduplicate, …), so the rendered HTML is always current at build time without a separate generation step. And when the generated Markdown should also live in the committed source (rendering on GitHub, reviewable in diffs), the :mirror: flag keeps a marker-delimited copy below the fence, refreshed by click-extra refresh-directives alongside the {matrix} blocks.

The click:* half of this story remains stuck upstream: the directives still live in pallets-sphinx-themes despite open requests to move them where Click users actually look for them, and MyST integration is unfinished:

The python:* half (rendering executed Python output as live document content) is click-extra’s own design rather than an answer to a single upstream ticket.

Declined by upstream

PRs and features rejected by upstream maintainers. click-extra provides the functionality regardless.

python-tabulate

click-extra maintains a local patch for GitHub-Flavored Markdown table alignment.

dbcli/cli_helpers

Open upstream

PRs and issues still pending upstream.

carapace-sh/carapace-spec

click

cloup

pygments

python-tabulate

executablebooks/MyST-Parser

  • #1119 - Batch syntax for consecutive same-type directives

  • content_offset is inflated by one when a directive’s option block precedes a body ending in blank line(s), shifting the reported source line of every body element. Worked around for myst-parser <= 5.1.0 in click_extra.sphinx.click._myst_content_offset_inflation; a root-cause fix is prepared upstream. (The earlier block_text alignment #1048 was superseded and merged as #1164, still unreleased.)

  • The open rework #1175 makes a directive’s content_offset document-relative (the rST convention) but does not fix the inflation above. When it releases, click_extra.sphinx.click.ClickDirective.abs_content_offset collapses onto its rST branch (content_offset verbatim) instead of adding directive.lineno; see the sketch tracked for that migration.

click-contrib/click-log