Skip to content

Commit

Permalink
Fixed #34983 -- Deprecated django.utils.itercompat.is_iterable().
Browse files Browse the repository at this point in the history
  • Loading branch information
ngnpope authored Nov 24, 2023
1 parent eabfa2d commit 5e28cd3
Show file tree
Hide file tree
Showing 11 changed files with 54 additions and 16 deletions.
7 changes: 4 additions & 3 deletions django/contrib/auth/models.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -8,7 +10,6 @@
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
Expand Down Expand Up @@ -315,7 +316,7 @@ def has_perms(self, perm_list, obj=None):
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)

Expand Down Expand Up @@ -480,7 +481,7 @@ def has_perm(self, perm, obj=None):
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)

Expand Down
4 changes: 2 additions & 2 deletions django/core/checks/registry.py
Original file line number Diff line number Diff line change
@@ -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:
Expand Down Expand Up @@ -86,7 +86,7 @@ def run_checks(

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,
Expand Down
6 changes: 3 additions & 3 deletions django/db/models/fields/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import uuid
import warnings
from base64 import b64decode, b64encode
from collections.abc import Iterable
from functools import partialmethod, total_ordering

from django import forms
Expand All @@ -31,7 +32,6 @@
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 _

Expand Down Expand Up @@ -317,13 +317,13 @@ def _check_field_name(self):

@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 "
Expand Down
4 changes: 2 additions & 2 deletions django/template/defaulttags.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@
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

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

Expand Down Expand Up @@ -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
Expand Down
6 changes: 4 additions & 2 deletions django/template/library.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -263,7 +263,9 @@ def render(self, context):
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)
Expand Down
4 changes: 2 additions & 2 deletions django/test/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import mimetypes
import os
import sys
from collections.abc import Iterable
from copy import copy
from functools import partial
from http import HTTPStatus
Expand All @@ -25,7 +26,6 @@
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__ = (
Expand Down Expand Up @@ -303,7 +303,7 @@ def is_file(thing):
)
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))
Expand Down
4 changes: 2 additions & 2 deletions django/utils/hashable.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from django.utils.itercompat import is_iterable
from collections.abc import Iterable


def make_hashable(value):
Expand All @@ -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
Expand Down
13 changes: 13 additions & 0 deletions django/utils/itercompat.py
Original file line number Diff line number Diff line change
@@ -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:
Expand Down
3 changes: 3 additions & 0 deletions docs/internals/deprecation.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions docs/releases/5.1.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
=======================

Expand Down
15 changes: 15 additions & 0 deletions tests/utils_tests/test_itercompat.py
Original file line number Diff line number Diff line change
@@ -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([])

0 comments on commit 5e28cd3

Please sign in to comment.