Skip to content

Commit

Permalink
refactor(common): remove ibis.common.bases.Base in favor of Abstract
Browse files Browse the repository at this point in the history
  • Loading branch information
kszucs committed Oct 9, 2023
1 parent 815c12f commit 8ed313c
Show file tree
Hide file tree
Showing 4 changed files with 39 additions and 66 deletions.
86 changes: 34 additions & 52 deletions ibis/common/bases.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,43 @@
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__ = ()

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.
Expand All @@ -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):
Expand All @@ -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()
Expand All @@ -130,7 +112,7 @@ def __create__(cls, *args, **kwargs):
return instance


class Final(Base):
class Final(Abstract):
"""Prohibit subclassing."""

def __init_subclass__(cls, **kwargs):
Expand Down Expand Up @@ -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.
Expand Down
2 changes: 0 additions & 2 deletions ibis/common/grounds.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,6 @@
from ibis.common.bases import ( # noqa: F401
Abstract,
AbstractMeta,
Base,
BaseMeta,
Comparable,
Final,
Immutable,
Expand Down
15 changes: 5 additions & 10 deletions ibis/common/tests/test_bases.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@
from ibis.common.bases import (
Abstract,
AbstractMeta,
Base,
BaseMeta,
Comparable,
Final,
Immutable,
Expand All @@ -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():
Expand All @@ -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.*"):
Expand All @@ -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()


Expand Down
2 changes: 0 additions & 2 deletions ibis/common/tests/test_grounds.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
Abstract,
Annotable,
AnnotableMeta,
Base,
Comparable,
Concrete,
Immutable,
Expand Down Expand Up @@ -959,7 +958,6 @@ def test_concrete():
Comparable,
Annotable,
Abstract,
Base,
object,
)

Expand Down

0 comments on commit 8ed313c

Please sign in to comment.