diff --git a/ibis/common/bases.py b/ibis/common/bases.py index 504cc66ee27d..de7c2bc6d9d2 100644 --- a/ibis/common/bases.py +++ b/ibis/common/bases.py @@ -13,12 +13,16 @@ from typing_extensions import Self -class BaseMeta(type): +class AbstractMeta(type): """Base metaclass for many of the ibis core classes. - This metaclass enforces the subclasses to define a `__slots__` attribute and - provides a `__create__` classmethod that can be used to change the class - instantiation behavior. + Enforce the subclasses to define a `__slots__` attribute and provide a + `__create__` classmethod to change the instantiation behavior of the class. + + Support abstract methods without extending `abc.ABCMeta`. While it provides + a reduced feature set compared to `abc.ABCMeta` (no way to register virtual + subclasses) but avoids expensive instance checks by enforcing explicit + subclassing. """ __slots__ = () @@ -26,7 +30,26 @@ class BaseMeta(type): def __new__(metacls, clsname, bases, dct, **kwargs): # enforce slot definitions dct.setdefault("__slots__", ()) - return super().__new__(metacls, clsname, bases, dct, **kwargs) + + # construct the class object + cls = super().__new__(metacls, clsname, bases, dct, **kwargs) + + # calculate abstract methods existing in the class + abstracts = { + name + for name, value in dct.items() + if getattr(value, "__isabstractmethod__", False) + } + for parent in bases: + for name in getattr(parent, "__abstractmethods__", set()): + value = getattr(cls, name, None) + if getattr(value, "__isabstractmethod__", False): + abstracts.add(name) + + # set the abstract methods for the class + cls.__abstractmethods__ = frozenset(abstracts) + + return cls def __call__(cls, *args, **kwargs): """Create a new instance of the class. @@ -50,55 +73,14 @@ def __call__(cls, *args, **kwargs): return cls.__create__(*args, **kwargs) -class AbstractMeta(BaseMeta): - """Metaclass to support abstract methods without extending `abc.ABCMeta`. - - Provide a reduced feature set compared to `abc.ABCMeta` (no way to register - virtual subclasses) but avoids expensive instance checks by enforcing explicit - subclassing. - """ - - __slots__ = () - - def __new__(metacls, clsname, bases, dct, **kwargs): - cls = super().__new__(metacls, clsname, bases, dct, **kwargs) - - # calculate abstract methods - abstracts = { - name - for name, value in dct.items() - if getattr(value, "__isabstractmethod__", False) - } - for parent in bases: - for name in getattr(parent, "__abstractmethods__", set()): - value = getattr(cls, name, None) - if getattr(value, "__isabstractmethod__", False): - abstracts.add(name) - - cls.__abstractmethods__ = frozenset(abstracts) - return cls - - -class Base(metaclass=BaseMeta): - """Base class for many of the ibis core classes. - - This class enforces the subclasses to define a `__slots__` attribute and - provides a `__create__` classmethod that can be used to change the class - instantiation behavior. Also enables weak references for the subclasses. - """ +class Abstract(metaclass=AbstractMeta): + """Base class for many of the ibis core classes, see `AbstractMeta`.""" __slots__ = ("__weakref__",) __create__ = classmethod(type.__call__) # type: ignore -class Abstract(Base, metaclass=AbstractMeta): - """Base class for abstract classes. - - Extend the `Base` class with support for abstract methods and properties. - """ - - -class Immutable(Base): +class Immutable(Abstract): """Prohibit attribute assignment on the instance.""" def __copy__(self): @@ -114,7 +96,7 @@ def __setattr__(self, name: str, _: Any) -> None: ) -class Singleton(Base): +class Singleton(Abstract): """Cache instances of the class based on instantiation arguments.""" __instances__: Mapping[Any, Self] = WeakValueDictionary() @@ -130,7 +112,7 @@ def __create__(cls, *args, **kwargs): return instance -class Final(Base): +class Final(Abstract): """Prohibit subclassing.""" def __init_subclass__(cls, **kwargs): @@ -196,7 +178,7 @@ def __cached_equals__(self, other) -> bool: return result -class Slotted(Base): +class Slotted(Abstract): """A lightweight alternative to `ibis.common.grounds.Annotable`. The class is mostly used to reduce boilerplate code. diff --git a/ibis/common/grounds.py b/ibis/common/grounds.py index 975242a746b2..951e319b3ae4 100644 --- a/ibis/common/grounds.py +++ b/ibis/common/grounds.py @@ -20,8 +20,6 @@ from ibis.common.bases import ( # noqa: F401 Abstract, AbstractMeta, - Base, - BaseMeta, Comparable, Final, Immutable, diff --git a/ibis/common/tests/test_bases.py b/ibis/common/tests/test_bases.py index 87049147a065..fb55b66ff6a0 100644 --- a/ibis/common/tests/test_bases.py +++ b/ibis/common/tests/test_bases.py @@ -9,8 +9,6 @@ from ibis.common.bases import ( Abstract, AbstractMeta, - Base, - BaseMeta, Comparable, Final, Immutable, @@ -19,12 +17,11 @@ from ibis.common.caching import WeakCache -def test_bases_are_based_on_base(): - assert issubclass(Comparable, Base) - assert issubclass(Final, Base) - assert issubclass(Immutable, Base) - assert issubclass(Singleton, Base) - assert issubclass(Abstract, Base) +def test_classes_are_based_on_abstract(): + assert issubclass(Comparable, Abstract) + assert issubclass(Final, Abstract) + assert issubclass(Immutable, Abstract) + assert issubclass(Singleton, Abstract) def test_abstract(): @@ -40,7 +37,6 @@ def bar(self): assert not issubclass(type(Foo), ABCMeta) assert issubclass(type(Foo), AbstractMeta) - assert issubclass(type(Foo), BaseMeta) assert Foo.__abstractmethods__ == frozenset({"foo", "bar"}) with pytest.raises(TypeError, match="Can't instantiate abstract class .*Foo.*"): @@ -59,7 +55,6 @@ def bar(self): assert bar.bar == 2 assert isinstance(bar, Foo) assert isinstance(bar, Abstract) - assert isinstance(bar, Base) assert Bar.__abstractmethods__ == frozenset() diff --git a/ibis/common/tests/test_grounds.py b/ibis/common/tests/test_grounds.py index f6ce54e506d9..c75957d778a5 100644 --- a/ibis/common/tests/test_grounds.py +++ b/ibis/common/tests/test_grounds.py @@ -25,7 +25,6 @@ Abstract, Annotable, AnnotableMeta, - Base, Comparable, Concrete, Immutable, @@ -959,7 +958,6 @@ def test_concrete(): Comparable, Annotable, Abstract, - Base, object, )