Coverage for src/click/types.py: 30%
408 statements
« prev ^ index » next coverage.py v7.2.2, created at 2023-05-21 11:49 +0200
« prev ^ index » next coverage.py v7.2.2, created at 2023-05-21 11:49 +0200
1import os
2import stat
3import typing as t
4from datetime import datetime
5from gettext import gettext as _
6from gettext import ngettext
8from ._compat import _get_argv_encoding
9from ._compat import get_filesystem_encoding
10from ._compat import open_stream
11from .exceptions import BadParameter
12from .utils import LazyFile
13from .utils import safecall
15if t.TYPE_CHECKING: 15 ↛ 16line 15 didn't jump to line 16, because the condition on line 15 was never true
16 import typing_extensions as te
17 from .core import Context
18 from .core import Parameter
19 from .shell_completion import CompletionItem
22class ParamType:
23 """Represents the type of a parameter. Validates and converts values
24 from the command line or Python into the correct type.
26 To implement a custom type, subclass and implement at least the
27 following:
29 - The :attr:`name` class attribute must be set.
30 - Calling an instance of the type with ``None`` must return
31 ``None``. This is already implemented by default.
32 - :meth:`convert` must convert string values to the correct type.
33 - :meth:`convert` must accept values that are already the correct
34 type.
35 - It must be able to convert a value if the ``ctx`` and ``param``
36 arguments are ``None``. This can occur when converting prompt
37 input.
38 """
40 is_composite: t.ClassVar[bool] = False
41 arity: t.ClassVar[int] = 1
43 #: the descriptive name of this type
44 name: str
46 #: if a list of this type is expected and the value is pulled from a
47 #: string environment variable, this is what splits it up. `None`
48 #: means any whitespace. For all parameters the general rule is that
49 #: whitespace splits them up. The exception are paths and files which
50 #: are split by ``os.path.pathsep`` by default (":" on Unix and ";" on
51 #: Windows).
52 envvar_list_splitter: t.ClassVar[t.Optional[str]] = None
54 def to_info_dict(self) -> t.Dict[str, t.Any]:
55 """Gather information that could be useful for a tool generating
56 user-facing documentation.
58 Use :meth:`click.Context.to_info_dict` to traverse the entire
59 CLI structure.
61 .. versionadded:: 8.0
62 """
63 # The class name without the "ParamType" suffix.
64 param_type = type(self).__name__.partition("ParamType")[0]
65 param_type = param_type.partition("ParameterType")[0]
67 # Custom subclasses might not remember to set a name.
68 if hasattr(self, "name"):
69 name = self.name
70 else:
71 name = param_type
73 return {"param_type": param_type, "name": name}
75 def __call__(
76 self,
77 value: t.Any,
78 param: t.Optional["Parameter"] = None,
79 ctx: t.Optional["Context"] = None,
80 ) -> t.Any:
81 if value is not None:
82 return self.convert(value, param, ctx)
84 def get_metavar(self, param: "Parameter") -> t.Optional[str]:
85 """Returns the metavar default for this param if it provides one."""
87 def get_missing_message(self, param: "Parameter") -> t.Optional[str]:
88 """Optionally might return extra information about a missing
89 parameter.
91 .. versionadded:: 2.0
92 """
94 def convert(
95 self, value: t.Any, param: t.Optional["Parameter"], ctx: t.Optional["Context"]
96 ) -> t.Any:
97 """Convert the value to the correct type. This is not called if
98 the value is ``None`` (the missing value).
100 This must accept string values from the command line, as well as
101 values that are already the correct type. It may also convert
102 other compatible types.
104 The ``param`` and ``ctx`` arguments may be ``None`` in certain
105 situations, such as when converting prompt input.
107 If the value cannot be converted, call :meth:`fail` with a
108 descriptive message.
110 :param value: The value to convert.
111 :param param: The parameter that is using this type to convert
112 its value. May be ``None``.
113 :param ctx: The current context that arrived at this value. May
114 be ``None``.
115 """
116 return value
118 def split_envvar_value(self, rv: str) -> t.Sequence[str]:
119 """Given a value from an environment variable this splits it up
120 into small chunks depending on the defined envvar list splitter.
122 If the splitter is set to `None`, which means that whitespace splits,
123 then leading and trailing whitespace is ignored. Otherwise, leading
124 and trailing splitters usually lead to empty items being included.
125 """
126 return (rv or "").split(self.envvar_list_splitter)
128 def fail(
129 self,
130 message: str,
131 param: t.Optional["Parameter"] = None,
132 ctx: t.Optional["Context"] = None,
133 ) -> "t.NoReturn":
134 """Helper method to fail with an invalid value message."""
135 raise BadParameter(message, ctx=ctx, param=param)
137 def shell_complete(
138 self, ctx: "Context", param: "Parameter", incomplete: str
139 ) -> t.List["CompletionItem"]:
140 """Return a list of
141 :class:`~click.shell_completion.CompletionItem` objects for the
142 incomplete value. Most types do not provide completions, but
143 some do, and this allows custom types to provide custom
144 completions as well.
146 :param ctx: Invocation context for this command.
147 :param param: The parameter that is requesting completion.
148 :param incomplete: Value being completed. May be empty.
150 .. versionadded:: 8.0
151 """
152 return []
155class CompositeParamType(ParamType):
156 is_composite = True
158 @property
159 def arity(self) -> int: # type: ignore
160 raise NotImplementedError()
163class FuncParamType(ParamType):
164 def __init__(self, func: t.Callable[[t.Any], t.Any]) -> None:
165 self.name = func.__name__
166 self.func = func
168 def to_info_dict(self) -> t.Dict[str, t.Any]:
169 info_dict = super().to_info_dict()
170 info_dict["func"] = self.func
171 return info_dict
173 def convert(
174 self, value: t.Any, param: t.Optional["Parameter"], ctx: t.Optional["Context"]
175 ) -> t.Any:
176 try:
177 return self.func(value)
178 except ValueError:
179 try:
180 value = str(value)
181 except UnicodeError:
182 value = value.decode("utf-8", "replace")
184 self.fail(value, param, ctx)
187class UnprocessedParamType(ParamType):
188 name = "text"
190 def convert(
191 self, value: t.Any, param: t.Optional["Parameter"], ctx: t.Optional["Context"]
192 ) -> t.Any:
193 return value
195 def __repr__(self) -> str:
196 return "UNPROCESSED"
199class StringParamType(ParamType):
200 name = "text"
202 def convert(
203 self, value: t.Any, param: t.Optional["Parameter"], ctx: t.Optional["Context"]
204 ) -> t.Any:
205 if isinstance(value, bytes):
206 enc = _get_argv_encoding()
207 try:
208 value = value.decode(enc)
209 except UnicodeError:
210 fs_enc = get_filesystem_encoding()
211 if fs_enc != enc:
212 try:
213 value = value.decode(fs_enc)
214 except UnicodeError:
215 value = value.decode("utf-8", "replace")
216 else:
217 value = value.decode("utf-8", "replace")
218 return value
219 return str(value)
221 def __repr__(self) -> str:
222 return "STRING"
225class Choice(ParamType):
226 """The choice type allows a value to be checked against a fixed set
227 of supported values. All of these values have to be strings.
229 You should only pass a list or tuple of choices. Other iterables
230 (like generators) may lead to surprising results.
232 The resulting value will always be one of the originally passed choices
233 regardless of ``case_sensitive`` or any ``ctx.token_normalize_func``
234 being specified.
236 See :ref:`choice-opts` for an example.
238 :param case_sensitive: Set to false to make choices case
239 insensitive. Defaults to true.
240 """
242 name = "choice"
244 def __init__(self, choices: t.Sequence[str], case_sensitive: bool = True) -> None:
245 self.choices = choices
246 self.case_sensitive = case_sensitive
248 def to_info_dict(self) -> t.Dict[str, t.Any]:
249 info_dict = super().to_info_dict()
250 info_dict["choices"] = self.choices
251 info_dict["case_sensitive"] = self.case_sensitive
252 return info_dict
254 def get_metavar(self, param: "Parameter") -> str:
255 choices_str = "|".join(self.choices)
257 # Use curly braces to indicate a required argument.
258 if param.required and param.param_type_name == "argument":
259 return f"{{{choices_str}}}"
261 # Use square braces to indicate an option or optional argument.
262 return f"[{choices_str}]"
264 def get_missing_message(self, param: "Parameter") -> str:
265 return _("Choose from:\n\t{choices}").format(choices=",\n\t".join(self.choices))
267 def convert(
268 self, value: t.Any, param: t.Optional["Parameter"], ctx: t.Optional["Context"]
269 ) -> t.Any:
270 # Match through normalization and case sensitivity
271 # first do token_normalize_func, then lowercase
272 # preserve original `value` to produce an accurate message in
273 # `self.fail`
274 normed_value = value
275 normed_choices = {choice: choice for choice in self.choices}
277 if ctx is not None and ctx.token_normalize_func is not None:
278 normed_value = ctx.token_normalize_func(value)
279 normed_choices = {
280 ctx.token_normalize_func(normed_choice): original
281 for normed_choice, original in normed_choices.items()
282 }
284 if not self.case_sensitive:
285 normed_value = normed_value.casefold()
286 normed_choices = {
287 normed_choice.casefold(): original
288 for normed_choice, original in normed_choices.items()
289 }
291 if normed_value in normed_choices:
292 return normed_choices[normed_value]
294 choices_str = ", ".join(map(repr, self.choices))
295 self.fail(
296 ngettext(
297 "{value!r} is not {choice}.",
298 "{value!r} is not one of {choices}.",
299 len(self.choices),
300 ).format(value=value, choice=choices_str, choices=choices_str),
301 param,
302 ctx,
303 )
305 def __repr__(self) -> str:
306 return f"Choice({list(self.choices)})"
308 def shell_complete(
309 self, ctx: "Context", param: "Parameter", incomplete: str
310 ) -> t.List["CompletionItem"]:
311 """Complete choices that start with the incomplete value.
313 :param ctx: Invocation context for this command.
314 :param param: The parameter that is requesting completion.
315 :param incomplete: Value being completed. May be empty.
317 .. versionadded:: 8.0
318 """
319 from click.shell_completion import CompletionItem
321 str_choices = map(str, self.choices)
323 if self.case_sensitive:
324 matched = (c for c in str_choices if c.startswith(incomplete))
325 else:
326 incomplete = incomplete.lower()
327 matched = (c for c in str_choices if c.lower().startswith(incomplete))
329 return [CompletionItem(c) for c in matched]
332class DateTime(ParamType):
333 """The DateTime type converts date strings into `datetime` objects.
335 The format strings which are checked are configurable, but default to some
336 common (non-timezone aware) ISO 8601 formats.
338 When specifying *DateTime* formats, you should only pass a list or a tuple.
339 Other iterables, like generators, may lead to surprising results.
341 The format strings are processed using ``datetime.strptime``, and this
342 consequently defines the format strings which are allowed.
344 Parsing is tried using each format, in order, and the first format which
345 parses successfully is used.
347 :param formats: A list or tuple of date format strings, in the order in
348 which they should be tried. Defaults to
349 ``'%Y-%m-%d'``, ``'%Y-%m-%dT%H:%M:%S'``,
350 ``'%Y-%m-%d %H:%M:%S'``.
351 """
353 name = "datetime"
355 def __init__(self, formats: t.Optional[t.Sequence[str]] = None):
356 self.formats = formats or ["%Y-%m-%d", "%Y-%m-%dT%H:%M:%S", "%Y-%m-%d %H:%M:%S"]
358 def to_info_dict(self) -> t.Dict[str, t.Any]:
359 info_dict = super().to_info_dict()
360 info_dict["formats"] = self.formats
361 return info_dict
363 def get_metavar(self, param: "Parameter") -> str:
364 return f"[{'|'.join(self.formats)}]"
366 def _try_to_convert_date(self, value: t.Any, format: str) -> t.Optional[datetime]:
367 try:
368 return datetime.strptime(value, format)
369 except ValueError:
370 return None
372 def convert(
373 self, value: t.Any, param: t.Optional["Parameter"], ctx: t.Optional["Context"]
374 ) -> t.Any:
375 if isinstance(value, datetime):
376 return value
378 for format in self.formats:
379 converted = self._try_to_convert_date(value, format)
381 if converted is not None:
382 return converted
384 formats_str = ", ".join(map(repr, self.formats))
385 self.fail(
386 ngettext(
387 "{value!r} does not match the format {format}.",
388 "{value!r} does not match the formats {formats}.",
389 len(self.formats),
390 ).format(value=value, format=formats_str, formats=formats_str),
391 param,
392 ctx,
393 )
395 def __repr__(self) -> str:
396 return "DateTime"
399class _NumberParamTypeBase(ParamType):
400 _number_class: t.ClassVar[t.Type[t.Any]]
402 def convert(
403 self, value: t.Any, param: t.Optional["Parameter"], ctx: t.Optional["Context"]
404 ) -> t.Any:
405 try:
406 return self._number_class(value)
407 except ValueError:
408 self.fail(
409 _("{value!r} is not a valid {number_type}.").format(
410 value=value, number_type=self.name
411 ),
412 param,
413 ctx,
414 )
417class _NumberRangeBase(_NumberParamTypeBase):
418 def __init__(
419 self,
420 min: t.Optional[float] = None,
421 max: t.Optional[float] = None,
422 min_open: bool = False,
423 max_open: bool = False,
424 clamp: bool = False,
425 ) -> None:
426 self.min = min
427 self.max = max
428 self.min_open = min_open
429 self.max_open = max_open
430 self.clamp = clamp
432 def to_info_dict(self) -> t.Dict[str, t.Any]:
433 info_dict = super().to_info_dict()
434 info_dict.update(
435 min=self.min,
436 max=self.max,
437 min_open=self.min_open,
438 max_open=self.max_open,
439 clamp=self.clamp,
440 )
441 return info_dict
443 def convert(
444 self, value: t.Any, param: t.Optional["Parameter"], ctx: t.Optional["Context"]
445 ) -> t.Any:
446 import operator
448 rv = super().convert(value, param, ctx)
449 lt_min: bool = self.min is not None and (
450 operator.le if self.min_open else operator.lt
451 )(rv, self.min)
452 gt_max: bool = self.max is not None and (
453 operator.ge if self.max_open else operator.gt
454 )(rv, self.max)
456 if self.clamp:
457 if lt_min:
458 return self._clamp(self.min, 1, self.min_open) # type: ignore
460 if gt_max:
461 return self._clamp(self.max, -1, self.max_open) # type: ignore
463 if lt_min or gt_max:
464 self.fail(
465 _("{value} is not in the range {range}.").format(
466 value=rv, range=self._describe_range()
467 ),
468 param,
469 ctx,
470 )
472 return rv
474 def _clamp(self, bound: float, dir: "te.Literal[1, -1]", open: bool) -> float:
475 """Find the valid value to clamp to bound in the given
476 direction.
478 :param bound: The boundary value.
479 :param dir: 1 or -1 indicating the direction to move.
480 :param open: If true, the range does not include the bound.
481 """
482 raise NotImplementedError
484 def _describe_range(self) -> str:
485 """Describe the range for use in help text."""
486 if self.min is None:
487 op = "<" if self.max_open else "<="
488 return f"x{op}{self.max}"
490 if self.max is None:
491 op = ">" if self.min_open else ">="
492 return f"x{op}{self.min}"
494 lop = "<" if self.min_open else "<="
495 rop = "<" if self.max_open else "<="
496 return f"{self.min}{lop}x{rop}{self.max}"
498 def __repr__(self) -> str:
499 clamp = " clamped" if self.clamp else ""
500 return f"<{type(self).__name__} {self._describe_range()}{clamp}>"
503class IntParamType(_NumberParamTypeBase):
504 name = "integer"
505 _number_class = int
507 def __repr__(self) -> str:
508 return "INT"
511class IntRange(_NumberRangeBase, IntParamType):
512 """Restrict an :data:`click.INT` value to a range of accepted
513 values. See :ref:`ranges`.
515 If ``min`` or ``max`` are not passed, any value is accepted in that
516 direction. If ``min_open`` or ``max_open`` are enabled, the
517 corresponding boundary is not included in the range.
519 If ``clamp`` is enabled, a value outside the range is clamped to the
520 boundary instead of failing.
522 .. versionchanged:: 8.0
523 Added the ``min_open`` and ``max_open`` parameters.
524 """
526 name = "integer range"
528 def _clamp( # type: ignore
529 self, bound: int, dir: "te.Literal[1, -1]", open: bool
530 ) -> int:
531 if not open:
532 return bound
534 return bound + dir
537class FloatParamType(_NumberParamTypeBase):
538 name = "float"
539 _number_class = float
541 def __repr__(self) -> str:
542 return "FLOAT"
545class FloatRange(_NumberRangeBase, FloatParamType):
546 """Restrict a :data:`click.FLOAT` value to a range of accepted
547 values. See :ref:`ranges`.
549 If ``min`` or ``max`` are not passed, any value is accepted in that
550 direction. If ``min_open`` or ``max_open`` are enabled, the
551 corresponding boundary is not included in the range.
553 If ``clamp`` is enabled, a value outside the range is clamped to the
554 boundary instead of failing. This is not supported if either
555 boundary is marked ``open``.
557 .. versionchanged:: 8.0
558 Added the ``min_open`` and ``max_open`` parameters.
559 """
561 name = "float range"
563 def __init__(
564 self,
565 min: t.Optional[float] = None,
566 max: t.Optional[float] = None,
567 min_open: bool = False,
568 max_open: bool = False,
569 clamp: bool = False,
570 ) -> None:
571 super().__init__(
572 min=min, max=max, min_open=min_open, max_open=max_open, clamp=clamp
573 )
575 if (min_open or max_open) and clamp: 575 ↛ 576line 575 didn't jump to line 576, because the condition on line 575 was never true
576 raise TypeError("Clamping is not supported for open bounds.")
578 def _clamp(self, bound: float, dir: "te.Literal[1, -1]", open: bool) -> float:
579 if not open:
580 return bound
582 # Could use Python 3.9's math.nextafter here, but clamping an
583 # open float range doesn't seem to be particularly useful. It's
584 # left up to the user to write a callback to do it if needed.
585 raise RuntimeError("Clamping is not supported for open bounds.")
588class BoolParamType(ParamType):
589 name = "boolean"
591 def convert(
592 self, value: t.Any, param: t.Optional["Parameter"], ctx: t.Optional["Context"]
593 ) -> t.Any:
594 if value in {False, True}:
595 return bool(value)
597 norm = value.strip().lower()
599 if norm in {"1", "true", "t", "yes", "y", "on"}:
600 return True
602 if norm in {"0", "false", "f", "no", "n", "off"}:
603 return False
605 self.fail(
606 _("{value!r} is not a valid boolean.").format(value=value), param, ctx
607 )
609 def __repr__(self) -> str:
610 return "BOOL"
613class UUIDParameterType(ParamType):
614 name = "uuid"
616 def convert(
617 self, value: t.Any, param: t.Optional["Parameter"], ctx: t.Optional["Context"]
618 ) -> t.Any:
619 import uuid
621 if isinstance(value, uuid.UUID):
622 return value
624 value = value.strip()
626 try:
627 return uuid.UUID(value)
628 except ValueError:
629 self.fail(
630 _("{value!r} is not a valid UUID.").format(value=value), param, ctx
631 )
633 def __repr__(self) -> str:
634 return "UUID"
637class File(ParamType):
638 """Declares a parameter to be a file for reading or writing. The file
639 is automatically closed once the context tears down (after the command
640 finished working).
642 Files can be opened for reading or writing. The special value ``-``
643 indicates stdin or stdout depending on the mode.
645 By default, the file is opened for reading text data, but it can also be
646 opened in binary mode or for writing. The encoding parameter can be used
647 to force a specific encoding.
649 The `lazy` flag controls if the file should be opened immediately or upon
650 first IO. The default is to be non-lazy for standard input and output
651 streams as well as files opened for reading, `lazy` otherwise. When opening a
652 file lazily for reading, it is still opened temporarily for validation, but
653 will not be held open until first IO. lazy is mainly useful when opening
654 for writing to avoid creating the file until it is needed.
656 Starting with Click 2.0, files can also be opened atomically in which
657 case all writes go into a separate file in the same folder and upon
658 completion the file will be moved over to the original location. This
659 is useful if a file regularly read by other users is modified.
661 See :ref:`file-args` for more information.
662 """
664 name = "filename"
665 envvar_list_splitter = os.path.pathsep
667 def __init__(
668 self,
669 mode: str = "r",
670 encoding: t.Optional[str] = None,
671 errors: t.Optional[str] = "strict",
672 lazy: t.Optional[bool] = None,
673 atomic: bool = False,
674 ) -> None:
675 self.mode = mode
676 self.encoding = encoding
677 self.errors = errors
678 self.lazy = lazy
679 self.atomic = atomic
681 def to_info_dict(self) -> t.Dict[str, t.Any]:
682 info_dict = super().to_info_dict()
683 info_dict.update(mode=self.mode, encoding=self.encoding)
684 return info_dict
686 def resolve_lazy_flag(self, value: t.Any) -> bool:
687 if self.lazy is not None:
688 return self.lazy
689 if value == "-":
690 return False
691 elif "w" in self.mode:
692 return True
693 return False
695 def convert(
696 self, value: t.Any, param: t.Optional["Parameter"], ctx: t.Optional["Context"]
697 ) -> t.Any:
698 try:
699 if hasattr(value, "read") or hasattr(value, "write"):
700 return value
702 lazy = self.resolve_lazy_flag(value)
704 if lazy:
705 lf = LazyFile(
706 value, self.mode, self.encoding, self.errors, atomic=self.atomic
707 )
709 if ctx is not None:
710 ctx.call_on_close(lf.close_intelligently)
712 return t.cast(t.IO[t.Any], lf)
714 f, should_close = open_stream(
715 value, self.mode, self.encoding, self.errors, atomic=self.atomic
716 )
718 # If a context is provided, we automatically close the file
719 # at the end of the context execution (or flush out). If a
720 # context does not exist, it's the caller's responsibility to
721 # properly close the file. This for instance happens when the
722 # type is used with prompts.
723 if ctx is not None:
724 if should_close:
725 ctx.call_on_close(safecall(f.close))
726 else:
727 ctx.call_on_close(safecall(f.flush))
729 return f
730 except OSError as e: # noqa: B014
731 self.fail(f"'{os.fsdecode(value)}': {e.strerror}", param, ctx)
733 def shell_complete(
734 self, ctx: "Context", param: "Parameter", incomplete: str
735 ) -> t.List["CompletionItem"]:
736 """Return a special completion marker that tells the completion
737 system to use the shell to provide file path completions.
739 :param ctx: Invocation context for this command.
740 :param param: The parameter that is requesting completion.
741 :param incomplete: Value being completed. May be empty.
743 .. versionadded:: 8.0
744 """
745 from click.shell_completion import CompletionItem
747 return [CompletionItem(incomplete, type="file")]
750class Path(ParamType):
751 """The ``Path`` type is similar to the :class:`File` type, but
752 returns the filename instead of an open file. Various checks can be
753 enabled to validate the type of file and permissions.
755 :param exists: The file or directory needs to exist for the value to
756 be valid. If this is not set to ``True``, and the file does not
757 exist, then all further checks are silently skipped.
758 :param file_okay: Allow a file as a value.
759 :param dir_okay: Allow a directory as a value.
760 :param readable: if true, a readable check is performed.
761 :param writable: if true, a writable check is performed.
762 :param executable: if true, an executable check is performed.
763 :param resolve_path: Make the value absolute and resolve any
764 symlinks. A ``~`` is not expanded, as this is supposed to be
765 done by the shell only.
766 :param allow_dash: Allow a single dash as a value, which indicates
767 a standard stream (but does not open it). Use
768 :func:`~click.open_file` to handle opening this value.
769 :param path_type: Convert the incoming path value to this type. If
770 ``None``, keep Python's default, which is ``str``. Useful to
771 convert to :class:`pathlib.Path`.
773 .. versionchanged:: 8.1
774 Added the ``executable`` parameter.
776 .. versionchanged:: 8.0
777 Allow passing ``path_type=pathlib.Path``.
779 .. versionchanged:: 6.0
780 Added the ``allow_dash`` parameter.
781 """
783 envvar_list_splitter = os.path.pathsep
785 def __init__(
786 self,
787 exists: bool = False,
788 file_okay: bool = True,
789 dir_okay: bool = True,
790 writable: bool = False,
791 readable: bool = True,
792 resolve_path: bool = False,
793 allow_dash: bool = False,
794 path_type: t.Optional[t.Type[t.Any]] = None,
795 executable: bool = False,
796 ):
797 self.exists = exists
798 self.file_okay = file_okay
799 self.dir_okay = dir_okay
800 self.readable = readable
801 self.writable = writable
802 self.executable = executable
803 self.resolve_path = resolve_path
804 self.allow_dash = allow_dash
805 self.type = path_type
807 if self.file_okay and not self.dir_okay: 807 ↛ 809line 807 didn't jump to line 809, because the condition on line 807 was never false
808 self.name = _("file")
809 elif self.dir_okay and not self.file_okay:
810 self.name = _("directory")
811 else:
812 self.name = _("path")
814 def to_info_dict(self) -> t.Dict[str, t.Any]:
815 info_dict = super().to_info_dict()
816 info_dict.update(
817 exists=self.exists,
818 file_okay=self.file_okay,
819 dir_okay=self.dir_okay,
820 writable=self.writable,
821 readable=self.readable,
822 allow_dash=self.allow_dash,
823 )
824 return info_dict
826 def coerce_path_result(self, rv: t.Any) -> t.Any:
827 if self.type is not None and not isinstance(rv, self.type):
828 if self.type is str:
829 rv = os.fsdecode(rv)
830 elif self.type is bytes:
831 rv = os.fsencode(rv)
832 else:
833 rv = self.type(rv)
835 return rv
837 def convert(
838 self, value: t.Any, param: t.Optional["Parameter"], ctx: t.Optional["Context"]
839 ) -> t.Any:
840 rv = value
842 is_dash = self.file_okay and self.allow_dash and rv in (b"-", "-")
844 if not is_dash:
845 if self.resolve_path:
846 # os.path.realpath doesn't resolve symlinks on Windows
847 # until Python 3.8. Use pathlib for now.
848 import pathlib
850 rv = os.fsdecode(pathlib.Path(rv).resolve())
852 try:
853 st = os.stat(rv)
854 except OSError:
855 if not self.exists:
856 return self.coerce_path_result(rv)
857 self.fail(
858 _("{name} {filename!r} does not exist.").format(
859 name=self.name.title(), filename=os.fsdecode(value)
860 ),
861 param,
862 ctx,
863 )
865 if not self.file_okay and stat.S_ISREG(st.st_mode):
866 self.fail(
867 _("{name} {filename!r} is a file.").format(
868 name=self.name.title(), filename=os.fsdecode(value)
869 ),
870 param,
871 ctx,
872 )
873 if not self.dir_okay and stat.S_ISDIR(st.st_mode):
874 self.fail(
875 _("{name} '{filename}' is a directory.").format(
876 name=self.name.title(), filename=os.fsdecode(value)
877 ),
878 param,
879 ctx,
880 )
882 if self.readable and not os.access(rv, os.R_OK):
883 self.fail(
884 _("{name} {filename!r} is not readable.").format(
885 name=self.name.title(), filename=os.fsdecode(value)
886 ),
887 param,
888 ctx,
889 )
891 if self.writable and not os.access(rv, os.W_OK):
892 self.fail(
893 _("{name} {filename!r} is not writable.").format(
894 name=self.name.title(), filename=os.fsdecode(value)
895 ),
896 param,
897 ctx,
898 )
900 if self.executable and not os.access(value, os.X_OK):
901 self.fail(
902 _("{name} {filename!r} is not executable.").format(
903 name=self.name.title(), filename=os.fsdecode(value)
904 ),
905 param,
906 ctx,
907 )
909 return self.coerce_path_result(rv)
911 def shell_complete(
912 self, ctx: "Context", param: "Parameter", incomplete: str
913 ) -> t.List["CompletionItem"]:
914 """Return a special completion marker that tells the completion
915 system to use the shell to provide path completions for only
916 directories or any paths.
918 :param ctx: Invocation context for this command.
919 :param param: The parameter that is requesting completion.
920 :param incomplete: Value being completed. May be empty.
922 .. versionadded:: 8.0
923 """
924 from click.shell_completion import CompletionItem
926 type = "dir" if self.dir_okay and not self.file_okay else "file"
927 return [CompletionItem(incomplete, type=type)]
930class Tuple(CompositeParamType):
931 """The default behavior of Click is to apply a type on a value directly.
932 This works well in most cases, except for when `nargs` is set to a fixed
933 count and different types should be used for different items. In this
934 case the :class:`Tuple` type can be used. This type can only be used
935 if `nargs` is set to a fixed number.
937 For more information see :ref:`tuple-type`.
939 This can be selected by using a Python tuple literal as a type.
941 :param types: a list of types that should be used for the tuple items.
942 """
944 def __init__(self, types: t.Sequence[t.Union[t.Type[t.Any], ParamType]]) -> None:
945 self.types = [convert_type(ty) for ty in types] 945 ↛ exit, 945 ↛ exit2 missed branches: 1) line 945 didn't run the list comprehension on line 945, 2) line 945 didn't return from function '__init__'
947 def to_info_dict(self) -> t.Dict[str, t.Any]:
948 info_dict = super().to_info_dict()
949 info_dict["types"] = [t.to_info_dict() for t in self.types]
950 return info_dict
952 @property
953 def name(self) -> str: # type: ignore
954 return f"<{' '.join(ty.name for ty in self.types)}>"
956 @property
957 def arity(self) -> int: # type: ignore
958 return len(self.types)
960 def convert(
961 self, value: t.Any, param: t.Optional["Parameter"], ctx: t.Optional["Context"]
962 ) -> t.Any:
963 len_type = len(self.types)
964 len_value = len(value)
966 if len_value != len_type:
967 self.fail(
968 ngettext(
969 "{len_type} values are required, but {len_value} was given.",
970 "{len_type} values are required, but {len_value} were given.",
971 len_value,
972 ).format(len_type=len_type, len_value=len_value),
973 param=param,
974 ctx=ctx,
975 )
977 return tuple(ty(x, param, ctx) for ty, x in zip(self.types, value))
980def convert_type(ty: t.Optional[t.Any], default: t.Optional[t.Any] = None) -> ParamType:
981 """Find the most appropriate :class:`ParamType` for the given Python
982 type. If the type isn't provided, it can be inferred from a default
983 value.
984 """
985 guessed_type = False
987 if ty is None and default is not None:
988 if isinstance(default, (tuple, list)): 988 ↛ 991, 988 ↛ 10022 missed branches: 1) line 988 didn't jump to line 991, because the condition on line 988 was never true, 2) line 988 didn't jump to line 1002, because the condition on line 988 was never false
989 # If the default is empty, ty will remain None and will
990 # return STRING.
991 if default: 991 ↛ 992, 991 ↛ 10042 missed branches: 1) line 991 didn't jump to line 992, because the condition on line 991 was never true, 2) line 991 didn't jump to line 1004, because the condition on line 991 was never false
992 item = default[0]
994 # A tuple of tuples needs to detect the inner types.
995 # Can't call convert recursively because that would
996 # incorrectly unwind the tuple to a single type.
997 if isinstance(item, (tuple, list)):
998 ty = tuple(map(type, item))
999 else:
1000 ty = type(item)
1001 else:
1002 ty = type(default)
1004 guessed_type = True
1006 if isinstance(ty, tuple):
1007 return Tuple(ty)
1009 if isinstance(ty, ParamType): 1009 ↛ 1010line 1009 didn't jump to line 1010, because the condition on line 1009 was never true
1010 return ty
1012 if ty is str or ty is None:
1013 return STRING
1015 if ty is int:
1016 return INT
1018 if ty is float:
1019 return FLOAT
1021 if ty is bool: 1021 ↛ 1022line 1021 didn't jump to line 1022, because the condition on line 1021 was never true
1022 return BOOL
1024 if guessed_type: 1024 ↛ 1028line 1024 didn't jump to line 1028, because the condition on line 1024 was never false
1025 return STRING
1027 if __debug__:
1028 try:
1029 if issubclass(ty, ParamType):
1030 raise AssertionError(
1031 f"Attempted to use an uninstantiated parameter type ({ty})."
1032 )
1033 except TypeError:
1034 # ty is an instance (correct), so issubclass fails.
1035 pass
1037 return FuncParamType(ty)
1040#: A dummy parameter type that just does nothing. From a user's
1041#: perspective this appears to just be the same as `STRING` but
1042#: internally no string conversion takes place if the input was bytes.
1043#: This is usually useful when working with file paths as they can
1044#: appear in bytes and unicode.
1045#:
1046#: For path related uses the :class:`Path` type is a better choice but
1047#: there are situations where an unprocessed type is useful which is why
1048#: it is is provided.
1049#:
1050#: .. versionadded:: 4.0
1051UNPROCESSED = UnprocessedParamType()
1053#: A unicode string parameter type which is the implicit default. This
1054#: can also be selected by using ``str`` as type.
1055STRING = StringParamType()
1057#: An integer parameter. This can also be selected by using ``int`` as
1058#: type.
1059INT = IntParamType()
1061#: A floating point value parameter. This can also be selected by using
1062#: ``float`` as type.
1063FLOAT = FloatParamType()
1065#: A boolean parameter. This is the default for boolean flags. This can
1066#: also be selected by using ``bool`` as a type.
1067BOOL = BoolParamType()
1069#: A UUID parameter.
1070UUID = UUIDParameterType()