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.
Files changed (39) hide show
  1. {mininterface-0.7.4 → mininterface-0.7.5}/PKG-INFO +10 -2
  2. {mininterface-0.7.4 → mininterface-0.7.5}/README.md +8 -0
  3. {mininterface-0.7.4 → mininterface-0.7.5}/mininterface/__init__.py +5 -5
  4. {mininterface-0.7.4 → mininterface-0.7.5}/mininterface/auxiliary.py +5 -1
  5. {mininterface-0.7.4 → mininterface-0.7.5}/mininterface/cli_parser.py +6 -0
  6. mininterface-0.7.5/mininterface/config.py +28 -0
  7. {mininterface-0.7.4 → mininterface-0.7.5}/mininterface/experimental.py +0 -4
  8. {mininterface-0.7.4 → mininterface-0.7.5}/mininterface/facet.py +6 -2
  9. {mininterface-0.7.4 → mininterface-0.7.5}/mininterface/interfaces.py +3 -0
  10. {mininterface-0.7.4 → mininterface-0.7.5}/mininterface/mininterface.py +1 -1
  11. {mininterface-0.7.4 → mininterface-0.7.5}/mininterface/showcase.py +3 -0
  12. {mininterface-0.7.4 → mininterface-0.7.5}/mininterface/textual_interface/textual_adaptor.py +1 -8
  13. {mininterface-0.7.4 → mininterface-0.7.5}/mininterface/textual_interface/textual_app.py +14 -1
  14. {mininterface-0.7.4 → mininterface-0.7.5}/mininterface/tk_interface/utils.py +5 -5
  15. {mininterface-0.7.4 → mininterface-0.7.5}/pyproject.toml +2 -2
  16. {mininterface-0.7.4 → mininterface-0.7.5}/LICENSE +0 -0
  17. {mininterface-0.7.4 → mininterface-0.7.5}/mininterface/ValidationFail.py +0 -0
  18. {mininterface-0.7.4 → mininterface-0.7.5}/mininterface/__main__.py +0 -0
  19. {mininterface-0.7.4 → mininterface-0.7.5}/mininterface/exceptions.py +0 -0
  20. {mininterface-0.7.4 → mininterface-0.7.5}/mininterface/form_dict.py +0 -0
  21. {mininterface-0.7.4 → mininterface-0.7.5}/mininterface/redirectable.py +0 -0
  22. {mininterface-0.7.4 → mininterface-0.7.5}/mininterface/start.py +0 -0
  23. {mininterface-0.7.4 → mininterface-0.7.5}/mininterface/subcommands.py +0 -0
  24. {mininterface-0.7.4 → mininterface-0.7.5}/mininterface/tag.py +0 -0
  25. {mininterface-0.7.4 → mininterface-0.7.5}/mininterface/tag_factory.py +0 -0
  26. {mininterface-0.7.4 → mininterface-0.7.5}/mininterface/text_interface.py +0 -0
  27. {mininterface-0.7.4 → mininterface-0.7.5}/mininterface/textual_interface/__init__.py +0 -0
  28. {mininterface-0.7.4 → mininterface-0.7.5}/mininterface/textual_interface/textual_button_app.py +0 -0
  29. {mininterface-0.7.4 → mininterface-0.7.5}/mininterface/textual_interface/textual_facet.py +0 -0
  30. {mininterface-0.7.4 → mininterface-0.7.5}/mininterface/textual_interface/widgets.py +0 -0
  31. {mininterface-0.7.4 → mininterface-0.7.5}/mininterface/tk_interface/__init__.py +0 -0
  32. {mininterface-0.7.4 → mininterface-0.7.5}/mininterface/tk_interface/date_entry.py +0 -0
  33. {mininterface-0.7.4 → mininterface-0.7.5}/mininterface/tk_interface/external_fix.py +0 -0
  34. {mininterface-0.7.4 → mininterface-0.7.5}/mininterface/tk_interface/redirect_text_tkinter.py +0 -0
  35. {mininterface-0.7.4 → mininterface-0.7.5}/mininterface/tk_interface/tk_facet.py +0 -0
  36. {mininterface-0.7.4 → mininterface-0.7.5}/mininterface/tk_interface/tk_window.py +0 -0
  37. {mininterface-0.7.4 → mininterface-0.7.5}/mininterface/type_stubs.py +0 -0
  38. {mininterface-0.7.4 → mininterface-0.7.5}/mininterface/types.py +0 -0
  39. {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.4
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 (>=0.84,<0.85)
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
- interface = get_interface(title, interface, env)
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
- interface.form(wrong_fields)
197
- {setattr(interface.env, k, v.val) for k, v in wrong_fields.items()}
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
- interface.form()
199
+ m.form()
200
200
 
201
- return interface
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
- return issubclass(cls, annotation)
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. """
@@ -53,7 +53,3 @@ class FacetCallback():
53
53
  """
54
54
  pass
55
55
  # NOTE, just a stub. Deprecated, use CallbackTag instead.
56
-
57
-
58
- # NOTE should we use the dataclasses, isn't that slow?
59
- MININTERFACE_CONFIG = {"gui": {"combobox_since": 5}}
@@ -19,11 +19,14 @@ if TYPE_CHECKING:
19
19
 
20
20
  @dataclass
21
21
  class Image:
22
- """ NOTE. Experimental. Undocumented. """
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
  ![Asking number](asset/ask-number.avif)
117
117
 
118
- However, when run in a non-interactive session with TUI (ex. no display), [TextInterface](Interfaces.md#TextInterface)
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 pathlib import Path, PosixPath
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, flatten_keys
12
- from ..experimental import MININTERFACE_CONFIG, FacetCallback, SubmitButton
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()) > MININTERFACE_CONFIG["gui"]["combobox_since"]:
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.4"
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 = "~0.84"
23
+ textual = "<2.0.0"
24
24
  tkinter-tooltip = "*"
25
25
  tkinter_form = "0.2.1"
26
26
  tkscrollableframe = "*"
File without changes