Skip to content

Commit a9c4c70

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

File tree

13 files changed

+292
-136
lines changed

13 files changed

+292
-136
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: 119 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,101 @@ 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(
203+
prefix,
204+
"verbose",
205+
"v",
206+
action="count",
207+
default=0,
208+
helptext="verbosity level, can be used multiple times to increase",
209+
)
210+
211+
if self.add_version:
212+
self.add_typed_argument(
213+
prefix,
214+
"version",
215+
action="version",
216+
version=self.version,
217+
default="",
218+
helptext=f"show program's version number ({self.version}) and exit",
219+
)
220+
221+
if self.add_quiet:
222+
self.add_typed_argument(
223+
prefix, "quiet", "q", action="store_true", helptext="suppress warnings, display only errors"
224+
)
225+
226+
if self.add_config:
227+
self.add_typed_argument(
228+
prefix, "config", helptext=f"path to config file to fetch the defaults from", metavar="PATH"
229+
)
230+
231+
def apply_to_parser(self, parser):
232+
for item in self.arguments_prepared:
233+
kwargs = {k: v for k, v in item["kwargs"].items() if v is not None}
234+
parser.add_argument(*item["names"], **kwargs)

mininterface/_lib/cli_parser.py

Lines changed: 38 additions & 18 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,10 +32,20 @@
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+
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
48+
4049
from tyro.conf import OmitArgPrefixes, OmitSubcommandPrefixes, DisallowNone, FlagCreatePairsOff
4150

4251
from .tyro_patches import (
@@ -45,7 +54,8 @@
4554
custom_init,
4655
custom_parse_known_args,
4756
failed_fields,
48-
patched_parse_known_args,
57+
patched__parse_known_args,
58+
patched__format_help,
4959
subparser_call,
5060
argparse_init,
5161
)
@@ -183,6 +193,7 @@ def annot(type_form):
183193
helponly = False
184194
try:
185195
# Why redirect_stdout? Help-text shows the defaults, which also uses the subcommanded-config.
196+
# TODO maybe new tyro 0.10 will not output to stdout, get rid of the buffer
186197
with redirect_stdout(buffer):
187198
try:
188199
# Standard way.
@@ -234,16 +245,16 @@ def annot(type_form):
234245
kwargs, None if helponly else m, args, type_form, env_classes, _custom_registry, annot, _req_fields
235246
)
236247

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
248+
# Why setting m.env instead of putting into into a constructor of a new get_interface() call?
249+
# 1. Getting the interface is a costly operation
250+
# 2. There is this bug so that we need to use single interface:
251+
# TODO
252+
# As this works badly, lets make sure we use single interface now
253+
# and will not need the second one.
254+
# get_interface("gui")
255+
# m = get_interface("gui")
256+
# m.select([1,2,3])
257+
m.env = env
247258
except SystemExit as exception:
248259
# --- (C) The dialog missing section ---
249260
# Some fields are needed to be filled up.
@@ -339,8 +350,7 @@ def _apply_patches(cf: Optional[CliFlags], ask_for_missing, env_classes, kwargs)
339350
patches = []
340351

341352
patches.append(patch.object(_SubParsersAction, "__call__", subparser_call))
342-
patches.append(patch.object(TyroArgumentParser, "_parse_known_args", patched_parse_known_args))
343-
353+
patches.append(patch.object(TyroArgumentParser, "_parse_known_args", patched__parse_known_args))
344354
kw = {
345355
k: v for k, v in kwargs.items() if k != "default"
346356
} # NOTE I might separate kwargs['default'] and do not do this filtering
@@ -359,6 +369,11 @@ def _apply_patches(cf: Optional[CliFlags], ask_for_missing, env_classes, kwargs)
359369
"__init__",
360370
custom_init(cf),
361371
),
372+
patch.object(
373+
TyroArgumentParser,
374+
"format_help",
375+
patched__format_help(cf),
376+
),
362377
patch.object(
363378
TyroArgumentParser,
364379
"parse_known_args",
@@ -486,7 +501,12 @@ def _fetch_currently_failed(requireds) -> TagDict:
486501
missing_req = {}
487502
for field in failed_fields.get():
488503
# ex: `_subcommands._nested_subcommands (positional)`
489-
fname = field.dest.replace(" (positional)", "").replace("-", "_") # `_subcommands._nested_subcommands`
504+
fname = (
505+
field.dest.replace(" (positional)", "")
506+
.replace("-", "_")
507+
.replace("__tyro_dummy_inner__.", "")
508+
.replace("__tyro_dummy_inner__", "")
509+
) # `_subcommands._nested_subcommands`
490510
fname_raw = fname.rsplit(".", 1)[-1] # `_nested_subcommands`
491511

492512
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)