Skip to content

Commit 7defc95

Browse files
committed
gui descriptions back to the bottom
1 parent 2954a93 commit 7defc95

File tree

5 files changed

+114
-32
lines changed

5 files changed

+114
-32
lines changed

docs/index.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,10 @@ pip install --no-dependencies mininterface
114114
pip install tyro typing_extensions pyyaml
115115
```
116116

117+
## MacOS GUI
118+
119+
If the GUI does not work on MacOS, you might need to launch: `brew install python-tk`
120+
117121
# Docs
118122
See the docs overview at [https://cz-nic.github.io/mininterface/](https://cz-nic.github.io/mininterface/Overview/).
119123

mininterface/cli_parser.py

Lines changed: 33 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ def run_tyro_parser(env_or_list: Type[EnvClass] | list[Type[EnvClass]],
150150
if ask_for_missing and getattr(e, "code", None) == 2 and eavesdrop:
151151
# Some required arguments are missing. Determine which.
152152
wf = {}
153-
for arg in eavesdrop.partition(":")[2].strip().split(", "):
153+
for arg in _fetch_eavesdrop_args():
154154
treat_missing(type_form, kwargs, parser, wf, arg)
155155

156156
# Second attempt to parse CLI
@@ -195,7 +195,6 @@ def treat_missing(env_class, kwargs: dict, parser: ArgumentParser, wf: dict, arg
195195
# However, the UI then is not able to use ex. the number filtering capabilities.
196196
# Putting there None is not a good idea as dataclass_to_tagdict fails if None is not allowed by the annotation.
197197
tag = wf[field_name] = tag_factory(MissingTagValue(),
198-
# tag = wf[field_name] = tag_factory(MISSING,
199198
argument.help.replace("(required)", ""),
200199
validation=not_empty,
201200
_src_class=env_class,
@@ -205,9 +204,17 @@ def treat_missing(env_class, kwargs: dict, parser: ArgumentParser, wf: dict, arg
205204
# A None would be enough because Mininterface will ask for the missing values
206205
# promply, however, Pydantic model would fail.
207206
# As it serves only for tyro parsing and the field is marked wrong, the made up value is never used or seen.
208-
if "default" not in kwargs:
209-
kwargs["default"] = SimpleNamespace()
210-
setattr(kwargs["default"], field_name, tag._make_default_value())
207+
set_default(kwargs, field_name, tag._make_default_value())
208+
209+
210+
def _fetch_eavesdrop_args():
211+
return eavesdrop.partition(":")[2].strip().split(", ")
212+
213+
214+
def set_default(kwargs, field_name, val):
215+
if "default" not in kwargs:
216+
kwargs["default"] = SimpleNamespace()
217+
setattr(kwargs["default"], field_name, val)
211218

212219

213220
def _parse_cli(env_or_list: Type[EnvClass] | list[Type[EnvClass]],
@@ -228,45 +235,49 @@ def _parse_cli(env_or_list: Type[EnvClass] | list[Type[EnvClass]],
228235
Returns:
229236
Configuration namespace.
230237
"""
238+
if isinstance(env_or_list, list):
239+
subcommands, env = env_or_list, None
240+
else:
241+
subcommands, env = None, env_or_list
242+
231243
# Load config file
232-
if config_file and isinstance(env_or_list, list):
233-
# NOTE. Reading config files when using subcommands is not implemented.
244+
if config_file and subcommands:
245+
# Reading config files when using subcommands is not implemented.
234246
static = {}
235247
kwargs["default"] = None
236248
warnings.warn(f"Config file {config_file} is ignored because subcommands are used."
237-
"It is not easy to set who this should work. "
238-
"Describe the developer your usecase so that they might implement this.")
239-
if "default" not in kwargs and not isinstance(env_or_list, list):
249+
" It is not easy to set how this should work."
250+
" Describe the developer your usecase so that they might implement this.")
251+
if "default" not in kwargs and not subcommands:
240252
# Undocumented feature. User put a namespace into kwargs["default"]
241253
# that already serves for defaults. We do not fetch defaults yet from a config file.
242254
disk = {}
243255
if config_file:
244256
disk = yaml.safe_load(config_file.read_text()) or {} # empty file is ok
245257
# Nested dataclasses have to be properly initialized. YAML gave them as dicts only.
246258
for key in (key for key, val in disk.items() if isinstance(val, dict)):
247-
disk[key] = env_or_list.__annotations__[key](**disk[key])
259+
disk[key] = env.__annotations__[key](**disk[key])
248260

249261
# Fill default fields
250-
if pydantic and issubclass(env_or_list, BaseModel):
262+
if pydantic and issubclass(env, BaseModel):
251263
# Unfortunately, pydantic needs to fill the default with the actual values,
252264
# the default value takes the precedence over the hard coded one, even if missing.
253-
static = {key: env_or_list.model_fields.get(key).default
254-
for ann in yield_annotations(env_or_list) for key in ann if not key.startswith("__") and not key in disk}
255-
# static = {key: env_or_list.model_fields.get(key).default
256-
# for key, _ in iterate_attributes(env_or_list) if not key in disk}
257-
elif attr and attr.has(env_or_list):
265+
static = {key: env.model_fields.get(key).default
266+
for ann in yield_annotations(env) for key in ann if not key.startswith("__") and not key in disk}
267+
# static = {key: env_.model_fields.get(key).default
268+
# for key, _ in iterate_attributes(env_) if not key in disk}
269+
elif attr and attr.has(env):
258270
# Unfortunately, attrs needs to fill the default with the actual values,
259271
# the default value takes the precedence over the hard coded one, even if missing.
260272
# NOTE Might not work for inherited models.
261273
static = {key: field.default
262-
for key, field in attr.fields_dict(env_or_list).items() if not key.startswith("__") and not key in disk}
274+
for key, field in attr.fields_dict(env).items() if not key.startswith("__") and not key in disk}
263275
else:
264276
# To ensure the configuration file does not need to contain all keys, we have to fill in the missing ones.
265277
# Otherwise, tyro will spawn warnings about missing fields.
266278
static = {key: val
267-
for key, val in yield_defaults(env_or_list) if not key.startswith("__") and not key in disk}
268-
kwargs["default"] = SimpleNamespace(**(disk | static))
279+
for key, val in yield_defaults(env) if not key.startswith("__") and not key in disk}
280+
kwargs["default"] = SimpleNamespace(**(static | disk))
269281

270282
# Load configuration from CLI
271-
env, wrong_fields = run_tyro_parser(env_or_list, kwargs, add_verbosity, ask_for_missing, args)
272-
return env, wrong_fields
283+
return run_tyro_parser(subcommands or env, kwargs, add_verbosity, ask_for_missing, args)
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
# The purpose of the file is to put the descriptions to the bottom of the widgets as it was in the former version of the tkinter_form.
2+
from tkinter import ttk
3+
4+
from tkinter_form import Form, Value, FieldForm
5+
6+
orig = Form._Form__create_widgets
7+
8+
9+
def __create_widgets_monkeypatched(
10+
self, form_dict: dict, name_config: str, button_command: callable
11+
) -> None:
12+
"""
13+
Create form widgets
14+
15+
Args:
16+
form_dict (dict): form dict base
17+
name_config (str): name_config
18+
button (bool): button_config
19+
"""
20+
21+
index = 0
22+
for _, (name_key, value) in enumerate(form_dict.items()):
23+
index += 1
24+
description = None
25+
if isinstance(value, Value):
26+
value, description = value.val, value.description
27+
28+
self.rowconfigure(index, weight=1)
29+
30+
if isinstance(value, dict):
31+
widget = Form(self, name_key, value)
32+
widget.grid(row=index, column=0, columnspan=3, sticky="nesw")
33+
34+
self.fields[name_key] = widget
35+
last_index = index
36+
continue
37+
38+
variable = self._Form__type_vars[type(value)]()
39+
widget = self._Form__type_widgets[type(value)](self)
40+
41+
self.columnconfigure(1, weight=1)
42+
widget.grid(row=index, column=1, sticky="nesw", padx=2, pady=2)
43+
label = ttk.Label(self, text=name_key)
44+
self.columnconfigure(0, weight=1)
45+
label.grid(row=index, column=0, sticky="nes", padx=2, pady=2)
46+
47+
# Add a further description to the row below the widget
48+
description_label = None
49+
if not description is None:
50+
index += 1
51+
description_label = ttk.Label(self, text=description)
52+
description_label.grid(row=index, column=1, columnspan=2, sticky="nesw", padx=2, pady=2)
53+
54+
self.fields[name_key] = FieldForm(
55+
master=self,
56+
label=label,
57+
widget=widget,
58+
variable=variable,
59+
value=value,
60+
description=description_label,
61+
)
62+
63+
last_index = index
64+
65+
if button_command:
66+
self._Form__command = button_command
67+
self.button = ttk.Button(
68+
self, text=name_config, command=self._Form__command_button
69+
)
70+
self.button.grid(row=last_index + 1, column=0, columnspan=3, sticky="nesw")
71+
72+
73+
Form._Form__create_widgets = __create_widgets_monkeypatched

mininterface/tk_interface/utils.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
from ..tag import Tag
1515
from ..types import DatetimeTag, PathTag
1616
from .date_entry import DateEntryFrame
17+
from .external_fix import __create_widgets_monkeypatched
1718

1819
if TYPE_CHECKING:
1920
from tk_window import TkWindow

mininterface/types.py

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -159,10 +159,7 @@ def __post_init__(self):
159159
@dataclass(repr=False)
160160
class DatetimeTag(Tag):
161161
"""
162-
!!! warning
163-
Experimental. Still in development.
164-
165-
Datetime is supported.
162+
Datetime, date and time types are supported.
166163
167164
```python3
168165
from datetime import datetime
@@ -214,13 +211,12 @@ class Env:
214211
# ![Time only](asset/datetime_time.avif)
215212

216213
# NOTE: It would be nice we might put any date format to be parsed.
217-
# NOTE: The parameters are still ignored.
218214

219215
date: bool = False
220-
""" The date part is active """
216+
""" The date part is active. True for datetime and date. """
221217

222218
time: bool = False
223-
""" The time part is active """
219+
""" The time part is active. True for datetime and time. """
224220

225221
full_precision: bool = False
226222
""" Include full time precison, seconds, microseconds. """
@@ -230,9 +226,6 @@ def __post_init__(self):
230226
if self.annotation:
231227
self.date = issubclass(self.annotation, date)
232228
self.time = issubclass(self.annotation, time) or issubclass(self.annotation, datetime)
233-
# NOTE: remove
234-
# if not self.time and self.full_precision:
235-
# self.full_precision = False
236229

237230
def _make_default_value(self):
238231
return datetime.now()

0 commit comments

Comments
 (0)