diff --git a/CHANGES/1668.feature.rst b/CHANGES/1668.feature.rst new file mode 100644 index 00000000..629cfd36 --- /dev/null +++ b/CHANGES/1668.feature.rst @@ -0,0 +1 @@ +Feature: Make CallbackData prefix optional and default to class name diff --git a/aiogram/filters/callback_data.py b/aiogram/filters/callback_data.py index e504d50b..3cd1c369 100644 --- a/aiogram/filters/callback_data.py +++ b/aiogram/filters/callback_data.py @@ -47,24 +47,26 @@ class CallbackData(BaseModel): This class should be used as super-class of user-defined callbacks. - The class-keyword :code:`prefix` is required to define prefix - and also the argument :code:`sep` can be passed to define separator (default is :code:`:`). + An optional class-keyword :code:`prefix` can be passed to define prefix. + If no prefix is provided, the class name will be used as the prefix. + + An optional argument :code:`sep` can be passed to define the separator + (default is :code:`:`). """ if TYPE_CHECKING: __separator__: ClassVar[str] """Data separator (default is :code:`:`)""" __prefix__: ClassVar[str] - """Callback prefix""" + """Callback prefix (default is class name)""" def __init_subclass__(cls, **kwargs: Any) -> None: - if "prefix" not in kwargs: - raise ValueError( - f"prefix required, usage example: " - f"`class {cls.__name__}(CallbackData, prefix='my_callback'): ...`" - ) + # If no prefix is provided explicitly, default to the class name + prefix = kwargs.pop("prefix", None) + if prefix is None: + prefix = cls.__name__ cls.__separator__ = kwargs.pop("sep", ":") - cls.__prefix__ = kwargs.pop("prefix") + cls.__prefix__ = prefix if cls.__separator__ in cls.__prefix__: raise ValueError( f"Separator symbol {cls.__separator__!r} can not be used " diff --git a/tests/test_filters/test_callback_data.py b/tests/test_filters/test_callback_data.py index 1bc50cae..e0e9b1f3 100644 --- a/tests/test_filters/test_callback_data.py +++ b/tests/test_filters/test_callback_data.py @@ -28,13 +28,18 @@ class MyCallback(CallbackData, prefix="test"): class TestCallbackData: - def test_init_subclass_prefix_required(self): - assert MyCallback.__prefix__ == "test" + def test_init_subclass_prefix_optional(self): + # Case 1: Explicitly provided prefix + class ExplicitCallbackData(CallbackData, prefix="explicit"): + pass - with pytest.raises(ValueError, match="prefix required.+"): + assert ExplicitCallbackData.__prefix__ == "explicit" - class MyInvalidCallback(CallbackData): - pass + # Case 2: No prefix provided; should default to class name + class DefaultCallbackData(CallbackData): + pass + + assert DefaultCallbackData.__prefix__ == "DefaultCallbackData" def test_init_subclass_sep_validation(self): assert MyCallback.__separator__ == ":"