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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: mininterface
3
- Version: 0.4.3
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
- ![Hello world example: GUI window](asset/hello-world.png "A minimal use case – GUI")
31
- ![Hello world example: TUI fallback](asset/hello-tui.webp "A minimal use case – TUI fallback")
30
+ ![Hello world example: GUI window](https://github.com/CZ-NIC/mininterface/blob/main/asset/hello-world.png?raw=True "A minimal use case – GUI")
31
+ ![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")
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
- """My testing flag"""
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: Config = run(Config, prog="My application").get_args()
49
- print(args.important_number) # suggested by the IDE with the hint text "This number is very important"
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
- It's 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.
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 get several useful methods to handle user dialogues. Here we bound the interface to a `with` statement that redirects stdout directly to the window.
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
- ![Small window with the text 'Your important number'](asset/hello-with-statement.webp "With statement to redirect the output")
76
- ![The same in terminal'](asset/hello-with-statement-tui.webp "With statement in TUI fallback")
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
+ ![Small window with the text 'Your important number'](https://github.com/CZ-NIC/mininterface/blob/main/asset/hello-with-statement.webp?raw=True "With statement to redirect the output")
83
+ ![The same in terminal'](https://github.com/CZ-NIC/mininterface/blob/main/asset/hello-with-statement-tui.webp?raw=True "With statement in TUI fallback")
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(self, text: str)`](#alertself-text-str)
93
- + [`ask(self, text: str) -> str`](#askself-text-str---str)
94
- + [`ask_args(self) -> ~ConfigInstance`](#ask_argsself---configinstance)
95
- + [`ask_form(self, args: FormDict, title="") -> int`](#ask_formself-args-formdict-title---dict)
96
- + [`ask_number(self, text: str) -> int`](#ask_numberself-text-str---int)
97
- + [`get_args(self, ask_on_empty_cli=True) -> ~ConfigInstance`](#get_argsself-ask_on_empty_clitrue---configinstance)
98
- + [`is_no(self, text: str) -> bool`](#is_noself-text-str---bool)
99
- + [`is_yes(self, text: str) -> bool`](#is_yesself-text-str---bool)
100
- + [`parse_args(self, config: Callable[..., ~ConfigInstance], config_file: pathlib.Path | None = None, **kwargs) -> ~ConfigInstance`](#parse_argsself-config-callable-configinstance-config_file-pathlibpath--none--none-kwargs---configinstance)
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:ConfigClass`: Dataclass with the configuration.
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(self, text: str)`
193
+ ### `alert(text: str)`
192
194
  Prompt the user to confirm the text.
193
- ### `ask(self, text: str) -> str`
195
+ ### `ask(text: str) -> str`
194
196
  Prompt the user to input a text.
195
- ### `ask_args(self) -> ~ConfigInstance`
197
+ ### `ask_args() -> ConfigInstance`
196
198
  Allow the user to edit whole configuration. (Previously fetched from CLI and config file by parse_args.)
197
- ### `ask_form(self, args: FormDict, title="") -> dict`
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(self, text: str) -> int`
206
+ ### `ask_number(text: str) -> int`
205
207
  Prompt the user to input a number. Empty input = 0.
206
- ### `get_args(self, ask_on_empty_cli=True) -> ~ConfigInstance`
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(self, text: str) -> bool`
211
+ ### `is_no(text: str) -> bool`
210
212
  Display confirm box, focusing no.
211
- ### `is_yes(self, text: str) -> bool`
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(self, config: Callable[..., ~ConfigInstance], config_file: pathlib.Path | None = None, **kwargs) -> ~ConfigInstance`
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
- ![Hello world example: GUI window](asset/hello-world.png "A minimal use case – GUI")
8
- ![Hello world example: TUI fallback](asset/hello-tui.webp "A minimal use case – TUI fallback")
7
+ ![Hello world example: GUI window](https://github.com/CZ-NIC/mininterface/blob/main/asset/hello-world.png?raw=True "A minimal use case – GUI")
8
+ ![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")
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
- """My testing flag"""
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: Config = run(Config, prog="My application").get_args()
26
- print(args.important_number) # suggested by the IDE with the hint text "This number is very important"
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
- It's 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.
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 get several useful methods to handle user dialogues. Here we bound the interface to a `with` statement that redirects stdout directly to the window.
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
- ![Small window with the text 'Your important number'](asset/hello-with-statement.webp "With statement to redirect the output")
53
- ![The same in terminal'](asset/hello-with-statement-tui.webp "With statement in TUI fallback")
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
+ ![Small window with the text 'Your important number'](https://github.com/CZ-NIC/mininterface/blob/main/asset/hello-with-statement.webp?raw=True "With statement to redirect the output")
60
+ ![The same in terminal'](https://github.com/CZ-NIC/mininterface/blob/main/asset/hello-with-statement-tui.webp?raw=True "With statement in TUI fallback")
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(self, text: str)`](#alertself-text-str)
70
- + [`ask(self, text: str) -> str`](#askself-text-str---str)
71
- + [`ask_args(self) -> ~ConfigInstance`](#ask_argsself---configinstance)
72
- + [`ask_form(self, args: FormDict, title="") -> int`](#ask_formself-args-formdict-title---dict)
73
- + [`ask_number(self, text: str) -> int`](#ask_numberself-text-str---int)
74
- + [`get_args(self, ask_on_empty_cli=True) -> ~ConfigInstance`](#get_argsself-ask_on_empty_clitrue---configinstance)
75
- + [`is_no(self, text: str) -> bool`](#is_noself-text-str---bool)
76
- + [`is_yes(self, text: str) -> bool`](#is_yesself-text-str---bool)
77
- + [`parse_args(self, config: Callable[..., ~ConfigInstance], config_file: pathlib.Path | None = None, **kwargs) -> ~ConfigInstance`](#parse_argsself-config-callable-configinstance-config_file-pathlibpath--none--none-kwargs---configinstance)
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:ConfigClass`: Dataclass with the configuration.
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(self, text: str)`
170
+ ### `alert(text: str)`
169
171
  Prompt the user to confirm the text.
170
- ### `ask(self, text: str) -> str`
172
+ ### `ask(text: str) -> str`
171
173
  Prompt the user to input a text.
172
- ### `ask_args(self) -> ~ConfigInstance`
174
+ ### `ask_args() -> ConfigInstance`
173
175
  Allow the user to edit whole configuration. (Previously fetched from CLI and config file by parse_args.)
174
- ### `ask_form(self, args: FormDict, title="") -> dict`
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(self, text: str) -> int`
183
+ ### `ask_number(text: str) -> int`
182
184
  Prompt the user to input a number. Empty input = 0.
183
- ### `get_args(self, ask_on_empty_cli=True) -> ~ConfigInstance`
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(self, text: str) -> bool`
188
+ ### `is_no(text: str) -> bool`
187
189
  Display confirm box, focusing no.
188
- ### `is_yes(self, text: str) -> bool`
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(self, config: Callable[..., ~ConfigInstance], config_file: pathlib.Path | None = None, **kwargs) -> ~ConfigInstance`
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 = Callable[..., ConfigInstance]
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: ConfigClass, kwargs: dict, parser: ArgumentParser):
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(", "):
@@ -9,6 +9,7 @@ if TYPE_CHECKING:
9
9
  try:
10
10
  from tkinter_form import Value
11
11
  except ImportError:
12
+ # TODO put into GuiInterface create_ui(ff: FormField)
12
13
  @dataclass
13
14
  class Value:
14
15
  """ This class helps to enrich the field with a description. """
@@ -57,7 +57,7 @@ class GuiInterface(Mininterface):
57
57
  self.window.run_dialog(formDict)
58
58
  return self.args
59
59
 
60
- def ask_form(self, form: FormDict, title: str = "") -> dict:
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 ask_form(self, data: FormDict, title: str = "") -> dict:
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: ConfigClass,
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. You do not have to re-define all the settings, you can choose a few.
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.ask_form(self.args)
29
+ return self.form(self.args)
30
30
 
31
- def ask_form(self, form: FormDict) -> dict:
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.ask_form({text: ""})[text]
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 ask_form(self, form: FormDict, title: str = "") -> dict:
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 ConfigClass, ConfigInstance, Mininterface
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: ConfigClass | None = None,
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.3"
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"