Skip to content

Commit

Permalink
Merge 07fe9be into a0f422b
Browse files Browse the repository at this point in the history
  • Loading branch information
mlenzen committed Nov 23, 2019
2 parents a0f422b + 07fe9be commit 3f51d3a
Show file tree
Hide file tree
Showing 13 changed files with 61 additions and 144 deletions.
2 changes: 0 additions & 2 deletions .travis.yml
Expand Up @@ -4,13 +4,11 @@ dist: xenial # required for Python >= 3.7
language: python

python:
- "2.7"
- "3.4"
- "3.5"
- "3.6"
- "3.7"
- "3.8-dev"
- "pypy"
- "pypy3"

# command to install dependencies, e.g. pip install -r requirements.txt --use-mirrors
Expand Down
15 changes: 8 additions & 7 deletions Makefile
@@ -1,12 +1,13 @@
.PHONY: help
help:
@echo " clean remove unwanted files like .pyc's"
@echo " lint check style with flake8"
@echo " tests run tests (using py.test)"
@echo " testall run tests for all Python versions (using tox)"
@echo " coverage run coverage report"
@echo " publish publish to PyPI"
@echo " docs create HMTL docs (using Sphinx)"
@echo " clean remove unwanted files like .pyc's"
@echo " lint check style with flake8"
@echo " tests run tests (using py.test)"
@echo " testall run tests for all Python versions (using tox)"
@echo " coverage run coverage report"
@echo " publish publish to PyPI"
@echo " publish-force publish to PyPI ignoring tests and linting"
@echo " docs create HMTL docs (using Sphinx)"

.PHONY: tests
tests:
Expand Down
8 changes: 7 additions & 1 deletion README.rst
Expand Up @@ -27,7 +27,7 @@ a ``IndexedDict`` class, which is an ordered mapping whose elements can be acces
in addition to key.
There are also frozen (hashable) varieties of bags and setlists.

Tested against Python 2.7, 3.4, 3.5, 3.6, 3.7, PyPy & PyPy3.
Tested against Python 3.4, 3.5, 3.6, 3.7 & PyPy3.

Getting Started
===============
Expand Down Expand Up @@ -140,6 +140,12 @@ RangeMap
IndexedDict
A mapping that keeps insertion order and allows access by index.

Python 2
--------

The package no longer supports Python 2. The last version to support
Python 2 was 1.0

:Author: Michael Lenzen
:Copyright: 2019 Michael Lenzen
:License: Apache License, Version 2.0
Expand Down
93 changes: 19 additions & 74 deletions collections_extended/_compat.py
@@ -1,90 +1,35 @@
"""Python 2/3 compatibility helpers."""
"""Python version compatibility helpers."""
import sys

__all__ = (
'handle_rich_comp_not_implemented',
'keys_set',
'Collection',
'Hashable',
'Mapping',
'MutableMapping',
'MutableSequence',
'MutableSet',
'Sequence',
'Set',
)

is_py2 = sys.version_info[0] == 2

if is_py2:
def keys_set(d):
"""Return a set of passed dictionary's keys."""
return set(d.keys())

from collections import (
Container,
Hashable,
Iterable,
Mapping,
MutableMapping,
MutableSequence,
MutableSet,
Sequence,
Set,
Sized,
)
else:
keys_set = dict.keys
from collections.abc import (
Container,
Hashable,
Iterable,
Mapping,
MutableMapping,
MutableSequence,
MutableSet,
Sequence,
Set,
Sized,
)

__all__ = ('Collection', )

if sys.version_info < (3, 6):
from abc import ABCMeta
from collections.abc import Sized, Iterable, Container

def _check_methods(C, *methods):
mro = C.__mro__
def _check_methods(klass, *methods):
_missing = object()
for method in methods:
for B in mro:
if method in B.__dict__:
if B.__dict__[method] is None:
return NotImplemented
for superclass in klass.__mro__:
implementation = superclass.__dict__.get(method, _missing)
if implementation is _missing:
continue
elif implementation is None:
return NotImplemented
else:
break
else:
return NotImplemented
return True

class Collection(Sized, Iterable, Container):
"""Backport from Python3.6."""
class Collection(Sized, Iterable, Container, metaclass=ABCMeta):
"""Backport from Python3.6+."""

__slots__ = tuple()

@classmethod
def __subclasshook__(cls, C):
if cls is Collection:
return _check_methods(C, "__len__", "__iter__", "__contains__")
return NotImplemented
def __subclasshook__(cls, klass):
if cls is not Collection:
return NotImplemented
return _check_methods(klass, "__len__", "__iter__", "__contains__")

else:
from collections.abc import Collection


def handle_rich_comp_not_implemented():
"""Correctly handle unimplemented rich comparisons.
In Python 3, return NotImplemented.
In Python 2, raise a TypeError.
"""
if is_py2:
raise TypeError()
else:
return NotImplemented
19 changes: 7 additions & 12 deletions collections_extended/bags.py
@@ -1,16 +1,11 @@
"""Bag class definitions."""
from abc import ABCMeta, abstractmethod
import heapq
from abc import ABCMeta, abstractmethod
from collections import defaultdict
from collections.abc import Hashable, MutableSet, Set
from operator import itemgetter

from ._compat import (
Collection,
Hashable,
MutableSet,
Set,
handle_rich_comp_not_implemented,
)
from ._compat import Collection
from ._util import deprecated

__all__ = (
Expand Down Expand Up @@ -291,22 +286,22 @@ def is_superset(self, other):

def __le__(self, other):
if not isinstance(other, Set):
return handle_rich_comp_not_implemented()
return NotImplemented
return len(self) <= len(other) and self.is_subset(other)

def __lt__(self, other):
if not isinstance(other, Set):
return handle_rich_comp_not_implemented()
return NotImplemented
return len(self) < len(other) and self.is_subset(other)

def __gt__(self, other):
if not isinstance(other, Set):
return handle_rich_comp_not_implemented()
return NotImplemented
return len(self) > len(other) and self.is_superset(other)

def __ge__(self, other):
if not isinstance(other, Set):
return handle_rich_comp_not_implemented()
return NotImplemented
return len(self) >= len(other) and self.is_superset(other)

def __eq__(self, other):
Expand Down
2 changes: 1 addition & 1 deletion collections_extended/bijection.py
@@ -1,5 +1,5 @@
"""Class definition for bijection."""
from ._compat import Mapping, MutableMapping
from collections.abc import Mapping, MutableMapping

__all__ = ('bijection', )

Expand Down
18 changes: 2 additions & 16 deletions collections_extended/range_map.py
@@ -1,8 +1,9 @@
"""RangeMap class definition."""
from abc import ABCMeta, abstractmethod
from bisect import bisect_left, bisect_right
from collections.abc import Mapping, Set

from ._compat import Collection, Mapping, Set
from ._compat import Collection
from .sentinel import NOT_SET


Expand Down Expand Up @@ -431,18 +432,3 @@ def values(self):
def items(self):
"""Return a view of the item pairs."""
return RangeMapItemsView(self)

# Python2 - override slice methods
def __setslice__(self, i, j, value):
"""Implement __setslice__ to override behavior in Python 2.
This is required because empty slices pass integers in python2 as opposed
to None in python 3.
"""
raise SyntaxError("Assigning slices doesn't work in Python 2, use set.")

def __delslice__(self, i, j):
raise SyntaxError("Deleting slices doesn't work in Python 2, use delete.")

def __getslice__(self, i, j):
raise SyntaxError("Getting slices doesn't work in Python 2, use get_range.")
8 changes: 7 additions & 1 deletion collections_extended/setlists.py
@@ -1,8 +1,14 @@
"""Setlist class definitions."""
import random as random_
from collections.abc import (
Hashable,
MutableSequence,
MutableSet,
Sequence,
Set,
)

from . import _util
from ._compat import Hashable, MutableSequence, MutableSet, Sequence, Set

__all__ = ('setlist', 'frozensetlist')

Expand Down
5 changes: 4 additions & 1 deletion docs/index.rst
Expand Up @@ -12,7 +12,10 @@ There are also frozen (hashable) varieties of bags and setlists.

The ABC :class:`collections.abc.Collection` is backported to Python versions < 3.6

It is `tested against`_ Python 2.7, 3.4, 3.5, 3.6, 3.7, PyPy & PyPy3.
It is `tested against`_ Python 3.4, 3.5, 3.6, 3.7 & PyPy3.
The current version no longer supports Python 2, install a
1.x version for a Python 2 compatible version. New features will
not be developed for Python 2 but serious bugs may be fixed.

Contents:

Expand Down
2 changes: 0 additions & 2 deletions setup.py
Expand Up @@ -55,8 +55,6 @@ def long_description():
'License :: OSI Approved :: Apache Software License',
'Operating System :: OS Independent',
'Programming Language :: Python',
'Programming Language :: Python :: 2',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
Expand Down
3 changes: 0 additions & 3 deletions tests/test_bags.py
Expand Up @@ -5,7 +5,6 @@
import pytest

from collections_extended.bags import _basebag, bag, frozenbag
from collections_extended._compat import is_py2


def test_init():
Expand Down Expand Up @@ -42,8 +41,6 @@ def compare_bag_string(b):
assert "'c'" in str(_basebag('abracadabra'))
abra_elems = set(("'a'^5", "'b'^2", "'r'^2", "'c'", "'d'"))
assert compare_bag_string(bag('abracadabra')) == abra_elems
if not is_py2:
assert compare_bag_string(bag('abc')) == compare_bag_string(set('abc'))


def test_count():
Expand Down
28 changes: 5 additions & 23 deletions tests/test_range_map.py
Expand Up @@ -5,7 +5,6 @@
# from hypothesis.strategies import integers
import pytest

from collections_extended._compat import is_py2
from collections_extended.range_map import RangeMap, MappedRange


Expand Down Expand Up @@ -196,18 +195,10 @@ def test_dates():
def test_version_differences():
"""Test python 2 and 3 differences."""
rm = RangeMap({1: 'a', 2: 'b', 3: 'c', 4: 'd', 5: 'e'})
if is_py2:
with pytest.raises(SyntaxError):
rm[3:] = 'a'
with pytest.raises(SyntaxError):
del rm[4:5]
with pytest.raises(SyntaxError):
assert rm[2:] == RangeMap({2: 'b', 3: 'a'})
else:
rm[3:] = 'a'
assert rm == RangeMap({1: 'a', 2: 'b', 3: 'a'})
del rm[1:2]
assert rm == RangeMap({2: 'b', 3: 'a'})
rm[3:] = 'a'
assert rm == RangeMap({1: 'a', 2: 'b', 3: 'a'})
del rm[1:2]
assert rm == RangeMap({2: 'b', 3: 'a'})


def test_slice_errors():
Expand Down Expand Up @@ -257,11 +248,6 @@ def test_delete():
def test_delitem_beginning():
"""Test RangeMap.__delitem__ at the beginning."""
rm = RangeMap({1: 'a', 2: 'b', 3: 'c', 4: 'd', 5: 'e'})
if not is_py2:
with pytest.raises(TypeError):
del rm[2]
with pytest.raises(ValueError):
del rm[2:4:2]
rm.delete(1, 2)
assert rm == RangeMap({2: 'b', 3: 'c', 4: 'd', 5: 'e'})

Expand Down Expand Up @@ -375,11 +361,7 @@ def test_get_range():
rm.get_range(stop=3) ==
RangeMap.from_iterable(((None, 1, 'z'), (1, 2, 'a'), (2, 3, 'b')))
)
if is_py2:
with pytest.raises(SyntaxError):
rm[2:3]
else:
assert rm[2:3] == rm.get_range(2, 3)
assert rm[2:3] == rm.get_range(2, 3)


def test_start_gt_stop():
Expand Down
2 changes: 1 addition & 1 deletion tox.ini
@@ -1,5 +1,5 @@
[tox]
envlist = py27, py34, py35, py36, py37, pypy, pypy3
envlist = py34, py35, py36, py37, pypy3

[testenv]
setenv =
Expand Down

0 comments on commit 3f51d3a

Please sign in to comment.