Skip to content

Commit 314273c

Browse files
authored
Fixed tuple index migrations (#2113)
* Fixed tuple index migrations * Fix lint * Fix flaky test
1 parent 937f600 commit 314273c

6 files changed

Lines changed: 45 additions & 4 deletions

File tree

CHANGELOG.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,13 @@ Changelog
88
1.1
99
===
1010

11+
1.1.5
12+
-----
13+
14+
Fixed
15+
^^^^^
16+
- ``makemigrations`` no longer crashes with ``AttributeError: 'tuple' object has no attribute 'deconstruct'`` when generating a fresh ``CreateModel`` migration for models using tuple-style ``Meta.indexes`` (e.g. ``indexes = [("field_a", "field_b")]``). Tuple entries are now normalised to ``Index`` objects before rendering.
17+
1118
1.1.4
1219
-----
1320

tests/contrib/test_decorator.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ async def test_basic_example_script(db) -> None:
1818
r = subprocess.run( # nosec
1919
[sys.executable, "examples/basic.py"], capture_output=True, text=True, env=env
2020
)
21-
assert not r.stderr, f"Script had errors: {r.stderr}"
21+
assert r.returncode == 0, f"Script failed (rc={r.returncode}): {r.stderr}"
2222
output = r.stdout
2323
s = "[{'id': 1, 'name': 'Updated name'}, {'id': 2, 'name': 'Test 2'}]"
2424
assert s in output

tests/migrations/test_writer.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,36 @@ class Migration(migrations.Migration):
187187
_write_migration(tmp_path, monkeypatch, "0003_options", operations, expected)
188188

189189

190+
def test_writer_handles_tuple_indexes_in_options(tmp_path: Path, monkeypatch) -> None:
191+
"""Tuple-style indexes in options should be normalised to Index objects without crashing."""
192+
operations = [
193+
CreateModel(
194+
name="Token",
195+
fields=[
196+
("id", fields.IntField(primary_key=True)),
197+
("user_id", fields.IntField()),
198+
("revoked_at", fields.DatetimeField(null=True)),
199+
],
200+
options={
201+
"indexes": [
202+
("user_id", "revoked_at"),
203+
],
204+
},
205+
),
206+
]
207+
module_path = _prepare_migration_package(tmp_path, "app")
208+
monkeypatch.syspath_prepend(str(tmp_path))
209+
writer = MigrationWriter(
210+
"0001_initial",
211+
"app",
212+
operations,
213+
migrations_module=module_path,
214+
)
215+
content = writer.as_string()
216+
assert "Index(fields=['user_id', 'revoked_at'])" in content
217+
assert "from tortoise.indexes import Index" in content
218+
219+
190220
def test_writer_renders_fk_field(tmp_path: Path, monkeypatch) -> None:
191221
operations = [
192222
CreateModel(

tests/test_relations.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -599,7 +599,7 @@ async def test_reverse_relation_create_fk_errors_for_unsaved_instance(db):
599599
async def test_recursive(db) -> None:
600600
file = "examples/relations_recursive.py"
601601
r = subprocess.run([sys.executable, file], capture_output=True, text=True) # nosec
602-
assert not r.stderr, f"Script had errors: {r.stderr}"
602+
assert r.returncode == 0, f"Script failed (rc={r.returncode}): {r.stderr}"
603603
output = r.stdout
604604
s = "2.1. Second H2 (to: ) (from: 2.2. Third H2, Loose, 1.1. First H2)"
605605
assert s in output

tortoise/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -585,7 +585,7 @@ async def main() -> None:
585585
portal.call(main)
586586

587587

588-
__version__ = "1.1.4"
588+
__version__ = "1.1.5"
589589

590590
__all__ = [
591591
"BackwardFKRelation",

tortoise/migrations/writer.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
from pypika_tortoise.context import DEFAULT_SQL_CONTEXT
1818

19+
from tortoise.indexes import Index
1920
from tortoise.migrations.constraints import CheckConstraint, UniqueConstraint
2021
from tortoise.migrations.operations import (
2122
AddConstraint,
@@ -455,8 +456,11 @@ def _render_model_options(self, options: dict[str, Any], imports: ImportManager)
455456
rendered: dict[str, str] = {}
456457
for key, value in options.items():
457458
if key == "indexes":
459+
normalized = [
460+
item if isinstance(item, Index) else Index(fields=tuple(item)) for item in value
461+
]
458462
rendered[key] = (
459-
"[" + ", ".join(self._render_index(item, imports) for item in value) + "]"
463+
"[" + ", ".join(self._render_index(item, imports) for item in normalized) + "]"
460464
)
461465
continue
462466
if key == "constraints":

0 commit comments

Comments
 (0)