mininterface 0.6.2rc2__tar.gz → 0.7.0__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 (43) hide show
  1. {mininterface-0.6.2rc2 → mininterface-0.7.0}/PKG-INFO +40 -29
  2. {mininterface-0.6.2rc2 → mininterface-0.7.0}/README.md +35 -26
  3. mininterface-0.7.0/mininterface/ValidationFail.py +5 -0
  4. {mininterface-0.6.2rc2 → mininterface-0.7.0}/mininterface/__init__.py +63 -48
  5. mininterface-0.7.0/mininterface/__main__.py +78 -0
  6. {mininterface-0.6.2rc2 → mininterface-0.7.0}/mininterface/auxiliary.py +11 -4
  7. {mininterface-0.6.2rc2 → mininterface-0.7.0}/mininterface/cli_parser.py +117 -60
  8. mininterface-0.7.0/mininterface/exceptions.py +30 -0
  9. {mininterface-0.6.2rc2 → mininterface-0.7.0}/mininterface/facet.py +36 -8
  10. {mininterface-0.6.2rc2 → mininterface-0.7.0}/mininterface/form_dict.py +68 -16
  11. mininterface-0.7.0/mininterface/interfaces.py +46 -0
  12. {mininterface-0.6.2rc2 → mininterface-0.7.0}/mininterface/mininterface.py +63 -33
  13. mininterface-0.7.0/mininterface/showcase.py +42 -0
  14. mininterface-0.7.0/mininterface/start.py +95 -0
  15. mininterface-0.7.0/mininterface/subcommands.py +141 -0
  16. {mininterface-0.6.2rc2 → mininterface-0.7.0}/mininterface/tag.py +57 -23
  17. mininterface-0.7.0/mininterface/tag_factory.py +51 -0
  18. {mininterface-0.6.2rc2 → mininterface-0.7.0}/mininterface/text_interface.py +9 -3
  19. mininterface-0.7.0/mininterface/textual_interface/__init__.py +45 -0
  20. mininterface-0.7.0/mininterface/textual_interface/textual_adaptor.py +95 -0
  21. mininterface-0.7.0/mininterface/textual_interface/textual_app.py +105 -0
  22. {mininterface-0.6.2rc2 → mininterface-0.7.0}/mininterface/textual_interface/textual_button_app.py +3 -3
  23. mininterface-0.7.0/mininterface/textual_interface/textual_facet.py +31 -0
  24. {mininterface-0.6.2rc2 → mininterface-0.7.0}/mininterface/textual_interface/widgets.py +2 -6
  25. {mininterface-0.6.2rc2 → mininterface-0.7.0}/mininterface/tk_interface/__init__.py +17 -11
  26. mininterface-0.7.0/mininterface/tk_interface/tk_facet.py +21 -0
  27. {mininterface-0.6.2rc2 → mininterface-0.7.0}/mininterface/tk_interface/tk_window.py +17 -15
  28. {mininterface-0.6.2rc2 → mininterface-0.7.0}/mininterface/tk_interface/utils.py +4 -5
  29. {mininterface-0.6.2rc2 → mininterface-0.7.0}/mininterface/type_stubs.py +3 -2
  30. {mininterface-0.6.2rc2 → mininterface-0.7.0}/mininterface/types.py +5 -1
  31. {mininterface-0.6.2rc2 → mininterface-0.7.0}/mininterface/validators.py +1 -1
  32. {mininterface-0.6.2rc2 → mininterface-0.7.0}/pyproject.toml +10 -4
  33. mininterface-0.6.2rc2/mininterface/__main__.py +0 -39
  34. mininterface-0.6.2rc2/mininterface/common.py +0 -8
  35. mininterface-0.6.2rc2/mininterface/tag_factory.py +0 -28
  36. mininterface-0.6.2rc2/mininterface/textual_interface/__init__.py +0 -59
  37. mininterface-0.6.2rc2/mininterface/textual_interface/textual_app.py +0 -147
  38. mininterface-0.6.2rc2/mininterface/textual_interface/textual_facet.py +0 -21
  39. mininterface-0.6.2rc2/mininterface/tk_interface/tk_facet.py +0 -20
  40. {mininterface-0.6.2rc2 → mininterface-0.7.0}/LICENSE +0 -0
  41. {mininterface-0.6.2rc2 → mininterface-0.7.0}/mininterface/experimental.py +0 -0
  42. {mininterface-0.6.2rc2 → mininterface-0.7.0}/mininterface/redirectable.py +0 -0
  43. {mininterface-0.6.2rc2 → mininterface-0.7.0}/mininterface/tk_interface/redirect_text_tkinter.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: mininterface
3
- Version: 0.6.2rc2
3
+ Version: 0.7.0
4
4
  Summary: A minimal access to GUI, TUI, CLI and config
5
5
  Home-page: https://github.com/CZ-NIC/mininterface
6
6
  License: GPL-3.0-or-later
@@ -13,24 +13,26 @@ Classifier: Programming Language :: Python :: 3.10
13
13
  Classifier: Programming Language :: Python :: 3.11
14
14
  Classifier: Programming Language :: Python :: 3.12
15
15
  Classifier: Programming Language :: Python :: 3.13
16
+ Provides-Extra: all
17
+ Provides-Extra: web
16
18
  Requires-Dist: autocombobox (==1.4.2)
17
19
  Requires-Dist: pyyaml
18
- Requires-Dist: requests
19
- Requires-Dist: textual
20
+ Requires-Dist: textual (>=0.84,<0.85)
20
21
  Requires-Dist: tkinter-tooltip
21
22
  Requires-Dist: tkinter_form (==0.1.5.2)
23
+ Requires-Dist: typing_extensions
22
24
  Requires-Dist: tyro
23
25
  Description-Content-Type: text/markdown
24
26
 
25
27
  # Mininterface – access to GUI, TUI, CLI and config files
26
28
  [![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0)
27
29
  [![Build Status](https://github.com/CZ-NIC/mininterface/actions/workflows/run-unittest.yml/badge.svg)](https://github.com/CZ-NIC/mininterface/actions)
28
- [![Downloads](https://pepy.tech/badge/mininterface)](https://pepy.tech/project/mininterface)
30
+ [![Downloads](https://static.pepy.tech/badge/mininterface)](https://pepy.tech/project/mininterface)
29
31
 
30
32
  Write the program core, do not bother with the input/output.
31
33
 
32
- ![Hello world example: GUI window](https://github.com/CZ-NIC/mininterface/blob/main/asset/hello-world.png?raw=True "A minimal use case – GUI")
33
- ![Hello world example: TUI fallback](https://github.com/CZ-NIC/mininterface/blob/main/asset/hello-tui.webp?raw=True "A minimal use case – TUI fallback")
34
+ ![Hello world example: GUI window](https://github.com/CZ-NIC/mininterface/blob/main/asset/hello-gui.avif?raw=True "A minimal use case – GUI")
35
+ ![Hello world example: TUI fallback](https://github.com/CZ-NIC/mininterface/blob/main/asset/hello-tui.avif?raw=True "A minimal use case – TUI fallback")
34
36
 
35
37
  Check out the code, which is surprisingly short, that displays such a window or its textual fallback.
36
38
 
@@ -49,10 +51,11 @@ class Env:
49
51
  """ This number is very important """
50
52
 
51
53
  if __name__ == "__main__":
52
- env = run(Env, prog="My application").env
54
+ m = run(Env, prog="My application")
55
+ m.form()
53
56
  # Attributes are suggested by the IDE
54
57
  # along with the hint text 'This number is very important'.
55
- print(env.my_number)
58
+ print(m.env.my_number)
56
59
  ```
57
60
 
58
61
  # Contents
@@ -69,16 +72,18 @@ It was all the code you need. No lengthy blocks of code imposed by an external d
69
72
 
70
73
 
71
74
  ```bash
72
- $ ./hello.py
73
- usage: My application [-h] [--test | --no-test] [--important-number INT]
75
+ $ ./hello.py --help
76
+ usage: My application [-h] [-v] [--my-flag | --no-my-flag] [--my-number INT]
74
77
 
75
78
  This calculates something.
76
79
 
77
- ╭─ options ──────────────────────────────────────────────────────────╮
78
- │ -h, --help show this help message and exit
79
- --test, --no-test My testing flag (default: False)
80
- │ --important-number INT This number is very important (default: 4)
81
- ╰────────────────────────────────────────────────────────────────────╯
80
+ ╭─ options ───────────────────────────────────────────────────────────────╮
81
+ │ -h, --help show this help message and exit
82
+ -v, --verbose Verbosity level. Can be used twice to increase.
83
+ │ --my-flag, --no-my-flag
84
+ │ This switches the functionality (default: False) │
85
+ │ --my-number INT This number is very important (default: 4) │
86
+ ╰─────────────────────────────────────────────────────────────────────────╯
82
87
  ```
83
88
 
84
89
  ## You got config file management
@@ -120,11 +125,22 @@ Install with a single command from [PyPi](https://pypi.org/project/mininterface/
120
125
  pip install mininterface
121
126
  ```
122
127
 
128
+ ## Minimal installation
129
+
130
+ Should you need just the CLI part and you are happy with basic text dialogs, use these commands instead:
131
+
132
+ ```bash
133
+ pip install --no-dependencies mininterface
134
+ pip install tyro typing_extensions pyyaml
135
+ ```
136
+
123
137
  # Docs
124
138
  See the docs overview at [https://cz-nic.github.io/mininterface/](https://cz-nic.github.io/mininterface/Overview/).
125
139
 
126
140
  # Examples
127
141
 
142
+ A powerful [`m.form`][mininterface.Mininterface.form] dialog method accepts either a dataclass or a dict. Take a look on both.
143
+
128
144
  ## A complex dataclass.
129
145
 
130
146
  ```python3
@@ -143,7 +159,7 @@ class Env:
143
159
  nested_config: NestedEnv
144
160
 
145
161
  mandatory_str: str
146
- """ As there is not default value, you will be prompted automatically to fill up the field """
162
+ """ As there is no default value, you will be prompted automatically to fill up the field """
147
163
 
148
164
  my_number: int | None = None
149
165
  """ This is not just a dummy number, if left empty, it is None. """
@@ -167,6 +183,14 @@ print(m.env)
167
183
  m.form()
168
184
  ```
169
185
 
186
+ As the attribute `mandatory_str` requires a value, a prompt appears automatically:
187
+
188
+ ![Complex example missing field](https://github.com/CZ-NIC/mininterface/blob/main/asset/complex_example_missing_field.avif?raw=True)
189
+
190
+ Then, full form appears:
191
+
192
+ ![Complex example](https://github.com/CZ-NIC/mininterface/blob/main/asset/complex_example.avif?raw=True)
193
+
170
194
  ## Form with paths
171
195
 
172
196
  We have a dict with some paths. Here is how it looks.
@@ -187,16 +211,3 @@ m.form(my_dictionary)
187
211
 
188
212
  ![List of paths](https://github.com/CZ-NIC/mininterface/blob/main/asset/list_of_paths.avif?raw=True)
189
213
 
190
-
191
-
192
-
193
-
194
-
195
-
196
-
197
-
198
-
199
-
200
-
201
-
202
-
@@ -1,12 +1,12 @@
1
1
  # Mininterface – access to GUI, TUI, CLI and config files
2
2
  [![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0)
3
3
  [![Build Status](https://github.com/CZ-NIC/mininterface/actions/workflows/run-unittest.yml/badge.svg)](https://github.com/CZ-NIC/mininterface/actions)
4
- [![Downloads](https://pepy.tech/badge/mininterface)](https://pepy.tech/project/mininterface)
4
+ [![Downloads](https://static.pepy.tech/badge/mininterface)](https://pepy.tech/project/mininterface)
5
5
 
6
6
  Write the program core, do not bother with the input/output.
7
7
 
8
- ![Hello world example: GUI window](https://github.com/CZ-NIC/mininterface/blob/main/asset/hello-world.png?raw=True "A minimal use case – GUI")
9
- ![Hello world example: TUI fallback](https://github.com/CZ-NIC/mininterface/blob/main/asset/hello-tui.webp?raw=True "A minimal use case – TUI fallback")
8
+ ![Hello world example: GUI window](https://github.com/CZ-NIC/mininterface/blob/main/asset/hello-gui.avif?raw=True "A minimal use case – GUI")
9
+ ![Hello world example: TUI fallback](https://github.com/CZ-NIC/mininterface/blob/main/asset/hello-tui.avif?raw=True "A minimal use case – TUI fallback")
10
10
 
11
11
  Check out the code, which is surprisingly short, that displays such a window or its textual fallback.
12
12
 
@@ -25,10 +25,11 @@ class Env:
25
25
  """ This number is very important """
26
26
 
27
27
  if __name__ == "__main__":
28
- env = run(Env, prog="My application").env
28
+ m = run(Env, prog="My application")
29
+ m.form()
29
30
  # Attributes are suggested by the IDE
30
31
  # along with the hint text 'This number is very important'.
31
- print(env.my_number)
32
+ print(m.env.my_number)
32
33
  ```
33
34
 
34
35
  # Contents
@@ -45,16 +46,18 @@ It was all the code you need. No lengthy blocks of code imposed by an external d
45
46
 
46
47
 
47
48
  ```bash
48
- $ ./hello.py
49
- usage: My application [-h] [--test | --no-test] [--important-number INT]
49
+ $ ./hello.py --help
50
+ usage: My application [-h] [-v] [--my-flag | --no-my-flag] [--my-number INT]
50
51
 
51
52
  This calculates something.
52
53
 
53
- ╭─ options ──────────────────────────────────────────────────────────╮
54
- │ -h, --help show this help message and exit
55
- --test, --no-test My testing flag (default: False)
56
- │ --important-number INT This number is very important (default: 4)
57
- ╰────────────────────────────────────────────────────────────────────╯
54
+ ╭─ options ───────────────────────────────────────────────────────────────╮
55
+ │ -h, --help show this help message and exit
56
+ -v, --verbose Verbosity level. Can be used twice to increase.
57
+ │ --my-flag, --no-my-flag
58
+ │ This switches the functionality (default: False) │
59
+ │ --my-number INT This number is very important (default: 4) │
60
+ ╰─────────────────────────────────────────────────────────────────────────╯
58
61
  ```
59
62
 
60
63
  ## You got config file management
@@ -96,11 +99,22 @@ Install with a single command from [PyPi](https://pypi.org/project/mininterface/
96
99
  pip install mininterface
97
100
  ```
98
101
 
102
+ ## Minimal installation
103
+
104
+ Should you need just the CLI part and you are happy with basic text dialogs, use these commands instead:
105
+
106
+ ```bash
107
+ pip install --no-dependencies mininterface
108
+ pip install tyro typing_extensions pyyaml
109
+ ```
110
+
99
111
  # Docs
100
112
  See the docs overview at [https://cz-nic.github.io/mininterface/](https://cz-nic.github.io/mininterface/Overview/).
101
113
 
102
114
  # Examples
103
115
 
116
+ A powerful [`m.form`][mininterface.Mininterface.form] dialog method accepts either a dataclass or a dict. Take a look on both.
117
+
104
118
  ## A complex dataclass.
105
119
 
106
120
  ```python3
@@ -119,7 +133,7 @@ class Env:
119
133
  nested_config: NestedEnv
120
134
 
121
135
  mandatory_str: str
122
- """ As there is not default value, you will be prompted automatically to fill up the field """
136
+ """ As there is no default value, you will be prompted automatically to fill up the field """
123
137
 
124
138
  my_number: int | None = None
125
139
  """ This is not just a dummy number, if left empty, it is None. """
@@ -143,6 +157,14 @@ print(m.env)
143
157
  m.form()
144
158
  ```
145
159
 
160
+ As the attribute `mandatory_str` requires a value, a prompt appears automatically:
161
+
162
+ ![Complex example missing field](https://github.com/CZ-NIC/mininterface/blob/main/asset/complex_example_missing_field.avif?raw=True)
163
+
164
+ Then, full form appears:
165
+
166
+ ![Complex example](https://github.com/CZ-NIC/mininterface/blob/main/asset/complex_example.avif?raw=True)
167
+
146
168
  ## Form with paths
147
169
 
148
170
  We have a dict with some paths. Here is how it looks.
@@ -162,16 +184,3 @@ m.form(my_dictionary)
162
184
  ```
163
185
 
164
186
  ![List of paths](https://github.com/CZ-NIC/mininterface/blob/main/asset/list_of_paths.avif?raw=True)
165
-
166
-
167
-
168
-
169
-
170
-
171
-
172
-
173
-
174
-
175
-
176
-
177
-
@@ -0,0 +1,5 @@
1
+ class ValidationFail(ValueError):
2
+ """ Signal to the form that submit failed and we want to restore it.
3
+ """
4
+ # NOTE example
5
+ pass
@@ -1,31 +1,22 @@
1
1
  import sys
2
+ from dataclasses import dataclass
2
3
  from pathlib import Path
3
- from typing import TYPE_CHECKING, Type
4
+ from types import UnionType
5
+ from typing import TYPE_CHECKING, Literal, Optional, Sequence, Type, Union
4
6
 
5
- from .types import Validation, Choices, PathTag
6
- from .cli_parser import _parse_cli
7
- from .common import InterfaceNotAvailable, Cancelled
7
+ from .exceptions import Cancelled, InterfaceNotAvailable
8
+
9
+ from .interfaces import get_interface
10
+
11
+ from . import validators
12
+ from .cli_parser import _parse_cli, assure_args
13
+ from .subcommands import Command, SubcommandPlaceholder
8
14
  from .form_dict import DataClass, EnvClass
9
- from .tag import Tag
10
15
  from .mininterface import EnvClass, Mininterface
16
+ from .start import Start
17
+ from .tag import Tag
11
18
  from .text_interface import ReplInterface, TextInterface
12
- from . import validators
13
-
14
- # Import optional interfaces
15
- try:
16
- from mininterface.tk_interface import TkInterface
17
- except ImportError:
18
- if TYPE_CHECKING:
19
- pass # Replace TYPE_CHECKING with `type GuiInterface = None` since Python 3.12
20
- else:
21
- TkInterface = None
22
- try:
23
- from mininterface.textual_interface import TextualInterface
24
- except ImportError:
25
- TextualInterface = None
26
-
27
- GuiInterface = TkInterface
28
- TuiInterface = TextualInterface or TextInterface
19
+ from .types import Choices, PathTag, Validation
29
20
 
30
21
  # NOTE:
31
22
  # ask_for_missing does not work with tyro Positional, stays missing.
@@ -33,14 +24,22 @@ TuiInterface = TextualInterface or TextInterface
33
24
  # class Env:
34
25
  # files: Positional[list[Path]]
35
26
 
27
+ # NOTE: imgs missing in Interfaces.md
28
+
29
+
30
+ @dataclass
31
+ class _Empty:
32
+ pass
33
+
36
34
 
37
- def run(env_class: Type[EnvClass] | None = None,
35
+ def run(env_or_list: Type[EnvClass] | list[Type[Command]] | None = None,
38
36
  ask_on_empty_cli: bool = False,
39
37
  title: str = "",
40
38
  config_file: Path | str | bool = True,
41
39
  add_verbosity: bool = True,
42
40
  ask_for_missing: bool = True,
43
- interface: Type[Mininterface] = GuiInterface or TuiInterface,
41
+ interface: Type[Mininterface] | Literal["gui"] | Literal["tui"] = None,
42
+ args: Optional[Sequence[str]] = None,
44
43
  **kwargs) -> Mininterface[EnvClass]:
45
44
  """ The main access, start here.
46
45
  Wrap your configuration dataclass into `run` to access the interface. An interface is chosen automatically,
@@ -51,7 +50,12 @@ def run(env_class: Type[EnvClass] | None = None,
51
50
  with the program name ending on *.yaml*, ex: `program.py` will fetch `./program.yaml`.
52
51
 
53
52
  Args:
54
- env_class: Dataclass with the configuration. Their values will be modified with the CLI arguments.
53
+ env_or_list:
54
+ * `dataclass` Dataclass with the configuration. Their values will be modified with the CLI arguments.
55
+ * `list` of [Commands][mininterface.subcommands.Command] let you create multiple commands within a single program, each with unique options.
56
+ * `None` You need just the dialogs, no CLI/config file parsing.
57
+
58
+
55
59
  ask_on_empty_cli: If program was launched with no arguments (empty CLI), invokes self.form() to edit the fields.
56
60
  (Withdrawn when `ask_for_missing` happens.)
57
61
  ```python
@@ -106,8 +110,10 @@ def run(env_class: Type[EnvClass] | None = None,
106
110
  $ program.py # omitting --required-number
107
111
  # Dialog for `required_number` appears
108
112
  ```
109
- interface: Which interface to prefer. By default, we use the GUI, the fallback is the TUI. See the full [list](Overview.md#all-possible-interfaces) of possible interfaces.
110
-
113
+ interface: Which interface to prefer. By default, we use the GUI, the fallback is the TUI.
114
+ You may write "gui" or "tui" literal or pass a specific Mininterface type,
115
+ see the full [list](Interfaces.md) of possible interfaces.
116
+ args: Parse arguments from a sequence instead of the command line.
111
117
  Kwargs:
112
118
  The same as for [argparse.ArgumentParser](https://docs.python.org/3/library/argparse.html).
113
119
 
@@ -119,8 +125,15 @@ def run(env_class: Type[EnvClass] | None = None,
119
125
  The stdout will be redirected to the interface (ex. a GUI window).
120
126
 
121
127
  ```python
128
+ from dataclasses import dataclass
129
+ from mininterface import run
130
+
131
+ @dataclass
132
+ class Env:
133
+ my_number: int = 4
134
+
122
135
  with run(Env) as m:
123
- print(f"Your important number is {m.env.important_number}")
136
+ print(f"Your important number is {m.env.my_number}")
124
137
  boolean = m.is_yes("Is that alright?")
125
138
  ```
126
139
 
@@ -139,7 +152,7 @@ def run(env_class: Type[EnvClass] | None = None,
139
152
  # Undocumented experimental: `default` keyword argument for tyro may serve for default values instead of a config file.
140
153
 
141
154
  # Prepare the config file
142
- if config_file is True and not kwargs.get("default") and env_class:
155
+ if config_file is True and not kwargs.get("default") and env_or_list:
143
156
  # Undocumented feature. User put a namespace into kwargs["default"]
144
157
  # that already serves for defaults. We do not fetch defaults yet from a config file.
145
158
  try:
@@ -155,26 +168,29 @@ def run(env_class: Type[EnvClass] | None = None,
155
168
  elif isinstance(config_file, str):
156
169
  config_file = Path(config_file)
157
170
 
158
- # Load configuration from CLI and a config file
171
+ # Determine title
172
+ title = title or kwargs.get("prog") or Path(sys.argv[0]).name
173
+ start = Start(title, interface)
174
+
175
+ # Hidden meta-commands in args
176
+ args = assure_args(args)
177
+ if len(args) == 1 and args[0] == "--integrate-to-system":
178
+ start.integrate(env_or_list or _Empty)
179
+ quit()
180
+
159
181
  env, wrong_fields = None, {}
160
- if env_class:
161
- verb_ = add_verbosity and "verbose" not in env_class.__annotations__
162
- env, wrong_fields = _parse_cli(env_class, config_file, verb_, ask_for_missing, **kwargs)
182
+ if isinstance(env_or_list, list) and SubcommandPlaceholder in env_or_list and args and args[0] == "subcommand":
183
+ start.choose_subcommand(env_or_list, args=args[1:])
184
+ elif isinstance(env_or_list, list) and not args:
185
+ start.choose_subcommand(env_or_list)
186
+ elif env_or_list:
187
+ # Load configuration from CLI and a config file
188
+ env, wrong_fields = _parse_cli(env_or_list, config_file, add_verbosity, ask_for_missing, args, **kwargs)
189
+ else: # even though there is no configuration, yet we need to parse CLI for meta-commands like --help or --verbose
190
+ _parse_cli(_Empty, None, add_verbosity, ask_for_missing, args)
163
191
 
164
192
  # Build the interface
165
- title = title or kwargs.get("prog") or Path(sys.argv[0]).name
166
- if "prog" not in kwargs:
167
- kwargs["prog"] = title
168
- try:
169
- if interface == "tui": # undocumented feature
170
- interface = TuiInterface
171
- elif interface == "gui": # undocumented feature
172
- interface = GuiInterface
173
- if interface is None:
174
- raise InterfaceNotAvailable # GuiInterface might be None when import fails
175
- interface = interface(title, env)
176
- except InterfaceNotAvailable: # Fallback to a different interface
177
- interface = TuiInterface(title, env)
193
+ interface = get_interface(title, interface, env)
178
194
 
179
195
  # Empty CLI → GUI edit
180
196
  if ask_for_missing and wrong_fields:
@@ -189,5 +205,4 @@ def run(env_class: Type[EnvClass] | None = None,
189
205
 
190
206
  __all__ = ["run", "Tag", "validators", "InterfaceNotAvailable", "Cancelled",
191
207
  "Validation", "Choices", "PathTag",
192
- "Mininterface", "GuiInterface", "TuiInterface", "TextInterface", "TextualInterface", "TkInterface"
193
- ]
208
+ "Mininterface"]
@@ -0,0 +1,78 @@
1
+ from dataclasses import dataclass
2
+ import sys
3
+ from typing import Literal
4
+ from tyro.conf import FlagConversionOff
5
+
6
+ from .exceptions import DependencyRequired
7
+
8
+ from . import run, Mininterface
9
+ from .showcase import showcase
10
+
11
+ __doc__ = """Simple GUI dialog. Outputs the value the user entered."""
12
+
13
+
14
+ @dataclass
15
+ class Web:
16
+ """ Experimenal undocumented feature. """
17
+
18
+ cmd: str = ""
19
+ """ Launch a miniterface program, while the TextualInterface will be exposed to the web."""
20
+ # NOTE: The textual app ends after the first submit. We have to correct that before the web makes sense.
21
+ # with run(interface=TextualInterface) as m:
22
+ # m.form({"hello": 1}) # the app ends here
23
+ # m.form({"hello": 2}) # we never get here
24
+
25
+ port: int = 64646
26
+
27
+
28
+ @dataclass
29
+ class CliInteface:
30
+ web: Web
31
+ alert: str = ""
32
+ """ Display the OK dialog with text. """
33
+ ask: str = ""
34
+ """ Prompt the user to input a text. """
35
+ ask_number: str = ""
36
+ """ Prompt the user to input a number. Empty input = 0. """
37
+ is_yes: str = ""
38
+ """ Display confirm box, focusing 'yes'. """
39
+ is_no: str = ""
40
+ """ Display confirm box, focusing 'no'. """
41
+
42
+ showcase: Literal["gui"] | Literal["tui"] | Literal["all"] = None
43
+ """ Prints various form just to show what's possible. """
44
+
45
+
46
+ def web(m: Mininterface):
47
+ try:
48
+ from textual_serve.server import Server
49
+ except ImportError:
50
+ raise DependencyRequired("web")
51
+ server = Server(m.env.web.cmd, port=m.env.web.port)
52
+ server.serve()
53
+
54
+
55
+ def main():
56
+ result = []
57
+ # We tested both GuiInterface and TextualInterface are able to pass a variable to i.e. a bash script.
58
+ # NOTE TextInterface fails (`mininterface --ask Test | grep Hello` – pipe causes no visible output).
59
+ with run(CliInteface, prog="Mininterface", description=__doc__) as m:
60
+ for method, label in vars(m.env).items():
61
+ if method in ["web", "showcase"]: # processed later
62
+ continue
63
+ if label:
64
+ result.append(getattr(m, method)(label))
65
+
66
+ # Displays each result on a new line. Currently, this is an undocumented feature.
67
+ # As we use the script for a single value only and it is not currently possible
68
+ # to ask two numbers or determine a dialog order etc.
69
+ [print(val) for val in result]
70
+
71
+ if m.env.web.cmd:
72
+ web(m)
73
+ if m.env.showcase:
74
+ showcase(m.env.showcase)
75
+
76
+
77
+ if __name__ == "__main__":
78
+ main()
@@ -1,7 +1,8 @@
1
+ from dataclasses import is_dataclass
1
2
  import os
2
3
  import re
3
4
  from argparse import ArgumentParser
4
- from typing import Iterable, TypeVar
5
+ from typing import Callable, Iterable, Optional, TypeVar
5
6
 
6
7
  from tyro.extras import get_parser
7
8
 
@@ -11,10 +12,12 @@ common_iterables = list, tuple, set
11
12
  """ collections, and not a str """
12
13
 
13
14
 
14
- def flatten(d: dict[str, T | dict]) -> Iterable[T]:
15
+ def flatten(d: dict[str, T | dict], include_keys: Optional[Callable[[str], list]] = None) -> Iterable[T]:
15
16
  """ Recursively traverse whole dict """
16
- for v in d.values():
17
+ for k, v in d.items():
17
18
  if isinstance(v, dict):
19
+ if include_keys:
20
+ yield from include_keys(k)
18
21
  yield from flatten(v)
19
22
  else:
20
23
  yield v
@@ -53,9 +56,13 @@ def get_terminal_size():
53
56
  def get_descriptions(parser: ArgumentParser) -> dict:
54
57
  """ Load descriptions from the parser. Strip argparse info about the default value as it will be editable in the form. """
55
58
  # clean-up tyro stuff that may have a meaning in the CLI, but not in the UI
56
- return {action.dest.replace("-", "_"): re.sub(r"\((default|fixed to).*\)", "", action.help or "")
59
+ return {action.dest.replace("-", "_"): re.sub(r"\((default|fixed to|required).*\)", "", action.help or "")
57
60
  for action in parser._actions}
58
61
 
59
62
 
60
63
  def get_description(obj, param: str) -> str:
61
64
  return get_descriptions(get_parser(obj))[param]
65
+
66
+
67
+ def yield_annotations(dataclass):
68
+ yield from (cl.__annotations__ for cl in dataclass.__mro__ if is_dataclass(cl))