mininterface 0.7.3__tar.gz → 0.7.4__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 (38) hide show
  1. {mininterface-0.7.3 → mininterface-0.7.4}/PKG-INFO +3 -8
  2. {mininterface-0.7.3 → mininterface-0.7.4}/mininterface/__init__.py +3 -3
  3. {mininterface-0.7.3 → mininterface-0.7.4}/mininterface/auxiliary.py +0 -9
  4. {mininterface-0.7.3 → mininterface-0.7.4}/mininterface/cli_parser.py +90 -42
  5. {mininterface-0.7.3 → mininterface-0.7.4}/pyproject.toml +3 -3
  6. {mininterface-0.7.3 → mininterface-0.7.4}/LICENSE +0 -0
  7. {mininterface-0.7.3 → mininterface-0.7.4}/README.md +0 -0
  8. {mininterface-0.7.3 → mininterface-0.7.4}/mininterface/ValidationFail.py +0 -0
  9. {mininterface-0.7.3 → mininterface-0.7.4}/mininterface/__main__.py +0 -0
  10. {mininterface-0.7.3 → mininterface-0.7.4}/mininterface/exceptions.py +0 -0
  11. {mininterface-0.7.3 → mininterface-0.7.4}/mininterface/experimental.py +0 -0
  12. {mininterface-0.7.3 → mininterface-0.7.4}/mininterface/facet.py +0 -0
  13. {mininterface-0.7.3 → mininterface-0.7.4}/mininterface/form_dict.py +0 -0
  14. {mininterface-0.7.3 → mininterface-0.7.4}/mininterface/interfaces.py +0 -0
  15. {mininterface-0.7.3 → mininterface-0.7.4}/mininterface/mininterface.py +0 -0
  16. {mininterface-0.7.3 → mininterface-0.7.4}/mininterface/redirectable.py +0 -0
  17. {mininterface-0.7.3 → mininterface-0.7.4}/mininterface/showcase.py +0 -0
  18. {mininterface-0.7.3 → mininterface-0.7.4}/mininterface/start.py +0 -0
  19. {mininterface-0.7.3 → mininterface-0.7.4}/mininterface/subcommands.py +0 -0
  20. {mininterface-0.7.3 → mininterface-0.7.4}/mininterface/tag.py +0 -0
  21. {mininterface-0.7.3 → mininterface-0.7.4}/mininterface/tag_factory.py +0 -0
  22. {mininterface-0.7.3 → mininterface-0.7.4}/mininterface/text_interface.py +0 -0
  23. {mininterface-0.7.3 → mininterface-0.7.4}/mininterface/textual_interface/__init__.py +0 -0
  24. {mininterface-0.7.3 → mininterface-0.7.4}/mininterface/textual_interface/textual_adaptor.py +0 -0
  25. {mininterface-0.7.3 → mininterface-0.7.4}/mininterface/textual_interface/textual_app.py +0 -0
  26. {mininterface-0.7.3 → mininterface-0.7.4}/mininterface/textual_interface/textual_button_app.py +0 -0
  27. {mininterface-0.7.3 → mininterface-0.7.4}/mininterface/textual_interface/textual_facet.py +0 -0
  28. {mininterface-0.7.3 → mininterface-0.7.4}/mininterface/textual_interface/widgets.py +0 -0
  29. {mininterface-0.7.3 → mininterface-0.7.4}/mininterface/tk_interface/__init__.py +0 -0
  30. {mininterface-0.7.3 → mininterface-0.7.4}/mininterface/tk_interface/date_entry.py +0 -0
  31. {mininterface-0.7.3 → mininterface-0.7.4}/mininterface/tk_interface/external_fix.py +0 -0
  32. {mininterface-0.7.3 → mininterface-0.7.4}/mininterface/tk_interface/redirect_text_tkinter.py +0 -0
  33. {mininterface-0.7.3 → mininterface-0.7.4}/mininterface/tk_interface/tk_facet.py +0 -0
  34. {mininterface-0.7.3 → mininterface-0.7.4}/mininterface/tk_interface/tk_window.py +0 -0
  35. {mininterface-0.7.3 → mininterface-0.7.4}/mininterface/tk_interface/utils.py +0 -0
  36. {mininterface-0.7.3 → mininterface-0.7.4}/mininterface/type_stubs.py +0 -0
  37. {mininterface-0.7.3 → mininterface-0.7.4}/mininterface/types.py +0 -0
  38. {mininterface-0.7.3 → mininterface-0.7.4}/mininterface/validators.py +0 -0
@@ -1,8 +1,7 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: mininterface
3
- Version: 0.7.3
3
+ Version: 0.7.4
4
4
  Summary: A minimal access to GUI, TUI, CLI and config
5
- Home-page: https://github.com/CZ-NIC/mininterface
6
5
  License: GPL-3.0-or-later
7
6
  Author: Edvard Rejthar
8
7
  Author-email: edvard.rejthar@nic.cz
@@ -13,11 +12,6 @@ Classifier: Programming Language :: Python :: 3.10
13
12
  Classifier: Programming Language :: Python :: 3.11
14
13
  Classifier: Programming Language :: Python :: 3.12
15
14
  Classifier: Programming Language :: Python :: 3.13
16
- Provides-Extra: all
17
- Provides-Extra: gui
18
- Provides-Extra: img
19
- Provides-Extra: tui
20
- Provides-Extra: web
21
15
  Requires-Dist: autocombobox (==1.4.2)
22
16
  Requires-Dist: humanize
23
17
  Requires-Dist: pyyaml
@@ -26,7 +20,8 @@ Requires-Dist: tkinter-tooltip
26
20
  Requires-Dist: tkinter_form (==0.2.1)
27
21
  Requires-Dist: tkscrollableframe
28
22
  Requires-Dist: typing_extensions
29
- Requires-Dist: tyro (==0.8.14)
23
+ Requires-Dist: tyro (>=0.9,<0.10)
24
+ Project-URL: Homepage, https://github.com/CZ-NIC/mininterface
30
25
  Description-Content-Type: text/markdown
31
26
 
32
27
  # Mininterface – access to GUI, TUI, CLI and config files
@@ -8,7 +8,7 @@ from .exceptions import Cancelled, InterfaceNotAvailable
8
8
  from .interfaces import get_interface
9
9
 
10
10
  from . import validators
11
- from .cli_parser import _parse_cli, assure_args
11
+ from .cli_parser import parse_cli, assure_args
12
12
  from .subcommands import Command, SubcommandPlaceholder
13
13
  from .form_dict import DataClass, EnvClass
14
14
  from .mininterface import EnvClass, Mininterface
@@ -183,9 +183,9 @@ def run(env_or_list: Type[EnvClass] | list[Type[Command]] | None = None,
183
183
  start.choose_subcommand(env_or_list)
184
184
  elif env_or_list:
185
185
  # Load configuration from CLI and a config file
186
- env, wrong_fields = _parse_cli(env_or_list, config_file, add_verbosity, ask_for_missing, args, **kwargs)
186
+ env, wrong_fields = parse_cli(env_or_list, config_file, add_verbosity, ask_for_missing, args, **kwargs)
187
187
  else: # even though there is no configuration, yet we need to parse CLI for meta-commands like --help or --verbose
188
- _parse_cli(_Empty, None, add_verbosity, ask_for_missing, args)
188
+ parse_cli(_Empty, None, add_verbosity, ask_for_missing, args)
189
189
 
190
190
  # Build the interface
191
191
  interface = get_interface(title, interface, env)
@@ -70,15 +70,6 @@ def yield_annotations(dataclass):
70
70
  yield from (cl.__annotations__ for cl in dataclass.__mro__ if is_dataclass(cl))
71
71
 
72
72
 
73
- def yield_defaults(dataclass):
74
- """ Return tuple(name, type, default value or MISSING).
75
- (Default factory is automatically resolved.)
76
- """
77
- return ((f.name,
78
- f.default_factory() if f.default_factory is not MISSING else f.default)
79
- for f in fields(dataclass))
80
-
81
-
82
73
  def matches_annotation(value, annotation) -> bool:
83
74
  """ Check whether the value type corresponds to the annotation.
84
75
  Because built-in isinstance is not enough, it cannot determine parametrized generics.
@@ -6,7 +6,7 @@ import sys
6
6
  import warnings
7
7
  from argparse import Action, ArgumentParser
8
8
  from contextlib import ExitStack
9
- from dataclasses import MISSING
9
+ from dataclasses import MISSING, fields, is_dataclass
10
10
  from pathlib import Path
11
11
  from types import SimpleNamespace
12
12
  from typing import Optional, Sequence, Type, Union
@@ -15,12 +15,10 @@ from unittest.mock import patch
15
15
  import yaml
16
16
  from tyro import cli
17
17
  from tyro._argparse_formatter import TyroArgumentParser
18
- from tyro._fields import NonpropagatingMissingType
19
- # NOTE in the future versions of tyro, include that way:
20
- # from tyro._singleton import NonpropagatingMissingType
18
+ from tyro._singleton import MISSING_NONPROP
21
19
  from tyro.extras import get_parser
22
20
 
23
- from .auxiliary import yield_annotations, yield_defaults
21
+ from .auxiliary import yield_annotations
24
22
  from .form_dict import EnvClass, MissingTagValue
25
23
  from .tag import Tag
26
24
  from .tag_factory import tag_factory
@@ -137,8 +135,9 @@ def run_tyro_parser(env_or_list: Type[EnvClass] | list[Type[EnvClass]],
137
135
  with ExitStack() as stack:
138
136
  [stack.enter_context(p) for p in patches] # apply just the chosen mocks
139
137
  res = cli(type_form, args=args, **kwargs)
140
- if isinstance(res, NonpropagatingMissingType):
141
- # NOTE tyro does not work if a required positional is missing tyro.cli() returns just NonpropagatingMissingType.
138
+ if res is MISSING_NONPROP:
139
+ # NOTE tyro does not work if a required positional is missing tyro.cli()
140
+ # returns just NonpropagatingMissingType (MISSING_NONPROP).
142
141
  # If this is supported, I might set other attributes like required (date, time).
143
142
  # Fail if missing:
144
143
  # files: Positional[list[Path]]
@@ -217,12 +216,12 @@ def set_default(kwargs, field_name, val):
217
216
  setattr(kwargs["default"], field_name, val)
218
217
 
219
218
 
220
- def _parse_cli(env_or_list: Type[EnvClass] | list[Type[EnvClass]],
221
- config_file: Path | None = None,
222
- add_verbosity=True,
223
- ask_for_missing=True,
224
- args=None,
225
- **kwargs) -> tuple[EnvClass | None, dict, WrongFields]:
219
+ def parse_cli(env_or_list: Type[EnvClass] | list[Type[EnvClass]],
220
+ config_file: Path | None = None,
221
+ add_verbosity=True,
222
+ ask_for_missing=True,
223
+ args=None,
224
+ **kwargs) -> tuple[EnvClass | None, dict, WrongFields]:
226
225
  """ Parse CLI arguments, possibly merged from a config file.
227
226
 
228
227
  Args:
@@ -243,41 +242,90 @@ def _parse_cli(env_or_list: Type[EnvClass] | list[Type[EnvClass]],
243
242
  # Load config file
244
243
  if config_file and subcommands:
245
244
  # Reading config files when using subcommands is not implemented.
246
- static = {}
247
245
  kwargs["default"] = None
248
246
  warnings.warn(f"Config file {config_file} is ignored because subcommands are used."
249
247
  " It is not easy to set how this should work."
250
248
  " Describe the developer your usecase so that they might implement this.")
251
- if "default" not in kwargs and not subcommands:
249
+
250
+ if "default" not in kwargs and not subcommands and config_file:
252
251
  # Undocumented feature. User put a namespace into kwargs["default"]
253
252
  # that already serves for defaults. We do not fetch defaults yet from a config file.
254
- disk = {}
255
- if config_file:
256
- disk = yaml.safe_load(config_file.read_text()) or {} # empty file is ok
257
- # Nested dataclasses have to be properly initialized. YAML gave them as dicts only.
258
- for key in (key for key, val in disk.items() if isinstance(val, dict)):
259
- disk[key] = env.__annotations__[key](**disk[key])
260
-
261
- # Fill default fields
262
- if pydantic and issubclass(env, BaseModel):
263
- # Unfortunately, pydantic needs to fill the default with the actual values,
264
- # the default value takes the precedence over the hard coded one, even if missing.
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):
270
- # Unfortunately, attrs needs to fill the default with the actual values,
271
- # the default value takes the precedence over the hard coded one, even if missing.
272
- # NOTE Might not work for inherited models.
273
- static = {key: field.default
274
- for key, field in attr.fields_dict(env).items() if not key.startswith("__") and not key in disk}
275
- else:
276
- # To ensure the configuration file does not need to contain all keys, we have to fill in the missing ones.
277
- # Otherwise, tyro will spawn warnings about missing fields.
278
- static = {key: val
279
- for key, val in yield_defaults(env) if not key.startswith("__") and not key in disk}
280
- kwargs["default"] = SimpleNamespace(**(static | disk))
253
+ disk = yaml.safe_load(config_file.read_text()) or {} # empty file is ok
254
+ kwargs["default"] = _create_with_missing(env, disk)
281
255
 
282
256
  # Load configuration from CLI
283
257
  return run_tyro_parser(subcommands or env, kwargs, add_verbosity, ask_for_missing, args)
258
+
259
+
260
+ def _create_with_missing(env, disk: dict):
261
+ """
262
+ Create a default instance of an Env object. This is due to provent tyro to spawn warnings about missing fields.
263
+ Nested dataclasses have to be properly initialized. YAML gave them as dicts only.
264
+ """
265
+
266
+ # Determine model
267
+ if pydantic and issubclass(env, BaseModel):
268
+ m = _process_pydantic
269
+ elif attr and attr.has(env):
270
+ m = _process_attr
271
+ else: # dataclass
272
+ m = _process_dataclass
273
+
274
+ # Fill default fields with the config file values or leave the defaults.
275
+ # Unfortunately, we have to fill the defaults, we cannot leave them empty
276
+ # as the default value takes the precedence over the hard coded one, even if missing.
277
+ out = {}
278
+ for name, v in m(env, disk):
279
+ out[name] = v
280
+ disk.pop(name, None)
281
+
282
+ # Check for unknown fields
283
+ if disk:
284
+ warnings.warn(f"Unknown fields in the configuration file: {', '.join(disk)}")
285
+
286
+ # Safely initialize the model
287
+ return env(**out)
288
+
289
+
290
+ def _process_pydantic(env, disk):
291
+ for name, f in env.model_fields.items():
292
+ if name in disk:
293
+ if isinstance(f.default, BaseModel):
294
+ v = _create_with_missing(f.default.__class__, disk[name])
295
+ else:
296
+ v = disk[name]
297
+ elif f.default is not None:
298
+ v = f.default
299
+ yield name, v
300
+
301
+
302
+ def _process_attr(env, disk):
303
+ for f in attr.fields(env):
304
+ if f.name in disk:
305
+ if attr.has(f.default):
306
+ v = _create_with_missing(f.default.__class__, disk[f.name])
307
+ else:
308
+ v = disk[f.name]
309
+ elif f.default is not attr.NOTHING:
310
+ v = f.default
311
+ else:
312
+ v = MISSING_NONPROP
313
+ yield f.name, v
314
+
315
+
316
+ def _process_dataclass(env, disk):
317
+ for f in fields(env):
318
+ if f.name.startswith("__"):
319
+ continue
320
+ elif f.name in disk:
321
+ if is_dataclass(f.type):
322
+ v = _create_with_missing(f.type, disk[f.name])
323
+ else:
324
+ v = disk[f.name]
325
+ elif f.default_factory is not MISSING:
326
+ v = f.default_factory()
327
+ elif f.default is not MISSING:
328
+ v = f.default
329
+ else:
330
+ v = MISSING_NONPROP
331
+ yield f.name, v
@@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api"
4
4
 
5
5
  [tool.poetry]
6
6
  name = "mininterface"
7
- version = "0.7.3"
7
+ version = "0.7.4"
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"
@@ -14,7 +14,7 @@ readme = "README.md"
14
14
  [tool.poetry.dependencies]
15
15
  # Minimal requirements
16
16
  python = "^3.10"
17
- tyro = "0.8.14" # NOTE: 0.9 brings some test breaking changes
17
+ tyro = "^0.9"
18
18
  typing_extensions = "*"
19
19
  pyyaml = "*"
20
20
  # Standard requirements
@@ -25,7 +25,7 @@ tkinter-tooltip = "*"
25
25
  tkinter_form = "0.2.1"
26
26
  tkscrollableframe = "*"
27
27
 
28
- [tool.poetry.extras]
28
+ [tool.poetry.project.optional-dependencies]
29
29
  web = ["textual-serve"]
30
30
  img = ["pillow", "textual_imageview"]
31
31
  tui = ["textual_imageview"]
File without changes
File without changes