"""Validators used in fields and models."""
from __future__ import annotations
import asyncio
import enum
import typing
from . import tutils, utility
__all__ = ["Order", "RootValidator", "Validator", "root_validator", "validator"]
T = typing.TypeVar("T")
[docs]class Order(enum.IntEnum):
"""The order of a validator."""
def _generate_next_value_(self, start: int, count: int, last_values: typing.Sequence[object]) -> int:
return count * 10
[docs] INITIAL_ROOT = enum.auto()
"""Root Validator acting upon the completely raw data."""
"""Root Validator acting upon the data with converted names."""
[docs] VALIDATOR = enum.auto()
"""Validator acting upon a field before annotation conversion."""
[docs] ANNOTATION = enum.auto()
"""Annotation Validator. Not meant to be defined by the user."""
[docs] POST_VALIDATOR = enum.auto()
"""Validator acting upon a field after annotation conversion."""
[docs] FINAL_ROOT = enum.auto()
"""Root Validator acting upon the data after individual field conversion."""
class BaseValidator(utility.Representation):
"""Base class for validators."""
__slots__ = ("callback", "order", "bound")
callback: tutils.AnyCallable
order: int
bound: bool
def __init__(self, callback: tutils.AnyCallable, *, order: int) -> None:
"""Initialize a validator.
Validators are unbound by default.
"""
self.callback = callback
self.order = order
self.bound = False
async def __call__(self, model: object, value: object) -> typing.Any:
"""Call the validator and optionally give it a model."""
return await self.asynchronous(model, value)
@utility.as_universal_method
def asynchronous(self, model: object, value: object) -> typing.Any:
"""Call the validator and optionally give it a model."""
if self.bound:
return self.callback(model, value)
else:
return self.callback(value)
def synchronous(self, model: object, value: object) -> typing.Any:
"""Call the validator and optionally give it a model."""
return self.asynchronous.synchronous(model, value)
@property
def isasync(self) -> bool:
"""Whether the callback returns an awaitable."""
return asyncio.iscoroutinefunction(self.callback)
@property
def _is_coroutine(self) -> object:
"""Helper attribute for asyncio.iscoroutinefunction."""
if self.isasync:
return getattr(asyncio.coroutines, "_is_coroutine")
else:
return None
[docs]class Validator(BaseValidator):
"""Basic validator for a single value."""
__slots__ = ("_fields",)
_fields: typing.Sequence[str]
def __init__(self, callback: tutils.AnyCallable, *, order: int = Order.VALIDATOR) -> None:
"""Initialize a standard field validator. Order cannot have ROOT."""
self._fields = ()
super().__init__(callback, order=order)
[docs]class RootValidator(BaseValidator):
"""Root validator for an entire model."""
__slots__ = ()
def __init__(self, callback: tutils.AnyCallable, *, order: int = Order.INITIAL_ROOT) -> None:
"""Initialize a root validator. Order must have ROOT."""
super().__init__(callback, order=order)
async def __call__(self, model: object, values: tutils.JSONMapping) -> tutils.JSONMapping:
"""Call the validator with its dict and optionally give it a model.
May return an Awaitable if the callback is async.
"""
return await super().__call__(model, values)
[docs]def validator(*fields: str, order: int = Order.VALIDATOR) -> tutils.DecoratorCallable[Validator]:
"""Create a validator for one or more fields."""
def decorator(callback: tutils.AnyCallable) -> Validator:
validator = Validator(callback, order=order)
validator._fields = fields
validator.bound = True
return validator
return decorator
[docs]def root_validator(order: int = Order.ROOT) -> tutils.DecoratorCallable[RootValidator]:
"""Create a root validator."""
def decorator(callback: tutils.AnyCallable) -> RootValidator:
validator = RootValidator(callback, order=order)
validator.bound = True
return validator
return decorator