Skip to content

Commit

Permalink
Merge pull request #159 from zopefoundation/issue138
Browse files Browse the repository at this point in the history
Make objects actually implement interfaces and update interface definitions
  • Loading branch information
jamadden committed Apr 7, 2021
2 parents 9760743 + fa2cbc4 commit 49776df
Show file tree
Hide file tree
Showing 11 changed files with 187 additions and 84 deletions.
1 change: 1 addition & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ env:
PYTHONDEVMODE: 1
PYTHONFAULTHANDLER: 1
ZOPE_INTERFACE_STRICT_IRO: 1
ZOPE_INTERFACE_LOG_CHANGED_IRO: 1
# Require C extensions; this will be disabled later for
# PyPy because zope.interface through at least 5.3
# tries to import its C module even on PyPy with this, but
Expand Down
22 changes: 22 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,28 @@
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__``.

- Adjust the definition of ``ISet`` to produce the same resolution
order under the C3 and legacy orderings. This means that the legacy
order has changed slightly, but that this package emits no warnings
when ``ZOPE_INTERFACE_LOG_CHANGED_IRO=1``. Note that the legacy
order was not being used for these objects because the C3 ordering
was still consistent; it could only be obtained using
``ZOPE_INTERFACE_USE_LEGACY_IRO=1``. See `PR 159
<https://github.com/zopefoundation/BTrees/pull/159>`_ for all the
interface updates.

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

Expand Down
25 changes: 23 additions & 2 deletions docs/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,32 @@ 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
:mod:`zope.interface.common.sequence.IMinimalSequence <zope.interface.common.sequence>`.

.. caution::

Before version 4.8.0, most of these interfaces served as
documentation only, and were *not* implemented by the classes of
this package. For example, :class:`BTrees.OOBTree.BTree` did *not*
implement `IBTree`. (The exceptions were the :class:`IBTreeModule`
and :class:`IBTreeFamily` families of interfaces and
implementations.)

Beginning with version 4.8.0, objects implement the expected
interface; the ``BTree`` classes implement ``IBTree``, the set
classes implement the appropriate set interface and so on.


.. autointerface:: ICollection
.. autointerface:: IReadSequence
.. autointerface:: IKeyed
.. autointerface:: ISetMutable
.. autointerface:: ISized
.. autointerface:: IKeySequence
.. autointerface:: IMinimalDictionary
.. autointerface:: IDictionaryIsh
Expand Down
9 changes: 5 additions & 4 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@
exclude_patterns = ['_build']

# The reST default role (used for this markup: `text`) to use for all documents.
#default_role = None
default_role = "obj"

# If true, '()' will be appended to :func: etc. cross-reference text.
#add_function_parentheses = True
Expand Down 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: 2 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,8 @@ def BTreeExtension(family):
# 4.1.0 is the first version that PURE_PYTHON can run
# ZODB tests
'persistent >= 4.1.0',
'zope.interface',
# 5.0.0 added zope.interface.common.collections
'zope.interface >= 5.0.0',
]

TESTS_REQUIRE = [
Expand Down
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
160 changes: 83 additions & 77 deletions src/BTrees/Interfaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,35 +13,29 @@
##############################################################################

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.
# Docs deprecated only in docs/api.rst.
IReadSequence = IMinimalSequence

class IKeyed(ICollection):

Expand All @@ -52,25 +46,29 @@ 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 :mod:`IMinimalSequence <zope.interface.common.sequence>`
containing the keys in the collection.
The type of the :class:`~BTrees.Interfaces.IReadSequence` is not specified. It could be a list
or a tuple or some other type.
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
arguments, or by position.
If a min is specified, then output is constrained to keys greater
than or equal to the given min, and, if excludemin is specified and
true, is further constrained to keys strictly greater than min. A
min value of None is ignored. If min is None or not specified, and
excludemin is true, the smallest key is excluded.
If a max is specified, then output is constrained to keys less than
or equal to the given max, and, if excludemax is specified and
true, is further constrained to keys strictly less than max. A max
value of None is ignored. If max is None or not specified, and
excludemax is true, the largest key is excluded.
If a *min* is specified, then output is constrained to keys
greater than or equal to the given min, and, if *excludemin*
is specified and true, is further constrained to keys strictly
greater than *min*. A *min* value of `None` is ignored. If
*min* is `None` or not specified, and *excludemin* is true,
the smallest key is excluded.
If a *max* is specified, then output is constrained to keys
less than or equal to the given *max*, and, if *excludemax* is
specified and true, is further constrained to keys strictly
less than *max*. A *max* value of `None` is ignored. If *max*
is `None` or not specified, and *excludemax* is true, the
largest key is excluded.
"""

def maxKey(key=None):
Expand Down Expand Up @@ -108,13 +106,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 All @@ -125,7 +116,7 @@ def __getitem__(index):
"""


class ISet(IKeySequence, ISetMutable):
class ISet(ISetMutable, IKeySequence):
def __and__(other):
"""Shortcut for :meth:`~BTrees.Interfaces.IMerge.intersection`"""

Expand All @@ -147,7 +138,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,53 +172,61 @@ 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 :mod:`IMinimalSequence <zope.interface.common.sequence.IMinimalSequence>`
containing the values in the collection.
The type of the :class:`~BTrees.Interfaces.IReadSequence` is not specified. It could be a list
or a tuple or some other type.
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
arguments, or by position.
If a min is specified, then output is constrained to values whose
keys are greater than or equal to the given min, and, if excludemin
is specified and true, is further constrained to values whose keys
are strictly greater than min. A min value of None is ignored. If
min is None or not specified, and excludemin is true, the value
corresponding to the smallest key is excluded.
If a max is specified, then output is constrained to values whose
keys are less than or equal to the given max, and, if excludemax is
specified and true, is further constrained to values whose keys are
strictly less than max. A max value of None is ignored. If max is
None or not specified, and excludemax is true, the value
corresponding to the largest key is excluded.
If a *min* is specified, then output is constrained to values
whose keys are greater than or equal to the given *min*, and, if
*excludemin* is specified and true, is further constrained to
values whose keys are strictly greater than *min*. A *min* value
of `None` is ignored. If *min* is `None` or not specified, and
*excludemin* is true, the value corresponding to the smallest
key is excluded.
If a *max* is specified, then output is constrained to values
whose keys are less than or equal to the given *max*, and, if
*excludemax* is specified and true, is further constrained to
values whose keys are strictly less than *max*. A *max* value of
`None` is ignored. If *max* is `None` or not specified, and
*excludemax* is true, the value corresponding to the largest key
is excluded.
"""

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.
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
or a tuple or some other type.
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
arguments, or by position.
If a min is specified, then output is constrained to items whose
keys are greater than or equal to the given min, and, if excludemin
is specified and true, is further constrained to items whose keys
are strictly greater than min. A min value of None is ignored. If
min is None or not specified, and excludemin is true, the item with
the smallest key is excluded.
If a max is specified, then output is constrained to items whose
keys are less than or equal to the given max, and, if excludemax is
specified and true, is further constrained to items whose keys are
strictly less than max. A max value of None is ignored. If max is
None or not specified, and excludemax is true, the item with the
largest key is excluded.
If a *min* is specified, then output is constrained to items
whose keys are greater than or equal to the given *min*, and,
if *excludemin* is specified and true, is further constrained
to items whose keys are strictly greater than *min*. A *min*
value of `None` is ignored. If *min* is `None` or not
specified, and *excludemin* is true, the item with the
smallest key is excluded.
If a *max* is specified, then output is constrained to items
whose keys are less than or equal to the given *max*, and, if
*excludemax is specified and true, is further constrained to
items whose keys are strictly less than *max*. A *max* value
of `None` is ignored. If *max* is `None` or not specified, and
*excludemax* is true, the item with the largest key is
excluded.
"""

class IDictionaryIsh(IMinimalDictionary):
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
Loading

0 comments on commit 49776df

Please sign in to comment.