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
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
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