123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561 |
- 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)
|