Fixed #34983 -- Deprecated django.utils.itercompat.is_iterable().

This commit is contained in:
Nick Pope 2023-11-24 11:06:29 +00:00 committed by GitHub
parent eabfa2d0e3
commit 5e28cd3f2c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 54 additions and 16 deletions

View File

@ -1,3 +1,5 @@
from collections.abc import Iterable
from django.apps import apps from django.apps import apps
from django.contrib import auth from django.contrib import auth
from django.contrib.auth.base_user import AbstractBaseUser, BaseUserManager 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 import models
from django.db.models.manager import EmptyManager from django.db.models.manager import EmptyManager
from django.utils import timezone from django.utils import timezone
from django.utils.itercompat import is_iterable
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from .validators import UnicodeUsernameValidator from .validators import UnicodeUsernameValidator
@ -315,7 +316,7 @@ class PermissionsMixin(models.Model):
Return True if the user has each of the specified permissions. If 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. 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.") raise ValueError("perm_list must be an iterable of permissions.")
return all(self.has_perm(perm, obj) for perm in perm_list) 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) return _user_has_perm(self, perm, obj=obj)
def has_perms(self, perm_list, obj=None): 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.") raise ValueError("perm_list must be an iterable of permissions.")
return all(self.has_perm(perm, obj) for perm in perm_list) return all(self.has_perm(perm, obj) for perm in perm_list)

View File

@ -1,7 +1,7 @@
from collections.abc import Iterable
from itertools import chain from itertools import chain
from django.utils.inspect import func_accepts_kwargs from django.utils.inspect import func_accepts_kwargs
from django.utils.itercompat import is_iterable
class Tags: class Tags:
@ -86,7 +86,7 @@ class CheckRegistry:
for check in checks: for check in checks:
new_errors = check(app_configs=app_configs, databases=databases) new_errors = check(app_configs=app_configs, databases=databases)
if not is_iterable(new_errors): if not isinstance(new_errors, Iterable):
raise TypeError( raise TypeError(
"The function %r did not return a list. All functions " "The function %r did not return a list. All functions "
"registered with the checks registry must return a list." % check, "registered with the checks registry must return a list." % check,

View File

@ -5,6 +5,7 @@ import operator
import uuid import uuid
import warnings import warnings
from base64 import b64decode, b64encode from base64 import b64decode, b64encode
from collections.abc import Iterable
from functools import partialmethod, total_ordering from functools import partialmethod, total_ordering
from django import forms 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.duration import duration_microseconds, duration_string
from django.utils.functional import Promise, cached_property from django.utils.functional import Promise, cached_property
from django.utils.ipv6 import clean_ipv6_address from django.utils.ipv6 import clean_ipv6_address
from django.utils.itercompat import is_iterable
from django.utils.text import capfirst from django.utils.text import capfirst
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
@ -317,13 +317,13 @@ class Field(RegisterLookupMixin):
@classmethod @classmethod
def _choices_is_value(cls, value): 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): def _check_choices(self):
if not self.choices: if not self.choices:
return [] return []
if not is_iterable(self.choices) or isinstance(self.choices, str): if not isinstance(self.choices, Iterable) or isinstance(self.choices, str):
return [ return [
checks.Error( checks.Error(
"'choices' must be a mapping (e.g. a dictionary) or an iterable " "'choices' must be a mapping (e.g. a dictionary) or an iterable "

View File

@ -3,6 +3,7 @@ import re
import sys import sys
import warnings import warnings
from collections import namedtuple from collections import namedtuple
from collections.abc import Iterable
from datetime import datetime from datetime import datetime
from itertools import cycle as itertools_cycle from itertools import cycle as itertools_cycle
from itertools import groupby from itertools import groupby
@ -10,7 +11,6 @@ from itertools import groupby
from django.conf import settings from django.conf import settings
from django.utils import timezone from django.utils import timezone
from django.utils.html import conditional_escape, escape, format_html 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.lorem_ipsum import paragraphs, words
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
@ -1198,7 +1198,7 @@ def query_string(context, query_dict=None, **kwargs):
if value is None: if value is None:
if key in query_dict: if key in query_dict:
del query_dict[key] 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) query_dict.setlist(key, value)
else: else:
query_dict[key] = value query_dict[key] = value

View File

@ -1,9 +1,9 @@
from collections.abc import Iterable
from functools import wraps from functools import wraps
from importlib import import_module from importlib import import_module
from inspect import getfullargspec, unwrap from inspect import getfullargspec, unwrap
from django.utils.html import conditional_escape from django.utils.html import conditional_escape
from django.utils.itercompat import is_iterable
from .base import Node, Template, token_kwargs from .base import Node, Template, token_kwargs
from .exceptions import TemplateSyntaxError from .exceptions import TemplateSyntaxError
@ -263,7 +263,9 @@ class InclusionNode(TagHelperNode):
t = self.filename t = self.filename
elif isinstance(getattr(self.filename, "template", None), Template): elif isinstance(getattr(self.filename, "template", None), Template):
t = self.filename.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) t = context.template.engine.select_template(self.filename)
else: else:
t = context.template.engine.get_template(self.filename) t = context.template.engine.get_template(self.filename)

View File

@ -2,6 +2,7 @@ import json
import mimetypes import mimetypes
import os import os
import sys import sys
from collections.abc import Iterable
from copy import copy from copy import copy
from functools import partial from functools import partial
from http import HTTPStatus from http import HTTPStatus
@ -25,7 +26,6 @@ from django.urls import resolve
from django.utils.encoding import force_bytes from django.utils.encoding import force_bytes
from django.utils.functional import SimpleLazyObject from django.utils.functional import SimpleLazyObject
from django.utils.http import urlencode from django.utils.http import urlencode
from django.utils.itercompat import is_iterable
from django.utils.regex_helper import _lazy_re_compile from django.utils.regex_helper import _lazy_re_compile
__all__ = ( __all__ = (
@ -303,7 +303,7 @@ def encode_multipart(boundary, data):
) )
elif is_file(value): elif is_file(value):
lines.extend(encode_file(boundary, key, 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: for item in value:
if is_file(item): if is_file(item):
lines.extend(encode_file(boundary, key, item)) lines.extend(encode_file(boundary, key, item))

View File

@ -1,4 +1,4 @@
from django.utils.itercompat import is_iterable from collections.abc import Iterable
def make_hashable(value): def make_hashable(value):
@ -19,7 +19,7 @@ def make_hashable(value):
try: try:
hash(value) hash(value)
except TypeError: except TypeError:
if is_iterable(value): if isinstance(value, Iterable):
return tuple(map(make_hashable, value)) return tuple(map(make_hashable, value))
# Non-hashable, non-iterable. # Non-hashable, non-iterable.
raise raise

View File

@ -1,5 +1,18 @@
# RemovedInDjango60Warning: Remove this entire module.
import warnings
from django.utils.deprecation import RemovedInDjango60Warning
def is_iterable(x): def is_iterable(x):
"An implementation independent way of checking for iterables" "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: try:
iter(x) iter(x)
except TypeError: except TypeError:

View File

@ -59,6 +59,9 @@ details on these changes.
* The ``ModelAdmin.log_deletion()`` and ``LogEntryManager.log_action()`` * The ``ModelAdmin.log_deletion()`` and ``LogEntryManager.log_action()``
methods will be removed. 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: .. _deprecation-removed-in-5.1:
5.1 5.1

View File

@ -303,6 +303,10 @@ Miscellaneous
``ModelAdmin.log_deletions()`` and ``LogEntryManager.log_actions()`` ``ModelAdmin.log_deletions()`` and ``LogEntryManager.log_actions()``
instead. 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 Features removed in 5.1
======================= =======================

View File

@ -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([])