@@ -1693,3 +1693,202 @@ async def test_save_nx_with_pipeline(m, address):
16931693 fetched2 = await m .Member .get (member2 .pk )
16941694 assert fetched1 .first_name == "Andrew"
16951695 assert fetched2 .first_name == "Kim"
1696+
1697+
1698+ @py_test_mark_asyncio
1699+ async def test_schema_for_fields_does_not_modify_dict_during_iteration (m ):
1700+ """
1701+ Regression test for GitHub issue #763.
1702+
1703+ In Python 3.14, iterating over cls.__dict__.items() directly can raise
1704+ RuntimeError: dictionary changed size during iteration. This test verifies
1705+ that JsonModel.schema_for_fields() works without raising this error by
1706+ iterating over annotation keys and looking up in __dict__ individually.
1707+ """
1708+ # This should not raise RuntimeError on Python 3.14+
1709+ schema = m .Member .schema_for_fields ()
1710+
1711+ # Verify the schema is generated correctly
1712+ assert isinstance (schema , list )
1713+ assert len (schema ) > 0
1714+
1715+ # Verify schema contains expected fields
1716+ schema_str = " " .join (schema )
1717+ assert "first_name" in schema_str
1718+ assert "last_name" in schema_str
1719+
1720+
1721+ @py_test_mark_asyncio
1722+ async def test_schema_for_fields_with_indexed_fields (key_prefix , redis ):
1723+ """Test schema_for_fields includes all indexed field types correctly."""
1724+
1725+ class TestIndexedFields (JsonModel , index = True ):
1726+ text_field : str = Field (index = True )
1727+ numeric_field : int = Field (index = True )
1728+ tag_field : str = Field (index = True )
1729+ sortable_field : str = Field (index = True , sortable = True )
1730+ fulltext_field : str = Field (full_text_search = True )
1731+
1732+ class Meta :
1733+ global_key_prefix = key_prefix
1734+ database = redis
1735+
1736+ schema = TestIndexedFields .schema_for_fields ()
1737+ schema_str = " " .join (schema )
1738+
1739+ # All indexed fields should appear in schema
1740+ assert "text_field" in schema_str
1741+ assert "numeric_field" in schema_str
1742+ assert "tag_field" in schema_str
1743+ assert "sortable_field" in schema_str
1744+ assert "fulltext_field" in schema_str
1745+ assert "SORTABLE" in schema_str
1746+
1747+
1748+ @py_test_mark_asyncio
1749+ async def test_schema_for_fields_with_optional_fields (key_prefix , redis ):
1750+ """Test schema_for_fields handles Optional fields correctly."""
1751+
1752+ class TestOptionalFields (JsonModel , index = True ):
1753+ required_field : str = Field (index = True )
1754+ optional_field : Optional [str ] = Field (index = True , default = None )
1755+ optional_with_default : Optional [int ] = Field (index = True , default = 42 )
1756+
1757+ class Meta :
1758+ global_key_prefix = key_prefix
1759+ database = redis
1760+
1761+ schema = TestOptionalFields .schema_for_fields ()
1762+ schema_str = " " .join (schema )
1763+
1764+ assert "required_field" in schema_str
1765+ assert "optional_field" in schema_str
1766+ assert "optional_with_default" in schema_str
1767+
1768+
1769+ @py_test_mark_asyncio
1770+ async def test_schema_for_fields_with_inherited_fields (key_prefix , redis ):
1771+ """Test schema_for_fields correctly includes inherited fields."""
1772+
1773+ class BaseModel (JsonModel ):
1774+ base_field : str = Field (index = True )
1775+
1776+ class Meta :
1777+ global_key_prefix = key_prefix
1778+ database = redis
1779+
1780+ class ChildModel (BaseModel , index = True ):
1781+ child_field : str = Field (index = True )
1782+
1783+ schema = ChildModel .schema_for_fields ()
1784+ schema_str = " " .join (schema )
1785+
1786+ # Both base and child fields should be in schema
1787+ assert "base_field" in schema_str
1788+ assert "child_field" in schema_str
1789+
1790+
1791+ @py_test_mark_asyncio
1792+ async def test_schema_for_fields_with_embedded_model (key_prefix , redis ):
1793+ """Test schema_for_fields handles embedded models."""
1794+
1795+ class EmbeddedAddress (EmbeddedJsonModel , index = True ):
1796+ city : str = Field (index = True )
1797+ zip_code : str = Field (index = True )
1798+
1799+ class PersonWithAddress (JsonModel , index = True ):
1800+ name : str = Field (index = True )
1801+ address : EmbeddedAddress
1802+
1803+ class Meta :
1804+ global_key_prefix = key_prefix
1805+ database = redis
1806+
1807+ schema = PersonWithAddress .schema_for_fields ()
1808+ schema_str = " " .join (schema )
1809+
1810+ # Main field and embedded fields should be in schema
1811+ assert "name" in schema_str
1812+ assert "city" in schema_str or "address" in schema_str
1813+
1814+
1815+ @py_test_mark_asyncio
1816+ async def test_schema_for_fields_with_list_fields (key_prefix , redis ):
1817+ """Test schema_for_fields handles List[str] fields."""
1818+
1819+ class ModelWithList (JsonModel , index = True ):
1820+ tags : List [str ] = Field (index = True )
1821+ name : str = Field (index = True )
1822+
1823+ class Meta :
1824+ global_key_prefix = key_prefix
1825+ database = redis
1826+
1827+ schema = ModelWithList .schema_for_fields ()
1828+ schema_str = " " .join (schema )
1829+
1830+ assert "tags" in schema_str
1831+ assert "name" in schema_str
1832+
1833+
1834+ @py_test_mark_asyncio
1835+ async def test_schema_for_fields_field_info_has_annotation (key_prefix , redis ):
1836+ """Test that FieldInfo objects have their annotations set correctly."""
1837+ from pydantic .fields import FieldInfo
1838+
1839+ class TestModel (JsonModel , index = True ):
1840+ indexed_str : str = Field (index = True )
1841+ indexed_int : int = Field (index = True )
1842+
1843+ class Meta :
1844+ global_key_prefix = key_prefix
1845+ database = redis
1846+
1847+ # Call schema_for_fields to trigger field processing
1848+ TestModel .schema_for_fields ()
1849+
1850+ # Check that model_fields have annotations
1851+ for name , field in TestModel .model_fields .items ():
1852+ if name == "pk" :
1853+ continue
1854+ assert field .annotation is not None , f"Field { name } should have annotation"
1855+
1856+
1857+ @py_test_mark_asyncio
1858+ async def test_schema_for_fields_with_primary_key (key_prefix , redis ):
1859+ """Test schema_for_fields handles custom primary keys."""
1860+
1861+ class ModelWithCustomPK (JsonModel , index = True ):
1862+ custom_id : str = Field (primary_key = True , index = True )
1863+ name : str = Field (index = True )
1864+
1865+ class Meta :
1866+ global_key_prefix = key_prefix
1867+ database = redis
1868+
1869+ schema = ModelWithCustomPK .schema_for_fields ()
1870+ schema_str = " " .join (schema )
1871+
1872+ assert "custom_id" in schema_str
1873+ assert "name" in schema_str
1874+
1875+
1876+ @py_test_mark_asyncio
1877+ async def test_schema_for_fields_with_case_sensitive (key_prefix , redis ):
1878+ """Test schema_for_fields respects case_sensitive option."""
1879+
1880+ class ModelWithCaseSensitive (JsonModel , index = True ):
1881+ case_sensitive_field : str = Field (index = True , case_sensitive = True )
1882+ normal_field : str = Field (index = True )
1883+
1884+ class Meta :
1885+ global_key_prefix = key_prefix
1886+ database = redis
1887+
1888+ schema = ModelWithCaseSensitive .schema_for_fields ()
1889+ schema_str = " " .join (schema )
1890+
1891+ assert "case_sensitive_field" in schema_str
1892+ assert "normal_field" in schema_str
1893+ # Case sensitive fields use CASESENSITIVE in schema
1894+ assert "CASESENSITIVE" in schema_str
0 commit comments