mininterface 0.4.3__tar.gz → 0.4.4rc2__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-0.4.3 → mininterface-0.4.4rc2}/PKG-INFO +41 -39
- {mininterface-0.4.3 → mininterface-0.4.4rc2}/README.md +40 -38
- {mininterface-0.4.3 → mininterface-0.4.4rc2}/mininterface/FormDict.py +17 -4
- {mininterface-0.4.3 → mininterface-0.4.4rc2}/mininterface/FormField.py +1 -0
- {mininterface-0.4.3 → mininterface-0.4.4rc2}/mininterface/GuiInterface.py +1 -1
- {mininterface-0.4.3 → mininterface-0.4.4rc2}/mininterface/Mininterface.py +11 -9
- {mininterface-0.4.3 → mininterface-0.4.4rc2}/mininterface/TextInterface.py +2 -2
- {mininterface-0.4.3 → mininterface-0.4.4rc2}/mininterface/TextualInterface.py +2 -2
- {mininterface-0.4.3 → mininterface-0.4.4rc2}/mininterface/__init__.py +14 -6
- {mininterface-0.4.3 → mininterface-0.4.4rc2}/mininterface/__main__.py +0 -5
- {mininterface-0.4.3 → mininterface-0.4.4rc2}/pyproject.toml +1 -1
- {mininterface-0.4.3 → mininterface-0.4.4rc2}/mininterface/auxiliary.py +0 -0
- {mininterface-0.4.3 → mininterface-0.4.4rc2}/mininterface/common.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: mininterface
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.4rc2
|
|
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
|
|
@@ -27,10 +27,10 @@ Description-Content-Type: text/markdown
|
|
|
27
27
|
|
|
28
28
|
Write the program core, do not bother with the input/output.
|
|
29
29
|
|
|
30
|
-

|
|
31
|
-

|
|
30
|
+

|
|
31
|
+

|
|
32
32
|
|
|
33
|
-
Check out the code that displays such window or its textual fallback.
|
|
33
|
+
Check out the code, which is surprisingly short, that displays such a window or its textual fallback.
|
|
34
34
|
|
|
35
35
|
```python
|
|
36
36
|
from dataclasses import dataclass
|
|
@@ -39,17 +39,16 @@ from mininterface import run
|
|
|
39
39
|
@dataclass
|
|
40
40
|
class Config:
|
|
41
41
|
"""Set of options."""
|
|
42
|
-
test: bool = False
|
|
43
|
-
|
|
44
|
-
important_number: int = 4
|
|
45
|
-
"""This number is very important"""
|
|
42
|
+
test: bool = False # My testing flag
|
|
43
|
+
important_number: int = 4 # This number is very important
|
|
46
44
|
|
|
47
45
|
if __name__ == "__main__":
|
|
48
|
-
args
|
|
49
|
-
print(args.important_number)
|
|
46
|
+
args = run(Config, prog="My application").get_args()
|
|
47
|
+
print(args.important_number) # suggested by the IDE with the hint text "This number is very important"
|
|
50
48
|
```
|
|
51
49
|
|
|
52
|
-
|
|
50
|
+
## You got CLI
|
|
51
|
+
It was all the code you need. No lengthy blocks of code imposed by an external dependency. Besides the GUI/TUI, you receive powerful YAML-configurable CLI parsing.
|
|
53
52
|
|
|
54
53
|
```bash
|
|
55
54
|
$ ./hello.py
|
|
@@ -64,7 +63,15 @@ Set of options.
|
|
|
64
63
|
╰────────────────────────────────────────────────────────────────────╯
|
|
65
64
|
```
|
|
66
65
|
|
|
67
|
-
You
|
|
66
|
+
## You got config file management
|
|
67
|
+
Loading config file is a piece of cake. Alongside `program.py`, put `program.yaml` and put there some of the arguments. They are seamlessly taken as defaults.
|
|
68
|
+
|
|
69
|
+
```yaml
|
|
70
|
+
important_number: 555
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## You got dialogues
|
|
74
|
+
Check out several useful methods to handle user dialogues. Here we bound the interface to a `with` statement that redirects stdout directly to the window.
|
|
68
75
|
|
|
69
76
|
```python
|
|
70
77
|
with run(Config) as m:
|
|
@@ -72,15 +79,10 @@ with run(Config) as m:
|
|
|
72
79
|
boolean = m.is_yes("Is that alright?")
|
|
73
80
|
```
|
|
74
81
|
|
|
75
|
-

|
|
76
|
-

|
|
77
|
-
|
|
78
|
-
Loading config file is a piece of cake. Alongside `program.py`, put `program.yaml` and put there some of the arguments. Instantly loaded.
|
|
79
|
-
|
|
80
|
-
```yaml
|
|
81
|
-
important_number: 555
|
|
82
|
-
```
|
|
82
|
+

|
|
83
|
+

|
|
83
84
|
|
|
85
|
+
# Contents
|
|
84
86
|
- [Mininterface – GUI, TUI, CLI and config](#mininterface-gui-tui-cli-and-config)
|
|
85
87
|
- [Background](#background)
|
|
86
88
|
- [Installation](#installation)
|
|
@@ -89,15 +91,15 @@ important_number: 555
|
|
|
89
91
|
+ [`run(config=None, interface=GuiInterface, **kwargs)`](#runconfignone-interfaceguiinterface-kwargs)
|
|
90
92
|
* [Interfaces](#interfaces)
|
|
91
93
|
+ [`Mininterface(title: str = '')`](#mininterfacetitle-str--)
|
|
92
|
-
+ [`alert(
|
|
93
|
-
+ [`ask(
|
|
94
|
-
+ [`ask_args(
|
|
95
|
-
+ [`
|
|
96
|
-
+ [`
|
|
97
|
-
+ [`get_args(
|
|
98
|
-
+ [`is_no(
|
|
99
|
-
+ [`is_yes(
|
|
100
|
-
+ [`parse_args(
|
|
94
|
+
+ [`alert(text: str)`](#alerttext-str)
|
|
95
|
+
+ [`ask(text: str) -> str`](#asktext-str---str)
|
|
96
|
+
+ [`ask_args() -> ConfigInstance`](#ask_args--configinstance)
|
|
97
|
+
+ [`ask_number(text: str) -> int`](#ask_numbertext-str---int)
|
|
98
|
+
+ [`form(args: FormDict, title="") -> int`](#formargs-formdict-title---dict)
|
|
99
|
+
+ [`get_args(ask_on_empty_cli=True) -> ~ConfigInstance`](#get_argsask_on_empty_clitrue---configinstance)
|
|
100
|
+
+ [`is_no(text: str) -> bool`](#is_notext-str---bool)
|
|
101
|
+
+ [`is_yes(text: str) -> bool`](#is_yestext-str---bool)
|
|
102
|
+
+ [`parse_args(config: Type[ConfigInstance], config_file: pathlib.Path | None = None, **kwargs) -> ConfigInstance`](#parse_argsconfig-type-configinstance-config_file-pathlibpath--none--none-kwargs---configinstance)
|
|
101
103
|
* [Standalone](#standalone)
|
|
102
104
|
|
|
103
105
|
# Background
|
|
@@ -159,7 +161,7 @@ $./program.py --further.host example.net
|
|
|
159
161
|
Wrap your configuration dataclass into `run` to access the interface. Normally, an interface is chosen automatically. We prefer the graphical one, regressed to a text interface on a machine without display.
|
|
160
162
|
Besides, if given a configuration dataclass, the function enriches it with the CLI commands and possibly with the default from a config file if such exists. It searches the config file in the current working directory, with the program name ending on *.yaml*, ex: `program.py` will fetch `./program.yaml`.
|
|
161
163
|
|
|
162
|
-
* `config:
|
|
164
|
+
* `config:Type[ConfigInstance]`: Dataclass with the configuration.
|
|
163
165
|
* `interface`: Which interface to prefer. By default, we use the GUI, the fallback is the REPL.
|
|
164
166
|
* `**kwargs`: The same as for [`argparse.ArgumentParser`](https://docs.python.org/3/library/argparse.html).
|
|
165
167
|
* Returns: `interface` Interface used.
|
|
@@ -188,27 +190,27 @@ with TuiInterface("My program") as m:
|
|
|
188
190
|
|
|
189
191
|
### `Mininterface(title: str = '')`
|
|
190
192
|
Initialize.
|
|
191
|
-
### `alert(
|
|
193
|
+
### `alert(text: str)`
|
|
192
194
|
Prompt the user to confirm the text.
|
|
193
|
-
### `ask(
|
|
195
|
+
### `ask(text: str) -> str`
|
|
194
196
|
Prompt the user to input a text.
|
|
195
|
-
### `ask_args(
|
|
197
|
+
### `ask_args() -> ConfigInstance`
|
|
196
198
|
Allow the user to edit whole configuration. (Previously fetched from CLI and config file by parse_args.)
|
|
197
|
-
### `
|
|
199
|
+
### `form(args: FormDict, title="") -> dict`
|
|
198
200
|
Prompt the user to fill up whole form.
|
|
199
201
|
* `args`: Dict of `{labels: default value}`. The form widget infers from the default value type.
|
|
200
202
|
The dict can be nested, it can contain a subgroup.
|
|
201
203
|
The default value might be `mininterface.FormField` that allows you to add descriptions.
|
|
202
204
|
A checkbox example: `{"my label": FormField(True, "my description")}`
|
|
203
205
|
* `title`: Optional form title.
|
|
204
|
-
### `ask_number(
|
|
206
|
+
### `ask_number(text: str) -> int`
|
|
205
207
|
Prompt the user to input a number. Empty input = 0.
|
|
206
|
-
### `get_args(
|
|
208
|
+
### `get_args(ask_on_empty_cli=True) -> ConfigInstance`
|
|
207
209
|
Returns whole configuration (previously fetched from CLI and config file by parse_args).
|
|
208
210
|
If program was launched with no arguments (empty CLI), invokes self.ask_args() to edit the fields.
|
|
209
|
-
### `is_no(
|
|
211
|
+
### `is_no(text: str) -> bool`
|
|
210
212
|
Display confirm box, focusing no.
|
|
211
|
-
### `is_yes(
|
|
213
|
+
### `is_yes(text: str) -> bool`
|
|
212
214
|
Display confirm box, focusing yes.
|
|
213
215
|
|
|
214
216
|
```python
|
|
@@ -216,7 +218,7 @@ m = run(prog="My program")
|
|
|
216
218
|
print(m.ask_yes("Is it true?")) # True/False
|
|
217
219
|
```
|
|
218
220
|
|
|
219
|
-
### `parse_args(
|
|
221
|
+
### `parse_args(config: Type[ConfigInstance], config_file: pathlib.Path | None = None, **kwargs) -> ~ConfigInstance`
|
|
220
222
|
Parse CLI arguments, possibly merged from a config file.
|
|
221
223
|
* `config`: Dataclass with the configuration.
|
|
222
224
|
* `config_file`: File to load YAML to be merged with the configuration. You do not have to re-define all the settings, you can choose a few.
|
|
@@ -4,10 +4,10 @@
|
|
|
4
4
|
|
|
5
5
|
Write the program core, do not bother with the input/output.
|
|
6
6
|
|
|
7
|
-

|
|
8
|
-

|
|
7
|
+

|
|
8
|
+

|
|
9
9
|
|
|
10
|
-
Check out the code that displays such window or its textual fallback.
|
|
10
|
+
Check out the code, which is surprisingly short, that displays such a window or its textual fallback.
|
|
11
11
|
|
|
12
12
|
```python
|
|
13
13
|
from dataclasses import dataclass
|
|
@@ -16,17 +16,16 @@ from mininterface import run
|
|
|
16
16
|
@dataclass
|
|
17
17
|
class Config:
|
|
18
18
|
"""Set of options."""
|
|
19
|
-
test: bool = False
|
|
20
|
-
|
|
21
|
-
important_number: int = 4
|
|
22
|
-
"""This number is very important"""
|
|
19
|
+
test: bool = False # My testing flag
|
|
20
|
+
important_number: int = 4 # This number is very important
|
|
23
21
|
|
|
24
22
|
if __name__ == "__main__":
|
|
25
|
-
args
|
|
26
|
-
print(args.important_number)
|
|
23
|
+
args = run(Config, prog="My application").get_args()
|
|
24
|
+
print(args.important_number) # suggested by the IDE with the hint text "This number is very important"
|
|
27
25
|
```
|
|
28
26
|
|
|
29
|
-
|
|
27
|
+
## You got CLI
|
|
28
|
+
It was all the code you need. No lengthy blocks of code imposed by an external dependency. Besides the GUI/TUI, you receive powerful YAML-configurable CLI parsing.
|
|
30
29
|
|
|
31
30
|
```bash
|
|
32
31
|
$ ./hello.py
|
|
@@ -41,7 +40,15 @@ Set of options.
|
|
|
41
40
|
╰────────────────────────────────────────────────────────────────────╯
|
|
42
41
|
```
|
|
43
42
|
|
|
44
|
-
You
|
|
43
|
+
## You got config file management
|
|
44
|
+
Loading config file is a piece of cake. Alongside `program.py`, put `program.yaml` and put there some of the arguments. They are seamlessly taken as defaults.
|
|
45
|
+
|
|
46
|
+
```yaml
|
|
47
|
+
important_number: 555
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## You got dialogues
|
|
51
|
+
Check out several useful methods to handle user dialogues. Here we bound the interface to a `with` statement that redirects stdout directly to the window.
|
|
45
52
|
|
|
46
53
|
```python
|
|
47
54
|
with run(Config) as m:
|
|
@@ -49,15 +56,10 @@ with run(Config) as m:
|
|
|
49
56
|
boolean = m.is_yes("Is that alright?")
|
|
50
57
|
```
|
|
51
58
|
|
|
52
|
-

|
|
53
|
-

|
|
54
|
-
|
|
55
|
-
Loading config file is a piece of cake. Alongside `program.py`, put `program.yaml` and put there some of the arguments. Instantly loaded.
|
|
56
|
-
|
|
57
|
-
```yaml
|
|
58
|
-
important_number: 555
|
|
59
|
-
```
|
|
59
|
+

|
|
60
|
+

|
|
60
61
|
|
|
62
|
+
# Contents
|
|
61
63
|
- [Mininterface – GUI, TUI, CLI and config](#mininterface-gui-tui-cli-and-config)
|
|
62
64
|
- [Background](#background)
|
|
63
65
|
- [Installation](#installation)
|
|
@@ -66,15 +68,15 @@ important_number: 555
|
|
|
66
68
|
+ [`run(config=None, interface=GuiInterface, **kwargs)`](#runconfignone-interfaceguiinterface-kwargs)
|
|
67
69
|
* [Interfaces](#interfaces)
|
|
68
70
|
+ [`Mininterface(title: str = '')`](#mininterfacetitle-str--)
|
|
69
|
-
+ [`alert(
|
|
70
|
-
+ [`ask(
|
|
71
|
-
+ [`ask_args(
|
|
72
|
-
+ [`
|
|
73
|
-
+ [`
|
|
74
|
-
+ [`get_args(
|
|
75
|
-
+ [`is_no(
|
|
76
|
-
+ [`is_yes(
|
|
77
|
-
+ [`parse_args(
|
|
71
|
+
+ [`alert(text: str)`](#alerttext-str)
|
|
72
|
+
+ [`ask(text: str) -> str`](#asktext-str---str)
|
|
73
|
+
+ [`ask_args() -> ConfigInstance`](#ask_args--configinstance)
|
|
74
|
+
+ [`ask_number(text: str) -> int`](#ask_numbertext-str---int)
|
|
75
|
+
+ [`form(args: FormDict, title="") -> int`](#formargs-formdict-title---dict)
|
|
76
|
+
+ [`get_args(ask_on_empty_cli=True) -> ~ConfigInstance`](#get_argsask_on_empty_clitrue---configinstance)
|
|
77
|
+
+ [`is_no(text: str) -> bool`](#is_notext-str---bool)
|
|
78
|
+
+ [`is_yes(text: str) -> bool`](#is_yestext-str---bool)
|
|
79
|
+
+ [`parse_args(config: Type[ConfigInstance], config_file: pathlib.Path | None = None, **kwargs) -> ConfigInstance`](#parse_argsconfig-type-configinstance-config_file-pathlibpath--none--none-kwargs---configinstance)
|
|
78
80
|
* [Standalone](#standalone)
|
|
79
81
|
|
|
80
82
|
# Background
|
|
@@ -136,7 +138,7 @@ $./program.py --further.host example.net
|
|
|
136
138
|
Wrap your configuration dataclass into `run` to access the interface. Normally, an interface is chosen automatically. We prefer the graphical one, regressed to a text interface on a machine without display.
|
|
137
139
|
Besides, if given a configuration dataclass, the function enriches it with the CLI commands and possibly with the default from a config file if such exists. It searches the config file in the current working directory, with the program name ending on *.yaml*, ex: `program.py` will fetch `./program.yaml`.
|
|
138
140
|
|
|
139
|
-
* `config:
|
|
141
|
+
* `config:Type[ConfigInstance]`: Dataclass with the configuration.
|
|
140
142
|
* `interface`: Which interface to prefer. By default, we use the GUI, the fallback is the REPL.
|
|
141
143
|
* `**kwargs`: The same as for [`argparse.ArgumentParser`](https://docs.python.org/3/library/argparse.html).
|
|
142
144
|
* Returns: `interface` Interface used.
|
|
@@ -165,27 +167,27 @@ with TuiInterface("My program") as m:
|
|
|
165
167
|
|
|
166
168
|
### `Mininterface(title: str = '')`
|
|
167
169
|
Initialize.
|
|
168
|
-
### `alert(
|
|
170
|
+
### `alert(text: str)`
|
|
169
171
|
Prompt the user to confirm the text.
|
|
170
|
-
### `ask(
|
|
172
|
+
### `ask(text: str) -> str`
|
|
171
173
|
Prompt the user to input a text.
|
|
172
|
-
### `ask_args(
|
|
174
|
+
### `ask_args() -> ConfigInstance`
|
|
173
175
|
Allow the user to edit whole configuration. (Previously fetched from CLI and config file by parse_args.)
|
|
174
|
-
### `
|
|
176
|
+
### `form(args: FormDict, title="") -> dict`
|
|
175
177
|
Prompt the user to fill up whole form.
|
|
176
178
|
* `args`: Dict of `{labels: default value}`. The form widget infers from the default value type.
|
|
177
179
|
The dict can be nested, it can contain a subgroup.
|
|
178
180
|
The default value might be `mininterface.FormField` that allows you to add descriptions.
|
|
179
181
|
A checkbox example: `{"my label": FormField(True, "my description")}`
|
|
180
182
|
* `title`: Optional form title.
|
|
181
|
-
### `ask_number(
|
|
183
|
+
### `ask_number(text: str) -> int`
|
|
182
184
|
Prompt the user to input a number. Empty input = 0.
|
|
183
|
-
### `get_args(
|
|
185
|
+
### `get_args(ask_on_empty_cli=True) -> ConfigInstance`
|
|
184
186
|
Returns whole configuration (previously fetched from CLI and config file by parse_args).
|
|
185
187
|
If program was launched with no arguments (empty CLI), invokes self.ask_args() to edit the fields.
|
|
186
|
-
### `is_no(
|
|
188
|
+
### `is_no(text: str) -> bool`
|
|
187
189
|
Display confirm box, focusing no.
|
|
188
|
-
### `is_yes(
|
|
190
|
+
### `is_yes(text: str) -> bool`
|
|
189
191
|
Display confirm box, focusing yes.
|
|
190
192
|
|
|
191
193
|
```python
|
|
@@ -193,7 +195,7 @@ m = run(prog="My program")
|
|
|
193
195
|
print(m.ask_yes("Is it true?")) # True/False
|
|
194
196
|
```
|
|
195
197
|
|
|
196
|
-
### `parse_args(
|
|
198
|
+
### `parse_args(config: Type[ConfigInstance], config_file: pathlib.Path | None = None, **kwargs) -> ~ConfigInstance`
|
|
197
199
|
Parse CLI arguments, possibly merged from a config file.
|
|
198
200
|
* `config`: Dataclass with the configuration.
|
|
199
201
|
* `config_file`: File to load YAML to be merged with the configuration. You do not have to re-define all the settings, you can choose a few.
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"""
|
|
4
4
|
import logging
|
|
5
5
|
from argparse import Action, ArgumentParser
|
|
6
|
-
from typing import Callable, Optional, TypeVar, Union, get_type_hints
|
|
6
|
+
from typing import Callable, Optional, Type, TypeVar, Union, get_type_hints
|
|
7
7
|
from unittest.mock import patch
|
|
8
8
|
|
|
9
9
|
from tyro import cli
|
|
@@ -14,7 +14,7 @@ from .FormField import FormField
|
|
|
14
14
|
logger = logging.getLogger(__name__)
|
|
15
15
|
|
|
16
16
|
ConfigInstance = TypeVar("ConfigInstance")
|
|
17
|
-
ConfigClass =
|
|
17
|
+
ConfigClass = Type[ConfigInstance]
|
|
18
18
|
FormDict = dict[str, Union[FormField, 'FormDict']]
|
|
19
19
|
""" Nested form that can have descriptions (through FormField) instead of plain values. """
|
|
20
20
|
|
|
@@ -71,7 +71,7 @@ def config_to_formdict(args: ConfigInstance, descr: dict, _path="") -> FormDict:
|
|
|
71
71
|
return params
|
|
72
72
|
|
|
73
73
|
|
|
74
|
-
def get_args_allow_missing(config:
|
|
74
|
+
def get_args_allow_missing(config: Type[ConfigInstance], kwargs: dict, parser: ArgumentParser) -> ConfigInstance:
|
|
75
75
|
""" Fetch missing required options in GUI. """
|
|
76
76
|
# On missing argument, tyro fail. We cannot determine which one was missing, except by intercepting
|
|
77
77
|
# the error message function. Then, we reconstruct the missing options.
|
|
@@ -85,9 +85,22 @@ def get_args_allow_missing(config: ConfigClass, kwargs: dict, parser: ArgumentPa
|
|
|
85
85
|
return original_error(self, message)
|
|
86
86
|
eavesdrop = message
|
|
87
87
|
raise SystemExit(2) # will be catched
|
|
88
|
+
|
|
89
|
+
# Set args to determine whether to use sys.argv.
|
|
90
|
+
# Why settings args? Prevent tyro using sys.argv if we are in an interactive shell like Jupyter,
|
|
91
|
+
# as sys.argv is non-related there.
|
|
92
|
+
try:
|
|
93
|
+
# Note wherease `"get_ipython" in globals()` returns True in Jupyter, it is still False
|
|
94
|
+
# in a script a Jupyter cell runs. Hence we must put here this lengthty statement.
|
|
95
|
+
global get_ipython
|
|
96
|
+
get_ipython()
|
|
97
|
+
except:
|
|
98
|
+
args = None
|
|
99
|
+
else:
|
|
100
|
+
args = []
|
|
88
101
|
try:
|
|
89
102
|
with patch.object(TyroArgumentParser, 'error', custom_error):
|
|
90
|
-
return cli(config, **kwargs)
|
|
103
|
+
return cli(config, args=args, **kwargs)
|
|
91
104
|
except BaseException as e:
|
|
92
105
|
if hasattr(e, "code") and e.code == 2 and eavesdrop: # Some arguments are missing. Determine which.
|
|
93
106
|
for arg in eavesdrop.partition(":")[2].strip().split(", "):
|
|
@@ -57,7 +57,7 @@ class GuiInterface(Mininterface):
|
|
|
57
57
|
self.window.run_dialog(formDict)
|
|
58
58
|
return self.args
|
|
59
59
|
|
|
60
|
-
def
|
|
60
|
+
def form(self, form: FormDict, title: str = "") -> dict:
|
|
61
61
|
""" Prompt the user to fill up whole form.
|
|
62
62
|
:param args: Dict of `{labels: default value}`. The form widget infers from the default value type.
|
|
63
63
|
The dict can be nested, it can contain a subgroup.
|
|
@@ -4,6 +4,7 @@ from argparse import ArgumentParser
|
|
|
4
4
|
from dataclasses import MISSING
|
|
5
5
|
from pathlib import Path
|
|
6
6
|
from types import SimpleNamespace
|
|
7
|
+
from typing import Generic, Type
|
|
7
8
|
|
|
8
9
|
import yaml
|
|
9
10
|
from tyro.extras import get_parser
|
|
@@ -19,7 +20,7 @@ class Cancelled(SystemExit):
|
|
|
19
20
|
pass
|
|
20
21
|
|
|
21
22
|
|
|
22
|
-
class Mininterface:
|
|
23
|
+
class Mininterface(Generic[ConfigInstance]):
|
|
23
24
|
""" The base interface.
|
|
24
25
|
Does not require any user input and hence is suitable for headless testing.
|
|
25
26
|
"""
|
|
@@ -54,7 +55,12 @@ class Mininterface:
|
|
|
54
55
|
print("Asking the args", self.args)
|
|
55
56
|
return self.args
|
|
56
57
|
|
|
57
|
-
def
|
|
58
|
+
def ask_number(self, text: str) -> int:
|
|
59
|
+
""" Prompt the user to input a number. Empty input = 0. """
|
|
60
|
+
print("Asking number", text)
|
|
61
|
+
return 0
|
|
62
|
+
|
|
63
|
+
def form(self, data: FormDict, title: str = "") -> dict:
|
|
58
64
|
""" Prompt the user to fill up whole form.
|
|
59
65
|
:param args: Dict of `{labels: default value}`. The form widget infers from the default value type.
|
|
60
66
|
The dict can be nested, it can contain a subgroup.
|
|
@@ -64,11 +70,6 @@ class Mininterface:
|
|
|
64
70
|
print(f"Asking the form {title}", data)
|
|
65
71
|
return data # NOTE – this should return dict, not FormDict (get rid of auxiliary.FormField values)
|
|
66
72
|
|
|
67
|
-
def ask_number(self, text: str) -> int:
|
|
68
|
-
""" Prompt the user to input a number. Empty input = 0. """
|
|
69
|
-
print("Asking number", text)
|
|
70
|
-
return 0
|
|
71
|
-
|
|
72
73
|
def get_args(self, ask_on_empty_cli=True) -> ConfigInstance:
|
|
73
74
|
""" Returns whole configuration (previously fetched from CLI and config file by parse_args).
|
|
74
75
|
If program was launched with no arguments (empty CLI), invokes self.ask_args() to edit the fields. """
|
|
@@ -77,13 +78,14 @@ class Mininterface:
|
|
|
77
78
|
return self.ask_args()
|
|
78
79
|
return self.args
|
|
79
80
|
|
|
80
|
-
def parse_args(self, config:
|
|
81
|
+
def parse_args(self, config: Type[ConfigInstance],
|
|
81
82
|
config_file: Path | None = None,
|
|
82
83
|
**kwargs) -> ConfigInstance:
|
|
83
84
|
""" Parse CLI arguments, possibly merged from a config file.
|
|
84
85
|
|
|
85
86
|
:param config: Class with the configuration.
|
|
86
|
-
:param config_file: File to load YAML to be merged with the configuration.
|
|
87
|
+
:param config_file: File to load YAML to be merged with the configuration.
|
|
88
|
+
You do not have to re-define all the settings in the config file, you can choose a few.
|
|
87
89
|
:param **kwargs The same as for argparse.ArgumentParser.
|
|
88
90
|
:return: Configuration namespace.
|
|
89
91
|
"""
|
|
@@ -26,9 +26,9 @@ class TextInterface(Mininterface):
|
|
|
26
26
|
# params_ = dataclass_to_dict(self.args, self.descriptions)
|
|
27
27
|
# data = FormDict → dict self.window.run_dialog(params_)
|
|
28
28
|
# dict_to_dataclass(self.args, params_)
|
|
29
|
-
return self.
|
|
29
|
+
return self.form(self.args)
|
|
30
30
|
|
|
31
|
-
def
|
|
31
|
+
def form(self, form: FormDict) -> dict:
|
|
32
32
|
# NOTE: This is minimal implementation that should rather go the ReplInterface.
|
|
33
33
|
print("Access `v` (as var) and change values. Then (c)ontinue.")
|
|
34
34
|
pprint(form)
|
|
@@ -36,7 +36,7 @@ class TextualInterface(TextInterface):
|
|
|
36
36
|
TextualButtonApp().buttons(text, [("Ok", None)]).run()
|
|
37
37
|
|
|
38
38
|
def ask(self, text: str = None):
|
|
39
|
-
return self.
|
|
39
|
+
return self.form({text: ""})[text]
|
|
40
40
|
|
|
41
41
|
def ask_args(self) -> ConfigInstance:
|
|
42
42
|
""" Display a window form with all parameters. """
|
|
@@ -46,7 +46,7 @@ class TextualInterface(TextInterface):
|
|
|
46
46
|
TextualApp.run_dialog(TextualApp(), params_)
|
|
47
47
|
return self.args
|
|
48
48
|
|
|
49
|
-
def
|
|
49
|
+
def form(self, form: FormDict, title: str = "") -> dict:
|
|
50
50
|
return TextualApp.run_dialog(TextualApp(), dict_to_formdict(form), title)
|
|
51
51
|
|
|
52
52
|
# NOTE we should implement better, now the user does not know it needs an int
|
|
@@ -4,7 +4,7 @@ from typing import TYPE_CHECKING, Type
|
|
|
4
4
|
from unittest.mock import patch
|
|
5
5
|
|
|
6
6
|
|
|
7
|
-
from mininterface.Mininterface import
|
|
7
|
+
from mininterface.Mininterface import ConfigInstance, Mininterface
|
|
8
8
|
from mininterface.TextInterface import ReplInterface, TextInterface
|
|
9
9
|
from mininterface.FormField import FormField
|
|
10
10
|
|
|
@@ -29,10 +29,11 @@ class TuiInterface(TextualInterface or TextInterface):
|
|
|
29
29
|
pass
|
|
30
30
|
|
|
31
31
|
|
|
32
|
-
def run(config:
|
|
32
|
+
def run(config: Type[ConfigInstance] | None = None,
|
|
33
33
|
interface: Type[Mininterface] = GuiInterface or TuiInterface,
|
|
34
|
-
**kwargs) -> Mininterface:
|
|
34
|
+
**kwargs) -> Mininterface[ConfigInstance]:
|
|
35
35
|
"""
|
|
36
|
+
Main access.
|
|
36
37
|
Wrap your configuration dataclass into `run` to access the interface. Normally, an interface is chosen automatically.
|
|
37
38
|
We prefer the graphical one, regressed to a text interface on a machine without display.
|
|
38
39
|
Besides, if given a configuration dataclass, the function enriches it with the CLI commands and possibly
|
|
@@ -50,16 +51,23 @@ def run(config: ConfigClass | None = None,
|
|
|
50
51
|
"""
|
|
51
52
|
# Build the interface
|
|
52
53
|
prog = kwargs.get("prog") or sys.argv[0]
|
|
53
|
-
# try:
|
|
54
54
|
interface: GuiInterface | Mininterface = interface(prog)
|
|
55
|
-
# except InterfaceNotAvailable: # Fallback to a different interface
|
|
56
|
-
# interface = TuiInterface(prog)
|
|
57
55
|
|
|
58
56
|
# Load configuration from CLI and a config file
|
|
59
57
|
if config:
|
|
60
58
|
cf = Path(sys.argv[0]).with_suffix(".yaml")
|
|
61
59
|
interface.parse_args(config, cf if cf.exists() and not kwargs.get("default") else None, **kwargs)
|
|
62
60
|
|
|
61
|
+
# NOTE draft – move the functionality inside Mininterface?
|
|
62
|
+
# What will be the most used params?
|
|
63
|
+
# run(config: Type[ConfigInstance],
|
|
64
|
+
# prog="merge to kwargs later",
|
|
65
|
+
# config_file:Path|str="",
|
|
66
|
+
# interface: Type[Mininterface] = GuiInterface or TuiInterface,
|
|
67
|
+
# **kwargs)
|
|
68
|
+
# title = prog or sys.argv
|
|
69
|
+
# Mininterface(title, configClass, configFile, **kwargs)
|
|
70
|
+
|
|
63
71
|
return interface
|
|
64
72
|
|
|
65
73
|
|
|
@@ -1,11 +1,6 @@
|
|
|
1
1
|
from dataclasses import dataclass
|
|
2
|
-
from typing import List
|
|
3
|
-
|
|
4
|
-
from .GuiInterface import GuiInterface
|
|
5
|
-
|
|
6
2
|
from . import run
|
|
7
3
|
|
|
8
|
-
from tyro.conf import UseCounterAction, UseAppendAction
|
|
9
4
|
__doc__ = """Simple GUI dialog. Outputs the value the user entered."""
|
|
10
5
|
|
|
11
6
|
|
|
@@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api"
|
|
|
4
4
|
|
|
5
5
|
[tool.poetry]
|
|
6
6
|
name = "mininterface"
|
|
7
|
-
version = "0.4.
|
|
7
|
+
version = "0.4.4rc2"
|
|
8
8
|
description = "A minimal access to GUI, TUI, CLI and config"
|
|
9
9
|
authors = ["Edvard Rejthar <edvard.rejthar@nic.cz>"]
|
|
10
10
|
license = "GPL-3.0-or-later"
|
|
File without changes
|
|
File without changes
|