django 1.8 / python 3.10

django 1.8 / python 3.10

  • Written by
    Walter Doekes
  • Published on

After upgrading a machine to Ubuntu/Jammy there was an old Django 1.8 project that refused to run with the newer Python 3.10.

...
  File "django/db/models/sql/query.py", line 11, in <module>
    from collections import Iterator, Mapping, OrderedDict
ImportError: cannot import name 'Iterator' from 'collections' (/usr/lib/python3.10/collections/__init__.py)

This was relatively straight forward to fix, by using the following patch. Some parts were stolen from a stackoverflow response by Elias Prado.

--- a/django/core/paginator.py  2023-01-11 14:09:04.915505171 +0100
+++ b/django/core/paginator.py  2023-01-11 14:09:29.407130151 +0100
@@ -1,4 +1,4 @@
-import collections
+from collections.abc import Sequence
 from math import ceil

 from django.utils import six
@@ -103,7 +103,7 @@ class Paginator(object):
 QuerySetPaginator = Paginator   # For backwards-compatibility.


-class Page(collections.Sequence):
+class Page(Sequence):

     def __init__(self, object_list, number, paginator):
         self.object_list = object_list
--- a/django/db/migrations/writer.py  2023-01-11 14:13:07.507799080 +0100
+++ b/django/db/migrations/writer.py  2023-01-11 14:14:36.978436145 +0100
@@ -1,6 +1,6 @@
 from __future__ import unicode_literals

-import collections
+from collections.abc import Iterable
 import datetime
 import decimal
 import math
@@ -434,7 +434,7 @@ class MigrationWriter(object):
                     % (value.__name__, module_name, get_docs_version()))
             return "%s.%s" % (module_name, value.__name__), {"import %s" % module_name}
         # Other iterables
-        elif isinstance(value, collections.Iterable):
+        elif isinstance(value, Iterable):
             imports = set()
             strings = []
             for item in value:
--- a/django/db/models/base.py  2023-01-11 14:17:13.471982572 +0100
+++ b/django/db/models/base.py  2023-01-11 14:19:38.337720520 +0100
@@ -80,7 +80,12 @@ class ModelBase(type):

         # Create the class.
         module = attrs.pop('__module__')
-        new_class = super_new(cls, name, bases, {'__module__': module})
+        new_attrs = {'__module__': module}
+        classcell = attrs.pop('__classcell__', None)
+        if classcell is not None:
+            new_attrs['__classcell__'] = classcell
+        new_class = super_new(cls, name, bases, new_attrs)
+
         attr_meta = attrs.pop('Meta', None)
         abstract = getattr(attr_meta, 'abstract', False)
         if not attr_meta:
--- a/django/db/models/fields/__init__.py 2023-01-11 14:12:50.780054102 +0100
+++ b/django/db/models/fields/__init__.py 2023-01-11 14:14:02.290964344 +0100
@@ -1,7 +1,7 @@
 # -*- coding: utf-8 -*-
 from __future__ import unicode_literals

-import collections
+from collections.abc import Iterable, Iterator
 import copy
 import datetime
 import decimal
@@ -417,7 +417,7 @@ class Field(RegisterLookupMixin):
         for name, default in possibles.items():
             value = getattr(self, attr_overrides.get(name, name))
             # Unroll anything iterable for choices into a concrete list
-            if name == "choices" and isinstance(value, collections.Iterable):
+            if name == "choices" and isinstance(value, Iterable):
                 value = list(value)
             # Do correct kind of comparison
             if name in equals_comparison:
@@ -852,7 +852,7 @@ class Field(RegisterLookupMixin):
         return smart_text(self._get_val_from_obj(obj))

     def _get_choices(self):
-        if isinstance(self._choices, collections.Iterator):
+        if isinstance(self._choices, Iterator):
             choices, self._choices = tee(self._choices)
             return choices
         else:
--- a/django/db/models/sql/query.py 2023-01-11 14:07:45.900716653 +0100
+++ b/django/db/models/sql/query.py 2023-01-11 14:08:08.724366450 +0100
@@ -8,7 +8,8 @@ all about the internals of models in ord
 """
 import copy
 import warnings
-from collections import Iterator, Mapping, OrderedDict
+from collections import OrderedDict
+from collections.abc import Iterator, Mapping
 from itertools import chain, count, product
 from string import ascii_uppercase

--- a/django/db/models/sql/where.py 2023-01-11 14:13:01.595889201 +0100
+++ b/django/db/models/sql/where.py 2023-01-11 14:14:25.322613605 +0100
@@ -2,7 +2,7 @@
 Code to manage the creation and SQL rendering of 'where' constraints.
 """

-import collections
+from collections.abc import Iterator
 import datetime
 import warnings
 from itertools import repeat
@@ -59,7 +59,7 @@ class WhereNode(tree.Node):
         if not isinstance(data, (list, tuple)):
             return data
         obj, lookup_type, value = data
-        if isinstance(value, collections.Iterator):
+        if isinstance(value, Iterator):
             # Consume any generators immediately, so that we can determine
             # emptiness and transform any non-empty values correctly.
             value = list(value)

And to avoid the following warnings, the Django included six can be patched.

<frozen importlib._bootstrap>:914:
  ImportWarning: _SixMetaPathImporter.find_spec() not found;
  falling back to find_module()
<frozen importlib._bootstrap>:671:
  ImportWarning: _SixMetaPathImporter.exec_module() not found;
  falling back to load_module()

These changes were taken from six 1.16:

--- a/django/utils/six.py  2023-01-17 11:08:00.267645405 +0100
+++ b/django/utils/six.py  2023-01-17 11:12:13.993813451 +0100
@@ -71,6 +71,7 @@ else:
             MAXSIZE = int((1 << 63) - 1)
         del X

+from importlib.util import spec_from_loader

 def _add_doc(func, doc):
     """Add documentation to a function."""
@@ -186,6 +187,11 @@ class _SixMetaPathImporter(object):
             return self
         return None

+    def find_spec(self, fullname, path, target=None):
+        if fullname in self.known_modules:
+            return spec_from_loader(fullname, self)
+        return None
+
     def __get_module(self, fullname):
         try:
             return self.known_modules[fullname]
@@ -223,6 +229,12 @@ class _SixMetaPathImporter(object):
         return None
     get_source = get_code  # same as get_code

+    def create_module(self, spec):
+        return self.load_module(spec.name)
+
+    def exec_module(self, module):
+        pass
+
 _importer = _SixMetaPathImporter(__name__)


@@ -679,11 +691,15 @@ if PY3:
     exec_ = getattr(moves.builtins, "exec")

     def reraise(tp, value, tb=None):
-        if value is None:
-            value = tp()
-        if value.__traceback__ is not tb:
-            raise value.with_traceback(tb)
-        raise value
+        try:
+            if value is None:
+                value = tp()
+            if value.__traceback__ is not tb:
+                raise value.with_traceback(tb)
+            raise value
+        finally:
+            value = None
+            tb = None

 else:
     def exec_(_code_, _globs_=None, _locs_=None):
@@ -699,19 +715,19 @@ else:
         exec("""exec _code_ in _globs_, _locs_""")

     exec_("""def reraise(tp, value, tb=None):
-    raise tp, value, tb
+    try:
+        raise tp, value, tb
+    finally:
+        tb = None
 """)


-if sys.version_info[:2] == (3, 2):
-    exec_("""def raise_from(value, from_value):
-    if from_value is None:
-        raise value
-    raise value from from_value
-""")
-elif sys.version_info[:2] > (3, 2):
+if sys.version_info[:2] > (3,):
     exec_("""def raise_from(value, from_value):
-    raise value from from_value
+    try:
+        raise value from from_value
+    finally:
+        value = None
 """)
 else:
     def raise_from(value, from_value):
@@ -788,11 +804,10 @@ _add_doc(reraise, """Reraise an exceptio
 if sys.version_info[0:2] < (3, 4):
     def wraps(wrapped, assigned=functools.WRAPPER_ASSIGNMENTS,
               updated=functools.WRAPPER_UPDATES):
-        def wrapper(f):
-            f = functools.wraps(wrapped, assigned, updated)(f)
-            f.__wrapped__ = wrapped
-            return f
-        return wrapper
+        return functools.partial(_update_wrapper, wrapped=wrapped,
+                                 assigned=assigned, updated=updated)
+    wraps.__doc__ = functools.wraps.__doc__
+
 else:
     wraps = functools.wraps

@@ -802,10 +817,22 @@ def with_metaclass(meta, *bases):
     # This requires a bit of explanation: the basic idea is to make a dummy
     # metaclass for one level of class instantiation that replaces itself with
     # the actual metaclass.
-    class metaclass(meta):
+    class metaclass(type):

         def __new__(cls, name, this_bases, d):
-            return meta(name, bases, d)
+            if sys.version_info[:2] >= (3, 7):
+                # This version introduced PEP 560 that requires a bit
+                # of extra care (we mimic what is done by __build_class__).
+                resolved_bases = types.resolve_bases(bases)
+                if resolved_bases is not bases:
+                    d['__orig_bases__'] = bases
+            else:
+                resolved_bases = bases
+            return meta(name, resolved_bases, d)
+
+        @classmethod
+        def __prepare__(cls, name, this_bases):
+            return meta.__prepare__(name, bases)
     return type.__new__(metaclass, 'temporary_class', (), {})


@@ -821,6 +848,8 @@ def add_metaclass(metaclass):
                 orig_vars.pop(slots_var)
         orig_vars.pop('__dict__', None)
         orig_vars.pop('__weakref__', None)
+        if hasattr(cls, '__qualname__'):
+            orig_vars['__qualname__'] = cls.__qualname__
         return metaclass(cls.__name__, cls.__bases__, orig_vars)
     return wrapper

Patching is done using patch -p1 — you should know how.


Back to overview Newer post: Kubernetes CRL support with the front-proxy-client and Haproxy Older post: sysctl / modules / load order / nf_conntrack