django 1.8 / python 3.10
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.