# Copyright 2017 Camptocamp SA
# Copyright 2017 Odoo
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html)

"""

Core
====

Core classes for the components.
The most common classes used publicly are:

* :class:`Component`
* :class:`AbstractComponent`
* :class:`WorkContext`

"""

import logging
import operator
from collections import OrderedDict, defaultdict

from odoo import models
from odoo.tools.misc import LastOrderedSet, OrderedSet

from .exception import NoComponentError, RegistryNotReadyError, SeveralComponentError

_logger = logging.getLogger(__name__)

try:
    from cachetools import LRUCache, cachedmethod
except ImportError:
    _logger.debug("Cannot import 'cachetools'.")


# The Cache size represents the number of items, so the number
# of components (include abstract components) we will keep in the LRU
# cache. We would need stats to know what is the average but this is a bit
# early.
DEFAULT_CACHE_SIZE = 512


# this is duplicated from odoo.models.MetaModel._get_addon_name() which we
# unfortunately can't use because it's an instance method and should have been
# a @staticmethod
def _get_addon_name(full_name):
    # The (Odoo) module name can be in the ``odoo.addons`` namespace
    # or not. For instance, module ``sale`` can be imported as
    # ``odoo.addons.sale`` (the right way) or ``sale`` (for backward
    # compatibility).
    module_parts = full_name.split(".")
    if len(module_parts) > 2 and module_parts[:2] == ["odoo", "addons"]:
        addon_name = full_name.split(".")[2]
    else:
        addon_name = full_name.split(".")[0]
    return addon_name


class ComponentDatabases(dict):
    """Holds a registry of components for each database"""


class ComponentRegistry:
    """Store all the components and allow to find them using criteria

    The key is the ``_name`` of the components.

    This is an OrderedDict, because we want to keep the registration order of
    the components, addons loaded first have their components found first.

    The :attr:`ready` attribute must be set to ``True`` when all the components
    are loaded.

    """

    def __init__(self, cachesize=DEFAULT_CACHE_SIZE):
        self._cache = LRUCache(maxsize=cachesize)
        self._components = OrderedDict()
        self._loaded_modules = set()
        self.ready = False

    def __getitem__(self, key):
        return self._components[key]

    def __setitem__(self, key, value):
        self._components[key] = value

    def __contains__(self, key):
        return key in self._components

    def get(self, key, default=None):
        return self._components.get(key, default)

    def __iter__(self):
        return iter(self._components)

    def load_components(self, module):
        if module in self._loaded_modules:
            return
        for component_class in MetaComponent._modules_components[module]:
            component_class._build_component(self)
        self._loaded_modules.add(module)

    @cachedmethod(operator.attrgetter("_cache"))
    def lookup(self, collection_name=None, usage=None, model_name=None):
        """Find and return a list of components for a usage

        If a component is not registered in a particular collection (no
        ``_collection``), it will be returned in any case (as far as
        the ``usage`` and ``model_name`` match).  This is useful to share
        generic components across different collections.

        If no collection name is given, components from any collection
        will be returned.

        Then, the components of a collection are filtered by usage and/or
        model. The ``_usage`` is mandatory on the components. When the
        ``_model_name`` is empty, it means it can be used for every models,
        and it will ignore the ``model_name`` argument.

        The abstract components are never returned.

        This is a rather low-level function, usually you will use the
        high-level :meth:`AbstractComponent.component`,
        :meth:`AbstractComponent.many_components` or even
        :meth:`AbstractComponent.component_by_name`.

        :param collection_name: the name of the collection the component is
                                registered into.
        :param usage: the usage of component we are looking for
        :param model_name: filter on components that apply on this model

        """

        # keep the order so addons loaded first have components used first
        candidates = (
            component
            for component in self._components.values()
            if not component._abstract
        )

        if collection_name is not None:
            candidates = (
                component
                for component in candidates
                if (
                    component._collection == collection_name
                    or component._collection is None
                )
            )

        if usage is not None:
            candidates = (
                component for component in candidates if component._usage == usage
            )

        if model_name is not None:
            candidates = (
                c
                for c in candidates
                if c.apply_on_models is None or model_name in c.apply_on_models
            )

        return list(candidates)


# We will store a ComponentRegistry per database here,
# it will be cleared and updated when the odoo's registry is rebuilt
_component_databases = ComponentDatabases()


class WorkContext:
    """Transport the context required to work with components

    It is propagated through all the components, so any
    data or instance (like a random RPC client) that need
    to be propagated transversally to the components
    should be kept here.

    Including:

    .. attribute:: model_name

        Name of the model we are working with. It means that any lookup for a
        component will be done for this model. It also provides a shortcut
        as a `model` attribute to use directly with the Odoo model from
        the components

    .. attribute:: collection

        The collection we are working with. The collection is an Odoo
        Model that inherit from 'collection.base'. The collection attribute
        can be a record or an "empty" model.

    .. attribute:: model

        Odoo Model for ``model_name`` with the same Odoo
        :class:`~odoo.api.Environment` than the ``collection`` attribute.

    This is also the entrypoint to work with the components.

    ::

        collection = self.env['my.collection'].browse(1)
        work = WorkContext(model_name='res.partner', collection=collection)
        component = work.component(usage='record.importer')

    Usually you will use the context manager on the ``collection.base`` Model:

    ::

        collection = self.env['my.collection'].browse(1)
        with collection.work_on('res.partner') as work:
            component = work.component(usage='record.importer')

    It supports any arbitrary keyword arguments that will become attributes of
    the instance, and be propagated throughout all the components.

    ::

        collection = self.env['my.collection'].browse(1)
        with collection.work_on('res.partner', hello='world') as work:
            assert work.hello == 'world'

    When you need to work on a different model, a new work instance will be
    created for you when you are using the high-level API. This is what
    happens under the hood:

    ::

        collection = self.env['my.collection'].browse(1)
        with collection.work_on('res.partner', hello='world') as work:
            assert work.model_name == 'res.partner'
            assert work.hello == 'world'
            work2 = work.work_on('res.users')
            # => spawn a new WorkContext with a copy of the attributes
            assert work2.model_name == 'res.users'
            assert work2.hello == 'world'

    """

    def __init__(
        self, model_name=None, collection=None, components_registry=None, **kwargs
    ):
        self.collection = collection
        self.model_name = model_name
        self.model = self.env[model_name]
        # Allow propagation of custom component registry via context
        if collection:
            custom_registry = collection.env.context.get("components_registry")
            if custom_registry:
                components_registry = custom_registry
        # lookup components in an alternative registry, used by the tests
        if components_registry is not None:
            self.components_registry = components_registry
        else:
            dbname = self.env.cr.dbname
            try:
                self.components_registry = _component_databases[dbname]
            except KeyError as exc:
                msg = (
                    "No component registry for database %s. "
                    "Probably because the Odoo registry has not been built "
                    "yet."
                )
                _logger.error(
                    msg,
                    dbname,
                )
                raise RegistryNotReadyError(msg) from exc
        self._propagate_kwargs = ["collection", "model_name", "components_registry"]
        for attr_name, value in kwargs.items():
            setattr(self, attr_name, value)
            self._propagate_kwargs.append(attr_name)

    @property
    def env(self):
        """Return the current Odoo env

        This is the environment of the current collection.
        """
        return self.collection.env

    def work_on(self, model_name=None, collection=None):
        """Create a new work context for another model keeping attributes

        Used when one need to lookup components for another model.
        """
        kwargs = {
            attr_name: getattr(self, attr_name) for attr_name in self._propagate_kwargs
        }
        if collection is not None:
            kwargs["collection"] = collection
        if model_name is not None:
            kwargs["model_name"] = model_name
        return self.__class__(**kwargs)

    def _component_class_by_name(self, name):
        components_registry = self.components_registry
        component_class = components_registry.get(name)
        if not component_class:
            raise NoComponentError(f"No component with name '{name}' found.")
        return component_class

    def component_by_name(self, name, model_name=None):
        """Return a component by its name

        If the component exists, an instance of it will be returned,
        initialized with the current :class:`WorkContext`.

        A :exc:`odoo.addons.component.exception.NoComponentError` is raised
        if:

        * no component with this name exists
        * the ``_apply_on`` of the found component does not match
          with the current working model

        In the latter case, it can be an indication that you need to switch to
        a different model, you can do so by providing the ``model_name``
        argument.

        """
        if isinstance(model_name, models.BaseModel):
            model_name = model_name._name
        component_class = self._component_class_by_name(name)
        work_model = model_name or self.model_name
        if (
            component_class._collection
            and self.collection._name != component_class._collection
        ):
            raise NoComponentError(
                f"""Component with name '{name}' can't be used for collection
                     '{self.collection._name}'."""
            )

        if (
            component_class.apply_on_models
            and work_model not in component_class.apply_on_models
        ):
            if len(component_class.apply_on_models) == 1:
                hint_models = f"'{component_class.apply_on_models[0]}'"
            else:
                hint_models = f"<one of {component_class.apply_on_models!r}>"
            raise NoComponentError(
                f"Component with name '{name}' can't be used for model '{work_model}'."
                f"\nHint: you might want to use: "
                f"component_by_name('{name}', model_name={hint_models})"
            )

        if work_model == self.model_name:
            work_context = self
        else:
            work_context = self.work_on(model_name)
        return component_class(work_context)

    def _lookup_components(self, usage=None, model_name=None, **kw):
        component_classes = self.components_registry.lookup(
            self.collection._name, usage=usage, model_name=model_name
        )
        matching_components = []
        for cls in component_classes:
            try:
                matching = cls._component_match(
                    self, usage=usage, model_name=model_name, **kw
                )
            except TypeError as err:
                # Backward compat
                _logger.info(str(err))
                _logger.info(
                    "The signature of %s._component_match has changed. "
                    "Please, adapt your code as "
                    "(self, usage=usage, model_name=model_name, **kw)",
                    cls.__name__,
                )
                matching = cls._component_match(self)
            if matching:
                matching_components.append(cls)
        return matching_components

    def _filter_components_by_collection(self, component_classes):
        return [c for c in component_classes if c._collection == self.collection._name]

    def _filter_components_by_model(self, component_classes, model_name):
        return [
            c
            for c in component_classes
            if c.apply_on_models and model_name in c.apply_on_models
        ]

    def _ensure_model_name(self, model_name):
        """Make sure model name is a string or fallback to current ctx value."""
        if isinstance(model_name, models.BaseModel):
            model_name = model_name._name
        return model_name or self.model_name

    def _matching_components(self, usage=None, model_name=None, **kw):
        """Retrieve matching components and their work context."""
        component_classes = self._lookup_components(
            usage=usage, model_name=model_name, **kw
        )
        if model_name == self.model_name:
            work_context = self
        else:
            work_context = self.work_on(model_name)
        return component_classes, work_context

    def component(self, usage=None, model_name=None, **kw):
        """Find a component by usage and model for the current collection

        It searches a component using the rules of
        :meth:`ComponentRegistry.lookup`. When a component is found,
        it initialize it with the current :class:`WorkContext` and returned.

        A component with a ``_apply_on`` matching the asked ``model_name``
        takes precedence over a generic component without ``_apply_on``.
        A component with a ``_collection`` matching the current collection
        takes precedence over a generic component without ``_collection``.
        This behavior allows to define generic components across collections
        and/or models and override them only for a particular collection and/or
        model.

        A :exc:`odoo.addons.component.exception.SeveralComponentError` is
        raised if more than one component match for the provided
        ``usage``/``model_name``.

        A :exc:`odoo.addons.component.exception.NoComponentError` is raised
        if no component is found for the provided ``usage``/``model_name``.

        """
        model_name = self._ensure_model_name(model_name)
        component_classes, work_context = self._matching_components(
            usage=usage, model_name=model_name, **kw
        )
        if not component_classes:
            raise NoComponentError(
                f"No component found for collection '{self.collection._name}', "
                f"usage '{usage}', model_name '{model_name}'."
            )
        elif len(component_classes) > 1:
            # If we have more than one component, try to find the one
            # specifically linked to the collection...
            component_classes = self._filter_components_by_collection(component_classes)
        if len(component_classes) > 1:
            # ... or try to find the one specifically linked to the model
            component_classes = self._filter_components_by_model(
                component_classes, model_name
            )
        if len(component_classes) != 1:
            raise SeveralComponentError(
                "Several components found for collection '{}', "
                "usage '{}', model_name '{}'. Found: {}".format(
                    self.collection._name,
                    usage or "",
                    model_name or "",
                    component_classes,
                )
            )
        return component_classes[0](work_context)

    def many_components(self, usage=None, model_name=None, **kw):
        """Find many components by usage and model for the current collection

        It searches a component using the rules of
        :meth:`ComponentRegistry.lookup`. When components are found, they
        initialized with the current :class:`WorkContext` and returned as a
        list.

        If no component is found, an empty list is returned.

        """
        model_name = self._ensure_model_name(model_name)
        component_classes, work_context = self._matching_components(
            usage=usage, model_name=model_name, **kw
        )
        return [comp(work_context) for comp in component_classes]

    def __str__(self):
        return f"WorkContext({self.model_name}, {repr(self.collection)})"

    __repr__ = __str__


class MetaComponent(type):
    """Metaclass for Components

    Every new :class:`Component` will be added to ``_modules_components``,
    that will be used by the component builder.

    """

    _modules_components = defaultdict(list)

    def __init__(cls, name, bases, attrs):
        if not cls._register:
            cls._register = True
            super().__init__(name, bases, attrs)
            return

        # If components are declared in tests, exclude them from the
        # "components of the addon" list. If not, when we use the
        # "load_components" method, all the test components would be loaded.
        # This should never be an issue when running the app normally, as the
        # Python tests should never be executed. But this is an issue when a
        # test creates a test components for the purpose of the test, then a
        # second tests uses the "load_components" to load all the addons of the
        # module: it will load the component of the previous test.
        if "tests" in cls.__module__.split("."):
            return

        if not hasattr(cls, "_module"):
            cls._module = _get_addon_name(cls.__module__)

        cls._modules_components[cls._module].append(cls)

    @property
    def apply_on_models(cls):
        # None means all models
        if cls._apply_on is None:
            return None
        # always return a list, used for the lookup
        elif isinstance(cls._apply_on, str):
            return [cls._apply_on]
        return cls._apply_on


class AbstractComponent(metaclass=MetaComponent):
    """Main Component Model

    All components have a Python inheritance either on
    :class:`AbstractComponent` or either on :class:`Component`.

    Abstract Components will not be returned by lookups on components, however
    they can be used as a base for other Components through inheritance (using
    ``_inherit``).

    Inheritance mechanism
        The inheritance mechanism is like the Odoo's one for Models.  Each
        component has a ``_name``. This is the absolute minimum in a Component
        class.

        ::

            class MyComponent(Component):
                _name = 'my.component'

                def speak(self, message):
                    print message

        Every component implicitly inherit from the `'base'` component.

        There are two close but distinct inheritance types, which look
        familiar if you already know Odoo.  The first uses ``_inherit`` with
        an existing name, the name of the component we want to extend.  With
        the following example, ``my.component`` is now able to speak and to
        yell.

        ::

            class MyComponent(Component):  # name of the class does not matter
                _inherit = 'my.component'

                def yell(self, message):
                    print message.upper()

        The second has a different ``_name``, it creates a new component,
        including the behavior of the inherited component, but without
        modifying it. In the following example, ``my.component`` is still able
        to speak and to yell (brough by the previous inherit), but not to
        sing.  ``another.component`` is able to speak, to yell and to sing.

        ::

            class AnotherComponent(Component):
                _name = 'another.component'
                _inherit = 'my.component'

                def sing(self, message):
                    print message.upper()

    Registration and lookups
        It is handled by 3 attributes on the class:

        _collection
            The name of the collection where we want to register the
            component.  This is not strictly mandatory as a component can be
            shared across several collections. But usually, you want to set a
            collection to segregate the components for a domain.  A collection
            can be for instance ``magento.backend``. It is also the name of a
            model that inherits from ``collection.base``.  See also
            :class:`~WorkContext` and
            :class:`~odoo.addons.component.models.collection.Collection`.

        _apply_on
            List of names or name of the Odoo model(s) for which the component
            can be used.  When not set, the component can be used on any model.

        _usage
           The collection and the model (``_apply_on``) will help to filter
           the candidate components according to our working context (e.g. I'm
           working on ``magento.backend`` with the model
           ``magento.res.partner``).  The usage will define **what** kind of
           task the component we are looking for serves to. For instance, it
           might be ``record.importer``, ``export.mapper```... but you can be
           as creative as you want.

        Now, to get a component, you'll likely use
        :meth:`WorkContext.component` when you start to work with components
        in your flow, but then from within your components, you are more
        likely to use one of:

        * :meth:`component`
        * :meth:`many_components`
        * :meth:`component_by_name` (more rarely though)

        Declaration of some Components can look like::

            class FooBar(models.Model):
                _name = 'foo.bar.collection'
                _inherit = 'collection.base'  # this inherit is required


            class FooBarBase(AbstractComponent):
                _name = 'foo.bar.base'
                _collection = 'foo.bar.collection'  # name of the model above


            class Foo(Component):
                _name = 'foo'
                _inherit = 'foo.bar.base'  # we will inherit the _collection
                _apply_on = 'res.users'
                _usage = 'speak'

                def utter(self, message):
                    print message


            class Bar(Component):
                _name = 'bar'
                _inherit = 'foo.bar.base'  # we will inherit the _collection
                _apply_on = 'res.users'
                _usage = 'yell'

                def utter(self, message):
                    print message.upper() + '!!!'


            class Vocalizer(Component):
                _name = 'vocalizer'
                _inherit = 'foo.bar.base'
                _usage = 'vocalizer'
                # can be used for any model

                def vocalize(action, message):
                    self.component(usage=action).utter(message)


        And their usage::

            >>> coll = self.env['foo.bar.collection'].browse(1)
            >>> with coll.work_on('res.users') as work:
            ...     vocalizer = work.component(usage='vocalizer')
            ...     vocalizer.vocalize('speak', 'hello world')
            ...
            hello world
            ...     vocalizer.vocalize('yell', 'hello world')
            HELLO WORLD!!!

    Hints:

    * If you want to create components without ``_apply_on``, choose a
      ``_usage`` that will not conflict other existing components.
    * Unless this is what you want and in that case you use
      :meth:`many_components` which will return all components for a usage
      with a matching or a not set ``_apply_on``.
    * It is advised to namespace the names of the components (e.g.
      ``magento.xxx``) to prevent conflicts between addons.

    """

    _register = False
    _abstract = True

    # used for inheritance
    _name = None  #: Name of the component

    #: Name or list of names of the component(s) to inherit from
    _inherit = None

    #: name of the collection to subscribe in
    _collection = None

    #: List of models on which the component can be applied.
    #: None means any Model, can be a list ['res.users', ...]
    _apply_on = None

    #: Component purpose ('import.mapper', ...).
    _usage = None

    def __init__(self, work_context):
        super().__init__()
        self.work = work_context

    @classmethod
    def _component_match(cls, work, usage=None, model_name=None, **kw):
        """Evaluated on candidate components

        When a component lookup is done and candidate(s) have
        been found for a usage, a final call is done on this method.
        If the method return False, the candidate component is ignored.

        It can be used for instance to dynamically choose a component
        according to a value in the :class:`WorkContext`.

        Beware, if the lookups from usage, model and collection are
        cached, the calls to :meth:`_component_match` are executed
        each time we get components. Heavy computation should be
        avoided.

        :param work: the :class:`WorkContext` we are working with

        """
        return True

    @property
    def collection(self):
        """Collection we are working with"""
        return self.work.collection

    @property
    def env(self):
        """Current Odoo environment, the one of the collection record"""
        return self.work.env

    @property
    def model(self):
        """The model instance we are working with"""
        return self.work.model

    def component_by_name(self, name, model_name=None):
        """Return a component by its name

        Shortcut to meth:`~WorkContext.component_by_name`
        """
        return self.work.component_by_name(name, model_name=model_name)

    def component(self, usage=None, model_name=None, **kw):
        """Return a component

        Shortcut to meth:`~WorkContext.component`
        """
        return self.work.component(usage=usage, model_name=model_name, **kw)

    def many_components(self, usage=None, model_name=None, **kw):
        """Return several components

        Shortcut to meth:`~WorkContext.many_components`
        """
        return self.work.many_components(usage=usage, model_name=model_name, **kw)

    def __str__(self):
        return f"Component({self._name})"

    __repr__ = __str__

    @classmethod
    def _build_component(cls, registry):
        """Instantiate a given Component in the components registry.

        This method is called at the end of the Odoo's registry build.  The
        caller is :meth:`component.builder.ComponentBuilder.load_components`.

        It generates new classes, which will be the Component classes we will
        be using.  The new classes are generated following the inheritance
        of ``_inherit``. It ensures that the ``__bases__`` of the generated
        Component classes follow the ``_inherit`` chain.

        Once a Component class is created, it adds it in the Component Registry
        (:class:`ComponentRegistry`), so it will be available for
        lookups.

        At the end of new class creation, a hook method
        :meth:`_complete_component_build` is called, so you can customize
        further the created components. An example can be found in
        :meth:`odoo.addons.connector.components.mapper.Mapper._complete_component_build`

        The following code is roughly the same than the Odoo's one for
        building Models.

        """

        # In the simplest case, the component's registry class inherits from
        # cls and the other classes that define the component in a flat
        # hierarchy.  The registry contains the instance ``component`` (on the
        # left). Its class, ``ComponentClass``, carries inferred metadata that
        # is shared between all the component's instances for this registry
        # only.
        #
        #   class A1(Component):                    Component
        #       _name = 'a'                           / | \
        #                                            A3 A2 A1
        #   class A2(Component):                      \ | /
        #       _inherit = 'a'                    ComponentClass
        #
        #   class A3(Component):
        #       _inherit = 'a'
        #
        # When a component is extended by '_inherit', its base classes are
        # modified to include the current class and the other inherited
        # component classes.
        # Note that we actually inherit from other ``ComponentClass``, so that
        # extensions to an inherited component are immediately visible in the
        # current component class, like in the following example:
        #
        #   class A1(Component):
        #       _name = 'a'                          Component
        #                                            /  / \  \
        #   class B1(Component):                    /  A2 A1  \
        #       _name = 'b'                        /   \  /    \
        #                                         B2 ComponentA B1
        #   class B2(Component):                   \     |     /
        #       _name = 'b'                         \    |    /
        #       _inherit = ['b', 'a']                \   |   /
        #                                            ComponentB
        #   class A2(Component):
        #       _inherit = 'a'

        # determine inherited components
        parents = cls._inherit
        if isinstance(parents, str):
            parents = [parents]
        elif parents is None:
            parents = []

        if cls._name in registry and not parents:
            raise TypeError(
                f"Component {cls._name} (in class {cls}) already exists. "
                "Consider using _inherit instead of _name "
                "or using a different _name."
            )

        # determine the component's name
        name = cls._name or (len(parents) == 1 and parents[0])

        if not name:
            raise TypeError(f"Component {cls} must have a _name")

        # all components except 'base' implicitly inherit from 'base'
        if name != "base":
            parents = list(parents) + ["base"]

        # create or retrieve the component's class
        if name in parents:
            if name not in registry:
                raise TypeError(f"Component {name} does not exist in registry.")
            ComponentClass = registry[name]
            ComponentClass._build_component_check_base(cls)
            check_parent = ComponentClass._build_component_check_parent
        else:
            ComponentClass = type(
                name,
                (AbstractComponent,),
                {
                    "_name": name,
                    "_register": False,
                    # names of children component
                    "_inherit_children": OrderedSet(),
                },
            )
            check_parent = cls._build_component_check_parent

        # determine all the classes the component should inherit from
        bases = LastOrderedSet([cls])
        for parent in parents:
            if parent not in registry:
                raise TypeError(
                    f"Component {name} inherits from non-existing component {parent}."
                )
            parent_class = registry[parent]
            if parent == name:
                for base in parent_class.__bases__:
                    bases.add(base)
            else:
                check_parent(cls, parent_class)
                bases.add(parent_class)
                parent_class._inherit_children.add(name)
        ComponentClass.__bases__ = tuple(bases)

        ComponentClass._complete_component_build()

        registry[name] = ComponentClass

        return ComponentClass

    @classmethod
    def _build_component_check_base(cls, extend_cls):
        """Check whether ``cls`` can be extended with ``extend_cls``."""
        if cls._abstract and not extend_cls._abstract:
            msg = (
                "%s transforms the abstract component %r into a "
                "non-abstract component. "
                "That class should either inherit from AbstractComponent, "
                "or set a different '_name'."
            )
            raise TypeError(msg % (extend_cls, cls._name))

    @classmethod
    def _build_component_check_parent(component_class, cls, parent_class):  # noqa: B902
        """Check whether ``model_class`` can inherit from ``parent_class``."""
        if component_class._abstract and not parent_class._abstract:
            msg = (
                "In %s, the abstract Component %r cannot inherit "
                "from the non-abstract Component %r."
            )
            raise TypeError(msg % (cls, component_class._name, parent_class._name))

    @classmethod
    def _complete_component_build(cls):
        """Complete build of the new component class

        After the component has been built from its bases, this method is
        called, and can be used to customize the class before it can be used.

        Nothing is done in the base Component, but a Component can inherit
        the method to add its own behavior.
        """


class Component(AbstractComponent):
    """Concrete Component class

    This is the class you inherit from when you want your component to
    be registered in the component collections.

    Look in :class:`AbstractComponent` for more details.

    """

    _register = False
    _abstract = False
