davidtran999 commited on
Commit
18d8a20
·
verified ·
1 Parent(s): dcc3925

Upload backend/venv/lib/python3.10/site-packages/marshmallow/validate.py with huggingface_hub

Browse files
backend/venv/lib/python3.10/site-packages/marshmallow/validate.py ADDED
@@ -0,0 +1,700 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Validation classes for various types of data."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import re
6
+ import typing
7
+ import warnings
8
+ from abc import ABC, abstractmethod
9
+ from itertools import zip_longest
10
+ from operator import attrgetter
11
+
12
+ from marshmallow.exceptions import ValidationError
13
+ from marshmallow.warnings import ChangedInMarshmallow4Warning
14
+
15
+ if typing.TYPE_CHECKING:
16
+ from marshmallow import types
17
+
18
+ _T = typing.TypeVar("_T")
19
+
20
+
21
+ class Validator(ABC):
22
+ """Abstract base class for validators.
23
+
24
+ .. note::
25
+ This class does not provide any validation behavior. It is only used to
26
+ add a useful `__repr__` implementation for validators.
27
+ """
28
+
29
+ error: str | None = None
30
+
31
+ def __repr__(self) -> str:
32
+ args = self._repr_args()
33
+ args = f"{args}, " if args else ""
34
+
35
+ return f"<{self.__class__.__name__}({args}error={self.error!r})>"
36
+
37
+ def _repr_args(self) -> str:
38
+ """A string representation of the args passed to this validator. Used by
39
+ `__repr__`.
40
+ """
41
+ return ""
42
+
43
+ @abstractmethod
44
+ def __call__(self, value: typing.Any) -> typing.Any: ...
45
+
46
+
47
+ class And(Validator):
48
+ """Compose multiple validators and combine their error messages.
49
+
50
+ Example: ::
51
+
52
+ from marshmallow import validate, ValidationError
53
+
54
+
55
+ def is_even(value):
56
+ if value % 2 != 0:
57
+ raise ValidationError("Not an even value.")
58
+
59
+
60
+ validator = validate.And(validate.Range(min=0), is_even)
61
+ validator(-1)
62
+ # ValidationError: ['Must be greater than or equal to 0.', 'Not an even value.']
63
+
64
+ :param validators: Validators to combine.
65
+ :param error: Error message to use when a validator returns ``False``.
66
+ """
67
+
68
+ default_error_message = "Invalid value."
69
+
70
+ def __init__(self, *validators: types.Validator, error: str | None = None):
71
+ self.validators = tuple(validators)
72
+ self.error: str = error or self.default_error_message
73
+
74
+ def _repr_args(self) -> str:
75
+ return f"validators={self.validators!r}"
76
+
77
+ def __call__(self, value: typing.Any) -> typing.Any:
78
+ errors: list[str | dict] = []
79
+ kwargs: dict[str, typing.Any] = {}
80
+ for validator in self.validators:
81
+ try:
82
+ r = validator(value)
83
+ if not isinstance(validator, Validator) and r is False:
84
+ warnings.warn(
85
+ "Returning `False` from a validator is deprecated. "
86
+ "Raise a `ValidationError` instead.",
87
+ ChangedInMarshmallow4Warning,
88
+ stacklevel=2,
89
+ )
90
+ raise ValidationError(self.error) # noqa: TRY301
91
+ except ValidationError as err:
92
+ kwargs.update(err.kwargs)
93
+ if isinstance(err.messages, dict):
94
+ errors.append(err.messages)
95
+ else:
96
+ errors.extend(err.messages)
97
+ if errors:
98
+ raise ValidationError(errors, **kwargs)
99
+ return value
100
+
101
+
102
+ class URL(Validator):
103
+ """Validate a URL.
104
+
105
+ :param relative: Whether to allow relative URLs.
106
+ :param absolute: Whether to allow absolute URLs.
107
+ :param error: Error message to raise in case of a validation error.
108
+ Can be interpolated with `{input}`.
109
+ :param schemes: Valid schemes. By default, ``http``, ``https``,
110
+ ``ftp``, and ``ftps`` are allowed.
111
+ :param require_tld: Whether to reject non-FQDN hostnames.
112
+ """
113
+
114
+ class RegexMemoizer:
115
+ def __init__(self):
116
+ self._memoized = {}
117
+
118
+ def _regex_generator(
119
+ self, *, relative: bool, absolute: bool, require_tld: bool
120
+ ) -> typing.Pattern:
121
+ hostname_variants = [
122
+ # a normal domain name, expressed in [A-Z0-9] chars with hyphens allowed only in the middle
123
+ # note that the regex will be compiled with IGNORECASE, so these are upper and lowercase chars
124
+ (
125
+ r"(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+"
126
+ r"(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}\.?)"
127
+ ),
128
+ # or the special string 'localhost'
129
+ r"localhost",
130
+ # or IPv4
131
+ r"\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}",
132
+ # or IPv6
133
+ r"\[[A-F0-9]*:[A-F0-9:]+\]",
134
+ ]
135
+ if not require_tld:
136
+ # allow dotless hostnames
137
+ hostname_variants.append(r"(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.?)")
138
+
139
+ absolute_part = "".join(
140
+ (
141
+ # scheme (e.g. 'https://', 'ftp://', etc)
142
+ # this is validated separately against allowed schemes, so in the regex
143
+ # we simply want to capture its existence
144
+ r"(?:[a-z0-9\.\-\+]*)://",
145
+ # userinfo, for URLs encoding authentication
146
+ # e.g. 'ftp://foo:bar@ftp.example.org/'
147
+ r"(?:(?:[a-z0-9\-._~!$&'()*+,;=:]|%[0-9a-f]{2})*@)?",
148
+ # netloc, the hostname/domain part of the URL plus the optional port
149
+ r"(?:",
150
+ "|".join(hostname_variants),
151
+ r")",
152
+ r"(?::\d+)?",
153
+ )
154
+ )
155
+ relative_part = r"(?:/?|[/?]\S+)\Z"
156
+
157
+ if relative:
158
+ if absolute:
159
+ parts: tuple[str, ...] = (
160
+ r"^(",
161
+ absolute_part,
162
+ r")?",
163
+ relative_part,
164
+ )
165
+ else:
166
+ parts = (r"^", relative_part)
167
+ else:
168
+ parts = (r"^", absolute_part, relative_part)
169
+
170
+ return re.compile("".join(parts), re.IGNORECASE)
171
+
172
+ def __call__(
173
+ self, *, relative: bool, absolute: bool, require_tld: bool
174
+ ) -> typing.Pattern:
175
+ key = (relative, absolute, require_tld)
176
+ if key not in self._memoized:
177
+ self._memoized[key] = self._regex_generator(
178
+ relative=relative, absolute=absolute, require_tld=require_tld
179
+ )
180
+
181
+ return self._memoized[key]
182
+
183
+ _regex = RegexMemoizer()
184
+
185
+ default_message = "Not a valid URL."
186
+ default_schemes = {"http", "https", "ftp", "ftps"}
187
+
188
+ def __init__(
189
+ self,
190
+ *,
191
+ relative: bool = False,
192
+ absolute: bool = True,
193
+ schemes: types.StrSequenceOrSet | None = None,
194
+ require_tld: bool = True,
195
+ error: str | None = None,
196
+ ):
197
+ if not relative and not absolute:
198
+ raise ValueError(
199
+ "URL validation cannot set both relative and absolute to False."
200
+ )
201
+ self.relative = relative
202
+ self.absolute = absolute
203
+ self.error: str = error or self.default_message
204
+ self.schemes = schemes or self.default_schemes
205
+ self.require_tld = require_tld
206
+
207
+ def _repr_args(self) -> str:
208
+ return f"relative={self.relative!r}, absolute={self.absolute!r}"
209
+
210
+ def _format_error(self, value) -> str:
211
+ return self.error.format(input=value)
212
+
213
+ def __call__(self, value: str) -> str:
214
+ message = self._format_error(value)
215
+ if not value:
216
+ raise ValidationError(message)
217
+
218
+ # Check first if the scheme is valid
219
+ scheme = None
220
+ if "://" in value:
221
+ scheme = value.split("://")[0].lower()
222
+ if scheme not in self.schemes:
223
+ raise ValidationError(message)
224
+
225
+ regex = self._regex(
226
+ relative=self.relative, absolute=self.absolute, require_tld=self.require_tld
227
+ )
228
+
229
+ # Hostname is optional for file URLS. If absent it means `localhost`.
230
+ # Fill it in for the validation if needed
231
+ if scheme == "file" and value.startswith("file:///"):
232
+ matched = regex.search(value.replace("file:///", "file://localhost/", 1))
233
+ else:
234
+ matched = regex.search(value)
235
+
236
+ if not matched:
237
+ raise ValidationError(message)
238
+
239
+ return value
240
+
241
+
242
+ class Email(Validator):
243
+ """Validate an email address.
244
+
245
+ :param error: Error message to raise in case of a validation error. Can be
246
+ interpolated with `{input}`.
247
+ """
248
+
249
+ USER_REGEX = re.compile(
250
+ r"(^[-!#$%&'*+/=?^`{}|~\w]+(\.[-!#$%&'*+/=?^`{}|~\w]+)*\Z" # dot-atom
251
+ # quoted-string
252
+ r'|^"([\001-\010\013\014\016-\037!#-\[\]-\177]'
253
+ r'|\\[\001-\011\013\014\016-\177])*"\Z)',
254
+ re.IGNORECASE | re.UNICODE,
255
+ )
256
+
257
+ DOMAIN_REGEX = re.compile(
258
+ # domain
259
+ r"(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+"
260
+ r"(?:[A-Z]{2,6}|[A-Z0-9-]{2,})\Z"
261
+ # literal form, ipv4 address (SMTP 4.1.3)
262
+ r"|^\[(25[0-5]|2[0-4]\d|[0-1]?\d?\d)"
263
+ r"(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}\]\Z",
264
+ re.IGNORECASE | re.UNICODE,
265
+ )
266
+
267
+ DOMAIN_WHITELIST = ("localhost",)
268
+
269
+ default_message = "Not a valid email address."
270
+
271
+ def __init__(self, *, error: str | None = None):
272
+ self.error: str = error or self.default_message
273
+
274
+ def _format_error(self, value: str) -> str:
275
+ return self.error.format(input=value)
276
+
277
+ def __call__(self, value: str) -> str:
278
+ message = self._format_error(value)
279
+
280
+ if not value or "@" not in value:
281
+ raise ValidationError(message)
282
+
283
+ user_part, domain_part = value.rsplit("@", 1)
284
+
285
+ if not self.USER_REGEX.match(user_part):
286
+ raise ValidationError(message)
287
+
288
+ if domain_part not in self.DOMAIN_WHITELIST:
289
+ if not self.DOMAIN_REGEX.match(domain_part):
290
+ try:
291
+ domain_part = domain_part.encode("idna").decode("ascii")
292
+ except UnicodeError:
293
+ pass
294
+ else:
295
+ if self.DOMAIN_REGEX.match(domain_part):
296
+ return value
297
+ raise ValidationError(message)
298
+
299
+ return value
300
+
301
+
302
+ class Range(Validator):
303
+ """Validator which succeeds if the value passed to it is within the specified
304
+ range. If ``min`` is not specified, or is specified as `None`,
305
+ no lower bound exists. If ``max`` is not specified, or is specified as `None`,
306
+ no upper bound exists. The inclusivity of the bounds (if they exist) is configurable.
307
+ If ``min_inclusive`` is not specified, or is specified as `True`, then
308
+ the ``min`` bound is included in the range. If ``max_inclusive`` is not specified,
309
+ or is specified as `True`, then the ``max`` bound is included in the range.
310
+
311
+ :param min: The minimum value (lower bound). If not provided, minimum
312
+ value will not be checked.
313
+ :param max: The maximum value (upper bound). If not provided, maximum
314
+ value will not be checked.
315
+ :param min_inclusive: Whether the `min` bound is included in the range.
316
+ :param max_inclusive: Whether the `max` bound is included in the range.
317
+ :param error: Error message to raise in case of a validation error.
318
+ Can be interpolated with `{input}`, `{min}` and `{max}`.
319
+ """
320
+
321
+ message_min = "Must be {min_op} {{min}}."
322
+ message_max = "Must be {max_op} {{max}}."
323
+ message_all = "Must be {min_op} {{min}} and {max_op} {{max}}."
324
+
325
+ message_gte = "greater than or equal to"
326
+ message_gt = "greater than"
327
+ message_lte = "less than or equal to"
328
+ message_lt = "less than"
329
+
330
+ def __init__(
331
+ self,
332
+ min=None, # noqa: A002
333
+ max=None, # noqa: A002
334
+ *,
335
+ min_inclusive: bool = True,
336
+ max_inclusive: bool = True,
337
+ error: str | None = None,
338
+ ):
339
+ self.min = min
340
+ self.max = max
341
+ self.error = error
342
+ self.min_inclusive = min_inclusive
343
+ self.max_inclusive = max_inclusive
344
+
345
+ # interpolate messages based on bound inclusivity
346
+ self.message_min = self.message_min.format(
347
+ min_op=self.message_gte if self.min_inclusive else self.message_gt
348
+ )
349
+ self.message_max = self.message_max.format(
350
+ max_op=self.message_lte if self.max_inclusive else self.message_lt
351
+ )
352
+ self.message_all = self.message_all.format(
353
+ min_op=self.message_gte if self.min_inclusive else self.message_gt,
354
+ max_op=self.message_lte if self.max_inclusive else self.message_lt,
355
+ )
356
+
357
+ def _repr_args(self) -> str:
358
+ return f"min={self.min!r}, max={self.max!r}, min_inclusive={self.min_inclusive!r}, max_inclusive={self.max_inclusive!r}"
359
+
360
+ def _format_error(self, value: _T, message: str) -> str:
361
+ return (self.error or message).format(input=value, min=self.min, max=self.max)
362
+
363
+ def __call__(self, value: _T) -> _T:
364
+ if self.min is not None and (
365
+ value < self.min if self.min_inclusive else value <= self.min
366
+ ):
367
+ message = self.message_min if self.max is None else self.message_all
368
+ raise ValidationError(self._format_error(value, message))
369
+
370
+ if self.max is not None and (
371
+ value > self.max if self.max_inclusive else value >= self.max
372
+ ):
373
+ message = self.message_max if self.min is None else self.message_all
374
+ raise ValidationError(self._format_error(value, message))
375
+
376
+ return value
377
+
378
+
379
+ _SizedT = typing.TypeVar("_SizedT", bound=typing.Sized)
380
+
381
+
382
+ class Length(Validator):
383
+ """Validator which succeeds if the value passed to it has a
384
+ length between a minimum and maximum. Uses len(), so it
385
+ can work for strings, lists, or anything with length.
386
+
387
+ :param min: The minimum length. If not provided, minimum length
388
+ will not be checked.
389
+ :param max: The maximum length. If not provided, maximum length
390
+ will not be checked.
391
+ :param equal: The exact length. If provided, maximum and minimum
392
+ length will not be checked.
393
+ :param error: Error message to raise in case of a validation error.
394
+ Can be interpolated with `{input}`, `{min}` and `{max}`.
395
+ """
396
+
397
+ message_min = "Shorter than minimum length {min}."
398
+ message_max = "Longer than maximum length {max}."
399
+ message_all = "Length must be between {min} and {max}."
400
+ message_equal = "Length must be {equal}."
401
+
402
+ def __init__(
403
+ self,
404
+ min: int | None = None, # noqa: A002
405
+ max: int | None = None, # noqa: A002
406
+ *,
407
+ equal: int | None = None,
408
+ error: str | None = None,
409
+ ):
410
+ if equal is not None and any([min, max]):
411
+ raise ValueError(
412
+ "The `equal` parameter was provided, maximum or "
413
+ "minimum parameter must not be provided."
414
+ )
415
+
416
+ self.min = min
417
+ self.max = max
418
+ self.error = error
419
+ self.equal = equal
420
+
421
+ def _repr_args(self) -> str:
422
+ return f"min={self.min!r}, max={self.max!r}, equal={self.equal!r}"
423
+
424
+ def _format_error(self, value: _SizedT, message: str) -> str:
425
+ return (self.error or message).format(
426
+ input=value, min=self.min, max=self.max, equal=self.equal
427
+ )
428
+
429
+ def __call__(self, value: _SizedT) -> _SizedT:
430
+ length = len(value)
431
+
432
+ if self.equal is not None:
433
+ if length != self.equal:
434
+ raise ValidationError(self._format_error(value, self.message_equal))
435
+ return value
436
+
437
+ if self.min is not None and length < self.min:
438
+ message = self.message_min if self.max is None else self.message_all
439
+ raise ValidationError(self._format_error(value, message))
440
+
441
+ if self.max is not None and length > self.max:
442
+ message = self.message_max if self.min is None else self.message_all
443
+ raise ValidationError(self._format_error(value, message))
444
+
445
+ return value
446
+
447
+
448
+ class Equal(Validator):
449
+ """Validator which succeeds if the ``value`` passed to it is
450
+ equal to ``comparable``.
451
+
452
+ :param comparable: The object to compare to.
453
+ :param error: Error message to raise in case of a validation error.
454
+ Can be interpolated with `{input}` and `{other}`.
455
+ """
456
+
457
+ default_message = "Must be equal to {other}."
458
+
459
+ def __init__(self, comparable, *, error: str | None = None):
460
+ self.comparable = comparable
461
+ self.error: str = error or self.default_message
462
+
463
+ def _repr_args(self) -> str:
464
+ return f"comparable={self.comparable!r}"
465
+
466
+ def _format_error(self, value: _T) -> str:
467
+ return self.error.format(input=value, other=self.comparable)
468
+
469
+ def __call__(self, value: _T) -> _T:
470
+ if value != self.comparable:
471
+ raise ValidationError(self._format_error(value))
472
+ return value
473
+
474
+
475
+ class Regexp(Validator):
476
+ """Validator which succeeds if the ``value`` matches ``regex``.
477
+
478
+ .. note::
479
+
480
+ Uses `re.match`, which searches for a match at the beginning of a string.
481
+
482
+ :param regex: The regular expression string to use. Can also be a compiled
483
+ regular expression pattern.
484
+ :param flags: The regexp flags to use, for example re.IGNORECASE. Ignored
485
+ if ``regex`` is not a string.
486
+ :param error: Error message to raise in case of a validation error.
487
+ Can be interpolated with `{input}` and `{regex}`.
488
+ """
489
+
490
+ default_message = "String does not match expected pattern."
491
+
492
+ def __init__(
493
+ self,
494
+ regex: str | bytes | typing.Pattern,
495
+ flags: int = 0,
496
+ *,
497
+ error: str | None = None,
498
+ ):
499
+ self.regex = (
500
+ re.compile(regex, flags) if isinstance(regex, (str, bytes)) else regex
501
+ )
502
+ self.error: str = error or self.default_message
503
+
504
+ def _repr_args(self) -> str:
505
+ return f"regex={self.regex!r}"
506
+
507
+ def _format_error(self, value: str | bytes) -> str:
508
+ return self.error.format(input=value, regex=self.regex.pattern)
509
+
510
+ @typing.overload
511
+ def __call__(self, value: str) -> str: ...
512
+
513
+ @typing.overload
514
+ def __call__(self, value: bytes) -> bytes: ...
515
+
516
+ def __call__(self, value):
517
+ if self.regex.match(value) is None:
518
+ raise ValidationError(self._format_error(value))
519
+
520
+ return value
521
+
522
+
523
+ class Predicate(Validator):
524
+ """Call the specified ``method`` of the ``value`` object. The
525
+ validator succeeds if the invoked method returns an object that
526
+ evaluates to True in a Boolean context. Any additional keyword
527
+ argument will be passed to the method.
528
+
529
+ :param method: The name of the method to invoke.
530
+ :param error: Error message to raise in case of a validation error.
531
+ Can be interpolated with `{input}` and `{method}`.
532
+ :param kwargs: Additional keyword arguments to pass to the method.
533
+ """
534
+
535
+ default_message = "Invalid input."
536
+
537
+ def __init__(self, method: str, *, error: str | None = None, **kwargs):
538
+ self.method = method
539
+ self.error: str = error or self.default_message
540
+ self.kwargs = kwargs
541
+
542
+ def _repr_args(self) -> str:
543
+ return f"method={self.method!r}, kwargs={self.kwargs!r}"
544
+
545
+ def _format_error(self, value: typing.Any) -> str:
546
+ return self.error.format(input=value, method=self.method)
547
+
548
+ def __call__(self, value: _T) -> _T:
549
+ method = getattr(value, self.method)
550
+
551
+ if not method(**self.kwargs):
552
+ raise ValidationError(self._format_error(value))
553
+
554
+ return value
555
+
556
+
557
+ class NoneOf(Validator):
558
+ """Validator which fails if ``value`` is a member of ``iterable``.
559
+
560
+ :param iterable: A sequence of invalid values.
561
+ :param error: Error message to raise in case of a validation error. Can be
562
+ interpolated using `{input}` and `{values}`.
563
+ """
564
+
565
+ default_message = "Invalid input."
566
+
567
+ def __init__(self, iterable: typing.Iterable, *, error: str | None = None):
568
+ self.iterable = iterable
569
+ self.values_text = ", ".join(str(each) for each in self.iterable)
570
+ self.error: str = error or self.default_message
571
+
572
+ def _repr_args(self) -> str:
573
+ return f"iterable={self.iterable!r}"
574
+
575
+ def _format_error(self, value) -> str:
576
+ return self.error.format(input=value, values=self.values_text)
577
+
578
+ def __call__(self, value: typing.Any) -> typing.Any:
579
+ try:
580
+ if value in self.iterable:
581
+ raise ValidationError(self._format_error(value))
582
+ except TypeError:
583
+ pass
584
+
585
+ return value
586
+
587
+
588
+ class OneOf(Validator):
589
+ """Validator which succeeds if ``value`` is a member of ``choices``.
590
+
591
+ :param choices: A sequence of valid values.
592
+ :param labels: Optional sequence of labels to pair with the choices.
593
+ :param error: Error message to raise in case of a validation error. Can be
594
+ interpolated with `{input}`, `{choices}` and `{labels}`.
595
+ """
596
+
597
+ default_message = "Must be one of: {choices}."
598
+
599
+ def __init__(
600
+ self,
601
+ choices: typing.Iterable,
602
+ labels: typing.Iterable[str] | None = None,
603
+ *,
604
+ error: str | None = None,
605
+ ):
606
+ self.choices = choices
607
+ self.choices_text = ", ".join(str(choice) for choice in self.choices)
608
+ self.labels = labels if labels is not None else []
609
+ self.labels_text = ", ".join(str(label) for label in self.labels)
610
+ self.error: str = error or self.default_message
611
+
612
+ def _repr_args(self) -> str:
613
+ return f"choices={self.choices!r}, labels={self.labels!r}"
614
+
615
+ def _format_error(self, value) -> str:
616
+ return self.error.format(
617
+ input=value, choices=self.choices_text, labels=self.labels_text
618
+ )
619
+
620
+ def __call__(self, value: typing.Any) -> typing.Any:
621
+ try:
622
+ if value not in self.choices:
623
+ raise ValidationError(self._format_error(value))
624
+ except TypeError as error:
625
+ raise ValidationError(self._format_error(value)) from error
626
+
627
+ return value
628
+
629
+ def options(
630
+ self,
631
+ valuegetter: str | typing.Callable[[typing.Any], typing.Any] = str,
632
+ ) -> typing.Iterable[tuple[typing.Any, str]]:
633
+ """Return a generator over the (value, label) pairs, where value
634
+ is a string associated with each choice. This convenience method
635
+ is useful to populate, for instance, a form select field.
636
+
637
+ :param valuegetter: Can be a callable or a string. In the former case, it must
638
+ be a one-argument callable which returns the value of a
639
+ choice. In the latter case, the string specifies the name
640
+ of an attribute of the choice objects. Defaults to `str()`
641
+ or `str()`.
642
+ """
643
+ valuegetter = valuegetter if callable(valuegetter) else attrgetter(valuegetter)
644
+ pairs = zip_longest(self.choices, self.labels, fillvalue="")
645
+
646
+ return ((valuegetter(choice), label) for choice, label in pairs)
647
+
648
+
649
+ class ContainsOnly(OneOf):
650
+ """Validator which succeeds if ``value`` is a sequence and each element
651
+ in the sequence is also in the sequence passed as ``choices``. Empty input
652
+ is considered valid.
653
+
654
+ :param choices: Same as :class:`OneOf`.
655
+ :param labels: Same as :class:`OneOf`.
656
+ :param error: Same as :class:`OneOf`.
657
+
658
+ .. versionchanged:: 3.0.0b2
659
+ Duplicate values are considered valid.
660
+ .. versionchanged:: 3.0.0b2
661
+ Empty input is considered valid. Use `validate.Length(min=1) <marshmallow.validate.Length>`
662
+ to validate against empty inputs.
663
+ """
664
+
665
+ default_message = "One or more of the choices you made was not in: {choices}."
666
+
667
+ def _format_error(self, value) -> str:
668
+ value_text = ", ".join(str(val) for val in value)
669
+ return super()._format_error(value_text)
670
+
671
+ def __call__(self, value: typing.Sequence[_T]) -> typing.Sequence[_T]:
672
+ # We can't use set.issubset because does not handle unhashable types
673
+ for val in value:
674
+ if val not in self.choices:
675
+ raise ValidationError(self._format_error(value))
676
+ return value
677
+
678
+
679
+ class ContainsNoneOf(NoneOf):
680
+ """Validator which fails if ``value`` is a sequence and any element
681
+ in the sequence is a member of the sequence passed as ``iterable``. Empty input
682
+ is considered valid.
683
+
684
+ :param iterable: Same as :class:`NoneOf`.
685
+ :param error: Same as :class:`NoneOf`.
686
+
687
+ .. versionadded:: 3.6.0
688
+ """
689
+
690
+ default_message = "One or more of the choices you made was in: {values}."
691
+
692
+ def _format_error(self, value) -> str:
693
+ value_text = ", ".join(str(val) for val in value)
694
+ return super()._format_error(value_text)
695
+
696
+ def __call__(self, value: typing.Sequence[_T]) -> typing.Sequence[_T]:
697
+ for val in value:
698
+ if val in self.iterable:
699
+ raise ValidationError(self._format_error(value))
700
+ return value