Skip to content

Commit

Permalink
Merge cb42da4 into cc173e7
Browse files Browse the repository at this point in the history
  • Loading branch information
jamadden committed Apr 5, 2021
2 parents cc173e7 + cb42da4 commit 9af1033
Show file tree
Hide file tree
Showing 18 changed files with 245 additions and 91 deletions.
1 change: 1 addition & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ relative_files = True
exclude_lines =
pragma: no cover
class I[A-Z]\w+\((Interface|I[A-Z].*)\):
self.fail
17 changes: 16 additions & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,12 @@ 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
# the wheels tend not to have it.
PURE_PYTHON: 0

PIP_UPGRADE_STRATEGY: eager
# Don't get warnings about Python 2 support being deprecated. We
Expand Down Expand Up @@ -132,7 +138,12 @@ jobs:
run: |
pip install -U pip
pip install -U setuptools wheel twine
pip install -U coveralls coverage
pip install -U coveralls coverage persistent
- name: Set pure Python
if: ${{ startsWith(matrix.python-version, 'pypy') }}
run: |
echo PURE_PYTHON=1 >> $GITHUB_ENV
- name: Build BTrees
run: |
Expand Down Expand Up @@ -198,6 +209,10 @@ jobs:
key: ${{ runner.os }}-pip-${{ matrix.python-version }}
restore-keys: |
${{ runner.os }}-pip-
- name: Set pure Python
if: ${{ startsWith(matrix.python-version, 'pypy') }}
run: |
echo PURE_PYTHON=1 >> $GITHUB_ENV
- name: Download BTrees wheel
uses: actions/download-artifact@v2
Expand Down
6 changes: 2 additions & 4 deletions .manylinux-install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ else
export CFLAGS="-Ofast $CFLAGS"
fi

export PURE_PYTHON=0

# Compile wheels
cd /io/
for variant in `ls -d /opt/python/cp{27,36,37,38,39}*`; do
Expand All @@ -49,10 +51,6 @@ for variant in `ls -d /opt/python/cp{27,36,37,38,39}*`; do
# in just one interpreter, the newest one on the list (which in principle
# should be the fastest), and we don't install the ZODB extra.
if [[ "${PYBIN}" == *"cp39"* ]]; then
# Until we move from ./BTrees/ to ./src/BTrees/,
# installing BTrees as non-editable is incompatible with using
# /io/ as the working directory: the local directory shadows the installed
# version, and we can't import the C extensions.
"${PYBIN}/pip" install -e .[test]
"${PYBIN}/python" -c 'import BTrees.OOBTree; print(BTrees.OOBTree.BTree, BTrees.OOBTree.BTreePy)'
"${PYBIN}/python" -m unittest discover -s src
Expand Down
30 changes: 30 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,36 @@

- Build and upload aarch64 wheels.

- Make a value of ``0`` in the ``PURE_PYTHON`` environment variable
require the C extensions (except on PyPy). Previously, and if this
variable is unset, missing or unusable C extensions would be
silently ignored. With this variable set to ``0``, an
``ImportError`` will be raised if the C extensions are unavailable.
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
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: 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
65 changes: 29 additions & 36 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 All @@ -125,7 +111,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 +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
84 changes: 71 additions & 13 deletions src/BTrees/_compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,9 @@
import sys

PYPY = hasattr(sys, 'pypy_version_info')
# We can and do build the C extensions on PyPy, but
# as of Persistent 4.2.5 the persistent C extension is not
# built on PyPy, so importing our C extension will fail anyway.
PURE_PYTHON = os.environ.get('PURE_PYTHON', PYPY)


if sys.version_info[0] < 3: #pragma NO COVER Python2
if sys.version_info[0] < 3: # pragma: no cover Python2

PY2 = True
PY3 = False
Expand All @@ -43,7 +39,7 @@ def compare(x, y):
def _ascii(x):
return bytes(x)

else: #pragma NO COVER Python3
else: # pragma: no cover Python3

PY2 = False
PY3 = True
Expand All @@ -67,18 +63,80 @@ def _ascii(x):
return bytes(x, 'ascii')


def import_c_extension(mod_globals):
def _c_optimizations_required():
"""
Return a true value if the C optimizations are required.
This uses the ``PURE_PYTHON`` variable as documented in `import_c_extension`.
"""
pure_env = os.environ.get('PURE_PYTHON')
require_c = pure_env == "0"
return require_c


def _c_optimizations_available(module_name):
"""
Return the C optimization module, if available, otherwise
a false value.
If the optimizations are required but not available, this
raises the ImportError.
This does not say whether they should be used or not.
"""
import importlib
catch = () if _c_optimizations_required() else (ImportError,)
try:
return importlib.import_module('BTrees._' + module_name)
except catch: # pragma: no cover
return False


def _c_optimizations_ignored():
"""
The opposite of `_c_optimizations_required`.
"""
pure_env = os.environ.get('PURE_PYTHON')
return pure_env != "0" if pure_env is not None else PYPY


def _should_attempt_c_optimizations():
"""
Return a true value if we should attempt to use the C optimizations.
This takes into account whether we're on PyPy and the value of the
``PURE_PYTHON`` environment variable, as defined in `import_c_extension`.
"""
if PYPY:
return False

if _c_optimizations_required():
return True
return not _c_optimizations_ignored()


def import_c_extension(mod_globals):
"""
Call this function with the globals of a module that implements
Python versions of a BTree family to find the C optimizations.
If the ``PURE_PYTHON`` environment variable is set to any value
other than ``"0"``, or we're on PyPy, ignore the C implementation.
If the C implementation cannot be imported, return the Python
version. If ``PURE_PYTHON`` is set to ``"0"``, *require* the C
implementation (let the ImportError propagate); the exception again
is PyPy, where we never use the C extension (although it builds here, the
``persistent`` library doesn't provide native extensions for PyPy).
"""
c_module = None
module_name = mod_globals['__name__']
assert module_name.startswith('BTrees.')
module_name = module_name.split('.')[1]
if not PURE_PYTHON:
try:
c_module = importlib.import_module('BTrees._' + module_name)
except ImportError:
pass
if c_module is not None:
if _should_attempt_c_optimizations():
c_module = _c_optimizations_available(module_name)

if c_module:
new_values = dict(c_module.__dict__)
new_values.pop("__name__", None)
new_values.pop('__file__', None)
Expand Down
Loading

0 comments on commit 9af1033

Please sign in to comment.