bagof-validators 0.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. bagof_validators-0.1/LICENSE +21 -0
  2. bagof_validators-0.1/PKG-INFO +52 -0
  3. bagof_validators-0.1/README.md +18 -0
  4. bagof_validators-0.1/pyproject.toml +110 -0
  5. bagof_validators-0.1/setup.cfg +4 -0
  6. bagof_validators-0.1/src/bagof/validators/__init__.py +75 -0
  7. bagof_validators-0.1/src/bagof/validators/_compat.py +35 -0
  8. bagof_validators-0.1/src/bagof/validators/_version.py +1 -0
  9. bagof_validators-0.1/src/bagof/validators/base.py +335 -0
  10. bagof_validators-0.1/src/bagof/validators/collections.py +378 -0
  11. bagof_validators-0.1/src/bagof/validators/common.py +224 -0
  12. bagof_validators-0.1/src/bagof/validators/exceptions.py +33 -0
  13. bagof_validators-0.1/src/bagof/validators/misc.py +78 -0
  14. bagof_validators-0.1/src/bagof/validators/numbers.py +246 -0
  15. bagof_validators-0.1/src/bagof/validators/numpy.py +28 -0
  16. bagof_validators-0.1/src/bagof/validators/py.typed +0 -0
  17. bagof_validators-0.1/src/bagof/validators/strings.py +53 -0
  18. bagof_validators-0.1/src/bagof_validators.egg-info/PKG-INFO +52 -0
  19. bagof_validators-0.1/src/bagof_validators.egg-info/SOURCES.txt +31 -0
  20. bagof_validators-0.1/src/bagof_validators.egg-info/dependency_links.txt +1 -0
  21. bagof_validators-0.1/src/bagof_validators.egg-info/requires.txt +8 -0
  22. bagof_validators-0.1/src/bagof_validators.egg-info/top_level.txt +1 -0
  23. bagof_validators-0.1/tests/test_base.py +378 -0
  24. bagof_validators-0.1/tests/test_collections.py +618 -0
  25. bagof_validators-0.1/tests/test_common.py +613 -0
  26. bagof_validators-0.1/tests/test_compat.py +58 -0
  27. bagof_validators-0.1/tests/test_exceptions.py +66 -0
  28. bagof_validators-0.1/tests/test_import.py +9 -0
  29. bagof_validators-0.1/tests/test_misc.py +105 -0
  30. bagof_validators-0.1/tests/test_numbers.py +308 -0
  31. bagof_validators-0.1/tests/test_numpy.py +66 -0
  32. bagof_validators-0.1/tests/test_strings.py +110 -0
  33. bagof_validators-0.1/tests/test_typevars.py +187 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 bagofseeds
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,52 @@
1
+ Metadata-Version: 2.4
2
+ Name: bagof-validators
3
+ Version: 0.1
4
+ Summary: Hint-based runtime validators.
5
+ Author-email: Yael Balbastre <yael.balbastre@gmail.com>
6
+ Maintainer-email: Yael Balbastre <yael.balbastre@gmail.com>
7
+ License: MIT
8
+ Project-URL: Homepage, https://github.com/bagofseeds/bagof-validators
9
+ Project-URL: Documentation, https://github.com/bagofseeds/bagof-validators#readme
10
+ Project-URL: Issues, https://github.com/bagofseeds/bagof-validators/issues
11
+ Project-URL: Repository, https://github.com/bagofseeds/bagof-validators
12
+ Keywords: bagof,validators,python
13
+ Classifier: Development Status :: 1 - Planning
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.8
17
+ Classifier: Programming Language :: Python :: 3.9
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3 :: Only
22
+ Classifier: Typing :: Typed
23
+ Requires-Python: >=3.8
24
+ Description-Content-Type: text/markdown
25
+ License-File: LICENSE
26
+ Requires-Dist: typing_extensions>=4.13
27
+ Requires-Dist: bagof-hints~=0.1
28
+ Requires-Dist: bagof-core-magic~=0.1
29
+ Provides-Extra: test
30
+ Requires-Dist: pytest>=7; extra == "test"
31
+ Requires-Dist: pytest-cov>=4; extra == "test"
32
+ Requires-Dist: numpy; extra == "test"
33
+ Dynamic: license-file
34
+
35
+ # bagof-validators
36
+
37
+ Hint-based validators that operate at runtime.
38
+
39
+ ## Example
40
+
41
+ ```pycon
42
+ >>> from collections import abc
43
+ >>> from bagof.validators import get_validator
44
+ >>> validate = get_validator(abc.Sequence[int])
45
+ >>> validate([1, 2, 3])
46
+ >>> validate(1)
47
+ TypeValidationError: IsSequence(collections.abc.Sequence[int]): Not a valid instance.
48
+ |> value = 1
49
+ >>> validate(["a", "b", "c"])
50
+ ValueValidationError: IsSequence(collections.abc.Sequence[int]): Iterable's 0th element is not valid.
51
+ |> value = ['a', 'b', 'c']
52
+ ```
@@ -0,0 +1,18 @@
1
+ # bagof-validators
2
+
3
+ Hint-based validators that operate at runtime.
4
+
5
+ ## Example
6
+
7
+ ```pycon
8
+ >>> from collections import abc
9
+ >>> from bagof.validators import get_validator
10
+ >>> validate = get_validator(abc.Sequence[int])
11
+ >>> validate([1, 2, 3])
12
+ >>> validate(1)
13
+ TypeValidationError: IsSequence(collections.abc.Sequence[int]): Not a valid instance.
14
+ |> value = 1
15
+ >>> validate(["a", "b", "c"])
16
+ ValueValidationError: IsSequence(collections.abc.Sequence[int]): Iterable's 0th element is not valid.
17
+ |> value = ['a', 'b', 'c']
18
+ ```
@@ -0,0 +1,110 @@
1
+ [project]
2
+ name = "bagof-validators"
3
+ authors = [
4
+ {name = "Yael Balbastre", email = "yael.balbastre@gmail.com"},
5
+ ]
6
+ maintainers = [
7
+ {name = "Yael Balbastre", email = "yael.balbastre@gmail.com"},
8
+ ]
9
+ description = "Hint-based runtime validators."
10
+ readme = "README.md"
11
+ license = {text = "MIT"}
12
+ keywords = ["bagof", "validators", "python"]
13
+ classifiers = [
14
+ "Development Status :: 1 - Planning",
15
+ "Intended Audience :: Developers",
16
+ "Programming Language :: Python :: 3",
17
+ "Programming Language :: Python :: 3.8",
18
+ "Programming Language :: Python :: 3.9",
19
+ "Programming Language :: Python :: 3.10",
20
+ "Programming Language :: Python :: 3.11",
21
+ "Programming Language :: Python :: 3.12",
22
+ "Programming Language :: Python :: 3 :: Only",
23
+ "Typing :: Typed",
24
+ ]
25
+ dynamic = ["version"]
26
+ requires-python = ">=3.8"
27
+ dependencies = [
28
+ "typing_extensions >= 4.13",
29
+ "bagof-hints ~= 0.1",
30
+ "bagof-core-magic ~= 0.1"
31
+ ]
32
+
33
+ [project.urls]
34
+ Homepage = "https://github.com/bagofseeds/bagof-validators"
35
+ Documentation = "https://github.com/bagofseeds/bagof-validators#readme"
36
+ Issues = "https://github.com/bagofseeds/bagof-validators/issues"
37
+ Repository = "https://github.com/bagofseeds/bagof-validators"
38
+
39
+ [project.optional-dependencies]
40
+ test = [
41
+ "pytest >= 7",
42
+ "pytest-cov >= 4",
43
+ # Optional runtime dependency: the numpy validators are only tested
44
+ # when it is installed (see tests/test_numpy.py).
45
+ "numpy",
46
+ ]
47
+
48
+ [build-system]
49
+ requires = [
50
+ "setuptools >= 59.0",
51
+ "wheel >= 0.45.1",
52
+ "versioningit >= 1.0",
53
+ ]
54
+ build-backend = "setuptools.build_meta"
55
+
56
+ [tool.setuptools]
57
+ package-dir = {"" = "src"}
58
+
59
+ [tool.setuptools.packages.find]
60
+ where = ["src"]
61
+ include = ["bagof*"]
62
+ namespaces = true
63
+
64
+ [tool.setuptools.package-data]
65
+ "bagof.validators" = ["py.typed"]
66
+
67
+ [tool.versioningit]
68
+ default-version = "0+unknown"
69
+
70
+ [tool.versioningit.vcs]
71
+ default-tag = "0.1"
72
+
73
+ [tool.versioningit.format]
74
+ distance = "{base_version}+{distance}.{vcs}{rev}"
75
+ dirty = "{base_version}+{distance}.{vcs}{rev}.dirty"
76
+ distance-dirty = "{base_version}+{distance}.{vcs}{rev}.dirty"
77
+
78
+ [tool.versioningit.write]
79
+ file = "src/bagof/validators/_version.py"
80
+
81
+ [tool.pytest.ini_options]
82
+ testpaths = ["tests"]
83
+
84
+ [tool.coverage.run]
85
+ source = ["bagof.validators"]
86
+
87
+ [tool.coverage.report]
88
+ exclude_also = [
89
+ # Type-checking blocks are never executed at runtime.
90
+ "if tx.TYPE_CHECKING:",
91
+ "if tx.TYPE_CHECKING or",
92
+ ]
93
+
94
+ [tool.ruff]
95
+ line-length = 79
96
+ target-version = "py38"
97
+
98
+ [tool.ruff.lint]
99
+ select = ["ANN", "B", "E", "F", "I", "UP", "W"]
100
+ ignore = [
101
+ "ANN002", # Do not type *args
102
+ "ANN003", # Do not type *kwargs
103
+ "ANN401", # Allow typing with Any
104
+ "UP006", # Old-style collections hints (List instead of list)
105
+ "UP035", # Use `typing_extensions` instead of `typing`
106
+ ]
107
+
108
+ [tool.codespell]
109
+ check-filenames = true
110
+ skip = ".git,*.egg-info,build,dist,*.pdf,*.svg"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,75 @@
1
+ """
2
+ Automatic type-based validators.
3
+
4
+ Modules
5
+ -------
6
+ base
7
+ Base class for magic validators.
8
+ collections
9
+ Validators for collection types (list, tuple, dict, etc.).
10
+ common
11
+ Common validators (any, union, etc.).
12
+ exceptions
13
+ Exceptions raised by validators on validation error.
14
+ misc
15
+ Miscellaneous validators (forbidden values, etc.).
16
+ numbers
17
+ Validators for numeric types (int, float, etc.).
18
+ numpy
19
+ Validators for numpy types (dtype, etc.).
20
+ strings
21
+ Validators for string types (regex patterns, etc.).
22
+ """
23
+
24
+ __all__ = [
25
+ "__version__",
26
+ "base",
27
+ "collections",
28
+ "common",
29
+ "exceptions",
30
+ "misc",
31
+ "numbers",
32
+ "numpy",
33
+ "strings",
34
+ ]
35
+
36
+ try:
37
+ from ._version import __version__
38
+ except ImportError: # pragma: no cover
39
+ __version__ = "0+unknown"
40
+
41
+ from . import (
42
+ base,
43
+ collections,
44
+ common,
45
+ exceptions,
46
+ misc,
47
+ numbers,
48
+ numpy,
49
+ strings,
50
+ )
51
+ from .base import * # noqa: F401, F403
52
+ from .base import __all__ as __all_base
53
+ from .collections import * # noqa: F401, F403
54
+ from .collections import __all__ as __all_collections
55
+ from .common import * # noqa: F401, F403
56
+ from .common import __all__ as __all_common
57
+ from .exceptions import * # noqa: F401, F403
58
+ from .exceptions import __all__ as __all_exceptions
59
+ from .misc import * # noqa: F401, F403
60
+ from .misc import __all__ as __all_misc
61
+ from .numbers import * # noqa: F401, F403
62
+ from .numbers import __all__ as __all_numbers
63
+ from .numpy import * # noqa: F401, F403
64
+ from .numpy import __all__ as __all_numpy
65
+ from .strings import * # noqa: F401, F403
66
+ from .strings import __all__ as __all_strings
67
+
68
+ __all__ += __all_base
69
+ __all__ += __all_collections
70
+ __all__ += __all_common
71
+ __all__ += __all_exceptions
72
+ __all__ += __all_misc
73
+ __all__ += __all_numbers
74
+ __all__ += __all_numpy
75
+ __all__ += __all_strings
@@ -0,0 +1,35 @@
1
+ import typing_extensions as tx
2
+
3
+ # optional
4
+ if tx.TYPE_CHECKING:
5
+ from types import NoneType, UnionType
6
+
7
+ UNION_TYPES = (tx.Union, UnionType)
8
+
9
+ else:
10
+ try:
11
+ from types import NoneType
12
+ except ImportError: # pragma: no cover
13
+ NoneType = type(None) # type: ignore[assignment]
14
+
15
+ try:
16
+ from types import UnionType
17
+ UNION_TYPES = (tx.Union, UnionType)
18
+ except ImportError: # pragma: no cover
19
+ UnionType = tx.Union # type: ignore[assignment]
20
+ UNION_TYPES = (tx.Union,)
21
+
22
+ if tx.TYPE_CHECKING:
23
+ import numpy as np
24
+ import numpy.typing as npt
25
+
26
+ else:
27
+ try:
28
+ import numpy as np
29
+ except ImportError: # pragma: no cover
30
+ np = None # type: ignore[assignment]
31
+
32
+ try:
33
+ import numpy.typing as npt
34
+ except ImportError: # pragma: no cover
35
+ npt = None # type: ignore[assignment]
@@ -0,0 +1 @@
1
+ __version__ = "0.1"
@@ -0,0 +1,335 @@
1
+ """Base class for all validators."""
2
+
3
+ __all__ = [
4
+ "Validator",
5
+ "register_validator",
6
+ "get_validator",
7
+ "get_validator_class"
8
+ ]
9
+
10
+ # dependencies
11
+ import typing_extensions as tx # noqa: I001
12
+
13
+ # bags
14
+ from bagof.core.magic import (
15
+ UNSET,
16
+ MagicHint,
17
+ get_from_registry,
18
+ ishintstance,
19
+ safe_isinstance,
20
+ safe_issubclass,
21
+ )
22
+ from bagof.hints.typevars.co import TYPE, T
23
+
24
+ # locals
25
+ from .exceptions import (
26
+ TypeValidationError,
27
+ ValidationError,
28
+ ValueValidationError,
29
+ )
30
+
31
+ # typing
32
+
33
+ ClassDecorator: tx.TypeAlias = tx.Callable[[TYPE], TYPE]
34
+ """A class decorator (that takes a class and returns a class)."""
35
+
36
+ ValidatorRegistry = tx.Dict[tx.Hashable, tx.Type["Validator"]]
37
+ """A registry of validators, mapping type hints to validator classes."""
38
+
39
+ # constants
40
+ VALIDATORS: ValidatorRegistry = {}
41
+ """The global registry of validators."""
42
+
43
+
44
+ class ValidatorMetaclass(type(MagicHint)):
45
+ """Metaclass for all validators."""
46
+
47
+ def __new__(
48
+ metacls,
49
+ name: str,
50
+ bases: tx.Tuple[type, ...],
51
+ namespace: tx.Mapping[str, tx.Any],
52
+ **kwargs
53
+ ) -> tx.Self:
54
+ register = kwargs.pop("register", UNSET)
55
+ cls = super().__new__(metacls, name, bases, namespace, **kwargs)
56
+ if register is not UNSET:
57
+ if register is True:
58
+ register = (cls.DEFAULT,)
59
+ if not isinstance(register, tuple):
60
+ register = (register,)
61
+ Validator.register(cls, *register)
62
+ return cls
63
+
64
+
65
+ class Validator(MagicHint[T], metaclass=ValidatorMetaclass):
66
+ """
67
+ Base class for magic validators.
68
+
69
+ The default validator falls back to a rather crude heuristic to check
70
+ whether the type of the validated object is compatible with the type
71
+ hint.
72
+
73
+ !!! warning
74
+ This heuristic does not work well for generic types, so it is
75
+ recommended to implement a custom validator for generic types.
76
+ """
77
+
78
+ DEFAULT = tx.Any
79
+
80
+ def __init__(self, hint: tx.Any = UNSET, compose: bool = False) -> None:
81
+ """
82
+ Parameters
83
+ ----------
84
+ hint : Any, optional
85
+ The type hint to use for this magic object.
86
+ If not provided, the default hint for the class is used.
87
+ compose : bool
88
+ Whether to compose this validator with others, when they are
89
+ found in [`Annotated`][typing.Annotated] metadata.
90
+ """
91
+ super().__init__(hint)
92
+ self.compose = compose
93
+
94
+ def __call__(self, value: T) -> None:
95
+ """
96
+ Validate the given value.
97
+
98
+ Parameters
99
+ ----------
100
+ value : T
101
+ The value to validate.
102
+
103
+ Raises
104
+ ------
105
+ ValidationError
106
+ If the value is not valid for this validator.
107
+ """
108
+ if not ishintstance(value, self.hint):
109
+ raise self.type_error(value, "Not a valid instance.")
110
+
111
+ def error(
112
+ self, value: tx.Any, message: tx.Optional[str] = None, **kwargs
113
+ ) -> ValidationError:
114
+ """Return a [`ValidationError`][] with the given value and message."""
115
+ type = kwargs.pop("type", ValidationError)
116
+ type = {
117
+ "value": ValueValidationError,
118
+ "type": TypeValidationError
119
+ }.get(type, type)
120
+ kwargs.setdefault("this", self)
121
+ kwargs.setdefault("value", value)
122
+ if message is None:
123
+ message = "Invalid value."
124
+ return type(message, **kwargs)
125
+
126
+ def type_error(
127
+ self, value: tx.Any, message: tx.Optional[str] = None
128
+ ) -> TypeValidationError:
129
+ """Return a [`TypeValidationError`][] with the given value."""
130
+ if message is None:
131
+ message = f"Invalid value type: {type(value)}"
132
+ return self.error(value, message, type=TypeValidationError)
133
+
134
+ def value_error(
135
+ self, value: tx.Any, message: tx.Optional[str] = None
136
+ ) -> ValueValidationError:
137
+ """Return a [`ValueValidationError`][] with the given value."""
138
+ if message is None:
139
+ message = "Invalid value."
140
+ return self.error(value, message, type=ValueValidationError)
141
+
142
+ def _wrap_validator(self, validator: tx.Callable) -> tx.Callable:
143
+ """
144
+ Wrap a validator so that a plain [`TypeError`][] or
145
+ [`ValueError`][] raised inside it surfaces as a
146
+ [`ValidationError`][], with the original attached as its cause.
147
+
148
+ A [`ValidationError`][] raised by the wrapped validator passes
149
+ through unchanged.
150
+ """
151
+ return _trywrap_validator(validator, self.value_error)
152
+
153
+ @tx.overload
154
+ @staticmethod
155
+ def register(
156
+ validator: tx.Type["Validator"],
157
+ *hints: tx.Unpack[tx.Tuple[tx.Any, ...]],
158
+ registry: ValidatorRegistry = ...
159
+ ) -> tx.Type["Validator"]:
160
+ ...
161
+
162
+ @tx.overload
163
+ @staticmethod
164
+ def register(
165
+ *hints: tx.Unpack[tx.Tuple[tx.Any, ...]],
166
+ registry: ValidatorRegistry = ...
167
+ ) -> ClassDecorator:
168
+ ...
169
+
170
+ @staticmethod
171
+ def register(*hints, registry=VALIDATORS):
172
+ """
173
+ Decorator to register a validator class for one or more type hints.
174
+
175
+ Can be used as a bare decorator (its first argument is then the
176
+ validator class itself, as in the example below), or called with
177
+ one or more type hints to obtain a decorator factory instead.
178
+
179
+ !!! example
180
+ ```python
181
+ @Validator.register
182
+ class IntValidator(Validator[int]):
183
+
184
+ DEFAULT = int
185
+
186
+ def __call__(self, value: int) -> None:
187
+ try:
188
+ int(value)
189
+ except (TypeError, ValueError) as e:
190
+ raise self.type_error(value) from e
191
+ ```
192
+
193
+ Parameters
194
+ ----------
195
+ *hints
196
+ One or more type hints to register the validator class for.
197
+ Defaults to the validator class's `DEFAULT` hint if none are
198
+ given. When used as a bare decorator, the first "hint" is
199
+ actually the validator class to register.
200
+ registry : ValidatorRegistry
201
+ The registry to register the validator class in.
202
+ Defaults to the global registry.
203
+
204
+ Returns
205
+ -------
206
+ tx.Type["Validator"] | ClassDecorator
207
+ When used as a bare decorator, the same validator class, now
208
+ registered. Otherwise, a decorator that registers the
209
+ validator class it is applied to.
210
+
211
+ """
212
+ if hints and safe_issubclass(hints[0], Validator):
213
+ validator, *hints = hints
214
+ return Validator.register(*hints, registry=registry)(validator)
215
+
216
+ def decorator(cls: tx.Type[Validator]) -> tx.Type[Validator]:
217
+ hints_ = hints or (cls.DEFAULT,)
218
+ for hint in hints_:
219
+ registry[hint] = cls
220
+ return cls
221
+
222
+ return decorator
223
+
224
+ @staticmethod
225
+ def get(
226
+ hint: tx.Any,
227
+ registry: ValidatorRegistry = VALIDATORS,
228
+ fallback: tx.Optional[tx.Type["Validator"]] = UNSET
229
+ ) -> tx.Optional["Validator"]:
230
+ """
231
+ Get the best-matching conversion function for a given type hint.
232
+
233
+ The returned validator is a callable that returns `None` on success
234
+ and raises a [`ValidationError`][] on failure:
235
+
236
+ !!! example
237
+ ```pycon
238
+ >>> from bagof.validators import get_validator
239
+ >>> validate = get_validator(int)
240
+ >>> validate(3)
241
+ >>> validate("x")
242
+ TypeValidationError: IsNumber(<class 'int'>): Not a valid instance.
243
+ |> value = 'x'
244
+ ```
245
+
246
+ Parameters
247
+ ----------
248
+ hint
249
+ The type hint for which to get a validator.
250
+ registry : ValidatorRegistry
251
+ The registry to look up the validator in.
252
+ Defaults to the global registry.
253
+ fallback : tx.Optional[Type[Validator]]
254
+ The fallback validator class to use if no matching validator
255
+ is found. Defaults to [`Validator`][].
256
+ Pass `None` explicitly to get `None` instead of a fallback.
257
+
258
+ Returns
259
+ -------
260
+ tx.Optional[Validator]
261
+ The best-matching validator for the given type hint, or `None`
262
+ if no matching validator is found and no fallback is provided.
263
+ """
264
+ cls = Validator.get_class(hint, registry, fallback)
265
+ if cls is None:
266
+ return None
267
+ return cls(hint)
268
+
269
+ @staticmethod
270
+ def get_class(
271
+ hint: tx.Any,
272
+ registry: ValidatorRegistry = VALIDATORS,
273
+ fallback: tx.Optional[tx.Type["Validator"]] = UNSET
274
+ ) -> tx.Optional[tx.Type["Validator"]]:
275
+ """
276
+ Get the best-matching conversion class for a given type hint.
277
+
278
+ Parameters
279
+ ----------
280
+ hint
281
+ The type hint for which to get a validator.
282
+ registry : ValidatorRegistry
283
+ The registry to look up the validator in.
284
+ Defaults to the global registry.
285
+ fallback : tx.Optional[Type[Validator]]
286
+ The fallback validator class to use if no matching validator
287
+ is found. Defaults to [`Validator`][].
288
+ Pass `None` explicitly to get `None` instead of a fallback.
289
+
290
+ Returns
291
+ -------
292
+ tx.Optional[Type[Validator]]
293
+ The best-matching validator class for the given type hint,
294
+ or `None` if no matching validator is found and no fallback
295
+ is provided.
296
+ """
297
+ if fallback is UNSET:
298
+ fallback = Validator
299
+ return get_from_registry(hint, registry) or fallback
300
+
301
+
302
+ register_validator = Validator.register
303
+ """Backward-compatible alias for [`Validator.register`][]"""
304
+
305
+ get_validator = Validator.get
306
+ """Backward-compatible alias for [`Validator.get`][]"""
307
+
308
+ get_validator_class = Validator.get_class
309
+ """Backward-compatible alias for [`Validator.get_class`][]"""
310
+
311
+
312
+ def _trywrap_validator(
313
+ validator: tx.Callable[[T], None],
314
+ error: tx.Union[Exception, tx.Type[Exception], tx.Callable[[T], Exception]]
315
+ ) -> tx.Callable[[T], None]:
316
+ """
317
+ Wrap a validator to catch errors and raise a [`ValidationError`][] instead.
318
+ """
319
+ def wrapped(value: T) -> None:
320
+ try:
321
+ return validator(value)
322
+ except ValidationError:
323
+ # Already the right kind of error: re-raise it untouched, so
324
+ # its specific type and its `causes` survive. `TypeError` and
325
+ # `ValueError` are the base classes of `TypeValidationError`
326
+ # and `ValueValidationError`, so without this the `except`
327
+ # below would swallow and downgrade every real failure.
328
+ raise
329
+ except (TypeError, ValueError) as e:
330
+ _error = error
331
+ if not safe_isinstance(_error, BaseException):
332
+ # Either an exception class or a factory (e.g. `value_error`).
333
+ _error = _error(value)
334
+ raise _error from e
335
+ return wrapped