Version

Click Extra provides its own version option which, compared to Click’s built-in:

Defaults

Here is how the defaults looks like:

import click
import click_extra

@click.command
@click_extra.version_option(fields={"version": "1.2.3"})
def cli():
    pass
$ cli --help
Usage: cli [OPTIONS]

Options:
  --version  Show the version and exit.
  --help     Show this message and exit.

The default version message is the same as Click’s default, but colored:

$ cli --version
cli, version 1.2.3

Hint

In the examples of this page the version is hard-coded to 1.2.3 for the sake of demonstration.

In most cases, you do not need to force it, as the version will be automatically fetched from the package metadata of the CLI or the __version__ attribute of the command.

Variables

The message template is a format string, which defaults to:

f"{prog_name}, version {version}"

Caution

This is different from Click, which uses the %(prog)s, version %(version)s template.

Click is based on old-school printf-style formatting, which relies on variables of the %(variable)s form.

Click Extra uses modern format string syntax, with variables of the {variable} form, to provide a more flexible and powerful templating.

You can customize the message template with the following variables:

Variable

Description

{module}

The module object in which the command is implemented.

{module_name}

The __name__ of the module in which the command is implemented.

{module_file}

The full path of the file in which the command is implemented.

{module_version}

The string found in the local __version__ variable of the module.

{package_name}

The name of the package in which the CLI is distributed.

{package_version}

The version from the package metadata in which the CLI is distributed.

{author}

The package author(s) from the core metadata, or None if not declared.

{license}

The package license from the core metadata: SPDX License-Expression, classifier, or free-form License.

{exec_name}

User-friendly name of the executed CLI. Returns {module_name}, {package_name} or script’s filename, in this order.

{version}

Version of the CLI. Returns {module_version}, {package_version} or None, in this order. For .dev versions, automatically appends the Git commit hash.

{git_repo_path}

The full path to the Git repository root directory, or None if not in a Git repository.

{git_branch}

The current Git branch name, or None if not in a Git repository or Git is not available.

{git_long_hash}

The full Git commit hash of the current HEAD, or None if not in a Git repository or Git is not available.

{git_short_hash}

The short Git commit hash of the current HEAD, or None if not in a Git repository or Git is not available.

{git_date}

The commit date of the current HEAD in ISO format (YYYY-MM-DD HH:MM:SS +ZZZZ), or None if not in a Git repository or Git is not available.

{git_tag}

The Git tag pointing at HEAD, or None if HEAD is not at a tagged commit.

{git_tag_sha}

The full commit SHA that the current tag points at, or None if HEAD is not at a tagged commit.

{git_distance}

The number of commits since the most recent tag, or None if no tag is reachable or Git is not available.

{git_dirty}

The work-tree state: dirty for uncommitted changes, clean otherwise, or None if not in a Git repository or Git is not available.

{prog_name}

The display name of the program. Defaults to Click’s info_name, but can be overridden via prog_name on the command decorator.

{env_info}

The environment information in JSON.

Note

The git_* variables are evaluated at runtime by calling git. They return None in environments where Git is not available (like standalone Nuitka binaries or Docker containers without Git).

All git_* fields can be pre-baked at build time by defining __<field>__ dunder variables in the CLI module. Pre-baked values take priority over subprocess calls.

The hash, date, branch, tag and distance fields also fall back to a .git_archival.json file, so they keep working when a CLI runs from a git archive export (like a GitHub source tarball) that has no .git directory.

Hint

The {version} variable is resolved in this order:

  1. A __version__ variable defined alongside your CLI (see standalone scripts).

  2. A __version__ variable in the parent package’s __init__.py (for __main__ entry points, like Nuitka-compiled binaries).

  3. The version from package metadata via importlib.metadata: this is the most common source for installed packages.

  4. None if none of the above succeeds (like unpackaged scripts without __version__).

Both {exec_name} and {version} are derived through a short fallback chain ({version} additionally appends the Git short hash for .dev builds):

        flowchart TD
    subgraph EXEC["{exec_name}"]
        direction TB
        x1["{module_name}"] -->|"is __main__"| x2["{package_name}"]
        x2 -->|"not packaged"| x3["script filename"]
    end
    subgraph VER["{version}"]
        direction TB
        m["{module_version}"] -->|unset| p["{package_version}"]
        p -->|unset| nil["None"]
    end
    VER -.->|"if .dev without +local, and git available"| gh["append the Git short hash<br/>for example 1.2.3.dev0+abc1234"]
    click EXEC "#click_extra.version.VersionOption.exec_name" "exec_name property"
    click x1 "#click_extra.version.VersionOption.module_name" "module_name property"
    click x2 "#click_extra.version.VersionOption.package_name" "package_name property"
    click VER "#click_extra.version.VersionOption.version" "version property"
    click m "#click_extra.version.VersionOption.module_version" "module_version property"
    click p "#click_extra.version.VersionOption.package_version" "package_version property"
    

Error

Some Click’s built-in variables are not recognized:

  • %(package)s should be replaced by {package_name}

  • %(prog)s should be replaced by {prog_name}

  • All other %(variable)s should be replaced by their {variable} counterpart

You can compose your own version string by passing the message argument:

import click
import click_extra

@click.command
@click_extra.version_option(
    message="✨ {prog_name} v{version} - {package_name}",
    fields={"version": "1.2.3"},
)
def my_own_cli():
    pass
$ my-own-cli --version
✨ my-own-cli v1.2.3 - click_extra.sphinx

Caution

This results reports the package name as click_extra.sphinx because we are running the example from the click-extra documentation build environment. This is just a quirk of the documentation setup and will not affect your own CLI.

Overriding variables from the command

The version_fields parameter on @command and @group lets you override any template field without touching the default params list.

Fields can also be forced directly on the VersionOption instance via the params= argument:

import click
from click_extra import VersionOption

@click.command(params=[
    VersionOption(
        message="{prog_name} {version} (branch: {git_branch})",
        fields={
            "prog_name": "Acme CLI",
            "version": "42.0",
            "git_branch": "release/42",
        },
    ),
])
def acme():
    pass
$ acme --version
Acme CLI 42.0 (branch: release/42)

Standalone script

The --version option works with standalone scripts.

Let’s put this code in a file named greet.py:

greet.py
 1#!/usr/bin/env -S uv run --script
 2# /// script
 3# dependencies = ["click-extra"]
 4# ///
 5
 6import click_extra
 7
 8
 9@click_extra.command
10def greet():
11    print("Hello world")
12
13
14if __name__ == "__main__":
15    greet()

Here is the result of the --version option:

$ greet --version
greet, version None

Because the script is not packaged, the {version} variable is None.

But Click Extra recognize the __version__ variable, to force it in your script:

greet.py
 1#!/usr/bin/env -S uv run --script
 2# /// script
 3# dependencies = ["click-extra"]
 4# ///
 5
 6import click_extra
 7
 8
 9__version__ = "0.9.3-alpha"
10
11
12@click_extra.command
13def greet():
14    print("Hello world")
15
16
17if __name__ == "__main__":
18    greet()
$ greet --version
greet, version 0.9.3-alpha

Caution

The __version__ variable is not an enforced Python standard and more like a tradition.

It is supported by Click Extra as a convenience for script developers.

Development versions

When the version string contains .dev (as in PEP 440 development releases), Click Extra automatically appends the Git short commit hash as a PEP 440 local version identifier.

This lets you identify exactly which commit a development build was produced from:

import click
import click_extra

__version__ = "1.2.3.dev0"

@click.command
@click_extra.version_option()
def dev_cli():
    pass
$ dev-cli --version
dev-cli, version 1.2.3.dev0+08c7736

For example, a version like 1.2.3.dev0 becomes 1.2.3.dev0+6e59c8c1 during development. Release versions (without .dev) are never modified.

If Git is not available or the CLI is not running from a Git repository, the plain .dev version is returned as-is.

Pre-baked versions

If the version string already contains a + (a PEP 440 local version identifier), Click Extra assumes the hash was pre-baked at build time and returns the version as-is, without appending a second hash.

This is useful for CI pipelines or Nuitka binaries where git is not available at runtime but the build step can inject the commit hash into __version__ before compilation:

import click
import click_extra

__version__ = "1.2.3.dev0+abc1234"

@click.command
@click_extra.version_option()
def prebaked_cli():
    pass
$ prebaked-cli --version
prebaked-cli, version 1.2.3.dev0+abc1234

Hint

Click Extra ships prebake_version(), a utility to automate this injection. It parses a Python source file with ast, locates the __version__ assignment, and appends a +<local_version> suffix in place. Call it in your build step before Nuitka/PyInstaller compilation.

Version lifecycle

The version resolution adapts to the runtime environment:

Scenario

__version__ in source

Git available?

{version} output

Local dev (from source)

1.0.0.dev0

Yes

1.0.0.dev0+abc1234

Nuitka binary (pre-baked)

1.0.0.dev0+abc1234

No

1.0.0.dev0+abc1234

Nuitka binary (not pre-baked)

1.0.0.dev0

No

1.0.0.dev0

Release

1.0.0

N/A

1.0.0

For Nuitka binaries, the recommended workflow is to inject the commit hash into __version__ before compilation. Repomatic automates this via its prebake-version command.

Pre-baking git metadata

All git_* template fields support pre-baking. If the CLI module defines a __<field>__ dunder variable with a non-empty string value, that value is used instead of calling git at runtime. This is the recommended approach for compiled binaries (Nuitka, PyInstaller) where git is unavailable.

The supported dunders are:

Dunder variable

Template field

Subprocess fallback

__git_branch__

{git_branch}

git rev-parse --abbrev-ref HEAD

__git_long_hash__

{git_long_hash}

git rev-parse HEAD

__git_short_hash__

{git_short_hash}

git rev-parse --short HEAD

__git_date__

{git_date}

git show -s --format=%ci HEAD

__git_tag__

{git_tag}

git describe --tags --exact-match HEAD

__git_tag_sha__

{git_tag_sha}

git rev-list -1 <tag> (if {git_tag} resolves)

__git_distance__

{git_distance}

git describe --tags --long (commit count parsed)

__git_dirty__

{git_dirty}

git status --porcelain (mapped to dirty/clean)

To pre-bake a value, declare the dunder with an empty string placeholder in your __init__.py:

mypackage/__init__.py
__version__ = "1.0.0.dev0"
__git_branch__ = ""
__git_short_hash__ = ""

Then inject values at build time using prebake_dunder():

from pathlib import Path
from click_extra.prebake import prebake_dunder

prebake_dunder(Path("mypackage/__init__.py"), "__git_branch__", "main")
prebake_dunder(Path("mypackage/__init__.py"), "__git_short_hash__", "abc1234")

prebake_dunder() only replaces empty strings, so running it twice is safe (idempotent). It preserves the quoting style and surrounding file content.

discover_package_init_files() can auto-discover __init__.py paths from [project.scripts] in pyproject.toml, so you don’t need to hardcode paths in your build scripts.

CLI usage

The click-extra prebake command exposes these utilities from the command line, without writing Python:

$ # Bake __version__ and all git fields in one pass
$ click-extra prebake all

$ # Only inject Git hash into __version__
$ click-extra prebake version
$ click-extra prebake version --hash abc1234

$ # Set a specific field (double underscores added automatically)
$ click-extra prebake field git_tag_sha abc123def456...
$ click-extra prebake field git_branch main --module mypackage/__init__.py

All subcommands resolve the target file by precedence: an explicit --module, then the module key of the [tool.click-extra.prebake] configuration, then auto-discovery from [project.scripts] in pyproject.toml. Pin the target once to drop --module from repeated build invocations:

[tool.click-extra.prebake]
module = "mypackage/__init__.py"

Git metadata in archives

The git_* variables normally shell out to git, so they go blank when a CLI runs from a tree that has no .git directory. The most common case is a source archive: the tar.gz GitHub generates for a tag, or any git archive export.

Git can bake the metadata into such archives at export time. Commit a .git_archival.json file holding git archive placeholders, and mark it for substitution in .gitattributes:

.git_archival.json
{
    "node": "$Format:%H$",
    "node-date": "$Format:%cI$",
    "describe-name": "$Format:%(describe:tags=true,match=*[0-9]*)$",
    "ref-names": "$Format:%D$"
}
.gitattributes
.git_archival.json  export-subst

When git archive packs the file (GitHub does this for its source tarballs), it replaces each $Format:…$ token with the real value. Click Extra reads the result and populates {git_long_hash}, {git_short_hash}, {git_date}, {git_branch}, {git_tag}, {git_tag_sha} and {git_distance} from it. {git_dirty} is not covered: an archive has no work tree, so its state is unknowable.

Note

This is the schema used by setuptools-scm and Dunamai, so a single committed .git_archival.json works with all three.

Important

Substitution happens only inside git archive output. In a normal checkout the file still holds the literal $Format:…$ placeholders, which Click Extra ignores in favor of live git calls. The resolution order for each field is: pre-baked dunder, then live git, then .git_archival.json.

Colors

Each variable listed in the section above can be rendered in its own style. Pass a styles mapping to the version_option decorator to set the style of individual fields, keyed by field name:

  • styles={"version": Style(fg="green")} paints the {version} field green.

  • styles={"version": None} clears the field’s style, so it falls back to message_style.

The message_style parameter sets the style of the message literals (the text around the fields) and of any field that has no style of its own. It defaults to None (no color).

Fields not listed in styles keep the defaults below, taken from VersionOption.default_styles. Fields absent from this table have no style of their own and fall back to message_style:

Field

Default style

exec_name

BUILTIN_THEMES["dark"].invoked_command

git_branch

Style(fg="cyan")

git_date

Style(fg="bright_black")

git_distance

Style(fg="green")

git_dirty

Style(fg="red")

git_long_hash

Style(fg="yellow")

git_repo_path

Style(fg="bright_black")

git_short_hash

Style(fg="yellow")

git_tag

Style(fg="cyan")

git_tag_sha

Style(fg="yellow")

module_name

BUILTIN_THEMES["dark"].invoked_command

module_version

Style(fg="green")

package_name

BUILTIN_THEMES["dark"].invoked_command

package_version

Style(fg="green")

prog_name

BUILTIN_THEMES["dark"].invoked_command

version

Style(fg="green")

env_info

Style(fg="bright_black")

The remaining fields (module, module_file, author, license) have no default style and fall back to message_style.

Here is an example:

import click
from click_extra import version_option, Style

@click.command
@version_option(
    message="{prog_name} v{version} 🔥 {package_name} ( ͡❛ ͜ʖ ͡❛)",
    message_style=Style(fg="cyan"),
    styles={
        "prog_name": Style(fg="green", bold=True),
        "version": Style(fg="bright_yellow", bg="red"),
        "package_name": Style(fg="bright_blue", italic=True),
    },
    fields={"version": "1.2.3"},
)
def cli():
    pass
$ cli --version
cli v1.2.3 🔥 click_extra.sphinx ( ͡❛ ͜ʖ ͡❛)

You can pass None as a field’s style to disable styling for the corresponding variable, and set message_style=None to strip the style of the message literals:

import click
from click_extra import version_option

@click.command
@version_option(
    message_style=None,
    styles={"version": None, "prog_name": None},
    fields={"version": "1.2.3"},
)
def cli():
    pass
$ cli --version
cli, version 1.2.3

Environment information

The {env_info} variable compiles all sorts of environment information.

Here is how it looks like:

import click
from click_extra import version_option

@click.command
@version_option(message="{env_info}")
def env_info_cli():
    pass
$ env-info-cli --version
{'username': '-', 'guid': '22f57e0789c121b5aef96f55c2905ca', 'hostname': '-', 'hostfqdn': '-', 'uname': {'system': 'Linux', 'node': '-', 'release': '6.1.146.1-microsoft-standard', 'version': '#1 SMP Mon Jul 21 20:38:16 UTC 2025', 'machine': 'x86_64', 'processor': 'x86_64'}, 'linux_dist_name': '', 'linux_dist_version': '', 'cpu_count': 1, 'fs_encoding': 'utf-8', 'ulimit_soft': 1048576, 'ulimit_hard': 1048576, 'cwd': '-', 'umask': '0o2', 'python': {'argv': '-', 'bin': '-', 'version': '3.14.6 (main, Jun 11 2026, 04:03:53) [Clang 22.1.3 ]', 'compiler': 'Clang 22.1.3 ', 'build_date': 'Jun 11 2026 04:03:53', 'version_info': [3, 14, 6, 'final', 0], 'features': {'openssl': 'OpenSSL 3.5.7 9 Jun 2026', 'expat': 'expat_2.8.1', 'sqlite': '3.53.1', 'tkinter': '9.0', 'zlib': '1.3.1', 'unicode_wide': True, 'readline': True, '64bit': True, 'ipv6': True, 'threading': True, 'urandom': True}}, 'time_utc': '2026-06-22 15:32:47.664928+00:00', 'time_utc_offset': 0.0, '_eco_version': '1.1.0'}

It’s verbose but it’s helpful for debugging and reporting of issues from end users.

Important

The JSON output is scrubbed out of identifiable information by default: current working directory, hostname, Python executable path, command-line arguments and username are replaced with -.

Another trick consist in picking into the content of {env_info} to produce highly customized version strings. This can be done because {env_info} is kept as a dict:

import click
from click_extra import version_option

@click.command
@version_option(
    message="{prog_name} {version}, from {module_file} (Python {env_info[python][version]})",
    fields={"version": "1.2.3"},
)
def custom_env_info():
    pass
$ custom-env-info --version
custom-env-info 1.2.3, from /home/runner/work/click-extra/click-extra/click_extra/sphinx/click.py (Python 3.14.6 (main, Jun 11 2026, 04:03:53) [Clang 22.1.3 ])

Debug logs

When the DEBUG level is enabled, all available variables will be printed in the log:

import click
from click_extra import version_option, verbosity_option, echo

@click.command
@version_option(fields={"version": "1.2.3"})
@verbosity_option
def version_in_logs():
    echo("Standard operation")

Which is great to see how each variable is populated and styled:

$ version-in-logs --verbosity DEBUG
debug: Set <Logger click_extra (DEBUG)> to DEBUG.
debug: Set <RootLogger root (DEBUG)> to DEBUG.
debug: 'click_extra.sphinx' package not found or not installed.
debug: Version string template variables:
debug: {module}         : <module 'click_extra.sphinx.click' from '/home/runner/work/click-extra/click-extra/click_extra/sphinx/click.py'>
debug: {module_name}    : click_extra.sphinx.click
debug: {module_file}    : /home/runner/work/click-extra/click-extra/click_extra/sphinx/click.py
debug: {module_version} : 1.2.3.dev0+abc1234
debug: {package_name}   : click_extra.sphinx
debug: {package_version}: None
debug: {author}         : None
debug: {license}        : None
debug: {exec_name}      : click_extra.sphinx.click
debug: {version}        : 1.2.3
debug: {git_repo_path}  : /home/runner/work/click-extra/click-extra
debug: {git_branch}     : main
debug: {git_long_hash}  : 08c7736b6ce9932ce275ee06a2d2ce7f9b82aa3f
debug: {git_short_hash} : 08c7736
debug: {git_date}       : 2026-06-22 19:19:53 +0400
debug: {git_tag}        : None
debug: {git_tag_sha}    : None
debug: {git_distance}   : None
debug: {git_dirty}      : clean
debug: {prog_name}      : version-in-logs
debug: {env_info}       : {'username': '-', 'guid': '22f57e0789c121b5aef96f55c2905ca', 'hostname': '-', 'hostfqdn': '-', 'uname': {'system': 'Linux', 'node': '-', 'release': '6.1.146.1-microsoft-standard', 'version': '#1 SMP Mon Jul 21 20:38:16 UTC 2025', 'machine': 'x86_64', 'processor': 'x86_64'}, 'linux_dist_name': '', 'linux_dist_version': '', 'cpu_count': 1, 'fs_encoding': 'utf-8', 'ulimit_soft': 1048576, 'ulimit_hard': 1048576, 'cwd': '-', 'umask': '0o2', 'python': {'argv': '-', 'bin': '-', 'version': '3.14.6 (main, Jun 11 2026, 04:03:53) [Clang 22.1.3 ]', 'compiler': 'Clang 22.1.3 ', 'build_date': 'Jun 11 2026 04:03:53', 'version_info': [3, 14, 6, 'final', 0], 'features': {'openssl': 'OpenSSL 3.5.7 9 Jun 2026', 'expat': 'expat_2.8.1', 'sqlite': '3.53.1', 'tkinter': '9.0', 'zlib': '1.3.1', 'unicode_wide': True, 'readline': True, '64bit': True, 'ipv6': True, 'threading': True, 'urandom': True}}, 'time_utc': '2026-06-22 15:32:47.664928+00:00', 'time_utc_offset': 0.0, '_eco_version': '1.1.0'}
Standard operation
debug: Reset <RootLogger root (DEBUG)> to WARNING.
debug: Reset <Logger click_extra (DEBUG)> to WARNING.

Get metadata values

You can get the uncolored, Python values used in the composition of the version message from the context:

import click
from click_extra import echo, pass_context, version_option

@click.command
@version_option(fields={"version": "1.2.3"})
@pass_context
def version_metadata(ctx):
    version = ctx.meta["click_extra.version"]
    package_name = ctx.meta["click_extra.package_name"]
    prog_name = ctx.meta["click_extra.prog_name"]
    env_info = ctx.meta["click_extra.env_info"]

    echo(f"version = {version}")
    echo(f"package_name = {package_name}")
    echo(f"prog_name = {prog_name}")
    echo(f"env_info = {env_info}")
$ version-metadata --version
version-metadata, version 1.2.3
$ version-metadata
version = 1.2.3
package_name = click_extra.sphinx
prog_name = version-metadata
env_info = {'username': '-', 'guid': '22f57e0789c121b5aef96f55c2905ca', 'hostname': '-', 'hostfqdn': '-', 'uname': {'system': 'Linux', 'node': '-', 'release': '6.1.146.1-microsoft-standard', 'version': '#1 SMP Mon Jul 21 20:38:16 UTC 2025', 'machine': 'x86_64', 'processor': 'x86_64'}, 'linux_dist_name': '', 'linux_dist_version': '', 'cpu_count': 1, 'fs_encoding': 'utf-8', 'ulimit_soft': 1048576, 'ulimit_hard': 1048576, 'cwd': '-', 'umask': '0o2', 'python': {'argv': '-', 'bin': '-', 'version': '3.14.6 (main, Jun 11 2026, 04:03:53) [Clang 22.1.3 ]', 'compiler': 'Clang 22.1.3 ', 'build_date': 'Jun 11 2026 04:03:53', 'version_info': [3, 14, 6, 'final', 0], 'features': {'openssl': 'OpenSSL 3.5.7 9 Jun 2026', 'expat': 'expat_2.8.1', 'sqlite': '3.53.1', 'tkinter': '9.0', 'zlib': '1.3.1', 'unicode_wide': True, 'readline': True, '64bit': True, 'ipv6': True, 'threading': True, 'urandom': True}}, 'time_utc': '2026-06-22 15:32:47.664928+00:00', 'time_utc_offset': 0.0, '_eco_version': '1.1.0'}

Hint

These variables are presented in their original Python type. If most of these variables are strings, others like env_info retains their original dict type.

Note

Metadata values in ctx.meta are lazily evaluated: a field like env_info or git_long_hash is only computed the first time you access it. If your command only reads ctx.meta["click_extra.version"], the expensive Git subprocess calls and environment profiling are never executed.

Template rendering

You can render the version string manually by calling the option’s internal methods:

import click
from click_extra import echo, pass_context, version_option, VersionOption, search_params

@click.command
@version_option(fields={"version": "1.2.3"})
@pass_context
def template_rendering(ctx):
    # Search for a ``--version`` parameter.
    version_opt = search_params(ctx.command.params, VersionOption)
    version_string = version_opt.render_message()
    echo(f"Version string ~> {version_string}")

Hint

To fetch the --version parameter defined on the command, we rely on click_extra.search_params.

$ template-rendering --version
template-rendering, version 1.2.3
$ template-rendering
Version string ~> template-rendering, version 1.2.3

That way you can collect the rendered version_string, as if it was printed to the terminal by a call to --version, and use it in your own way.

Other internal methods to build-up and render the version string are available in the API below.

click_extra.version API

        classDiagram
  ExtraOption <|-- VersionOption
    

Introspect CLI metadata at runtime and print a colored --version string.

VersionOption gathers the executed CLI’s metadata (module and package names, distribution version, author and license, environment profile, and the live Git state) and renders them through a customizable, colorized message template.

Git fields (git_branch, git_short_hash, …) are resolved at runtime by shelling out to git, with two fallbacks for git-less environments: a pre-baked __<field>__ dunder in the CLI module (injected before build by click_extra.prebake), then a committed .git_archival.json populated by git archive.

click_extra.version.GIT_FIELDS: dict[str, tuple[str, ...]] = {'git_branch': ('rev-parse', '--abbrev-ref', 'HEAD'), 'git_date': ('show', '-s', '--format=%ci', 'HEAD'), 'git_long_hash': ('rev-parse', 'HEAD'), 'git_short_hash': ('rev-parse', '--short', 'HEAD'), 'git_tag': ('describe', '--tags', '--exact-match', 'HEAD')}

Git fields whose live value is the stripped output of one static git subcommand, mapped to that subcommand’s args.

git_tag_sha, git_distance and git_dirty are excluded: their resolution is not a single static git invocation whose stripped output is the value. git_tag_sha dereferences the tag (git rev-list -1 <tag>), git_distance parses git describe and git_dirty maps the porcelain status to a label. See resolve_git_tag_sha(), resolve_git_distance() and resolve_git_dirty().

For the resolver of every pre-bakeable git field (these five plus the three computed ones), keyed uniformly by field ID, see GIT_RESOLVERS.

click_extra.version.run_git(*args, cwd=None, allow_empty=False)[source]

Run a git command and return its stripped output, or None.

cwd defaults to the current working directory when not provided.

By default an empty output is collapsed to None (treated like a failure). Set allow_empty to keep an empty string instead, which some commands use meaningfully: git status --porcelain prints nothing for a clean work tree, and that is distinct from the command failing.

Return type:

str | None

click_extra.version.resolve_git_dirty(cwd=None)[source]

Report the work-tree state as "dirty", "clean" or None.

Returns "dirty" when git status --porcelain reports uncommitted changes, "clean" when it reports none, and None when the state cannot be determined (not a Git repository, or git is unavailable).

The empty output of a clean work tree is meaningful here, so the command is run with allow_empty to tell it apart from a failure.

Return type:

str | None

click_extra.version.resolve_git_distance(cwd=None)[source]

Count commits since the most recent tag, as a string, or None.

Parses git describe --tags --long, whose output has the form <tag>-<distance>-g<short_hash>. Returns None when no tag is reachable, the directory is not a Git repository, or git is unavailable.

Return type:

str | None

click_extra.version.resolve_git_tag_sha(cwd=None)[source]

Resolve the commit SHA the tag at HEAD points at, or None.

Runs git describe --tags --exact-match HEAD to find the tag, then git rev-list -1 <tag> to dereference it to a commit SHA. Returns None when HEAD is not at a tagged commit, the directory is not a Git repository, or git is unavailable.

Return type:

str | None

click_extra.version.GIT_RESOLVERS: dict[str, Callable[[Path | None], str | None]] = {'git_branch': <function _direct_git_resolver.<locals>.resolver>, 'git_date': <function _direct_git_resolver.<locals>.resolver>, 'git_dirty': <function resolve_git_dirty>, 'git_distance': <function resolve_git_distance>, 'git_long_hash': <function _direct_git_resolver.<locals>.resolver>, 'git_short_hash': <function _direct_git_resolver.<locals>.resolver>, 'git_tag': <function _direct_git_resolver.<locals>.resolver>, 'git_tag_sha': <function resolve_git_tag_sha>}

Canonical live resolver for every pre-bakeable git_* field.

Maps each field ID to a callable that takes an optional working directory and returns the field’s value by shelling out to git (or None when it cannot be resolved). This is the single source of truth for how each git field is computed live, shared by two consumers:

  • VersionOption’s runtime accessors, which wrap each resolver with the pre-baked-dunder and .git_archival.json fallbacks.

  • the click-extra prebake all command, which calls every resolver to bake values into source files at build time.

Keeping it here means adding a new git field is a one-line edit in this module, with no matching change needed in the CLI.

click_extra.version.find_archival_file(start)[source]

Walk up from start to find a .git_archival.json file.

Returns the first match in start or any of its parents, or None.

Return type:

Path | None

click_extra.version.read_archival(path)[source]

Parse a .git_archival.json file into a string mapping.

Returns an empty mapping when the file is missing, unreadable, or not a valid JSON object.

Return type:

dict[str, str]

click_extra.version.archival_field(data, field_id)[source]

Resolve a git_* field from parsed .git_archival.json data.

data follows the setuptools-scm archival schema: node (full hash), node-date, describe-name and ref-names. The same file is read by setuptools-scm and Dunamai, so a single committed .git_archival.json serves all three.

Returns None when the field is absent, empty, or still holds an unsubstituted $Format:…$ placeholder. That last case is what a plain checkout contains: git archive performs the substitution, so values are real only inside an exported archive (including GitHub’s source tarballs).

There is no entry for git_dirty: an archive has no work tree, so its state is unknowable.

Return type:

str | None

click_extra.version.resolve_distribution(names)[source]

Return the first installed distribution among names, or None.

Probes each candidate name in order with importlib.metadata.distribution() and returns the first that resolves to an installed distribution. Used to pick a distribution from a set of plausible spellings (for example the program name with - / _ variants) before reading its metadata.

Return type:

str | None

click_extra.version.meta_value(meta, *keys)[source]

Return the first non-empty value among core-metadata keys.

Accessed through in + [] (rather than .get()) to dodge the deprecated implicit-None return on missing keys.

Return type:

str | None

click_extra.version.resolve_author(meta)[source]

Return the author(s) from meta’s core metadata, or None.

Prefers the Author field, then the Maintainer field, then the display name parsed out of the Author-email / Maintainer-email fields (Name <email>). Returns None when meta is None or no author can be determined.

Return type:

str | None

click_extra.version.resolve_license(meta)[source]

Return the license from meta’s core metadata, or None.

Prefers the SPDX License-Expression field (core metadata 2.4+). Falls back to the human-readable name of the first License :: trove classifier, then to the free-form License field (which may hold the full license text). Returns None when meta is None or no license can be determined.

Return type:

str | None

class click_extra.version.VersionOption(param_decls=None, message=None, fields=None, styles=None, message_style=None, is_flag=True, expose_value=False, is_eager=True, help='Show the version and exit.', **kwargs)[source]

Bases: ExtraOption

Gather CLI metadata and prints a colored version string.

Note

This started as a copy of the standard @click.version_option() decorator, but is no longer a drop-in replacement. Hence the Extra prefix.

This address the following Click issues:

  • click#2324, to allow its use with the declarative params= argument.

  • click#2331, by distinguishing the module from the package.

  • click#1756, by allowing path and Python version.

Preconfigured as a --version option flag.

Parameters:
  • message (str | None) – the message template to print, in format string syntax. Defaults to {prog_name}, version {version}.

  • fields (Mapping[str, Any] | None) – mapping of template field name to a forced value, overriding the value auto-computed for that field. Keys must be members of template_fields (for example {"version": "1.2.3"}).

  • styles (Mapping[str, Callable[[str], str] | None] | None) – mapping of template field name to its Style, merged over default_styles. Pass None as a value to clear a field’s default style. Keys must be members of template_fields.

  • message_style (Callable[[str], str] | None) – fallback style for the message literals and for any field that has no style of its own.

template_fields: tuple[str, ...] = ('module', 'module_name', 'module_file', 'module_version', 'package_name', 'package_version', 'author', 'license', 'exec_name', 'version', 'git_repo_path', 'git_branch', 'git_long_hash', 'git_short_hash', 'git_date', 'git_tag', 'git_tag_sha', 'git_distance', 'git_dirty', 'prog_name', 'env_info')

List of field IDs recognized by the message template.

default_styles: ClassVar[dict[str, Callable[[str], str]]] = {'env_info': Style(fg='bright_black'), 'exec_name': Style(fg='bright_white', bold), 'git_branch': Style(fg='cyan'), 'git_date': Style(fg='bright_black'), 'git_dirty': Style(fg='red'), 'git_distance': Style(fg='green'), 'git_long_hash': Style(fg='yellow'), 'git_repo_path': Style(fg='bright_black'), 'git_short_hash': Style(fg='yellow'), 'git_tag': Style(fg='cyan'), 'git_tag_sha': Style(fg='yellow'), 'module_name': Style(fg='bright_white', bold), 'module_version': Style(fg='green'), 'package_name': Style(fg='bright_white', bold), 'package_version': Style(fg='green'), 'prog_name': Style(fg='bright_white', bold), 'version': Style(fg='green')}

Default style for each template field.

Fields absent from this mapping render with no style of their own and fall back to message_style (or no color when that is unset). User-provided styles are merged over these defaults.

message: str = '{prog_name}, version {version}'

Default message template used to render the version string.

styles: dict[str, Callable[[str], str] | None]
static cli_frame()[source]

Returns the frame in which the CLI is implemented.

Inspects the execution stack frames to find the package in which the user’s CLI is implemented.

Returns the frame itself.

Return type:

FrameType

property module: ModuleType[source]

Returns the module in which the CLI resides.

property module_name: str[source]

Returns the full module name or __main__.

property module_file: str | None[source]

Returns the module’s file full path.

property module_version: str | None[source]

Returns the string found in the local __version__ variable.

Hint

__version__ is an old pattern from early Python packaging. It is not a standard variable and is not defined in the packaging PEPs.

You should prefer using the package_version property below instead, which uses the standard library importlib.metadata API.

We’re still supporting it for backward compatibility with existing codebases, as Click removed it in version 8.2.0.

property package_name: str | None[source]

Returns the package name.

property package_version: str | None[source]

Returns the package version if installed.

Resolved from the distribution name (see _distribution_name) via importlib.metadata.version(). Returns None if the package is not installed or cannot be resolved.

property author: str | None[source]

Returns the package author(s) from its core metadata.

Delegates to resolve_author(): prefers the Author field, then the Maintainer field, then the display name parsed out of the Author-email / Maintainer-email fields (Name <email>). Returns None if no author can be determined.

property license: str | None[source]

Returns the package license from its core metadata.

Delegates to resolve_license(): prefers the SPDX License-Expression field, falls back to the human-readable name of the first License :: trove classifier, then to the free-form License field. Returns None if no license can be determined.

property exec_name: str[source]

User-friendly name of the executed CLI.

Returns the module name. But if the later is __main__, returns the package name.

If not packaged, the CLI is assumed to be a simple standalone script, and the returned name is the script’s file name (including its extension).

property version: str | None[source]

Return the version of the CLI.

Returns the module version if a __version__ variable is set alongside the CLI in its module.

Else returns the package version if the CLI is implemented in a package, using importlib.metadata.version().

For development versions (containing .dev), automatically appends the Git short hash as a PEP 440 local version identifier, producing versions like 1.2.3.dev0+abc1234. This helps identify the exact commit a dev build was produced from. If Git is unavailable, the plain dev version is returned.

Versions that already contain a + (a pre-baked local version identifier, typically set at build time by CI pipelines) are returned as-is to avoid producing invalid double-suffixed versions like 1.2.3.dev0+abc1234+xyz5678.

property git_repo_path: Path | None[source]

Find the Git repository root directory.

property git_branch: str | None[source]

Returns the current Git branch name.

Checks for a pre-baked __git_branch__ dunder first, then git rev-parse --abbrev-ref HEAD, then .git_archival.json.

property git_long_hash: str | None[source]

Returns the full Git commit hash.

Checks for a pre-baked __git_long_hash__ dunder first, then git rev-parse HEAD, then .git_archival.json.

property git_short_hash: str | None[source]

Returns the short Git commit hash.

Checks for a pre-baked __git_short_hash__ dunder first, then git rev-parse --short HEAD, then .git_archival.json (where it is derived from the first 7 characters of the full hash).

Hint

The short hash is usually the first 7 characters of the full hash, but this is not guaranteed to be the case.

But it is at least guaranteed to be unique within the repository, and a minimum of 4 characters.

property git_date: str | None[source]

Returns the commit date in ISO format: YYYY-MM-DD HH:MM:SS +ZZZZ.

Checks for a pre-baked __git_date__ dunder first, then git show -s --format=%ci HEAD, then .git_archival.json (whose node-date is strict ISO 8601, like 2021-01-01T12:00:00+00:00).

property git_tag: str | None[source]

Returns the Git tag pointing at HEAD, if any.

Checks for a pre-baked __git_tag__ dunder first, then git describe --tags --exact-match HEAD, then .git_archival.json.

Returns None if HEAD is not at a tagged commit.

property git_tag_sha: str | None[source]

Returns the commit SHA that the current tag points at.

Checks for a pre-baked __git_tag_sha__ dunder first, then git rev-list -1 on the tag returned by git_tag, then .git_archival.json. Returns None if HEAD is not at a tag.

property git_distance: str | None[source]

Number of commits since the most recent tag, or None.

Checks for a pre-baked __git_distance__ dunder first, then parses git describe --tags --long, then falls back to .git_archival.json. None when no tag is reachable or Git is unavailable.

property git_dirty: str | None[source]

Work-tree state: "dirty", "clean" or None.

Checks for a pre-baked __git_dirty__ dunder first, then runs git status --porcelain. None when not in a Git repository or Git is unavailable. There is no .git_archival.json fallback: an archive has no work tree, so its state is unknowable.

property prog_name: str | None[source]

Return the name of the CLI, from Click’s point of view.

Get the info_name of the root command.

property env_info: dict[str, Any][source]

Various environment info.

Returns the data produced by boltons.ecoutils.get_profile().

colored_template(template=None)[source]

Insert ANSI styles to a message template.

Accepts a custom template as parameter, otherwise uses the default message defined on the Option instance.

This step is necessary because we need to linearize the template to apply the ANSI codes on the string segments. This is a consequence of the nature of ANSI, directives which cannot be encapsulated within another (unlike markup tags like HTML).

Return type:

str

render_message(template=None)[source]

Render the version string from the provided template.

Accepts a custom template as parameter, otherwise uses the default self.colored_template() produced by the instance.

Return type:

str

print_debug_message()[source]

Render in debug logs all template fields in color.

Todo

Pretty print JSON output (easier to read in bug reports)?

Return type:

None

print_and_exit(ctx, param, value)[source]

Print the version string and exits.

Also stores all version string elements in the Context’s meta dict.

Return type:

None

click_extra.prebake API

Bake build-time metadata into Python source files before compilation.

Compiled binaries (Nuitka, PyInstaller) and git-less runtimes (Docker images, archive checkouts) cannot resolve version or Git metadata at runtime the way click_extra.version.VersionOption does. The values must instead be written into the source before the build, by rewriting the relevant dunder assignments (__version__, __git_short_hash__, …) in place with ast.

This mirrors shadow-rs, which injects build-time constants (BRANCH, SHORT_COMMIT, COMMIT_HASH, COMMIT_DATE, TAG, …) into Rust binaries at compile time.

Todo

Add the following build-time template fields, mirroring the constants shadow-rs injects:

  • {build_time}: when the distribution was built (shadow-rs exposes it as BUILD_TIME, with RFC 2822 and RFC 3339 variants BUILD_TIME_2822 / BUILD_TIME_3339).

  • {build_os} / {build_target} / {build_target_arch}: the OS, target triple and architecture the build ran on. These describe the build host, unlike {env_info} which reports the runtime Python, OS and architecture, so both are worth keeping for cross-built binaries.

click_extra.prebake.prebake_version(file_path, local_version)[source]

Pre-bake a __version__ string with a PEP 440 local version identifier.

Reads file_path, finds the __version__ assignment via ast, and, if the version contains .dev and does not already contain +, appends +<local_version>.

This is the compile-time complement to the runtime click_extra.version.VersionOption.version property: Nuitka/PyInstaller binaries cannot run git at runtime, so the hash must be baked into __version__ in the source file before compilation.

Returns the new version string on success, or None if no change was made (release version, already pre-baked, or no __version__ found).

Return type:

str | None

click_extra.prebake.prebake_dunder(file_path, name, value)[source]

Replace an empty dunder variable’s value in a Python source file.

Reads file_path, finds a top-level name = "" assignment via ast, and, if the current value is an empty string, replaces it with value.

Placeholders must use empty strings (__field__ = "", not None). The AST matcher only recognizes string literals, and the empty string acts as a falsy sentinel that stays type-consistent with baked values (always str).

This is the generic counterpart to prebake_version(): where prebake_version appends a PEP 440 local identifier to __version__, this function does a full replacement of any dunder variable that starts empty. Typical use case: injecting a release commit SHA into __git_tag_sha__ = "" at build time.

Returns the new value on success, or None if no change was made (variable not found, or already has a non-empty value).

Return type:

str | None

click_extra.prebake.discover_package_init_files()[source]

Discover __init__.py files from [project.scripts].

Reads the pyproject.toml in the current working directory, extracts [project.scripts] entry points, and returns the unique __init__.py paths for each top-level package.

Only returns paths that exist on disk. Returns an empty list if pyproject.toml is missing or has no [project.scripts].

Return type:

list[Path]