Skip to content

Commit

Permalink
Make objects actually implement interfaces, and update definitions.
Browse files Browse the repository at this point in the history
- Make the BTree objects (BTree, TreeSet, Set, Bucket)
  of each module actually provide the interfaces defined in
  BTrees.Interfaces. Previously, they provided no interfaces.

- Update the definitions of ISized and IReadSequence to simply
  be zope.interface.common.collections.ISized and
  zope.interface.common.sequence.IMinimalSequence respectively.

- Remove the __nonzero__ interface method from ICollection. No
  objects actually implemented such a method; instead, the boolean value
  is typically taken from __len__.

Fixes #138
  • Loading branch information
jamadden committed Mar 31, 2021
1 parent ee35194 commit 8bdc05a
Show file tree
Hide file tree
Showing 8 changed files with 103 additions and 40 deletions.
12 changes: 12 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,18 @@
See `issue 156
<https://github.com/zopefoundation/BTrees/issues/156>`_.

- Make the BTree objects (``BTree``, ``TreeSet``, ``Set``, ``Bucket``)
of each module actually provide the interfaces defined in
``BTrees.Interfaces``. Previously, they provided no interfaces.

- Update the definitions of ``ISized`` and ``IReadSequence`` to simply
be ``zope.interface.common.collections.ISized`` and
``zope.interface.common.sequence.IMinimalSequence`` respectively.

- Remove the ``__nonzero__`` interface method from ``ICollection``. No
objects actually implemented such a method; instead, the boolean value
is typically taken from ``__len__``.

4.7.2 (2020-04-07)
==================

Expand Down
10 changes: 8 additions & 2 deletions docs/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,17 @@ Protocol APIs

.. module:: BTrees.Interfaces

.. versionchanged:: 4.8.0
Previously, ``ISized`` was defined here, but now it is
imported from :mod:`zope.interface.common.collections`. The
definition is the same.

Similarly, ``IReadSequence``, previously defined here,
has been replaced with :class:`zope.interface.common.sequence.IMinimalSequence`.

.. autointerface:: ICollection
.. autointerface:: IReadSequence
.. autointerface:: IKeyed
.. autointerface:: ISetMutable
.. autointerface:: ISized
.. autointerface:: IKeySequence
.. autointerface:: IMinimalDictionary
.. autointerface:: IDictionaryIsh
Expand Down
7 changes: 4 additions & 3 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,8 @@

# Example configuration for intersphinx: refer to the Python standard library.
intersphinx_mapping = {
"python": ('https://docs.python.org/3/', None),
"persistent": ('https://persistent.readthedocs.io/en/latest/', None),
"ZODB": ("https://zodb-docs.readthedocs.io/en/latest/", None),
'https://docs.python.org/3/': None,
'https://persistent.readthedocs.io/en/latest/': None,
"https://zodb.readthedocs.io/en/latest/": None,
"https://zopeinterface.readthedocs.io/en/latest/": None,
}
3 changes: 3 additions & 0 deletions src/BTrees/BTreeModuleTemplate.c
Original file line number Diff line number Diff line change
Expand Up @@ -742,6 +742,9 @@ module_init(void)
if (PyDict_SetItemString(mod_dict, "TreeSet",
(PyObject *)&TreeSetType) < 0)
return NULL;
if (PyDict_SetItemString(mod_dict, "TreeItems",
(PyObject *)&BTreeItemsType) < 0)
return NULL;
#if defined(ZODB_64BIT_INTS) && defined(NEED_LONG_LONG_SUPPORT)
if (PyDict_SetItemString(mod_dict, "using64bits", Py_True) < 0)
return NULL;
Expand Down
63 changes: 28 additions & 35 deletions src/BTrees/Interfaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,35 +13,28 @@
##############################################################################

from zope.interface import Interface, Attribute
from zope.interface.common.collections import ISized
from zope.interface.common.sequence import IMinimalSequence
from zope.interface.common.collections import IMapping

# pylint:disable=inherit-non-class,no-method-argument,no-self-argument
# pylint:disable=unexpected-special-method-signature

class ICollection(Interface):
"""
A collection of zero or more objects.
In a boolean context, objects implementing this interface are
`True` if the collection is non-empty, and `False` if the
collection is empty.
"""

def clear():
"""Remove all of the items from the collection."""

def __nonzero__():
"""Check if the collection is non-empty.
Return a true value if the collection is non-empty and a
false value otherwise.
"""


class IReadSequence(Interface):

def __getitem__(index):
"""Return the value at the given index.
An :class:`IndexError` is raised if the index cannot be found.
"""

def __getslice__(index1, index2):
"""Return a subsequence from the original sequence.

The subsequence includes the items from index1 up to, but not
including, index2.
"""
# Backwards compatibility alias. To be removed in 5.0
IReadSequence = IMinimalSequence

class IKeyed(ICollection):

Expand All @@ -52,9 +45,9 @@ def has_key(key):
"""

def keys(min=None, max=None, excludemin=False, excludemax=False):
"""Return an :class:`~BTrees.Interfaces.IReadSequence` containing the keys in the collection.
"""Return an :class:`~BTrees.Interfaces.IMinimalSequence` containing the keys in the collection.
The type of the :class:`~BTrees.Interfaces.IReadSequence` is not specified. It could be a list
The type of the :class:`~BTrees.Interfaces.IMinimalSequence` is not specified. It could be a list
or a tuple or some other type.
All arguments are optional, and may be specified as keyword
Expand Down Expand Up @@ -108,13 +101,6 @@ def update(seq):
"""Add the items from the given sequence to the set."""


class ISized(Interface):
"""An object that supports __len__."""

def __len__():
"""Return the number of items in the container."""


class IKeySequence(IKeyed, ISized):

def __getitem__(index):
Expand Down Expand Up @@ -147,7 +133,14 @@ def __sub__(other):
"""Shortcut for :meth:`~BTrees.Interfaces.IMerge.difference"""


class IMinimalDictionary(ISized, IKeyed):

class IMinimalDictionary(IKeyed, IMapping):
"""
Mapping operations.
.. versionchanged:: 4.8.0
Now extends :class:`zope.interface.common.collections.IMapping`.
"""

def get(key, default):
"""Get the value associated with the given key.
Expand All @@ -174,9 +167,9 @@ def __delitem__(key):
"""

def values(min=None, max=None, excludemin=False, excludemax=False):
"""Return an :class:`BTrees.Interfaces.IReadSequence` containing the values in the collection.
"""Return an :class:`BTrees.Interfaces.IMinimalSequence` containing the values in the collection.
The type of the :class:`~BTrees.Interfaces.IReadSequence` is not specified. It could be a list
The type of the :class:`~BTrees.Interfaces.IMinimalSequence` is not specified. It could be a list
or a tuple or some other type.
All arguments are optional, and may be specified as keyword
Expand All @@ -198,11 +191,11 @@ def values(min=None, max=None, excludemin=False, excludemax=False):
"""

def items(min=None, max=None, excludemin=False, excludemax=False):
"""Return an :class:`BTrees.Interfaces.IReadSequence` containing the items in the collection.
"""Return an ``IMinimalSequence`` containing the items in the collection.
An item is a 2-tuple, a (key, value) pair.
The type of the :class:`BTrees.Interfaces.IReadSequence` is not specified. It could be a list
The type of the ``IMinimalSequence`` is not specified. It could be a list
or a tuple or some other type.
All arguments are optional, and may be specified as keyword
Expand Down
16 changes: 16 additions & 0 deletions src/BTrees/_module_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"""
import sys
from zope.interface import directlyProvides
from zope.interface import classImplements


def _create_classes(
Expand All @@ -27,13 +28,15 @@ def _create_classes(
from ._base import Set
from ._base import Tree
from ._base import TreeSet
from ._base import _TreeItems as TreeItems
from ._base import _TreeIterator
from ._base import _fix_pickle

classes = {}

prefix = key_datatype.prefix_code + value_datatype.prefix_code

classes['TreeItems'] = classes['TreeItemsPy'] = TreeItems
for base in (
Bucket,
Set,
Expand Down Expand Up @@ -123,6 +126,7 @@ def _create_globals(module_name, key_datatype, value_datatype):
def populate_module(mod_globals,
key_datatype, value_datatype,
interface, module=None):
from . import Interfaces as interfaces
from ._compat import import_c_extension
from ._base import _fix_pickle

Expand Down Expand Up @@ -154,7 +158,19 @@ def populate_module(mod_globals,
# we can know if we're going to be renaming classes
# ahead of time. See above.
_fix_pickle(mod_globals, module_name)

# Apply interface definitions.
directlyProvides(module or sys.modules[module_name], interface)
for cls_name, iface in {
'BTree': interfaces.IBTree,
'Bucket': interfaces.IMinimalDictionary,
'Set': interfaces.ISet,
'TreeSet': interfaces.ITreeSet,
'TreeItems': interfaces.IMinimalSequence,
}.items():
classImplements(mod_globals[cls_name], iface)
classImplements(mod_globals[cls_name + 'Py'], iface)


def create_module(prefix):
import types
Expand Down
9 changes: 9 additions & 0 deletions src/BTrees/tests/_test_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,15 @@ class Test(ModuleTest, unittest.TestCase):
self._fixup_and_store_class(_FilteredModuleProxy(self.btree_module, ''), '', Test)

def _create_type_tests(self, btree_module, type_name, test_bases):
from BTrees import Interfaces as interfaces
tree = getattr(btree_module, type_name)
iface = {
'BTree': interfaces.IBTree,
'Bucket': interfaces.IMinimalDictionary,
'Set': interfaces.ISet,
'TreeSet': interfaces.ITreeSet
}[type_name]

for test_base in test_bases:
if not self._needs_test(tree, test_base):
continue
Expand All @@ -230,6 +238,7 @@ def _create_type_tests(self, btree_module, type_name, test_bases):
test_cls = type(test_name, bases, {
'__module__': self.test_module,
'_getTargetClass': lambda _, t=tree: t,
'_getTargetInterface': lambda _, i=iface: i,
'getTwoKeys': self.key_type.getTwoExamples,
'getTwoValues': self.value_type.getTwoExamples,
'key_type': self.key_type,
Expand Down
23 changes: 23 additions & 0 deletions src/BTrees/tests/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,13 +128,36 @@ class Base(ZODBAccess, SignedMixin):
def _getTargetClass(self):
raise NotImplementedError("subclass should return the target type")

def _getTargetInterface(self):
raise NotImplementedError("subclass must return the expected interface ")

def _makeOne(self):
return self._getTargetClass()()

def setUp(self):
super(Base, self).setUp()
_skip_if_pure_py_and_py_test(self)

def testProvidesInterface(self):
from zope.interface.verify import verifyObject
t = self._makeOne()
self._populate(t, 10)
# reprs are usually the same in the Python and C implementations,
# so you need the actual class to be sure of what you're dealing with
__traceback_info__ = type(t)
verifyObject(self._getTargetInterface(), t)

if hasattr(t, 'keys'):
from BTrees.Interfaces import IReadSequence
keys = t.keys()
if type(keys) not in (tuple, list):
verifyObject(IReadSequence, keys)

if hasattr(t, 'values'):
from BTrees.Interfaces import IReadSequence
values = t.values()
if type(values) not in (tuple, list):
verifyObject(IReadSequence, values)

def testPersistentSubclass(self):
# Can we subclass this and Persistent?
Expand Down

0 comments on commit 8bdc05a

Please sign in to comment.