"""Provides classes to represent required fields for key/value pairs that must
be presenting in the `settings.ACTION_TRIGGERS` dictionary for a given action.
"""
import typing as _t
from abc import ABC, abstractmethod, abstractproperty
[docs]
class RequiredFieldBase(ABC):
"""Represents a required field for the connection details and parameters
that must be provided by the user.
"""
def __init__(self, field: str, *args, **kwargs) -> None:
self.field = field
self.args = args
self.kwargs = kwargs
self._validate_init_params()
def __str__(self) -> str:
return self.key_repr
def __repr__(self) -> str:
return f"{self.__class__.__name__}({self.key_repr!r})"
[docs]
@abstractmethod
def check(self, context: dict) -> bool:
"""Check if the required field is present in the context.
:param context: The context to check.
:return: True if the field is present, False otherwise.
"""
@abstractproperty
def error_msg(self) -> str:
"""The error message to display if the required field is not
present.
"""
def _validate_init_params(self) -> None:
"""Validate the initialization parameters.
:raises ValueError: If there is an issue with the initialisation
parameters.
"""
@property
def key_repr(self) -> str:
"""The key representation of the field."""
return self.field
[docs]
class HasField(RequiredFieldBase):
"""Represents a required field that must be present in the context."""
[docs]
def check_exists(self, context: dict) -> bool:
"""Check if the field is present in the context.
:param context: The context to check.
:return: True if the field is present, False otherwise.
"""
if self.field in context.keys():
return True
else:
self._error_msg = f"The field '{self.field}' must be provided."
return False
[docs]
def check_type_from_args(self, context: dict) -> bool:
"""Check if the field type is provided in the first positional
argument, and if so, check if the field is of that type.
:param context: The context to check.
:return: True if the field is of the specified type, False otherwise.
:raises ValueError: If the first positional argument is not a type.
"""
if not self.args:
return True
arg_0 = self.args[0]
if not isinstance(context[self.field], arg_0):
self._error_msg = (
f"The field '{self.field}' must be of type {arg_0.__name__}."
)
return False
return True
[docs]
def check_type_from_kwargs(self, context: dict) -> bool:
"""Check if the field type is provided in the `type` keyword argument,
and if so, check if the field is of that type.
:param context: The context to check.
:return: True if the field is of the specified type, False otherwise.
"""
if "type" not in self.kwargs:
return True
arg_0 = self.kwargs["type"]
if not isinstance(context[self.field], arg_0):
self._error_msg = (
f"The field '{self.field}' must be of type {arg_0.__name__}."
)
return False
return True
[docs]
def check(self, context: dict) -> bool:
"""Check if the field is present in the context.
:param context: The context to check.
:return: True if the field is present, False otherwise.
"""
checks = (
self.check_exists,
self.check_type_from_args,
self.check_type_from_kwargs,
)
return all(check(context) for check in checks)
@property
def error_msg(self) -> str:
"""The error message to display if the field is not present."""
return self._error_msg
[docs]
class HasAtLeastOneOffField(RequiredFieldBase):
"""Represents a required field that requires at least one of the fields to
be present in the context.
"""
def __init__(self, field: str = "", *args, **kwargs) -> None:
super().__init__(field, *args, **kwargs)
def _validate_init_params(self) -> None:
if self.field:
raise ValueError(
"'field' parameter not supported for this class. Use "
"'fields' keyword argument instead."
)
self.fields = self.kwargs.get("fields")
if not self.fields or not isinstance(self.fields, _t.Sequence):
raise ValueError(
"'fields' keyword argument must be a non-empty sequence."
)
[docs]
def check(self, context: dict) -> bool:
"""Check if at least one of the fields is present in the context.
:param context: The context to check.
:return: True if at least one of the fields is present,
False otherwise.
"""
return bool(
set(_t.cast(_t.Sequence, self.fields)).intersection(context.keys())
)
@property
def error_msg(self) -> str:
"""The error message to display if at least one of the fields is not
present.
"""
return f"At least one of the fields {self.fields} must be provided."
@property
def key_repr(self) -> str:
"""The key representation of the field."""
return ", ".join(_t.cast(_t.Sequence, self.fields))