mininterface 0.7.4__tar.gz → 0.7.5__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.7.4 → mininterface-0.7.5}/PKG-INFO +10 -2
- {mininterface-0.7.4 → mininterface-0.7.5}/README.md +8 -0
- {mininterface-0.7.4 → mininterface-0.7.5}/mininterface/__init__.py +5 -5
- {mininterface-0.7.4 → mininterface-0.7.5}/mininterface/auxiliary.py +5 -1
- {mininterface-0.7.4 → mininterface-0.7.5}/mininterface/cli_parser.py +6 -0
- mininterface-0.7.5/mininterface/config.py +28 -0
- {mininterface-0.7.4 → mininterface-0.7.5}/mininterface/experimental.py +0 -4
- {mininterface-0.7.4 → mininterface-0.7.5}/mininterface/facet.py +6 -2
- {mininterface-0.7.4 → mininterface-0.7.5}/mininterface/interfaces.py +3 -0
- {mininterface-0.7.4 → mininterface-0.7.5}/mininterface/mininterface.py +1 -1
- {mininterface-0.7.4 → mininterface-0.7.5}/mininterface/showcase.py +3 -0
- {mininterface-0.7.4 → mininterface-0.7.5}/mininterface/textual_interface/textual_adaptor.py +1 -8
- {mininterface-0.7.4 → mininterface-0.7.5}/mininterface/textual_interface/textual_app.py +14 -1
- {mininterface-0.7.4 → mininterface-0.7.5}/mininterface/tk_interface/utils.py +5 -5
- {mininterface-0.7.4 → mininterface-0.7.5}/pyproject.toml +2 -2
- {mininterface-0.7.4 → mininterface-0.7.5}/LICENSE +0 -0
- {mininterface-0.7.4 → mininterface-0.7.5}/mininterface/ValidationFail.py +0 -0
- {mininterface-0.7.4 → mininterface-0.7.5}/mininterface/__main__.py +0 -0
- {mininterface-0.7.4 → mininterface-0.7.5}/mininterface/exceptions.py +0 -0
- {mininterface-0.7.4 → mininterface-0.7.5}/mininterface/form_dict.py +0 -0
- {mininterface-0.7.4 → mininterface-0.7.5}/mininterface/redirectable.py +0 -0
- {mininterface-0.7.4 → mininterface-0.7.5}/mininterface/start.py +0 -0
- {mininterface-0.7.4 → mininterface-0.7.5}/mininterface/subcommands.py +0 -0
- {mininterface-0.7.4 → mininterface-0.7.5}/mininterface/tag.py +0 -0
- {mininterface-0.7.4 → mininterface-0.7.5}/mininterface/tag_factory.py +0 -0
- {mininterface-0.7.4 → mininterface-0.7.5}/mininterface/text_interface.py +0 -0
- {mininterface-0.7.4 → mininterface-0.7.5}/mininterface/textual_interface/__init__.py +0 -0
- {mininterface-0.7.4 → mininterface-0.7.5}/mininterface/textual_interface/textual_button_app.py +0 -0
- {mininterface-0.7.4 → mininterface-0.7.5}/mininterface/textual_interface/textual_facet.py +0 -0
- {mininterface-0.7.4 → mininterface-0.7.5}/mininterface/textual_interface/widgets.py +0 -0
- {mininterface-0.7.4 → mininterface-0.7.5}/mininterface/tk_interface/__init__.py +0 -0
- {mininterface-0.7.4 → mininterface-0.7.5}/mininterface/tk_interface/date_entry.py +0 -0
- {mininterface-0.7.4 → mininterface-0.7.5}/mininterface/tk_interface/external_fix.py +0 -0
- {mininterface-0.7.4 → mininterface-0.7.5}/mininterface/tk_interface/redirect_text_tkinter.py +0 -0
- {mininterface-0.7.4 → mininterface-0.7.5}/mininterface/tk_interface/tk_facet.py +0 -0
- {mininterface-0.7.4 → mininterface-0.7.5}/mininterface/tk_interface/tk_window.py +0 -0
- {mininterface-0.7.4 → mininterface-0.7.5}/mininterface/type_stubs.py +0 -0
- {mininterface-0.7.4 → mininterface-0.7.5}/mininterface/types.py +0 -0
- {mininterface-0.7.4 → mininterface-0.7.5}/mininterface/validators.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: mininterface
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.5
|
|
4
4
|
Summary: A minimal access to GUI, TUI, CLI and config
|
|
5
5
|
License: GPL-3.0-or-later
|
|
6
6
|
Author: Edvard Rejthar
|
|
@@ -15,7 +15,7 @@ Classifier: Programming Language :: Python :: 3.13
|
|
|
15
15
|
Requires-Dist: autocombobox (==1.4.2)
|
|
16
16
|
Requires-Dist: humanize
|
|
17
17
|
Requires-Dist: pyyaml
|
|
18
|
-
Requires-Dist: textual (
|
|
18
|
+
Requires-Dist: textual (<2.0.0)
|
|
19
19
|
Requires-Dist: tkinter-tooltip
|
|
20
20
|
Requires-Dist: tkinter_form (==0.2.1)
|
|
21
21
|
Requires-Dist: tkscrollableframe
|
|
@@ -65,6 +65,7 @@ if __name__ == "__main__":
|
|
|
65
65
|
- [Background](#background)
|
|
66
66
|
- [Installation](#installation)
|
|
67
67
|
- [Docs](#docs)
|
|
68
|
+
- [Gallery](#gallery)
|
|
68
69
|
- [Examples](#examples)
|
|
69
70
|
|
|
70
71
|
## You got CLI
|
|
@@ -147,6 +148,13 @@ If the GUI does not work on MacOS, you might need to launch: `brew install pytho
|
|
|
147
148
|
# Docs
|
|
148
149
|
See the docs overview at [https://cz-nic.github.io/mininterface/](https://cz-nic.github.io/mininterface/Overview/).
|
|
149
150
|
|
|
151
|
+
# Gallery
|
|
152
|
+
|
|
153
|
+
These projects have the code base reduced thanks to the mininterface:
|
|
154
|
+
|
|
155
|
+
* **[deduplidog](https://github.com/CZ-NIC/deduplidog/)** – Find duplicates in a scattered directory structure
|
|
156
|
+
* **[touch-timestamp](https://github.com/CZ-NIC/touch-timestamp/)** – A powerful dialog to change the files' timestamp
|
|
157
|
+
|
|
150
158
|
# Examples
|
|
151
159
|
|
|
152
160
|
A powerful [`m.form`](https://cz-nic.github.io/mininterface/Mininterface/#mininterface.Mininterface.form) dialog method accepts either a dataclass or a dict. Take a look on both.
|
|
@@ -39,6 +39,7 @@ if __name__ == "__main__":
|
|
|
39
39
|
- [Background](#background)
|
|
40
40
|
- [Installation](#installation)
|
|
41
41
|
- [Docs](#docs)
|
|
42
|
+
- [Gallery](#gallery)
|
|
42
43
|
- [Examples](#examples)
|
|
43
44
|
|
|
44
45
|
## You got CLI
|
|
@@ -121,6 +122,13 @@ If the GUI does not work on MacOS, you might need to launch: `brew install pytho
|
|
|
121
122
|
# Docs
|
|
122
123
|
See the docs overview at [https://cz-nic.github.io/mininterface/](https://cz-nic.github.io/mininterface/Overview/).
|
|
123
124
|
|
|
125
|
+
# Gallery
|
|
126
|
+
|
|
127
|
+
These projects have the code base reduced thanks to the mininterface:
|
|
128
|
+
|
|
129
|
+
* **[deduplidog](https://github.com/CZ-NIC/deduplidog/)** – Find duplicates in a scattered directory structure
|
|
130
|
+
* **[touch-timestamp](https://github.com/CZ-NIC/touch-timestamp/)** – A powerful dialog to change the files' timestamp
|
|
131
|
+
|
|
124
132
|
# Examples
|
|
125
133
|
|
|
126
134
|
A powerful [`m.form`](https://cz-nic.github.io/mininterface/Mininterface/#mininterface.Mininterface.form) dialog method accepts either a dataclass or a dict. Take a look on both.
|
|
@@ -188,17 +188,17 @@ def run(env_or_list: Type[EnvClass] | list[Type[Command]] | None = None,
|
|
|
188
188
|
parse_cli(_Empty, None, add_verbosity, ask_for_missing, args)
|
|
189
189
|
|
|
190
190
|
# Build the interface
|
|
191
|
-
|
|
191
|
+
m = get_interface(title, interface, env)
|
|
192
192
|
|
|
193
193
|
# Empty CLI → GUI edit
|
|
194
194
|
if ask_for_missing and wrong_fields:
|
|
195
195
|
# Some fields must be set.
|
|
196
|
-
|
|
197
|
-
{setattr(
|
|
196
|
+
m.form(wrong_fields)
|
|
197
|
+
{setattr(m.env, k, v.val) for k, v in wrong_fields.items()}
|
|
198
198
|
elif ask_on_empty_cli and len(sys.argv) <= 1:
|
|
199
|
-
|
|
199
|
+
m.form()
|
|
200
200
|
|
|
201
|
-
return
|
|
201
|
+
return m
|
|
202
202
|
|
|
203
203
|
|
|
204
204
|
__all__ = ["run", "Tag", "validators", "InterfaceNotAvailable", "Cancelled",
|
|
@@ -25,6 +25,7 @@ def flatten(d: dict[str, T | dict], include_keys: Optional[Callable[[str], list]
|
|
|
25
25
|
yield v
|
|
26
26
|
|
|
27
27
|
|
|
28
|
+
# NOTE: Not used.
|
|
28
29
|
def flatten_keys(d: dict[KT, T | dict]) -> Iterable[tuple[KT, T]]:
|
|
29
30
|
""" Recursively traverse whole dict """
|
|
30
31
|
for k, v in d.items():
|
|
@@ -129,7 +130,10 @@ def subclass_matches_annotation(cls, annotation) -> bool:
|
|
|
129
130
|
return True
|
|
130
131
|
|
|
131
132
|
# simple types like scalars
|
|
132
|
-
|
|
133
|
+
try:
|
|
134
|
+
return issubclass(cls, annotation) # cls=tuple[int, str] raises an error since Python 3.13
|
|
135
|
+
except TypeError:
|
|
136
|
+
return False
|
|
133
137
|
|
|
134
138
|
|
|
135
139
|
def serialize_structure(obj):
|
|
@@ -18,7 +18,9 @@ from tyro._argparse_formatter import TyroArgumentParser
|
|
|
18
18
|
from tyro._singleton import MISSING_NONPROP
|
|
19
19
|
from tyro.extras import get_parser
|
|
20
20
|
|
|
21
|
+
|
|
21
22
|
from .auxiliary import yield_annotations
|
|
23
|
+
from .config import MininterfaceConfig, Config
|
|
22
24
|
from .form_dict import EnvClass, MissingTagValue
|
|
23
25
|
from .tag import Tag
|
|
24
26
|
from .tag_factory import tag_factory
|
|
@@ -251,6 +253,10 @@ def parse_cli(env_or_list: Type[EnvClass] | list[Type[EnvClass]],
|
|
|
251
253
|
# Undocumented feature. User put a namespace into kwargs["default"]
|
|
252
254
|
# that already serves for defaults. We do not fetch defaults yet from a config file.
|
|
253
255
|
disk = yaml.safe_load(config_file.read_text()) or {} # empty file is ok
|
|
256
|
+
if mininterface := disk.pop("mininterface", None):
|
|
257
|
+
# Section 'mininterface' in the config file changes the global configuration.
|
|
258
|
+
for key, value in vars(_create_with_missing(MininterfaceConfig, mininterface)).items():
|
|
259
|
+
setattr(Config, key, value)
|
|
254
260
|
kwargs["default"] = _create_with_missing(env, disk)
|
|
255
261
|
|
|
256
262
|
# Load configuration from CLI
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Configuration used by all minterfaces in the program.
|
|
2
|
+
# Might be changed by a 'mininterface' section in a config file.
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import Literal
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dataclass
|
|
8
|
+
class Gui:
|
|
9
|
+
combobox_since: int = 5
|
|
10
|
+
""" The threshold to switch from radio buttons to a combobox. """
|
|
11
|
+
test: bool = False
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass
|
|
15
|
+
class Tui:
|
|
16
|
+
...
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass # (slots=True)
|
|
20
|
+
class MininterfaceConfig:
|
|
21
|
+
gui: Gui
|
|
22
|
+
tui: Tui
|
|
23
|
+
interface: Literal["gui"] | Literal["tui"] | None = None
|
|
24
|
+
""" Enforce an interface. By default, we choose automatically. """
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
Config = MininterfaceConfig(Gui(), Tui())
|
|
28
|
+
""" Global configuration singleton to be accessed by all minterfaces. """
|
|
@@ -19,11 +19,14 @@ if TYPE_CHECKING:
|
|
|
19
19
|
|
|
20
20
|
@dataclass
|
|
21
21
|
class Image:
|
|
22
|
-
""" NOTE. Experimental.
|
|
22
|
+
""" NOTE. Experimental. """
|
|
23
|
+
|
|
23
24
|
src: str | Path
|
|
25
|
+
""" Src to the image. """
|
|
24
26
|
|
|
25
27
|
|
|
26
28
|
LayoutElement = TypeVar("LayoutElement", str, Image, Path, "Self")
|
|
29
|
+
""" Either a string, Path or facet.Image. """
|
|
27
30
|
|
|
28
31
|
|
|
29
32
|
class BackendAdaptor(ABC):
|
|
@@ -102,7 +105,7 @@ class Facet(Generic[EnvClass]):
|
|
|
102
105
|
print("Title", text)
|
|
103
106
|
|
|
104
107
|
def _layout(self, elements: list[LayoutElement]):
|
|
105
|
-
""" Experimental. """
|
|
108
|
+
""" Experimental. Input is a list of `LayoutElements`."""
|
|
106
109
|
# NOTE remove warn when working in textual
|
|
107
110
|
warn("Facet layout not implemented for this interface.")
|
|
108
111
|
|
|
@@ -120,6 +123,7 @@ class Facet(Generic[EnvClass]):
|
|
|
120
123
|
"My choice": Tag(choices=["one", "two"], on_change=callback)
|
|
121
124
|
})
|
|
122
125
|
# continue here immediately after clicking on a radio button
|
|
126
|
+
```
|
|
123
127
|
|
|
124
128
|
"""
|
|
125
129
|
self.adaptor.post_submit_action = _post_submit
|
|
@@ -3,6 +3,8 @@ from importlib import import_module
|
|
|
3
3
|
import sys
|
|
4
4
|
from typing import Literal, Type
|
|
5
5
|
|
|
6
|
+
|
|
7
|
+
from .config import Config
|
|
6
8
|
from .mininterface import Mininterface
|
|
7
9
|
from .exceptions import InterfaceNotAvailable
|
|
8
10
|
from .text_interface import TextInterface
|
|
@@ -38,6 +40,7 @@ def __getattr__(name):
|
|
|
38
40
|
|
|
39
41
|
def get_interface(title="", interface: InterfaceType = None, env=None):
|
|
40
42
|
args = title, env
|
|
43
|
+
interface = interface or Config.interface
|
|
41
44
|
if isinstance(interface, type) and issubclass(interface, Mininterface):
|
|
42
45
|
# the user gave a specific interface, let them catch InterfaceNotAvailable then
|
|
43
46
|
return interface(*args)
|
|
@@ -115,7 +115,7 @@ class Mininterface(Generic[EnvClass]):
|
|
|
115
115
|
|
|
116
116
|

|
|
117
117
|
|
|
118
|
-
However, when run in a non-interactive session with TUI (ex. no display), [TextInterface](Interfaces.md#
|
|
118
|
+
However, when run in a non-interactive session with TUI (ex. no display), [TextInterface](Interfaces.md#textinterface)
|
|
119
119
|
is used which is able to turn it into an interactive one.
|
|
120
120
|
|
|
121
121
|
```python3
|
|
@@ -26,15 +26,18 @@ class Subcommand1(SharedArgs):
|
|
|
26
26
|
my_local: int = 1
|
|
27
27
|
|
|
28
28
|
def run(self):
|
|
29
|
+
print("Subcommand 1 clicked")
|
|
29
30
|
print("Common:", self.common) # user input
|
|
30
31
|
print("Number:", self.my_local) # 1 or user input
|
|
31
32
|
print("Internal:", self.internal)
|
|
33
|
+
print("The submit button blocked!")
|
|
32
34
|
raise ValidationFail("The submit button blocked!")
|
|
33
35
|
|
|
34
36
|
|
|
35
37
|
@dataclass
|
|
36
38
|
class Subcommand2(SharedArgs):
|
|
37
39
|
def run(self):
|
|
40
|
+
print("Subcommand 2 clicked")
|
|
38
41
|
self._facet.set_title("Button clicked") # you can access internal self._facet: Facet
|
|
39
42
|
print("Common files", self.files)
|
|
40
43
|
|
|
@@ -82,18 +82,11 @@ class TextualAdaptor(BackendAdaptor):
|
|
|
82
82
|
if title:
|
|
83
83
|
app.title = title
|
|
84
84
|
|
|
85
|
-
widgets: WidgetList = [f for f in flatten(formdict_to_widgetdict(
|
|
86
|
-
form, self.widgetize), include_keys=self.header)]
|
|
87
|
-
if len(widgets) and isinstance(widgets[0], Rule):
|
|
88
|
-
# there are multiple sections in the list, <hr>ed by Rule elements. However, the first takes much space.
|
|
89
|
-
widgets.pop(0)
|
|
90
|
-
app.widgets = widgets
|
|
91
|
-
|
|
92
85
|
if not app.run():
|
|
93
86
|
raise Cancelled
|
|
94
87
|
|
|
95
88
|
# validate and store the UI value → Tag value → original value
|
|
96
|
-
vals = ((field._link, field.get_ui_value()) for field in widgets if hasattr(field, "_link"))
|
|
89
|
+
vals = ((field._link, field.get_ui_value()) for field in app.widgets if hasattr(field, "_link"))
|
|
97
90
|
if not Tag._submit_values(vals) or not self.submit_done():
|
|
98
91
|
return self.run_dialog(form, title, submit)
|
|
99
92
|
|
|
@@ -5,12 +5,15 @@ from textual.binding import Binding
|
|
|
5
5
|
from textual.containers import VerticalScroll
|
|
6
6
|
from textual.widget import Widget
|
|
7
7
|
from textual.widgets import (Checkbox, Footer, Header, Input, Label,
|
|
8
|
-
RadioButton, Static)
|
|
8
|
+
RadioButton, Static, Rule)
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
from .widgets import (Changeable, MyButton, MyCheckbox, MyInput, MyRadioSet,
|
|
12
12
|
MySubmitButton)
|
|
13
13
|
|
|
14
|
+
from ..form_dict import formdict_to_widgetdict
|
|
15
|
+
|
|
16
|
+
from ..auxiliary import flatten
|
|
14
17
|
from ..facet import BackendAdaptor
|
|
15
18
|
|
|
16
19
|
if TYPE_CHECKING:
|
|
@@ -52,6 +55,16 @@ class TextualApp(App[bool | None]):
|
|
|
52
55
|
self.bind("escape", "exit", description="Cancel")
|
|
53
56
|
|
|
54
57
|
def compose(self) -> ComposeResult:
|
|
58
|
+
# prepare widgets
|
|
59
|
+
# since textual 1.0.0 we have to build widgets not earlier than the context app is ready
|
|
60
|
+
self.widgets = list(flatten(formdict_to_widgetdict(
|
|
61
|
+
self.adaptor.facet._form, self.adaptor.widgetize), include_keys=self.adaptor.header))
|
|
62
|
+
|
|
63
|
+
# there are multiple sections in the list, <hr>ed by Rule elements. However, the first takes much space.
|
|
64
|
+
if len(self.widgets) and isinstance(self.widgets[0], Rule):
|
|
65
|
+
self.widgets.pop(0)
|
|
66
|
+
|
|
67
|
+
# start yielding widgets
|
|
55
68
|
if self.title:
|
|
56
69
|
yield Header()
|
|
57
70
|
yield self.output # NOTE not used
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
from
|
|
2
|
-
from tkinter import Button, Entry, Label, TclError, Variable, Widget, Spinbox
|
|
1
|
+
from tkinter import Button, Entry, TclError, Variable, Widget, Spinbox
|
|
3
2
|
from tkinter.filedialog import askopenfilename, askopenfilenames
|
|
4
3
|
from tkinter.ttk import Checkbutton, Combobox, Frame, Radiobutton, Widget
|
|
5
4
|
from typing import TYPE_CHECKING
|
|
@@ -8,8 +7,9 @@ from autocombobox import AutoCombobox
|
|
|
8
7
|
|
|
9
8
|
from tkinter_form.tkinter_form import Form, FieldForm
|
|
10
9
|
|
|
11
|
-
from ..auxiliary import flatten
|
|
12
|
-
from ..
|
|
10
|
+
from ..auxiliary import flatten
|
|
11
|
+
from ..config import Config
|
|
12
|
+
from ..experimental import FacetCallback, SubmitButton
|
|
13
13
|
from ..form_dict import TagDict
|
|
14
14
|
from ..tag import Tag
|
|
15
15
|
from ..types import DatetimeTag, PathTag
|
|
@@ -113,7 +113,7 @@ def replace_widgets(tk_app: "TkWindow", nested_widgets, form: TagDict):
|
|
|
113
113
|
nested_frame = Frame(master)
|
|
114
114
|
nested_frame.grid(row=grid_info['row'], column=grid_info['column'])
|
|
115
115
|
|
|
116
|
-
if len(tag._get_choices())
|
|
116
|
+
if len(tag._get_choices()) >= Config.gui.combobox_since:
|
|
117
117
|
widget = AutoCombobox(nested_frame, textvariable=variable)
|
|
118
118
|
widget['values'] = list(tag._get_choices())
|
|
119
119
|
widget.pack()
|
|
@@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api"
|
|
|
4
4
|
|
|
5
5
|
[tool.poetry]
|
|
6
6
|
name = "mininterface"
|
|
7
|
-
version = "0.7.
|
|
7
|
+
version = "0.7.5"
|
|
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"
|
|
@@ -20,7 +20,7 @@ pyyaml = "*"
|
|
|
20
20
|
# Standard requirements
|
|
21
21
|
autocombobox = "1.4.2"
|
|
22
22
|
humanize = "*" # used only in the TkInterface, hence it is not a minimal requirement
|
|
23
|
-
textual = "
|
|
23
|
+
textual = "<2.0.0"
|
|
24
24
|
tkinter-tooltip = "*"
|
|
25
25
|
tkinter_form = "0.2.1"
|
|
26
26
|
tkscrollableframe = "*"
|
|
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-0.7.4 → mininterface-0.7.5}/mininterface/textual_interface/textual_button_app.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mininterface-0.7.4 → mininterface-0.7.5}/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
|