Hello community,
here is the log from the commit of package python-immutables for
openSUSE:Factory checked in at 2020-10-29 09:47:45
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-immutables (Old)
and /work/SRC/openSUSE:Factory/.python-immutables.new.3463 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-immutables"
Thu Oct 29 09:47:45 2020 rev:4 rq:841417 version:0.14
Changes:
--------
--- /work/SRC/openSUSE:Factory/python-immutables/python-immutables.changes
2020-03-10 09:51:12.984019255 +0100
+++
/work/SRC/openSUSE:Factory/.python-immutables.new.3463/python-immutables.changes
2020-10-29 09:47:47.452139414 +0100
@@ -1,0 +2,16 @@
+Tue Oct 13 07:42:13 UTC 2020 - Dirk Mueller <[email protected]>
+
+- skip tests that fail on 32bit
+
+-------------------------------------------------------------------
+Wed Sep 16 11:27:37 UTC 2020 - Dirk Mueller <[email protected]>
+
+- update to 0.14:
+ * python 3.8 support
+ * Various improvements w.r.t. type annotations & typing
+ * Fix pure-Python implementation to accept keyword argument
+ * Fix the mutation API to maintain elements count correctly
+ * Allow None to be used as key in pure-Python implementation.
+- remove py38.patch (upstream)
+
+-------------------------------------------------------------------
Old:
----
immutables-0.11.tar.gz
py38.patch
New:
----
immutables-0.14.tar.gz
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ python-immutables.spec ++++++
--- /var/tmp/diff_new_pack.TUCfQU/_old 2020-10-29 09:47:47.984139917 +0100
+++ /var/tmp/diff_new_pack.TUCfQU/_new 2020-10-29 09:47:47.988139921 +0100
@@ -19,13 +19,12 @@
%{?!python_module:%define python_module() python-%{**} python3-%{**}}
%define skip_python2 1
Name: python-immutables
-Version: 0.11
+Version: 0.14
Release: 0
Summary: Immutable collections for Python
License: Apache-2.0
URL: https://github.com/MagicStack/immutables
Source:
https://files.pythonhosted.org/packages/source/i/immutables/immutables-%{version}.tar.gz
-Patch0: py38.patch
BuildRequires: %{python_module devel}
BuildRequires: %{python_module setuptools}
BuildRequires: fdupes
@@ -37,7 +36,6 @@
%prep
%setup -q -n immutables-%{version}
-%patch0 -p1
sed -i 's/\.system//' setup.py
%build
@@ -51,6 +49,11 @@
}
%check
+# Fails on 32bit for some reason
+%ifarch %ix86
+rm -v tests/test_none_keys.py
+%endif
+
%python_exec setup.py test
%files %{python_files}
++++++ immutables-0.11.tar.gz -> immutables-0.14.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/immutables-0.11/PKG-INFO new/immutables-0.14/PKG-INFO
--- old/immutables-0.11/PKG-INFO 2019-10-15 15:59:44.000000000 +0200
+++ new/immutables-0.14/PKG-INFO 2020-05-18 06:37:31.000000000 +0200
@@ -1,6 +1,6 @@
-Metadata-Version: 1.1
+Metadata-Version: 1.2
Name: immutables
-Version: 0.11
+Version: 0.14
Summary: Immutable Collections
Home-page: https://github.com/MagicStack/immutables
Author: MagicStack Inc
@@ -9,11 +9,8 @@
Description: immutables
==========
- .. image::
https://travis-ci.org/MagicStack/immutables.svg?branch=master
- :target: https://travis-ci.org/MagicStack/immutables
-
- .. image::
https://ci.appveyor.com/api/projects/status/tgbc6tq56u63qqhf?svg=true
- :target: https://ci.appveyor.com/project/MagicStack/immutables
+ .. image::
https://github.com/MagicStack/immutables/workflows/Tests/badge.svg?branch=master
+ :target:
https://github.com/MagicStack/immutables/actions?query=workflow%3ATests+branch%3Amaster+event%3Apush
.. image:: https://img.shields.io/pypi/v/immutables.svg
:target: https://pypi.python.org/pypi/immutables
@@ -139,7 +136,9 @@
Classifier: Programming Language :: Python :: 3.5
Classifier: Programming Language :: Python :: 3.6
Classifier: Programming Language :: Python :: 3.7
+Classifier: Programming Language :: Python :: 3.8
Classifier: Operating System :: POSIX
Classifier: Operating System :: MacOS :: MacOS X
Classifier: Operating System :: Microsoft :: Windows
Provides: immutables
+Requires-Python: >=3.5
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/immutables-0.11/README.rst
new/immutables-0.14/README.rst
--- old/immutables-0.11/README.rst 2019-10-15 15:59:09.000000000 +0200
+++ new/immutables-0.14/README.rst 2020-05-18 06:37:20.000000000 +0200
@@ -1,11 +1,8 @@
immutables
==========
-.. image:: https://travis-ci.org/MagicStack/immutables.svg?branch=master
- :target: https://travis-ci.org/MagicStack/immutables
-
-.. image::
https://ci.appveyor.com/api/projects/status/tgbc6tq56u63qqhf?svg=true
- :target: https://ci.appveyor.com/project/MagicStack/immutables
+.. image::
https://github.com/MagicStack/immutables/workflows/Tests/badge.svg?branch=master
+ :target:
https://github.com/MagicStack/immutables/actions?query=workflow%3ATests+branch%3Amaster+event%3Apush
.. image:: https://img.shields.io/pypi/v/immutables.svg
:target: https://pypi.python.org/pypi/immutables
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/immutables-0.11/immutables/__init__.py
new/immutables-0.14/immutables/__init__.py
--- old/immutables-0.11/immutables/__init__.py 2019-10-15 15:59:09.000000000
+0200
+++ new/immutables-0.14/immutables/__init__.py 2020-05-18 06:37:20.000000000
+0200
@@ -1,3 +1,5 @@
+# flake8: noqa
+
try:
from ._map import Map
except ImportError:
@@ -6,6 +8,6 @@
import collections.abc as _abc
_abc.Mapping.register(Map)
+from ._version import __version__
__all__ = 'Map',
-__version__ = '0.11'
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/immutables-0.11/immutables/_map.c
new/immutables-0.14/immutables/_map.c
--- old/immutables-0.11/immutables/_map.c 2019-10-15 15:59:09.000000000
+0200
+++ new/immutables-0.14/immutables/_map.c 2020-05-18 06:37:20.000000000
+0200
@@ -393,12 +393,13 @@
map_update(uint64_t mutid, MapObject *o, PyObject *src);
-#ifdef NDEBUG
+#if !defined(NDEBUG)
static void
_map_node_array_validate(void *o)
{
assert(IS_ARRAY_NODE(o));
MapNode_Array *node = (MapNode_Array*)(o);
+ assert(node->a_count <= HAMT_ARRAY_NODE_SIZE);
Py_ssize_t i = 0, count = 0;
for (; i < HAMT_ARRAY_NODE_SIZE; i++) {
if (node->a_array[i] != NULL) {
@@ -1109,7 +1110,7 @@
}
}
-#ifdef NDEBUG
+#if !defined(NDEBUG)
/* Ensure that Collision.without implementation
converts to Bitmap nodes itself.
*/
@@ -1744,6 +1745,7 @@
Py_ssize_t i;
VALIDATE_ARRAY_NODE(node)
+ assert(node->a_count <= HAMT_ARRAY_NODE_SIZE);
/* Create a new Array node. */
clone = (MapNode_Array *)map_node_array_new(node->a_count, mutid);
@@ -1806,6 +1808,7 @@
if (mutid != 0 && self->a_mutid == mutid) {
new_node = self;
+ self->a_count++;
Py_INCREF(self);
}
else {
@@ -1940,9 +1943,9 @@
if (target == NULL) {
return W_ERROR;
}
- target->a_count = new_count;
}
+ target->a_count = new_count;
Py_CLEAR(target->a_array[idx]);
*new_node = (MapNode*)target; /* borrow */
@@ -2006,7 +2009,7 @@
}
else {
-#ifdef NDEBUG
+#if !defined(NDEBUG)
if (IS_COLLISION_NODE(node)) {
assert(
(map_node_collision_count(
@@ -2101,7 +2104,9 @@
goto error;
}
- if (_map_dump_format(writer, "ArrayNode(id=%p):\n", node)) {
+ if (_map_dump_format(writer, "ArrayNode(id=%p count=%zd):\n",
+ node, node->a_count)
+ ) {
goto error;
}
@@ -2298,7 +2303,7 @@
Py_ssize_t pos = iter->i_pos[level];
if (pos + 1 >= Py_SIZE(node)) {
-#ifdef NDEBUG
+#if !defined(NDEBUG)
assert(iter->i_level >= 0);
iter->i_nodes[iter->i_level] = NULL;
#endif
@@ -2335,7 +2340,7 @@
Py_ssize_t pos = iter->i_pos[level];
if (pos + 1 >= Py_SIZE(node)) {
-#ifdef NDEBUG
+#if !defined(NDEBUG)
assert(iter->i_level >= 0);
iter->i_nodes[iter->i_level] = NULL;
#endif
@@ -2359,7 +2364,7 @@
Py_ssize_t pos = iter->i_pos[level];
if (pos >= HAMT_ARRAY_NODE_SIZE) {
-#ifdef NDEBUG
+#if !defined(NDEBUG)
assert(iter->i_level >= 0);
iter->i_nodes[iter->i_level] = NULL;
#endif
@@ -2381,7 +2386,7 @@
}
}
-#ifdef NDEBUG
+#if !defined(NDEBUG)
assert(iter->i_level >= 0);
iter->i_nodes[iter->i_level] = NULL;
#endif
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/immutables-0.11/immutables/_map.pyi
new/immutables-0.14/immutables/_map.pyi
--- old/immutables-0.11/immutables/_map.pyi 2019-10-15 15:59:09.000000000
+0200
+++ new/immutables-0.14/immutables/_map.pyi 2020-05-18 06:37:20.000000000
+0200
@@ -3,10 +3,10 @@
from typing import Hashable
from typing import Iterable
from typing import Iterator
-from typing import Literal
from typing import Mapping
from typing import MutableMapping
from typing import NoReturn
+from typing import overload
from typing import Tuple
from typing import Type
from typing import TypeVar
@@ -40,14 +40,20 @@
class Map(Mapping[K, V]):
+ @overload
+ def __init__(self, **kw: V) -> None: ...
+ @overload
def __init__(
- self, col: Union[Mapping[K, V], Iterable[Tuple[K, V]]] = ..., **kw: V
- ): ...
- def __reduce__(self) -> NoReturn: ...
+ self, col: Union[Mapping[K, V], Iterable[Tuple[K, V]]], **kw: V
+ ) -> None: ...
+ def __reduce__(self) -> Tuple[Type[Map], Tuple[dict]]: ...
def __len__(self) -> int: ...
def __eq__(self, other: Any) -> bool: ...
+ @overload
+ def update(self, **kw: V) -> Map[str, V]: ...
+ @overload
def update(
- self, col: Union[Mapping[K, V], Iterable[Tuple[K, V]]] = ..., **kw: V
+ self, col: Union[Mapping[K, V], Iterable[Tuple[K, V]]], **kw: V
) -> Map[K, V]: ...
def mutate(self) -> MapMutation[K, V]: ...
def set(self, key: K, val: V) -> Map[K, V]: ...
@@ -71,7 +77,7 @@
def __init__(self, count: int, root: BitmapNode) -> None: ...
def set(self, key: K, val: V) -> None: ...
def __enter__(self: S) -> S: ...
- def __exit__(self, *exc: Any) -> Literal[False]: ...
+ def __exit__(self, *exc: Any): ...
def __iter__(self) -> NoReturn: ...
def __delitem__(self, key: K) -> None: ...
def __setitem__(self, key: K, val: V) -> None: ...
@@ -79,9 +85,12 @@
def get(self, key: K, default: D = ...) -> Union[V, D]: ...
def __getitem__(self, key: K) -> V: ...
def __contains__(self, key: Any) -> bool: ...
+ @overload
+ def update(self, **kw: V) -> None: ...
+ @overload
def update(
- self, col: Union[Mapping[K, V], Iterable[Tuple[K, V]]] = ..., **kw: V
- ): ...
+ self, col: Union[Mapping[K, V], Iterable[Tuple[K, V]]], **kw: V
+ ) -> None: ...
def finish(self) -> Map[K, V]: ...
def __len__(self) -> int: ...
def __eq__(self, other: Any) -> bool: ...
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/immutables-0.11/immutables/_testutils.py
new/immutables-0.14/immutables/_testutils.py
--- old/immutables-0.11/immutables/_testutils.py 1970-01-01
01:00:00.000000000 +0100
+++ new/immutables-0.14/immutables/_testutils.py 2020-05-18
06:37:20.000000000 +0200
@@ -0,0 +1,80 @@
+class HashKey:
+ _crasher = None
+
+ def __init__(self, hash, name, *, error_on_eq_to=None):
+ assert hash != -1
+ self.name = name
+ self.hash = hash
+ self.error_on_eq_to = error_on_eq_to
+
+ def __repr__(self):
+ if self._crasher is not None and self._crasher.error_on_repr:
+ raise ReprError
+ return '<Key name:{} hash:{}>'.format(self.name, self.hash)
+
+ def __hash__(self):
+ if self._crasher is not None and self._crasher.error_on_hash:
+ raise HashingError
+
+ return self.hash
+
+ def __eq__(self, other):
+ if not isinstance(other, HashKey):
+ return NotImplemented
+
+ if self._crasher is not None and self._crasher.error_on_eq:
+ raise EqError
+
+ if self.error_on_eq_to is not None and self.error_on_eq_to is other:
+ raise ValueError('cannot compare {!r} to {!r}'.format(self, other))
+ if other.error_on_eq_to is not None and other.error_on_eq_to is self:
+ raise ValueError('cannot compare {!r} to {!r}'.format(other, self))
+
+ return (self.name, self.hash) == (other.name, other.hash)
+
+
+class KeyStr(str):
+
+ def __hash__(self):
+ if HashKey._crasher is not None and HashKey._crasher.error_on_hash:
+ raise HashingError
+ return super().__hash__()
+
+ def __eq__(self, other):
+ if HashKey._crasher is not None and HashKey._crasher.error_on_eq:
+ raise EqError
+ return super().__eq__(other)
+
+ def __repr__(self, other):
+ if HashKey._crasher is not None and HashKey._crasher.error_on_repr:
+ raise ReprError
+ return super().__eq__(other)
+
+
+class HashKeyCrasher:
+
+ def __init__(self, *, error_on_hash=False, error_on_eq=False,
+ error_on_repr=False):
+ self.error_on_hash = error_on_hash
+ self.error_on_eq = error_on_eq
+ self.error_on_repr = error_on_repr
+
+ def __enter__(self):
+ if HashKey._crasher is not None:
+ raise RuntimeError('cannot nest crashers')
+ HashKey._crasher = self
+
+ def __exit__(self, *exc):
+ HashKey._crasher = None
+
+
+class HashingError(Exception):
+ pass
+
+
+class EqError(Exception):
+ pass
+
+
+class ReprError(Exception):
+ pass
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/immutables-0.11/immutables/_version.py
new/immutables-0.14/immutables/_version.py
--- old/immutables-0.11/immutables/_version.py 1970-01-01 01:00:00.000000000
+0100
+++ new/immutables-0.14/immutables/_version.py 2020-05-18 06:37:20.000000000
+0200
@@ -0,0 +1,13 @@
+# This file MUST NOT contain anything but the __version__ assignment.
+#
+# When making a release, change the value of __version__
+# to an appropriate value, and open a pull request against
+# the correct branch (master if making a new feature release).
+# The commit message MUST contain a properly formatted release
+# log, and the commit must be signed.
+#
+# The release automation will: build and test the packages for the
+# supported platforms, publish the packages on PyPI, merge the PR
+# to the target branch, create a Git tag pointing to the commit.
+
+__version__ = '0.14'
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/immutables-0.11/immutables/map.py
new/immutables-0.14/immutables/map.py
--- old/immutables-0.11/immutables/map.py 2019-10-15 15:59:09.000000000
+0200
+++ new/immutables-0.14/immutables/map.py 2020-05-18 06:37:20.000000000
+0200
@@ -46,6 +46,13 @@
W_EMPTY, W_NEWNODE, W_NOT_FOUND = range(3)
void = object()
+class _Unhashable:
+ __slots__ = ()
+ __hash__ = None
+
+_NULL = _Unhashable()
+del _Unhashable
+
class BitmapNode:
@@ -70,7 +77,7 @@
key_or_null = self.array[key_idx]
val_or_node = self.array[val_idx]
- if key_or_null is None:
+ if key_or_null is _NULL:
sub_node, added = val_or_node.assoc(
shift + 5, hash, key, val, mutid)
if val_or_node is sub_node:
@@ -111,12 +118,12 @@
mutid)
if mutid and mutid == self.mutid:
- self.array[key_idx] = None
+ self.array[key_idx] = _NULL
self.array[val_idx] = sub_node
return self, True
else:
ret = self.clone(mutid)
- ret.array[key_idx] = None
+ ret.array[key_idx] = _NULL
ret.array[val_idx] = sub_node
return ret, True
@@ -153,7 +160,7 @@
key_or_null = self.array[key_idx]
val_or_node = self.array[val_idx]
- if key_or_null is None:
+ if key_or_null is _NULL:
return val_or_node.find(shift + 5, hash, key)
if key == key_or_null:
@@ -173,7 +180,7 @@
key_or_null = self.array[key_idx]
val_or_node = self.array[val_idx]
- if key_or_null is None:
+ if key_or_null is _NULL:
res, sub_node = val_or_node.without(shift + 5, hash, key, mutid)
if res is W_EMPTY:
@@ -182,7 +189,7 @@
elif res is W_NEWNODE:
if (type(sub_node) is BitmapNode and
sub_node.size == 2 and
- sub_node.array[0] is not None):
+ sub_node.array[0] is not _NULL):
if mutid and mutid == self.mutid:
self.array[key_idx] = sub_node.array[0]
@@ -231,7 +238,7 @@
for i in range(0, self.size, 2):
key_or_null = self.array[i]
- if key_or_null is None:
+ if key_or_null is _NULL:
val_or_node = self.array[i + 1]
yield from val_or_node.keys()
else:
@@ -242,7 +249,7 @@
key_or_null = self.array[i]
val_or_node = self.array[i + 1]
- if key_or_null is None:
+ if key_or_null is _NULL:
yield from val_or_node.values()
else:
yield val_or_node
@@ -252,7 +259,7 @@
key_or_null = self.array[i]
val_or_node = self.array[i + 1]
- if key_or_null is None:
+ if key_or_null is _NULL:
yield from val_or_node.items()
else:
yield key_or_null, val_or_node
@@ -269,8 +276,8 @@
pad = ' ' * (level + 2)
- if key_or_null is None:
- buf.append(pad + 'None:')
+ if key_or_null is _NULL:
+ buf.append(pad + 'NULL:')
val_or_node.dump(buf, level + 2)
else:
buf.append(pad + '{!r}: {!r}'.format(key_or_null, val_or_node))
@@ -328,7 +335,7 @@
else:
new_node = BitmapNode(
- 2, map_bitpos(self.hash, shift), [None, self], mutid)
+ 2, map_bitpos(self.hash, shift), [_NULL, self], mutid)
return new_node.assoc(shift, hash, key, val, mutid)
def without(self, shift, hash, key, mutid):
@@ -433,7 +440,17 @@
class Map:
- def __init__(self, col=None, **kw):
+ def __init__(self, *args, **kw):
+ if not args:
+ col = None
+ elif len(args) == 1:
+ col = args[0]
+ else:
+ raise TypeError(
+ "immutables.Map expected at most 1 arguments, "
+ "got {}".format(len(args))
+ )
+
self.__count = 0
self.__root = BitmapNode(0, 0, [], 0)
self.__hash = -1
@@ -483,8 +500,18 @@
return True
- def update(self, col=None, **kw):
+ def update(self, *args, **kw):
+ if not args:
+ col = None
+ elif len(args) == 1:
+ col = args[0]
+ else:
+ raise TypeError(
+ "update expected at most 1 arguments, got {}".format(len(args))
+ )
+
it = None
+
if col is not None:
if hasattr(col, 'items'):
it = iter(col.items())
@@ -721,7 +748,16 @@
else:
return True
- def update(self, col=None, **kw):
+ def update(self, *args, **kw):
+ if not args:
+ col = None
+ elif len(args) == 1:
+ col = args[0]
+ else:
+ raise TypeError(
+ "update expected at most 1 arguments, got {}".format(len(args))
+ )
+
if self.__mutid == 0:
raise ValueError('mutation {!r} has been finished'.format(self))
@@ -740,8 +776,7 @@
it = iter(kw.items())
if it is None:
-
- return self
+ return
root = self.__root
count = self.__count
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/immutables-0.11/immutables.egg-info/PKG-INFO
new/immutables-0.14/immutables.egg-info/PKG-INFO
--- old/immutables-0.11/immutables.egg-info/PKG-INFO 2019-10-15
15:59:44.000000000 +0200
+++ new/immutables-0.14/immutables.egg-info/PKG-INFO 2020-05-18
06:37:30.000000000 +0200
@@ -1,6 +1,6 @@
-Metadata-Version: 1.1
+Metadata-Version: 1.2
Name: immutables
-Version: 0.11
+Version: 0.14
Summary: Immutable Collections
Home-page: https://github.com/MagicStack/immutables
Author: MagicStack Inc
@@ -9,11 +9,8 @@
Description: immutables
==========
- .. image::
https://travis-ci.org/MagicStack/immutables.svg?branch=master
- :target: https://travis-ci.org/MagicStack/immutables
-
- .. image::
https://ci.appveyor.com/api/projects/status/tgbc6tq56u63qqhf?svg=true
- :target: https://ci.appveyor.com/project/MagicStack/immutables
+ .. image::
https://github.com/MagicStack/immutables/workflows/Tests/badge.svg?branch=master
+ :target:
https://github.com/MagicStack/immutables/actions?query=workflow%3ATests+branch%3Amaster+event%3Apush
.. image:: https://img.shields.io/pypi/v/immutables.svg
:target: https://pypi.python.org/pypi/immutables
@@ -139,7 +136,9 @@
Classifier: Programming Language :: Python :: 3.5
Classifier: Programming Language :: Python :: 3.6
Classifier: Programming Language :: Python :: 3.7
+Classifier: Programming Language :: Python :: 3.8
Classifier: Operating System :: POSIX
Classifier: Operating System :: MacOS :: MacOS X
Classifier: Operating System :: Microsoft :: Windows
Provides: immutables
+Requires-Python: >=3.5
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/immutables-0.11/immutables.egg-info/SOURCES.txt
new/immutables-0.14/immutables.egg-info/SOURCES.txt
--- old/immutables-0.11/immutables.egg-info/SOURCES.txt 2019-10-15
15:59:44.000000000 +0200
+++ new/immutables-0.14/immutables.egg-info/SOURCES.txt 2020-05-18
06:37:31.000000000 +0200
@@ -6,6 +6,8 @@
immutables/_map.c
immutables/_map.h
immutables/_map.pyi
+immutables/_testutils.py
+immutables/_version.py
immutables/map.py
immutables/py.typed
immutables.egg-info/PKG-INFO
@@ -13,4 +15,6 @@
immutables.egg-info/dependency_links.txt
immutables.egg-info/top_level.txt
tests/__init__.py
-tests/test_map.py
\ No newline at end of file
+tests/test_issue24.py
+tests/test_map.py
+tests/test_none_keys.py
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/immutables-0.11/setup.py new/immutables-0.14/setup.py
--- old/immutables-0.11/setup.py 2019-10-15 15:59:09.000000000 +0200
+++ new/immutables-0.14/setup.py 2020-05-18 06:37:20.000000000 +0200
@@ -1,4 +1,4 @@
-import os.path
+import os
import platform
import setuptools
@@ -10,7 +10,7 @@
with open(os.path.join(
- os.path.dirname(__file__), 'immutables', '__init__.py')) as f:
+ os.path.dirname(__file__), 'immutables', '_version.py')) as f:
for line in f:
if line.startswith('__version__ ='):
_, _, version = line.partition('=')
@@ -18,15 +18,24 @@
break
else:
raise RuntimeError(
- 'unable to read the version from immutables/__init__.py')
+ 'unable to read the version from immutables/_version.py')
if platform.python_implementation() == 'CPython':
+ if os.environ.get("DEBUG_IMMUTABLES") == '1':
+ define_macros = []
+ undef_macros = ['NDEBUG']
+ else:
+ define_macros = [('NDEBUG', '1')]
+ undef_macros = []
+
ext_modules = [
setuptools.Extension(
"immutables._map",
["immutables/_map.c"],
- extra_compile_args=CFLAGS)
+ extra_compile_args=CFLAGS,
+ define_macros=define_macros,
+ undef_macros=undef_macros)
]
else:
ext_modules = []
@@ -41,6 +50,7 @@
version=VERSION,
description='Immutable Collections',
long_description=readme,
+ python_requires='>=3.5',
classifiers=[
'License :: OSI Approved :: Apache Software License',
'Intended Audience :: Developers',
@@ -48,6 +58,7 @@
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
+ 'Programming Language :: Python :: 3.8',
'Operating System :: POSIX',
'Operating System :: MacOS :: MacOS X',
'Operating System :: Microsoft :: Windows',
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/immutables-0.11/tests/test_issue24.py
new/immutables-0.14/tests/test_issue24.py
--- old/immutables-0.11/tests/test_issue24.py 1970-01-01 01:00:00.000000000
+0100
+++ new/immutables-0.14/tests/test_issue24.py 2020-05-18 06:37:20.000000000
+0200
@@ -0,0 +1,155 @@
+import unittest
+
+from immutables.map import Map as PyMap, map_bitcount
+
+
+class CollisionKey:
+ def __hash__(self):
+ return 0
+
+
+class Issue24Base:
+ Map = None
+
+ def test_issue24(self):
+ keys = range(27)
+ new_entries = dict.fromkeys(keys, True)
+ m = self.Map(new_entries)
+ self.assertTrue(17 in m)
+ with m.mutate() as mm:
+ for i in keys:
+ del mm[i]
+ self.assertEqual(len(mm), 0)
+
+ def dump_check_node_kind(self, header, kind):
+ header = header.strip()
+ self.assertTrue(header.strip().startswith(kind))
+
+ def dump_check_node_size(self, header, size):
+ node_size = header.split('size=', 1)[1]
+ node_size = int(node_size.split(maxsplit=1)[0])
+ self.assertEqual(node_size, size)
+
+ def dump_check_bitmap_count(self, header, count):
+ header = header.split('bitmap=')[1]
+ bitmap = int(header.split(maxsplit=1)[0], 0)
+ self.assertEqual(map_bitcount(bitmap), count)
+
+ def dump_check_bitmap_node_count(self, header, count):
+ self.dump_check_node_kind(header, 'Bitmap')
+ self.dump_check_node_size(header, count * 2)
+ self.dump_check_bitmap_count(header, count)
+
+ def dump_check_collision_node_count(self, header, count):
+ self.dump_check_node_kind(header, 'Collision')
+ self.dump_check_node_size(header, 2 * count)
+
+ def test_bitmap_node_update_in_place_count(self):
+ keys = range(7)
+ new_entries = dict.fromkeys(keys, True)
+ m = self.Map(new_entries)
+ d = m.__dump__().splitlines()
+ self.assertTrue(d)
+ if d[0].startswith('HAMT'):
+ header = d[1] # skip _map.Map.__dump__() header
+ else:
+ header = d[0]
+ self.dump_check_bitmap_node_count(header, 7)
+
+ def test_bitmap_node_delete_in_place_count(self):
+ keys = range(7)
+ new_entries = dict.fromkeys(keys, True)
+ m = self.Map(new_entries)
+ with m.mutate() as mm:
+ del mm[0], mm[2], mm[3]
+ m2 = mm.finish()
+ d = m2.__dump__().splitlines()
+ self.assertTrue(d)
+ if d[0].startswith('HAMT'):
+ header = d[1] # skip _map.Map.__dump__() header
+ else:
+ header = d[0]
+ self.dump_check_bitmap_node_count(header, 4)
+
+ def test_collision_node_update_in_place_count(self):
+ keys = (CollisionKey() for i in range(7))
+ new_entries = dict.fromkeys(keys, True)
+ m = self.Map(new_entries)
+ d = m.__dump__().splitlines()
+ self.assertTrue(len(d) > 3)
+ # get node headers
+ if d[0].startswith('HAMT'):
+ h1, h2 = d[1], d[3] # skip _map.Map.__dump__() header
+ else:
+ h1, h2 = d[0], d[2]
+ self.dump_check_node_kind(h1, 'Bitmap')
+ self.dump_check_collision_node_count(h2, 7)
+
+ def test_collision_node_delete_in_place_count(self):
+ keys = [CollisionKey() for i in range(7)]
+ new_entries = dict.fromkeys(keys, True)
+ m = self.Map(new_entries)
+ with m.mutate() as mm:
+ del mm[keys[0]], mm[keys[2]], mm[keys[3]]
+ m2 = mm.finish()
+ d = m2.__dump__().splitlines()
+ self.assertTrue(len(d) > 3)
+ # get node headers
+ if d[0].startswith('HAMT'):
+ h1, h2 = d[1], d[3] # skip _map.Map.__dump__() header
+ else:
+ h1, h2 = d[0], d[2]
+ self.dump_check_node_kind(h1, 'Bitmap')
+ self.dump_check_collision_node_count(h2, 4)
+
+try:
+ from immutables._map import Map as CMap
+except ImportError:
+ CMap = None
+
+
+class Issue24PyTest(Issue24Base, unittest.TestCase):
+ Map = PyMap
+
+
[email protected](CMap is None, 'C Map is not available')
+class Issue24CTest(Issue24Base, unittest.TestCase):
+ Map = CMap
+
+ def hamt_dump_check_first_return_second(self, m):
+ d = m.__dump__().splitlines()
+ self.assertTrue(len(d) > 2)
+ self.assertTrue(d[0].startswith('HAMT'))
+ return d[1]
+
+ def test_array_node_update_in_place_count(self):
+ keys = range(27)
+ new_entries = dict.fromkeys(keys, True)
+ m = self.Map(new_entries)
+ header = self.hamt_dump_check_first_return_second(m)
+ self.dump_check_node_kind(header, 'Array')
+ for i in range(2, 18):
+ m = m.delete(i)
+ header = self.hamt_dump_check_first_return_second(m)
+ self.dump_check_bitmap_node_count(header, 11)
+
+ def test_array_node_delete_in_place_count(self):
+ keys = range(27)
+ new_entries = dict.fromkeys(keys, True)
+ m = self.Map(new_entries)
+ header = self.hamt_dump_check_first_return_second(m)
+ self.dump_check_node_kind(header, 'Array')
+ with m.mutate() as mm:
+ for i in range(5):
+ del mm[i]
+ m2 = mm.finish()
+ header = self.hamt_dump_check_first_return_second(m2)
+ self.dump_check_node_kind(header, 'Array')
+ for i in range(6, 17):
+ m2 = m2.delete(i)
+ header = self.hamt_dump_check_first_return_second(m2)
+ self.dump_check_bitmap_node_count(header, 11)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/immutables-0.11/tests/test_map.py
new/immutables-0.14/tests/test_map.py
--- old/immutables-0.11/tests/test_map.py 2019-10-15 15:59:09.000000000
+0200
+++ new/immutables-0.14/tests/test_map.py 2020-05-18 06:37:20.000000000
+0200
@@ -7,88 +7,7 @@
import weakref
from immutables.map import Map as PyMap
-
-
-class HashKey:
- _crasher = None
-
- def __init__(self, hash, name, *, error_on_eq_to=None):
- assert hash != -1
- self.name = name
- self.hash = hash
- self.error_on_eq_to = error_on_eq_to
-
- def __repr__(self):
- if self._crasher is not None and self._crasher.error_on_repr:
- raise ReprError
- return '<Key name:{} hash:{}>'.format(self.name, self.hash)
-
- def __hash__(self):
- if self._crasher is not None and self._crasher.error_on_hash:
- raise HashingError
-
- return self.hash
-
- def __eq__(self, other):
- if not isinstance(other, HashKey):
- return NotImplemented
-
- if self._crasher is not None and self._crasher.error_on_eq:
- raise EqError
-
- if self.error_on_eq_to is not None and self.error_on_eq_to is other:
- raise ValueError('cannot compare {!r} to {!r}'.format(self, other))
- if other.error_on_eq_to is not None and other.error_on_eq_to is self:
- raise ValueError('cannot compare {!r} to {!r}'.format(other, self))
-
- return (self.name, self.hash) == (other.name, other.hash)
-
-
-class KeyStr(str):
-
- def __hash__(self):
- if HashKey._crasher is not None and HashKey._crasher.error_on_hash:
- raise HashingError
- return super().__hash__()
-
- def __eq__(self, other):
- if HashKey._crasher is not None and HashKey._crasher.error_on_eq:
- raise EqError
- return super().__eq__(other)
-
- def __repr__(self, other):
- if HashKey._crasher is not None and HashKey._crasher.error_on_repr:
- raise ReprError
- return super().__eq__(other)
-
-
-class HashKeyCrasher:
-
- def __init__(self, *, error_on_hash=False, error_on_eq=False,
- error_on_repr=False):
- self.error_on_hash = error_on_hash
- self.error_on_eq = error_on_eq
- self.error_on_repr = error_on_repr
-
- def __enter__(self):
- if HashKey._crasher is not None:
- raise RuntimeError('cannot nest crashers')
- HashKey._crasher = self
-
- def __exit__(self, *exc):
- HashKey._crasher = None
-
-
-class HashingError(Exception):
- pass
-
-
-class EqError(Exception):
- pass
-
-
-class ReprError(Exception):
- pass
+from immutables._testutils import * # NoQA
class BaseMapTest:
@@ -240,7 +159,9 @@
# <Key name:E hash:362244>: 'e'
# <Key name:B hash:101>: 'b'
- def test_map_stress(self):
+
+
+ def test_map_stress_01(self):
COLLECTION_SIZE = 7000
TEST_ITERS_EVERY = 647
CRASH_HASH_EVERY = 97
@@ -330,6 +251,78 @@
self.assertEqual(len(h), 0)
self.assertEqual(list(h.items()), [])
+ def test_map_stress_02(self):
+ COLLECTION_SIZE = 20000
+ TEST_ITERS_EVERY = 647
+ CRASH_HASH_EVERY = 97
+ DELETE_EVERY = 3
+ CRASH_EQ_EVERY = 11
+
+ h = self.Map()
+ d = dict()
+
+ for i in range(COLLECTION_SIZE // 2):
+ key = KeyStr(i)
+
+ if not (i % CRASH_HASH_EVERY):
+ with HashKeyCrasher(error_on_hash=True):
+ with self.assertRaises(HashingError):
+ h.set(key, i)
+
+ h = h.set(key, i)
+
+ if not (i % CRASH_EQ_EVERY):
+ with HashKeyCrasher(error_on_eq=True):
+ with self.assertRaises(EqError):
+ h.get(KeyStr(i)) # really trigger __eq__
+
+ d[key] = i
+ self.assertEqual(len(d), len(h))
+
+ if not (i % TEST_ITERS_EVERY):
+ self.assertEqual(set(h.items()), set(d.items()))
+ self.assertEqual(len(h.items()), len(d.items()))
+
+ with h.mutate() as m:
+ for i in range(COLLECTION_SIZE // 2, COLLECTION_SIZE):
+ key = KeyStr(i)
+
+ if not (i % CRASH_HASH_EVERY):
+ with HashKeyCrasher(error_on_hash=True):
+ with self.assertRaises(HashingError):
+ m[key] = i
+
+ m[key] = i
+
+ if not (i % CRASH_EQ_EVERY):
+ with HashKeyCrasher(error_on_eq=True):
+ with self.assertRaises(EqError):
+ m[KeyStr(i)]
+
+ d[key] = i
+ self.assertEqual(len(d), len(m))
+
+ if not (i % DELETE_EVERY):
+ del m[key]
+ del d[key]
+
+ self.assertEqual(len(d), len(m))
+
+ h = m.finish()
+
+ self.assertEqual(len(h), len(d))
+ self.assertEqual(set(h.items()), set(d.items()))
+
+ with h.mutate() as m:
+ for key in list(d):
+ del d[key]
+ del m[key]
+ self.assertEqual(len(m), len(d))
+ h = m.finish()
+
+ self.assertEqual(len(h), len(d))
+ self.assertEqual(set(h.items()), set(d.items()))
+
def test_map_delete_1(self):
A = HashKey(100, 'A')
B = HashKey(101, 'B')
@@ -1235,6 +1228,67 @@
m2 = m.update({'a': 20})
self.assertEqual(len(m2), 2)
+ def test_map_mut_20(self):
+ # Issue 24:
+
+ h = self.Map()
+
+ for i in range(19):
+ # Create more than 16 keys to trigger the root bitmap
+ # node to be converted into an array node
+ h = h.set(HashKey(i, i), i)
+
+
+ h = h.set(HashKey(18, '18-collision'), 18)
+
+ with h.mutate() as m:
+ del m[HashKey(18, 18)]
+ del m[HashKey(18, '18-collision')]
+
+ # The pre-issue-24 code failed to update the number of array
+ # node element, so at this point it would be greater than it
+ # actually is.
+ h = m.finish()
+
+ # Any of the below operations shouldn't crash the debug build.
+ with h.mutate() as m:
+ for i in range(18):
+ del m[HashKey(i, i)]
+ h = m.finish()
+ h = h.set(HashKey(21, 21), 21)
+ h = h.set(HashKey(22, 22), 22)
+
+ def test_map_mut_21(self):
+ # Issue 24:
+ # Array nodes, while in mutation, failed to increment the
+ # internal count of elements when adding a new key to it.
+ # Because the internal count
+
+ h = self.Map()
+
+ for i in range(18):
+ # Create more than 16 keys to trigger the root bitmap
+ # node to be converted into an array node
+ h = h.set(HashKey(i, i), i)
+
+ with h.mutate() as m:
+ # Add one new key to the array node
+ m[HashKey(18, 18)] = 18
+ # Add another key -- after this the old code failed
+ # to increment the number of elements in the mutated
+ # array node.
+ m[HashKey(19, 19)] = 19
+ h = m.finish()
+
+ for i in range(20):
+ # Start deleting keys one by one. Because array node
+ # element count was accounted incorrectly (smaller by 1
+ # than it actually is, the mutation for "del h[18]" would
+ # create an empty array node, clipping the "19" key).
+ # Before the issue #24 fix, the below line would crash
+ # on i=19.
+ h = h.delete(HashKey(i, i))
+
def test_map_mut_stress(self):
COLLECTION_SIZE = 7000
TEST_ITERS_EVERY = 647
@@ -1294,13 +1348,18 @@
self.assertTrue(isinstance(uh, self.Map))
self.assertEqual(h, uh)
- with self.assertRaisesRegex(TypeError, "can't pickle"):
+ with self.assertRaisesRegex(TypeError, "can('t|not) pickle"):
pickle.dumps(h.mutate())
@unittest.skipIf(sys.version_info < (3, 7, 0), "__class_getitem__ is not
available")
def test_map_is_subscriptable(self):
self.assertIs(self.Map[int, str], self.Map)
+ def test_kwarg_named_col(self):
+ self.assertEqual(dict(self.Map(col=0)), {"col": 0})
+ self.assertEqual(dict(self.Map(a=0, col=1)), {"a": 0, "col": 1})
+ self.assertEqual(dict(self.Map({"a": 0}, col=1)), {"a": 0, "col": 1})
+
class PyMapTest(BaseMapTest, unittest.TestCase):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/immutables-0.11/tests/test_none_keys.py
new/immutables-0.14/tests/test_none_keys.py
--- old/immutables-0.11/tests/test_none_keys.py 1970-01-01 01:00:00.000000000
+0100
+++ new/immutables-0.14/tests/test_none_keys.py 2020-05-18 06:37:20.000000000
+0200
@@ -0,0 +1,511 @@
+import unittest
+
+from immutables.map import map_hash, map_mask, Map as PyMap
+from immutables._testutils import * # NoQA
+
+
+none_hash = map_hash(None)
+assert(none_hash != 1)
+assert((none_hash >> 32) == 0)
+
+not_collision = 0xffffffff & (~none_hash)
+
+mask = 0x7ffffffff
+none_collisions = [none_hash & (mask >> shift)
+ for shift in reversed(range(0, 32, 5))]
+assert(len(none_collisions) == 7)
+none_collisions = [h | (not_collision & (mask << shift))
+ for shift, h in zip(range(5, 37, 5), none_collisions)]
+
+
+class NoneCollision(HashKey):
+ def __init__(self, name, level):
+ if name is None:
+ raise ValueError("Can't have a NoneCollision with a None value")
+ super().__init__(none_collisions[level], name)
+
+ def __eq__(self, other):
+ if other is None:
+ return False
+ return super().__eq__(other)
+
+ __hash__ = HashKey.__hash__
+
+
+class BaseNoneTest:
+ Map = None
+
+ def test_none_collisions(self):
+ collisions = [NoneCollision('a', level) for level in range(7)]
+ indices = [map_mask(none_hash, shift) for shift in range(0, 32, 5)]
+
+ for i, c in enumerate(collisions[:-1], 1):
+ self.assertNotEqual(c, None)
+ c_hash = map_hash(c)
+ self.assertNotEqual(c_hash, none_hash)
+ for j, idx in enumerate(indices[:i]):
+ self.assertEqual(map_mask(c_hash, j*5), idx)
+ for j, idx in enumerate(indices[i:], i):
+ self.assertNotEqual(map_mask(c_hash, j*5), idx)
+
+ c = collisions[-1]
+ self.assertNotEqual(c, None)
+ c_hash = map_hash(c)
+ self.assertEqual(c_hash, none_hash)
+ for i, idx in enumerate(indices):
+ self.assertEqual(map_mask(c_hash, i*5), idx)
+
+ def test_none_as_key(self):
+ m = self.Map({None: 1})
+
+ self.assertEqual(len(m), 1)
+ self.assertTrue(None in m)
+ self.assertEqual(m[None], 1)
+ self.assertTrue(repr(m).startswith('<immutables.Map({None: 1}) at 0x'))
+
+ for level in range(7):
+ key = NoneCollision('a', level)
+ self.assertFalse(key in m)
+ with self.assertRaises(KeyError):
+ m.delete(key)
+
+ m = m.delete(None)
+ self.assertEqual(len(m), 0)
+ self.assertFalse(None in m)
+ self.assertTrue(repr(m).startswith('<immutables.Map({}) at 0x'))
+
+ self.assertEqual(m, self.Map())
+
+ with self.assertRaises(KeyError):
+ m.delete(None)
+
+ def test_none_set(self):
+ m = self.Map().set(None, 2)
+
+ self.assertEqual(len(m), 1)
+ self.assertTrue(None in m)
+ self.assertEqual(m[None], 2)
+
+ m = m.set(None, 1)
+
+ self.assertEqual(len(m), 1)
+ self.assertTrue(None in m)
+ self.assertEqual(m[None], 1)
+
+ m = m.delete(None)
+
+ self.assertEqual(len(m), 0)
+ self.assertEqual(m, self.Map())
+ self.assertFalse(None in m)
+
+ with self.assertRaises(KeyError):
+ m.delete(None)
+
+ def test_none_collision_1(self):
+ for level in range(7):
+ key = NoneCollision('a', level)
+ m = self.Map({None: 1, key: 2})
+
+ self.assertEqual(len(m), 2)
+ self.assertTrue(None in m)
+ self.assertEqual(m[None], 1)
+ self.assertTrue(key in m)
+ self.assertEqual(m[key], 2)
+
+ m2 = m.delete(None)
+ self.assertEqual(len(m2), 1)
+ self.assertTrue(key in m2)
+ self.assertEqual(m2[key], 2)
+ self.assertFalse(None in m2)
+ with self.assertRaises(KeyError):
+ m2.delete(None)
+
+ m3 = m2.delete(key)
+ self.assertEqual(len(m3), 0)
+ self.assertFalse(None in m3)
+ self.assertFalse(key in m3)
+ self.assertEqual(m3, self.Map())
+ self.assertTrue(repr(m3).startswith('<immutables.Map({}) at 0x'))
+ with self.assertRaises(KeyError):
+ m3.delete(None)
+ with self.assertRaises(KeyError):
+ m3.delete(key)
+
+ m2 = m.delete(key)
+ self.assertEqual(len(m2), 1)
+ self.assertTrue(None in m2)
+ self.assertEqual(m2[None], 1)
+ self.assertFalse(key in m2)
+ with self.assertRaises(KeyError):
+ m2.delete(key)
+
+ m4 = m2.delete(None)
+ self.assertEqual(len(m4), 0)
+ self.assertFalse(None in m4)
+ self.assertFalse(key in m4)
+ self.assertEqual(m4, self.Map())
+ self.assertTrue(repr(m4).startswith('<immutables.Map({}) at 0x'))
+ with self.assertRaises(KeyError):
+ m4.delete(None)
+ with self.assertRaises(KeyError):
+ m4.delete(key)
+
+ self.assertEqual(m3, m4)
+
+ def test_none_collision_2(self):
+ key = HashKey(not_collision, 'a')
+ m = self.Map().set(None, 1).set(key, 2)
+
+ self.assertEqual(len(m), 2)
+ self.assertTrue(key in m)
+ self.assertTrue(None in m)
+ self.assertEqual(m[key], 2)
+ self.assertEqual
+
+ m = m.set(None, 0)
+ self.assertEqual(len(m), 2)
+ self.assertTrue(key in m)
+ self.assertTrue(None in m)
+
+ for level in range(7):
+ key2 = NoneCollision('b', level)
+ self.assertFalse(key2 in m)
+ m2 = m.set(key2, 1)
+
+ self.assertEqual(len(m2), 3)
+ self.assertTrue(key in m2)
+ self.assertTrue(None in m2)
+ self.assertTrue(key2 in m2)
+ self.assertEqual(m2[key], 2)
+ self.assertEqual(m2[None], 0)
+ self.assertEqual(m2[key2], 1)
+
+ m2 = m2.set(None, 1)
+ self.assertEqual(len(m2), 3)
+ self.assertTrue(key in m2)
+ self.assertTrue(None in m2)
+ self.assertTrue(key2 in m2)
+ self.assertEqual(m2[key], 2)
+ self.assertEqual(m2[None], 1)
+ self.assertEqual(m2[key2], 1)
+
+ m2 = m2.set(None, 2)
+ self.assertEqual(len(m2), 3)
+ self.assertTrue(key in m2)
+ self.assertTrue(None in m2)
+ self.assertTrue(key2 in m2)
+ self.assertEqual(m2[key], 2)
+ self.assertEqual(m2[None], 2)
+ self.assertEqual(m2[key2], 1)
+
+ m3 = m2.delete(key)
+ self.assertEqual(len(m3), 2)
+ self.assertTrue(None in m3)
+ self.assertTrue(key2 in m3)
+ self.assertFalse(key in m3)
+ self.assertEqual(m3[None], 2)
+ self.assertEqual(m3[key2], 1)
+ with self.assertRaises(KeyError):
+ m3.delete(key)
+
+ m3 = m2.delete(key2)
+ self.assertEqual(len(m3), 2)
+ self.assertTrue(None in m3)
+ self.assertTrue(key in m3)
+ self.assertFalse(key2 in m3)
+ self.assertEqual(m3[None], 2)
+ self.assertEqual(m3[key], 2)
+ with self.assertRaises(KeyError):
+ m3.delete(key2)
+
+ m3 = m2.delete(None)
+ self.assertEqual(len(m3), 2)
+ self.assertTrue(key in m3)
+ self.assertTrue(key2 in m3)
+ self.assertFalse(None in m3)
+ self.assertEqual(m3[key], 2)
+ self.assertEqual(m3[key2], 1)
+ with self.assertRaises(KeyError):
+ m3.delete(None)
+
+ m2 = m.delete(None)
+ self.assertEqual(len(m2), 1)
+ self.assertFalse(None in m2)
+ self.assertTrue(key in m2)
+ self.assertEqual(m2[key], 2)
+ with self.assertRaises(KeyError):
+ m2.delete(None)
+
+ m2 = m.delete(key)
+ self.assertEqual(len(m2), 1)
+ self.assertFalse(key in m2)
+ self.assertTrue(None in m2)
+ self.assertEqual(m2[None], 0)
+ with self.assertRaises(KeyError):
+ m2.delete(key)
+
+ def test_none_collision_3(self):
+ for level in range(7):
+ key = NoneCollision('a', level)
+ m = self.Map({key: 2})
+
+ self.assertEqual(len(m), 1)
+ self.assertFalse(None in m)
+ self.assertTrue(key in m)
+ self.assertEqual(m[key], 2)
+ with self.assertRaises(KeyError):
+ m.delete(None)
+
+ m = m.set(None, 1)
+ self.assertEqual(len(m), 2)
+ self.assertTrue(key in m)
+ self.assertEqual(m[key], 2)
+ self.assertTrue(None in m)
+ self.assertEqual(m[None], 1)
+
+ m = m.set(None, 0)
+ self.assertEqual(len(m), 2)
+ self.assertTrue(key in m)
+ self.assertEqual(m[key], 2)
+ self.assertTrue(None in m)
+ self.assertEqual(m[None], 0)
+
+ m2 = m.delete(key)
+ self.assertEqual(len(m2), 1)
+ self.assertTrue(None in m2)
+ self.assertEqual(m2[None], 0)
+ self.assertFalse(key in m2)
+ with self.assertRaises(KeyError):
+ m2.delete(key)
+
+ m2 = m.delete(None)
+ self.assertEqual(len(m2), 1)
+ self.assertTrue(key in m2)
+ self.assertEqual(m2[key], 2)
+ self.assertFalse(None in m2)
+ with self.assertRaises(KeyError):
+ m2.delete(None)
+
+ def test_collision_4(self):
+ key2 = NoneCollision('a', 2)
+ key4 = NoneCollision('b', 4)
+ m = self.Map({key2: 2, key4: 4})
+
+ self.assertEqual(len(m), 2)
+ self.assertTrue(key2 in m)
+ self.assertTrue(key4 in m)
+ self.assertEqual(m[key2], 2)
+ self.assertEqual(m[key4], 4)
+ self.assertFalse(None in m)
+
+ m2 = m.set(None, 9)
+
+ self.assertEqual(len(m2), 3)
+ self.assertTrue(key2 in m2)
+ self.assertTrue(key4 in m2)
+ self.assertTrue(None in m2)
+ self.assertEqual(m2[key2], 2)
+ self.assertEqual(m2[key4], 4)
+ self.assertEqual(m2[None], 9)
+
+ m3 = m2.set(None, 0)
+ self.assertEqual(len(m3), 3)
+ self.assertTrue(key2 in m3)
+ self.assertTrue(key4 in m3)
+ self.assertTrue(None in m3)
+ self.assertEqual(m3[key2], 2)
+ self.assertEqual(m3[key4], 4)
+ self.assertEqual(m3[None], 0)
+
+ m3 = m2.set(key2, 0)
+ self.assertEqual(len(m3), 3)
+ self.assertTrue(key2 in m3)
+ self.assertTrue(key4 in m3)
+ self.assertTrue(None in m3)
+ self.assertEqual(m3[key2], 0)
+ self.assertEqual(m3[key4], 4)
+ self.assertEqual(m3[None], 9)
+
+ m3 = m2.set(key4, 0)
+ self.assertEqual(len(m3), 3)
+ self.assertTrue(key2 in m3)
+ self.assertTrue(key4 in m3)
+ self.assertTrue(None in m3)
+ self.assertEqual(m3[key2], 2)
+ self.assertEqual(m3[key4], 0)
+ self.assertEqual(m3[None], 9)
+
+ m3 = m2.delete(None)
+ self.assertEqual(m3, m)
+ self.assertEqual(len(m3), 2)
+ self.assertTrue(key2 in m3)
+ self.assertTrue(key4 in m3)
+ self.assertEqual(m3[key2], 2)
+ self.assertEqual(m3[key4], 4)
+ self.assertFalse(None in m3)
+ with self.assertRaises(KeyError):
+ m3.delete(None)
+
+ m3 = m2.delete(key2)
+ self.assertEqual(len(m3), 2)
+ self.assertTrue(None in m3)
+ self.assertTrue(key4 in m3)
+ self.assertEqual(m3[None], 9)
+ self.assertEqual(m3[key4], 4)
+ self.assertFalse(key2 in m3)
+ with self.assertRaises(KeyError):
+ m3.delete(key2)
+
+ m3 = m2.delete(key4)
+ self.assertEqual(len(m3), 2)
+ self.assertTrue(None in m3)
+ self.assertTrue(key2 in m3)
+ self.assertEqual(m3[None], 9)
+ self.assertEqual(m3[key2], 2)
+ self.assertFalse(key4 in m3)
+ with self.assertRaises(KeyError):
+ m3.delete(key4)
+
+ def test_none_mutation(self):
+ key2 = NoneCollision('a', 2)
+ key4 = NoneCollision('b', 4)
+ key = NoneCollision('c', -1)
+ m = self.Map({key: -1, key2: 2, key4: 4, None: 9})
+
+ with m.mutate() as mm:
+ self.assertEqual(len(mm), 4)
+ self.assertTrue(key in mm)
+ self.assertTrue(key2 in mm)
+ self.assertTrue(key4 in mm)
+ self.assertTrue(None in mm)
+ self.assertEqual(mm[key2], 2)
+ self.assertEqual(mm[key4], 4)
+ self.assertEqual(mm[key], -1)
+ self.assertEqual(mm[None], 9)
+
+ for k in m:
+ mm[k] = -mm[k]
+
+ self.assertEqual(len(mm), 4)
+ self.assertTrue(key in mm)
+ self.assertTrue(key2 in mm)
+ self.assertTrue(key4 in mm)
+ self.assertTrue(None in mm)
+ self.assertEqual(mm[key2], -2)
+ self.assertEqual(mm[key4], -4)
+ self.assertEqual(mm[key], 1)
+ self.assertEqual(mm[None], -9)
+
+ for k in m:
+ del mm[k]
+ self.assertEqual(len(mm), 3)
+ self.assertFalse(k in mm)
+ for n in m:
+ if n != k:
+ self.assertTrue(n in mm)
+ self.assertEqual(mm[n], -m[n])
+ with self.assertRaises(KeyError):
+ del mm[k]
+ mm[k] = -m[k]
+ self.assertEqual(len(mm), 4)
+ self.assertTrue(k in mm)
+ self.assertEqual(mm[k], -m[k])
+
+ for k in m:
+ mm[k] = -mm[k]
+
+ self.assertEqual(len(mm), 4)
+ self.assertTrue(key in mm)
+ self.assertTrue(key2 in mm)
+ self.assertTrue(key4 in mm)
+ self.assertTrue(None in mm)
+ self.assertEqual(mm[key2], 2)
+ self.assertEqual(mm[key4], 4)
+ self.assertEqual(mm[key], -1)
+ self.assertEqual(mm[None], 9)
+
+ for k in m:
+ mm[k] = -mm[k]
+
+ self.assertEqual(len(mm), 4)
+ self.assertTrue(key in mm)
+ self.assertTrue(key2 in mm)
+ self.assertTrue(key4 in mm)
+ self.assertTrue(None in mm)
+ self.assertEqual(mm[key2], -2)
+ self.assertEqual(mm[key4], -4)
+ self.assertEqual(mm[key], 1)
+ self.assertEqual(mm[None], -9)
+
+ m2 = mm.finish()
+
+ self.assertEqual(set(m), set(m2))
+ self.assertEqual(len(m2), 4)
+ self.assertTrue(key in m2)
+ self.assertTrue(key2 in m2)
+ self.assertTrue(key4 in m2)
+ self.assertTrue(None in m2)
+ self.assertEqual(m2[key2], -2)
+ self.assertEqual(m2[key4], -4)
+ self.assertEqual(m2[key], 1)
+ self.assertEqual(m2[None], -9)
+
+ for k, v in m.items():
+ self.assertTrue(k in m2)
+ self.assertEqual(m2[k], -v)
+
+ def test_iterators(self):
+ key2 = NoneCollision('a', 2)
+ key4 = NoneCollision('b', 4)
+ key = NoneCollision('c', -1)
+ m = self.Map({key: -1, key2: 2, key4: 4, None: 9})
+
+ self.assertEqual(len(m), 4)
+ self.assertTrue(key in m)
+ self.assertTrue(key2 in m)
+ self.assertTrue(key4 in m)
+ self.assertTrue(None in m)
+ self.assertEqual(m[key2], 2)
+ self.assertEqual(m[key4], 4)
+ self.assertEqual(m[key], -1)
+ self.assertEqual(m[None], 9)
+
+ s = set(m)
+ self.assertEqual(len(s), 4)
+ self.assertEqual(s, set([None, key, key2, key4]))
+
+ sk = set(m.keys())
+ self.assertEqual(s, sk)
+
+ sv = set(m.values())
+ self.assertEqual(len(sv), 4)
+ self.assertEqual(sv, set([-1, 2, 4, 9]))
+
+ si = set(m.items())
+ self.assertEqual(len(si), 4)
+ self.assertEqual(si,
+ set([(key, -1), (key2, 2), (key4, 4), (None, 9)]))
+
+ d = {key: -1, key2: 2, key4: 4, None: 9}
+ self.assertEqual(dict(m.items()), d)
+
+
+class PyMapNoneTest(BaseNoneTest, unittest.TestCase):
+
+ Map = PyMap
+
+
+try:
+ from immutables._map import Map as CMap
+except ImportError:
+ CMap = None
+
+
[email protected](CMap is None, 'C Map is not available')
+class CMapNoneTest(BaseNoneTest, unittest.TestCase):
+
+ Map = CMap
+
+
+if __name__ == "__main__":
+ unittest.main()