Version#

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

Hint

To prevent any confusion, and to keep on the promise of drop-in replacement, Click Extra’s version option is prefixed with extra_:

Defaults#

Here is how the defaults looks like:

from click_extra import command, extra_version_option

@command
@extra_version_option(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 this example I have hard-coded the version to 1.2.3 for the sake of demonstration. In most cases, you do not need to force it. By default, the version will be automattically fetched from the __version__ attribute of the module where the command is defined.

Variables#

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

"{prog_name}, version {version}"

Important

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.

{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.

{prog_name}

The name of the program, from Click’s point of view.

{env_info}

The environment information in JSON.

Caution

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:

from click_extra import command, extra_version_option

@command
@extra_version_option(message="✨ {prog_name} v{version} - {package_name}")
def my_own_cli():
   pass
$ my-own-cli --version
✨ my-own-cli v4.7.6 - click_extra

Note

Notice here how the {package_name} string takes the click_extra value. That’s because this snippet of code is dynamiccaly executed by Sphinx in the context of Click Extra itself. And as a result, the {version} string takes the value of the current version of Click Extra (i.e. click_extra.__version__).

You will not have this behavior once your get your CLI packaged: your CLI will properly inherits its metadata automatically from your package.

Standalone script#

The --version option works with standalone scripts.

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

from click_extra import extra_command


@extra_command
def greet():
    print("Hello world")


if __name__ == "__main__":
    greet()

Here is the result of the --version option:

$ python ./greet.py --version
greet.py, version None

Because the script is not packaged, the {package_name} is set to the script file name (greet.py) and {version} variable to None.

But Click Extra recognize a __version__ variable. So you can force the version string in your script:

from click_extra import extra_command


__version__ = "0.9.3-alpha"


@extra_command
def greet():
    print("Hello world")


if __name__ == "__main__":
    greet()
$ python ./greet.py --version
greet.py, 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.

Colors#

Each variable listed in the section above can be rendered in its own style. They all have dedicated parameters you can pass to the extra_version_option decorator:

Parameter

Description

message_style

Default style of the message.

module_style

Style of the {module} variable.

module_name_style

Style of the {module_name} variable.

module_file_style

Style of the {module_file} variable.

module_version_style

Style of the {module_version} variable.

package_name_style

Style of the {package_name} variable.

package_version_style

Style of the {package_version} variable.

exec_name_style

Style of the {exec_name} variable.

version_style

Style of the {version} variable.

prog_name_style

Style of the {prog_name} variable.

env_info_style

Style of the {env_info} variable.

Here is an example:

from click_extra import command, extra_version_option, Style

@command
@extra_version_option(
   message="{prog_name} v{version} 🔥 {package_name} ( ͡❛ ͜ʖ ͡❛)",
   message_style=Style(fg="cyan"),
   prog_name_style=Style(fg="green", bold=True),
   version_style=Style(fg="bright_yellow", bg="red"),
   package_name_style=Style(fg="bright_blue", italic=True),
)
def cli():
   pass
$ cli --version
cli v4.7.6 🔥 click_extra ( ͡❛ ͜ʖ ͡❛)

You can pass None to any of the style parameters to disable styling for the corresponding variable:

from click_extra import command, extra_version_option

@command
@extra_version_option(
    message_style=None,
    version_style=None,
    prog_name_style=None,
)
def cli():
   pass
$ cli --version
cli, version 4.7.6

Environment information#

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

Here is how it looks like:

from click_extra import command, extra_version_option

@command
@extra_version_option(message="{env_info}")
def env_info_cli():
   pass
$ env-info-cli --version
{'username': '-', 'guid': '75c54a7cf51dc9196409f82b708dfcd', 'hostname': '-', 'hostfqdn': '-', 'uname': {'system': 'Linux', 'node': '-', 'release': '6.5.0-1018-azure', 'version': '#19~22.04.2-Ubuntu SMP Thu Mar 21 16:45:46 UTC 2024', 'machine': 'x86_64', 'processor': 'x86_64'}, 'linux_dist_name': '', 'linux_dist_version': '', 'cpu_count': 4, 'fs_encoding': 'utf-8', 'ulimit_soft': 65536, 'ulimit_hard': 65536, 'cwd': '-', 'umask': '0o2', 'python': {'argv': '-', 'bin': '-', 'version': '3.12.3 (main, Apr 10 2024, 03:36:41) [GCC 11.4.0]', 'compiler': 'GCC 11.4.0', 'build_date': 'Apr 10 2024 03:36:41', 'version_info': [3, 12, 3, 'final', 0], 'features': {'openssl': 'OpenSSL 3.0.2 15 Mar 2022', 'expat': 'expat_2.6.0', 'sqlite': '3.37.2', 'tkinter': '8.6', 'zlib': '1.2.11', 'unicode_wide': True, 'readline': True, '64bit': True, 'ipv6': True, 'threading': True, 'urandom': True}}, 'time_utc': '2024-04-25 18:42:49.114292', '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:

from click_extra import command, extra_version_option

@command
@extra_version_option(
    message="{prog_name} {version}, from {module_file} (Python {env_info[python][version]})"
)
def custom_env_info():
   pass
$ custom-env-info --version
custom-env-info 4.7.6, from /home/runner/work/click-extra/click-extra/click_extra/testing.py (Python 3.12.3 (main, Apr 10 2024, 03:36:41) [GCC 11.4.0])

Debug logs#

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

from click_extra import command, verbosity_option, extra_version_option, echo

@command
@extra_version_option
@verbosity_option
def version_in_logs():
    echo("Standard operation")
$ version-in-logs --verbosity DEBUG
debug: Set <Logger click_extra (DEBUG)> to DEBUG.
debug: Set <RootLogger root (DEBUG)> to DEBUG.
debug: Version string template variables:
debug: {module}         : <module 'click_extra.testing' from '/home/runner/work/click-extra/click-extra/click_extra/testing.py'>
debug: {module_name}    : click_extra.testing
debug: {module_file}    : /home/runner/work/click-extra/click-extra/click_extra/testing.py
debug: {module_version} : None
debug: {package_name}   : click_extra
debug: {package_version}: 4.7.6
debug: {exec_name}      : click_extra.testing
debug: {version}        : 4.7.6
debug: {prog_name}      : version-in-logs
debug: {env_info}       : {'username': '-', 'guid': '75c54a7cf51dc9196409f82b708dfcd', 'hostname': '-', 'hostfqdn': '-', 'uname': {'system': 'Linux', 'node': '-', 'release': '6.5.0-1018-azure', 'version': '#19~22.04.2-Ubuntu SMP Thu Mar 21 16:45:46 UTC 2024', 'machine': 'x86_64', 'processor': 'x86_64'}, 'linux_dist_name': '', 'linux_dist_version': '', 'cpu_count': 4, 'fs_encoding': 'utf-8', 'ulimit_soft': 65536, 'ulimit_hard': 65536, 'cwd': '-', 'umask': '0o2', 'python': {'argv': '-', 'bin': '-', 'version': '3.12.3 (main, Apr 10 2024, 03:36:41) [GCC 11.4.0]', 'compiler': 'GCC 11.4.0', 'build_date': 'Apr 10 2024 03:36:41', 'version_info': [3, 12, 3, 'final', 0], 'features': {'openssl': 'OpenSSL 3.0.2 15 Mar 2022', 'expat': 'expat_2.6.0', 'sqlite': '3.37.2', 'tkinter': '8.6', 'zlib': '1.2.11', 'unicode_wide': True, 'readline': True, '64bit': True, 'ipv6': True, 'threading': True, 'urandom': True}}, 'time_utc': '2024-04-25 18:42:49.114292', '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:

from click_extra import command, echo, pass_context, extra_version_option

@command
@extra_version_option
@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 4.7.6
$ version-metadata
version = 4.7.6
package_name = click_extra
prog_name = version-metadata
env_info = {'username': '-', 'guid': '75c54a7cf51dc9196409f82b708dfcd', 'hostname': '-', 'hostfqdn': '-', 'uname': {'system': 'Linux', 'node': '-', 'release': '6.5.0-1018-azure', 'version': '#19~22.04.2-Ubuntu SMP Thu Mar 21 16:45:46 UTC 2024', 'machine': 'x86_64', 'processor': 'x86_64'}, 'linux_dist_name': '', 'linux_dist_version': '', 'cpu_count': 4, 'fs_encoding': 'utf-8', 'ulimit_soft': 65536, 'ulimit_hard': 65536, 'cwd': '-', 'umask': '0o2', 'python': {'argv': '-', 'bin': '-', 'version': '3.12.3 (main, Apr 10 2024, 03:36:41) [GCC 11.4.0]', 'compiler': 'GCC 11.4.0', 'build_date': 'Apr 10 2024 03:36:41', 'version_info': [3, 12, 3, 'final', 0], 'features': {'openssl': 'OpenSSL 3.0.2 15 Mar 2022', 'expat': 'expat_2.6.0', 'sqlite': '3.37.2', 'tkinter': '8.6', 'zlib': '1.2.11', 'unicode_wide': True, 'readline': True, '64bit': True, 'ipv6': True, 'threading': True, 'urandom': True}}, 'time_utc': '2024-04-25 18:42:49.114292', '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.

Template rendering#

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

from click_extra import command, echo, pass_context, extra_version_option, ExtraVersionOption, search_params

@command
@extra_version_option
@pass_context
def template_rendering(ctx):
     # Search for a ``--version`` parameter.
     version_opt = search_params(ctx.command.params, ExtraVersionOption)
     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 the click_extra.search_params.

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

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 <|-- ExtraVersionOption

Gather CLI metadata and print them.

class click_extra.version.ExtraVersionOption(param_decls=None, message=None, module=None, module_name=None, module_file=None, module_version=None, package_name=None, package_version=None, exec_name=None, version=None, prog_name=None, env_info=None, message_style=None, module_style=None, module_name_style=Style(fg='bright_white', bg=None, bold=None, dim=None, underline=None, overline=None, italic=None, blink=None, reverse=None, strikethrough=None, text_transform=None, _style_kwargs={'fg': 'bright_white', 'bg': None, 'bold': None, 'dim': None, 'underline': None, 'overline': None, 'italic': None, 'blink': None, 'reverse': None, 'strikethrough': None}), module_file_style=None, module_version_style=Style(fg='green', bg=None, bold=None, dim=None, underline=None, overline=None, italic=None, blink=None, reverse=None, strikethrough=None, text_transform=None, _style_kwargs={'fg': 'green', 'bg': None, 'bold': None, 'dim': None, 'underline': None, 'overline': None, 'italic': None, 'blink': None, 'reverse': None, 'strikethrough': None}), package_name_style=Style(fg='bright_white', bg=None, bold=None, dim=None, underline=None, overline=None, italic=None, blink=None, reverse=None, strikethrough=None, text_transform=None, _style_kwargs={'fg': 'bright_white', 'bg': None, 'bold': None, 'dim': None, 'underline': None, 'overline': None, 'italic': None, 'blink': None, 'reverse': None, 'strikethrough': None}), package_version_style=Style(fg='green', bg=None, bold=None, dim=None, underline=None, overline=None, italic=None, blink=None, reverse=None, strikethrough=None, text_transform=None, _style_kwargs={'fg': 'green', 'bg': None, 'bold': None, 'dim': None, 'underline': None, 'overline': None, 'italic': None, 'blink': None, 'reverse': None, 'strikethrough': None}), exec_name_style=Style(fg='bright_white', bg=None, bold=None, dim=None, underline=None, overline=None, italic=None, blink=None, reverse=None, strikethrough=None, text_transform=None, _style_kwargs={'fg': 'bright_white', 'bg': None, 'bold': None, 'dim': None, 'underline': None, 'overline': None, 'italic': None, 'blink': None, 'reverse': None, 'strikethrough': None}), version_style=Style(fg='green', bg=None, bold=None, dim=None, underline=None, overline=None, italic=None, blink=None, reverse=None, strikethrough=None, text_transform=None, _style_kwargs={'fg': 'green', 'bg': None, 'bold': None, 'dim': None, 'underline': None, 'overline': None, 'italic': None, 'blink': None, 'reverse': None, 'strikethrough': None}), prog_name_style=Style(fg='bright_white', bg=None, bold=None, dim=None, underline=None, overline=None, italic=None, blink=None, reverse=None, strikethrough=None, text_transform=None, _style_kwargs={'fg': 'bright_white', 'bg': None, 'bold': None, 'dim': None, 'underline': None, 'overline': None, 'italic': None, 'blink': None, 'reverse': None, 'strikethrough': None}), env_info_style=Style(fg='bright_black', bg=None, bold=None, dim=None, underline=None, overline=None, italic=None, blink=None, reverse=None, strikethrough=None, text_transform=None, _style_kwargs={'fg': 'bright_black', 'bg': None, 'bold': None, 'dim': None, 'underline': None, 'overline': None, 'italic': None, 'blink': None, 'reverse': None, 'strikethrough': 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:
template_fields: tuple[str, ...] = ('module', 'module_name', 'module_file', 'module_version', 'package_name', 'package_version', 'exec_name', 'version', 'prog_name', 'env_info')#

List of field IDs recognized by the message template.

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

Default message template used to render the version string.

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 name, the frame itself, and the frame chain for debugging.

Return type:

FrameType

property module: module#

Returns the module in which the CLI resides.

property module_name: str#

Returns the full module name or ``__main__`.

property module_file: str | None#

Returns the module’s file full path.

property module_version: str | None#

Returns the string found in the local __version__ variable.

property package_name: str | None#

Returns the package name.

property package_version: str | None#

Returns the package version if installed.

Will raise an error if the package is not installed, or if the package version cannot be determined from the package metadata.

property exec_name: str#

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#

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().

property prog_name: str | None#

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

property env_info: dict[str, str]#

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

default: t.Union[t.Any, t.Callable[[], t.Any]]#
type: types.ParamType#
is_flag: bool#
is_bool_flag: bool#
flag_value: t.Any#
name: t.Optional[str]#
opts: t.List[str]#
secondary_opts: t.List[str]#
print_debug_message()[source]#

Render in debug logs all template fields in color. :rtype: None

Todo

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

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