Repository: TurboGears/Ming

Issue:
MappedClass metaclass ignores __init_subclass__ arguments
Next code works with `M` but fails with `MappedClass`:

```python
#!/usr/bin/env python

from ming.odm import MappedClass

# class M:
#     pass

# class A(M):
class A(MappedClass):

    def __init_subclass__(cls, constant=1, **kwargs):
        super().__init_subclass__(**kwargs)
        cls.prop = constant

class B(A, constant=2):
    pass

print(B().prop)
```

It seems to be fixed just by adding a `**kwargs` parameter to `__new__` (and `*` and `/` parameters if appropriate).

Some refs:
- https://github.com/samuelcolvin/pydantic/issues/867
- https://stackoverflow.com/questions/42281697/typeerror-when-combining-abcmeta-with-init-subclass-in-python-3-6

Discussion:
codecov[bot]: # [Codecov](https://codecov.io/gh/TurboGears/Ming/pull/38?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=TurboGears) Report
> Merging [#38](https://codecov.io/gh/TurboGears/Ming/pull/38?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=TurboGears) (629092f) into [master](https://codecov.io/gh/TurboGears/Ming/commit/19883fa47dd51644cef596e3a4138054f3ade1c7?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=TurboGears) (19883fa) will **decrease** coverage by `0.13%`.
> The diff coverage is `100.00%`.

[![Impacted file tree graph](https://codecov.io/gh/TurboGears/Ming/pull/38/graphs/tree.svg?width=650&height=150&src=pr&token=Iy3CU62Ga5&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=TurboGears)](https://codecov.io/gh/TurboGears/Ming/pull/38?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=TurboGears)

```diff
@@            Coverage Diff             @@
##           master      #38      +/-   ##
==========================================
- Coverage   89.35%   89.22%   -0.14%     
==========================================
  Files          46       46              
  Lines        6662     6673      +11     
==========================================
+ Hits         5953     5954       +1     
- Misses        709      719      +10     
```

| Flag | Coverage Δ | |
|---|---|---|
| tests-2.7 | `?` | |
| tests-3.5 | `?` | |
| tests-3.6 | `89.07% <100.00%> (+0.02%)` | :arrow_up: |
| tests-3.7 | `89.07% <100.00%> (+0.02%)` | :arrow_up: |
| tests-3.8 | `89.22% <100.00%> (+0.02%)` | :arrow_up: |
| tests-3.9 | `?` | |
| tests-pypy3 | `?` | |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=TurboGears#carryforward-flags-in-the-pull-request-comment) to find out more.

| [Impacted Files](https://codecov.io/gh/TurboGears/Ming/pull/38?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=TurboGears) | Coverage Δ | |
|---|---|---|
| [ming/declarative.py](https://codecov.io/gh/TurboGears/Ming/pull/38/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=TurboGears#diff-bWluZy9kZWNsYXJhdGl2ZS5weQ==) | `100.00% <100.00%> (ø)` | |
| [ming/odm/declarative.py](https://codecov.io/gh/TurboGears/Ming/pull/38/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=TurboGears#diff-bWluZy9vZG0vZGVjbGFyYXRpdmUucHk=) | `92.85% <100.00%> (ø)` | |
| [ming/tests/odm/test\_declarative.py](https://codecov.io/gh/TurboGears/Ming/pull/38/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=TurboGears#diff-bWluZy90ZXN0cy9vZG0vdGVzdF9kZWNsYXJhdGl2ZS5weQ==) | `99.54% <100.00%> (+<0.01%)` | :arrow_up: |
| [ming/odm/icollection.py](https://codecov.io/gh/TurboGears/Ming/pull/38/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=TurboGears#diff-bWluZy9vZG0vaWNvbGxlY3Rpb24ucHk=) | `84.53% <0.00%> (-4.98%)` | :arrow_down: |
| [ming/tests/test\_base.py](https://codecov.io/gh/TurboGears/Ming/pull/38/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=TurboGears#diff-bWluZy90ZXN0cy90ZXN0X2Jhc2UucHk=) | `95.45% <0.00%> (-2.28%)` | :arrow_down: |
| [ming/odm/mapper.py](https://codecov.io/gh/TurboGears/Ming/pull/38/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=TurboGears#diff-bWluZy9vZG0vbWFwcGVyLnB5) | `88.81% <0.00%> (-0.34%)` | :arrow_down: |
| [ming/mim.py](https://codecov.io/gh/TurboGears/Ming/pull/38/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=TurboGears#diff-bWluZy9taW0ucHk=) | `78.57% <0.00%> (-0.10%)` | :arrow_down: |
| [ming/schema.py](https://codecov.io/gh/TurboGears/Ming/pull/38/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=TurboGears#diff-bWluZy9zY2hlbWEucHk=) | `89.51% <0.00%> (-0.03%)` | :arrow_down: |
| [ming/metadata.py](https://codecov.io/gh/TurboGears/Ming/pull/38/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=TurboGears#diff-bWluZy9tZXRhZGF0YS5weQ==) | `85.71% <0.00%> (+0.57%)` | :arrow_up: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/TurboGears/Ming/pull/38?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=TurboGears).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=TurboGears)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/TurboGears/Ming/pull/38?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=TurboGears). Last update [19883fa...629092f](https://codecov.io/gh/TurboGears/Ming/pull/38?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=TurboGears). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=TurboGears).


Patch:
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index b63e91b..fb5a450 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -21,7 +21,6 @@ jobs:
     strategy:
       matrix:
         python-version:
-          - 2.7
           # - 3.4 not in github x64
           - 3.5
           - 3.6
@@ -50,7 +49,7 @@ jobs:
           python -m pip install --upgrade tox
 
       - name: Run tox targets for ${{ matrix.python-version }}
-        # could do this if we install tox-py, but it doesn't support 2.7 or 3.5
+        # could do this if we install tox-py, but it doesn't support 3.5
         #   run: tox --py current
         # sed is used to remove the '.' so '3.7' becomes '37' for tox (and pypy3 doesn't become pypypy3)
         run: tox -e py`echo ${{ matrix.python-version }} | sed s/\\\.// | sed s/pypy/py/`
diff --git a/.gitignore b/.gitignore
index a19231f..83e8a08 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,6 +6,7 @@
 *.swp
 */.coverage
 .coverage
+coverage.xml
 /cover/
 *~
 pyforge/docs/_build/*
diff --git a/docs/news.rst b/docs/news.rst
index 551a179..edb62f8 100644
--- a/docs/news.rst
+++ b/docs/news.rst
@@ -1,7 +1,7 @@
 Ming News / Release Notes
 =====================================
 
-The latest releases support PyMongo 3.  The older 0.5.x releases support PyMongo 2.  Both work on Python 2 and 3.
+The latest releases support PyMongo 3.  The older 0.5.x releases support PyMongo 2 and Python 2.
 
 0.10.2 (Jun 19, 2020)
 ---------------------
diff --git a/ming/declarative.py b/ming/declarative.py
index 3574d63..627464c 100644
--- a/ming/declarative.py
+++ b/ming/declarative.py
@@ -5,7 +5,7 @@ import six
 
 
 class _DocumentMeta(type):
-    def __new__(meta, classname, bases, dct):
+    def __new__(meta, classname, bases, dct, **kwargs):
         mm = _build_mongometa(bases, dct)
         collection_name = mm.name
         session = mm.session
@@ -42,7 +42,7 @@ class _DocumentMeta(type):
             migrate = getattr(migrate, '__func__', migrate)
         if before_save:
             before_save = getattr(before_save, '__func__', before_save)
-        cls = type.__new__(meta, classname, bases, clsdct)
+        cls = type.__new__(meta, classname, bases, clsdct, **kwargs)
         m = _ClassManager(
             cls, collection_name, session, fields, indexes,
             polymorphic_on=polymorphic_on,
diff --git a/ming/mim.py b/ming/mim.py
index ed8d7a3..01a2717 100644
--- a/ming/mim.py
+++ b/ming/mim.py
@@ -1354,7 +1354,7 @@ def validate(doc):
     for k,v in six.iteritems(doc):
         assert '$' not in k
         assert '.' not in k
-        if hasattr(v, 'iteritems'):
+        if hasattr(v, 'items'):
             validate(v)
 
 def bson_safe(obj):
diff --git a/ming/odm/declarative.py b/ming/odm/declarative.py
index 6c5a384..c6e1500 100644
--- a/ming/odm/declarative.py
+++ b/ming/odm/declarative.py
@@ -6,11 +6,11 @@ import six
 
 class _MappedClassMeta(type):
  
-    def __init__(cls, name, bases, dct):
+    def __init__(cls, name, bases, dct, **kwargs):
         cls._registry['%s.%s' % (cls.__module__, cls.__name__)] = mapper(cls)
         cls._compiled = False
 
-    def __new__(meta, name, bases, dct):
+    def __new__(meta, name, bases, dct, **kwargs):
         # Get the mapped base class(es)
         mapped_bases = [b for b in bases if hasattr(b, 'query')]
         doc_bases = [mapper(b).collection for b in mapped_bases]
@@ -42,7 +42,7 @@ class _MappedClassMeta(type):
                 properties[k] = v
             else:
                 clsdict[k] = v
-        cls = type.__new__(meta, name, bases, clsdict)
+        cls = type.__new__(meta, name, bases, clsdict, **kwargs)
         mapper(cls, collection_class, mm.session,
                properties=properties,
                include_properties=include_properties,
diff --git a/ming/odm/icollection.py b/ming/odm/icollection.py
index 2e0f813..bb6aaf2 100644
--- a/ming/odm/icollection.py
+++ b/ming/odm/icollection.py
@@ -162,20 +162,6 @@ class InstrumentedList(list):
             self._tracker.removed_item(self._impl[i])
             del self._impl[i]
 
-    def __setslice__(self, i, j, v):
-        """Removed in Python 3"""
-        v = list(map(deinstrument, v))
-        iv = (instrument(item, self._tracker) for item in v)
-        super(InstrumentedList, self).__getitem__(slice(i, j))
-        self._tracker.removed_items(self._impl[i:j])
-        self._impl[i:j] = v
-        self._tracker.added_items(v)
-
-    def __delslice__(self, i, j):
-        super(InstrumentedList, self).__delslice__(i, j)
-        self._tracker.removed_items(self._impl[i:j])
-        del self._impl[i:j]
-
     def __add__(self, y):
         return instrument(self._impl + y, self._tracker)
 
diff --git a/ming/odm/mapper.py b/ming/odm/mapper.py
index 99840f4..6e89a22 100644
--- a/ming/odm/mapper.py
+++ b/ming/odm/mapper.py
@@ -225,11 +225,7 @@ class Mapper(object):
         for k in ('__repr__', '__getitem__', '__setitem__', '__contains__',
                   'delete'):
             if getattr(self.mapped_class, k, ()) == getattr(object, k, ()):
-                if six.PY2:
-                    # im_func has been deprecated in Python 3
-                    setattr(self.mapped_class, k, getattr(inst, k).im_func)
-                else:
-                    setattr(self.mapped_class, k, getattr(inst, k))
+                setattr(self.mapped_class, k, getattr(inst, k))
 
     def _instrumentation(self):
         class _Instrumentation(object):
diff --git a/setup.py b/setup.py
index 4e0b6c3..8914314 100644
--- a/setup.py
+++ b/setup.py
@@ -17,8 +17,6 @@ setup(name='Ming',
         'Programming Language :: Python',
         'Topic :: Database',
         'Topic :: Software Development :: Libraries :: Python Modules',
-        'Programming Language :: Python :: 2',
-        'Programming Language :: Python :: 2.7',
         'Programming Language :: Python :: 3',
         'Programming Language :: Python :: 3.5',
         'Programming Language :: Python :: 3.6',
diff --git a/tox.ini b/tox.ini
index ac1bb7f..b7e7ddb 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,7 +1,7 @@
 [tox]
 skip_missing_interpreters = True
 envlist =
-    py{27,35,36,37,38,39},pypy3
+    py{35,36,37,38,39},pypy3
 
 [testenv]
 deps =


Test patch:
diff --git a/ming/tests/odm/test_declarative.py b/ming/tests/odm/test_declarative.py
index ff3cf9b..9cbd9f3 100644
--- a/ming/tests/odm/test_declarative.py
+++ b/ming/tests/odm/test_declarative.py
@@ -1,5 +1,6 @@
+import sys
 from collections import defaultdict
-from unittest import TestCase
+from unittest import TestCase, SkipTest
 
 from ming import schema as S
 from ming import create_datastore
@@ -67,6 +68,28 @@ class TestMapping(TestCase):
         u2 = User.query.find({"username": "anonymous"}).first()
         assert u._id == u2._id
 
+    def test_with_init_subclass(self):
+        if sys.version_info[0:2] < (3, 6):
+            raise SkipTest('__init_subclass__ not supported before python 3.6')
+
+        class User(MappedClass):
+            class __mongometa__:
+                name = "users_with_init_subclass"
+                session = self.session
+
+            _id = FieldProperty(S.ObjectId)
+            username = FieldProperty(str)
+
+            def __init_subclass__(cls, constant=1, **kwargs):
+                super().__init_subclass__(**kwargs)
+                cls.prop = constant
+
+        class B(User, constant=2):
+            pass
+
+        b = B()
+        assert b.prop == 2
+
     def test_delete_super(self):
         class User(MappedClass):
             class __mongometa__:
diff --git a/ming/tests/test_base.py b/ming/tests/test_base.py
index 4da7905..fb5cc6e 100644
--- a/ming/tests/test_base.py
+++ b/ming/tests/test_base.py
@@ -45,10 +45,6 @@ class TestObject(TestCase):
         c = [ 'foo', 1, 1.0, now,
               Decimal('0.3'), None, oid ]
 
-        if six.PY2:
-            c = [ 'foo', 1, long(1), 1.0, now,
-                  Decimal('0.3'), None, oid ]
-
         safe_obj = Object(
             a=[1,2,3],
             b=dict(a=12),