From 02d1a8952e7de8520041464814e928755d99034e Mon Sep 17 00:00:00 2001 From: "utkarsh.arya@zomato.com" Date: Sat, 15 Nov 2025 23:01:25 +0000 Subject: [PATCH] Add numeric conversion support to LazyObject Enable int(), float(), and complex() on SimpleLazyObject to fix regression with nested subquery annotations. This restores compatibility with querysets expecting numeric types and prevents TypeError when filtering with SimpleLazyObject. Includes regression tests for numeric conversions and subquery usage. Fixes regression described in #35431298226165986ad07e91f9d3aca721ff38ec. --- django/utils/functional.py | 5 ++++ tests/queries/tests.py | 36 +++++++++++++++++++++++++++- tests/utils_tests/test_lazyobject.py | 18 ++++++++++++++ 3 files changed, 58 insertions(+), 1 deletion(-) diff --git a/django/utils/functional.py b/django/utils/functional.py index 6d38f932f9..9d1d163447 100644 --- a/django/utils/functional.py +++ b/django/utils/functional.py @@ -324,6 +324,11 @@ class LazyObject: __str__ = new_method_proxy(str) __bool__ = new_method_proxy(bool) + # Numeric conversion support + __int__ = new_method_proxy(int) + __float__ = new_method_proxy(float) + __complex__ = new_method_proxy(complex) + # Introspection support __dir__ = new_method_proxy(dir) diff --git a/tests/queries/tests.py b/tests/queries/tests.py index e57cad79ef..2c7236c4be 100644 --- a/tests/queries/tests.py +++ b/tests/queries/tests.py @@ -6,11 +6,12 @@ from operator import attrgetter from django.core.exceptions import EmptyResultSet, FieldError from django.db import DEFAULT_DB_ALIAS, connection -from django.db.models import Count, Exists, F, OuterRef, Q +from django.db.models import Count, Exists, F, OuterRef, Q, Subquery from django.db.models.sql.constants import LOUTER from django.db.models.sql.where import NothingNode, WhereNode from django.test import SimpleTestCase, TestCase, skipUnlessDBFeature from django.test.utils import CaptureQueriesContext +from django.utils.functional import SimpleLazyObject from .models import ( FK1, Annotation, Article, Author, BaseA, Book, CategoryItem, @@ -2130,6 +2131,39 @@ class SubqueryTests(TestCase): ).order_by('id').values_list('id', flat=True), [2, 4] ) + def test_nested_subquery_with_simplelazyobject(self): + """ + Test that SimpleLazyObject works with nested subquery annotations. + Regression test for the issue where using SimpleLazyObject in a filter + with nested subquery annotation would fail with: + TypeError: int() argument must be a string, a bytes-like object or a + number, not 'SimpleLazyObject' + """ + # Create test data + category = NamedCategory.objects.create(id=5, name="test") + + # Create a nested subquery annotation + # This simulates the scenario in the bug report where we have: + # - An outer query on DumbCategory + # - Annotated with a subquery on Tag + # - That subquery is itself annotated with a subquery on another model + # - And we filter using a SimpleLazyObject + tag_subquery = Tag.objects.filter( + category=OuterRef('pk') + ).values('category') + + # Wrap the category id in a SimpleLazyObject + # This is the key part - wrapping a numeric value (like a User.id) + lazy_category_id = SimpleLazyObject(lambda: category.id) + + # This should not raise a TypeError + queryset = NamedCategory.objects.annotate( + tag_category=Subquery(tag_subquery) + ).filter(tag_category=lazy_category_id) + + # Force evaluation - this is where the bug would occur + str(queryset.query) + @skipUnlessDBFeature('allow_sliced_subqueries_with_in') class QuerySetBitwiseOperationTests(TestCase): diff --git a/tests/utils_tests/test_lazyobject.py b/tests/utils_tests/test_lazyobject.py index e5bccc6362..b622d3f44c 100644 --- a/tests/utils_tests/test_lazyobject.py +++ b/tests/utils_tests/test_lazyobject.py @@ -91,6 +91,24 @@ class LazyObjectTestCase(TestCase): for t in [True, 1, (1,), {1: 2}, [1], object(), {1}]: self.assertTrue(t) + def test_int(self): + obj = self.lazy_wrap(42) + self.assertEqual(int(obj), 42) + obj = self.lazy_wrap(3.14) + self.assertEqual(int(obj), 3) + + def test_float(self): + obj = self.lazy_wrap(42) + self.assertEqual(float(obj), 42.0) + obj = self.lazy_wrap(3.14) + self.assertEqual(float(obj), 3.14) + + def test_complex(self): + obj = self.lazy_wrap(42) + self.assertEqual(complex(obj), 42+0j) + obj = self.lazy_wrap(3.14) + self.assertEqual(complex(obj), 3.14+0j) + def test_dir(self): obj = self.lazy_wrap('foo') self.assertEqual(dir(obj), dir('foo'))