3434try :
3535 from .cli_flags import CliFlags
3636 from tyro import cli
37-
38- try : # tyro >= 0.10
39- from tyro import _experimental_options
40-
41- _experimental_options ["backend" ] = "argparse"
42- from tyro ._backends ._argparse import _SubParsersAction , ArgumentParser
43- from tyro ._backends ._argparse_formatter import TyroArgumentParser
44- except ImportError :
45- from tyro ._argparse import _SubParsersAction , ArgumentParser
46- from tyro ._argparse_formatter import TyroArgumentParser
47- from tyro ._parsers import ParserSpecification
37+ from tyro import _experimental_options
38+ from tyro ._backends import _tyro_help_formatting
39+ from tyro ._backends ._tyro_backend import TyroBackend
40+ from tyro ._parsers import ParserSpecification , SubparsersSpecification , ArgWithContext
4841
4942 from tyro .conf import OmitArgPrefixes , OmitSubcommandPrefixes , DisallowNone , FlagCreatePairsOff
5043
5144 from .tyro_patches import (
5245 _crawling ,
53- custom_error ,
54- custom_init ,
55- custom_parse_known_args ,
5646 failed_fields ,
57- patched__parse_known_args ,
58- patched__format_help ,
59- subparser_call ,
60- argparse_init ,
47+ tyro_required_args_error ,
48+ tyro_error_and_exit ,
49+ tyro_parse_args ,
6150 )
51+
52+ # The native backend (tyro >= 1.0) is ~2-3× faster than the argparse one
53+ # (HeavyNesting: 15 ms → 5 ms) and is the only one mininterface patches support.
54+ _experimental_options ["backend" ] = "tyro"
6255except ImportError :
6356 from ..exceptions import DependencyRequired
6457
@@ -250,7 +243,7 @@ def annot(type_form):
250243 if sys .version_info < (3 , 11 ):
251244 raise
252245 # Form did not work, cancelled or run through minadaptor.
253- # We use the original tyro exception message, caught in tyro_patches.custom_error
246+ # We use the original tyro exception message, noted in tyro_patches.tyro_required_args_error
254247 # instead of a validation error the minadaptor might produce.
255248 # NOTE We might add minadaptor validation error. But it seems too similar to the better tyro's one.
256249 # if str(e):
@@ -297,7 +290,7 @@ def _try_with_subcommands(kwargs, m, args, type_form, env_classes, _custom_regis
297290 old_defs = kwargs .get ("default" , {})
298291 if old_defs :
299292 old_defs = asdict (old_defs )
300- passage = [cl_name for _ , cl_name , _ in _crawling .get ()]
293+ passage = [cl_name for cl_name , _ in _crawling .get ()]
301294
302295 if len (env_classes ) > 1 :
303296 if len (passage ):
@@ -322,42 +315,21 @@ def _try_with_subcommands(kwargs, m, args, type_form, env_classes, _custom_regis
322315
323316
324317def _apply_patches (cf : Optional [CliFlags ], ask_for_missing , env_classes , kwargs ):
325- patches = []
326-
327- patches .append (patch .object (_SubParsersAction , "__call__" , subparser_call ))
328- patches .append (patch .object (TyroArgumentParser , "_parse_known_args" , patched__parse_known_args ))
329- kw = {
330- k : v for k , v in kwargs .items () if k != "default"
331- } # NOTE I might separate kwargs['default'] and do not do this filtering
332- if kw :
333- patches .append (patch .object (ArgumentParser , "__init__" , argparse_init (kw )))
334-
335- if ask_for_missing : # Get the missing flags from the parser
336- patches .append (patch .object (TyroArgumentParser , "error" , custom_error ))
337- if cf and cf .should_add (env_classes ):
338- # Mock parser to add some flags
339- # Flags are added only if neither the env_class nor any of the subcommands have the same-name flag already
340- patches .extend (
341- (
342- patch .object (
343- TyroArgumentParser ,
344- "__init__" ,
345- custom_init (cf ),
346- ),
347- patch .object (
348- TyroArgumentParser ,
349- "format_help" ,
350- patched__format_help (cf ),
351- ),
352- patch .object (
353- TyroArgumentParser ,
354- "parse_known_args" ,
355- custom_parse_known_args (cf ),
356- ),
357- )
358- )
359-
360- return patches
318+ """Patches for the native tyro backend. See tyro_patches for details.
319+ CliFlags are added only if neither the env_class nor any of the subcommands
320+ have the same-name flag already."""
321+ return [
322+ patch .object (_tyro_help_formatting , "required_args_error" , tyro_required_args_error (ask_for_missing )),
323+ patch .object (_tyro_help_formatting , "error_and_exit" , tyro_error_and_exit (ask_for_missing )),
324+ patch .object (
325+ TyroBackend ,
326+ "parse_args" ,
327+ tyro_parse_args (
328+ cf if cf and cf .should_add (env_classes ) else None ,
329+ allow_abbrev = kwargs .get ("allow_abbrev" , False ),
330+ ),
331+ ),
332+ ]
361333
362334
363335def _dialog_missing (
@@ -375,7 +347,7 @@ def _dialog_missing(
375347
376348 * kwargs["default"]. Struct (dataclass). The fields that must be filled are marked as MISSING_NONPROP.
377349 If marked directly with `tag._make_default_value()`, tyro would resolve CLI instantly with no further problem but we would never known which were missing CLI flags were missing.
378- * failed_fields – Argparse Actions . Parser needs them filled. (It might not tell us about all of them. There is a use-case when superparser is resolved after subparser. And if whole subparser command is missing, its fields are not there either.)
350+ * failed_fields – ArgWithContext / SubparsersSpecification . Parser needs them filled. (It might not tell us about all of them. There is a use-case when superparser is resolved after subparser. And if whole subparser command is missing, its fields are not there either.)
379351 * req_fields – Tags. The same form as kwargs["default"]. Recursively all fields, needed to build up a dataclass for mininterface. The fields that must be filled are marked as MissingTagValue().
380352 * missing_req – Tags. Those req_fields which are missing from CLI. Merge of failed_fields and req_fields. (Subset of req_fields.)
381353 Their values are `tag._make_default_value()`.
@@ -408,7 +380,7 @@ def _dialog_missing(
408380 req_fields ,
409381 m ,
410382 subc = kwargs .get ("subcommands_default" ),
411- subc_passage = [cl_name for _ , cl_name , _ in _crawling .get ()],
383+ subc_passage = [cl_name for cl_name , _ in _crawling .get ()],
412384 )
413385
414386 missing_req = _fetch_currently_failed (req_fields )
@@ -475,16 +447,24 @@ def _fetch_currently_failed(requireds) -> TagDict:
475447 who pose problem for tyro (through implanted failed_fields)."""
476448 missing_req = {}
477449 for field in failed_fields .get ():
478- # ex: `_subcommands._nested_subcommands (positional)`
450+ # Determine the dest-like dotted name, ex: `_subcommands._nested_subcommands (positional)`
451+ if isinstance (field , SubparsersSpecification ):
452+ # a whole subcommand is missing
453+ dest = field .intern_prefix
454+ else :
455+ # ArgWithContext, a required argument is missing
456+ # (get_output_key gives lowered.dest, or the positional name)
457+ dest = field .arg .get_output_key ()
458+
479459 fname = (
480- field . dest .replace (" (positional)" , "" )
460+ dest .replace (" (positional)" , "" )
481461 .replace ("-" , "_" )
482462 .replace ("__tyro_dummy_inner__." , "" )
483463 .replace ("__tyro_dummy_inner__" , "" )
484464 ) # `_subcommands._nested_subcommands`
485465 fname_raw = fname .rsplit ("." , 1 )[- 1 ] # `_nested_subcommands`
486466
487- if isinstance (field , _SubParsersAction ):
467+ if isinstance (field , SubparsersSpecification ):
488468 # The function create_with_missing don't makes every encountered field a wrong field
489469 # (with the exception of the config fields, defined in the kwargs["default"] earlier).
490470 # The CLI options are unknown to it.
0 commit comments