modelgen: recurse through nested generics in list resolvers

_get_list_inner (Pydantic) and _resolve_ts_list (TypeScript) collapsed
nested generics to "str" / "string[]" — so List[List[int]] became
List[str] and List[Dict[str,Any]] became List[str]. Fix recurses on
list-of-list and falls back to Dict[str,Any] / Record<string,unknown>
for list-of-dict.

Regenerated outputs:
- core/gpu/models/models.py: SegmentFieldResponse.boundary now
  List[List[int]], unblocking /segment_field which was 500ing on every
  request with pydantic ValidationError
- core/db/models.py + ui/common/types/generated.ts: Brand.airings now
  matches its source schema (List[Dict[str,Any]] / Record<string,unknown>[])
This commit is contained in:
2026-04-29 08:48:21 -03:00
parent f66d3a273f
commit ddb4f17faa
4 changed files with 18 additions and 6 deletions

View File

@@ -8,6 +8,8 @@ Used by generators to convert Python types to target framework types.
import dataclasses as dc
from typing import Any, Callable, get_args
from .helpers import get_origin_name
# =============================================================================
# Django Type Mappings
# =============================================================================
@@ -38,7 +40,7 @@ DJANGO_SPECIAL: dict[str, str] = {
def _get_list_inner(type_hint: Any) -> str:
"""Get inner type of List[T] for Pydantic."""
"""Get inner type of List[T] for Pydantic. Recurses for nested generics."""
args = get_args(type_hint)
if args:
inner = args[0]
@@ -46,6 +48,11 @@ def _get_list_inner(type_hint: Any) -> str:
return {str: "str", int: "int", float: "float", bool: "bool"}[inner]
if isinstance(inner, type) and dc.is_dataclass(inner):
return inner.__name__
origin = get_origin_name(inner)
if origin == "list":
return f"List[{_get_list_inner(inner)}]"
if origin == "dict":
return "Dict[str, Any]"
return "str"
@@ -69,7 +76,7 @@ PYDANTIC_RESOLVERS: dict[Any, Callable[[Any], str]] = {
def _resolve_ts_list(base: Any) -> str:
"""Resolve TypeScript list type."""
"""Resolve TypeScript list type. Recurses for nested generics."""
args = get_args(base)
if args:
inner = args[0]
@@ -81,6 +88,11 @@ def _resolve_ts_list(base: Any) -> str:
return "boolean[]"
elif isinstance(inner, type) and dc.is_dataclass(inner):
return f"{inner.__name__}[]"
origin = get_origin_name(inner)
if origin == "list":
return f"{_resolve_ts_list(inner)}[]"
if origin == "dict":
return "Record<string, unknown>[]"
return "string[]"