|
@@ -1,13 +1,17 @@
|
|
-from typing import Any, Callable, Dict, Optional, Union
|
|
|
|
|
|
+from typing import Any, Awaitable, Callable, Dict, Optional, Union
|
|
|
|
|
|
from typing_extensions import Self
|
|
from typing_extensions import Self
|
|
|
|
|
|
|
|
+from ... import background_tasks, helpers
|
|
from .value_element import ValueElement
|
|
from .value_element import ValueElement
|
|
|
|
|
|
|
|
+ValidationFunction = Callable[[Any], Union[Optional[str], Awaitable[Optional[str]]]]
|
|
|
|
+ValidationDict = Dict[str, Callable[[Any], bool]]
|
|
|
|
+
|
|
|
|
|
|
class ValidationElement(ValueElement):
|
|
class ValidationElement(ValueElement):
|
|
|
|
|
|
- def __init__(self, validation: Optional[Union[Callable[..., Optional[str]], Dict[str, Callable[..., bool]]]], **kwargs: Any) -> None:
|
|
|
|
|
|
+ def __init__(self, validation: Optional[Union[ValidationFunction, ValidationDict]], **kwargs: Any) -> None:
|
|
self._validation = validation
|
|
self._validation = validation
|
|
self._auto_validation = True
|
|
self._auto_validation = True
|
|
self._error: Optional[str] = None
|
|
self._error: Optional[str] = None
|
|
@@ -15,18 +19,18 @@ class ValidationElement(ValueElement):
|
|
self._props['error'] = None if validation is None else False # NOTE: reserve bottom space for error message
|
|
self._props['error'] = None if validation is None else False # NOTE: reserve bottom space for error message
|
|
|
|
|
|
@property
|
|
@property
|
|
- def validation(self) -> Optional[Union[Callable[..., Optional[str]], Dict[str, Callable[..., bool]]]]:
|
|
|
|
|
|
+ def validation(self) -> Optional[Union[ValidationFunction, ValidationDict]]:
|
|
"""The validation function or dictionary of validation functions."""
|
|
"""The validation function or dictionary of validation functions."""
|
|
return self._validation
|
|
return self._validation
|
|
|
|
|
|
@validation.setter
|
|
@validation.setter
|
|
- def validation(self, validation: Optional[Union[Callable[..., Optional[str]], Dict[str, Callable[..., bool]]]]) -> None:
|
|
|
|
|
|
+ def validation(self, validation: Optional[Union[ValidationFunction, ValidationDict]]) -> None:
|
|
"""Sets the validation function or dictionary of validation functions.
|
|
"""Sets the validation function or dictionary of validation functions.
|
|
|
|
|
|
:param validation: validation function or dictionary of validation functions (``None`` to disable validation)
|
|
:param validation: validation function or dictionary of validation functions (``None`` to disable validation)
|
|
"""
|
|
"""
|
|
self._validation = validation
|
|
self._validation = validation
|
|
- self.validate()
|
|
|
|
|
|
+ self.validate(return_result=False)
|
|
|
|
|
|
@property
|
|
@property
|
|
def error(self) -> Optional[str]:
|
|
def error(self) -> Optional[str]:
|
|
@@ -47,13 +51,30 @@ class ValidationElement(ValueElement):
|
|
self._props['error-message'] = error
|
|
self._props['error-message'] = error
|
|
self.update()
|
|
self.update()
|
|
|
|
|
|
- def validate(self) -> bool:
|
|
|
|
|
|
+ def validate(self, *, return_result: bool = True) -> bool:
|
|
"""Validate the current value and set the error message if necessary.
|
|
"""Validate the current value and set the error message if necessary.
|
|
|
|
|
|
- :return: True if the value is valid, False otherwise
|
|
|
|
|
|
+ For async validation functions, ``return_result`` must be set to ``False`` and the return value will be ``True``,
|
|
|
|
+ independently of the validation result which is evaluated in the background.
|
|
|
|
+
|
|
|
|
+ :param return_result: whether to return the result of the validation (default: ``True``)
|
|
|
|
+ :return: whether the validation was successful (always ``True`` for async validation functions)
|
|
"""
|
|
"""
|
|
|
|
+ if helpers.is_coroutine_function(self._validation):
|
|
|
|
+ async def await_error():
|
|
|
|
+ assert callable(self._validation)
|
|
|
|
+ result = self._validation(self.value)
|
|
|
|
+ assert isinstance(result, Awaitable)
|
|
|
|
+ self.error = await result
|
|
|
|
+ if return_result:
|
|
|
|
+ raise NotImplementedError('The validate method cannot return results for async validation functions.')
|
|
|
|
+ background_tasks.create(await_error())
|
|
|
|
+ return True
|
|
|
|
+
|
|
if callable(self._validation):
|
|
if callable(self._validation):
|
|
- self.error = self._validation(self.value)
|
|
|
|
|
|
+ result = self._validation(self.value)
|
|
|
|
+ assert not isinstance(result, Awaitable)
|
|
|
|
+ self.error = result
|
|
return self.error is None
|
|
return self.error is None
|
|
|
|
|
|
if isinstance(self._validation, dict):
|
|
if isinstance(self._validation, dict):
|
|
@@ -73,4 +94,4 @@ class ValidationElement(ValueElement):
|
|
def _handle_value_change(self, value: Any) -> None:
|
|
def _handle_value_change(self, value: Any) -> None:
|
|
super()._handle_value_change(value)
|
|
super()._handle_value_change(value)
|
|
if self._auto_validation:
|
|
if self._auto_validation:
|
|
- self.validate()
|
|
|
|
|
|
+ self.validate(return_result=False)
|