import inspect
import types
import typing
as t
from functools
import update_wrapper
from gettext
import gettext
as _
from .core
import Argument
from .core
import Command
from .core
import Context
from .core
import Group
from .core
import Option
from .core
import Parameter
from .globals
import get_current_context
from .utils
import echo
if t.TYPE_CHECKING:
import typing_extensions
as te
P = te.ParamSpec(
"P")
R = t.TypeVar(
"R")
T = t.TypeVar(
"T")
_AnyCallable = t.Callable[..., t.Any]
FC = t.TypeVar(
"FC", bound=t.Union[_AnyCallable, Command])
def pass_context(f:
"t.Callable[te.Concatenate[Context, P], R]") ->
"t.Callable[P, R]":
"""Marks a callback as wanting to receive the current context
object
as first argument.
"""
def new_func(*args:
"P.args", **kwargs:
"P.kwargs") ->
"R":
return f(get_current_context(), *args, **kwargs)
return update_wrapper(new_func, f)
def pass_obj(f:
"t.Callable[te.Concatenate[t.Any, P], R]") ->
"t.Callable[P, R]":
"""Similar to :func:`pass_context`, but only pass the object on the
context onwards (:attr:`Context.obj`). This
is useful
if that object
represents the state of a nested system.
"""
def new_func(*args:
"P.args", **kwargs:
"P.kwargs") ->
"R":
return f(get_current_context().obj, *args, **kwargs)
return update_wrapper(new_func, f)
def make_pass_decorator(
object_type: t.Type[T], ensure: bool =
False
) -> t.Callable[[
"t.Callable[te.Concatenate[T, P], R]"],
"t.Callable[P, R]"]:
"""Given an object type this creates a decorator that will work
similar to :func:`pass_obj` but instead of passing the object of the
current context, it will find the innermost context of type
:func:`object_type`.
This generates a decorator that works roughly like this::
from functools
import update_wrapper
def decorator(f):
@pass_context
def new_func(ctx, *args, **kwargs):
obj = ctx.find_object(object_type)
return ctx.invoke(f, obj, *args, **kwargs)
return update_wrapper(new_func, f)
return decorator
:param object_type: the type of the object to
pass.
:param ensure:
if set to `
True`, a new object will be created
and
remembered on the context
if it
's not there yet.
"""
def decorator(f:
"t.Callable[te.Concatenate[T, P], R]") ->
"t.Callable[P, R]":
def new_func(*args:
"P.args", **kwargs:
"P.kwargs") ->
"R":
ctx = get_current_context()
obj: t.Optional[T]
if ensure:
obj = ctx.ensure_object(object_type)
else:
obj = ctx.find_object(object_type)
if obj
is None:
raise RuntimeError(
"Managed to invoke callback without a context"
f
" object of type {object_type.__name__!r}"
" existing."
)
return ctx.invoke(f, obj, *args, **kwargs)
return update_wrapper(new_func, f)
return decorator
# type: ignore[return-value]
def pass_meta_key(
key: str, *, doc_description: t.Optional[str] =
None
) ->
"t.Callable[[t.Callable[te.Concatenate[t.Any, P], R]], t.Callable[P, R]]":
"""Create a decorator that passes a key from
:attr:`click.Context.meta`
as the first argument to the decorated
function.
:param key: Key
in ``Context.meta`` to
pass.
:param doc_description: Description of the object being passed,
inserted into the decorator
's docstring. Defaults to "the 'key
'
key
from Context.meta
".
.. versionadded:: 8.0
"""
def decorator(f:
"t.Callable[te.Concatenate[t.Any, P], R]") ->
"t.Callable[P, R]":
def new_func(*args:
"P.args", **kwargs:
"P.kwargs") -> R:
ctx = get_current_context()
obj = ctx.meta[key]
return ctx.invoke(f, obj, *args, **kwargs)
return update_wrapper(new_func, f)
if doc_description
is None:
doc_description = f
"the {key!r} key from :attr:`click.Context.meta`"
decorator.__doc__ = (
f
"Decorator that passes {doc_description} as the first argument"
" to the decorated function."
)
return decorator
# type: ignore[return-value]
CmdType = t.TypeVar(
"CmdType", bound=Command)
# variant: no call, directly as decorator for a function.
@t.overload
def command(name: _AnyCallable) -> Command:
...
# variant: with positional name and with positional or keyword cls argument:
# @command(namearg, CommandCls, ...) or @command(namearg, cls=CommandCls, ...)
@t.overload
def command(
name: t.Optional[str],
cls: t.Type[CmdType],
**attrs: t.Any,
) -> t.Callable[[_AnyCallable], CmdType]:
...
# variant: name omitted, cls _must_ be a keyword argument, @command(cls=CommandCls, ...)
@t.overload
def command(
name:
None =
None,
*,
cls: t.Type[CmdType],
**attrs: t.Any,
) -> t.Callable[[_AnyCallable], CmdType]:
...
# variant: with optional string name, no cls argument provided.
@t.overload
def command(
name: t.Optional[str] = ..., cls:
None =
None, **attrs: t.Any
) -> t.Callable[[_AnyCallable], Command]:
...
def command(
name: t.Union[t.Optional[str], _AnyCallable] =
None,
cls: t.Optional[t.Type[CmdType]] =
None,
**attrs: t.Any,
) -> t.Union[Command, t.Callable[[_AnyCallable], t.Union[Command, CmdType]]]:
r
"""Creates a new :class:`Command` and uses the decorated function as
callback. This will also automatically attach all decorated
:func:`option`\s
and :func:`argument`\s
as parameters to the command.
The name of the command defaults to the name of the function
with
underscores replaced by dashes.
If you want to change that, you can
pass the intended name
as the first argument.
All keyword arguments are forwarded to the underlying command
class.
For the ``params`` argument, any decorated params are appended to
the end of the list.
Once decorated the function turns into a :
class:`Command` instance
that can be invoked
as a command line utility
or be attached to a
command :
class:`Group`.
:param name: the name of the command. This defaults to the function
name
with underscores replaced by dashes.
:param cls: the command
class to instantiate. This defaults to
:
class:`Command`.
.. versionchanged:: 8.1
This decorator can be applied without parentheses.
.. versionchanged:: 8.1
The ``params`` argument can be used. Decorated params are
appended to the end of the list.
"""
func: t.Optional[t.Callable[[_AnyCallable], t.Any]] =
None
if callable(name):
func = name
name =
None
assert cls
is None,
"Use 'command(cls=cls)(callable)' to specify a class."
assert not attrs,
"Use 'command(**kwargs)(callable)' to provide arguments."
if cls
is None:
cls = t.cast(t.Type[CmdType], Command)
def decorator(f: _AnyCallable) -> CmdType:
if isinstance(f, Command):
raise TypeError(
"Attempted to convert a callback into a command twice.")
attr_params = attrs.pop(
"params",
None)
params = attr_params
if attr_params
is not None else []
try:
decorator_params = f.__click_params__
# type: ignore
except AttributeError:
pass
else:
del f.__click_params__
# type: ignore
params.extend(reversed(decorator_params))
if attrs.get(
"help")
is None:
attrs[
"help"] = f.__doc__
if t.TYPE_CHECKING:
assert cls
is not None
assert not callable(name)
cmd = cls(
name=name
or f.__name__.lower().replace(
"_",
"-"),
callback=f,
params=params,
**attrs,
)
cmd.__doc__ = f.__doc__
return cmd
if func
is not None:
return decorator(func)
return decorator
GrpType = t.TypeVar(
"GrpType", bound=Group)
# variant: no call, directly as decorator for a function.
@t.overload
def group(name: _AnyCallable) -> Group:
...
# variant: with positional name and with positional or keyword cls argument:
# @group(namearg, GroupCls, ...) or @group(namearg, cls=GroupCls, ...)
@t.overload
def group(
name: t.Optional[str],
cls: t.Type[GrpType],
**attrs: t.Any,
) -> t.Callable[[_AnyCallable], GrpType]:
...
# variant: name omitted, cls _must_ be a keyword argument, @group(cmd=GroupCls, ...)
@t.overload
def group(
name:
None =
None,
*,
cls: t.Type[GrpType],
**attrs: t.Any,
) -> t.Callable[[_AnyCallable], GrpType]:
...
# variant: with optional string name, no cls argument provided.
@t.overload
def group(
name: t.Optional[str] = ..., cls:
None =
None, **attrs: t.Any
) -> t.Callable[[_AnyCallable], Group]:
...
def group(
name: t.Union[str, _AnyCallable,
None] =
None,
cls: t.Optional[t.Type[GrpType]] =
None,
**attrs: t.Any,
) -> t.Union[Group, t.Callable[[_AnyCallable], t.Union[Group, GrpType]]]:
"""Creates a new :class:`Group` with a function as callback. This
works otherwise the same
as :func:`command` just that the `cls`
parameter
is set to :
class:`Group`.
.. versionchanged:: 8.1
This decorator can be applied without parentheses.
"""
if cls
is None:
cls = t.cast(t.Type[GrpType], Group)
if callable(name):
return command(cls=cls, **attrs)(name)
return command(name, cls, **attrs)
def _param_memo(f: t.Callable[..., t.Any], param: Parameter) ->
None:
if isinstance(f, Command):
f.params.append(param)
else:
if not hasattr(f,
"__click_params__"):
f.__click_params__ = []
# type: ignore
f.__click_params__.append(param)
# type: ignore
def argument(
*param_decls: str, cls: t.Optional[t.Type[Argument]] =
None, **attrs: t.Any
) -> t.Callable[[FC], FC]:
"""Attaches an argument to the command. All positional arguments are
passed
as parameter declarations to :
class:`Argument`; all keyword
arguments are forwarded unchanged (
except ``cls``).
This
is equivalent to creating an :
class:`Argument` instance manually
and attaching it to the :attr:`Command.params` list.
For the default argument
class, refer to :
class:`Argument`
and
:
class:`Parameter`
for descriptions of parameters.
:param cls: the argument
class to instantiate. This defaults to
:
class:`Argument`.
:param param_decls: Passed
as positional arguments to the constructor of
``cls``.
:param attrs: Passed
as keyword arguments to the constructor of ``cls``.
"""
if cls
is None:
cls = Argument
def decorator(f: FC) -> FC:
_param_memo(f, cls(param_decls, **attrs))
return f
return decorator
def option(
*param_decls: str, cls: t.Optional[t.Type[Option]] =
None, **attrs: t.Any
) -> t.Callable[[FC], FC]:
"""Attaches an option to the command. All positional arguments are
passed
as parameter declarations to :
class:`Option`; all keyword
arguments are forwarded unchanged (
except ``cls``).
This
is equivalent to creating an :
class:`Option` instance manually
and attaching it to the :attr:`Command.params` list.
For the default option
class, refer to :
class:`Option`
and
:
class:`Parameter`
for descriptions of parameters.
:param cls: the option
class to instantiate. This defaults to
:
class:`Option`.
:param param_decls: Passed
as positional arguments to the constructor of
``cls``.
:param attrs: Passed
as keyword arguments to the constructor of ``cls``.
"""
if cls
is None:
cls = Option
def decorator(f: FC) -> FC:
_param_memo(f, cls(param_decls, **attrs))
return f
return decorator
def confirmation_option(*param_decls: str, **kwargs: t.Any) -> t.Callable[[FC], FC]:
"""Add a ``--yes`` option which shows a prompt before continuing if
not passed.
If the prompt
is declined, the program will exit.
:param param_decls: One
or more option names. Defaults to the single
value ``
"--yes"``.
:param kwargs: Extra arguments are passed to :func:`option`.
"""
def callback(ctx: Context, param: Parameter, value: bool) ->
None:
if not value:
ctx.abort()
if not param_decls:
param_decls = (
"--yes",)
kwargs.setdefault(
"is_flag",
True)
kwargs.setdefault(
"callback", callback)
kwargs.setdefault(
"expose_value",
False)
kwargs.setdefault(
"prompt",
"Do you want to continue?")
kwargs.setdefault(
"help",
"Confirm the action without prompting.")
return option(*param_decls, **kwargs)
def password_option(*param_decls: str, **kwargs: t.Any) -> t.Callable[[FC], FC]:
"""Add a ``--password`` option which prompts for a password, hiding
input
and asking to enter the value again
for confirmation.
:param param_decls: One
or more option names. Defaults to the single
value ``
"--password"``.
:param kwargs: Extra arguments are passed to :func:`option`.
"""
if not param_decls:
param_decls = (
"--password",)
kwargs.setdefault(
"prompt",
True)
kwargs.setdefault(
"confirmation_prompt",
True)
kwargs.setdefault(
"hide_input",
True)
return option(*param_decls, **kwargs)
def version_option(
version: t.Optional[str] =
None,
*param_decls: str,
package_name: t.Optional[str] =
None,
prog_name: t.Optional[str] =
None,
message: t.Optional[str] =
None,
**kwargs: t.Any,
) -> t.Callable[[FC], FC]:
"""Add a ``--version`` option which immediately prints the version
number
and exits the program.
If ``version``
is not provided, Click will
try to detect it using
:func:`importlib.metadata.version` to get the version
for the
``package_name``. On Python < 3.8, the ``importlib_metadata``
backport must be installed.
If ``package_name``
is not provided, Click will
try to detect it by
inspecting the stack frames. This will be used to detect the
version, so it must match the name of the installed package.
:param version: The version number to show.
If not provided, Click
will
try to detect it.
:param param_decls: One
or more option names. Defaults to the single
value ``
"--version"``.
:param package_name: The package name to detect the version
from.
If
not provided, Click will
try to detect it.
:param prog_name: The name of the CLI to show
in the message.
If not
provided, it will be detected
from the command.
:param message: The message to show. The values ``%(prog)s``,
``%(package)s``,
and ``%(version)s`` are available. Defaults to
``
"%(prog)s, version %(version)s"``.
:param kwargs: Extra arguments are passed to :func:`option`.
:
raise RuntimeError: ``version`` could
not be detected.
.. versionchanged:: 8.0
Add the ``package_name`` parameter,
and the ``%(package)s``
value
for messages.
.. versionchanged:: 8.0
Use :mod:`importlib.metadata` instead of ``pkg_resources``. The
version
is detected based on the package name,
not the entry
point name. The Python package name must match the installed
package name,
or be passed
with ``package_name=``.
"""
if message
is None:
message = _(
"%(prog)s, version %(version)s")
if version
is None and package_name
is None:
frame = inspect.currentframe()
f_back = frame.f_back
if frame
is not None else None
f_globals = f_back.f_globals
if f_back
is not None else None
# break reference cycle
# https://docs.python.org/3/library/inspect.html#the-interpreter-stack
del frame
if f_globals
is not None:
package_name = f_globals.get(
"__name__")
if package_name ==
"__main__":
package_name = f_globals.get(
"__package__")
if package_name:
package_name = package_name.partition(
".")[0]
def callback(ctx: Context, param: Parameter, value: bool) ->
None:
if not value
or ctx.resilient_parsing:
return
nonlocal prog_name
nonlocal version
if prog_name
is None:
prog_name = ctx.find_root().info_name
if version
is None and package_name
is not None:
metadata: t.Optional[types.ModuleType]
try:
from importlib
import metadata
# type: ignore
except ImportError:
# Python < 3.8
import importlib_metadata
as metadata
# type: ignore
try:
version = metadata.version(package_name)
# type: ignore
except metadata.PackageNotFoundError:
# type: ignore
raise RuntimeError(
f
"{package_name!r} is not installed. Try passing"
" 'package_name' instead."
)
from None
if version
is None:
raise RuntimeError(
f
"Could not determine the version for {package_name!r} automatically."
)
echo(
message % {
"prog": prog_name,
"package": package_name,
"version": version},
color=ctx.color,
)
ctx.exit()
if not param_decls:
param_decls = (
"--version",)
kwargs.setdefault(
"is_flag",
True)
kwargs.setdefault(
"expose_value",
False)
kwargs.setdefault(
"is_eager",
True)
kwargs.setdefault(
"help", _(
"Show the version and exit."))
kwargs[
"callback"] = callback
return option(*param_decls, **kwargs)
def help_option(*param_decls: str, **kwargs: t.Any) -> t.Callable[[FC], FC]:
"""Add a ``--help`` option which immediately prints the help page
and exits the program.
This
is usually unnecessary,
as the ``--help`` option
is added to
each command automatically unless ``add_help_option=
False``
is
passed.
:param param_decls: One
or more option names. Defaults to the single
value ``
"--help"``.
:param kwargs: Extra arguments are passed to :func:`option`.
"""
def callback(ctx: Context, param: Parameter, value: bool) ->
None:
if not value
or ctx.resilient_parsing:
return
echo(ctx.get_help(), color=ctx.color)
ctx.exit()
if not param_decls:
param_decls = (
"--help",)
kwargs.setdefault(
"is_flag",
True)
kwargs.setdefault(
"expose_value",
False)
kwargs.setdefault(
"is_eager",
True)
kwargs.setdefault(
"help", _(
"Show this message and exit."))
kwargs[
"callback"] = callback
return option(*param_decls, **kwargs)