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 <dmuel...@suse.com>
+
+- skip tests that fail on 32bit 
+
+-------------------------------------------------------------------
+Wed Sep 16 11:27:37 UTC 2020 - Dirk Mueller <dmuel...@suse.com>
+
+- 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
+
+
+@unittest.skipIf(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
+
+
+@unittest.skipIf(CMap is None, 'C Map is not available')
+class CMapNoneTest(BaseNoneTest, unittest.TestCase):
+
+    Map = CMap
+
+
+if __name__ == "__main__":
+    unittest.main()


Reply via email to