Skip to content

Feature request: Native support for Pydantic RootModel #421

@distsystem-dev

Description

@distsystem-dev

Summary

When using a Pydantic RootModel subclass as a CLI argument type, tyro attempts to expand it as a struct type (via pydantic_rule), which triggers a warning and creates an unwanted subcommand structure instead of treating it as a simple string argument.

Example

from pydantic import RootModel
from urllib.parse import ParseResult
from typing import Annotated
import tyro

class Resource(RootModel[ParseResult], frozen=True):
    pass

@dataclass
class Config:
    resource: Annotated[Resource, tyro.conf.Positional]

tyro.cli(Config)

Current behavior:

  • Warning: TyroWarning: Could not resolve type parameter ~RootModelRootType
  • resource is expanded into subcommands (resource.root:parse-result, etc.)

Expected behavior:

  • resource should be a simple positional argument accepting a string
  • The string is passed to RootModel.__init__ which handles coercion via validators

Proposed Solution

RootModel subclasses should be treated as primitive types (like Path or datetime), not struct types. This could be done by:

  1. Adding a check in pydantic_rule to skip RootModel subclasses
  2. Adding a primitive rule that handles RootModel with nargs=1

Example primitive rule:

@registry.primitive_rule
def rootmodel_rule(info: PrimitiveTypeInfo) -> PrimitiveConstructorSpec | None:
    from pydantic import RootModel
    try:
        if not issubclass(info.type, RootModel):
            return None
    except TypeError:
        return None

    typ = info.type
    return PrimitiveConstructorSpec(
        nargs=1,
        metavar=typ.__name__.upper(),
        instance_from_str=lambda args: typ(args[0]),
        is_instance=lambda x: isinstance(x, typ),
        str_from_instance=lambda r: [str(r.root)],
    )

Workaround

Currently we patch the default registry manually, but this relies on private APIs (_active_registries, _struct_rules).

Environment

  • tyro version: 0.9.x
  • pydantic version: 2.x
  • Python version: 3.12

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions