mininterface 0.7.1__tar.gz → 0.7.2__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 (37) hide show
  1. {mininterface-0.7.1 → mininterface-0.7.2}/PKG-INFO +4 -5
  2. {mininterface-0.7.1 → mininterface-0.7.2}/README.md +1 -1
  3. {mininterface-0.7.1 → mininterface-0.7.2}/mininterface/tag.py +2 -2
  4. {mininterface-0.7.1 → mininterface-0.7.2}/mininterface/tag_factory.py +5 -1
  5. {mininterface-0.7.1 → mininterface-0.7.2}/mininterface/tk_interface/date_entry.py +134 -64
  6. {mininterface-0.7.1 → mininterface-0.7.2}/mininterface/tk_interface/tk_window.py +3 -4
  7. {mininterface-0.7.1 → mininterface-0.7.2}/mininterface/tk_interface/utils.py +15 -12
  8. {mininterface-0.7.1 → mininterface-0.7.2}/mininterface/types.py +4 -4
  9. {mininterface-0.7.1 → mininterface-0.7.2}/pyproject.toml +5 -6
  10. {mininterface-0.7.1 → mininterface-0.7.2}/LICENSE +0 -0
  11. {mininterface-0.7.1 → mininterface-0.7.2}/mininterface/ValidationFail.py +0 -0
  12. {mininterface-0.7.1 → mininterface-0.7.2}/mininterface/__init__.py +0 -0
  13. {mininterface-0.7.1 → mininterface-0.7.2}/mininterface/__main__.py +0 -0
  14. {mininterface-0.7.1 → mininterface-0.7.2}/mininterface/auxiliary.py +0 -0
  15. {mininterface-0.7.1 → mininterface-0.7.2}/mininterface/cli_parser.py +0 -0
  16. {mininterface-0.7.1 → mininterface-0.7.2}/mininterface/exceptions.py +0 -0
  17. {mininterface-0.7.1 → mininterface-0.7.2}/mininterface/experimental.py +0 -0
  18. {mininterface-0.7.1 → mininterface-0.7.2}/mininterface/facet.py +0 -0
  19. {mininterface-0.7.1 → mininterface-0.7.2}/mininterface/form_dict.py +0 -0
  20. {mininterface-0.7.1 → mininterface-0.7.2}/mininterface/interfaces.py +0 -0
  21. {mininterface-0.7.1 → mininterface-0.7.2}/mininterface/mininterface.py +0 -0
  22. {mininterface-0.7.1 → mininterface-0.7.2}/mininterface/redirectable.py +0 -0
  23. {mininterface-0.7.1 → mininterface-0.7.2}/mininterface/showcase.py +0 -0
  24. {mininterface-0.7.1 → mininterface-0.7.2}/mininterface/start.py +0 -0
  25. {mininterface-0.7.1 → mininterface-0.7.2}/mininterface/subcommands.py +0 -0
  26. {mininterface-0.7.1 → mininterface-0.7.2}/mininterface/text_interface.py +0 -0
  27. {mininterface-0.7.1 → mininterface-0.7.2}/mininterface/textual_interface/__init__.py +0 -0
  28. {mininterface-0.7.1 → mininterface-0.7.2}/mininterface/textual_interface/textual_adaptor.py +0 -0
  29. {mininterface-0.7.1 → mininterface-0.7.2}/mininterface/textual_interface/textual_app.py +0 -0
  30. {mininterface-0.7.1 → mininterface-0.7.2}/mininterface/textual_interface/textual_button_app.py +0 -0
  31. {mininterface-0.7.1 → mininterface-0.7.2}/mininterface/textual_interface/textual_facet.py +0 -0
  32. {mininterface-0.7.1 → mininterface-0.7.2}/mininterface/textual_interface/widgets.py +0 -0
  33. {mininterface-0.7.1 → mininterface-0.7.2}/mininterface/tk_interface/__init__.py +0 -0
  34. {mininterface-0.7.1 → mininterface-0.7.2}/mininterface/tk_interface/redirect_text_tkinter.py +0 -0
  35. {mininterface-0.7.1 → mininterface-0.7.2}/mininterface/tk_interface/tk_facet.py +0 -0
  36. {mininterface-0.7.1 → mininterface-0.7.2}/mininterface/type_stubs.py +0 -0
  37. {mininterface-0.7.1 → mininterface-0.7.2}/mininterface/validators.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: mininterface
3
- Version: 0.7.1
3
+ Version: 0.7.2
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
@@ -14,15 +14,14 @@ Classifier: Programming Language :: Python :: 3.11
14
14
  Classifier: Programming Language :: Python :: 3.12
15
15
  Classifier: Programming Language :: Python :: 3.13
16
16
  Provides-Extra: all
17
- Provides-Extra: img
17
+ Provides-Extra: gui
18
18
  Provides-Extra: web
19
19
  Requires-Dist: autocombobox (==1.4.2)
20
20
  Requires-Dist: humanize
21
21
  Requires-Dist: pyyaml
22
22
  Requires-Dist: textual (>=0.84,<0.85)
23
- Requires-Dist: tkcalendar
24
23
  Requires-Dist: tkinter-tooltip
25
- Requires-Dist: tkinter_form (==0.1.5.2)
24
+ Requires-Dist: tkinter_form (==0.2.1)
26
25
  Requires-Dist: tkscrollableframe
27
26
  Requires-Dist: typing_extensions
28
27
  Requires-Dist: tyro (==0.8.14)
@@ -132,7 +131,7 @@ The config variables needed by your program are kept in cozy dataclasses. Write
132
131
  Install with a single command from [PyPi](https://pypi.org/project/mininterface/).
133
132
 
134
133
  ```bash
135
- pip install mininterface
134
+ pip install mininterface[all] # GPLv3 and compatible
136
135
  ```
137
136
 
138
137
  ## Minimal installation
@@ -102,7 +102,7 @@ The config variables needed by your program are kept in cozy dataclasses. Write
102
102
  Install with a single command from [PyPi](https://pypi.org/project/mininterface/).
103
103
 
104
104
  ```bash
105
- pip install mininterface
105
+ pip install mininterface[all] # GPLv3 and compatible
106
106
  ```
107
107
 
108
108
  ## Minimal installation
@@ -1,6 +1,6 @@
1
1
  from ast import literal_eval
2
2
  from dataclasses import dataclass, fields
3
- from datetime import datetime
3
+ from datetime import date, time
4
4
  from enum import Enum
5
5
  from types import FunctionType, MethodType, NoneType, UnionType
6
6
  from typing import TYPE_CHECKING, Any, Callable, Iterable, Optional, Type, TypeVar, Union, get_args, get_origin
@@ -683,7 +683,7 @@ class Tag:
683
683
  except (SyntaxError, ValueError):
684
684
  self.set_error_text(f"Not a valid {self._repr_annotation()}")
685
685
  return False
686
- elif self.annotation is datetime:
686
+ elif self._is_subclass((time, date)):
687
687
  try:
688
688
  out_value = self.annotation.fromisoformat(ui_value)
689
689
  except ValueError:
@@ -72,7 +72,11 @@ def tag_factory(val=None, description=None, annotation=None, *args, _src_obj=Non
72
72
  new = copy(metadata)
73
73
  new.val = val if val is not None else new.val
74
74
  new.description = description or new.description
75
- return new._fetch_from(Tag(*args, **kwargs))
75
+ if new.annotation is None:
76
+ # pAnnot: Annotated[date, Tag(name="hello")] = datetime.fromisoformat(...)
77
+ # -> DatetimeTag(date=True)
78
+ new.annotation = annotation
79
+ return tag_assure_type(new._fetch_from(Tag(*args, **kwargs)))
76
80
  # NOTE The mechanism is not perfect. When done, we may test configs.PathTagClass.
77
81
  # * fetch_from will not transfer PathTag.multiple
78
82
  # * copy will not transfer list[Path] from `Annotated[list[Path], Tag(...)]`
@@ -14,11 +14,24 @@ if TYPE_CHECKING:
14
14
 
15
15
 
16
16
  class DateEntryFrame(tk.Frame):
17
+
17
18
  def __init__(self, master, tk_app: "TkWindow", tag: DatetimeTag, variable: tk.Variable, **kwargs):
18
19
  super().__init__(master, **kwargs)
19
20
 
20
21
  self.tk_app = tk_app
21
22
  self.tag = tag
23
+ if tag.date and tag.time:
24
+ if tag.full_precision:
25
+ self.datetimeformat = '%Y-%m-%d %H:%M:%S'
26
+ else:
27
+ self.datetimeformat = '%Y-%m-%d %H:%M'
28
+ elif tag.time and not tag.date:
29
+ if tag.full_precision:
30
+ self.datetimeformat = '%H:%M:%S'
31
+ else:
32
+ self.datetimeformat = '%H:%M'
33
+ else:
34
+ self.datetimeformat = '%Y-%m-%d'
22
35
 
23
36
  # Date entry
24
37
  self.spinbox = self.create_spinbox(variable)
@@ -27,7 +40,7 @@ class DateEntryFrame(tk.Frame):
27
40
  self.frame = tk.Frame(self)
28
41
 
29
42
  # The calendar widget
30
- if Calendar:
43
+ if Calendar and tag.date:
31
44
  # Toggle calendar button
32
45
  tk.Button(self, text="…", command=self.toggle_calendar).grid(row=0, column=1)
33
46
 
@@ -37,19 +50,20 @@ class DateEntryFrame(tk.Frame):
37
50
  self.calendar.bind("<<CalendarSelected>>", self.on_date_select)
38
51
  self.calendar.grid()
39
52
  # Initialize calendar with the current date
40
- self.update_calendar(self.spinbox.get(), '%Y-%m-%d %H:%M:%S.%f')
53
+ self.update_calendar(self.spinbox.get(), self.datetimeformat)
41
54
  else:
42
55
  self.calendar = None
43
56
 
44
- self.bind_all_events()
45
-
46
57
  def create_spinbox(self, variable: tk.Variable):
47
- spinbox = tk.Spinbox(self, font=("Arial", 16), width=30, wrap=True, textvariable=variable)
58
+ spinbox = tk.Spinbox(self, wrap=True, textvariable=variable)
48
59
  spinbox.grid()
49
60
  if not variable.get():
50
- spinbox.insert(0, datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f")[:-4])
61
+ spinbox.insert(0, datetime.now().strftime(self.datetimeformat))
51
62
  spinbox.focus_set()
52
- spinbox.icursor(8)
63
+ if (not self.tag.date and self.tag.time):
64
+ spinbox.icursor(0)
65
+ else:
66
+ spinbox.icursor(8)
53
67
 
54
68
  # Bind up/down arrow keys
55
69
  spinbox.bind("<Up>", self.increment_value)
@@ -60,20 +74,20 @@ class DateEntryFrame(tk.Frame):
60
74
 
61
75
  # Bind key release event to update calendar when user changes the input field
62
76
  spinbox.bind("<KeyRelease>", self.on_spinbox_change)
63
- return spinbox
64
77
 
65
- def bind_all_events(self):
66
- # Copy to clipboard with ctrl+c
67
- self.bind_all("<Control-c>", self.copy_to_clipboard)
78
+ # Toggle calendar widget with ctrl+shift+c
79
+ spinbox.bind("<Control-Shift-C>", self.toggle_calendar)
68
80
 
69
81
  # Select all in the spinbox with ctrl+a
70
- self.bind_all("<Control-a>", lambda event: self.select_all())
82
+ spinbox.bind("<Control-a>", self.select_all)
83
+
84
+ # Copy to clipboard with ctrl+c
85
+ spinbox.bind("<Control-c>", self.copy_to_clipboard)
71
86
 
72
87
  # Paste from clipboard with ctrl+v
73
- self.bind_all("<Control-v>", lambda event: self.paste_from_clipboard())
88
+ spinbox.bind("<Control-v>", self.paste_from_clipboard)
74
89
 
75
- # Toggle calendar widget with ctrl+shift+c
76
- self.bind_all("<Control-Shift-C>", lambda event: self.toggle_calendar())
90
+ return spinbox
77
91
 
78
92
  def toggle_calendar(self, event=None):
79
93
  if not self.calendar:
@@ -102,7 +116,10 @@ class DateEntryFrame(tk.Frame):
102
116
  def find_valid_time(self):
103
117
  input = self.spinbox.get()
104
118
  # use regex to find the time part
105
- time_part = re.search(r'\d{2}:\d{2}:\d{2}', input)
119
+ if self.tag.full_precision:
120
+ time_part = re.search(r'\d{2}:\d{2}:\d{2}', input)
121
+ else:
122
+ time_part = re.search(r'\d{2}:\d{2}', input)
106
123
  if time_part:
107
124
  return time_part.group()
108
125
  return False
@@ -114,51 +131,82 @@ class DateEntryFrame(tk.Frame):
114
131
  date = self.find_valid_date()
115
132
  time = self.find_valid_time()
116
133
 
117
- if date:
118
- split_input = re.split(r'[- :.]', date_str)
119
- part_index = self.get_part_index(caret_pos, len(split_input))
134
+ if date and not time:
135
+ split_input = re.split(r'[-]', date)
136
+ new_value_str = self.increment_part(split_input, caret_pos, delta, '-')
137
+ elif date and time:
138
+ split_input = re.split(r'[- :]', date_str)
139
+ new_value_str = self.increment_part(split_input, caret_pos, delta, ' ')
140
+ elif not date and time:
141
+ split_input = re.split(r'[:]', time)
142
+ new_value_str = self.increment_part(split_input, caret_pos, delta, ':')
143
+ else:
144
+ return
120
145
 
121
- # Increment or decrement the relevant part
122
- number = int(split_input[part_index])
123
- new_number = number + delta
124
- split_input[part_index] = str(new_number).zfill(len(split_input[part_index]))
146
+ # Validate the new date
147
+ try:
148
+ datetime.strptime(new_value_str, self.datetimeformat)
149
+ self.spinbox.delete(0, tk.END)
150
+ self.spinbox.insert(0, new_value_str)
151
+ self.spinbox.icursor(caret_pos)
152
+ if Calendar:
153
+ self.update_calendar(new_value_str, self.datetimeformat)
154
+ except ValueError as e:
155
+ pass
125
156
 
126
- if time:
127
- new_value_str = f"{split_input[0]}-{split_input[1]}-{split_input[2]} "\
128
- f"{split_input[3]}:{split_input[4]}:{split_input[5]}.{split_input[6][:2]}"
129
- string_format = '%Y-%m-%d %H:%M:%S.%f'
157
+ def increment_part(self, split_input, caret_pos, delta, separator):
158
+ part_index = self.get_part_index(caret_pos)
159
+ if part_index > len(split_input) - 1:
160
+ return separator.join(split_input)
161
+
162
+ # Increment or decrement the relevant part
163
+ number = int(split_input[part_index])
164
+ new_number = number + delta
165
+ split_input[part_index] = str(new_number).zfill(len(split_input[part_index]))
166
+
167
+ if self.tag.full_precision and separator == ' ':
168
+ return f"{split_input[0]}-{split_input[1]}-{split_input[2]} "\
169
+ f"{split_input[3]}:{split_input[4]}:{split_input[5]}"
170
+ elif separator == ' ':
171
+ return f"{split_input[0]}-{split_input[1]}-{split_input[2]} "\
172
+ f"{split_input[3]}:{split_input[4]}"
173
+ elif separator == ':':
174
+ if self.tag.full_precision:
175
+ return f"{split_input[0]}:{split_input[1]}:{split_input[2]}"
130
176
  else:
131
- new_value_str = f"{split_input[0]}-{split_input[1]}-{split_input[2]}"
132
- string_format = '%Y-%m-%d'
133
-
134
- # Validate the new date
135
- try:
136
- datetime.strptime(new_value_str, string_format)
137
- self.spinbox.delete(0, tk.END)
138
- self.spinbox.insert(0, new_value_str)
139
- self.spinbox.icursor(caret_pos)
140
- if Calendar:
141
- self.update_calendar(new_value_str, string_format)
142
- except ValueError:
143
- pass
144
-
145
- def get_part_index(self, caret_pos, split_length):
146
- if caret_pos < 5: # year
147
- return 0
148
- elif caret_pos < 8: # month
149
- return 1
150
- elif caret_pos < 11: # day
151
- return 2
152
- elif split_length > 3:
153
- if caret_pos < 14: # hour
177
+ return f"{split_input[0]}:{split_input[1]}"
178
+ else:
179
+ return separator.join(split_input)
180
+
181
+ def get_part_index(self, caret_pos):
182
+ if self.tag.date and self.tag.time:
183
+ if caret_pos < 5: # year
184
+ return 0
185
+ elif caret_pos < 8: # month
186
+ return 1
187
+ elif caret_pos < 11: # day
188
+ return 2
189
+ elif caret_pos < 14: # hour
154
190
  return 3
155
191
  elif caret_pos < 17: # minute
156
192
  return 4
157
- elif caret_pos < 20: # second
193
+ else: # second
158
194
  return 5
159
- else: # millisecond
160
- return 6
161
- return 2
195
+ elif self.tag.date:
196
+ if caret_pos < 5: # year
197
+ return 0
198
+ elif caret_pos < 8: # month
199
+ return 1
200
+ elif caret_pos < 11: # day
201
+ return 2
202
+ elif self.tag.time:
203
+ if caret_pos < 3: # hour
204
+ return 0
205
+ elif caret_pos < 6: # minute
206
+ return 1
207
+ else: # second
208
+ return 2
209
+ return 0
162
210
 
163
211
  def on_spinbox_click(self, event):
164
212
  # Check if the click was on the spinbox arrows
@@ -168,21 +216,39 @@ class DateEntryFrame(tk.Frame):
168
216
  self.decrement_value()
169
217
 
170
218
  def on_date_select(self, event):
171
- selected_date = self.calendar.selection_get()
219
+
220
+ # find caret position to keep it in the same place
221
+ caret_pos = self.spinbox.index(tk.INSERT)
222
+
223
+ selected_date = self.calendar.selection_get().strftime('%Y-%m-%d')
224
+ if self.tag.time:
225
+ time = self.find_valid_time()
226
+ if time:
227
+ selected_date += f" {time}"
228
+ else:
229
+ if self.tag.full_precision:
230
+ selected_date += " 00:00:00"
231
+ else:
232
+ selected_date += " 00:00"
233
+
234
+
172
235
  self.spinbox.delete(0, tk.END)
173
- self.spinbox.insert(0, selected_date.strftime('%Y-%m-%d'))
174
- self.spinbox.icursor(len(self.spinbox.get()))
236
+ self.spinbox.insert(0, selected_date)
237
+
238
+ # Keep the caret position
239
+ self.spinbox.icursor(caret_pos)
175
240
 
176
241
  def on_spinbox_change(self, event):
177
242
  if Calendar:
178
243
  self.update_calendar(self.spinbox.get())
179
244
 
180
245
  def update_calendar(self, date_str, string_format='%Y-%m-%d'):
181
- try:
182
- date = datetime.strptime(date_str, string_format)
183
- self.calendar.selection_set(date)
184
- except ValueError:
185
- pass
246
+ if self.tag.date:
247
+ try:
248
+ date = datetime.strptime(date_str, string_format)
249
+ self.calendar.selection_set(date)
250
+ except ValueError:
251
+ pass
186
252
 
187
253
  def copy_to_clipboard(self, event=None):
188
254
  self.clipboard_clear()
@@ -194,7 +260,7 @@ class DateEntryFrame(tk.Frame):
194
260
  popup = tk.Toplevel(self)
195
261
  popup.wm_title("")
196
262
 
197
- label = tk.Label(popup, text=message, font=("Arial", 12))
263
+ label = tk.Label(popup, text=message)
198
264
  label.pack(side="top", fill="x", pady=10, padx=10)
199
265
 
200
266
  # Position the popup window in the top-left corner of the widget
@@ -218,4 +284,8 @@ class DateEntryFrame(tk.Frame):
218
284
 
219
285
  def paste_from_clipboard(self, event=None):
220
286
  self.spinbox.delete(0, tk.END)
221
- self.spinbox.insert(0, self.clipboard_get())
287
+
288
+ def round_time(self, dt):
289
+ if self.tag.full_precision:
290
+ return dt
291
+ return dt[:-4]
@@ -72,17 +72,16 @@ class TkWindow(Tk, BackendAdaptor):
72
72
  self.form = Form(self.frame,
73
73
  name_form="",
74
74
  form_dict=formdict_to_widgetdict(form, self.widgetize),
75
- name_config=submit if isinstance(submit, str) else "Ok",
76
- button=bool(submit)
75
+ name_button=submit if isinstance(submit, str) else "Ok",
76
+ button_command=self._ok if submit else None
77
77
  )
78
78
  self.form.pack()
79
79
 
80
80
  # Add radio etc.
81
- replace_widgets(self, self.form.widgets, form)
81
+ replace_widgets(self, self.form.fields, form)
82
82
 
83
83
  # Set the submit and exit options
84
84
  if self.form.button:
85
- self.form.button.config(command=self._ok)
86
85
  tip, keysym = ("Enter", "<Return>")
87
86
  ToolTip(self.form.button, msg=tip) # NOTE is not destroyed in _clear
88
87
  self._bind_event(keysym, self._ok)
@@ -6,6 +6,8 @@ from typing import TYPE_CHECKING
6
6
 
7
7
  from autocombobox import AutoCombobox
8
8
 
9
+ from tkinter_form.tkinter_form import Form, FieldForm
10
+
9
11
  from ..auxiliary import flatten, flatten_keys
10
12
  from ..experimental import MININTERFACE_CONFIG, FacetCallback, SubmitButton
11
13
  from ..form_dict import TagDict
@@ -42,13 +44,12 @@ class AnyVariable(Variable):
42
44
 
43
45
 
44
46
  def ready_to_replace(widget: Widget,
45
- name,
46
- tag: "Tag",
47
- variable: Variable) -> tuple[Widget, dict]:
47
+ variable: Variable,
48
+ field_form: FieldForm) -> tuple[Widget, dict]:
48
49
  if widget.winfo_manager() == 'grid':
49
50
  grid_info = widget.grid_info()
50
51
  widget.grid_forget()
51
- widget.master._Form__vars[name] = variable
52
+ field_form.variable = variable
52
53
  return grid_info
53
54
  else:
54
55
  raise ValueError(f"GuiInterface: Cannot tackle the form, unknown winfo_manager {widget.winfo_manager()}.")
@@ -87,17 +88,18 @@ def _set_true(variable: Variable, tag: Tag):
87
88
 
88
89
  def replace_widgets(tk_app: "TkWindow", nested_widgets, form: TagDict):
89
90
  def _fetch(variable):
90
- return ready_to_replace(widget, var_name, tag, variable)
91
+ return ready_to_replace(widget, variable, field_form)
91
92
 
92
93
  # NOTE tab order broken, injected to another position
93
94
  # NOTE should the button receive tag or directly
94
95
  # the whole facet (to change the current form)? Specifiable by experimental.FacetCallback.
95
96
  nested_widgets = widgets_to_dict(nested_widgets)
96
- for (var_name, tag), (label1, widget) in zip(flatten_keys(form), flatten(nested_widgets)):
97
+ for tag, field_form in zip(flatten(form), flatten(nested_widgets)):
97
98
  tag: Tag
98
- label1: Widget
99
- widget: Widget
100
- variable = widget.master._Form__vars[var_name]
99
+ field_form: FieldForm
100
+ label1: Widget = field_form.label
101
+ widget: Widget = field_form.widget
102
+ variable = field_form.variable
101
103
  subwidgets = []
102
104
  master = widget.master
103
105
 
@@ -136,6 +138,7 @@ def replace_widgets(tk_app: "TkWindow", nested_widgets, form: TagDict):
136
138
  # Calendar
137
139
  elif isinstance(tag, DatetimeTag):
138
140
  grid_info = widget.grid_info()
141
+ widget.grid_forget()
139
142
  nested_frame = DateEntryFrame(master, tk_app, tag, variable)
140
143
  nested_frame.grid(row=grid_info['row'], column=grid_info['column'])
141
144
  widget = nested_frame.spinbox
@@ -184,15 +187,15 @@ def create_button(master, _fetch, tag, label1, command=None):
184
187
  return variable, widget2
185
188
 
186
189
 
187
- def widgets_to_dict(widgets_dict) -> dict:
190
+ def widgets_to_dict(widgets_dict) -> dict[str, dict | FieldForm]:
188
191
  """ Convert tkinter_form.widgets to a dict """
189
192
  result = {}
190
193
  for key, value in widgets_dict.items():
191
194
  if isinstance(value, dict):
192
195
  result[key] = widgets_to_dict(value)
193
- elif hasattr(value, 'widgets'):
196
+ elif isinstance(value, Form):
194
197
  # this is another tkinter_form.Form, recursively parse
195
- result[key] = widgets_to_dict(value.widgets)
198
+ result[key] = widgets_to_dict(value.fields)
196
199
  else: # value is a tuple of (Label, Widget (like Entry))
197
200
  result[key] = value
198
201
  return result
@@ -222,7 +222,7 @@ class DatetimeTag(Tag):
222
222
  time: bool = False
223
223
  """ The time part is active """
224
224
 
225
- full_precision: Optional[bool] = None
225
+ full_precision: bool = False
226
226
  """ Include full time precison, seconds, microseconds. """
227
227
 
228
228
  def __post_init__(self):
@@ -230,9 +230,9 @@ class DatetimeTag(Tag):
230
230
  if self.annotation:
231
231
  self.date = issubclass(self.annotation, date)
232
232
  self.time = issubclass(self.annotation, time) or issubclass(self.annotation, datetime)
233
- if not self.date and not self.time:
234
- self.date = self.time = True
235
- # NOTE: self.full_precision ...
233
+ # NOTE: remove
234
+ # if not self.time and self.full_precision:
235
+ # self.full_precision = False
236
236
 
237
237
  def _make_default_value(self):
238
238
  return datetime.now()
@@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api"
4
4
 
5
5
  [tool.poetry]
6
6
  name = "mininterface"
7
- version = "0.7.1"
7
+ version = "0.7.2"
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"
@@ -19,17 +19,16 @@ typing_extensions = "*"
19
19
  pyyaml = "*"
20
20
  # Standard requirements
21
21
  autocombobox = "1.4.2"
22
- humanize = "*" # used only in the TkInterface, hence it is not a minimal requirement
22
+ humanize = "*" # used only in the TkInterface, hence it is not a minimal requirement
23
23
  textual = "~0.84"
24
24
  tkinter-tooltip = "*"
25
- tkinter_form = "0.1.5.2"
25
+ tkinter_form = "0.2.1"
26
26
  tkscrollableframe = "*"
27
- tkcalendar = "*" # TODO put into extras?
28
27
 
29
28
  [tool.poetry.extras]
30
29
  web = ["textual-serve"]
31
- img = ["pillow"]
32
- all = ["textual-serve", "pillow"]
30
+ gui = ["pillow", "tkcalendar"]
31
+ all = ["textual-serve", "pillow", "tkcalendar"]
33
32
 
34
33
  [tool.poetry.scripts]
35
34
  mininterface = "mininterface.__main__:main"
File without changes