summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNick Pope <nick@nickpope.me.uk>2023-11-24 11:06:29 +0000
committerGitHub <noreply@github.com>2023-11-24 12:06:29 +0100
commit5e28cd3f2cfc31bf947a747256bc036f8f64888a (patch)
tree7f687c30cd0bec4aec9837c7b311b616ebd8c4e1
parenteabfa2d0e38670365aa74117ad2f8710b81065c5 (diff)
Fixed #34983 -- Deprecated django.utils.itercompat.is_iterable().
-rw-r--r--django/contrib/auth/models.py7
-rw-r--r--django/core/checks/registry.py4
-rw-r--r--django/db/models/fields/__init__.py6
-rw-r--r--django/template/defaulttags.py4
-rw-r--r--django/template/library.py6
-rw-r--r--django/test/client.py4
-rw-r--r--django/utils/hashable.py4
-rw-r--r--django/utils/itercompat.py13
-rw-r--r--docs/internals/deprecation.txt3
-rw-r--r--docs/releases/5.1.txt4
-rw-r--r--tests/utils_tests/test_itercompat.py15
11 files changed, 54 insertions, 16 deletions
diff --git a/django/contrib/auth/models.py b/django/contrib/auth/models.py
index 85330e2c0e..e5ef1bb523 100644
--- a/django/contrib/auth/models.py
+++ b/django/contrib/auth/models.py
@@ -1,3 +1,5 @@
+from collections.abc import Iterable
+
from django.apps import apps
from django.contrib import auth
from django.contrib.auth.base_user import AbstractBaseUser, BaseUserManager
@@ -8,7 +10,6 @@ from django.core.mail import send_mail
from django.db import models
from django.db.models.manager import EmptyManager
from django.utils import timezone
-from django.utils.itercompat import is_iterable
from django.utils.translation import gettext_lazy as _
from .validators import UnicodeUsernameValidator
@@ -315,7 +316,7 @@ class PermissionsMixin(models.Model):
Return True if the user has each of the specified permissions. If
object is passed, check if the user has all required perms for it.
"""
- if not is_iterable(perm_list) or isinstance(perm_list, str):
+ if not isinstance(perm_list, Iterable) or isinstance(perm_list, str):
raise ValueError("perm_list must be an iterable of permissions.")
return all(self.has_perm(perm, obj) for perm in perm_list)
@@ -480,7 +481,7 @@ class AnonymousUser:
return _user_has_perm(self, perm, obj=obj)
def has_perms(self, perm_list, obj=None):
- if not is_iterable(perm_list) or isinstance(perm_list, str):
+ if not isinstance(perm_list, Iterable) or isinstance(perm_list, str):
raise ValueError("perm_list must be an iterable of permissions.")
return all(self.has_perm(perm, obj) for perm in perm_list)
diff --git a/django/core/checks/registry.py b/django/core/checks/registry.py
index f4bdea8691..146b28f65e 100644
--- a/django/core/checks/registry.py
+++ b/django/core/checks/registry.py
@@ -1,7 +1,7 @@
+from collections.abc import Iterable
from itertools import chain
from django.utils.inspect import func_accepts_kwargs
-from django.utils.itercompat import is_iterable
class Tags:
@@ -86,7 +86,7 @@ class CheckRegistry:
for check in checks:
new_errors = check(app_configs=app_configs, databases=databases)
- if not is_iterable(new_errors):
+ if not isinstance(new_errors, Iterable):
raise TypeError(
"The function %r did not return a list. All functions "
"registered with the checks registry must return a list." % check,
diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py
index 67e8ddc986..41735d3b7f 100644
--- a/django/db/models/fields/__init__.py
+++ b/django/db/models/fields/__init__.py
@@ -5,6 +5,7 @@ import operator
import uuid
import warnings
from base64 import b64decode, b64encode
+from collections.abc import Iterable
from functools import partialmethod, total_ordering
from django import forms
@@ -31,7 +32,6 @@ from django.utils.dateparse import (
from django.utils.duration import duration_microseconds, duration_string
from django.utils.functional import Promise, cached_property
from django.utils.ipv6 import clean_ipv6_address
-from django.utils.itercompat import is_iterable
from django.utils.text import capfirst
from django.utils.translation import gettext_lazy as _
@@ -317,13 +317,13 @@ class Field(RegisterLookupMixin):
@classmethod
def _choices_is_value(cls, value):
- return isinstance(value, (str, Promise)) or not is_iterable(value)
+ return isinstance(value, (str, Promise)) or not isinstance(value, Iterable)
def _check_choices(self):
if not self.choices:
return []
- if not is_iterable(self.choices) or isinstance(self.choices, str):
+ if not isinstance(self.choices, Iterable) or isinstance(self.choices, str):
return [
checks.Error(
"'choices' must be a mapping (e.g. a dictionary) or an iterable "
diff --git a/django/template/defaulttags.py b/django/template/defaulttags.py
index 188bdf8c05..40c2917f56 100644
--- a/django/template/defaulttags.py
+++ b/django/template/defaulttags.py
@@ -3,6 +3,7 @@ import re
import sys
import warnings
from collections import namedtuple
+from collections.abc import Iterable
from datetime import datetime
from itertools import cycle as itertools_cycle
from itertools import groupby
@@ -10,7 +11,6 @@ from itertools import groupby
from django.conf import settings
from django.utils import timezone
from django.utils.html import conditional_escape, escape, format_html
-from django.utils.itercompat import is_iterable
from django.utils.lorem_ipsum import paragraphs, words
from django.utils.safestring import mark_safe
@@ -1198,7 +1198,7 @@ def query_string(context, query_dict=None, **kwargs):
if value is None:
if key in query_dict:
del query_dict[key]
- elif is_iterable(value) and not isinstance(value, str):
+ elif isinstance(value, Iterable) and not isinstance(value, str):
query_dict.setlist(key, value)
else:
query_dict[key] = value
diff --git a/django/template/library.py b/django/template/library.py
index 16db79e4cd..4ee96cea89 100644
--- a/django/template/library.py
+++ b/django/template/library.py
@@ -1,9 +1,9 @@
+from collections.abc import Iterable
from functools import wraps
from importlib import import_module
from inspect import getfullargspec, unwrap
from django.utils.html import conditional_escape
-from django.utils.itercompat import is_iterable
from .base import Node, Template, token_kwargs
from .exceptions import TemplateSyntaxError
@@ -263,7 +263,9 @@ class InclusionNode(TagHelperNode):
t = self.filename
elif isinstance(getattr(self.filename, "template", None), Template):
t = self.filename.template
- elif not isinstance(self.filename, str) and is_iterable(self.filename):
+ elif not isinstance(self.filename, str) and isinstance(
+ self.filename, Iterable
+ ):
t = context.template.engine.select_template(self.filename)
else:
t = context.template.engine.get_template(self.filename)
diff --git a/django/test/client.py b/django/test/client.py
index 48e0588702..d1fd428ea8 100644
--- a/django/test/client.py
+++ b/django/test/client.py
@@ -2,6 +2,7 @@ import json
import mimetypes
import os
import sys
+from collections.abc import Iterable
from copy import copy
from functools import partial
from http import HTTPStatus
@@ -25,7 +26,6 @@ from django.urls import resolve
from django.utils.encoding import force_bytes
from django.utils.functional import SimpleLazyObject
from django.utils.http import urlencode
-from django.utils.itercompat import is_iterable
from django.utils.regex_helper import _lazy_re_compile
__all__ = (
@@ -303,7 +303,7 @@ def encode_multipart(boundary, data):
)
elif is_file(value):
lines.extend(encode_file(boundary, key, value))
- elif not isinstance(value, str) and is_iterable(value):
+ elif not isinstance(value, str) and isinstance(value, Iterable):
for item in value:
if is_file(item):
lines.extend(encode_file(boundary, key, item))
diff --git a/django/utils/hashable.py b/django/utils/hashable.py
index 042e1a4373..323dfe74e7 100644
--- a/django/utils/hashable.py
+++ b/django/utils/hashable.py
@@ -1,4 +1,4 @@
-from django.utils.itercompat import is_iterable
+from collections.abc import Iterable
def make_hashable(value):
@@ -19,7 +19,7 @@ def make_hashable(value):
try:
hash(value)
except TypeError:
- if is_iterable(value):
+ if isinstance(value, Iterable):
return tuple(map(make_hashable, value))
# Non-hashable, non-iterable.
raise
diff --git a/django/utils/itercompat.py b/django/utils/itercompat.py
index 9895e3f816..e4b34cd534 100644
--- a/django/utils/itercompat.py
+++ b/django/utils/itercompat.py
@@ -1,5 +1,18 @@
+# RemovedInDjango60Warning: Remove this entire module.
+
+import warnings
+
+from django.utils.deprecation import RemovedInDjango60Warning
+
+
def is_iterable(x):
"An implementation independent way of checking for iterables"
+ warnings.warn(
+ "django.utils.itercompat.is_iterable() is deprecated. "
+ "Use isinstance(..., collections.abc.Iterable) instead.",
+ RemovedInDjango60Warning,
+ stacklevel=2,
+ )
try:
iter(x)
except TypeError:
diff --git a/docs/internals/deprecation.txt b/docs/internals/deprecation.txt
index a2968ab625..dd6712e936 100644
--- a/docs/internals/deprecation.txt
+++ b/docs/internals/deprecation.txt
@@ -59,6 +59,9 @@ details on these changes.
* The ``ModelAdmin.log_deletion()`` and ``LogEntryManager.log_action()``
methods will be removed.
+* The undocumented ``django.utils.itercompat.is_iterable()`` function and the
+ ``django.utils.itercompat`` module will be removed.
+
.. _deprecation-removed-in-5.1:
5.1
diff --git a/docs/releases/5.1.txt b/docs/releases/5.1.txt
index dc48321f5c..4c95e4677f 100644
--- a/docs/releases/5.1.txt
+++ b/docs/releases/5.1.txt
@@ -303,6 +303,10 @@ Miscellaneous
``ModelAdmin.log_deletions()`` and ``LogEntryManager.log_actions()``
instead.
+* The undocumented ``django.utils.itercompat.is_iterable()`` function and the
+ ``django.utils.itercompat`` module are deprecated. Use
+ ``isinstance(..., collections.abc.Iterable)`` instead.
+
Features removed in 5.1
=======================
diff --git a/tests/utils_tests/test_itercompat.py b/tests/utils_tests/test_itercompat.py
new file mode 100644
index 0000000000..e6ea278ab4
--- /dev/null
+++ b/tests/utils_tests/test_itercompat.py
@@ -0,0 +1,15 @@
+# RemovedInDjango60Warning: Remove this entire module.
+
+from django.test import SimpleTestCase
+from django.utils.deprecation import RemovedInDjango60Warning
+from django.utils.itercompat import is_iterable
+
+
+class TestIterCompat(SimpleTestCase):
+ def test_is_iterable_deprecation(self):
+ msg = (
+ "django.utils.itercompat.is_iterable() is deprecated. "
+ "Use isinstance(..., collections.abc.Iterable) instead."
+ )
+ with self.assertWarnsMessage(RemovedInDjango60Warning, msg):
+ is_iterable([])