From 7f72c3a3985dde4fe3315190d9bbe395d60dcddc Mon Sep 17 00:00:00 2001 From: Edvard Rejthar Date: Mon, 16 Dec 2024 16:38:08 +0100 Subject: [PATCH] datetime gui tests --- docs/index.md | 2 +- mininterface/tag.py | 4 ++-- mininterface/tag_factory.py | 6 +++++- mininterface/tk_interface/date_entry.py | 7 ++----- mininterface/tk_interface/utils.py | 2 +- mininterface/types.py | 8 ++++---- tests/configs.py | 1 + tests/tests.py | 9 +++++++-- 8 files changed, 23 insertions(+), 16 deletions(-) diff --git a/docs/index.md b/docs/index.md index 004a4d8..2393c02 100644 --- a/docs/index.md +++ b/docs/index.md @@ -102,7 +102,7 @@ The config variables needed by your program are kept in cozy dataclasses. Write Install with a single command from [PyPi](https://pypi.org/project/mininterface/). ```bash -pip install mininterface +pip install mininterface[all] # GPLv3 and compatible ``` ## Minimal installation diff --git a/mininterface/tag.py b/mininterface/tag.py index d1823bf..617e282 100644 --- a/mininterface/tag.py +++ b/mininterface/tag.py @@ -1,6 +1,6 @@ from ast import literal_eval from dataclasses import dataclass, fields -from datetime import datetime +from datetime import date, time from enum import Enum from types import FunctionType, MethodType, NoneType, UnionType from typing import TYPE_CHECKING, Any, Callable, Iterable, Optional, Type, TypeVar, Union, get_args, get_origin @@ -683,7 +683,7 @@ def update(self, ui_value: TagValue) -> bool: except (SyntaxError, ValueError): self.set_error_text(f"Not a valid {self._repr_annotation()}") return False - elif self.annotation is datetime: + elif self._is_subclass((time, date)): try: out_value = self.annotation.fromisoformat(ui_value) except ValueError: diff --git a/mininterface/tag_factory.py b/mininterface/tag_factory.py index 1f2932d..af4839e 100644 --- a/mininterface/tag_factory.py +++ b/mininterface/tag_factory.py @@ -72,7 +72,11 @@ def tag_factory(val=None, description=None, annotation=None, *args, _src_obj=Non new = copy(metadata) new.val = val if val is not None else new.val new.description = description or new.description - return new._fetch_from(Tag(*args, **kwargs)) + if new.annotation is None: + # pAnnot: Annotated[date, Tag(name="hello")] = datetime.fromisoformat(...) + # -> DatetimeTag(date=True) + new.annotation = annotation + return tag_assure_type(new._fetch_from(Tag(*args, **kwargs))) # NOTE The mechanism is not perfect. When done, we may test configs.PathTagClass. # * fetch_from will not transfer PathTag.multiple # * copy will not transfer list[Path] from `Annotated[list[Path], Tag(...)]` diff --git a/mininterface/tk_interface/date_entry.py b/mininterface/tk_interface/date_entry.py index 0dacd07..1b73c2e 100644 --- a/mininterface/tk_interface/date_entry.py +++ b/mininterface/tk_interface/date_entry.py @@ -14,7 +14,6 @@ class DateEntryFrame(tk.Frame): - last_date_entry_frame = None def __init__(self, master, tk_app: "TkWindow", tag: DatetimeTag, variable: tk.Variable, **kwargs): super().__init__(master, **kwargs) @@ -34,7 +33,6 @@ def __init__(self, master, tk_app: "TkWindow", tag: DatetimeTag, variable: tk.Va else: self.datetimeformat = '%Y-%m-%d' - # Date entry self.spinbox = self.create_spinbox(variable) @@ -53,7 +51,6 @@ def __init__(self, master, tk_app: "TkWindow", tag: DatetimeTag, variable: tk.Va self.calendar.grid() # Initialize calendar with the current date self.update_calendar(self.spinbox.get(), self.datetimeformat) - DateEntryFrame.last_date_entry_frame = self else: self.calendar = None @@ -172,10 +169,10 @@ def increment_part(self, split_input, caret_pos, delta, separator): if self.tag.full_precision and separator == ' ': return f"{split_input[0]}-{split_input[1]}-{split_input[2]} "\ - f"{split_input[3]}:{split_input[4]}:{split_input[5]}.{split_input[6]}" + f"{split_input[3]}:{split_input[4]}:{split_input[5]}.{split_input[6]}" elif separator == ' ': return f"{split_input[0]}-{split_input[1]}-{split_input[2]} "\ - f"{split_input[3]}:{split_input[4]}:{split_input[5]}" + f"{split_input[3]}:{split_input[4]}:{split_input[5]}" elif separator == ':': if self.tag.full_precision: return f"{split_input[0]}:{split_input[1]}:{split_input[2]}.{split_input[3]}" diff --git a/mininterface/tk_interface/utils.py b/mininterface/tk_interface/utils.py index 0bf858e..9e987f3 100644 --- a/mininterface/tk_interface/utils.py +++ b/mininterface/tk_interface/utils.py @@ -136,7 +136,7 @@ def _fetch(variable): # Calendar elif isinstance(tag, DatetimeTag): grid_info = widget.grid_info() - widget.grid_forget() # HERE + widget.grid_forget() nested_frame = DateEntryFrame(master, tk_app, tag, variable) nested_frame.grid(row=grid_info['row'], column=grid_info['column']) widget = nested_frame.spinbox diff --git a/mininterface/types.py b/mininterface/types.py index f9d6833..08385cc 100644 --- a/mininterface/types.py +++ b/mininterface/types.py @@ -222,7 +222,7 @@ class Env: time: bool = False """ The time part is active """ - full_precision: Optional[bool] = None + full_precision: bool = False """ Include full time precison, seconds, microseconds. """ def __post_init__(self): @@ -230,9 +230,9 @@ def __post_init__(self): if self.annotation: self.date = issubclass(self.annotation, date) self.time = issubclass(self.annotation, time) or issubclass(self.annotation, datetime) - if not self.time and self.full_precision: - self.full_precision = False - # NOTE: self.full_precision ... + # NOTE: remove + # if not self.time and self.full_precision: + # self.full_precision = False def _make_default_value(self): return datetime.now() diff --git a/tests/configs.py b/tests/configs.py index 0113cee..928119a 100644 --- a/tests/configs.py +++ b/tests/configs.py @@ -177,6 +177,7 @@ class DatetimeTagClass: p1: datetime = datetime.fromisoformat("2024-09-10 17:35:39.922044") p2: time = time.fromisoformat("17:35:39.922044") p3: date = date.fromisoformat("2024-09-10") + pAnnot: Annotated[date, Tag(name="hello")] = datetime.fromisoformat("2024-09-10 17:35:39.922044") @dataclass diff --git a/tests/tests.py b/tests/tests.py index 1d47785..b5c11f4 100644 --- a/tests/tests.py +++ b/tests/tests.py @@ -2,7 +2,7 @@ import os import sys from contextlib import contextmanager, redirect_stderr, redirect_stdout -from datetime import datetime +from datetime import datetime, date from io import StringIO from pathlib import Path, PosixPath from types import NoneType, SimpleNamespace @@ -551,13 +551,18 @@ class TestTypes(TestAbstract): def test_datetime_tag(self): m = runm(DatetimeTagClass) d = dataclass_to_tagdict(m.env)[""] - for key, expected_date, expected_time in [("p1", True, True), ("p2", False, True), ("p3", True, False)]: + d["extern"] = dict_to_tagdict({"extern": date.fromisoformat("2024-09-10")})["extern"] + + for key, expected_date, expected_time in [("p1", True, True), ("p2", False, True), ("p3", True, False), + ("pAnnot", True, False), + ("extern", True, False)]: tag = d[key] self.assertIsInstance(tag, DatetimeTag) self.assertEqual(expected_date, tag.date) self.assertEqual(expected_time, tag.time) + class TestRun(TestAbstract): def test_run_ask_empty(self): with self.assertOutputs("Asking the form SimpleEnv(test=False, important_number=4)"):