Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/click/types.py: 39%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1from __future__ import annotations
3import collections.abc as cabc
4import enum
5import os
6import stat
7import sys
8import typing as t
9from datetime import datetime
10from gettext import gettext as _
11from gettext import ngettext
13from ._compat import _get_argv_encoding
14from ._compat import open_stream
15from .exceptions import BadParameter
16from .utils import format_filename
17from .utils import LazyFile
18from .utils import safecall
20if t.TYPE_CHECKING:
21 import typing_extensions as te
23 from .core import Context
24 from .core import Parameter
25 from .shell_completion import CompletionItem
27ParamTypeValue = t.TypeVar("ParamTypeValue")
30class ParamType:
31 """Represents the type of a parameter. Validates and converts values
32 from the command line or Python into the correct type.
34 To implement a custom type, subclass and implement at least the
35 following:
37 - The :attr:`name` class attribute must be set.
38 - Calling an instance of the type with ``None`` must return
39 ``None``. This is already implemented by default.
40 - :meth:`convert` must convert string values to the correct type.
41 - :meth:`convert` must accept values that are already the correct
42 type.
43 - It must be able to convert a value if the ``ctx`` and ``param``
44 arguments are ``None``. This can occur when converting prompt
45 input.
46 """
48 is_composite: t.ClassVar[bool] = False
49 arity: t.ClassVar[int] = 1
51 #: the descriptive name of this type
52 name: str
54 #: if a list of this type is expected and the value is pulled from a
55 #: string environment variable, this is what splits it up. `None`
56 #: means any whitespace. For all parameters the general rule is that
57 #: whitespace splits them up. The exception are paths and files which
58 #: are split by ``os.path.pathsep`` by default (":" on Unix and ";" on
59 #: Windows).
60 envvar_list_splitter: t.ClassVar[str | None] = None
62 def to_info_dict(self) -> dict[str, t.Any]:
63 """Gather information that could be useful for a tool generating
64 user-facing documentation.
66 Use :meth:`click.Context.to_info_dict` to traverse the entire
67 CLI structure.
69 .. versionadded:: 8.0
70 """
71 # The class name without the "ParamType" suffix.
72 param_type = type(self).__name__.partition("ParamType")[0]
73 param_type = param_type.partition("ParameterType")[0]
75 # Custom subclasses might not remember to set a name.
76 if hasattr(self, "name"):
77 name = self.name
78 else:
79 name = param_type
81 return {"param_type": param_type, "name": name}
83 def __call__(
84 self,
85 value: t.Any,
86 param: Parameter | None = None,
87 ctx: Context | None = None,
88 ) -> t.Any:
89 if value is not None:
90 return self.convert(value, param, ctx)
92 def get_metavar(self, param: Parameter, ctx: Context) -> str | None:
93 """Returns the metavar default for this param if it provides one."""
95 def get_missing_message(self, param: Parameter, ctx: Context | None) -> str | None:
96 """Optionally might return extra information about a missing
97 parameter.
99 .. versionadded:: 2.0
100 """
102 def convert(
103 self, value: t.Any, param: Parameter | None, ctx: Context | None
104 ) -> t.Any:
105 """Convert the value to the correct type. This is not called if
106 the value is ``None`` (the missing value).
108 This must accept string values from the command line, as well as
109 values that are already the correct type. It may also convert
110 other compatible types.
112 The ``param`` and ``ctx`` arguments may be ``None`` in certain
113 situations, such as when converting prompt input.
115 If the value cannot be converted, call :meth:`fail` with a
116 descriptive message.
118 :param value: The value to convert.
119 :param param: The parameter that is using this type to convert
120 its value. May be ``None``.
121 :param ctx: The current context that arrived at this value. May
122 be ``None``.
123 """
124 return value
126 def split_envvar_value(self, rv: str) -> cabc.Sequence[str]:
127 """Given a value from an environment variable this splits it up
128 into small chunks depending on the defined envvar list splitter.
130 If the splitter is set to `None`, which means that whitespace splits,
131 then leading and trailing whitespace is ignored. Otherwise, leading
132 and trailing splitters usually lead to empty items being included.
133 """
134 return (rv or "").split(self.envvar_list_splitter)
136 def fail(
137 self,
138 message: str,
139 param: Parameter | None = None,
140 ctx: Context | None = None,
141 ) -> t.NoReturn:
142 """Helper method to fail with an invalid value message."""
143 raise BadParameter(message, ctx=ctx, param=param)
145 def shell_complete(
146 self, ctx: Context, param: Parameter, incomplete: str
147 ) -> list[CompletionItem]:
148 """Return a list of
149 :class:`~click.shell_completion.CompletionItem` objects for the
150 incomplete value. Most types do not provide completions, but
151 some do, and this allows custom types to provide custom
152 completions as well.
154 :param ctx: Invocation context for this command.
155 :param param: The parameter that is requesting completion.
156 :param incomplete: Value being completed. May be empty.
158 .. versionadded:: 8.0
159 """
160 return []
163class CompositeParamType(ParamType):
164 is_composite = True
166 @property
167 def arity(self) -> int: # type: ignore
168 raise NotImplementedError()
171class FuncParamType(ParamType):
172 def __init__(self, func: t.Callable[[t.Any], t.Any]) -> None:
173 self.name: str = func.__name__
174 self.func = func
176 def to_info_dict(self) -> dict[str, t.Any]:
177 info_dict = super().to_info_dict()
178 info_dict["func"] = self.func
179 return info_dict
181 def convert(
182 self, value: t.Any, param: Parameter | None, ctx: Context | None
183 ) -> t.Any:
184 try:
185 return self.func(value)
186 except ValueError:
187 try:
188 value = str(value)
189 except UnicodeError:
190 value = value.decode("utf-8", "replace")
192 self.fail(value, param, ctx)
195class UnprocessedParamType(ParamType):
196 name = "text"
198 def convert(
199 self, value: t.Any, param: Parameter | None, ctx: Context | None
200 ) -> t.Any:
201 return value
203 def __repr__(self) -> str:
204 return "UNPROCESSED"
207class StringParamType(ParamType):
208 name = "text"
210 def convert(
211 self, value: t.Any, param: Parameter | None, ctx: Context | None
212 ) -> t.Any:
213 if isinstance(value, bytes):
214 enc = _get_argv_encoding()
215 try:
216 value = value.decode(enc)
217 except UnicodeError:
218 fs_enc = sys.getfilesystemencoding()
219 if fs_enc != enc:
220 try:
221 value = value.decode(fs_enc)
222 except UnicodeError:
223 value = value.decode("utf-8", "replace")
224 else:
225 value = value.decode("utf-8", "replace")
226 return value
227 return str(value)
229 def __repr__(self) -> str:
230 return "STRING"
233class Choice(ParamType, t.Generic[ParamTypeValue]):
234 """The choice type allows a value to be checked against a fixed set
235 of supported values.
237 You may pass any iterable value which will be converted to a tuple
238 and thus will only be iterated once.
240 The resulting value will always be one of the originally passed choices.
241 See :meth:`normalize_choice` for more info on the mapping of strings
242 to choices. See :ref:`choice-opts` for an example.
244 :param case_sensitive: Set to false to make choices case
245 insensitive. Defaults to true.
247 .. versionchanged:: 8.2.0
248 Non-``str`` ``choices`` are now supported. It can additionally be any
249 iterable. Before you were not recommended to pass anything but a list or
250 tuple.
252 .. versionadded:: 8.2.0
253 Choice normalization can be overridden via :meth:`normalize_choice`.
254 """
256 name = "choice"
258 def __init__(
259 self, choices: cabc.Iterable[ParamTypeValue], case_sensitive: bool = True
260 ) -> None:
261 self.choices: cabc.Sequence[ParamTypeValue] = tuple(choices)
262 self.case_sensitive = case_sensitive
264 def to_info_dict(self) -> dict[str, t.Any]:
265 info_dict = super().to_info_dict()
266 info_dict["choices"] = self.choices
267 info_dict["case_sensitive"] = self.case_sensitive
268 return info_dict
270 def _normalized_mapping(
271 self, ctx: Context | None = None
272 ) -> cabc.Mapping[ParamTypeValue, str]:
273 """
274 Returns mapping where keys are the original choices and the values are
275 the normalized values that are accepted via the command line.
277 This is a simple wrapper around :meth:`normalize_choice`, use that
278 instead which is supported.
279 """
280 return {
281 choice: self.normalize_choice(
282 choice=choice,
283 ctx=ctx,
284 )
285 for choice in self.choices
286 }
288 def normalize_choice(self, choice: ParamTypeValue, ctx: Context | None) -> str:
289 """
290 Normalize a choice value, used to map a passed string to a choice.
291 Each choice must have a unique normalized value.
293 By default uses :meth:`Context.token_normalize_func` and if not case
294 sensitive, convert it to a casefolded value.
296 .. versionadded:: 8.2.0
297 """
298 normed_value = choice.name if isinstance(choice, enum.Enum) else str(choice)
300 if ctx is not None and ctx.token_normalize_func is not None:
301 normed_value = ctx.token_normalize_func(normed_value)
303 if not self.case_sensitive:
304 normed_value = normed_value.casefold()
306 return normed_value
308 def get_metavar(self, param: Parameter, ctx: Context) -> str | None:
309 if param.param_type_name == "option" and not param.show_choices: # type: ignore
310 choice_metavars = [
311 convert_type(type(choice)).name.upper() for choice in self.choices
312 ]
313 choices_str = "|".join([*dict.fromkeys(choice_metavars)])
314 else:
315 choices_str = "|".join(
316 [str(i) for i in self._normalized_mapping(ctx=ctx).values()]
317 )
319 # Use curly braces to indicate a required argument.
320 if param.required and param.param_type_name == "argument":
321 return f"{{{choices_str}}}"
323 # Use square braces to indicate an option or optional argument.
324 return f"[{choices_str}]"
326 def get_missing_message(self, param: Parameter, ctx: Context | None) -> str:
327 """
328 Message shown when no choice is passed.
330 .. versionchanged:: 8.2.0 Added ``ctx`` argument.
331 """
332 return _("Choose from:\n\t{choices}").format(
333 choices=",\n\t".join(self._normalized_mapping(ctx=ctx).values())
334 )
336 def convert(
337 self, value: t.Any, param: Parameter | None, ctx: Context | None
338 ) -> ParamTypeValue:
339 """
340 For a given value from the parser, normalize it and find its
341 matching normalized value in the list of choices. Then return the
342 matched "original" choice.
343 """
344 normed_value = self.normalize_choice(choice=value, ctx=ctx)
345 normalized_mapping = self._normalized_mapping(ctx=ctx)
347 try:
348 return next(
349 original
350 for original, normalized in normalized_mapping.items()
351 if normalized == normed_value
352 )
353 except StopIteration:
354 self.fail(
355 self.get_invalid_choice_message(value=value, ctx=ctx),
356 param=param,
357 ctx=ctx,
358 )
360 def get_invalid_choice_message(self, value: t.Any, ctx: Context | None) -> str:
361 """Get the error message when the given choice is invalid.
363 :param value: The invalid value.
365 .. versionadded:: 8.2
366 """
367 choices_str = ", ".join(map(repr, self._normalized_mapping(ctx=ctx).values()))
368 return ngettext(
369 "{value!r} is not {choice}.",
370 "{value!r} is not one of {choices}.",
371 len(self.choices),
372 ).format(value=value, choice=choices_str, choices=choices_str)
374 def __repr__(self) -> str:
375 return f"Choice({list(self.choices)})"
377 def shell_complete(
378 self, ctx: Context, param: Parameter, incomplete: str
379 ) -> list[CompletionItem]:
380 """Complete choices that start with the incomplete value.
382 :param ctx: Invocation context for this command.
383 :param param: The parameter that is requesting completion.
384 :param incomplete: Value being completed. May be empty.
386 .. versionadded:: 8.0
387 """
388 from click.shell_completion import CompletionItem
390 str_choices = map(str, self.choices)
392 if self.case_sensitive:
393 matched = (c for c in str_choices if c.startswith(incomplete))
394 else:
395 incomplete = incomplete.lower()
396 matched = (c for c in str_choices if c.lower().startswith(incomplete))
398 return [CompletionItem(c) for c in matched]
401class DateTime(ParamType):
402 """The DateTime type converts date strings into `datetime` objects.
404 The format strings which are checked are configurable, but default to some
405 common (non-timezone aware) ISO 8601 formats.
407 When specifying *DateTime* formats, you should only pass a list or a tuple.
408 Other iterables, like generators, may lead to surprising results.
410 The format strings are processed using ``datetime.strptime``, and this
411 consequently defines the format strings which are allowed.
413 Parsing is tried using each format, in order, and the first format which
414 parses successfully is used.
416 :param formats: A list or tuple of date format strings, in the order in
417 which they should be tried. Defaults to
418 ``'%Y-%m-%d'``, ``'%Y-%m-%dT%H:%M:%S'``,
419 ``'%Y-%m-%d %H:%M:%S'``.
420 """
422 name = "datetime"
424 def __init__(self, formats: cabc.Sequence[str] | None = None):
425 self.formats: cabc.Sequence[str] = formats or [
426 "%Y-%m-%d",
427 "%Y-%m-%dT%H:%M:%S",
428 "%Y-%m-%d %H:%M:%S",
429 ]
431 def to_info_dict(self) -> dict[str, t.Any]:
432 info_dict = super().to_info_dict()
433 info_dict["formats"] = self.formats
434 return info_dict
436 def get_metavar(self, param: Parameter, ctx: Context) -> str | None:
437 return f"[{'|'.join(self.formats)}]"
439 def _try_to_convert_date(self, value: t.Any, format: str) -> datetime | None:
440 try:
441 return datetime.strptime(value, format)
442 except ValueError:
443 return None
445 def convert(
446 self, value: t.Any, param: Parameter | None, ctx: Context | None
447 ) -> t.Any:
448 if isinstance(value, datetime):
449 return value
451 for format in self.formats:
452 converted = self._try_to_convert_date(value, format)
454 if converted is not None:
455 return converted
457 formats_str = ", ".join(map(repr, self.formats))
458 self.fail(
459 ngettext(
460 "{value!r} does not match the format {format}.",
461 "{value!r} does not match the formats {formats}.",
462 len(self.formats),
463 ).format(value=value, format=formats_str, formats=formats_str),
464 param,
465 ctx,
466 )
468 def __repr__(self) -> str:
469 return "DateTime"
472class _NumberParamTypeBase(ParamType):
473 _number_class: t.ClassVar[type[t.Any]]
475 def convert(
476 self, value: t.Any, param: Parameter | None, ctx: Context | None
477 ) -> t.Any:
478 try:
479 return self._number_class(value)
480 except ValueError:
481 self.fail(
482 _("{value!r} is not a valid {number_type}.").format(
483 value=value, number_type=self.name
484 ),
485 param,
486 ctx,
487 )
490class _NumberRangeBase(_NumberParamTypeBase):
491 def __init__(
492 self,
493 min: float | None = None,
494 max: float | None = None,
495 min_open: bool = False,
496 max_open: bool = False,
497 clamp: bool = False,
498 ) -> None:
499 self.min = min
500 self.max = max
501 self.min_open = min_open
502 self.max_open = max_open
503 self.clamp = clamp
505 def to_info_dict(self) -> dict[str, t.Any]:
506 info_dict = super().to_info_dict()
507 info_dict.update(
508 min=self.min,
509 max=self.max,
510 min_open=self.min_open,
511 max_open=self.max_open,
512 clamp=self.clamp,
513 )
514 return info_dict
516 def convert(
517 self, value: t.Any, param: Parameter | None, ctx: Context | None
518 ) -> t.Any:
519 import operator
521 rv = super().convert(value, param, ctx)
522 lt_min: bool = self.min is not None and (
523 operator.le if self.min_open else operator.lt
524 )(rv, self.min)
525 gt_max: bool = self.max is not None and (
526 operator.ge if self.max_open else operator.gt
527 )(rv, self.max)
529 if self.clamp:
530 if lt_min:
531 return self._clamp(self.min, 1, self.min_open) # type: ignore
533 if gt_max:
534 return self._clamp(self.max, -1, self.max_open) # type: ignore
536 if lt_min or gt_max:
537 self.fail(
538 _("{value} is not in the range {range}.").format(
539 value=rv, range=self._describe_range()
540 ),
541 param,
542 ctx,
543 )
545 return rv
547 def _clamp(self, bound: float, dir: t.Literal[1, -1], open: bool) -> float:
548 """Find the valid value to clamp to bound in the given
549 direction.
551 :param bound: The boundary value.
552 :param dir: 1 or -1 indicating the direction to move.
553 :param open: If true, the range does not include the bound.
554 """
555 raise NotImplementedError
557 def _describe_range(self) -> str:
558 """Describe the range for use in help text."""
559 if self.min is None:
560 op = "<" if self.max_open else "<="
561 return f"x{op}{self.max}"
563 if self.max is None:
564 op = ">" if self.min_open else ">="
565 return f"x{op}{self.min}"
567 lop = "<" if self.min_open else "<="
568 rop = "<" if self.max_open else "<="
569 return f"{self.min}{lop}x{rop}{self.max}"
571 def __repr__(self) -> str:
572 clamp = " clamped" if self.clamp else ""
573 return f"<{type(self).__name__} {self._describe_range()}{clamp}>"
576class IntParamType(_NumberParamTypeBase):
577 name = "integer"
578 _number_class = int
580 def __repr__(self) -> str:
581 return "INT"
584class IntRange(_NumberRangeBase, IntParamType):
585 """Restrict an :data:`click.INT` value to a range of accepted
586 values. See :ref:`ranges`.
588 If ``min`` or ``max`` are not passed, any value is accepted in that
589 direction. If ``min_open`` or ``max_open`` are enabled, the
590 corresponding boundary is not included in the range.
592 If ``clamp`` is enabled, a value outside the range is clamped to the
593 boundary instead of failing.
595 .. versionchanged:: 8.0
596 Added the ``min_open`` and ``max_open`` parameters.
597 """
599 name = "integer range"
601 def _clamp( # type: ignore
602 self, bound: int, dir: t.Literal[1, -1], open: bool
603 ) -> int:
604 if not open:
605 return bound
607 return bound + dir
610class FloatParamType(_NumberParamTypeBase):
611 name = "float"
612 _number_class = float
614 def __repr__(self) -> str:
615 return "FLOAT"
618class FloatRange(_NumberRangeBase, FloatParamType):
619 """Restrict a :data:`click.FLOAT` value to a range of accepted
620 values. See :ref:`ranges`.
622 If ``min`` or ``max`` are not passed, any value is accepted in that
623 direction. If ``min_open`` or ``max_open`` are enabled, the
624 corresponding boundary is not included in the range.
626 If ``clamp`` is enabled, a value outside the range is clamped to the
627 boundary instead of failing. This is not supported if either
628 boundary is marked ``open``.
630 .. versionchanged:: 8.0
631 Added the ``min_open`` and ``max_open`` parameters.
632 """
634 name = "float range"
636 def __init__(
637 self,
638 min: float | None = None,
639 max: float | None = None,
640 min_open: bool = False,
641 max_open: bool = False,
642 clamp: bool = False,
643 ) -> None:
644 super().__init__(
645 min=min, max=max, min_open=min_open, max_open=max_open, clamp=clamp
646 )
648 if (min_open or max_open) and clamp:
649 raise TypeError("Clamping is not supported for open bounds.")
651 def _clamp(self, bound: float, dir: t.Literal[1, -1], open: bool) -> float:
652 if not open:
653 return bound
655 # Could use math.nextafter here, but clamping an
656 # open float range doesn't seem to be particularly useful. It's
657 # left up to the user to write a callback to do it if needed.
658 raise RuntimeError("Clamping is not supported for open bounds.")
661class BoolParamType(ParamType):
662 name = "boolean"
664 def convert(
665 self, value: t.Any, param: Parameter | None, ctx: Context | None
666 ) -> t.Any:
667 if value in {False, True}:
668 return bool(value)
670 norm = value.strip().lower()
672 if norm in {"1", "true", "t", "yes", "y", "on"}:
673 return True
675 if norm in {"0", "false", "f", "no", "n", "off"}:
676 return False
678 self.fail(
679 _("{value!r} is not a valid boolean.").format(value=value), param, ctx
680 )
682 def __repr__(self) -> str:
683 return "BOOL"
686class UUIDParameterType(ParamType):
687 name = "uuid"
689 def convert(
690 self, value: t.Any, param: Parameter | None, ctx: Context | None
691 ) -> t.Any:
692 import uuid
694 if isinstance(value, uuid.UUID):
695 return value
697 value = value.strip()
699 try:
700 return uuid.UUID(value)
701 except ValueError:
702 self.fail(
703 _("{value!r} is not a valid UUID.").format(value=value), param, ctx
704 )
706 def __repr__(self) -> str:
707 return "UUID"
710class File(ParamType):
711 """Declares a parameter to be a file for reading or writing. The file
712 is automatically closed once the context tears down (after the command
713 finished working).
715 Files can be opened for reading or writing. The special value ``-``
716 indicates stdin or stdout depending on the mode.
718 By default, the file is opened for reading text data, but it can also be
719 opened in binary mode or for writing. The encoding parameter can be used
720 to force a specific encoding.
722 The `lazy` flag controls if the file should be opened immediately or upon
723 first IO. The default is to be non-lazy for standard input and output
724 streams as well as files opened for reading, `lazy` otherwise. When opening a
725 file lazily for reading, it is still opened temporarily for validation, but
726 will not be held open until first IO. lazy is mainly useful when opening
727 for writing to avoid creating the file until it is needed.
729 Files can also be opened atomically in which case all writes go into a
730 separate file in the same folder and upon completion the file will
731 be moved over to the original location. This is useful if a file
732 regularly read by other users is modified.
734 See :ref:`file-args` for more information.
736 .. versionchanged:: 2.0
737 Added the ``atomic`` parameter.
738 """
740 name = "filename"
741 envvar_list_splitter: t.ClassVar[str] = os.path.pathsep
743 def __init__(
744 self,
745 mode: str = "r",
746 encoding: str | None = None,
747 errors: str | None = "strict",
748 lazy: bool | None = None,
749 atomic: bool = False,
750 ) -> None:
751 self.mode = mode
752 self.encoding = encoding
753 self.errors = errors
754 self.lazy = lazy
755 self.atomic = atomic
757 def to_info_dict(self) -> dict[str, t.Any]:
758 info_dict = super().to_info_dict()
759 info_dict.update(mode=self.mode, encoding=self.encoding)
760 return info_dict
762 def resolve_lazy_flag(self, value: str | os.PathLike[str]) -> bool:
763 if self.lazy is not None:
764 return self.lazy
765 if os.fspath(value) == "-":
766 return False
767 elif "w" in self.mode:
768 return True
769 return False
771 def convert(
772 self,
773 value: str | os.PathLike[str] | t.IO[t.Any],
774 param: Parameter | None,
775 ctx: Context | None,
776 ) -> t.IO[t.Any]:
777 if _is_file_like(value):
778 return value
780 value = t.cast("str | os.PathLike[str]", value)
782 try:
783 lazy = self.resolve_lazy_flag(value)
785 if lazy:
786 lf = LazyFile(
787 value, self.mode, self.encoding, self.errors, atomic=self.atomic
788 )
790 if ctx is not None:
791 ctx.call_on_close(lf.close_intelligently)
793 return t.cast("t.IO[t.Any]", lf)
795 f, should_close = open_stream(
796 value, self.mode, self.encoding, self.errors, atomic=self.atomic
797 )
799 # If a context is provided, we automatically close the file
800 # at the end of the context execution (or flush out). If a
801 # context does not exist, it's the caller's responsibility to
802 # properly close the file. This for instance happens when the
803 # type is used with prompts.
804 if ctx is not None:
805 if should_close:
806 ctx.call_on_close(safecall(f.close))
807 else:
808 ctx.call_on_close(safecall(f.flush))
810 return f
811 except OSError as e:
812 self.fail(f"'{format_filename(value)}': {e.strerror}", param, ctx)
814 def shell_complete(
815 self, ctx: Context, param: Parameter, incomplete: str
816 ) -> list[CompletionItem]:
817 """Return a special completion marker that tells the completion
818 system to use the shell to provide file path completions.
820 :param ctx: Invocation context for this command.
821 :param param: The parameter that is requesting completion.
822 :param incomplete: Value being completed. May be empty.
824 .. versionadded:: 8.0
825 """
826 from click.shell_completion import CompletionItem
828 return [CompletionItem(incomplete, type="file")]
831def _is_file_like(value: t.Any) -> te.TypeGuard[t.IO[t.Any]]:
832 return hasattr(value, "read") or hasattr(value, "write")
835class Path(ParamType):
836 """The ``Path`` type is similar to the :class:`File` type, but
837 returns the filename instead of an open file. Various checks can be
838 enabled to validate the type of file and permissions.
840 :param exists: The file or directory needs to exist for the value to
841 be valid. If this is not set to ``True``, and the file does not
842 exist, then all further checks are silently skipped.
843 :param file_okay: Allow a file as a value.
844 :param dir_okay: Allow a directory as a value.
845 :param readable: if true, a readable check is performed.
846 :param writable: if true, a writable check is performed.
847 :param executable: if true, an executable check is performed.
848 :param resolve_path: Make the value absolute and resolve any
849 symlinks. A ``~`` is not expanded, as this is supposed to be
850 done by the shell only.
851 :param allow_dash: Allow a single dash as a value, which indicates
852 a standard stream (but does not open it). Use
853 :func:`~click.open_file` to handle opening this value.
854 :param path_type: Convert the incoming path value to this type. If
855 ``None``, keep Python's default, which is ``str``. Useful to
856 convert to :class:`pathlib.Path`.
858 .. versionchanged:: 8.1
859 Added the ``executable`` parameter.
861 .. versionchanged:: 8.0
862 Allow passing ``path_type=pathlib.Path``.
864 .. versionchanged:: 6.0
865 Added the ``allow_dash`` parameter.
866 """
868 envvar_list_splitter: t.ClassVar[str] = os.path.pathsep
870 def __init__(
871 self,
872 exists: bool = False,
873 file_okay: bool = True,
874 dir_okay: bool = True,
875 writable: bool = False,
876 readable: bool = True,
877 resolve_path: bool = False,
878 allow_dash: bool = False,
879 path_type: type[t.Any] | None = None,
880 executable: bool = False,
881 ):
882 self.exists = exists
883 self.file_okay = file_okay
884 self.dir_okay = dir_okay
885 self.readable = readable
886 self.writable = writable
887 self.executable = executable
888 self.resolve_path = resolve_path
889 self.allow_dash = allow_dash
890 self.type = path_type
892 if self.file_okay and not self.dir_okay:
893 self.name: str = _("file")
894 elif self.dir_okay and not self.file_okay:
895 self.name = _("directory")
896 else:
897 self.name = _("path")
899 def to_info_dict(self) -> dict[str, t.Any]:
900 info_dict = super().to_info_dict()
901 info_dict.update(
902 exists=self.exists,
903 file_okay=self.file_okay,
904 dir_okay=self.dir_okay,
905 writable=self.writable,
906 readable=self.readable,
907 allow_dash=self.allow_dash,
908 )
909 return info_dict
911 def coerce_path_result(
912 self, value: str | os.PathLike[str]
913 ) -> str | bytes | os.PathLike[str]:
914 if self.type is not None and not isinstance(value, self.type):
915 if self.type is str:
916 return os.fsdecode(value)
917 elif self.type is bytes:
918 return os.fsencode(value)
919 else:
920 return t.cast("os.PathLike[str]", self.type(value))
922 return value
924 def convert(
925 self,
926 value: str | os.PathLike[str],
927 param: Parameter | None,
928 ctx: Context | None,
929 ) -> str | bytes | os.PathLike[str]:
930 rv = value
932 is_dash = self.file_okay and self.allow_dash and rv in (b"-", "-")
934 if not is_dash:
935 if self.resolve_path:
936 rv = os.path.realpath(rv)
938 try:
939 st = os.stat(rv)
940 except OSError:
941 if not self.exists:
942 return self.coerce_path_result(rv)
943 self.fail(
944 _("{name} {filename!r} does not exist.").format(
945 name=self.name.title(), filename=format_filename(value)
946 ),
947 param,
948 ctx,
949 )
951 if not self.file_okay and stat.S_ISREG(st.st_mode):
952 self.fail(
953 _("{name} {filename!r} is a file.").format(
954 name=self.name.title(), filename=format_filename(value)
955 ),
956 param,
957 ctx,
958 )
959 if not self.dir_okay and stat.S_ISDIR(st.st_mode):
960 self.fail(
961 _("{name} {filename!r} is a directory.").format(
962 name=self.name.title(), filename=format_filename(value)
963 ),
964 param,
965 ctx,
966 )
968 if self.readable and not os.access(rv, os.R_OK):
969 self.fail(
970 _("{name} {filename!r} is not readable.").format(
971 name=self.name.title(), filename=format_filename(value)
972 ),
973 param,
974 ctx,
975 )
977 if self.writable and not os.access(rv, os.W_OK):
978 self.fail(
979 _("{name} {filename!r} is not writable.").format(
980 name=self.name.title(), filename=format_filename(value)
981 ),
982 param,
983 ctx,
984 )
986 if self.executable and not os.access(value, os.X_OK):
987 self.fail(
988 _("{name} {filename!r} is not executable.").format(
989 name=self.name.title(), filename=format_filename(value)
990 ),
991 param,
992 ctx,
993 )
995 return self.coerce_path_result(rv)
997 def shell_complete(
998 self, ctx: Context, param: Parameter, incomplete: str
999 ) -> list[CompletionItem]:
1000 """Return a special completion marker that tells the completion
1001 system to use the shell to provide path completions for only
1002 directories or any paths.
1004 :param ctx: Invocation context for this command.
1005 :param param: The parameter that is requesting completion.
1006 :param incomplete: Value being completed. May be empty.
1008 .. versionadded:: 8.0
1009 """
1010 from click.shell_completion import CompletionItem
1012 type = "dir" if self.dir_okay and not self.file_okay else "file"
1013 return [CompletionItem(incomplete, type=type)]
1016class Tuple(CompositeParamType):
1017 """The default behavior of Click is to apply a type on a value directly.
1018 This works well in most cases, except for when `nargs` is set to a fixed
1019 count and different types should be used for different items. In this
1020 case the :class:`Tuple` type can be used. This type can only be used
1021 if `nargs` is set to a fixed number.
1023 For more information see :ref:`tuple-type`.
1025 This can be selected by using a Python tuple literal as a type.
1027 :param types: a list of types that should be used for the tuple items.
1028 """
1030 def __init__(self, types: cabc.Sequence[type[t.Any] | ParamType]) -> None:
1031 self.types: cabc.Sequence[ParamType] = [convert_type(ty) for ty in types]
1033 def to_info_dict(self) -> dict[str, t.Any]:
1034 info_dict = super().to_info_dict()
1035 info_dict["types"] = [t.to_info_dict() for t in self.types]
1036 return info_dict
1038 @property
1039 def name(self) -> str: # type: ignore
1040 return f"<{' '.join(ty.name for ty in self.types)}>"
1042 @property
1043 def arity(self) -> int: # type: ignore
1044 return len(self.types)
1046 def convert(
1047 self, value: t.Any, param: Parameter | None, ctx: Context | None
1048 ) -> t.Any:
1049 len_type = len(self.types)
1050 len_value = len(value)
1052 if len_value != len_type:
1053 self.fail(
1054 ngettext(
1055 "{len_type} values are required, but {len_value} was given.",
1056 "{len_type} values are required, but {len_value} were given.",
1057 len_value,
1058 ).format(len_type=len_type, len_value=len_value),
1059 param=param,
1060 ctx=ctx,
1061 )
1063 return tuple(
1064 ty(x, param, ctx) for ty, x in zip(self.types, value, strict=False)
1065 )
1068def convert_type(ty: t.Any | None, default: t.Any | None = None) -> ParamType:
1069 """Find the most appropriate :class:`ParamType` for the given Python
1070 type. If the type isn't provided, it can be inferred from a default
1071 value.
1072 """
1073 guessed_type = False
1075 if ty is None and default is not None:
1076 if isinstance(default, (tuple, list)):
1077 # If the default is empty, ty will remain None and will
1078 # return STRING.
1079 if default:
1080 item = default[0]
1082 # A tuple of tuples needs to detect the inner types.
1083 # Can't call convert recursively because that would
1084 # incorrectly unwind the tuple to a single type.
1085 if isinstance(item, (tuple, list)):
1086 ty = tuple(map(type, item))
1087 else:
1088 ty = type(item)
1089 else:
1090 ty = type(default)
1092 guessed_type = True
1094 if isinstance(ty, tuple):
1095 return Tuple(ty)
1097 if isinstance(ty, ParamType):
1098 return ty
1100 if ty is str or ty is None:
1101 return STRING
1103 if ty is int:
1104 return INT
1106 if ty is float:
1107 return FLOAT
1109 if ty is bool:
1110 return BOOL
1112 if guessed_type:
1113 return STRING
1115 if __debug__:
1116 try:
1117 if issubclass(ty, ParamType):
1118 raise AssertionError(
1119 f"Attempted to use an uninstantiated parameter type ({ty})."
1120 )
1121 except TypeError:
1122 # ty is an instance (correct), so issubclass fails.
1123 pass
1125 return FuncParamType(ty)
1128#: A dummy parameter type that just does nothing. From a user's
1129#: perspective this appears to just be the same as `STRING` but
1130#: internally no string conversion takes place if the input was bytes.
1131#: This is usually useful when working with file paths as they can
1132#: appear in bytes and unicode.
1133#:
1134#: For path related uses the :class:`Path` type is a better choice but
1135#: there are situations where an unprocessed type is useful which is why
1136#: it is is provided.
1137#:
1138#: .. versionadded:: 4.0
1139UNPROCESSED = UnprocessedParamType()
1141#: A unicode string parameter type which is the implicit default. This
1142#: can also be selected by using ``str`` as type.
1143STRING = StringParamType()
1145#: An integer parameter. This can also be selected by using ``int`` as
1146#: type.
1147INT = IntParamType()
1149#: A floating point value parameter. This can also be selected by using
1150#: ``float`` as type.
1151FLOAT = FloatParamType()
1153#: A boolean parameter. This is the default for boolean flags. This can
1154#: also be selected by using ``bool`` as a type.
1155BOOL = BoolParamType()
1157#: A UUID parameter.
1158UUID = UUIDParameterType()
1161class OptionHelpExtra(t.TypedDict, total=False):
1162 envvars: tuple[str, ...]
1163 default: str
1164 range: str
1165 required: str