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.
- {mininterface-0.7.3 → mininterface-0.7.4}/PKG-INFO +3 -8
- {mininterface-0.7.3 → mininterface-0.7.4}/mininterface/__init__.py +3 -3
- {mininterface-0.7.3 → mininterface-0.7.4}/mininterface/auxiliary.py +0 -9
- {mininterface-0.7.3 → mininterface-0.7.4}/mininterface/cli_parser.py +90 -42
- {mininterface-0.7.3 → mininterface-0.7.4}/pyproject.toml +3 -3
- {mininterface-0.7.3 → mininterface-0.7.4}/LICENSE +0 -0
- {mininterface-0.7.3 → mininterface-0.7.4}/README.md +0 -0
- {mininterface-0.7.3 → mininterface-0.7.4}/mininterface/ValidationFail.py +0 -0
- {mininterface-0.7.3 → mininterface-0.7.4}/mininterface/__main__.py +0 -0
- {mininterface-0.7.3 → mininterface-0.7.4}/mininterface/exceptions.py +0 -0
- {mininterface-0.7.3 → mininterface-0.7.4}/mininterface/experimental.py +0 -0
- {mininterface-0.7.3 → mininterface-0.7.4}/mininterface/facet.py +0 -0
- {mininterface-0.7.3 → mininterface-0.7.4}/mininterface/form_dict.py +0 -0
- {mininterface-0.7.3 → mininterface-0.7.4}/mininterface/interfaces.py +0 -0
- {mininterface-0.7.3 → mininterface-0.7.4}/mininterface/mininterface.py +0 -0
- {mininterface-0.7.3 → mininterface-0.7.4}/mininterface/redirectable.py +0 -0
- {mininterface-0.7.3 → mininterface-0.7.4}/mininterface/showcase.py +0 -0
- {mininterface-0.7.3 → mininterface-0.7.4}/mininterface/start.py +0 -0
- {mininterface-0.7.3 → mininterface-0.7.4}/mininterface/subcommands.py +0 -0
- {mininterface-0.7.3 → mininterface-0.7.4}/mininterface/tag.py +0 -0
- {mininterface-0.7.3 → mininterface-0.7.4}/mininterface/tag_factory.py +0 -0
- {mininterface-0.7.3 → mininterface-0.7.4}/mininterface/text_interface.py +0 -0
- {mininterface-0.7.3 → mininterface-0.7.4}/mininterface/textual_interface/__init__.py +0 -0
- {mininterface-0.7.3 → mininterface-0.7.4}/mininterface/textual_interface/textual_adaptor.py +0 -0
- {mininterface-0.7.3 → mininterface-0.7.4}/mininterface/textual_interface/textual_app.py +0 -0
- {mininterface-0.7.3 → mininterface-0.7.4}/mininterface/textual_interface/textual_button_app.py +0 -0
- {mininterface-0.7.3 → mininterface-0.7.4}/mininterface/textual_interface/textual_facet.py +0 -0
- {mininterface-0.7.3 → mininterface-0.7.4}/mininterface/textual_interface/widgets.py +0 -0
- {mininterface-0.7.3 → mininterface-0.7.4}/mininterface/tk_interface/__init__.py +0 -0
- {mininterface-0.7.3 → mininterface-0.7.4}/mininterface/tk_interface/date_entry.py +0 -0
- {mininterface-0.7.3 → mininterface-0.7.4}/mininterface/tk_interface/external_fix.py +0 -0
- {mininterface-0.7.3 → mininterface-0.7.4}/mininterface/tk_interface/redirect_text_tkinter.py +0 -0
- {mininterface-0.7.3 → mininterface-0.7.4}/mininterface/tk_interface/tk_facet.py +0 -0
- {mininterface-0.7.3 → mininterface-0.7.4}/mininterface/tk_interface/tk_window.py +0 -0
- {mininterface-0.7.3 → mininterface-0.7.4}/mininterface/tk_interface/utils.py +0 -0
- {mininterface-0.7.3 → mininterface-0.7.4}/mininterface/type_stubs.py +0 -0
- {mininterface-0.7.3 → mininterface-0.7.4}/mininterface/types.py +0 -0
- {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
|
+
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 (
|
|
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
|
|
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 =
|
|
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
|
-
|
|
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.
|
|
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
|
|
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
|
|
141
|
-
# NOTE tyro does not work if a required positional is missing tyro.cli()
|
|
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
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mininterface-0.7.3 → mininterface-0.7.4}/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.3 → mininterface-0.7.4}/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
|
|
File without changes
|