Skip to content

Commit 7595a87

Browse files
committed
deps (tyro): ready for 0.10
1 parent baf3974 commit 7595a87

File tree

13 files changed

+288
-137
lines changed

13 files changed

+288
-137
lines changed

docs/Changelog.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
# Changelog
22

3+
## 1.2.1 (2025-10-24)
4+
* deps (tyro): ready for 0.10
5+
36
## 1.2.0 (2025-10-17)
47
* feat: [`run`][mininterface.run] add_config flag
58
* feat: [subcommands](Supported-types.md/#dataclasses-union-subcommand) allowed in the config file

mininterface/__main__.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
from ast import literal_eval
21
from dataclasses import dataclass
32
from os import environ
43
from pathlib import Path

mininterface/_lib/cli_flags.py

Lines changed: 117 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,22 @@
1-
from functools import lru_cache
1+
from argparse import ArgumentParser
22
import logging
3-
from dataclasses import dataclass
3+
import sys
44
from typing import Optional, Sequence
55

6+
from tyro.conf import FlagConversionOff
7+
68
from .form_dict import EnvClass
79

10+
from typing import List, Any, Optional
11+
12+
from tyro._fields import FieldDefinition
13+
from tyro.conf._confstruct import _ArgConfig
14+
815

916
class CliFlags:
1017

1118
_add_verbose: bool = False
12-
version: bool | str = False
19+
version: str = ""
1320
_add_quiet: bool = False
1421

1522
default_verbosity: int = logging.WARNING
@@ -56,6 +63,17 @@ def __init__(
5663
# config
5764
self.config = add_config
5865

66+
self.orig_stream = (
67+
sys.stderr
68+
) # NOTE might be removed now. Might be used if we redirect_stderr while setting basicConfig.
69+
70+
self.field_list: list[FieldDefinition] = []
71+
""" List of FieldDefinitions corresponding to the arguments added via this helper"""
72+
73+
self.arguments_prepared: list[dict[str, Any]] = []
74+
self.setup_done = False
75+
""" Setup might be called multiple times – ex. parsing fails and we call tyro.cli in recursion. """
76+
5977
def should_add(self, env_classes: list[EnvClass]) -> bool:
6078
# Flags are added only if neither the env_class nor any of the subcommands have the same-name flag already
6179
self._enabled["verbose"] = self._add_verbose and self._attr_not_present("verbose", env_classes)
@@ -116,3 +134,99 @@ def get_log_level(self, count):
116134
seq = self._verbosity_sequence
117135
log_level = {i + 1: level for i, level in enumerate(seq)}.get(count, logging.NOTSET)
118136
return log_level
137+
138+
def add_typed_argument(
139+
self,
140+
prefix: str,
141+
*aliases: str,
142+
action: Optional[str] = None,
143+
default: Any = False,
144+
helptext: Optional[str] = None,
145+
metavar: Optional[str] = None,
146+
version: Optional[str] = None,
147+
) -> FieldDefinition:
148+
# Prepare FieldDefinition
149+
name = aliases[0]
150+
aliases_=tuple((prefix * (1 if len(n) == 1 else 2)+n) for n in aliases) if aliases else None
151+
typ_ = bool if action in ("store_true", "store_false") else int if action == "count" else str
152+
153+
field = FieldDefinition(
154+
intern_name=name,
155+
extern_name=name,
156+
type=typ_,
157+
type_stripped=typ_,
158+
default=default,
159+
helptext=helptext,
160+
markers={FlagConversionOff},
161+
custom_constructor=False,
162+
argconf=_ArgConfig(
163+
name=aliases_[0],
164+
metavar="",
165+
help=helptext,
166+
help_behavior_hint="",
167+
aliases=aliases_[1:] or None,
168+
prefix_name=False,
169+
constructor_factory=None,
170+
default=default,
171+
),
172+
mutex_group=None,
173+
call_argname=name,
174+
)
175+
176+
self.field_list.append(field)
177+
178+
# prepare argparse
179+
self.arguments_prepared.append(
180+
{
181+
"field": field,
182+
"names": aliases_,
183+
"kwargs": {
184+
"action": action,
185+
"default": default,
186+
"help": helptext,
187+
"metavar": metavar,
188+
"version": version,
189+
},
190+
}
191+
)
192+
193+
return field
194+
195+
def setup(self, parser:ArgumentParser):
196+
if self.setup_done:
197+
# tyro.cli might be called multiple times if some missing required fields
198+
return
199+
self.setup_done = True
200+
prefix = "-" if "-" in parser.prefix_chars else parser.prefix_chars[0]
201+
if self.add_verbose:
202+
self.add_typed_argument(prefix,
203+
"verbose",
204+
"v",
205+
action="count",
206+
default=0,
207+
helptext="verbosity level, can be used multiple times to increase",
208+
)
209+
210+
if self.add_version:
211+
self.add_typed_argument(prefix,
212+
"version",
213+
action="version",
214+
version=self.version,
215+
default="",
216+
helptext=f"show program's version number ({self.version}) and exit",
217+
)
218+
219+
if self.add_quiet:
220+
self.add_typed_argument(prefix,
221+
"quiet", "q", action="store_true", helptext="suppress warnings, display only errors"
222+
)
223+
224+
if self.add_config:
225+
self.add_typed_argument(prefix,
226+
"config", helptext=f"path to config file to fetch the defaults from", metavar="PATH"
227+
)
228+
229+
def apply_to_parser(self, parser):
230+
for item in self.arguments_prepared:
231+
kwargs = {k: v for k, v in item["kwargs"].items() if v is not None}
232+
parser.add_argument(*item["names"], **kwargs)

mininterface/_lib/cli_parser.py

Lines changed: 33 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
from typing import Annotated, Optional, Sequence, Type, Union
1212
from unittest.mock import patch
1313

14-
from .cli_flags import CliFlags
1514

1615
from ..cli import Command
1716
from ..settings import CliSettings
@@ -33,21 +32,31 @@
3332
from .form_dict import EnvClass, TagDict, dataclass_to_tagdict, MissingTagValue, dict_added_main
3433

3534
try:
35+
from .cli_flags import CliFlags
3636
from tyro import cli
37-
from tyro._argparse import _SubParsersAction, ArgumentParser
38-
from tyro._argparse_formatter import TyroArgumentParser
39-
from tyro._singleton import MISSING_NONPROP
37+
try: # tyro >= 0.10
38+
from tyro import _experimental_options
39+
_experimental_options["backend"] = "argparse"
40+
from tyro._backends._argparse import _SubParsersAction, ArgumentParser
41+
from tyro._backends._argparse_formatter import TyroArgumentParser
42+
except ImportError:
43+
from tyro._argparse import _SubParsersAction, ArgumentParser
44+
from tyro._argparse_formatter import TyroArgumentParser
45+
from tyro._parsers import ParserSpecification
46+
4047
from tyro.conf import OmitArgPrefixes, OmitSubcommandPrefixes, DisallowNone, FlagCreatePairsOff
4148

49+
4250
from .tyro_patches import (
4351
_crawling,
4452
custom_error,
4553
custom_init,
4654
custom_parse_known_args,
4755
failed_fields,
48-
patched_parse_known_args,
56+
patched__parse_known_args,
57+
patched__format_help,
4958
subparser_call,
50-
argparse_init,
59+
argparse_init
5160
)
5261
except ImportError:
5362
from ..exceptions import DependencyRequired
@@ -183,6 +192,7 @@ def annot(type_form):
183192
helponly = False
184193
try:
185194
# Why redirect_stdout? Help-text shows the defaults, which also uses the subcommanded-config.
195+
# TODO maybe new tyro 0.10 will not output to stdout, get rid of the buffer
186196
with redirect_stdout(buffer):
187197
try:
188198
# Standard way.
@@ -234,16 +244,16 @@ def annot(type_form):
234244
kwargs, None if helponly else m, args, type_form, env_classes, _custom_registry, annot, _req_fields
235245
)
236246

237-
# Why setting m.env instead of putting into into a constructor of a new get_interface() call?
238-
# 1. Getting the interface is a costly operation
239-
# 2. There is this bug so that we need to use single interface:
240-
# TODO
241-
# As this works badly, lets make sure we use single interface now
242-
# and will not need the second one.
243-
# get_interface("gui")
244-
# m = get_interface("gui")
245-
# m.select([1,2,3])
246-
m.env = env
247+
# Why setting m.env instead of putting into into a constructor of a new get_interface() call?
248+
# 1. Getting the interface is a costly operation
249+
# 2. There is this bug so that we need to use single interface:
250+
# TODO
251+
# As this works badly, lets make sure we use single interface now
252+
# and will not need the second one.
253+
# get_interface("gui")
254+
# m = get_interface("gui")
255+
# m.select([1,2,3])
256+
m.env = env
247257
except SystemExit as exception:
248258
# --- (C) The dialog missing section ---
249259
# Some fields are needed to be filled up.
@@ -339,8 +349,7 @@ def _apply_patches(cf: Optional[CliFlags], ask_for_missing, env_classes, kwargs)
339349
patches = []
340350

341351
patches.append(patch.object(_SubParsersAction, "__call__", subparser_call))
342-
patches.append(patch.object(TyroArgumentParser, "_parse_known_args", patched_parse_known_args))
343-
352+
patches.append(patch.object(TyroArgumentParser, "_parse_known_args", patched__parse_known_args))
344353
kw = {
345354
k: v for k, v in kwargs.items() if k != "default"
346355
} # NOTE I might separate kwargs['default'] and do not do this filtering
@@ -359,6 +368,11 @@ def _apply_patches(cf: Optional[CliFlags], ask_for_missing, env_classes, kwargs)
359368
"__init__",
360369
custom_init(cf),
361370
),
371+
patch.object(
372+
TyroArgumentParser,
373+
"format_help",
374+
patched__format_help(cf),
375+
),
362376
patch.object(
363377
TyroArgumentParser,
364378
"parse_known_args",
@@ -486,7 +500,7 @@ def _fetch_currently_failed(requireds) -> TagDict:
486500
missing_req = {}
487501
for field in failed_fields.get():
488502
# ex: `_subcommands._nested_subcommands (positional)`
489-
fname = field.dest.replace(" (positional)", "").replace("-", "_") # `_subcommands._nested_subcommands`
503+
fname = field.dest.replace(" (positional)", "").replace("-", "_").replace("__tyro_dummy_inner__.", "").replace("__tyro_dummy_inner__","") # `_subcommands._nested_subcommands`
490504
fname_raw = fname.rsplit(".", 1)[-1] # `_nested_subcommands`
491505

492506
if isinstance(field, _SubParsersAction):

mininterface/_lib/run.py

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88

99
from .._mininterface import Mininterface
10-
from ..exceptions import DependencyRequired, ValidationFail
10+
from ..exceptions import DependencyRequired, ValidationFail, _debug_wanted
1111
from ..interfaces import get_interface
1212
from ..settings import CliSettings, MininterfaceSettings, UiSettings
1313
from .form_dict import EnvClass
@@ -332,17 +332,7 @@ class Env:
332332
try:
333333
parse_cli(env_or_list, kwargs, m, cf, ask_for_missing, args, ask_on_empty_cli, cliset)
334334
except Exception as e:
335-
# Undocumented MININTERFACE_DEBUG flag. Note ipdb package requirement.
336-
from ast import literal_eval
337-
338-
if literal_eval(environ.get("MININTERFACE_DEBUG", "0")):
339-
import traceback
340-
341-
import ipdb
342-
343-
traceback.print_exception(e)
344-
ipdb.post_mortem()
345-
else:
335+
if not _debug_wanted(e):
346336
raise
347337

348338
# Command run

0 commit comments

Comments
 (0)