HomeDashboard/.venv/lib/python3.12/site-packages/nicegui/elements/mixins/validation_element.py
2026-01-03 14:54:18 +01:00

100 lines
3.9 KiB
Python

from collections.abc import Awaitable
from typing import Any, Callable, Optional, Union
from typing_extensions import Self
from ... import background_tasks, helpers
from .value_element import ValueElement
ValidationFunction = Callable[[Any], Union[Optional[str], Awaitable[Optional[str]]]]
ValidationDict = dict[str, Callable[[Any], bool]]
class ValidationElement(ValueElement):
def __init__(self, validation: Optional[Union[ValidationFunction, ValidationDict]], **kwargs: Any) -> None:
self._validation = validation
self._auto_validation = True
self._error: Optional[str] = None
super().__init__(**kwargs)
self._props['error'] = None if validation is None else False # NOTE: reserve bottom space for error message
@property
def validation(self) -> Optional[Union[ValidationFunction, ValidationDict]]:
"""The validation function or dictionary of validation functions."""
return self._validation
@validation.setter
def validation(self, validation: Optional[Union[ValidationFunction, ValidationDict]]) -> None:
"""Sets the validation function or dictionary of validation functions.
:param validation: validation function or dictionary of validation functions (``None`` to disable validation)
"""
self._validation = validation
self.validate(return_result=False)
@property
def error(self) -> Optional[str]:
"""The latest error message from the validation functions."""
return self._error
@error.setter
def error(self, error: Optional[str]) -> None:
"""Sets the error message.
:param error: The optional error message
"""
new_error_prop = None if self.validation is None else (error is not None)
if self._error == error and self._props['error'] == new_error_prop:
return
self._error = error
self._props['error'] = new_error_prop
self._props['error-message'] = error
def validate(self, *, return_result: bool = True) -> bool:
"""Validate the current value and set the error message if necessary.
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.
*Updated in version 2.7.0: Added support for async validation functions.*
: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(), name=f'validate {self.id}')
return True
if callable(self._validation):
result = self._validation(self.value)
assert not isinstance(result, Awaitable)
self.error = result
return self.error is None
if isinstance(self._validation, dict):
for message, check in self._validation.items():
if not check(self.value):
self.error = message
return False
self.error = None
return True
def without_auto_validation(self) -> Self:
"""Disable automatic validation on value change."""
self._auto_validation = False
return self
def _handle_value_change(self, value: Any) -> None:
super()._handle_value_change(value)
if self._auto_validation:
self.validate(return_result=False)