Skip to content

Commit

Permalink
prevent sanest.dict and sanest.list subclasses
Browse files Browse the repository at this point in the history
yes, this is opinionated, but it is too easy to break the behaviour.
composition over inheritance, thank you. closes #5.
  • Loading branch information
wbolster committed Jul 2, 2017
1 parent aa58008 commit 864bd84
Show file tree
Hide file tree
Showing 2 changed files with 35 additions and 2 deletions.
23 changes: 21 additions & 2 deletions src/sanest/sanest.py
Original file line number Diff line number Diff line change
Expand Up @@ -387,6 +387,19 @@ def resolve_path(obj, path, *, partial=False, create=False):
return obj


class FinalABCMeta(abc.ABCMeta):
"""
Meta-class to prevent subclassing.
"""
def __new__(cls, name, bases, classdict):
for b in bases:
if isinstance(b, FinalABCMeta):
raise TypeError(
"type '{}.{}' is not an acceptable base type"
.format(__package__, b.__name__))
return super().__new__(cls, name, bases, builtins.dict(classdict))


class SaneCollection(Collection):
"""
Base class for ``sanest.dict`` and ``sanest.list``.
Expand Down Expand Up @@ -532,7 +545,10 @@ def pprint_sanest_collection(
dispatch_table[SaneCollection.__repr__] = pprint_sanest_collection


class dict(SaneCollection, collections.abc.MutableMapping):
class dict(
SaneCollection,
collections.abc.MutableMapping,
metaclass=FinalABCMeta):
"""
dict-like container with support for nested lookups and type checking.
"""
Expand Down Expand Up @@ -814,7 +830,10 @@ def __iter__(self):
yield key, value


class list(SaneCollection, collections.abc.MutableSequence):
class list(
SaneCollection,
collections.abc.MutableSequence,
metaclass=FinalABCMeta):
"""
list-like container with support for nested lookups and type checking.
"""
Expand Down
14 changes: 14 additions & 0 deletions test_sanest.py
Original file line number Diff line number Diff line change
Expand Up @@ -1581,6 +1581,20 @@ def test_list_del_slice():
#


def test_prevent_subclassing():
with pytest.raises(TypeError) as excinfo:
class X(sanest.dict):
pass
assert str(excinfo.value) == (
"type 'sanest.dict' is not an acceptable base type")

with pytest.raises(TypeError) as excinfo:
class Y(sanest.list):
pass
assert str(excinfo.value) == (
"type 'sanest.list' is not an acceptable base type")


def test_wrap():
l = _sanest.list.wrap([1, 2])
assert isinstance(l, sanest.list)
Expand Down

0 comments on commit 864bd84

Please sign in to comment.