123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089 |
- import os
- import stat
- import sys
- import typing as t
- from datetime import datetime
- from gettext import gettext as _
- from gettext import ngettext
- from ._compat import _get_argv_encoding
- from ._compat import open_stream
- from .exceptions import BadParameter
- from .utils import format_filename
- from .utils import LazyFile
- from .utils import safecall
- if t.TYPE_CHECKING:
- import typing_extensions as te
- from .core import Context
- from .core import Parameter
- from .shell_completion import CompletionItem
- class ParamType:
- """Represents the type of a parameter. Validates and converts values
- from the command line or Python into the correct type.
- To implement a custom type, subclass and implement at least the
- following:
- - The :attr:`name` class attribute must be set.
- - Calling an instance of the type with ``None`` must return
- ``None``. This is already implemented by default.
- - :meth:`convert` must convert string values to the correct type.
- - :meth:`convert` must accept values that are already the correct
- type.
- - It must be able to convert a value if the ``ctx`` and ``param``
- arguments are ``None``. This can occur when converting prompt
- input.
- """
- is_composite: t.ClassVar[bool] = False
- arity: t.ClassVar[int] = 1
- #: the descriptive name of this type
- name: str
- #: if a list of this type is expected and the value is pulled from a
- #: string environment variable, this is what splits it up. `None`
- #: means any whitespace. For all parameters the general rule is that
- #: whitespace splits them up. The exception are paths and files which
- #: are split by ``os.path.pathsep`` by default (":" on Unix and ";" on
- #: Windows).
- envvar_list_splitter: t.ClassVar[t.Optional[str]] = None
- def to_info_dict(self) -> t.Dict[str, t.Any]:
- """Gather information that could be useful for a tool generating
- user-facing documentation.
- Use :meth:`click.Context.to_info_dict` to traverse the entire
- CLI structure.
- .. versionadded:: 8.0
- """
- # The class name without the "ParamType" suffix.
- param_type = type(self).__name__.partition("ParamType")[0]
- param_type = param_type.partition("ParameterType")[0]
- # Custom subclasses might not remember to set a name.
- if hasattr(self, "name"):
- name = self.name
- else:
- name = param_type
- return {"param_type": param_type, "name": name}
- def __call__(
- self,
- value: t.Any,
- param: t.Optional["Parameter"] = None,
- ctx: t.Optional["Context"] = None,
- ) -> t.Any:
- if value is not None:
- return self.convert(value, param, ctx)
- def get_metavar(self, param: "Parameter") -> t.Optional[str]:
- """Returns the metavar default for this param if it provides one."""
- def get_missing_message(self, param: "Parameter") -> t.Optional[str]:
- """Optionally might return extra information about a missing
- parameter.
- .. versionadded:: 2.0
- """
- def convert(
- self, value: t.Any, param: t.Optional["Parameter"], ctx: t.Optional["Context"]
- ) -> t.Any:
- """Convert the value to the correct type. This is not called if
- the value is ``None`` (the missing value).
- This must accept string values from the command line, as well as
- values that are already the correct type. It may also convert
- other compatible types.
- The ``param`` and ``ctx`` arguments may be ``None`` in certain
- situations, such as when converting prompt input.
- If the value cannot be converted, call :meth:`fail` with a
- descriptive message.
- :param value: The value to convert.
- :param param: The parameter that is using this type to convert
- its value. May be ``None``.
- :param ctx: The current context that arrived at this value. May
- be ``None``.
- """
- return value
- def split_envvar_value(self, rv: str) -> t.Sequence[str]:
- """Given a value from an environment variable this splits it up
- into small chunks depending on the defined envvar list splitter.
- If the splitter is set to `None`, which means that whitespace splits,
- then leading and trailing whitespace is ignored. Otherwise, leading
- and trailing splitters usually lead to empty items being included.
- """
- return (rv or "").split(self.envvar_list_splitter)
- def fail(
- self,
- message: str,
- param: t.Optional["Parameter"] = None,
- ctx: t.Optional["Context"] = None,
- ) -> "t.NoReturn":
- """Helper method to fail with an invalid value message."""
- raise BadParameter(message, ctx=ctx, param=param)
- def shell_complete(
- self, ctx: "Context", param: "Parameter", incomplete: str
- ) -> t.List["CompletionItem"]:
- """Return a list of
- :class:`~click.shell_completion.CompletionItem` objects for the
- incomplete value. Most types do not provide completions, but
- some do, and this allows custom types to provide custom
- completions as well.
- :param ctx: Invocation context for this command.
- :param param: The parameter that is requesting completion.
- :param incomplete: Value being completed. May be empty.
- .. versionadded:: 8.0
- """
- return []
- class CompositeParamType(ParamType):
- is_composite = True
- @property
- def arity(self) -> int: # type: ignore
- raise NotImplementedError()
- class FuncParamType(ParamType):
- def __init__(self, func: t.Callable[[t.Any], t.Any]) -> None:
- self.name: str = func.__name__
- self.func = func
- def to_info_dict(self) -> t.Dict[str, t.Any]:
- info_dict = super().to_info_dict()
- info_dict["func"] = self.func
- return info_dict
- def convert(
- self, value: t.Any, param: t.Optional["Parameter"], ctx: t.Optional["Context"]
- ) -> t.Any:
- try:
- return self.func(value)
- except ValueError:
- try:
- value = str(value)
- except UnicodeError:
- value = value.decode("utf-8", "replace")
- self.fail(value, param, ctx)
- class UnprocessedParamType(ParamType):
- name = "text"
- def convert(
- self, value: t.Any, param: t.Optional["Parameter"], ctx: t.Optional["Context"]
- ) -> t.Any:
- return value
- def __repr__(self) -> str:
- return "UNPROCESSED"
- class StringParamType(ParamType):
- name = "text"
- def convert(
- self, value: t.Any, param: t.Optional["Parameter"], ctx: t.Optional["Context"]
- ) -> t.Any:
- if isinstance(value, bytes):
- enc = _get_argv_encoding()
- try:
- value = value.decode(enc)
- except UnicodeError:
- fs_enc = sys.getfilesystemencoding()
- if fs_enc != enc:
- try:
- value = value.decode(fs_enc)
- except UnicodeError:
- value = value.decode("utf-8", "replace")
- else:
- value = value.decode("utf-8", "replace")
- return value
- return str(value)
- def __repr__(self) -> str:
- return "STRING"
- class Choice(ParamType):
- """The choice type allows a value to be checked against a fixed set
- of supported values. All of these values have to be strings.
- You should only pass a list or tuple of choices. Other iterables
- (like generators) may lead to surprising results.
- The resulting value will always be one of the originally passed choices
- regardless of ``case_sensitive`` or any ``ctx.token_normalize_func``
- being specified.
- See :ref:`choice-opts` for an example.
- :param case_sensitive: Set to false to make choices case
- insensitive. Defaults to true.
- """
- name = "choice"
- def __init__(self, choices: t.Sequence[str], case_sensitive: bool = True) -> None:
- self.choices = choices
- self.case_sensitive = case_sensitive
- def to_info_dict(self) -> t.Dict[str, t.Any]:
- info_dict = super().to_info_dict()
- info_dict["choices"] = self.choices
- info_dict["case_sensitive"] = self.case_sensitive
- return info_dict
- def get_metavar(self, param: "Parameter") -> str:
- choices_str = "|".join(self.choices)
- # Use curly braces to indicate a required argument.
- if param.required and param.param_type_name == "argument":
- return f"{{{choices_str}}}"
- # Use square braces to indicate an option or optional argument.
- return f"[{choices_str}]"
- def get_missing_message(self, param: "Parameter") -> str:
- return _("Choose from:\n\t{choices}").format(choices=",\n\t".join(self.choices))
- def convert(
- self, value: t.Any, param: t.Optional["Parameter"], ctx: t.Optional["Context"]
- ) -> t.Any:
- # Match through normalization and case sensitivity
- # first do token_normalize_func, then lowercase
- # preserve original `value` to produce an accurate message in
- # `self.fail`
- normed_value = value
- normed_choices = {choice: choice for choice in self.choices}
- if ctx is not None and ctx.token_normalize_func is not None:
- normed_value = ctx.token_normalize_func(value)
- normed_choices = {
- ctx.token_normalize_func(normed_choice): original
- for normed_choice, original in normed_choices.items()
- }
- if not self.case_sensitive:
- normed_value = normed_value.casefold()
- normed_choices = {
- normed_choice.casefold(): original
- for normed_choice, original in normed_choices.items()
- }
- if normed_value in normed_choices:
- return normed_choices[normed_value]
- choices_str = ", ".join(map(repr, self.choices))
- self.fail(
- ngettext(
- "{value!r} is not {choice}.",
- "{value!r} is not one of {choices}.",
- len(self.choices),
- ).format(value=value, choice=choices_str, choices=choices_str),
- param,
- ctx,
- )
- def __repr__(self) -> str:
- return f"Choice({list(self.choices)})"
- def shell_complete(
- self, ctx: "Context", param: "Parameter", incomplete: str
- ) -> t.List["CompletionItem"]:
- """Complete choices that start with the incomplete value.
- :param ctx: Invocation context for this command.
- :param param: The parameter that is requesting completion.
- :param incomplete: Value being completed. May be empty.
- .. versionadded:: 8.0
- """
- from click.shell_completion import CompletionItem
- str_choices = map(str, self.choices)
- if self.case_sensitive:
- matched = (c for c in str_choices if c.startswith(incomplete))
- else:
- incomplete = incomplete.lower()
- matched = (c for c in str_choices if c.lower().startswith(incomplete))
- return [CompletionItem(c) for c in matched]
- class DateTime(ParamType):
- """The DateTime type converts date strings into `datetime` objects.
- The format strings which are checked are configurable, but default to some
- common (non-timezone aware) ISO 8601 formats.
- When specifying *DateTime* formats, you should only pass a list or a tuple.
- Other iterables, like generators, may lead to surprising results.
- The format strings are processed using ``datetime.strptime``, and this
- consequently defines the format strings which are allowed.
- Parsing is tried using each format, in order, and the first format which
- parses successfully is used.
- :param formats: A list or tuple of date format strings, in the order in
- which they should be tried. Defaults to
- ``'%Y-%m-%d'``, ``'%Y-%m-%dT%H:%M:%S'``,
- ``'%Y-%m-%d %H:%M:%S'``.
- """
- name = "datetime"
- def __init__(self, formats: t.Optional[t.Sequence[str]] = None):
- self.formats: t.Sequence[str] = formats or [
- "%Y-%m-%d",
- "%Y-%m-%dT%H:%M:%S",
- "%Y-%m-%d %H:%M:%S",
- ]
- def to_info_dict(self) -> t.Dict[str, t.Any]:
- info_dict = super().to_info_dict()
- info_dict["formats"] = self.formats
- return info_dict
- def get_metavar(self, param: "Parameter") -> str:
- return f"[{'|'.join(self.formats)}]"
- def _try_to_convert_date(self, value: t.Any, format: str) -> t.Optional[datetime]:
- try:
- return datetime.strptime(value, format)
- except ValueError:
- return None
- def convert(
- self, value: t.Any, param: t.Optional["Parameter"], ctx: t.Optional["Context"]
- ) -> t.Any:
- if isinstance(value, datetime):
- return value
- for format in self.formats:
- converted = self._try_to_convert_date(value, format)
- if converted is not None:
- return converted
- formats_str = ", ".join(map(repr, self.formats))
- self.fail(
- ngettext(
- "{value!r} does not match the format {format}.",
- "{value!r} does not match the formats {formats}.",
- len(self.formats),
- ).format(value=value, format=formats_str, formats=formats_str),
- param,
- ctx,
- )
- def __repr__(self) -> str:
- return "DateTime"
- class _NumberParamTypeBase(ParamType):
- _number_class: t.ClassVar[t.Type[t.Any]]
- def convert(
- self, value: t.Any, param: t.Optional["Parameter"], ctx: t.Optional["Context"]
- ) -> t.Any:
- try:
- return self._number_class(value)
- except ValueError:
- self.fail(
- _("{value!r} is not a valid {number_type}.").format(
- value=value, number_type=self.name
- ),
- param,
- ctx,
- )
- class _NumberRangeBase(_NumberParamTypeBase):
- def __init__(
- self,
- min: t.Optional[float] = None,
- max: t.Optional[float] = None,
- min_open: bool = False,
- max_open: bool = False,
- clamp: bool = False,
- ) -> None:
- self.min = min
- self.max = max
- self.min_open = min_open
- self.max_open = max_open
- self.clamp = clamp
- def to_info_dict(self) -> t.Dict[str, t.Any]:
- info_dict = super().to_info_dict()
- info_dict.update(
- min=self.min,
- max=self.max,
- min_open=self.min_open,
- max_open=self.max_open,
- clamp=self.clamp,
- )
- return info_dict
- def convert(
- self, value: t.Any, param: t.Optional["Parameter"], ctx: t.Optional["Context"]
- ) -> t.Any:
- import operator
- rv = super().convert(value, param, ctx)
- lt_min: bool = self.min is not None and (
- operator.le if self.min_open else operator.lt
- )(rv, self.min)
- gt_max: bool = self.max is not None and (
- operator.ge if self.max_open else operator.gt
- )(rv, self.max)
- if self.clamp:
- if lt_min:
- return self._clamp(self.min, 1, self.min_open) # type: ignore
- if gt_max:
- return self._clamp(self.max, -1, self.max_open) # type: ignore
- if lt_min or gt_max:
- self.fail(
- _("{value} is not in the range {range}.").format(
- value=rv, range=self._describe_range()
- ),
- param,
- ctx,
- )
- return rv
- def _clamp(self, bound: float, dir: "te.Literal[1, -1]", open: bool) -> float:
- """Find the valid value to clamp to bound in the given
- direction.
- :param bound: The boundary value.
- :param dir: 1 or -1 indicating the direction to move.
- :param open: If true, the range does not include the bound.
- """
- raise NotImplementedError
- def _describe_range(self) -> str:
- """Describe the range for use in help text."""
- if self.min is None:
- op = "<" if self.max_open else "<="
- return f"x{op}{self.max}"
- if self.max is None:
- op = ">" if self.min_open else ">="
- return f"x{op}{self.min}"
- lop = "<" if self.min_open else "<="
- rop = "<" if self.max_open else "<="
- return f"{self.min}{lop}x{rop}{self.max}"
- def __repr__(self) -> str:
- clamp = " clamped" if self.clamp else ""
- return f"<{type(self).__name__} {self._describe_range()}{clamp}>"
- class IntParamType(_NumberParamTypeBase):
- name = "integer"
- _number_class = int
- def __repr__(self) -> str:
- return "INT"
- class IntRange(_NumberRangeBase, IntParamType):
- """Restrict an :data:`click.INT` value to a range of accepted
- values. See :ref:`ranges`.
- If ``min`` or ``max`` are not passed, any value is accepted in that
- direction. If ``min_open`` or ``max_open`` are enabled, the
- corresponding boundary is not included in the range.
- If ``clamp`` is enabled, a value outside the range is clamped to the
- boundary instead of failing.
- .. versionchanged:: 8.0
- Added the ``min_open`` and ``max_open`` parameters.
- """
- name = "integer range"
- def _clamp( # type: ignore
- self, bound: int, dir: "te.Literal[1, -1]", open: bool
- ) -> int:
- if not open:
- return bound
- return bound + dir
- class FloatParamType(_NumberParamTypeBase):
- name = "float"
- _number_class = float
- def __repr__(self) -> str:
- return "FLOAT"
- class FloatRange(_NumberRangeBase, FloatParamType):
- """Restrict a :data:`click.FLOAT` value to a range of accepted
- values. See :ref:`ranges`.
- If ``min`` or ``max`` are not passed, any value is accepted in that
- direction. If ``min_open`` or ``max_open`` are enabled, the
- corresponding boundary is not included in the range.
- If ``clamp`` is enabled, a value outside the range is clamped to the
- boundary instead of failing. This is not supported if either
- boundary is marked ``open``.
- .. versionchanged:: 8.0
- Added the ``min_open`` and ``max_open`` parameters.
- """
- name = "float range"
- def __init__(
- self,
- min: t.Optional[float] = None,
- max: t.Optional[float] = None,
- min_open: bool = False,
- max_open: bool = False,
- clamp: bool = False,
- ) -> None:
- super().__init__(
- min=min, max=max, min_open=min_open, max_open=max_open, clamp=clamp
- )
- if (min_open or max_open) and clamp:
- raise TypeError("Clamping is not supported for open bounds.")
- def _clamp(self, bound: float, dir: "te.Literal[1, -1]", open: bool) -> float:
- if not open:
- return bound
- # Could use Python 3.9's math.nextafter here, but clamping an
- # open float range doesn't seem to be particularly useful. It's
- # left up to the user to write a callback to do it if needed.
- raise RuntimeError("Clamping is not supported for open bounds.")
- class BoolParamType(ParamType):
- name = "boolean"
- def convert(
- self, value: t.Any, param: t.Optional["Parameter"], ctx: t.Optional["Context"]
- ) -> t.Any:
- if value in {False, True}:
- return bool(value)
- norm = value.strip().lower()
- if norm in {"1", "true", "t", "yes", "y", "on"}:
- return True
- if norm in {"0", "false", "f", "no", "n", "off"}:
- return False
- self.fail(
- _("{value!r} is not a valid boolean.").format(value=value), param, ctx
- )
- def __repr__(self) -> str:
- return "BOOL"
- class UUIDParameterType(ParamType):
- name = "uuid"
- def convert(
- self, value: t.Any, param: t.Optional["Parameter"], ctx: t.Optional["Context"]
- ) -> t.Any:
- import uuid
- if isinstance(value, uuid.UUID):
- return value
- value = value.strip()
- try:
- return uuid.UUID(value)
- except ValueError:
- self.fail(
- _("{value!r} is not a valid UUID.").format(value=value), param, ctx
- )
- def __repr__(self) -> str:
- return "UUID"
- class File(ParamType):
- """Declares a parameter to be a file for reading or writing. The file
- is automatically closed once the context tears down (after the command
- finished working).
- Files can be opened for reading or writing. The special value ``-``
- indicates stdin or stdout depending on the mode.
- By default, the file is opened for reading text data, but it can also be
- opened in binary mode or for writing. The encoding parameter can be used
- to force a specific encoding.
- The `lazy` flag controls if the file should be opened immediately or upon
- first IO. The default is to be non-lazy for standard input and output
- streams as well as files opened for reading, `lazy` otherwise. When opening a
- file lazily for reading, it is still opened temporarily for validation, but
- will not be held open until first IO. lazy is mainly useful when opening
- for writing to avoid creating the file until it is needed.
- Starting with Click 2.0, files can also be opened atomically in which
- case all writes go into a separate file in the same folder and upon
- completion the file will be moved over to the original location. This
- is useful if a file regularly read by other users is modified.
- See :ref:`file-args` for more information.
- """
- name = "filename"
- envvar_list_splitter: t.ClassVar[str] = os.path.pathsep
- def __init__(
- self,
- mode: str = "r",
- encoding: t.Optional[str] = None,
- errors: t.Optional[str] = "strict",
- lazy: t.Optional[bool] = None,
- atomic: bool = False,
- ) -> None:
- self.mode = mode
- self.encoding = encoding
- self.errors = errors
- self.lazy = lazy
- self.atomic = atomic
- def to_info_dict(self) -> t.Dict[str, t.Any]:
- info_dict = super().to_info_dict()
- info_dict.update(mode=self.mode, encoding=self.encoding)
- return info_dict
- def resolve_lazy_flag(self, value: "t.Union[str, os.PathLike[str]]") -> bool:
- if self.lazy is not None:
- return self.lazy
- if os.fspath(value) == "-":
- return False
- elif "w" in self.mode:
- return True
- return False
- def convert(
- self,
- value: t.Union[str, "os.PathLike[str]", t.IO[t.Any]],
- param: t.Optional["Parameter"],
- ctx: t.Optional["Context"],
- ) -> t.IO[t.Any]:
- if _is_file_like(value):
- return value
- value = t.cast("t.Union[str, os.PathLike[str]]", value)
- try:
- lazy = self.resolve_lazy_flag(value)
- if lazy:
- lf = LazyFile(
- value, self.mode, self.encoding, self.errors, atomic=self.atomic
- )
- if ctx is not None:
- ctx.call_on_close(lf.close_intelligently)
- return t.cast(t.IO[t.Any], lf)
- f, should_close = open_stream(
- value, self.mode, self.encoding, self.errors, atomic=self.atomic
- )
- # If a context is provided, we automatically close the file
- # at the end of the context execution (or flush out). If a
- # context does not exist, it's the caller's responsibility to
- # properly close the file. This for instance happens when the
- # type is used with prompts.
- if ctx is not None:
- if should_close:
- ctx.call_on_close(safecall(f.close))
- else:
- ctx.call_on_close(safecall(f.flush))
- return f
- except OSError as e: # noqa: B014
- self.fail(f"'{format_filename(value)}': {e.strerror}", param, ctx)
- def shell_complete(
- self, ctx: "Context", param: "Parameter", incomplete: str
- ) -> t.List["CompletionItem"]:
- """Return a special completion marker that tells the completion
- system to use the shell to provide file path completions.
- :param ctx: Invocation context for this command.
- :param param: The parameter that is requesting completion.
- :param incomplete: Value being completed. May be empty.
- .. versionadded:: 8.0
- """
- from click.shell_completion import CompletionItem
- return [CompletionItem(incomplete, type="file")]
- def _is_file_like(value: t.Any) -> "te.TypeGuard[t.IO[t.Any]]":
- return hasattr(value, "read") or hasattr(value, "write")
- class Path(ParamType):
- """The ``Path`` type is similar to the :class:`File` type, but
- returns the filename instead of an open file. Various checks can be
- enabled to validate the type of file and permissions.
- :param exists: The file or directory needs to exist for the value to
- be valid. If this is not set to ``True``, and the file does not
- exist, then all further checks are silently skipped.
- :param file_okay: Allow a file as a value.
- :param dir_okay: Allow a directory as a value.
- :param readable: if true, a readable check is performed.
- :param writable: if true, a writable check is performed.
- :param executable: if true, an executable check is performed.
- :param resolve_path: Make the value absolute and resolve any
- symlinks. A ``~`` is not expanded, as this is supposed to be
- done by the shell only.
- :param allow_dash: Allow a single dash as a value, which indicates
- a standard stream (but does not open it). Use
- :func:`~click.open_file` to handle opening this value.
- :param path_type: Convert the incoming path value to this type. If
- ``None``, keep Python's default, which is ``str``. Useful to
- convert to :class:`pathlib.Path`.
- .. versionchanged:: 8.1
- Added the ``executable`` parameter.
- .. versionchanged:: 8.0
- Allow passing ``path_type=pathlib.Path``.
- .. versionchanged:: 6.0
- Added the ``allow_dash`` parameter.
- """
- envvar_list_splitter: t.ClassVar[str] = os.path.pathsep
- def __init__(
- self,
- exists: bool = False,
- file_okay: bool = True,
- dir_okay: bool = True,
- writable: bool = False,
- readable: bool = True,
- resolve_path: bool = False,
- allow_dash: bool = False,
- path_type: t.Optional[t.Type[t.Any]] = None,
- executable: bool = False,
- ):
- self.exists = exists
- self.file_okay = file_okay
- self.dir_okay = dir_okay
- self.readable = readable
- self.writable = writable
- self.executable = executable
- self.resolve_path = resolve_path
- self.allow_dash = allow_dash
- self.type = path_type
- if self.file_okay and not self.dir_okay:
- self.name: str = _("file")
- elif self.dir_okay and not self.file_okay:
- self.name = _("directory")
- else:
- self.name = _("path")
- def to_info_dict(self) -> t.Dict[str, t.Any]:
- info_dict = super().to_info_dict()
- info_dict.update(
- exists=self.exists,
- file_okay=self.file_okay,
- dir_okay=self.dir_okay,
- writable=self.writable,
- readable=self.readable,
- allow_dash=self.allow_dash,
- )
- return info_dict
- def coerce_path_result(
- self, value: "t.Union[str, os.PathLike[str]]"
- ) -> "t.Union[str, bytes, os.PathLike[str]]":
- if self.type is not None and not isinstance(value, self.type):
- if self.type is str:
- return os.fsdecode(value)
- elif self.type is bytes:
- return os.fsencode(value)
- else:
- return t.cast("os.PathLike[str]", self.type(value))
- return value
- def convert(
- self,
- value: "t.Union[str, os.PathLike[str]]",
- param: t.Optional["Parameter"],
- ctx: t.Optional["Context"],
- ) -> "t.Union[str, bytes, os.PathLike[str]]":
- rv = value
- is_dash = self.file_okay and self.allow_dash and rv in (b"-", "-")
- if not is_dash:
- if self.resolve_path:
- # os.path.realpath doesn't resolve symlinks on Windows
- # until Python 3.8. Use pathlib for now.
- import pathlib
- rv = os.fsdecode(pathlib.Path(rv).resolve())
- try:
- st = os.stat(rv)
- except OSError:
- if not self.exists:
- return self.coerce_path_result(rv)
- self.fail(
- _("{name} {filename!r} does not exist.").format(
- name=self.name.title(), filename=format_filename(value)
- ),
- param,
- ctx,
- )
- if not self.file_okay and stat.S_ISREG(st.st_mode):
- self.fail(
- _("{name} {filename!r} is a file.").format(
- name=self.name.title(), filename=format_filename(value)
- ),
- param,
- ctx,
- )
- if not self.dir_okay and stat.S_ISDIR(st.st_mode):
- self.fail(
- _("{name} '{filename}' is a directory.").format(
- name=self.name.title(), filename=format_filename(value)
- ),
- param,
- ctx,
- )
- if self.readable and not os.access(rv, os.R_OK):
- self.fail(
- _("{name} {filename!r} is not readable.").format(
- name=self.name.title(), filename=format_filename(value)
- ),
- param,
- ctx,
- )
- if self.writable and not os.access(rv, os.W_OK):
- self.fail(
- _("{name} {filename!r} is not writable.").format(
- name=self.name.title(), filename=format_filename(value)
- ),
- param,
- ctx,
- )
- if self.executable and not os.access(value, os.X_OK):
- self.fail(
- _("{name} {filename!r} is not executable.").format(
- name=self.name.title(), filename=format_filename(value)
- ),
- param,
- ctx,
- )
- return self.coerce_path_result(rv)
- def shell_complete(
- self, ctx: "Context", param: "Parameter", incomplete: str
- ) -> t.List["CompletionItem"]:
- """Return a special completion marker that tells the completion
- system to use the shell to provide path completions for only
- directories or any paths.
- :param ctx: Invocation context for this command.
- :param param: The parameter that is requesting completion.
- :param incomplete: Value being completed. May be empty.
- .. versionadded:: 8.0
- """
- from click.shell_completion import CompletionItem
- type = "dir" if self.dir_okay and not self.file_okay else "file"
- return [CompletionItem(incomplete, type=type)]
- class Tuple(CompositeParamType):
- """The default behavior of Click is to apply a type on a value directly.
- This works well in most cases, except for when `nargs` is set to a fixed
- count and different types should be used for different items. In this
- case the :class:`Tuple` type can be used. This type can only be used
- if `nargs` is set to a fixed number.
- For more information see :ref:`tuple-type`.
- This can be selected by using a Python tuple literal as a type.
- :param types: a list of types that should be used for the tuple items.
- """
- def __init__(self, types: t.Sequence[t.Union[t.Type[t.Any], ParamType]]) -> None:
- self.types: t.Sequence[ParamType] = [convert_type(ty) for ty in types]
- def to_info_dict(self) -> t.Dict[str, t.Any]:
- info_dict = super().to_info_dict()
- info_dict["types"] = [t.to_info_dict() for t in self.types]
- return info_dict
- @property
- def name(self) -> str: # type: ignore
- return f"<{' '.join(ty.name for ty in self.types)}>"
- @property
- def arity(self) -> int: # type: ignore
- return len(self.types)
- def convert(
- self, value: t.Any, param: t.Optional["Parameter"], ctx: t.Optional["Context"]
- ) -> t.Any:
- len_type = len(self.types)
- len_value = len(value)
- if len_value != len_type:
- self.fail(
- ngettext(
- "{len_type} values are required, but {len_value} was given.",
- "{len_type} values are required, but {len_value} were given.",
- len_value,
- ).format(len_type=len_type, len_value=len_value),
- param=param,
- ctx=ctx,
- )
- return tuple(ty(x, param, ctx) for ty, x in zip(self.types, value))
- def convert_type(ty: t.Optional[t.Any], default: t.Optional[t.Any] = None) -> ParamType:
- """Find the most appropriate :class:`ParamType` for the given Python
- type. If the type isn't provided, it can be inferred from a default
- value.
- """
- guessed_type = False
- if ty is None and default is not None:
- if isinstance(default, (tuple, list)):
- # If the default is empty, ty will remain None and will
- # return STRING.
- if default:
- item = default[0]
- # A tuple of tuples needs to detect the inner types.
- # Can't call convert recursively because that would
- # incorrectly unwind the tuple to a single type.
- if isinstance(item, (tuple, list)):
- ty = tuple(map(type, item))
- else:
- ty = type(item)
- else:
- ty = type(default)
- guessed_type = True
- if isinstance(ty, tuple):
- return Tuple(ty)
- if isinstance(ty, ParamType):
- return ty
- if ty is str or ty is None:
- return STRING
- if ty is int:
- return INT
- if ty is float:
- return FLOAT
- if ty is bool:
- return BOOL
- if guessed_type:
- return STRING
- if __debug__:
- try:
- if issubclass(ty, ParamType):
- raise AssertionError(
- f"Attempted to use an uninstantiated parameter type ({ty})."
- )
- except TypeError:
- # ty is an instance (correct), so issubclass fails.
- pass
- return FuncParamType(ty)
- #: A dummy parameter type that just does nothing. From a user's
- #: perspective this appears to just be the same as `STRING` but
- #: internally no string conversion takes place if the input was bytes.
- #: This is usually useful when working with file paths as they can
- #: appear in bytes and unicode.
- #:
- #: For path related uses the :class:`Path` type is a better choice but
- #: there are situations where an unprocessed type is useful which is why
- #: it is is provided.
- #:
- #: .. versionadded:: 4.0
- UNPROCESSED = UnprocessedParamType()
- #: A unicode string parameter type which is the implicit default. This
- #: can also be selected by using ``str`` as type.
- STRING = StringParamType()
- #: An integer parameter. This can also be selected by using ``int`` as
- #: type.
- INT = IntParamType()
- #: A floating point value parameter. This can also be selected by using
- #: ``float`` as type.
- FLOAT = FloatParamType()
- #: A boolean parameter. This is the default for boolean flags. This can
- #: also be selected by using ``bool`` as a type.
- BOOL = BoolParamType()
- #: A UUID parameter.
- UUID = UUIDParameterType()
|