Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/click/types.py: 35%
413 statements
« prev ^ index » next coverage.py v7.3.2, created at 2023-12-09 06:03 +0000
« prev ^ index » next coverage.py v7.3.2, created at 2023-12-09 06:03 +0000
1from __future__ import annotations
3import collections.abc as cabc
4import os
5import stat
6import sys
7import typing as t
8from datetime import datetime
9from gettext import gettext as _
10from gettext import ngettext
12from ._compat import _get_argv_encoding
13from ._compat import open_stream
14from .exceptions import BadParameter
15from .utils import format_filename
16from .utils import LazyFile
17from .utils import safecall
19if t.TYPE_CHECKING:
20 import typing_extensions as te
21 from .core import Context
22 from .core import Parameter
23 from .shell_completion import CompletionItem
26class ParamType:
27 """Represents the type of a parameter. Validates and converts values
28 from the command line or Python into the correct type.
30 To implement a custom type, subclass and implement at least the
31 following:
33 - The :attr:`name` class attribute must be set.
34 - Calling an instance of the type with ``None`` must return
35 ``None``. This is already implemented by default.
36 - :meth:`convert` must convert string values to the correct type.
37 - :meth:`convert` must accept values that are already the correct
38 type.
39 - It must be able to convert a value if the ``ctx`` and ``param``
40 arguments are ``None``. This can occur when converting prompt
41 input.
42 """
44 is_composite: t.ClassVar[bool] = False
45 arity: t.ClassVar[int] = 1
47 #: the descriptive name of this type
48 name: str
50 #: if a list of this type is expected and the value is pulled from a
51 #: string environment variable, this is what splits it up. `None`
52 #: means any whitespace. For all parameters the general rule is that
53 #: whitespace splits them up. The exception are paths and files which
54 #: are split by ``os.path.pathsep`` by default (":" on Unix and ";" on
55 #: Windows).
56 envvar_list_splitter: t.ClassVar[str | None] = None
58 def to_info_dict(self) -> dict[str, t.Any]:
59 """Gather information that could be useful for a tool generating
60 user-facing documentation.
62 Use :meth:`click.Context.to_info_dict` to traverse the entire
63 CLI structure.
65 .. versionadded:: 8.0
66 """
67 # The class name without the "ParamType" suffix.
68 param_type = type(self).__name__.partition("ParamType")[0]
69 param_type = param_type.partition("ParameterType")[0]
71 # Custom subclasses might not remember to set a name.
72 if hasattr(self, "name"):
73 name = self.name
74 else:
75 name = param_type
77 return {"param_type": param_type, "name": name}
79 def __call__(
80 self,
81 value: t.Any,
82 param: Parameter | None = None,
83 ctx: Context | None = None,
84 ) -> t.Any:
85 if value is not None:
86 return self.convert(value, param, ctx)
88 def get_metavar(self, param: Parameter) -> str | None:
89 """Returns the metavar default for this param if it provides one."""
91 def get_missing_message(self, param: Parameter) -> str | None:
92 """Optionally might return extra information about a missing
93 parameter.
95 .. versionadded:: 2.0
96 """
98 def convert(
99 self, value: t.Any, param: Parameter | None, ctx: Context | None
100 ) -> t.Any:
101 """Convert the value to the correct type. This is not called if
102 the value is ``None`` (the missing value).
104 This must accept string values from the command line, as well as
105 values that are already the correct type. It may also convert
106 other compatible types.
108 The ``param`` and ``ctx`` arguments may be ``None`` in certain
109 situations, such as when converting prompt input.
111 If the value cannot be converted, call :meth:`fail` with a
112 descriptive message.
114 :param value: The value to convert.
115 :param param: The parameter that is using this type to convert
116 its value. May be ``None``.
117 :param ctx: The current context that arrived at this value. May
118 be ``None``.
119 """
120 return value
122 def split_envvar_value(self, rv: str) -> cabc.Sequence[str]:
123 """Given a value from an environment variable this splits it up
124 into small chunks depending on the defined envvar list splitter.
126 If the splitter is set to `None`, which means that whitespace splits,
127 then leading and trailing whitespace is ignored. Otherwise, leading
128 and trailing splitters usually lead to empty items being included.
129 """
130 return (rv or "").split(self.envvar_list_splitter)
132 def fail(
133 self,
134 message: str,
135 param: Parameter | None = None,
136 ctx: Context | None = None,
137 ) -> t.NoReturn:
138 """Helper method to fail with an invalid value message."""
139 raise BadParameter(message, ctx=ctx, param=param)
141 def shell_complete(
142 self, ctx: Context, param: Parameter, incomplete: str
143 ) -> list[CompletionItem]:
144 """Return a list of
145 :class:`~click.shell_completion.CompletionItem` objects for the
146 incomplete value. Most types do not provide completions, but
147 some do, and this allows custom types to provide custom
148 completions as well.
150 :param ctx: Invocation context for this command.
151 :param param: The parameter that is requesting completion.
152 :param incomplete: Value being completed. May be empty.
154 .. versionadded:: 8.0
155 """
156 return []
159class CompositeParamType(ParamType):
160 is_composite = True
162 @property
163 def arity(self) -> int: # type: ignore
164 raise NotImplementedError()
167class FuncParamType(ParamType):
168 def __init__(self, func: t.Callable[[t.Any], t.Any]) -> None:
169 self.name: str = func.__name__
170 self.func = func
172 def to_info_dict(self) -> dict[str, t.Any]:
173 info_dict = super().to_info_dict()
174 info_dict["func"] = self.func
175 return info_dict
177 def convert(
178 self, value: t.Any, param: Parameter | None, ctx: Context | None
179 ) -> t.Any:
180 try:
181 return self.func(value)
182 except ValueError:
183 try:
184 value = str(value)
185 except UnicodeError:
186 value = value.decode("utf-8", "replace")
188 self.fail(value, param, ctx)
191class UnprocessedParamType(ParamType):
192 name = "text"
194 def convert(
195 self, value: t.Any, param: Parameter | None, ctx: Context | None
196 ) -> t.Any:
197 return value
199 def __repr__(self) -> str:
200 return "UNPROCESSED"
203class StringParamType(ParamType):
204 name = "text"
206 def convert(
207 self, value: t.Any, param: Parameter | None, ctx: Context | None
208 ) -> t.Any:
209 if isinstance(value, bytes):
210 enc = _get_argv_encoding()
211 try:
212 value = value.decode(enc)
213 except UnicodeError:
214 fs_enc = sys.getfilesystemencoding()
215 if fs_enc != enc:
216 try:
217 value = value.decode(fs_enc)
218 except UnicodeError:
219 value = value.decode("utf-8", "replace")
220 else:
221 value = value.decode("utf-8", "replace")
222 return value
223 return str(value)
225 def __repr__(self) -> str:
226 return "STRING"
229class Choice(ParamType):
230 """The choice type allows a value to be checked against a fixed set
231 of supported values. All of these values have to be strings.
233 You should only pass a list or tuple of choices. Other iterables
234 (like generators) may lead to surprising results.
236 The resulting value will always be one of the originally passed choices
237 regardless of ``case_sensitive`` or any ``ctx.token_normalize_func``
238 being specified.
240 See :ref:`choice-opts` for an example.
242 :param case_sensitive: Set to false to make choices case
243 insensitive. Defaults to true.
244 """
246 name = "choice"
248 def __init__(
249 self, choices: cabc.Sequence[str], case_sensitive: bool = True
250 ) -> None:
251 self.choices = choices
252 self.case_sensitive = case_sensitive
254 def to_info_dict(self) -> dict[str, t.Any]:
255 info_dict = super().to_info_dict()
256 info_dict["choices"] = self.choices
257 info_dict["case_sensitive"] = self.case_sensitive
258 return info_dict
260 def get_metavar(self, param: Parameter) -> str:
261 choices_str = "|".join(self.choices)
263 # Use curly braces to indicate a required argument.
264 if param.required and param.param_type_name == "argument":
265 return f"{{{choices_str}}}"
267 # Use square braces to indicate an option or optional argument.
268 return f"[{choices_str}]"
270 def get_missing_message(self, param: Parameter) -> str:
271 return _("Choose from:\n\t{choices}").format(choices=",\n\t".join(self.choices))
273 def convert(
274 self, value: t.Any, param: Parameter | None, ctx: Context | None
275 ) -> t.Any:
276 # Match through normalization and case sensitivity
277 # first do token_normalize_func, then lowercase
278 # preserve original `value` to produce an accurate message in
279 # `self.fail`
280 normed_value = value
281 normed_choices = {choice: choice for choice in self.choices}
283 if ctx is not None and ctx.token_normalize_func is not None:
284 normed_value = ctx.token_normalize_func(value)
285 normed_choices = {
286 ctx.token_normalize_func(normed_choice): original
287 for normed_choice, original in normed_choices.items()
288 }
290 if not self.case_sensitive:
291 normed_value = normed_value.casefold()
292 normed_choices = {
293 normed_choice.casefold(): original
294 for normed_choice, original in normed_choices.items()
295 }
297 if normed_value in normed_choices:
298 return normed_choices[normed_value]
300 choices_str = ", ".join(map(repr, self.choices))
301 self.fail(
302 ngettext(
303 "{value!r} is not {choice}.",
304 "{value!r} is not one of {choices}.",
305 len(self.choices),
306 ).format(value=value, choice=choices_str, choices=choices_str),
307 param,
308 ctx,
309 )
311 def __repr__(self) -> str:
312 return f"Choice({list(self.choices)})"
314 def shell_complete(
315 self, ctx: Context, param: Parameter, incomplete: str
316 ) -> list[CompletionItem]:
317 """Complete choices that start with the incomplete value.
319 :param ctx: Invocation context for this command.
320 :param param: The parameter that is requesting completion.
321 :param incomplete: Value being completed. May be empty.
323 .. versionadded:: 8.0
324 """
325 from click.shell_completion import CompletionItem
327 str_choices = map(str, self.choices)
329 if self.case_sensitive:
330 matched = (c for c in str_choices if c.startswith(incomplete))
331 else:
332 incomplete = incomplete.lower()
333 matched = (c for c in str_choices if c.lower().startswith(incomplete))
335 return [CompletionItem(c) for c in matched]
338class DateTime(ParamType):
339 """The DateTime type converts date strings into `datetime` objects.
341 The format strings which are checked are configurable, but default to some
342 common (non-timezone aware) ISO 8601 formats.
344 When specifying *DateTime* formats, you should only pass a list or a tuple.
345 Other iterables, like generators, may lead to surprising results.
347 The format strings are processed using ``datetime.strptime``, and this
348 consequently defines the format strings which are allowed.
350 Parsing is tried using each format, in order, and the first format which
351 parses successfully is used.
353 :param formats: A list or tuple of date format strings, in the order in
354 which they should be tried. Defaults to
355 ``'%Y-%m-%d'``, ``'%Y-%m-%dT%H:%M:%S'``,
356 ``'%Y-%m-%d %H:%M:%S'``.
357 """
359 name = "datetime"
361 def __init__(self, formats: cabc.Sequence[str] | None = None):
362 self.formats: cabc.Sequence[str] = formats or [
363 "%Y-%m-%d",
364 "%Y-%m-%dT%H:%M:%S",
365 "%Y-%m-%d %H:%M:%S",
366 ]
368 def to_info_dict(self) -> dict[str, t.Any]:
369 info_dict = super().to_info_dict()
370 info_dict["formats"] = self.formats
371 return info_dict
373 def get_metavar(self, param: Parameter) -> str:
374 return f"[{'|'.join(self.formats)}]"
376 def _try_to_convert_date(self, value: t.Any, format: str) -> datetime | None:
377 try:
378 return datetime.strptime(value, format)
379 except ValueError:
380 return None
382 def convert(
383 self, value: t.Any, param: Parameter | None, ctx: Context | None
384 ) -> t.Any:
385 if isinstance(value, datetime):
386 return value
388 for format in self.formats:
389 converted = self._try_to_convert_date(value, format)
391 if converted is not None:
392 return converted
394 formats_str = ", ".join(map(repr, self.formats))
395 self.fail(
396 ngettext(
397 "{value!r} does not match the format {format}.",
398 "{value!r} does not match the formats {formats}.",
399 len(self.formats),
400 ).format(value=value, format=formats_str, formats=formats_str),
401 param,
402 ctx,
403 )
405 def __repr__(self) -> str:
406 return "DateTime"
409class _NumberParamTypeBase(ParamType):
410 _number_class: t.ClassVar[type[t.Any]]
412 def convert(
413 self, value: t.Any, param: Parameter | None, ctx: Context | None
414 ) -> t.Any:
415 try:
416 return self._number_class(value)
417 except ValueError:
418 self.fail(
419 _("{value!r} is not a valid {number_type}.").format(
420 value=value, number_type=self.name
421 ),
422 param,
423 ctx,
424 )
427class _NumberRangeBase(_NumberParamTypeBase):
428 def __init__(
429 self,
430 min: float | None = None,
431 max: float | None = None,
432 min_open: bool = False,
433 max_open: bool = False,
434 clamp: bool = False,
435 ) -> None:
436 self.min = min
437 self.max = max
438 self.min_open = min_open
439 self.max_open = max_open
440 self.clamp = clamp
442 def to_info_dict(self) -> dict[str, t.Any]:
443 info_dict = super().to_info_dict()
444 info_dict.update(
445 min=self.min,
446 max=self.max,
447 min_open=self.min_open,
448 max_open=self.max_open,
449 clamp=self.clamp,
450 )
451 return info_dict
453 def convert(
454 self, value: t.Any, param: Parameter | None, ctx: Context | None
455 ) -> t.Any:
456 import operator
458 rv = super().convert(value, param, ctx)
459 lt_min: bool = self.min is not None and (
460 operator.le if self.min_open else operator.lt
461 )(rv, self.min)
462 gt_max: bool = self.max is not None and (
463 operator.ge if self.max_open else operator.gt
464 )(rv, self.max)
466 if self.clamp:
467 if lt_min:
468 return self._clamp(self.min, 1, self.min_open) # type: ignore
470 if gt_max:
471 return self._clamp(self.max, -1, self.max_open) # type: ignore
473 if lt_min or gt_max:
474 self.fail(
475 _("{value} is not in the range {range}.").format(
476 value=rv, range=self._describe_range()
477 ),
478 param,
479 ctx,
480 )
482 return rv
484 def _clamp(self, bound: float, dir: t.Literal[1, -1], open: bool) -> float:
485 """Find the valid value to clamp to bound in the given
486 direction.
488 :param bound: The boundary value.
489 :param dir: 1 or -1 indicating the direction to move.
490 :param open: If true, the range does not include the bound.
491 """
492 raise NotImplementedError
494 def _describe_range(self) -> str:
495 """Describe the range for use in help text."""
496 if self.min is None:
497 op = "<" if self.max_open else "<="
498 return f"x{op}{self.max}"
500 if self.max is None:
501 op = ">" if self.min_open else ">="
502 return f"x{op}{self.min}"
504 lop = "<" if self.min_open else "<="
505 rop = "<" if self.max_open else "<="
506 return f"{self.min}{lop}x{rop}{self.max}"
508 def __repr__(self) -> str:
509 clamp = " clamped" if self.clamp else ""
510 return f"<{type(self).__name__} {self._describe_range()}{clamp}>"
513class IntParamType(_NumberParamTypeBase):
514 name = "integer"
515 _number_class = int
517 def __repr__(self) -> str:
518 return "INT"
521class IntRange(_NumberRangeBase, IntParamType):
522 """Restrict an :data:`click.INT` value to a range of accepted
523 values. See :ref:`ranges`.
525 If ``min`` or ``max`` are not passed, any value is accepted in that
526 direction. If ``min_open`` or ``max_open`` are enabled, the
527 corresponding boundary is not included in the range.
529 If ``clamp`` is enabled, a value outside the range is clamped to the
530 boundary instead of failing.
532 .. versionchanged:: 8.0
533 Added the ``min_open`` and ``max_open`` parameters.
534 """
536 name = "integer range"
538 def _clamp( # type: ignore
539 self, bound: int, dir: t.Literal[1, -1], open: bool
540 ) -> int:
541 if not open:
542 return bound
544 return bound + dir
547class FloatParamType(_NumberParamTypeBase):
548 name = "float"
549 _number_class = float
551 def __repr__(self) -> str:
552 return "FLOAT"
555class FloatRange(_NumberRangeBase, FloatParamType):
556 """Restrict a :data:`click.FLOAT` value to a range of accepted
557 values. See :ref:`ranges`.
559 If ``min`` or ``max`` are not passed, any value is accepted in that
560 direction. If ``min_open`` or ``max_open`` are enabled, the
561 corresponding boundary is not included in the range.
563 If ``clamp`` is enabled, a value outside the range is clamped to the
564 boundary instead of failing. This is not supported if either
565 boundary is marked ``open``.
567 .. versionchanged:: 8.0
568 Added the ``min_open`` and ``max_open`` parameters.
569 """
571 name = "float range"
573 def __init__(
574 self,
575 min: float | None = None,
576 max: float | None = None,
577 min_open: bool = False,
578 max_open: bool = False,
579 clamp: bool = False,
580 ) -> None:
581 super().__init__(
582 min=min, max=max, min_open=min_open, max_open=max_open, clamp=clamp
583 )
585 if (min_open or max_open) and clamp:
586 raise TypeError("Clamping is not supported for open bounds.")
588 def _clamp(self, bound: float, dir: t.Literal[1, -1], open: bool) -> float:
589 if not open:
590 return bound
592 # Could use Python 3.9's math.nextafter here, but clamping an
593 # open float range doesn't seem to be particularly useful. It's
594 # left up to the user to write a callback to do it if needed.
595 raise RuntimeError("Clamping is not supported for open bounds.")
598class BoolParamType(ParamType):
599 name = "boolean"
601 def convert(
602 self, value: t.Any, param: Parameter | None, ctx: Context | None
603 ) -> t.Any:
604 if value in {False, True}:
605 return bool(value)
607 norm = value.strip().lower()
609 if norm in {"1", "true", "t", "yes", "y", "on"}:
610 return True
612 if norm in {"0", "false", "f", "no", "n", "off"}:
613 return False
615 self.fail(
616 _("{value!r} is not a valid boolean.").format(value=value), param, ctx
617 )
619 def __repr__(self) -> str:
620 return "BOOL"
623class UUIDParameterType(ParamType):
624 name = "uuid"
626 def convert(
627 self, value: t.Any, param: Parameter | None, ctx: Context | None
628 ) -> t.Any:
629 import uuid
631 if isinstance(value, uuid.UUID):
632 return value
634 value = value.strip()
636 try:
637 return uuid.UUID(value)
638 except ValueError:
639 self.fail(
640 _("{value!r} is not a valid UUID.").format(value=value), param, ctx
641 )
643 def __repr__(self) -> str:
644 return "UUID"
647class File(ParamType):
648 """Declares a parameter to be a file for reading or writing. The file
649 is automatically closed once the context tears down (after the command
650 finished working).
652 Files can be opened for reading or writing. The special value ``-``
653 indicates stdin or stdout depending on the mode.
655 By default, the file is opened for reading text data, but it can also be
656 opened in binary mode or for writing. The encoding parameter can be used
657 to force a specific encoding.
659 The `lazy` flag controls if the file should be opened immediately or upon
660 first IO. The default is to be non-lazy for standard input and output
661 streams as well as files opened for reading, `lazy` otherwise. When opening a
662 file lazily for reading, it is still opened temporarily for validation, but
663 will not be held open until first IO. lazy is mainly useful when opening
664 for writing to avoid creating the file until it is needed.
666 Starting with Click 2.0, files can also be opened atomically in which
667 case all writes go into a separate file in the same folder and upon
668 completion the file will be moved over to the original location. This
669 is useful if a file regularly read by other users is modified.
671 See :ref:`file-args` for more information.
672 """
674 name = "filename"
675 envvar_list_splitter: t.ClassVar[str] = os.path.pathsep
677 def __init__(
678 self,
679 mode: str = "r",
680 encoding: str | None = None,
681 errors: str | None = "strict",
682 lazy: bool | None = None,
683 atomic: bool = False,
684 ) -> None:
685 self.mode = mode
686 self.encoding = encoding
687 self.errors = errors
688 self.lazy = lazy
689 self.atomic = atomic
691 def to_info_dict(self) -> dict[str, t.Any]:
692 info_dict = super().to_info_dict()
693 info_dict.update(mode=self.mode, encoding=self.encoding)
694 return info_dict
696 def resolve_lazy_flag(self, value: str | os.PathLike[str]) -> bool:
697 if self.lazy is not None:
698 return self.lazy
699 if os.fspath(value) == "-":
700 return False
701 elif "w" in self.mode:
702 return True
703 return False
705 def convert(
706 self,
707 value: str | os.PathLike[str] | t.IO[t.Any],
708 param: Parameter | None,
709 ctx: Context | None,
710 ) -> t.IO[t.Any]:
711 if _is_file_like(value):
712 return value
714 value = t.cast("str | os.PathLike[str]", value)
716 try:
717 lazy = self.resolve_lazy_flag(value)
719 if lazy:
720 lf = LazyFile(
721 value, self.mode, self.encoding, self.errors, atomic=self.atomic
722 )
724 if ctx is not None:
725 ctx.call_on_close(lf.close_intelligently)
727 return t.cast("t.IO[t.Any]", lf)
729 f, should_close = open_stream(
730 value, self.mode, self.encoding, self.errors, atomic=self.atomic
731 )
733 # If a context is provided, we automatically close the file
734 # at the end of the context execution (or flush out). If a
735 # context does not exist, it's the caller's responsibility to
736 # properly close the file. This for instance happens when the
737 # type is used with prompts.
738 if ctx is not None:
739 if should_close:
740 ctx.call_on_close(safecall(f.close))
741 else:
742 ctx.call_on_close(safecall(f.flush))
744 return f
745 except OSError as e: # noqa: B014
746 self.fail(f"'{format_filename(value)}': {e.strerror}", param, ctx)
748 def shell_complete(
749 self, ctx: Context, param: Parameter, incomplete: str
750 ) -> list[CompletionItem]:
751 """Return a special completion marker that tells the completion
752 system to use the shell to provide file path completions.
754 :param ctx: Invocation context for this command.
755 :param param: The parameter that is requesting completion.
756 :param incomplete: Value being completed. May be empty.
758 .. versionadded:: 8.0
759 """
760 from click.shell_completion import CompletionItem
762 return [CompletionItem(incomplete, type="file")]
765def _is_file_like(value: t.Any) -> te.TypeGuard[t.IO[t.Any]]:
766 return hasattr(value, "read") or hasattr(value, "write")
769class Path(ParamType):
770 """The ``Path`` type is similar to the :class:`File` type, but
771 returns the filename instead of an open file. Various checks can be
772 enabled to validate the type of file and permissions.
774 :param exists: The file or directory needs to exist for the value to
775 be valid. If this is not set to ``True``, and the file does not
776 exist, then all further checks are silently skipped.
777 :param file_okay: Allow a file as a value.
778 :param dir_okay: Allow a directory as a value.
779 :param readable: if true, a readable check is performed.
780 :param writable: if true, a writable check is performed.
781 :param executable: if true, an executable check is performed.
782 :param resolve_path: Make the value absolute and resolve any
783 symlinks. A ``~`` is not expanded, as this is supposed to be
784 done by the shell only.
785 :param allow_dash: Allow a single dash as a value, which indicates
786 a standard stream (but does not open it). Use
787 :func:`~click.open_file` to handle opening this value.
788 :param path_type: Convert the incoming path value to this type. If
789 ``None``, keep Python's default, which is ``str``. Useful to
790 convert to :class:`pathlib.Path`.
792 .. versionchanged:: 8.1
793 Added the ``executable`` parameter.
795 .. versionchanged:: 8.0
796 Allow passing ``path_type=pathlib.Path``.
798 .. versionchanged:: 6.0
799 Added the ``allow_dash`` parameter.
800 """
802 envvar_list_splitter: t.ClassVar[str] = os.path.pathsep
804 def __init__(
805 self,
806 exists: bool = False,
807 file_okay: bool = True,
808 dir_okay: bool = True,
809 writable: bool = False,
810 readable: bool = True,
811 resolve_path: bool = False,
812 allow_dash: bool = False,
813 path_type: type[t.Any] | None = None,
814 executable: bool = False,
815 ):
816 self.exists = exists
817 self.file_okay = file_okay
818 self.dir_okay = dir_okay
819 self.readable = readable
820 self.writable = writable
821 self.executable = executable
822 self.resolve_path = resolve_path
823 self.allow_dash = allow_dash
824 self.type = path_type
826 if self.file_okay and not self.dir_okay:
827 self.name: str = _("file")
828 elif self.dir_okay and not self.file_okay:
829 self.name = _("directory")
830 else:
831 self.name = _("path")
833 def to_info_dict(self) -> dict[str, t.Any]:
834 info_dict = super().to_info_dict()
835 info_dict.update(
836 exists=self.exists,
837 file_okay=self.file_okay,
838 dir_okay=self.dir_okay,
839 writable=self.writable,
840 readable=self.readable,
841 allow_dash=self.allow_dash,
842 )
843 return info_dict
845 def coerce_path_result(
846 self, value: str | os.PathLike[str]
847 ) -> str | bytes | os.PathLike[str]:
848 if self.type is not None and not isinstance(value, self.type):
849 if self.type is str:
850 return os.fsdecode(value)
851 elif self.type is bytes:
852 return os.fsencode(value)
853 else:
854 return t.cast("os.PathLike[str]", self.type(value))
856 return value
858 def convert(
859 self,
860 value: str | os.PathLike[str],
861 param: Parameter | None,
862 ctx: Context | None,
863 ) -> str | bytes | os.PathLike[str]:
864 rv = value
866 is_dash = self.file_okay and self.allow_dash and rv in (b"-", "-")
868 if not is_dash:
869 if self.resolve_path:
870 rv = os.path.realpath(rv)
872 try:
873 st = os.stat(rv)
874 except OSError:
875 if not self.exists:
876 return self.coerce_path_result(rv)
877 self.fail(
878 _("{name} {filename!r} does not exist.").format(
879 name=self.name.title(), filename=format_filename(value)
880 ),
881 param,
882 ctx,
883 )
885 if not self.file_okay and stat.S_ISREG(st.st_mode):
886 self.fail(
887 _("{name} {filename!r} is a file.").format(
888 name=self.name.title(), filename=format_filename(value)
889 ),
890 param,
891 ctx,
892 )
893 if not self.dir_okay and stat.S_ISDIR(st.st_mode):
894 self.fail(
895 _("{name} '{filename}' is a directory.").format(
896 name=self.name.title(), filename=format_filename(value)
897 ),
898 param,
899 ctx,
900 )
902 if self.readable and not os.access(rv, os.R_OK):
903 self.fail(
904 _("{name} {filename!r} is not readable.").format(
905 name=self.name.title(), filename=format_filename(value)
906 ),
907 param,
908 ctx,
909 )
911 if self.writable and not os.access(rv, os.W_OK):
912 self.fail(
913 _("{name} {filename!r} is not writable.").format(
914 name=self.name.title(), filename=format_filename(value)
915 ),
916 param,
917 ctx,
918 )
920 if self.executable and not os.access(value, os.X_OK):
921 self.fail(
922 _("{name} {filename!r} is not executable.").format(
923 name=self.name.title(), filename=format_filename(value)
924 ),
925 param,
926 ctx,
927 )
929 return self.coerce_path_result(rv)
931 def shell_complete(
932 self, ctx: Context, param: Parameter, incomplete: str
933 ) -> list[CompletionItem]:
934 """Return a special completion marker that tells the completion
935 system to use the shell to provide path completions for only
936 directories or any paths.
938 :param ctx: Invocation context for this command.
939 :param param: The parameter that is requesting completion.
940 :param incomplete: Value being completed. May be empty.
942 .. versionadded:: 8.0
943 """
944 from click.shell_completion import CompletionItem
946 type = "dir" if self.dir_okay and not self.file_okay else "file"
947 return [CompletionItem(incomplete, type=type)]
950class Tuple(CompositeParamType):
951 """The default behavior of Click is to apply a type on a value directly.
952 This works well in most cases, except for when `nargs` is set to a fixed
953 count and different types should be used for different items. In this
954 case the :class:`Tuple` type can be used. This type can only be used
955 if `nargs` is set to a fixed number.
957 For more information see :ref:`tuple-type`.
959 This can be selected by using a Python tuple literal as a type.
961 :param types: a list of types that should be used for the tuple items.
962 """
964 def __init__(self, types: cabc.Sequence[type[t.Any] | ParamType]) -> None:
965 self.types: cabc.Sequence[ParamType] = [convert_type(ty) for ty in types]
967 def to_info_dict(self) -> dict[str, t.Any]:
968 info_dict = super().to_info_dict()
969 info_dict["types"] = [t.to_info_dict() for t in self.types]
970 return info_dict
972 @property
973 def name(self) -> str: # type: ignore
974 return f"<{' '.join(ty.name for ty in self.types)}>"
976 @property
977 def arity(self) -> int: # type: ignore
978 return len(self.types)
980 def convert(
981 self, value: t.Any, param: Parameter | None, ctx: Context | None
982 ) -> t.Any:
983 len_type = len(self.types)
984 len_value = len(value)
986 if len_value != len_type:
987 self.fail(
988 ngettext(
989 "{len_type} values are required, but {len_value} was given.",
990 "{len_type} values are required, but {len_value} were given.",
991 len_value,
992 ).format(len_type=len_type, len_value=len_value),
993 param=param,
994 ctx=ctx,
995 )
997 return tuple(ty(x, param, ctx) for ty, x in zip(self.types, value))
1000def convert_type(ty: t.Any | None, default: t.Any | None = None) -> ParamType:
1001 """Find the most appropriate :class:`ParamType` for the given Python
1002 type. If the type isn't provided, it can be inferred from a default
1003 value.
1004 """
1005 guessed_type = False
1007 if ty is None and default is not None:
1008 if isinstance(default, (tuple, list)):
1009 # If the default is empty, ty will remain None and will
1010 # return STRING.
1011 if default:
1012 item = default[0]
1014 # A tuple of tuples needs to detect the inner types.
1015 # Can't call convert recursively because that would
1016 # incorrectly unwind the tuple to a single type.
1017 if isinstance(item, (tuple, list)):
1018 ty = tuple(map(type, item))
1019 else:
1020 ty = type(item)
1021 else:
1022 ty = type(default)
1024 guessed_type = True
1026 if isinstance(ty, tuple):
1027 return Tuple(ty)
1029 if isinstance(ty, ParamType):
1030 return ty
1032 if ty is str or ty is None:
1033 return STRING
1035 if ty is int:
1036 return INT
1038 if ty is float:
1039 return FLOAT
1041 if ty is bool:
1042 return BOOL
1044 if guessed_type:
1045 return STRING
1047 if __debug__:
1048 try:
1049 if issubclass(ty, ParamType):
1050 raise AssertionError(
1051 f"Attempted to use an uninstantiated parameter type ({ty})."
1052 )
1053 except TypeError:
1054 # ty is an instance (correct), so issubclass fails.
1055 pass
1057 return FuncParamType(ty)
1060#: A dummy parameter type that just does nothing. From a user's
1061#: perspective this appears to just be the same as `STRING` but
1062#: internally no string conversion takes place if the input was bytes.
1063#: This is usually useful when working with file paths as they can
1064#: appear in bytes and unicode.
1065#:
1066#: For path related uses the :class:`Path` type is a better choice but
1067#: there are situations where an unprocessed type is useful which is why
1068#: it is is provided.
1069#:
1070#: .. versionadded:: 4.0
1071UNPROCESSED = UnprocessedParamType()
1073#: A unicode string parameter type which is the implicit default. This
1074#: can also be selected by using ``str`` as type.
1075STRING = StringParamType()
1077#: An integer parameter. This can also be selected by using ``int`` as
1078#: type.
1079INT = IntParamType()
1081#: A floating point value parameter. This can also be selected by using
1082#: ``float`` as type.
1083FLOAT = FloatParamType()
1085#: A boolean parameter. This is the default for boolean flags. This can
1086#: also be selected by using ``bool`` as a type.
1087BOOL = BoolParamType()
1089#: A UUID parameter.
1090UUID = UUIDParameterType()