Skip to content

Commit

Permalink
Fix Common class ignoring other branches of multiple inheritance tree
Browse files Browse the repository at this point in the history
`Common` was not calling `super().__init__()` method, which means mixin
classes might have been excluded from the chain of `__init__()` calls.
Fixing that, and adding a "root" class to handle calls to
`object.__init__()`.
  • Loading branch information
happz committed Feb 14, 2023
1 parent f923745 commit 5b15c59
Showing 1 changed file with 41 additions and 8 deletions.
49 changes: 41 additions & 8 deletions tmt/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -352,7 +352,35 @@ class CommandOutput(NamedTuple):
stderr: Optional[str]


class Common:
class _CommonBase:
"""
A base class for **all** classes contributing to "common" tree of classes.
All classes derived from :py:class:`Common` or mixin classes used to enhance
classes derived from :py:class:`Common` need to have this class as one of
its most distant ancestors. They should not descend directly from ``object``
class, ``_CommonBase`` needs to be used instead.
Our classes and mixins use keyword-only arguments, and with mixins in play,
we do not have a trivial single-inheritance tree, therefore it's not simple
to realize when a ``super().__init__`` belongs to ``object``. To deliver
arguments to all classes, our ``__init__()`` methods must accept all
parameters, even those they have no immediate use for, and propagate them
via ``**kwargs``. Sooner or later, one of the classes would try to call
``object.__init__(**kwargs)``, but this particular ``__init__()`` accepts
no keyword arguments, which would lead to an exception.
``_CommonBase`` sits at the root of the inheritance tree, and is responsible
for calling ``object.__init__()`` *with no arguments*. Thanks to method
resolution order, all "branches" of our tree of common classes should lead
to ``_CommonBase``, making sure the call to ``object`` is correct.
"""

def __init__(self, **kwargs: Any) -> None:
super().__init__()


class Common(_CommonBase):
"""
Common shared stuff
Expand Down Expand Up @@ -405,6 +433,15 @@ def __init__(
if context is provided.
"""

super().__init__(
parent=parent,
name=name,
workdir=workdir,
context=context,
relative_indent=relative_indent,
logger=logger,
**kwargs)

# Use lowercase class name as the default name
self.name = name or self.__class__.__name__.lower()
self.parent = parent
Expand Down Expand Up @@ -3594,7 +3631,7 @@ def wait(
continue


class ValidateFmfMixin:
class ValidateFmfMixin(_CommonBase):
"""
Mixin adding validation of an fmf node.
Expand Down Expand Up @@ -3633,11 +3670,7 @@ def __init__(
if not skip_validation:
self._validate_fmf_node(node, raise_on_validation_error, logger)

# ignore[call-arg]: pypy is not aware of this class being a
# mixin, therefore it cannot allow keyword arguments, because
# object.__init__() allows none. But it's fine, as we will never
# instantiate this class itself.
super().__init__(node=node, logger=logger, **kwargs) # type: ignore[call-arg]
super().__init__(node=node, logger=logger, **kwargs)


# A type representing compatible sources of keys and values.
Expand Down Expand Up @@ -3771,7 +3804,7 @@ def normalize_shell_script_list(
f"Field can be either string or list of strings, '{type(value).__name__}' found.")


class NormalizeKeysMixin:
class NormalizeKeysMixin(_CommonBase):
"""
Mixin adding support for loading fmf keys into object attributes.
Expand Down

0 comments on commit 5b15c59

Please sign in to comment.