Coverage for /var/srv/projects/api.amasfac.comuna18.com/tmp/venv/lib/python3.9/site-packages/click/types.py: 27%
408 statements
« prev ^ index » next coverage.py v6.4.4, created at 2023-07-17 14:22 -0600
« prev ^ index » next coverage.py v6.4.4, created at 2023-07-17 14:22 -0600
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]
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:
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 f: t.IO = t.cast(
706 t.IO,
707 LazyFile(
708 value, self.mode, self.encoding, self.errors, atomic=self.atomic
709 ),
710 )
712 if ctx is not None:
713 ctx.call_on_close(f.close_intelligently) # type: ignore
715 return f
717 f, should_close = open_stream(
718 value, self.mode, self.encoding, self.errors, atomic=self.atomic
719 )
721 # If a context is provided, we automatically close the file
722 # at the end of the context execution (or flush out). If a
723 # context does not exist, it's the caller's responsibility to
724 # properly close the file. This for instance happens when the
725 # type is used with prompts.
726 if ctx is not None:
727 if should_close:
728 ctx.call_on_close(safecall(f.close))
729 else:
730 ctx.call_on_close(safecall(f.flush))
732 return f
733 except OSError as e: # noqa: B014
734 self.fail(f"'{os.fsdecode(value)}': {e.strerror}", param, ctx)
736 def shell_complete(
737 self, ctx: "Context", param: "Parameter", incomplete: str
738 ) -> t.List["CompletionItem"]:
739 """Return a special completion marker that tells the completion
740 system to use the shell to provide file path completions.
742 :param ctx: Invocation context for this command.
743 :param param: The parameter that is requesting completion.
744 :param incomplete: Value being completed. May be empty.
746 .. versionadded:: 8.0
747 """
748 from click.shell_completion import CompletionItem
750 return [CompletionItem(incomplete, type="file")]
753class Path(ParamType):
754 """The ``Path`` type is similar to the :class:`File` type, but
755 returns the filename instead of an open file. Various checks can be
756 enabled to validate the type of file and permissions.
758 :param exists: The file or directory needs to exist for the value to
759 be valid. If this is not set to ``True``, and the file does not
760 exist, then all further checks are silently skipped.
761 :param file_okay: Allow a file as a value.
762 :param dir_okay: Allow a directory as a value.
763 :param readable: if true, a readable check is performed.
764 :param writable: if true, a writable check is performed.
765 :param executable: if true, an executable check is performed.
766 :param resolve_path: Make the value absolute and resolve any
767 symlinks. A ``~`` is not expanded, as this is supposed to be
768 done by the shell only.
769 :param allow_dash: Allow a single dash as a value, which indicates
770 a standard stream (but does not open it). Use
771 :func:`~click.open_file` to handle opening this value.
772 :param path_type: Convert the incoming path value to this type. If
773 ``None``, keep Python's default, which is ``str``. Useful to
774 convert to :class:`pathlib.Path`.
776 .. versionchanged:: 8.1
777 Added the ``executable`` parameter.
779 .. versionchanged:: 8.0
780 Allow passing ``type=pathlib.Path``.
782 .. versionchanged:: 6.0
783 Added the ``allow_dash`` parameter.
784 """
786 envvar_list_splitter = os.path.pathsep
788 def __init__(
789 self,
790 exists: bool = False,
791 file_okay: bool = True,
792 dir_okay: bool = True,
793 writable: bool = False,
794 readable: bool = True,
795 resolve_path: bool = False,
796 allow_dash: bool = False,
797 path_type: t.Optional[t.Type] = None,
798 executable: bool = False,
799 ):
800 self.exists = exists
801 self.file_okay = file_okay
802 self.dir_okay = dir_okay
803 self.readable = readable
804 self.writable = writable
805 self.executable = executable
806 self.resolve_path = resolve_path
807 self.allow_dash = allow_dash
808 self.type = path_type
810 if self.file_okay and not self.dir_okay:
811 self.name = _("file")
812 elif self.dir_okay and not self.file_okay:
813 self.name = _("directory")
814 else:
815 self.name = _("path")
817 def to_info_dict(self) -> t.Dict[str, t.Any]:
818 info_dict = super().to_info_dict()
819 info_dict.update(
820 exists=self.exists,
821 file_okay=self.file_okay,
822 dir_okay=self.dir_okay,
823 writable=self.writable,
824 readable=self.readable,
825 allow_dash=self.allow_dash,
826 )
827 return info_dict
829 def coerce_path_result(self, rv: t.Any) -> t.Any:
830 if self.type is not None and not isinstance(rv, self.type):
831 if self.type is str:
832 rv = os.fsdecode(rv)
833 elif self.type is bytes:
834 rv = os.fsencode(rv)
835 else:
836 rv = self.type(rv)
838 return rv
840 def convert(
841 self, value: t.Any, param: t.Optional["Parameter"], ctx: t.Optional["Context"]
842 ) -> t.Any:
843 rv = value
845 is_dash = self.file_okay and self.allow_dash and rv in (b"-", "-")
847 if not is_dash:
848 if self.resolve_path:
849 # os.path.realpath doesn't resolve symlinks on Windows
850 # until Python 3.8. Use pathlib for now.
851 import pathlib
853 rv = os.fsdecode(pathlib.Path(rv).resolve())
855 try:
856 st = os.stat(rv)
857 except OSError:
858 if not self.exists:
859 return self.coerce_path_result(rv)
860 self.fail(
861 _("{name} {filename!r} does not exist.").format(
862 name=self.name.title(), filename=os.fsdecode(value)
863 ),
864 param,
865 ctx,
866 )
868 if not self.file_okay and stat.S_ISREG(st.st_mode):
869 self.fail(
870 _("{name} {filename!r} is a file.").format(
871 name=self.name.title(), filename=os.fsdecode(value)
872 ),
873 param,
874 ctx,
875 )
876 if not self.dir_okay and stat.S_ISDIR(st.st_mode):
877 self.fail(
878 _("{name} '{filename}' is a directory.").format(
879 name=self.name.title(), filename=os.fsdecode(value)
880 ),
881 param,
882 ctx,
883 )
885 if self.readable and not os.access(rv, os.R_OK):
886 self.fail(
887 _("{name} {filename!r} is not readable.").format(
888 name=self.name.title(), filename=os.fsdecode(value)
889 ),
890 param,
891 ctx,
892 )
894 if self.writable and not os.access(rv, os.W_OK):
895 self.fail(
896 _("{name} {filename!r} is not writable.").format(
897 name=self.name.title(), filename=os.fsdecode(value)
898 ),
899 param,
900 ctx,
901 )
903 if self.executable and not os.access(value, os.X_OK):
904 self.fail(
905 _("{name} {filename!r} is not executable.").format(
906 name=self.name.title(), filename=os.fsdecode(value)
907 ),
908 param,
909 ctx,
910 )
912 return self.coerce_path_result(rv)
914 def shell_complete(
915 self, ctx: "Context", param: "Parameter", incomplete: str
916 ) -> t.List["CompletionItem"]:
917 """Return a special completion marker that tells the completion
918 system to use the shell to provide path completions for only
919 directories or any paths.
921 :param ctx: Invocation context for this command.
922 :param param: The parameter that is requesting completion.
923 :param incomplete: Value being completed. May be empty.
925 .. versionadded:: 8.0
926 """
927 from click.shell_completion import CompletionItem
929 type = "dir" if self.dir_okay and not self.file_okay else "file"
930 return [CompletionItem(incomplete, type=type)]
933class Tuple(CompositeParamType):
934 """The default behavior of Click is to apply a type on a value directly.
935 This works well in most cases, except for when `nargs` is set to a fixed
936 count and different types should be used for different items. In this
937 case the :class:`Tuple` type can be used. This type can only be used
938 if `nargs` is set to a fixed number.
940 For more information see :ref:`tuple-type`.
942 This can be selected by using a Python tuple literal as a type.
944 :param types: a list of types that should be used for the tuple items.
945 """
947 def __init__(self, types: t.Sequence[t.Union[t.Type, ParamType]]) -> None:
948 self.types = [convert_type(ty) for ty in types]
950 def to_info_dict(self) -> t.Dict[str, t.Any]:
951 info_dict = super().to_info_dict()
952 info_dict["types"] = [t.to_info_dict() for t in self.types]
953 return info_dict
955 @property
956 def name(self) -> str: # type: ignore
957 return f"<{' '.join(ty.name for ty in self.types)}>"
959 @property
960 def arity(self) -> int: # type: ignore
961 return len(self.types)
963 def convert(
964 self, value: t.Any, param: t.Optional["Parameter"], ctx: t.Optional["Context"]
965 ) -> t.Any:
966 len_type = len(self.types)
967 len_value = len(value)
969 if len_value != len_type:
970 self.fail(
971 ngettext(
972 "{len_type} values are required, but {len_value} was given.",
973 "{len_type} values are required, but {len_value} were given.",
974 len_value,
975 ).format(len_type=len_type, len_value=len_value),
976 param=param,
977 ctx=ctx,
978 )
980 return tuple(ty(x, param, ctx) for ty, x in zip(self.types, value))
983def convert_type(ty: t.Optional[t.Any], default: t.Optional[t.Any] = None) -> ParamType:
984 """Find the most appropriate :class:`ParamType` for the given Python
985 type. If the type isn't provided, it can be inferred from a default
986 value.
987 """
988 guessed_type = False
990 if ty is None and default is not None:
991 if isinstance(default, (tuple, list)):
992 # If the default is empty, ty will remain None and will
993 # return STRING.
994 if default:
995 item = default[0]
997 # A tuple of tuples needs to detect the inner types.
998 # Can't call convert recursively because that would
999 # incorrectly unwind the tuple to a single type.
1000 if isinstance(item, (tuple, list)):
1001 ty = tuple(map(type, item))
1002 else:
1003 ty = type(item)
1004 else:
1005 ty = type(default)
1007 guessed_type = True
1009 if isinstance(ty, tuple):
1010 return Tuple(ty)
1012 if isinstance(ty, ParamType):
1013 return ty
1015 if ty is str or ty is None:
1016 return STRING
1018 if ty is int:
1019 return INT
1021 if ty is float:
1022 return FLOAT
1024 if ty is bool:
1025 return BOOL
1027 if guessed_type:
1028 return STRING
1030 if __debug__:
1031 try:
1032 if issubclass(ty, ParamType):
1033 raise AssertionError(
1034 f"Attempted to use an uninstantiated parameter type ({ty})."
1035 )
1036 except TypeError:
1037 # ty is an instance (correct), so issubclass fails.
1038 pass
1040 return FuncParamType(ty)
1043#: A dummy parameter type that just does nothing. From a user's
1044#: perspective this appears to just be the same as `STRING` but
1045#: internally no string conversion takes place if the input was bytes.
1046#: This is usually useful when working with file paths as they can
1047#: appear in bytes and unicode.
1048#:
1049#: For path related uses the :class:`Path` type is a better choice but
1050#: there are situations where an unprocessed type is useful which is why
1051#: it is is provided.
1052#:
1053#: .. versionadded:: 4.0
1054UNPROCESSED = UnprocessedParamType()
1056#: A unicode string parameter type which is the implicit default. This
1057#: can also be selected by using ``str`` as type.
1058STRING = StringParamType()
1060#: An integer parameter. This can also be selected by using ``int`` as
1061#: type.
1062INT = IntParamType()
1064#: A floating point value parameter. This can also be selected by using
1065#: ``float`` as type.
1066FLOAT = FloatParamType()
1068#: A boolean parameter. This is the default for boolean flags. This can
1069#: also be selected by using ``bool`` as a type.
1070BOOL = BoolParamType()
1072#: A UUID parameter.
1073UUID = UUIDParameterType()