Beispiel #1
0
def test_is_component_class():
    assert not utils.is_component_class(5)  # type: ignore
    assert not utils.is_component_class(lambda: "foo")  # type: ignore

    class A:
        pass

    assert not utils.is_component_class(A)

    @component
    class B:
        pass

    assert utils.is_component_class(B)
    assert not utils.is_component_class(B())
    def __init__(self, component_class: Type[_ComponentType], **kwargs):
        if utils.is_component_instance(component_class):
            raise TypeError(
                "`PartialComponent` must be passed component classes, not component "
                f"instances. Received: {repr(component_class)}")
        if not utils.is_component_class(component_class):
            raise TypeError(
                "The class passed to `PartialComponent` must be a component class. "
                f"Received: {component_class}.")
        if len(kwargs) == 0:
            raise TypeError(
                "`PartialComponent` must receive at least one keyword argument."
            )

        lazy_kwargs = {}
        for name, value in kwargs.items():
            if name not in component_class.__component_fields__:
                raise TypeError(
                    f"Keyword argument '{name}' passed to `PartialComponent` does "
                    "not correspond to any field of component class "
                    f"'{component_class.__name__}'.")
            if utils.is_immutable(value):
                lazy_kwargs[name] = utils.wrap_in_callable(value)
            else:
                if not inspect.isfunction(value):
                    raise _kwargs_error
                if len(inspect.signature(value).parameters) == 0:
                    lazy_kwargs[name] = value
                else:
                    raise _kwargs_error

        self._component_class = component_class
        self._lazy_kwargs = lazy_kwargs
Beispiel #3
0
    def __init__(
        self,
        default: Union[utils.Missing, F, PartialComponent[F]] = utils.missing,
        *,  # `allow_missing` must be a keyword argument.
        allow_missing: bool = False,
        **kwargs,
    ):
        if allow_missing and default is not utils.missing:
            raise ValueError(
                "If a `Field` has `allow_missing=True`, no default can be provided."
            )

        if default is utils.missing:
            if len(kwargs) > 0:
                raise TypeError(
                    "Keyword arguments can only be passed to `ComponentField` if "
                    "a default component class is also passed."
                )
        elif isinstance(default, PartialComponent) or utils.is_component_class(default):
            if len(kwargs) > 0:
                default = PartialComponent(default, **kwargs)
        elif utils.is_component_instance(default):
            raise TypeError(
                "The `default` passed to `ComponentField` must be a component class, "
                f"not a component instance. Received: {repr(default)}."
            )
        else:
            raise TypeError(
                "The `default` passed to `ComponentField` must be either a component "
                "class or a `PartialComponent`."
            )

        self.name = utils.missing
        self.allow_missing = allow_missing
        self.host_component_class = utils.missing
        self.type = utils.missing
        self._registered = False
        self._default = default
Beispiel #4
0
def component(cls: Type):
    """A decorator which turns a class into a Zookeeper component."""

    if not inspect.isclass(cls):
        raise TypeError("Only classes can be decorated with @component.")

    if inspect.isabstract(cls):
        raise TypeError(
            "Abstract classes cannot be decorated with @component.")

    if utils.is_component_class(cls):
        raise TypeError(
            f"The class {cls.__name__} is already a component; the @component decorator "
            "cannot be applied again.")

    if cls.__init__ not in (object.__init__, __component_init__):
        # A component class could have `__component_init__` as its init method
        # if it inherits from a component.
        raise TypeError(
            "Component classes must not define a custom `__init__` method.")
    cls.__init__ = __component_init__

    if hasattr(cls, "__post_configure__"):
        if not callable(cls.__post_configure__):
            raise TypeError(
                "The `__post_configure__` attribute of a @component class must be a "
                "method.")
        call_args = inspect.signature(cls.__post_configure__).parameters
        if len(call_args) > 1 or len(
                call_args) == 1 and "self" not in call_args:
            raise TypeError(
                "The `__post_configure__` method of a @component class must take no "
                f"arguments except `self`, but `{cls.__name__}.__post_configure__` "
                f"accepts arguments {tuple(name for name in call_args)}.")

    # Populate `__component_fields__` with all fields defined on this class and
    # all superclasses. We have to go through the MRO chain and collect them in
    # reverse order so that they are correctly overriden.
    fields = {}
    for base_class in reversed(inspect.getmro(cls)):
        for name, value in base_class.__dict__.items():
            if isinstance(value, Field):
                fields[name] = value

    if len(fields) == 0:
        utils.warn(f"Component {cls.__name__} has no defined fields.")

    # Throw an error if there is a field defined on a superclass that has been
    # overriden with a non-Field value.
    for name in dir(cls):
        if name in fields and not isinstance(getattr(cls, name), Field):
            super_class = fields[name].host_component_class
            raise ValueError(
                f"Field '{name}' is defined on super-class {super_class.__name__}. "
                f"In subclass {cls.__name__}, '{name}' has been overriden with value: "
                f"{getattr(cls, name)}.\n\n"
                f"If you wish to change the default value of field '{name}' in a "
                f"subclass of {super_class.__name__}, please wrap the new default "
                "value in a new `Field` instance.")

    cls.__component_fields__ = fields

    # Override class methods to correctly interact with component fields.
    _wrap_getattribute(cls)
    _wrap_setattr(cls)
    _wrap_delattr(cls)
    _wrap_dir(cls)

    # Components should have nice `__str__` and `__repr__` methods.
    cls.__str__ = __component_str__
    cls.__repr__ = __component_repr__

    # These will be overriden during configuration.
    cls.__component_name__ = cls.__name__
    cls.__component_parent__ = None
    cls.__component_configured__ = False

    return cls