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:
- Adding a check in
pydantic_rule to skip RootModel subclasses
- 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
Summary
When using a Pydantic
RootModelsubclass as a CLI argument type, tyro attempts to expand it as a struct type (viapydantic_rule), which triggers a warning and creates an unwanted subcommand structure instead of treating it as a simple string argument.Example
Current behavior:
TyroWarning: Could not resolve type parameter ~RootModelRootTyperesourceis expanded into subcommands (resource.root:parse-result, etc.)Expected behavior:
resourceshould be a simple positional argument accepting a stringRootModel.__init__which handles coercion via validatorsProposed Solution
RootModelsubclasses should be treated as primitive types (likePathordatetime), not struct types. This could be done by:pydantic_ruleto skipRootModelsubclassesRootModelwithnargs=1Example primitive rule:
Workaround
Currently we patch the default registry manually, but this relies on private APIs (
_active_registries,_struct_rules).Environment