| import os |
| import stat |
| 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 get_filesystem_encoding |
| from ._compat import open_stream |
| from .exceptions import BadParameter |
| 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 = 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 = get_filesystem_encoding() |
| 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 = 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] |
| |
| 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 = 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.Any) -> bool: |
| if self.lazy is not None: |
| return self.lazy |
| if value == "-": |
| return False |
| elif "w" in self.mode: |
| return True |
| return False |
| |
| def convert( |
| self, value: t.Any, param: t.Optional["Parameter"], ctx: t.Optional["Context"] |
| ) -> t.Any: |
| try: |
| if hasattr(value, "read") or hasattr(value, "write"): |
| return value |
| |
| lazy = self.resolve_lazy_flag(value) |
| |
| if lazy: |
| f: t.IO = t.cast( |
| t.IO, |
| LazyFile( |
| value, self.mode, self.encoding, self.errors, atomic=self.atomic |
| ), |
| ) |
| |
| if ctx is not None: |
| ctx.call_on_close(f.close_intelligently) # type: ignore |
| |
| return f |
| |
| 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"'{os.fsdecode(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")] |
| |
| |
| 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 ``type=pathlib.Path``. |
| |
| .. versionchanged:: 6.0 |
| Added the ``allow_dash`` parameter. |
| """ |
| |
| envvar_list_splitter = 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] = 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 = _("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, rv: t.Any) -> t.Any: |
| if self.type is not None and not isinstance(rv, self.type): |
| if self.type is str: |
| rv = os.fsdecode(rv) |
| elif self.type is bytes: |
| rv = os.fsencode(rv) |
| else: |
| rv = self.type(rv) |
| |
| return rv |
| |
| def convert( |
| self, value: t.Any, param: t.Optional["Parameter"], ctx: t.Optional["Context"] |
| ) -> t.Any: |
| 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=os.fsdecode(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=os.fsdecode(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=os.fsdecode(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=os.fsdecode(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=os.fsdecode(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=os.fsdecode(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, ParamType]]) -> None: |
| self.types = [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() |