Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support declaring a Parameterized class as an ABC #1031

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions param/_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -519,6 +519,8 @@ def _is_number(obj):


def _is_abstract(class_):
if inspect.isabstract(class_):
return True
try:
return class_.abstract
except AttributeError:
Expand Down
17 changes: 17 additions & 0 deletions param/parameterized.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
__init__.py (providing specialized Parameter types).
"""

import abc
import asyncio
import copy
import datetime as dt
Expand Down Expand Up @@ -4571,6 +4572,22 @@ def __str__(self):
return f"<{self.__class__.__name__} {self.name}>"


class ParameterizedABCMeta(abc.ABCMeta, ParameterizedMetaclass):
"""Metaclass for abstract base classes using Parameterized.

Ensures compatibility between ABCMeta and ParameterizedMetaclass.
"""


class ParameterizedABC(Parameterized, metaclass=ParameterizedABCMeta):
"""Base class for user-defined ABCs that extends Parameterized."""

def __init_subclass__(cls, **kwargs):
if cls.__bases__ and cls.__bases__[0] is ParameterizedABC:
setattr(cls, f'_{cls.__name__}__abstract', True)
super().__init_subclass__(**kwargs)


def print_all_param_defaults():
"""Print the default values for all imported Parameters."""
print("_______________________________________________________________________________")
Expand Down
19 changes: 18 additions & 1 deletion tests/testparameterizedobject.py
Original file line number Diff line number Diff line change
Expand Up @@ -365,11 +365,28 @@ def test_instantiation_inheritance(self):
assert t.param['instPO'].instantiate is True
assert isinstance(t.instPO,AnotherTestPO)

def test_abstract_class(self):
def test_abstract_class_attribute(self):
"""Check that a class declared abstract actually shows up as abstract."""
self.assertEqual(TestAbstractPO.abstract, True)
self.assertEqual(_AnotherAbstractPO.abstract, True)
self.assertEqual(TestPO.abstract, False)
# Test subclasses are not abstract
class A(param.Parameterized):
__abstract = True
class B(A): pass
class C(A): pass
self.assertEqual(A.abstract, True)
self.assertEqual(B.abstract, False)
self.assertEqual(C.abstract, False)

def test_abstract_class_abc(self):
"""Check that an ABC class actually shows up as abstract."""
class A(param.parameterized.ParameterizedABC): pass
class B(A): pass
class C(A): pass
self.assertEqual(A.abstract, True)
self.assertEqual(B.abstract, False)
self.assertEqual(C.abstract, False)

def test_override_class_param_validation(self):
test = TestPOValidation()
Expand Down
33 changes: 31 additions & 2 deletions tests/testutils.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,13 @@
import pytest

from param import guess_param_types, resolve_path
from param.parameterized import bothmethod
from param._utils import _is_mutable_container, iscoroutinefunction, gen_types
from param.parameterized import bothmethod, Parameterized, ParameterizedABC
from param._utils import (
_is_abstract,
_is_mutable_container,
iscoroutinefunction,
gen_types,
)


try:
Expand Down Expand Up @@ -439,3 +444,27 @@ def _int_types():
assert next(iter(_int_types())) is int
assert next(iter(_int_types)) is int
assert isinstance(_int_types, Iterable)


def test_is_abstract_false():
class A: pass
class B(Parameterized): pass
assert not _is_abstract(A)
assert not _is_abstract(B)


def test_is_abstract_attribute():
class A(Parameterized):
__abstract = True
class B(A): pass

assert _is_abstract(A)
assert not _is_abstract(B)


def test_is_abstract_abc():
class A(ParameterizedABC): pass
class B(A): pass

assert _is_abstract(A)
assert not _is_abstract(B)