mininterface 1.2.0__tar.gz → 1.2.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. {mininterface-1.2.0 → mininterface-1.2.1}/PKG-INFO +1 -1
  2. {mininterface-1.2.0 → mininterface-1.2.1}/mininterface/__main__.py +0 -1
  3. {mininterface-1.2.0 → mininterface-1.2.1}/mininterface/_lib/cli_flags.py +119 -3
  4. {mininterface-1.2.0 → mininterface-1.2.1}/mininterface/_lib/cli_parser.py +38 -18
  5. {mininterface-1.2.0 → mininterface-1.2.1}/mininterface/_lib/run.py +2 -12
  6. {mininterface-1.2.0 → mininterface-1.2.1}/mininterface/_lib/tyro_patches.py +67 -81
  7. {mininterface-1.2.0 → mininterface-1.2.1}/mininterface/exceptions.py +16 -0
  8. {mininterface-1.2.0 → mininterface-1.2.1}/pyproject.toml +1 -1
  9. {mininterface-1.2.0 → mininterface-1.2.1}/LICENSE +0 -0
  10. {mininterface-1.2.0 → mininterface-1.2.1}/README.md +0 -0
  11. {mininterface-1.2.0 → mininterface-1.2.1}/mininterface/__init__.py +0 -0
  12. {mininterface-1.2.0 → mininterface-1.2.1}/mininterface/_lib/__init__.py +0 -0
  13. {mininterface-1.2.0 → mininterface-1.2.1}/mininterface/_lib/argparse_support.py +0 -0
  14. {mininterface-1.2.0 → mininterface-1.2.1}/mininterface/_lib/auxiliary.py +0 -0
  15. {mininterface-1.2.0 → mininterface-1.2.1}/mininterface/_lib/cli_utils.py +0 -0
  16. {mininterface-1.2.0 → mininterface-1.2.1}/mininterface/_lib/config_file.py +0 -0
  17. {mininterface-1.2.0 → mininterface-1.2.1}/mininterface/_lib/dataclass_creation.py +0 -0
  18. {mininterface-1.2.0 → mininterface-1.2.1}/mininterface/_lib/form_dict.py +0 -0
  19. {mininterface-1.2.0 → mininterface-1.2.1}/mininterface/_lib/future_compatibility.py +0 -0
  20. {mininterface-1.2.0 → mininterface-1.2.1}/mininterface/_lib/redirectable.py +0 -0
  21. {mininterface-1.2.0 → mininterface-1.2.1}/mininterface/_lib/shortcuts.py +0 -0
  22. {mininterface-1.2.0 → mininterface-1.2.1}/mininterface/_lib/showcase.py +0 -0
  23. {mininterface-1.2.0 → mininterface-1.2.1}/mininterface/_lib/start.py +0 -0
  24. {mininterface-1.2.0 → mininterface-1.2.1}/mininterface/_mininterface/__init__.py +0 -0
  25. {mininterface-1.2.0 → mininterface-1.2.1}/mininterface/_mininterface/adaptor.py +0 -0
  26. {mininterface-1.2.0 → mininterface-1.2.1}/mininterface/_mininterface/mixin.py +0 -0
  27. {mininterface-1.2.0 → mininterface-1.2.1}/mininterface/_text_interface/__init__.py +0 -0
  28. {mininterface-1.2.0 → mininterface-1.2.1}/mininterface/_text_interface/adaptor.py +0 -0
  29. {mininterface-1.2.0 → mininterface-1.2.1}/mininterface/_text_interface/facet.py +0 -0
  30. {mininterface-1.2.0 → mininterface-1.2.1}/mininterface/_text_interface/timeout.py +0 -0
  31. {mininterface-1.2.0 → mininterface-1.2.1}/mininterface/_textual_interface/__init__.py +0 -0
  32. {mininterface-1.2.0 → mininterface-1.2.1}/mininterface/_textual_interface/adaptor.py +0 -0
  33. {mininterface-1.2.0 → mininterface-1.2.1}/mininterface/_textual_interface/button_contents.py +0 -0
  34. {mininterface-1.2.0 → mininterface-1.2.1}/mininterface/_textual_interface/facet.py +0 -0
  35. {mininterface-1.2.0 → mininterface-1.2.1}/mininterface/_textual_interface/file_picker_input.py +0 -0
  36. {mininterface-1.2.0 → mininterface-1.2.1}/mininterface/_textual_interface/form_contents.py +0 -0
  37. {mininterface-1.2.0 → mininterface-1.2.1}/mininterface/_textual_interface/secret_input.py +0 -0
  38. {mininterface-1.2.0 → mininterface-1.2.1}/mininterface/_textual_interface/style.tcss +0 -0
  39. {mininterface-1.2.0 → mininterface-1.2.1}/mininterface/_textual_interface/textual_app.py +0 -0
  40. {mininterface-1.2.0 → mininterface-1.2.1}/mininterface/_textual_interface/timeout.py +0 -0
  41. {mininterface-1.2.0 → mininterface-1.2.1}/mininterface/_textual_interface/widgets.py +0 -0
  42. {mininterface-1.2.0 → mininterface-1.2.1}/mininterface/_tk_interface/__init__.py +0 -0
  43. {mininterface-1.2.0 → mininterface-1.2.1}/mininterface/_tk_interface/adaptor.py +0 -0
  44. {mininterface-1.2.0 → mininterface-1.2.1}/mininterface/_tk_interface/date_entry.py +0 -0
  45. {mininterface-1.2.0 → mininterface-1.2.1}/mininterface/_tk_interface/external_fix.py +0 -0
  46. {mininterface-1.2.0 → mininterface-1.2.1}/mininterface/_tk_interface/facet.py +0 -0
  47. {mininterface-1.2.0 → mininterface-1.2.1}/mininterface/_tk_interface/redirect_text_tkinter.py +0 -0
  48. {mininterface-1.2.0 → mininterface-1.2.1}/mininterface/_tk_interface/secret_entry.py +0 -0
  49. {mininterface-1.2.0 → mininterface-1.2.1}/mininterface/_tk_interface/select_input.py +0 -0
  50. {mininterface-1.2.0 → mininterface-1.2.1}/mininterface/_tk_interface/timeout.py +0 -0
  51. {mininterface-1.2.0 → mininterface-1.2.1}/mininterface/_tk_interface/utils.py +0 -0
  52. {mininterface-1.2.0 → mininterface-1.2.1}/mininterface/_web_interface/__init__.py +0 -0
  53. {mininterface-1.2.0 → mininterface-1.2.1}/mininterface/_web_interface/app.py +0 -0
  54. {mininterface-1.2.0 → mininterface-1.2.1}/mininterface/_web_interface/child_adaptor.py +0 -0
  55. {mininterface-1.2.0 → mininterface-1.2.1}/mininterface/_web_interface/parent_adaptor.py +0 -0
  56. {mininterface-1.2.0 → mininterface-1.2.1}/mininterface/cli.py +0 -0
  57. {mininterface-1.2.0 → mininterface-1.2.1}/mininterface/experimental.py +0 -0
  58. {mininterface-1.2.0 → mininterface-1.2.1}/mininterface/facet/__init__.py +0 -0
  59. {mininterface-1.2.0 → mininterface-1.2.1}/mininterface/interfaces.py +0 -0
  60. {mininterface-1.2.0 → mininterface-1.2.1}/mininterface/settings.py +0 -0
  61. {mininterface-1.2.0 → mininterface-1.2.1}/mininterface/tag/__init__.py +0 -0
  62. {mininterface-1.2.0 → mininterface-1.2.1}/mininterface/tag/alias.py +0 -0
  63. {mininterface-1.2.0 → mininterface-1.2.1}/mininterface/tag/callback_tag.py +0 -0
  64. {mininterface-1.2.0 → mininterface-1.2.1}/mininterface/tag/datetime_tag.py +0 -0
  65. {mininterface-1.2.0 → mininterface-1.2.1}/mininterface/tag/flag.py +0 -0
  66. {mininterface-1.2.0 → mininterface-1.2.1}/mininterface/tag/internal.py +0 -0
  67. {mininterface-1.2.0 → mininterface-1.2.1}/mininterface/tag/path_tag.py +0 -0
  68. {mininterface-1.2.0 → mininterface-1.2.1}/mininterface/tag/secret_tag.py +0 -0
  69. {mininterface-1.2.0 → mininterface-1.2.1}/mininterface/tag/select_tag.py +0 -0
  70. {mininterface-1.2.0 → mininterface-1.2.1}/mininterface/tag/tag.py +0 -0
  71. {mininterface-1.2.0 → mininterface-1.2.1}/mininterface/tag/tag_factory.py +0 -0
  72. {mininterface-1.2.0 → mininterface-1.2.1}/mininterface/tag/type_stubs.py +0 -0
  73. {mininterface-1.2.0 → mininterface-1.2.1}/mininterface/validators.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mininterface
3
- Version: 1.2.0
3
+ Version: 1.2.1
4
4
  Summary: CLI & dialog toolkit – a minimal interface to Python application (GUI, TUI, CLI + config files, web)
5
5
  License: LGPL-3.0-or-later
6
6
  License-File: LICENSE
@@ -1,4 +1,3 @@
1
- from ast import literal_eval
2
1
  from dataclasses import dataclass
3
2
  from os import environ
4
3
  from pathlib import Path
@@ -1,15 +1,22 @@
1
- from functools import lru_cache
1
+ from argparse import ArgumentParser
2
2
  import logging
3
- from dataclasses import dataclass
3
+ import sys
4
4
  from typing import Optional, Sequence
5
5
 
6
+ from tyro.conf import FlagConversionOff
7
+
6
8
  from .form_dict import EnvClass
7
9
 
10
+ from typing import List, Any, Optional
11
+
12
+ from tyro._fields import FieldDefinition
13
+ from tyro.conf._confstruct import _ArgConfig
14
+
8
15
 
9
16
  class CliFlags:
10
17
 
11
18
  _add_verbose: bool = False
12
- version: bool | str = False
19
+ version: str = ""
13
20
  _add_quiet: bool = False
14
21
 
15
22
  default_verbosity: int = logging.WARNING
@@ -56,6 +63,17 @@ class CliFlags:
56
63
  # config
57
64
  self.config = add_config
58
65
 
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
+
59
77
  def should_add(self, env_classes: list[EnvClass]) -> bool:
60
78
  # Flags are added only if neither the env_class nor any of the subcommands have the same-name flag already
61
79
  self._enabled["verbose"] = self._add_verbose and self._attr_not_present("verbose", env_classes)
@@ -116,3 +134,101 @@ class CliFlags:
116
134
  seq = self._verbosity_sequence
117
135
  log_level = {i + 1: level for i, level in enumerate(seq)}.get(count, logging.NOTSET)
118
136
  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)
@@ -11,7 +11,6 @@ from contextlib import ExitStack, redirect_stderr, redirect_stdout
11
11
  from typing import Annotated, Optional, Sequence, Type, Union
12
12
  from unittest.mock import patch
13
13
 
14
- from .cli_flags import CliFlags
15
14
 
16
15
  from ..cli import Command
17
16
  from ..settings import CliSettings
@@ -33,10 +32,20 @@ from .dataclass_creation import (
33
32
  from .form_dict import EnvClass, TagDict, dataclass_to_tagdict, MissingTagValue, dict_added_main
34
33
 
35
34
  try:
35
+ from .cli_flags import CliFlags
36
36
  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
+
40
49
  from tyro.conf import OmitArgPrefixes, OmitSubcommandPrefixes, DisallowNone, FlagCreatePairsOff
41
50
 
42
51
  from .tyro_patches import (
@@ -45,7 +54,8 @@ try:
45
54
  custom_init,
46
55
  custom_parse_known_args,
47
56
  failed_fields,
48
- patched_parse_known_args,
57
+ patched__parse_known_args,
58
+ patched__format_help,
49
59
  subparser_call,
50
60
  argparse_init,
51
61
  )
@@ -183,6 +193,7 @@ def parse_cli(
183
193
  helponly = False
184
194
  try:
185
195
  # 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
186
197
  with redirect_stdout(buffer):
187
198
  try:
188
199
  # Standard way.
@@ -234,16 +245,16 @@ def parse_cli(
234
245
  kwargs, None if helponly else m, args, type_form, env_classes, _custom_registry, annot, _req_fields
235
246
  )
236
247
 
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
247
258
  except SystemExit as exception:
248
259
  # --- (C) The dialog missing section ---
249
260
  # Some fields are needed to be filled up.
@@ -339,8 +350,7 @@ def _apply_patches(cf: Optional[CliFlags], ask_for_missing, env_classes, kwargs)
339
350
  patches = []
340
351
 
341
352
  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))
344
354
  kw = {
345
355
  k: v for k, v in kwargs.items() if k != "default"
346
356
  } # 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)
359
369
  "__init__",
360
370
  custom_init(cf),
361
371
  ),
372
+ patch.object(
373
+ TyroArgumentParser,
374
+ "format_help",
375
+ patched__format_help(cf),
376
+ ),
362
377
  patch.object(
363
378
  TyroArgumentParser,
364
379
  "parse_known_args",
@@ -486,7 +501,12 @@ def _fetch_currently_failed(requireds) -> TagDict:
486
501
  missing_req = {}
487
502
  for field in failed_fields.get():
488
503
  # 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`
490
510
  fname_raw = fname.rsplit(".", 1)[-1] # `_nested_subcommands`
491
511
 
492
512
  if isinstance(field, _SubParsersAction):
@@ -7,7 +7,7 @@ from typing import Literal, Optional, Sequence, Type
7
7
 
8
8
 
9
9
  from .._mininterface import Mininterface
10
- from ..exceptions import DependencyRequired, ValidationFail
10
+ from ..exceptions import DependencyRequired, ValidationFail, _debug_wanted
11
11
  from ..interfaces import get_interface
12
12
  from ..settings import CliSettings, MininterfaceSettings, UiSettings
13
13
  from .form_dict import EnvClass
@@ -332,17 +332,7 @@ def run(
332
332
  try:
333
333
  parse_cli(env_or_list, kwargs, m, cf, ask_for_missing, args, ask_on_empty_cli, cliset)
334
334
  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):
346
336
  raise
347
337
 
348
338
  # Command run
@@ -5,14 +5,22 @@ from collections import deque
5
5
  from contextvars import ContextVar
6
6
  from gettext import gettext as _
7
7
  import sys
8
- from typing import Optional, Callable
9
8
 
10
- from tyro import _argparse as argparse
11
- from tyro._argparse import Action, _SubParsersAction, ArgumentParser
12
- from tyro._argparse_formatter import TyroArgumentParser
13
9
 
14
10
  from .cli_flags import CliFlags
15
11
 
12
+ try:
13
+ # tyro >= 0.10
14
+ from tyro._backends._argparse import _get_action_name, SUPPRESS, ArgumentError
15
+ from tyro._backends._argparse import Action, _SubParsersAction, ArgumentParser
16
+ from tyro._backends._argparse_formatter import TyroArgumentParser
17
+ except ImportError:
18
+ from tyro._argparse import _get_action_name, SUPPRESS, ArgumentError
19
+ from tyro._argparse import Action, _SubParsersAction, ArgumentParser
20
+ from tyro._argparse_formatter import TyroArgumentParser
21
+ from tyro import _arguments
22
+
23
+
16
24
  failed_fields: ContextVar[list[Action]] = ContextVar("failed_fields", default=[])
17
25
  _orig_call = _SubParsersAction.__call__
18
26
  _crawling = ContextVar("_crawling", default=deque())
@@ -25,18 +33,7 @@ _orig_init = ArgumentParser.__init__
25
33
  #
26
34
  # The only line changed: failed_fields
27
35
  #
28
- def patched_parse_known_args(self, arg_strings, namespace): # type: ignore # pragma: no cover
29
- """We override _parse_known_args() to improve error messages in the presence of
30
- subcommands. Difference is marked with <new>...</new> below."""
31
-
32
- # <new>
33
- # Reset the unused argument list in the root parser.
34
- # Subparsers will have spaces in self.prog.
35
- if " " not in self.prog:
36
- global global_unrecognized_arg_and_prog
37
- global_unrecognized_arg_and_prog = []
38
- # </new>
39
-
36
+ def patched__parse_known_args(self, arg_strings, namespace):
40
37
  # replace arg strings that are file references
41
38
  if self.fromfile_prefix_chars is not None:
42
39
  arg_strings = self._read_args_from_files(arg_strings)
@@ -58,6 +55,7 @@ def patched_parse_known_args(self, arg_strings, namespace): # type: ignore # p
58
55
  arg_string_pattern_parts = []
59
56
  arg_strings_iter = iter(arg_strings)
60
57
  for i, arg_string in enumerate(arg_strings_iter):
58
+
61
59
  # all args after -- are non-options
62
60
  if arg_string == "--":
63
61
  arg_string_pattern_parts.append("-")
@@ -94,16 +92,17 @@ def patched_parse_known_args(self, arg_strings, namespace): # type: ignore # p
94
92
  for conflict_action in action_conflicts.get(action, []):
95
93
  if conflict_action in seen_non_default_actions:
96
94
  msg = _("not allowed with argument %s")
97
- action_name = argparse._get_action_name(conflict_action)
98
- raise argparse.ArgumentError(action, msg % action_name)
95
+ action_name = _get_action_name(conflict_action)
96
+ raise ArgumentError(action, msg % action_name)
99
97
 
100
98
  # take the action if we didn't receive a SUPPRESS value
101
99
  # (e.g. from a default)
102
- if argument_values is not argparse.SUPPRESS:
100
+ if argument_values is not SUPPRESS:
103
101
  action(self, namespace, argument_values, option_string)
104
102
 
105
103
  # function to convert arg_strings into an optional action
106
104
  def consume_optional(start_index):
105
+
107
106
  # get the optional identified at this index
108
107
  option_tuple = option_string_indices[start_index]
109
108
  action, option_string, sep, explicit_arg = option_tuple
@@ -113,14 +112,9 @@ def patched_parse_known_args(self, arg_strings, namespace): # type: ignore # p
113
112
  match_argument = self._match_argument
114
113
  action_tuples = []
115
114
  while True:
115
+
116
116
  # if we found no optional action, skip it
117
117
  if action is None:
118
- # <new>
119
- # Manually track unused arguments to assist with error messages
120
- # later.
121
- if not self._parsing_known_args:
122
- global_unrecognized_arg_and_prog.append((option_string, self.prog))
123
- # </new>
124
118
  extras.append(arg_strings[start_index])
125
119
  return start_index + 1
126
120
 
@@ -136,7 +130,7 @@ def patched_parse_known_args(self, arg_strings, namespace): # type: ignore # p
136
130
  if arg_count == 0 and option_string[1] not in chars and explicit_arg != "":
137
131
  if sep or explicit_arg[0] in chars:
138
132
  msg = _("ignored explicit argument %r")
139
- raise argparse.ArgumentError(action, msg % explicit_arg)
133
+ raise ArgumentError(action, msg % explicit_arg)
140
134
  action_tuples.append((action, [], option_string))
141
135
  char = option_string[0]
142
136
  option_string = char + explicit_arg[0]
@@ -167,7 +161,7 @@ def patched_parse_known_args(self, arg_strings, namespace): # type: ignore # p
167
161
  # explicit argument
168
162
  else:
169
163
  msg = _("ignored explicit argument %r")
170
- raise argparse.ArgumentError(action, msg % explicit_arg)
164
+ raise ArgumentError(action, msg % explicit_arg)
171
165
 
172
166
  # if there is no explicit argument, try to match the
173
167
  # optional's string arguments with the following strings
@@ -220,6 +214,7 @@ def patched_parse_known_args(self, arg_strings, namespace): # type: ignore # p
220
214
  else:
221
215
  max_option_string_index = -1
222
216
  while start_index <= max_option_string_index:
217
+
223
218
  # consume any Positionals preceding the next option
224
219
  next_option_string_index = min([index for index in option_string_indices if index >= start_index])
225
220
  if start_index != next_option_string_index:
@@ -256,7 +251,7 @@ def patched_parse_known_args(self, arg_strings, namespace): # type: ignore # p
256
251
  if action not in seen_actions:
257
252
  if action.required:
258
253
  failed_fields.get().append(action) # WE ADDED THIS LINE
259
- required_actions.append(argparse._get_action_name(action))
254
+ required_actions.append(_get_action_name(action))
260
255
  else:
261
256
  # Convert action default now instead of doing it before
262
257
  # parsing arguments to avoid calling convert functions
@@ -268,11 +263,7 @@ def patched_parse_known_args(self, arg_strings, namespace): # type: ignore # p
268
263
  and hasattr(namespace, action.dest)
269
264
  and action.default is getattr(namespace, action.dest)
270
265
  ):
271
- setattr(
272
- namespace,
273
- action.dest,
274
- self._get_value(action, action.default),
275
- )
266
+ setattr(namespace, action.dest, self._get_value(action, action.default))
276
267
 
277
268
  if required_actions:
278
269
  self.error(_("the following arguments are required: %s") % ", ".join(required_actions))
@@ -286,13 +277,9 @@ def patched_parse_known_args(self, arg_strings, namespace): # type: ignore # p
286
277
 
287
278
  # if no actions were used, report the error
288
279
  else:
289
- names = [
290
- argparse._get_action_name(action)
291
- for action in group._group_actions
292
- if action.help is not argparse.SUPPRESS
293
- ]
280
+ names = [_get_action_name(action) for action in group._group_actions if action.help is not SUPPRESS]
294
281
  msg = _("one of the arguments %s is required")
295
- self.error(msg % " ".join(names)) # type: ignore
282
+ self.error(msg % " ".join(names))
296
283
 
297
284
  # return the updated namespace and the extra arguments
298
285
  return namespace, extras
@@ -314,65 +301,62 @@ def custom_error(self: TyroArgumentParser, message: str):
314
301
 
315
302
 
316
303
  def custom_init(cf: CliFlags):
304
+ orig_init = TyroArgumentParser.__init__
305
+
317
306
  def _(self: TyroArgumentParser, *args, **kwargs):
318
- super(TyroArgumentParser, self).__init__(*args, **kwargs)
319
- default_prefix = "-" if "-" in self.prefix_chars else self.prefix_chars[0]
320
- if cf.add_verbose:
321
- self.add_argument(
322
- default_prefix + "v",
323
- default_prefix * 2 + "verbose",
324
- action="count",
325
- default=0,
326
- help="verbosity level, can be used multiple times to increase",
327
- )
307
+ orig_init(self, *args, **kwargs)
328
308
 
329
- if cf.add_version:
330
- self.add_argument(
331
- default_prefix * 2 + "version",
332
- # NOTE We use the native version, but it inserts a blank line
333
- action="version",
334
- version=cf.version,
335
- # Our custom version works bad with subcommands, we have to first resolve subcommands,
336
- # than it comes to the version
337
- # action="store_const",
338
- # const=cf.version,
339
- help=f"show program's version number ({cf.version}) and exit",
340
- )
309
+ cf.setup(self)
310
+ cf.apply_to_parser(self)
341
311
 
342
- if cf.add_quiet:
343
- self.add_argument(
344
- default_prefix + "q",
345
- default_prefix * 2 + "quiet",
346
- action="store_true",
347
- help="suppress warnings, display only errors",
348
- )
312
+ return _
313
+
314
+
315
+ def patched__format_help(cf: CliFlags):
316
+ orig = TyroArgumentParser.format_help
349
317
 
350
- if cf.add_config:
351
- self.add_argument(
352
- default_prefix * 2 + "config", help=f"path to config file to fetch the defaults from", metavar="PATH"
318
+ def _(self, *args, **kwargs):
319
+ parser_spec = self._parser_specification
320
+
321
+ for field in reversed(cf.field_list):
322
+ field_out = _arguments.ArgumentDefinition(
323
+ intern_prefix=field.intern_name,
324
+ extern_prefix=field.extern_name,
325
+ subcommand_prefix="",
326
+ field=field,
353
327
  )
354
328
 
329
+ parser_spec.args.insert(0, field_out)
330
+
331
+ return orig(self, *args, **kwargs)
332
+
355
333
  return _
356
334
 
357
335
 
358
336
  def custom_parse_known_args(cf: CliFlags):
337
+ orig = TyroArgumentParser.parse_known_args
338
+
359
339
  def _(self: TyroArgumentParser, args=None, namespace=None):
360
- namespace, args = super(TyroArgumentParser, self).parse_known_args(args, namespace)
340
+ namespace, args = orig(self, args, namespace)
361
341
  # NOTE We may check that the Env does not have its own `verbose``
362
342
  # NOTE I do not like much tests need force=True here as they are run in paralel.
363
343
  if cf.add_verbose and hasattr(namespace, "verbose"):
364
344
  if namespace.verbose > 0:
365
- logging.basicConfig(level=cf.get_log_level(namespace.verbose), format="%(message)s", force=True)
345
+ logging.basicConfig(
346
+ level=cf.get_log_level(namespace.verbose), format="%(message)s", force=True, stream=cf.orig_stream
347
+ )
366
348
  else:
367
- logging.basicConfig(level=cf.default_verbosity, format="%(message)s", force=True)
349
+ logging.basicConfig(
350
+ level=cf.default_verbosity, format="%(message)s", force=True, stream=cf.orig_stream
351
+ )
368
352
  delattr(namespace, "verbose")
369
353
 
370
- # This code is now not used, see `custom_init`
371
- # if cf.add_verbose and hasattr(namespace, "version"):
372
- # if namespace.version:
373
- # print(namespace.version)
374
- # raise SystemExit(0)
375
- # delattr(namespace, "version")
354
+ if cf.add_version and hasattr(namespace, "version"):
355
+ # This code is now not used, see `custom_init`
356
+ # if namespace.version:
357
+ # print(namespace.version)
358
+ # raise SystemExit(0)
359
+ delattr(namespace, "version")
376
360
 
377
361
  # Note that we do not parse --config here as it is parsed at `run.py`, before CLI parsing.
378
362
  # Since config file serves as default fo CLI parsing.
@@ -381,7 +365,9 @@ def custom_parse_known_args(cf: CliFlags):
381
365
 
382
366
  if cf.add_quiet and hasattr(namespace, "quiet"):
383
367
  if namespace.quiet:
384
- logging.basicConfig(level=cf.get_log_level(-1), format="%(message)s", force=True)
368
+ logging.basicConfig(
369
+ level=cf.get_log_level(-1), format="%(message)s", force=True, stream=cf.orig_stream
370
+ )
385
371
  delattr(namespace, "quiet")
386
372
  return namespace, args
387
373
 
@@ -1,5 +1,7 @@
1
1
  """Exceptions that might make sense to be used outside the library."""
2
2
 
3
+ from os import environ as _environ
4
+
3
5
 
4
6
  class Cancelled(SystemExit):
5
7
  """User has cancelled.
@@ -47,4 +49,18 @@ class DependencyRequired(InterfaceNotAvailable):
47
49
 
48
50
  def exit(self):
49
51
  """Wrap the exception in a SystemExit so that the program exits without a traceback."""
52
+ _debug_wanted(self)
50
53
  raise SystemExit(self)
54
+
55
+
56
+ def _debug_wanted(e: Exception):
57
+ # Undocumented MININTERFACE_DEBUG flag. Note ipdb package requirement.
58
+ from ast import literal_eval
59
+
60
+ if literal_eval(_environ.get("MININTERFACE_DEBUG", "0")):
61
+ import traceback
62
+ import ipdb
63
+
64
+ traceback.print_exception(e)
65
+ ipdb.post_mortem(e)
66
+ return True
@@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api"
4
4
 
5
5
  [tool.poetry]
6
6
  name = "mininterface"
7
- version = "1.2.0"
7
+ version = "1.2.1"
8
8
  description = "CLI & dialog toolkit – a minimal interface to Python application (GUI, TUI, CLI + config files, web)"
9
9
  authors = ["Edvard Rejthar <edvard.rejthar@nic.cz>"]
10
10
  license = "LGPL-3.0-or-later"
File without changes
File without changes