mininterface 1.2.0__tar.gz → 1.2.2__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.
- {mininterface-1.2.0 → mininterface-1.2.2}/PKG-INFO +5 -5
- {mininterface-1.2.0 → mininterface-1.2.2}/README.md +4 -4
- {mininterface-1.2.0 → mininterface-1.2.2}/mininterface/__main__.py +0 -1
- {mininterface-1.2.0 → mininterface-1.2.2}/mininterface/_lib/auxiliary.py +81 -35
- {mininterface-1.2.0 → mininterface-1.2.2}/mininterface/_lib/cli_flags.py +119 -3
- {mininterface-1.2.0 → mininterface-1.2.2}/mininterface/_lib/cli_parser.py +30 -18
- {mininterface-1.2.0 → mininterface-1.2.2}/mininterface/_lib/dataclass_creation.py +12 -5
- {mininterface-1.2.0 → mininterface-1.2.2}/mininterface/_lib/run.py +2 -12
- {mininterface-1.2.0 → mininterface-1.2.2}/mininterface/_lib/tyro_patches.py +83 -84
- {mininterface-1.2.0 → mininterface-1.2.2}/mininterface/_tk_interface/adaptor.py +21 -0
- {mininterface-1.2.0 → mininterface-1.2.2}/mininterface/_tk_interface/secret_entry.py +1 -0
- {mininterface-1.2.0 → mininterface-1.2.2}/mininterface/_tk_interface/select_input.py +12 -1
- {mininterface-1.2.0 → mininterface-1.2.2}/mininterface/exceptions.py +16 -0
- {mininterface-1.2.0 → mininterface-1.2.2}/pyproject.toml +1 -1
- {mininterface-1.2.0 → mininterface-1.2.2}/LICENSE +0 -0
- {mininterface-1.2.0 → mininterface-1.2.2}/mininterface/__init__.py +0 -0
- {mininterface-1.2.0 → mininterface-1.2.2}/mininterface/_lib/__init__.py +0 -0
- {mininterface-1.2.0 → mininterface-1.2.2}/mininterface/_lib/argparse_support.py +0 -0
- {mininterface-1.2.0 → mininterface-1.2.2}/mininterface/_lib/cli_utils.py +0 -0
- {mininterface-1.2.0 → mininterface-1.2.2}/mininterface/_lib/config_file.py +0 -0
- {mininterface-1.2.0 → mininterface-1.2.2}/mininterface/_lib/form_dict.py +0 -0
- {mininterface-1.2.0 → mininterface-1.2.2}/mininterface/_lib/future_compatibility.py +0 -0
- {mininterface-1.2.0 → mininterface-1.2.2}/mininterface/_lib/redirectable.py +0 -0
- {mininterface-1.2.0 → mininterface-1.2.2}/mininterface/_lib/shortcuts.py +0 -0
- {mininterface-1.2.0 → mininterface-1.2.2}/mininterface/_lib/showcase.py +0 -0
- {mininterface-1.2.0 → mininterface-1.2.2}/mininterface/_lib/start.py +0 -0
- {mininterface-1.2.0 → mininterface-1.2.2}/mininterface/_mininterface/__init__.py +0 -0
- {mininterface-1.2.0 → mininterface-1.2.2}/mininterface/_mininterface/adaptor.py +0 -0
- {mininterface-1.2.0 → mininterface-1.2.2}/mininterface/_mininterface/mixin.py +0 -0
- {mininterface-1.2.0 → mininterface-1.2.2}/mininterface/_text_interface/__init__.py +0 -0
- {mininterface-1.2.0 → mininterface-1.2.2}/mininterface/_text_interface/adaptor.py +0 -0
- {mininterface-1.2.0 → mininterface-1.2.2}/mininterface/_text_interface/facet.py +0 -0
- {mininterface-1.2.0 → mininterface-1.2.2}/mininterface/_text_interface/timeout.py +0 -0
- {mininterface-1.2.0 → mininterface-1.2.2}/mininterface/_textual_interface/__init__.py +0 -0
- {mininterface-1.2.0 → mininterface-1.2.2}/mininterface/_textual_interface/adaptor.py +0 -0
- {mininterface-1.2.0 → mininterface-1.2.2}/mininterface/_textual_interface/button_contents.py +0 -0
- {mininterface-1.2.0 → mininterface-1.2.2}/mininterface/_textual_interface/facet.py +0 -0
- {mininterface-1.2.0 → mininterface-1.2.2}/mininterface/_textual_interface/file_picker_input.py +0 -0
- {mininterface-1.2.0 → mininterface-1.2.2}/mininterface/_textual_interface/form_contents.py +0 -0
- {mininterface-1.2.0 → mininterface-1.2.2}/mininterface/_textual_interface/secret_input.py +0 -0
- {mininterface-1.2.0 → mininterface-1.2.2}/mininterface/_textual_interface/style.tcss +0 -0
- {mininterface-1.2.0 → mininterface-1.2.2}/mininterface/_textual_interface/textual_app.py +0 -0
- {mininterface-1.2.0 → mininterface-1.2.2}/mininterface/_textual_interface/timeout.py +0 -0
- {mininterface-1.2.0 → mininterface-1.2.2}/mininterface/_textual_interface/widgets.py +0 -0
- {mininterface-1.2.0 → mininterface-1.2.2}/mininterface/_tk_interface/__init__.py +0 -0
- {mininterface-1.2.0 → mininterface-1.2.2}/mininterface/_tk_interface/date_entry.py +0 -0
- {mininterface-1.2.0 → mininterface-1.2.2}/mininterface/_tk_interface/external_fix.py +0 -0
- {mininterface-1.2.0 → mininterface-1.2.2}/mininterface/_tk_interface/facet.py +0 -0
- {mininterface-1.2.0 → mininterface-1.2.2}/mininterface/_tk_interface/redirect_text_tkinter.py +0 -0
- {mininterface-1.2.0 → mininterface-1.2.2}/mininterface/_tk_interface/timeout.py +0 -0
- {mininterface-1.2.0 → mininterface-1.2.2}/mininterface/_tk_interface/utils.py +0 -0
- {mininterface-1.2.0 → mininterface-1.2.2}/mininterface/_web_interface/__init__.py +0 -0
- {mininterface-1.2.0 → mininterface-1.2.2}/mininterface/_web_interface/app.py +0 -0
- {mininterface-1.2.0 → mininterface-1.2.2}/mininterface/_web_interface/child_adaptor.py +0 -0
- {mininterface-1.2.0 → mininterface-1.2.2}/mininterface/_web_interface/parent_adaptor.py +0 -0
- {mininterface-1.2.0 → mininterface-1.2.2}/mininterface/cli.py +0 -0
- {mininterface-1.2.0 → mininterface-1.2.2}/mininterface/experimental.py +0 -0
- {mininterface-1.2.0 → mininterface-1.2.2}/mininterface/facet/__init__.py +0 -0
- {mininterface-1.2.0 → mininterface-1.2.2}/mininterface/interfaces.py +0 -0
- {mininterface-1.2.0 → mininterface-1.2.2}/mininterface/settings.py +0 -0
- {mininterface-1.2.0 → mininterface-1.2.2}/mininterface/tag/__init__.py +0 -0
- {mininterface-1.2.0 → mininterface-1.2.2}/mininterface/tag/alias.py +0 -0
- {mininterface-1.2.0 → mininterface-1.2.2}/mininterface/tag/callback_tag.py +0 -0
- {mininterface-1.2.0 → mininterface-1.2.2}/mininterface/tag/datetime_tag.py +0 -0
- {mininterface-1.2.0 → mininterface-1.2.2}/mininterface/tag/flag.py +0 -0
- {mininterface-1.2.0 → mininterface-1.2.2}/mininterface/tag/internal.py +0 -0
- {mininterface-1.2.0 → mininterface-1.2.2}/mininterface/tag/path_tag.py +0 -0
- {mininterface-1.2.0 → mininterface-1.2.2}/mininterface/tag/secret_tag.py +0 -0
- {mininterface-1.2.0 → mininterface-1.2.2}/mininterface/tag/select_tag.py +0 -0
- {mininterface-1.2.0 → mininterface-1.2.2}/mininterface/tag/tag.py +0 -0
- {mininterface-1.2.0 → mininterface-1.2.2}/mininterface/tag/tag_factory.py +0 -0
- {mininterface-1.2.0 → mininterface-1.2.2}/mininterface/tag/type_stubs.py +0 -0
- {mininterface-1.2.0 → mininterface-1.2.2}/mininterface/validators.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mininterface
|
|
3
|
-
Version: 1.2.
|
|
3
|
+
Version: 1.2.2
|
|
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
|
|
@@ -191,8 +191,8 @@ These projects have the code base reduced thanks to the mininterface:
|
|
|
191
191
|
Take a look at the following example.
|
|
192
192
|
|
|
193
193
|
1. We define any Env class.
|
|
194
|
-
2. Then, we initialize mininterface with [`run(Env)`]
|
|
195
|
-
3. Then, we use various dialog methods, like [`confirm`]
|
|
194
|
+
2. Then, we initialize mininterface with [`run(Env)`](https://cz-nic.github.io/mininterface/run/) – the missing fields will be prompted for
|
|
195
|
+
3. Then, we use various dialog methods, like [`confirm`](https://cz-nic.github.io/mininterface/Mininterface/#mininterface.Mininterface.confirm), [`select`](https://cz-nic.github.io/mininterface/Mininterface/#mininterface.Mininterface.select) or [`form`](https://cz-nic.github.io/mininterface/Mininterface/#mininterface.Mininterface.form).
|
|
196
196
|
|
|
197
197
|
Below, you find the screenshots how the program looks in various environments ([graphic](Interfaces.md#guiinterface-or-tkinterface-or-gui) interface, [web](Interfaces.md#webinterface-or-web) interface...).
|
|
198
198
|
|
|
@@ -273,7 +273,7 @@ usage: program.py [-h] [OPTIONS]
|
|
|
273
273
|
|
|
274
274
|
You want to try out the Mininterface with your current [`ArgumentParser`](https://docs.python.org/3/library/argparse.html#argparse.ArgumentParser)?
|
|
275
275
|
|
|
276
|
-
You're using positional arguments, subparsers, types in the ArgumentParser... Mininterface will give you immediate benefit. Just wrap it inside the [`run`]
|
|
276
|
+
You're using positional arguments, subparsers, types in the ArgumentParser... Mininterface will give you immediate benefit. Just wrap it inside the [`run`](https://cz-nic.github.io/mininterface/run/) method.
|
|
277
277
|
|
|
278
278
|
```python
|
|
279
279
|
#!/usr/bin/env python3
|
|
@@ -330,7 +330,7 @@ Then, a `.form()` call will create a dialog with all the fields.
|
|
|
330
330
|
|
|
331
331
|

|
|
332
332
|
|
|
333
|
-
You will access the arguments through [`m.env`]
|
|
333
|
+
You will access the arguments through [`m.env`](https://cz-nic.github.io/mininterface/Mininterface/#mininterface.Mininterface.env)
|
|
334
334
|
|
|
335
335
|
```python
|
|
336
336
|
print(m.env.time) # -> 14:21
|
|
@@ -151,8 +151,8 @@ These projects have the code base reduced thanks to the mininterface:
|
|
|
151
151
|
Take a look at the following example.
|
|
152
152
|
|
|
153
153
|
1. We define any Env class.
|
|
154
|
-
2. Then, we initialize mininterface with [`run(Env)`]
|
|
155
|
-
3. Then, we use various dialog methods, like [`confirm`]
|
|
154
|
+
2. Then, we initialize mininterface with [`run(Env)`](https://cz-nic.github.io/mininterface/run/) – the missing fields will be prompted for
|
|
155
|
+
3. Then, we use various dialog methods, like [`confirm`](https://cz-nic.github.io/mininterface/Mininterface/#mininterface.Mininterface.confirm), [`select`](https://cz-nic.github.io/mininterface/Mininterface/#mininterface.Mininterface.select) or [`form`](https://cz-nic.github.io/mininterface/Mininterface/#mininterface.Mininterface.form).
|
|
156
156
|
|
|
157
157
|
Below, you find the screenshots how the program looks in various environments ([graphic](Interfaces.md#guiinterface-or-tkinterface-or-gui) interface, [web](Interfaces.md#webinterface-or-web) interface...).
|
|
158
158
|
|
|
@@ -233,7 +233,7 @@ usage: program.py [-h] [OPTIONS]
|
|
|
233
233
|
|
|
234
234
|
You want to try out the Mininterface with your current [`ArgumentParser`](https://docs.python.org/3/library/argparse.html#argparse.ArgumentParser)?
|
|
235
235
|
|
|
236
|
-
You're using positional arguments, subparsers, types in the ArgumentParser... Mininterface will give you immediate benefit. Just wrap it inside the [`run`]
|
|
236
|
+
You're using positional arguments, subparsers, types in the ArgumentParser... Mininterface will give you immediate benefit. Just wrap it inside the [`run`](https://cz-nic.github.io/mininterface/run/) method.
|
|
237
237
|
|
|
238
238
|
```python
|
|
239
239
|
#!/usr/bin/env python3
|
|
@@ -290,7 +290,7 @@ Then, a `.form()` call will create a dialog with all the fields.
|
|
|
290
290
|
|
|
291
291
|

|
|
292
292
|
|
|
293
|
-
You will access the arguments through [`m.env`]
|
|
293
|
+
You will access the arguments through [`m.env`](https://cz-nic.github.io/mininterface/Mininterface/#mininterface.Mininterface.env)
|
|
294
294
|
|
|
295
295
|
```python
|
|
296
296
|
print(m.env.time) # -> 14:21
|
|
@@ -1,20 +1,36 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
import os
|
|
3
|
-
import re
|
|
4
|
-
from argparse import ArgumentParser
|
|
5
3
|
from dataclasses import fields, is_dataclass
|
|
6
4
|
from functools import lru_cache
|
|
7
5
|
from types import UnionType
|
|
8
|
-
from typing import
|
|
6
|
+
from typing import (
|
|
7
|
+
Any,
|
|
8
|
+
Annotated,
|
|
9
|
+
Callable,
|
|
10
|
+
Iterable,
|
|
11
|
+
Optional,
|
|
12
|
+
TypeVar,
|
|
13
|
+
Union,
|
|
14
|
+
Literal,
|
|
15
|
+
get_args,
|
|
16
|
+
get_origin,
|
|
17
|
+
get_type_hints,
|
|
18
|
+
)
|
|
9
19
|
|
|
10
20
|
from annotated_types import Ge, Gt, Le, Len, Lt, MultipleOf
|
|
11
21
|
|
|
12
22
|
logger = logging.getLogger(__name__)
|
|
13
23
|
|
|
14
24
|
try:
|
|
15
|
-
|
|
25
|
+
import tyro
|
|
26
|
+
from tyro._docstrings import get_field_docstring as _tyro_get_field_docstring
|
|
27
|
+
from tyro._docstrings import get_callable_description as _tyro_get_callable_description
|
|
28
|
+
|
|
29
|
+
_tyro_docstrings_available = True
|
|
16
30
|
except ImportError:
|
|
17
|
-
|
|
31
|
+
tyro = None
|
|
32
|
+
_tyro_docstrings_available = False
|
|
33
|
+
_tyro_get_callable_description = None
|
|
18
34
|
|
|
19
35
|
try:
|
|
20
36
|
from humanize import naturalsize as naturalsize_
|
|
@@ -70,40 +86,70 @@ def get_terminal_size():
|
|
|
70
86
|
return 0, 0
|
|
71
87
|
|
|
72
88
|
|
|
73
|
-
def
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
return
|
|
77
|
-
re.sub(r"\s\(positional\)$", "", action.dest).replace("-", "_"): re.sub(
|
|
78
|
-
r"\((default|fixed to|required).*\)", "", action.help or ""
|
|
79
|
-
)
|
|
80
|
-
for action in parser._actions
|
|
81
|
-
}
|
|
82
|
-
|
|
89
|
+
def get_class_description(obj) -> str:
|
|
90
|
+
if _tyro_get_callable_description:
|
|
91
|
+
return _tyro_get_callable_description(obj)
|
|
92
|
+
return ""
|
|
83
93
|
|
|
84
94
|
@lru_cache
|
|
85
|
-
def
|
|
86
|
-
|
|
87
|
-
|
|
95
|
+
def _get_descriptions_from_docstring(obj) -> dict[str, str]:
|
|
96
|
+
"""Extract field descriptions for all fields of a class.
|
|
97
|
+
|
|
98
|
+
Uses tyro's internal helptext extraction (tyro._docstrings.get_field_docstring),
|
|
99
|
+
which supports the same sources and precedence as tyro's own CLI generation:
|
|
100
|
+
1. tyro.conf.arg(help=...)
|
|
101
|
+
2. PEP 727 Doc
|
|
102
|
+
3. Docstrings (attribute docstrings or class docstring params)
|
|
103
|
+
4. Comments (inline or preceding)
|
|
104
|
+
|
|
105
|
+
We used to rely on tyro.extras.get_parser(), but that was marked deprecated,
|
|
106
|
+
so we call tyro's internal API directly instead.
|
|
107
|
+
"""
|
|
108
|
+
if not _tyro_docstrings_available:
|
|
109
|
+
return {}
|
|
110
|
+
|
|
111
|
+
result = {}
|
|
112
|
+
|
|
113
|
+
# Highest priority: tyro.conf.arg(help=...) in Annotated metadata.
|
|
114
|
+
try:
|
|
115
|
+
hints = get_type_hints(obj, include_extras=True)
|
|
116
|
+
ArgConfig = tyro.conf._confstruct._ArgConfig
|
|
117
|
+
for field_name, hint in hints.items():
|
|
118
|
+
if get_origin(hint) is Annotated:
|
|
119
|
+
for meta in hint.__metadata__:
|
|
120
|
+
if isinstance(meta, ArgConfig) and meta.help:
|
|
121
|
+
result[field_name] = meta.help
|
|
122
|
+
except Exception:
|
|
123
|
+
hints = {}
|
|
124
|
+
|
|
125
|
+
# Mid priority: docstrings and comments via tyro's own extraction.
|
|
126
|
+
for field_name in hints:
|
|
127
|
+
doc = _tyro_get_field_docstring(obj, field_name, ())
|
|
128
|
+
if doc:
|
|
129
|
+
result.setdefault(field_name, doc)
|
|
130
|
+
|
|
131
|
+
# Lowest priority: field.metadata["help"] from dynamically generated
|
|
132
|
+
# dataclasses (e.g. built from ArgumentParser via make_dataclass).
|
|
133
|
+
try:
|
|
134
|
+
for f in fields(obj): # type: ignore
|
|
135
|
+
if help_text := f.metadata.get("help"):
|
|
136
|
+
result.setdefault(f.name, help_text)
|
|
137
|
+
except TypeError:
|
|
138
|
+
pass
|
|
139
|
+
|
|
140
|
+
return result
|
|
88
141
|
|
|
89
142
|
|
|
90
143
|
def get_description(obj, param: str) -> str:
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
return d
|
|
101
|
-
else:
|
|
102
|
-
# We are missing mininterface[basic] requirement. Tyro is missing.
|
|
103
|
-
# Without tyro, we are not able to evaluate the class: m.form(Env),
|
|
104
|
-
# we can still evaluate its instance: m.form(Env()).
|
|
105
|
-
# However, without descriptions.
|
|
106
|
-
return ""
|
|
144
|
+
desc = _get_descriptions_from_docstring(obj).get(param, "")
|
|
145
|
+
if desc and desc.replace("-", "_") != param:
|
|
146
|
+
return desc
|
|
147
|
+
|
|
148
|
+
# We are missing mininterface[basic] requirement. Tyro is missing.
|
|
149
|
+
# Without tyro, we are not able to evaluate the class: m.form(Env),
|
|
150
|
+
# we can still evaluate its instance: m.form(Env()).
|
|
151
|
+
# However, without descriptions.
|
|
152
|
+
return ""
|
|
107
153
|
|
|
108
154
|
|
|
109
155
|
def yield_annotations(dataclass):
|
|
@@ -358,7 +404,7 @@ def strip_none(annotation):
|
|
|
358
404
|
args = tuple(arg for arg in get_args(annotation) if arg is not type(None))
|
|
359
405
|
if len(args) == 1:
|
|
360
406
|
return args[0]
|
|
361
|
-
return Union[args]
|
|
407
|
+
return Union[args]
|
|
362
408
|
|
|
363
409
|
return annotation
|
|
364
410
|
|
|
@@ -1,15 +1,22 @@
|
|
|
1
|
-
from
|
|
1
|
+
from argparse import ArgumentParser
|
|
2
2
|
import logging
|
|
3
|
-
|
|
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:
|
|
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
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
-
|
|
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,8 @@ 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
|
-
|
|
238
|
-
|
|
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
|
+
# Make the interface ready for the user
|
|
249
|
+
m.env = env
|
|
247
250
|
except SystemExit as exception:
|
|
248
251
|
# --- (C) The dialog missing section ---
|
|
249
252
|
# Some fields are needed to be filled up.
|
|
@@ -339,8 +342,7 @@ def _apply_patches(cf: Optional[CliFlags], ask_for_missing, env_classes, kwargs)
|
|
|
339
342
|
patches = []
|
|
340
343
|
|
|
341
344
|
patches.append(patch.object(_SubParsersAction, "__call__", subparser_call))
|
|
342
|
-
patches.append(patch.object(TyroArgumentParser, "_parse_known_args",
|
|
343
|
-
|
|
345
|
+
patches.append(patch.object(TyroArgumentParser, "_parse_known_args", patched__parse_known_args))
|
|
344
346
|
kw = {
|
|
345
347
|
k: v for k, v in kwargs.items() if k != "default"
|
|
346
348
|
} # NOTE I might separate kwargs['default'] and do not do this filtering
|
|
@@ -359,6 +361,11 @@ def _apply_patches(cf: Optional[CliFlags], ask_for_missing, env_classes, kwargs)
|
|
|
359
361
|
"__init__",
|
|
360
362
|
custom_init(cf),
|
|
361
363
|
),
|
|
364
|
+
patch.object(
|
|
365
|
+
TyroArgumentParser,
|
|
366
|
+
"format_help",
|
|
367
|
+
patched__format_help(cf),
|
|
368
|
+
),
|
|
362
369
|
patch.object(
|
|
363
370
|
TyroArgumentParser,
|
|
364
371
|
"parse_known_args",
|
|
@@ -486,7 +493,12 @@ def _fetch_currently_failed(requireds) -> TagDict:
|
|
|
486
493
|
missing_req = {}
|
|
487
494
|
for field in failed_fields.get():
|
|
488
495
|
# ex: `_subcommands._nested_subcommands (positional)`
|
|
489
|
-
fname =
|
|
496
|
+
fname = (
|
|
497
|
+
field.dest.replace(" (positional)", "")
|
|
498
|
+
.replace("-", "_")
|
|
499
|
+
.replace("__tyro_dummy_inner__.", "")
|
|
500
|
+
.replace("__tyro_dummy_inner__", "")
|
|
501
|
+
) # `_subcommands._nested_subcommands`
|
|
490
502
|
fname_raw = fname.rsplit(".", 1)[-1] # `_nested_subcommands`
|
|
491
503
|
|
|
492
504
|
if isinstance(field, _SubParsersAction):
|
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
import re
|
|
2
2
|
import warnings
|
|
3
|
-
from dataclasses import MISSING,
|
|
3
|
+
from dataclasses import MISSING, fields, is_dataclass
|
|
4
4
|
from types import UnionType
|
|
5
5
|
from typing import Annotated, Optional, Sequence, Type, Union, get_args, get_origin, TypeVar
|
|
6
6
|
|
|
7
|
-
|
|
8
7
|
try:
|
|
9
8
|
from tyro._singleton import MISSING_NONPROP
|
|
10
|
-
from tyro.extras import subcommand_type_from_defaults
|
|
11
9
|
|
|
12
10
|
from ..cli import SubcommandPlaceholder
|
|
13
11
|
except ImportError:
|
|
@@ -19,7 +17,7 @@ except ImportError:
|
|
|
19
17
|
from ..tag import Tag
|
|
20
18
|
from ..tag.tag_factory import tag_factory
|
|
21
19
|
from ..validators import not_empty
|
|
22
|
-
from .auxiliary import _get_origin,
|
|
20
|
+
from .auxiliary import _get_origin, get_class_description, get_description
|
|
23
21
|
from .form_dict import DataClass, EnvClass, MissingTagValue
|
|
24
22
|
|
|
25
23
|
# Pydantic is not a project dependency, that is just an optional integration
|
|
@@ -49,6 +47,15 @@ def coerce_type_to_annotation(value, annotation):
|
|
|
49
47
|
annotation = _unwrap_annotated(annotation) # NOTE might be superfluous, called before
|
|
50
48
|
origin = get_origin(annotation)
|
|
51
49
|
|
|
50
|
+
# Handle Union (e.g. int | None)
|
|
51
|
+
if origin in (Union, UnionType):
|
|
52
|
+
for arg in get_args(annotation):
|
|
53
|
+
try:
|
|
54
|
+
return coerce_type_to_annotation(value, arg)
|
|
55
|
+
except Exception:
|
|
56
|
+
pass
|
|
57
|
+
return value
|
|
58
|
+
|
|
52
59
|
# Handle tuple[...] conversion
|
|
53
60
|
if origin is tuple and isinstance(value, list):
|
|
54
61
|
args = get_args(annotation)
|
|
@@ -353,7 +360,7 @@ def choose_subcommand(env_classes: list[Type[DataClass]], m: "Mininterface[EnvCl
|
|
|
353
360
|
# NOTE make select display buttons if there is a little amount of options.
|
|
354
361
|
env = m.select(
|
|
355
362
|
{
|
|
356
|
-
(to_kebab_case(cl.__name__).replace("-", " ").capitalize(),
|
|
363
|
+
(to_kebab_case(cl.__name__).replace("-", " ").capitalize(), get_class_description(cl)): cl
|
|
357
364
|
for cl in env_classes
|
|
358
365
|
if cl is not SubcommandPlaceholder
|
|
359
366
|
}
|
|
@@ -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
|
-
|
|
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
|
|
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 =
|
|
98
|
-
raise
|
|
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
|
|
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
|
|
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
|
|
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(
|
|
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))
|
|
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,69 @@ 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
|
-
|
|
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
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
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)
|
|
311
|
+
|
|
312
|
+
return _
|
|
341
313
|
|
|
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
|
-
)
|
|
349
314
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
315
|
+
def patched__format_help(cf: CliFlags):
|
|
316
|
+
orig = TyroArgumentParser.format_help
|
|
317
|
+
|
|
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 =
|
|
340
|
+
namespace, args = orig(self, args, namespace)
|
|
361
341
|
# NOTE We may check that the Env does not have its own `verbose``
|
|
362
|
-
# NOTE I do not like much tests need force=True here as they are run in paralel.
|
|
363
342
|
if cf.add_verbose and hasattr(namespace, "verbose"):
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
343
|
+
root = logging.getLogger()
|
|
344
|
+
if not root.handlers:
|
|
345
|
+
level = (
|
|
346
|
+
cf.get_log_level(namespace.verbose)
|
|
347
|
+
if namespace.verbose > 0
|
|
348
|
+
else cf.default_verbosity
|
|
349
|
+
)
|
|
350
|
+
logging.basicConfig(
|
|
351
|
+
level=level, format="%(message)s", stream=cf.orig_stream
|
|
352
|
+
)
|
|
353
|
+
elif namespace.verbose > 0:
|
|
354
|
+
level = cf.get_log_level(namespace.verbose)
|
|
355
|
+
root.setLevel(level)
|
|
356
|
+
for handler in root.handlers:
|
|
357
|
+
if handler.level > level: # increase verbosity for strict handlers
|
|
358
|
+
handler.setLevel(level)
|
|
368
359
|
delattr(namespace, "verbose")
|
|
369
360
|
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
361
|
+
if cf.add_version and hasattr(namespace, "version"):
|
|
362
|
+
# This code is now not used, see `custom_init`
|
|
363
|
+
# if namespace.version:
|
|
364
|
+
# print(namespace.version)
|
|
365
|
+
# raise SystemExit(0)
|
|
366
|
+
delattr(namespace, "version")
|
|
376
367
|
|
|
377
368
|
# Note that we do not parse --config here as it is parsed at `run.py`, before CLI parsing.
|
|
378
369
|
# Since config file serves as default fo CLI parsing.
|
|
@@ -381,7 +372,15 @@ def custom_parse_known_args(cf: CliFlags):
|
|
|
381
372
|
|
|
382
373
|
if cf.add_quiet and hasattr(namespace, "quiet"):
|
|
383
374
|
if namespace.quiet:
|
|
384
|
-
|
|
375
|
+
new_level = cf.get_log_level(-1)
|
|
376
|
+
root = logging.getLogger()
|
|
377
|
+
if not root.handlers:
|
|
378
|
+
logging.basicConfig(level=new_level, format="%(message)s", stream=cf.orig_stream)
|
|
379
|
+
else:
|
|
380
|
+
root.setLevel(new_level)
|
|
381
|
+
for handler in root.handlers:
|
|
382
|
+
if handler.level < new_level: # edit just benevolent handlers
|
|
383
|
+
handler.setLevel(new_level)
|
|
385
384
|
delattr(namespace, "quiet")
|
|
386
385
|
return namespace, args
|
|
387
386
|
|
|
@@ -32,7 +32,27 @@ class TkAdaptor(Tk, RichUiAdaptor, BackendAdaptor):
|
|
|
32
32
|
facet: TkFacet
|
|
33
33
|
settings: GuiSettings
|
|
34
34
|
|
|
35
|
+
_instance = None
|
|
36
|
+
""" singleton """
|
|
37
|
+
|
|
38
|
+
def __new__(cls, *args, **kwargs):
|
|
39
|
+
# Singleton.
|
|
40
|
+
# Why enforcing singleton?
|
|
41
|
+
# Invoking second tk would mean a strange second window
|
|
42
|
+
# and non-responding tkinter variables in the second invocation.
|
|
43
|
+
# get_interface("gui")
|
|
44
|
+
# m = get_interface("gui")
|
|
45
|
+
# m.select([1,2,3]) # cannot choose the value
|
|
46
|
+
if cls._instance is None:
|
|
47
|
+
return Tk.__new__(cls)
|
|
48
|
+
return cls._instance
|
|
49
|
+
|
|
35
50
|
def __init__(self, *args):
|
|
51
|
+
if self._instance:
|
|
52
|
+
return
|
|
53
|
+
else:
|
|
54
|
+
self.__class__._instance = self
|
|
55
|
+
|
|
36
56
|
BackendAdaptor.__init__(self, *args)
|
|
37
57
|
|
|
38
58
|
try:
|
|
@@ -40,6 +60,7 @@ class TkAdaptor(Tk, RichUiAdaptor, BackendAdaptor):
|
|
|
40
60
|
except TclError:
|
|
41
61
|
# even when installed the libraries are installed, display might not be available, hence tkinter fails
|
|
42
62
|
raise InterfaceNotAvailable
|
|
63
|
+
self._initialized = True
|
|
43
64
|
|
|
44
65
|
self.params = None
|
|
45
66
|
self._result = None
|
|
@@ -193,7 +193,8 @@ class SelectInputWrapper:
|
|
|
193
193
|
widget = AutoCombobox(self.frame, textvariable=self.variable)
|
|
194
194
|
widget["values"] = [k for k, *_ in options]
|
|
195
195
|
widget.pack()
|
|
196
|
-
widget.bind("<Return>", lambda _: "break") # override default enter that submits the form
|
|
196
|
+
#widget.bind("<Return>", lambda _: "break") # override default enter that submits the form
|
|
197
|
+
widget.bind("<Return>", lambda _: self._enter_handler())
|
|
197
198
|
|
|
198
199
|
self.set_default_label()
|
|
199
200
|
self.taking_focus = widget
|
|
@@ -212,3 +213,13 @@ class SelectInputWrapper:
|
|
|
212
213
|
# We never want to select the radiobutton in the initial phase
|
|
213
214
|
# as this might trigger on_change action (not caused by the user)
|
|
214
215
|
var.set(val)
|
|
216
|
+
|
|
217
|
+
def _enter_handler(self, event=None):
|
|
218
|
+
current_value = self.variable.get()
|
|
219
|
+
|
|
220
|
+
if not current_value:
|
|
221
|
+
return "break" # Let it perform the default behavior and open the dropdown
|
|
222
|
+
|
|
223
|
+
# If it has a value, submit it
|
|
224
|
+
self.adaptor._ok()
|
|
225
|
+
return "break"
|
|
@@ -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.
|
|
7
|
+
version = "1.2.2"
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mininterface-1.2.0 → mininterface-1.2.2}/mininterface/_textual_interface/button_contents.py
RENAMED
|
File without changes
|
|
File without changes
|
{mininterface-1.2.0 → mininterface-1.2.2}/mininterface/_textual_interface/file_picker_input.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mininterface-1.2.0 → mininterface-1.2.2}/mininterface/_tk_interface/redirect_text_tkinter.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|