Skip to content

typing

frequenz.core.typing ¤

Type hints and utility functions for type checking and types.

For now this module only provides a decorator to disable the __init__ constructor of a class, to force the use of a factory method to create instances. See disable_init for more information.

Attributes¤

frequenz.core.typing.TypeT module-attribute ¤

TypeT = TypeVar('TypeT', bound=type)

A type variable that is bound to a type.

Functions¤

frequenz.core.typing.disable_init ¤

disable_init(
    cls: None = None, *, error: Exception | None = None
) -> Callable[[TypeT], TypeT]
disable_init(cls: TypeT) -> TypeT
disable_init(
    cls: TypeT | None = None,
    *,
    error: Exception | None = None
) -> TypeT | Callable[[TypeT], TypeT]

Disable the __init__ constructor of a class.

This decorator can be used to disable the __init__ constructor of a class. It is intended to be used with classes that don't provide a default constructor and require the use of a factory method to create instances.

When marking a class with this decorator, the class cannot be even declared with a __init__ method, as it will raise a TypeError when the class is created, as soon as the class is parsed by the Python interpreter. It will also raise a TypeError when the __init__ method is called.

To create an instance you must provide a factory method, using __new__.

Warning

This decorator will use a custom metaclass to disable the __init__ constructor of the class, so if your class already uses a custom metaclass, you should be aware of potential conflicts.

Basic example defining a class with a factory method

To be able to type hint the class correctly, you can declare the instance attributes in the class body, and then use a factory method to create instances.

from typing import Self

@disable_init
class MyClass:
    value: int

    @classmethod
    def new(cls, value: int = 1) -> Self:
        self = cls.__new__(cls)
        self.value = value
        return self

instance = MyClass.new()

# Calling the default constructor (__init__) will raise a TypeError
try:
    instance = MyClass()
except TypeError as e:
    print(e)
Class wrongly providing an __init__ constructor
try:
    @disable_init
    class MyClass:
        def __init__(self) -> None:
            pass
except TypeError as e:
    assert isinstance(e, TypeError)
    print(e)
Using a custom error message when the default constructor is called
from typing import Self

class NoInitError(TypeError):
    def __init__(self) -> None:
        super().__init__("Please create instances of MyClass using MyClass.new()")

@disable_init(error=NoInitError())
class MyClass:
    @classmethod
    def new(cls) -> Self:
        return cls.__new__(cls)

try:
    instance = MyClass()
except NoInitError as e:
    assert str(e) == "Please create instances of MyClass using MyClass.new()"
    print(e)
PARAMETER DESCRIPTION
cls

The class to be decorated.

TYPE: TypeT | None DEFAULT: None

error

The error to raise if init is called, if None a default TypeError will be raised.

TYPE: Exception | None DEFAULT: None

RETURNS DESCRIPTION
TypeT | Callable[[TypeT], TypeT]

A decorator that disables the __init__ constructor of cls.

Source code in frequenz/core/typing.py
def disable_init(
    cls: TypeT | None = None,
    *,
    error: Exception | None = None,
) -> TypeT | Callable[[TypeT], TypeT]:
    """Disable the `__init__` constructor of a class.

    This decorator can be used to disable the `__init__` constructor of a class. It is
    intended to be used with classes that don't provide a default constructor and
    require the use of a factory method to create instances.

    When marking a class with this decorator, the class cannot be even declared with a
    `__init__` method, as it will raise a `TypeError` when the class is created, as soon
    as the class is parsed by the Python interpreter. It will also raise a `TypeError`
    when the `__init__` method is called.

    To create an instance you must provide a factory method, using `__new__`.

    Warning:
        This decorator will use a custom metaclass to disable the `__init__` constructor
        of the class, so if your class already uses a custom metaclass, you should be
        aware of potential conflicts.

    Example: Basic example defining a class with a factory method
        To be able to type hint the class correctly, you can declare the instance
        attributes in the class body, and then use a factory method to create instances.

        ```python
        from typing import Self

        @disable_init
        class MyClass:
            value: int

            @classmethod
            def new(cls, value: int = 1) -> Self:
                self = cls.__new__(cls)
                self.value = value
                return self

        instance = MyClass.new()

        # Calling the default constructor (__init__) will raise a TypeError
        try:
            instance = MyClass()
        except TypeError as e:
            print(e)
        ```

    Example: Class wrongly providing an `__init__` constructor
        ```python
        try:
            @disable_init
            class MyClass:
                def __init__(self) -> None:
                    pass
        except TypeError as e:
            assert isinstance(e, TypeError)
            print(e)
        ```

    Example: Using a custom error message when the default constructor is called
        ```python
        from typing import Self

        class NoInitError(TypeError):
            def __init__(self) -> None:
                super().__init__("Please create instances of MyClass using MyClass.new()")

        @disable_init(error=NoInitError())
        class MyClass:
            @classmethod
            def new(cls) -> Self:
                return cls.__new__(cls)

        try:
            instance = MyClass()
        except NoInitError as e:
            assert str(e) == "Please create instances of MyClass using MyClass.new()"
            print(e)
        ```

    Args:
        cls: The class to be decorated.
        error: The error to raise if __init__ is called, if `None` a default
            [TypeError][] will be raised.

    Returns:
        A decorator that disables the `__init__` constructor of `cls`.
    """

    def decorator(inner_cls: TypeT) -> TypeT:
        return cast(
            TypeT,
            _NoInitConstructibleMeta(
                inner_cls.__name__,
                inner_cls.__bases__,
                dict(inner_cls.__dict__),
                no_init_constructible_error=error,
            ),
        )

    if cls is None:
        return decorator
    return decorator(cls)