comparison env/lib/python3.9/site-packages/click/types.py @ 0:4f3585e2f14b draft default tip

"planemo upload commit 60cee0fc7c0cda8592644e1aad72851dec82c959"
author shellac
date Mon, 22 Mar 2021 18:12:50 +0000
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:4f3585e2f14b
1 import os
2 import stat
3 from datetime import datetime
4
5 from ._compat import _get_argv_encoding
6 from ._compat import filename_to_ui
7 from ._compat import get_filesystem_encoding
8 from ._compat import get_streerror
9 from ._compat import open_stream
10 from ._compat import PY2
11 from ._compat import text_type
12 from .exceptions import BadParameter
13 from .utils import LazyFile
14 from .utils import safecall
15
16
17 class ParamType(object):
18 """Helper for converting values through types. The following is
19 necessary for a valid type:
20
21 * it needs a name
22 * it needs to pass through None unchanged
23 * it needs to convert from a string
24 * it needs to convert its result type through unchanged
25 (eg: needs to be idempotent)
26 * it needs to be able to deal with param and context being `None`.
27 This can be the case when the object is used with prompt
28 inputs.
29 """
30
31 is_composite = False
32
33 #: the descriptive name of this type
34 name = None
35
36 #: if a list of this type is expected and the value is pulled from a
37 #: string environment variable, this is what splits it up. `None`
38 #: means any whitespace. For all parameters the general rule is that
39 #: whitespace splits them up. The exception are paths and files which
40 #: are split by ``os.path.pathsep`` by default (":" on Unix and ";" on
41 #: Windows).
42 envvar_list_splitter = None
43
44 def __call__(self, value, param=None, ctx=None):
45 if value is not None:
46 return self.convert(value, param, ctx)
47
48 def get_metavar(self, param):
49 """Returns the metavar default for this param if it provides one."""
50
51 def get_missing_message(self, param):
52 """Optionally might return extra information about a missing
53 parameter.
54
55 .. versionadded:: 2.0
56 """
57
58 def convert(self, value, param, ctx):
59 """Converts the value. This is not invoked for values that are
60 `None` (the missing value).
61 """
62 return value
63
64 def split_envvar_value(self, rv):
65 """Given a value from an environment variable this splits it up
66 into small chunks depending on the defined envvar list splitter.
67
68 If the splitter is set to `None`, which means that whitespace splits,
69 then leading and trailing whitespace is ignored. Otherwise, leading
70 and trailing splitters usually lead to empty items being included.
71 """
72 return (rv or "").split(self.envvar_list_splitter)
73
74 def fail(self, message, param=None, ctx=None):
75 """Helper method to fail with an invalid value message."""
76 raise BadParameter(message, ctx=ctx, param=param)
77
78
79 class CompositeParamType(ParamType):
80 is_composite = True
81
82 @property
83 def arity(self):
84 raise NotImplementedError()
85
86
87 class FuncParamType(ParamType):
88 def __init__(self, func):
89 self.name = func.__name__
90 self.func = func
91
92 def convert(self, value, param, ctx):
93 try:
94 return self.func(value)
95 except ValueError:
96 try:
97 value = text_type(value)
98 except UnicodeError:
99 value = str(value).decode("utf-8", "replace")
100 self.fail(value, param, ctx)
101
102
103 class UnprocessedParamType(ParamType):
104 name = "text"
105
106 def convert(self, value, param, ctx):
107 return value
108
109 def __repr__(self):
110 return "UNPROCESSED"
111
112
113 class StringParamType(ParamType):
114 name = "text"
115
116 def convert(self, value, param, ctx):
117 if isinstance(value, bytes):
118 enc = _get_argv_encoding()
119 try:
120 value = value.decode(enc)
121 except UnicodeError:
122 fs_enc = get_filesystem_encoding()
123 if fs_enc != enc:
124 try:
125 value = value.decode(fs_enc)
126 except UnicodeError:
127 value = value.decode("utf-8", "replace")
128 else:
129 value = value.decode("utf-8", "replace")
130 return value
131 return value
132
133 def __repr__(self):
134 return "STRING"
135
136
137 class Choice(ParamType):
138 """The choice type allows a value to be checked against a fixed set
139 of supported values. All of these values have to be strings.
140
141 You should only pass a list or tuple of choices. Other iterables
142 (like generators) may lead to surprising results.
143
144 The resulting value will always be one of the originally passed choices
145 regardless of ``case_sensitive`` or any ``ctx.token_normalize_func``
146 being specified.
147
148 See :ref:`choice-opts` for an example.
149
150 :param case_sensitive: Set to false to make choices case
151 insensitive. Defaults to true.
152 """
153
154 name = "choice"
155
156 def __init__(self, choices, case_sensitive=True):
157 self.choices = choices
158 self.case_sensitive = case_sensitive
159
160 def get_metavar(self, param):
161 return "[{}]".format("|".join(self.choices))
162
163 def get_missing_message(self, param):
164 return "Choose from:\n\t{}.".format(",\n\t".join(self.choices))
165
166 def convert(self, value, param, ctx):
167 # Match through normalization and case sensitivity
168 # first do token_normalize_func, then lowercase
169 # preserve original `value` to produce an accurate message in
170 # `self.fail`
171 normed_value = value
172 normed_choices = {choice: choice for choice in self.choices}
173
174 if ctx is not None and ctx.token_normalize_func is not None:
175 normed_value = ctx.token_normalize_func(value)
176 normed_choices = {
177 ctx.token_normalize_func(normed_choice): original
178 for normed_choice, original in normed_choices.items()
179 }
180
181 if not self.case_sensitive:
182 if PY2:
183 lower = str.lower
184 else:
185 lower = str.casefold
186
187 normed_value = lower(normed_value)
188 normed_choices = {
189 lower(normed_choice): original
190 for normed_choice, original in normed_choices.items()
191 }
192
193 if normed_value in normed_choices:
194 return normed_choices[normed_value]
195
196 self.fail(
197 "invalid choice: {}. (choose from {})".format(
198 value, ", ".join(self.choices)
199 ),
200 param,
201 ctx,
202 )
203
204 def __repr__(self):
205 return "Choice('{}')".format(list(self.choices))
206
207
208 class DateTime(ParamType):
209 """The DateTime type converts date strings into `datetime` objects.
210
211 The format strings which are checked are configurable, but default to some
212 common (non-timezone aware) ISO 8601 formats.
213
214 When specifying *DateTime* formats, you should only pass a list or a tuple.
215 Other iterables, like generators, may lead to surprising results.
216
217 The format strings are processed using ``datetime.strptime``, and this
218 consequently defines the format strings which are allowed.
219
220 Parsing is tried using each format, in order, and the first format which
221 parses successfully is used.
222
223 :param formats: A list or tuple of date format strings, in the order in
224 which they should be tried. Defaults to
225 ``'%Y-%m-%d'``, ``'%Y-%m-%dT%H:%M:%S'``,
226 ``'%Y-%m-%d %H:%M:%S'``.
227 """
228
229 name = "datetime"
230
231 def __init__(self, formats=None):
232 self.formats = formats or ["%Y-%m-%d", "%Y-%m-%dT%H:%M:%S", "%Y-%m-%d %H:%M:%S"]
233
234 def get_metavar(self, param):
235 return "[{}]".format("|".join(self.formats))
236
237 def _try_to_convert_date(self, value, format):
238 try:
239 return datetime.strptime(value, format)
240 except ValueError:
241 return None
242
243 def convert(self, value, param, ctx):
244 # Exact match
245 for format in self.formats:
246 dtime = self._try_to_convert_date(value, format)
247 if dtime:
248 return dtime
249
250 self.fail(
251 "invalid datetime format: {}. (choose from {})".format(
252 value, ", ".join(self.formats)
253 )
254 )
255
256 def __repr__(self):
257 return "DateTime"
258
259
260 class IntParamType(ParamType):
261 name = "integer"
262
263 def convert(self, value, param, ctx):
264 try:
265 return int(value)
266 except ValueError:
267 self.fail("{} is not a valid integer".format(value), param, ctx)
268
269 def __repr__(self):
270 return "INT"
271
272
273 class IntRange(IntParamType):
274 """A parameter that works similar to :data:`click.INT` but restricts
275 the value to fit into a range. The default behavior is to fail if the
276 value falls outside the range, but it can also be silently clamped
277 between the two edges.
278
279 See :ref:`ranges` for an example.
280 """
281
282 name = "integer range"
283
284 def __init__(self, min=None, max=None, clamp=False):
285 self.min = min
286 self.max = max
287 self.clamp = clamp
288
289 def convert(self, value, param, ctx):
290 rv = IntParamType.convert(self, value, param, ctx)
291 if self.clamp:
292 if self.min is not None and rv < self.min:
293 return self.min
294 if self.max is not None and rv > self.max:
295 return self.max
296 if (
297 self.min is not None
298 and rv < self.min
299 or self.max is not None
300 and rv > self.max
301 ):
302 if self.min is None:
303 self.fail(
304 "{} is bigger than the maximum valid value {}.".format(
305 rv, self.max
306 ),
307 param,
308 ctx,
309 )
310 elif self.max is None:
311 self.fail(
312 "{} is smaller than the minimum valid value {}.".format(
313 rv, self.min
314 ),
315 param,
316 ctx,
317 )
318 else:
319 self.fail(
320 "{} is not in the valid range of {} to {}.".format(
321 rv, self.min, self.max
322 ),
323 param,
324 ctx,
325 )
326 return rv
327
328 def __repr__(self):
329 return "IntRange({}, {})".format(self.min, self.max)
330
331
332 class FloatParamType(ParamType):
333 name = "float"
334
335 def convert(self, value, param, ctx):
336 try:
337 return float(value)
338 except ValueError:
339 self.fail(
340 "{} is not a valid floating point value".format(value), param, ctx
341 )
342
343 def __repr__(self):
344 return "FLOAT"
345
346
347 class FloatRange(FloatParamType):
348 """A parameter that works similar to :data:`click.FLOAT` but restricts
349 the value to fit into a range. The default behavior is to fail if the
350 value falls outside the range, but it can also be silently clamped
351 between the two edges.
352
353 See :ref:`ranges` for an example.
354 """
355
356 name = "float range"
357
358 def __init__(self, min=None, max=None, clamp=False):
359 self.min = min
360 self.max = max
361 self.clamp = clamp
362
363 def convert(self, value, param, ctx):
364 rv = FloatParamType.convert(self, value, param, ctx)
365 if self.clamp:
366 if self.min is not None and rv < self.min:
367 return self.min
368 if self.max is not None and rv > self.max:
369 return self.max
370 if (
371 self.min is not None
372 and rv < self.min
373 or self.max is not None
374 and rv > self.max
375 ):
376 if self.min is None:
377 self.fail(
378 "{} is bigger than the maximum valid value {}.".format(
379 rv, self.max
380 ),
381 param,
382 ctx,
383 )
384 elif self.max is None:
385 self.fail(
386 "{} is smaller than the minimum valid value {}.".format(
387 rv, self.min
388 ),
389 param,
390 ctx,
391 )
392 else:
393 self.fail(
394 "{} is not in the valid range of {} to {}.".format(
395 rv, self.min, self.max
396 ),
397 param,
398 ctx,
399 )
400 return rv
401
402 def __repr__(self):
403 return "FloatRange({}, {})".format(self.min, self.max)
404
405
406 class BoolParamType(ParamType):
407 name = "boolean"
408
409 def convert(self, value, param, ctx):
410 if isinstance(value, bool):
411 return bool(value)
412 value = value.lower()
413 if value in ("true", "t", "1", "yes", "y"):
414 return True
415 elif value in ("false", "f", "0", "no", "n"):
416 return False
417 self.fail("{} is not a valid boolean".format(value), param, ctx)
418
419 def __repr__(self):
420 return "BOOL"
421
422
423 class UUIDParameterType(ParamType):
424 name = "uuid"
425
426 def convert(self, value, param, ctx):
427 import uuid
428
429 try:
430 if PY2 and isinstance(value, text_type):
431 value = value.encode("ascii")
432 return uuid.UUID(value)
433 except ValueError:
434 self.fail("{} is not a valid UUID value".format(value), param, ctx)
435
436 def __repr__(self):
437 return "UUID"
438
439
440 class File(ParamType):
441 """Declares a parameter to be a file for reading or writing. The file
442 is automatically closed once the context tears down (after the command
443 finished working).
444
445 Files can be opened for reading or writing. The special value ``-``
446 indicates stdin or stdout depending on the mode.
447
448 By default, the file is opened for reading text data, but it can also be
449 opened in binary mode or for writing. The encoding parameter can be used
450 to force a specific encoding.
451
452 The `lazy` flag controls if the file should be opened immediately or upon
453 first IO. The default is to be non-lazy for standard input and output
454 streams as well as files opened for reading, `lazy` otherwise. When opening a
455 file lazily for reading, it is still opened temporarily for validation, but
456 will not be held open until first IO. lazy is mainly useful when opening
457 for writing to avoid creating the file until it is needed.
458
459 Starting with Click 2.0, files can also be opened atomically in which
460 case all writes go into a separate file in the same folder and upon
461 completion the file will be moved over to the original location. This
462 is useful if a file regularly read by other users is modified.
463
464 See :ref:`file-args` for more information.
465 """
466
467 name = "filename"
468 envvar_list_splitter = os.path.pathsep
469
470 def __init__(
471 self, mode="r", encoding=None, errors="strict", lazy=None, atomic=False
472 ):
473 self.mode = mode
474 self.encoding = encoding
475 self.errors = errors
476 self.lazy = lazy
477 self.atomic = atomic
478
479 def resolve_lazy_flag(self, value):
480 if self.lazy is not None:
481 return self.lazy
482 if value == "-":
483 return False
484 elif "w" in self.mode:
485 return True
486 return False
487
488 def convert(self, value, param, ctx):
489 try:
490 if hasattr(value, "read") or hasattr(value, "write"):
491 return value
492
493 lazy = self.resolve_lazy_flag(value)
494
495 if lazy:
496 f = LazyFile(
497 value, self.mode, self.encoding, self.errors, atomic=self.atomic
498 )
499 if ctx is not None:
500 ctx.call_on_close(f.close_intelligently)
501 return f
502
503 f, should_close = open_stream(
504 value, self.mode, self.encoding, self.errors, atomic=self.atomic
505 )
506 # If a context is provided, we automatically close the file
507 # at the end of the context execution (or flush out). If a
508 # context does not exist, it's the caller's responsibility to
509 # properly close the file. This for instance happens when the
510 # type is used with prompts.
511 if ctx is not None:
512 if should_close:
513 ctx.call_on_close(safecall(f.close))
514 else:
515 ctx.call_on_close(safecall(f.flush))
516 return f
517 except (IOError, OSError) as e: # noqa: B014
518 self.fail(
519 "Could not open file: {}: {}".format(
520 filename_to_ui(value), get_streerror(e)
521 ),
522 param,
523 ctx,
524 )
525
526
527 class Path(ParamType):
528 """The path type is similar to the :class:`File` type but it performs
529 different checks. First of all, instead of returning an open file
530 handle it returns just the filename. Secondly, it can perform various
531 basic checks about what the file or directory should be.
532
533 .. versionchanged:: 6.0
534 `allow_dash` was added.
535
536 :param exists: if set to true, the file or directory needs to exist for
537 this value to be valid. If this is not required and a
538 file does indeed not exist, then all further checks are
539 silently skipped.
540 :param file_okay: controls if a file is a possible value.
541 :param dir_okay: controls if a directory is a possible value.
542 :param writable: if true, a writable check is performed.
543 :param readable: if true, a readable check is performed.
544 :param resolve_path: if this is true, then the path is fully resolved
545 before the value is passed onwards. This means
546 that it's absolute and symlinks are resolved. It
547 will not expand a tilde-prefix, as this is
548 supposed to be done by the shell only.
549 :param allow_dash: If this is set to `True`, a single dash to indicate
550 standard streams is permitted.
551 :param path_type: optionally a string type that should be used to
552 represent the path. The default is `None` which
553 means the return value will be either bytes or
554 unicode depending on what makes most sense given the
555 input data Click deals with.
556 """
557
558 envvar_list_splitter = os.path.pathsep
559
560 def __init__(
561 self,
562 exists=False,
563 file_okay=True,
564 dir_okay=True,
565 writable=False,
566 readable=True,
567 resolve_path=False,
568 allow_dash=False,
569 path_type=None,
570 ):
571 self.exists = exists
572 self.file_okay = file_okay
573 self.dir_okay = dir_okay
574 self.writable = writable
575 self.readable = readable
576 self.resolve_path = resolve_path
577 self.allow_dash = allow_dash
578 self.type = path_type
579
580 if self.file_okay and not self.dir_okay:
581 self.name = "file"
582 self.path_type = "File"
583 elif self.dir_okay and not self.file_okay:
584 self.name = "directory"
585 self.path_type = "Directory"
586 else:
587 self.name = "path"
588 self.path_type = "Path"
589
590 def coerce_path_result(self, rv):
591 if self.type is not None and not isinstance(rv, self.type):
592 if self.type is text_type:
593 rv = rv.decode(get_filesystem_encoding())
594 else:
595 rv = rv.encode(get_filesystem_encoding())
596 return rv
597
598 def convert(self, value, param, ctx):
599 rv = value
600
601 is_dash = self.file_okay and self.allow_dash and rv in (b"-", "-")
602
603 if not is_dash:
604 if self.resolve_path:
605 rv = os.path.realpath(rv)
606
607 try:
608 st = os.stat(rv)
609 except OSError:
610 if not self.exists:
611 return self.coerce_path_result(rv)
612 self.fail(
613 "{} '{}' does not exist.".format(
614 self.path_type, filename_to_ui(value)
615 ),
616 param,
617 ctx,
618 )
619
620 if not self.file_okay and stat.S_ISREG(st.st_mode):
621 self.fail(
622 "{} '{}' is a file.".format(self.path_type, filename_to_ui(value)),
623 param,
624 ctx,
625 )
626 if not self.dir_okay and stat.S_ISDIR(st.st_mode):
627 self.fail(
628 "{} '{}' is a directory.".format(
629 self.path_type, filename_to_ui(value)
630 ),
631 param,
632 ctx,
633 )
634 if self.writable and not os.access(value, os.W_OK):
635 self.fail(
636 "{} '{}' is not writable.".format(
637 self.path_type, filename_to_ui(value)
638 ),
639 param,
640 ctx,
641 )
642 if self.readable and not os.access(value, os.R_OK):
643 self.fail(
644 "{} '{}' is not readable.".format(
645 self.path_type, filename_to_ui(value)
646 ),
647 param,
648 ctx,
649 )
650
651 return self.coerce_path_result(rv)
652
653
654 class Tuple(CompositeParamType):
655 """The default behavior of Click is to apply a type on a value directly.
656 This works well in most cases, except for when `nargs` is set to a fixed
657 count and different types should be used for different items. In this
658 case the :class:`Tuple` type can be used. This type can only be used
659 if `nargs` is set to a fixed number.
660
661 For more information see :ref:`tuple-type`.
662
663 This can be selected by using a Python tuple literal as a type.
664
665 :param types: a list of types that should be used for the tuple items.
666 """
667
668 def __init__(self, types):
669 self.types = [convert_type(ty) for ty in types]
670
671 @property
672 def name(self):
673 return "<{}>".format(" ".join(ty.name for ty in self.types))
674
675 @property
676 def arity(self):
677 return len(self.types)
678
679 def convert(self, value, param, ctx):
680 if len(value) != len(self.types):
681 raise TypeError(
682 "It would appear that nargs is set to conflict with the"
683 " composite type arity."
684 )
685 return tuple(ty(x, param, ctx) for ty, x in zip(self.types, value))
686
687
688 def convert_type(ty, default=None):
689 """Converts a callable or python type into the most appropriate
690 param type.
691 """
692 guessed_type = False
693 if ty is None and default is not None:
694 if isinstance(default, tuple):
695 ty = tuple(map(type, default))
696 else:
697 ty = type(default)
698 guessed_type = True
699
700 if isinstance(ty, tuple):
701 return Tuple(ty)
702 if isinstance(ty, ParamType):
703 return ty
704 if ty is text_type or ty is str or ty is None:
705 return STRING
706 if ty is int:
707 return INT
708 # Booleans are only okay if not guessed. This is done because for
709 # flags the default value is actually a bit of a lie in that it
710 # indicates which of the flags is the one we want. See get_default()
711 # for more information.
712 if ty is bool and not guessed_type:
713 return BOOL
714 if ty is float:
715 return FLOAT
716 if guessed_type:
717 return STRING
718
719 # Catch a common mistake
720 if __debug__:
721 try:
722 if issubclass(ty, ParamType):
723 raise AssertionError(
724 "Attempted to use an uninstantiated parameter type ({}).".format(ty)
725 )
726 except TypeError:
727 pass
728 return FuncParamType(ty)
729
730
731 #: A dummy parameter type that just does nothing. From a user's
732 #: perspective this appears to just be the same as `STRING` but internally
733 #: no string conversion takes place. This is necessary to achieve the
734 #: same bytes/unicode behavior on Python 2/3 in situations where you want
735 #: to not convert argument types. This is usually useful when working
736 #: with file paths as they can appear in bytes and unicode.
737 #:
738 #: For path related uses the :class:`Path` type is a better choice but
739 #: there are situations where an unprocessed type is useful which is why
740 #: it is is provided.
741 #:
742 #: .. versionadded:: 4.0
743 UNPROCESSED = UnprocessedParamType()
744
745 #: A unicode string parameter type which is the implicit default. This
746 #: can also be selected by using ``str`` as type.
747 STRING = StringParamType()
748
749 #: An integer parameter. This can also be selected by using ``int`` as
750 #: type.
751 INT = IntParamType()
752
753 #: A floating point value parameter. This can also be selected by using
754 #: ``float`` as type.
755 FLOAT = FloatParamType()
756
757 #: A boolean parameter. This is the default for boolean flags. This can
758 #: also be selected by using ``bool`` as a type.
759 BOOL = BoolParamType()
760
761 #: A UUID parameter.
762 UUID = UUIDParameterType()