From 17f15a2ef6696e11a7f26fc01114d73542816921 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 Jan 2024 10:54:57 +0100 Subject: [PATCH 1/5] Update readthedocs-sphinx-search requirement in /requirements (#33) Updates the requirements on [readthedocs-sphinx-search](https://github.com/readthedocs/readthedocs-sphinx-search) to permit the latest version. - [Changelog](https://github.com/readthedocs/readthedocs-sphinx-search/blob/main/CHANGELOG.rst) - [Commits](https://github.com/readthedocs/readthedocs-sphinx-search/compare/0.3.2...0.3.2) --- updated-dependencies: - dependency-name: readthedocs-sphinx-search dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements/docs.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/docs.txt b/requirements/docs.txt index d989e73..f1d0516 100644 --- a/requirements/docs.txt +++ b/requirements/docs.txt @@ -4,5 +4,5 @@ sphinx-copybutton~=0.5.2 furo==2023.9.10 enum-tools[sphinx]~=0.11.0 sphinx-design[furo]~=0.5.0 -readthedocs-sphinx-search~=0.3.1 +readthedocs-sphinx-search~=0.3.2 sphinxcontrib-svg2pdfconverter~=1.2.2 From 30174682d00973908022f2fe85c1904926046075 Mon Sep 17 00:00:00 2001 From: David Hozic Date: Wed, 24 Jan 2024 14:45:13 +0100 Subject: [PATCH 2/5] 1.3.1 --- docs/source/changelog.rst | 4 ++++ tkclasswiz/__init__.py | 2 +- tkclasswiz/convert.py | 9 ++++++--- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/docs/source/changelog.rst b/docs/source/changelog.rst index aad0621..0967fb5 100644 --- a/docs/source/changelog.rst +++ b/docs/source/changelog.rst @@ -23,6 +23,10 @@ Glossary --------------------- Releases --------------------- +v1.3.1 +================ +- Fixed :func:`tkclasswiz.convert.convert_objects_to_script` not including enum imports. + v1.3.0 ================ diff --git a/tkclasswiz/__init__.py b/tkclasswiz/__init__.py index 1bc7e3d..e9d57a3 100644 --- a/tkclasswiz/__init__.py +++ b/tkclasswiz/__init__.py @@ -26,7 +26,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ -__version__ = "1.3.0" +__version__ = "1.3.1" from .object_frame import * from .annotations import * diff --git a/tkclasswiz/convert.py b/tkclasswiz/convert.py index 5dc1e15..73b1556 100644 --- a/tkclasswiz/convert.py +++ b/tkclasswiz/convert.py @@ -240,7 +240,7 @@ def convert_objects_to_script(object: Union[ObjectInfo, list, tuple, set, str]): object_str = f"{object.class_.__name__}(\n " attr_str = [] for attr, value in object.data.items(): - if isinstance(value, (ObjectInfo, list, tuple, set)): + if isinstance(value, (ObjectInfo, list, tuple, set, Enum)): value, import_data_ = convert_objects_to_script(value) import_data.extend(import_data_) @@ -248,8 +248,6 @@ def convert_objects_to_script(object: Union[ObjectInfo, list, tuple, set, str]): value, _ = convert_objects_to_script(value) attr_str.append(f"{attr}={value},\n") - if issubclass(type(value), Enum): - import_data.append(f"from {type(value).__module__} import {type(value).__name__}") import_data.append(f"from {object.class_.__module__} import {object.class_.__name__}") @@ -265,6 +263,11 @@ def convert_objects_to_script(object: Union[ObjectInfo, list, tuple, set, str]): _list_data = " ".join(''.join(_list_data).splitlines(keepends=True)) + "]" object_data.append(_list_data) + + elif isinstance(object, Enum): + import_data.append(f"from {object.__module__} import {type(object).__name__}") + object_data.append(str(object)) + else: if isinstance(object, str): object = object.replace("\\", "\\\\").replace("\n", "\\n").replace('"', '\\"') From 81eb5c3df0ac1e1d17bd421b9180856e802e733f Mon Sep 17 00:00:00 2001 From: David Hozic Date: Thu, 25 Jan 2024 19:22:49 +0100 Subject: [PATCH 3/5] feat: Literal and enum list definition (#34) * Iterable literal and singleton definition * docs --- docs/source/changelog.rst | 6 +++ tkclasswiz/__init__.py | 2 +- tkclasswiz/object_frame/frame_base.py | 37 +++++++++++++---- tkclasswiz/object_frame/frame_iterable.py | 50 ++++++++++++++++++----- tkclasswiz/object_frame/frame_struct.py | 10 ++++- 5 files changed, 86 insertions(+), 19 deletions(-) diff --git a/docs/source/changelog.rst b/docs/source/changelog.rst index 0967fb5..eedc933 100644 --- a/docs/source/changelog.rst +++ b/docs/source/changelog.rst @@ -23,6 +23,12 @@ Glossary --------------------- Releases --------------------- + +v1.4.0 +================ +- Definition of enums and literal values inside iterable types. + + v1.3.1 ================ - Fixed :func:`tkclasswiz.convert.convert_objects_to_script` not including enum imports. diff --git a/tkclasswiz/__init__.py b/tkclasswiz/__init__.py index e9d57a3..602ac79 100644 --- a/tkclasswiz/__init__.py +++ b/tkclasswiz/__init__.py @@ -26,7 +26,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ -__version__ = "1.3.1" +__version__ = "1.4.0" from .object_frame import * from .annotations import * diff --git a/tkclasswiz/object_frame/frame_base.py b/tkclasswiz/object_frame/frame_base.py index cff106f..6e4f977 100644 --- a/tkclasswiz/object_frame/frame_base.py +++ b/tkclasswiz/object_frame/frame_base.py @@ -121,6 +121,36 @@ def get_cls_name(cls: Any, args: bool = False) -> str: def set_origin_window(cls, window: "ObjectEditWindow"): cls.origin_window = window + @classmethod + def filter_literals(cls, types_: Iterable): + "Returns only the literal types from ``types_``" + return [x for x in types_ if get_origin(x) is Literal] + + @classmethod + def check_literals(cls, value: str, literal_types: Iterable): + """ + Checks if the ``value`` is a correct iterable. + + Parameters + ------------- + value: str + The string value to be checked. + types: Iterable[Literal] + An iterable of accepted literals. + """ + allowed_values = [] + for literal_type in literal_types: + args = get_args(literal_type) + if value in args: + return value + + allowed_values.extend(args) + + raise ValueError( + f"'{value}' (a string) is not one of accepted types and does not match any literal values.\n" + f"'Allowed literals: {allowed_values}." + ) + @classmethod def cast_type(cls, value: Any, types: Iterable): """ @@ -136,13 +166,6 @@ def cast_type(cls, value: Any, types: Iterable): dict: lambda v: convert_to_object_info(json.loads(v)) } - # Validate literals - if get_origin(types[0]) is Literal: - if value not in (args := get_args(types[0])): - raise ValueError(f"'{value}' is not a valid value'. Accepted: {args}") - - return value - for type_ in filter(lambda t: t.__module__ == "builtins", types): with suppress(Exception): cast_funct = CAST_FUNTIONS.get(type_) diff --git a/tkclasswiz/object_frame/frame_iterable.py b/tkclasswiz/object_frame/frame_iterable.py index 3af139f..2f1bd1d 100644 --- a/tkclasswiz/object_frame/frame_iterable.py +++ b/tkclasswiz/object_frame/frame_iterable.py @@ -1,16 +1,18 @@ -from typing import get_args, get_origin, Any, List, Union +from typing import get_args, get_origin, Any, List, Literal +from inspect import isclass, isfunction from functools import partial +from enum import Enum +from ..doc import doc_category +from ..utilities import * from ..convert import * from ..dpi import * -from ..utilities import * -from ..doc import doc_category -from ..storage import * from ..messagebox import Messagebox from ..extensions import extendable -from .frame_base import * from .tooltip import ListboxTooltip +from .frame_base import * +from ..storage import * import tkinter as tk import tkinter.ttk as ttk @@ -65,7 +67,7 @@ def __init__( allow_save = True ): dpi_5 = dpi_scaled(5) - super().__init__(class_, return_widget, parent, old_data, check_parameters, allow_save) + super().__init__(self.convert_types(class_)[0], return_widget, parent, old_data, check_parameters, allow_save) self.storage_widget = w = ListBoxScrolled(self.frame_main, height=20) ListboxTooltip(self.storage_widget, 0) @@ -76,7 +78,7 @@ def __init__( ttk.Button(frame_cp, text="Copy", command=w.save_to_clipboard).pack(side="left", fill=tk.X, expand=True) ttk.Button(frame_cp, text="Paste", command=w.paste_from_clipboard).pack(side="left", fill=tk.X, expand=True) - menubtn = ttk.Menubutton(frame_edit_remove, text="Add object") + menubtn = ttk.Menubutton(frame_edit_remove, text="New") menu = tk.Menu(menubtn) menubtn.configure(menu=menu) menubtn.pack() @@ -88,8 +90,31 @@ def __init__( ttk.Button(frame_up_down, text="Up", command=lambda: w.move_selection(-1)).pack(side="left", fill=tk.X, expand=True) ttk.Button(frame_up_down, text="Down", command=lambda: w.move_selection(1)).pack(side="left", fill=tk.X, expand=True) - for arg in get_args(self.convert_types(class_)[0]): - menu.add_command(label=self.get_cls_name(arg, True), command=partial(self.new_object_frame, arg, w)) + self._list_args = get_args(self.class_) + insert_items = [] + for arg in self._list_args: + if arg is None: + insert_items.append(...) + insert_items.append(None) + elif issubclass_noexcept(arg, Enum): + insert_items.append(...) + insert_items.extend(list(arg)) + elif get_origin(arg) is Literal: + insert_items.append(...) + insert_items.extend(get_args(arg)) + elif isclass(arg) or isfunction(arg): + menu.add_command(label=self.get_cls_name(arg, True), command=partial(self.new_object_frame, arg, w)) + + if insert_items: + menu_insert = tk.Menu(menu) + for item in insert_items: + if item is Ellipsis: + menu_insert.add_separator() + else: + label = f"'{item}'" if isinstance(item, str) else str(item) + menu_insert.add_command(label=label, command=partial(w.insert, tk.END, item)) + + menu.add_cascade(label=">Insert", menu=menu_insert) w.pack(side="left", fill=tk.BOTH, expand=True) @@ -106,7 +131,12 @@ def get_gui_data(self) -> Any: return self.storage_widget.get() def to_object(self): - return self.get_gui_data() # List items are not to be converted + data = self.get_gui_data() # List items are not to be converted + for d in data: + if isinstance(d, str) and str not in self._list_args: + self.check_literals(d, self.filter_literals(self._list_args)) + + return data def _edit_selected(self): selection = self.storage_widget.curselection() diff --git a/tkclasswiz/object_frame/frame_struct.py b/tkclasswiz/object_frame/frame_struct.py index 961eca4..4730129 100644 --- a/tkclasswiz/object_frame/frame_struct.py +++ b/tkclasswiz/object_frame/frame_struct.py @@ -255,7 +255,15 @@ def to_object(self, *, ignore_checks = False) -> ObjectInfo: if not value: continue - value = self.cast_type(value, types_) + try: + value = self.cast_type(value, types_) + except TypeError as exc: + # Perhaps it's a valid literal: + literal_types = self.filter_literals(types_) + if not literal_types: + raise + + self.check_literals(value, literal_types) map_[attr] = value From e7c5fc5b803932d73519838e163dedfeaf9eaf90 Mon Sep 17 00:00:00 2001 From: David Hozic Date: Sat, 27 Jan 2024 14:07:39 +0100 Subject: [PATCH 4/5] Deprecated parameters support (#35) * Deprecated parameters * Better deprecations * Docs --- docs/source/changelog.rst | 1 + docs/source/guide/deprecations.rst | 34 +++++ .../new_define_frame_struct_deprecation.png | Bin 0 -> 39157 bytes docs/source/guide/index.rst | 1 + docs/source/scripts/generate_autodoc.py | 2 +- tkclasswiz/__init__.py | 1 + tkclasswiz/annotations.py | 2 +- tkclasswiz/deprecation.py | 78 ++++++++++ tkclasswiz/object_frame/frame_iterable.py | 44 ++++-- tkclasswiz/object_frame/frame_struct.py | 138 +++++++++++------- 10 files changed, 232 insertions(+), 69 deletions(-) create mode 100644 docs/source/guide/deprecations.rst create mode 100644 docs/source/guide/images/new_define_frame_struct_deprecation.png create mode 100644 tkclasswiz/deprecation.py diff --git a/docs/source/changelog.rst b/docs/source/changelog.rst index eedc933..c4aa6b8 100644 --- a/docs/source/changelog.rst +++ b/docs/source/changelog.rst @@ -27,6 +27,7 @@ Releases v1.4.0 ================ - Definition of enums and literal values inside iterable types. +- Ability to register deprecated parameters. v1.3.1 diff --git a/docs/source/guide/deprecations.rst b/docs/source/guide/deprecations.rst new file mode 100644 index 0000000..f6a11db --- /dev/null +++ b/docs/source/guide/deprecations.rst @@ -0,0 +1,34 @@ +======================== +Deprecations +======================== + +TkClassWizard allows to users to deprecate different classes, classes' parameters and types under classes' parameter. + +All the deprecations can be made with :func:`tkclasswiz.deprecation.register_deprecated` function. +The function has 3 modes: + +- Deprecate class globally (only ``cls`` parameter given) +- Deprecate a class parameter (``cls`` and ``parameter`` both given) +- Deprecate a type under class parameter (``cls``, ``parameter`` and ``types`` are all given) + Please note that ``types`` is a variadic parameter, which means multiple types can be passed by + just separating them with a comma. + + +.. code-block:: python + :caption: Deprecate usage of type :class:`~datetime.timedelta` under parameter ``next_service`` of class ``Car``. + + from datetime import timedelta, datetime + import tkclasswiz as wiz + + class Car: + def __init__(self, name: str, next_service: timedelta | datetime): + ... # Implementation + + wiz.register_deprecated(Car, "next_service", timedelta) + ... # Other needed code + + +The above example will create the following definition window: + +.. image:: ./images/new_define_frame_struct_deprecation.png + diff --git a/docs/source/guide/images/new_define_frame_struct_deprecation.png b/docs/source/guide/images/new_define_frame_struct_deprecation.png new file mode 100644 index 0000000000000000000000000000000000000000..1b1de22cb1c48dc0cfb88a36f6d8b09ebae1b4bf GIT binary patch literal 39157 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%0D-CS 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 c%LfZTp?pqY!$a=ojEi+fVrUAAe*%^8f$< literal 0 HcmV?d00001 diff --git a/docs/source/guide/index.rst b/docs/source/guide/index.rst index 0266086..6001813 100644 --- a/docs/source/guide/index.rst +++ b/docs/source/guide/index.rst @@ -18,3 +18,4 @@ Index: customrepr aliasing generics + deprecations diff --git a/docs/source/scripts/generate_autodoc.py b/docs/source/scripts/generate_autodoc.py index c0c69eb..cf63c3f 100644 --- a/docs/source/scripts/generate_autodoc.py +++ b/docs/source/scripts/generate_autodoc.py @@ -98,7 +98,7 @@ if manual: _async_ = ":async:" if inspect.iscoroutinefunction(item) else "" annotations = get_type_hints(item) - return_ano = annotations.pop("return") + return_ano = annotations.pop("return", None) doc_str = inspect.cleandoc(item.__doc__) # Replace titles with list titles doc_str_titles = re.findall(r"[A-z]+\n-+", doc_str) diff --git a/tkclasswiz/__init__.py b/tkclasswiz/__init__.py index 602ac79..f87ef55 100644 --- a/tkclasswiz/__init__.py +++ b/tkclasswiz/__init__.py @@ -37,3 +37,4 @@ from .storage import * from .utilities import * from .aliasing import * +from .deprecation import * diff --git a/tkclasswiz/annotations.py b/tkclasswiz/annotations.py index 2d264ef..5f16919 100644 --- a/tkclasswiz/annotations.py +++ b/tkclasswiz/annotations.py @@ -1,8 +1,8 @@ """ Module used for managing annotations. """ -from datetime import datetime, timedelta, timezone from typing import Union, Optional, get_args, Generic, get_origin, get_type_hints +from datetime import datetime, timedelta, timezone from contextlib import suppress from inspect import isclass from .doc import doc_category diff --git a/tkclasswiz/deprecation.py b/tkclasswiz/deprecation.py new file mode 100644 index 0000000..437f347 --- /dev/null +++ b/tkclasswiz/deprecation.py @@ -0,0 +1,78 @@ +""" +Module can be used to mark deprecated classes / parameters / parameter types. +""" +from typing import overload + +from .doc import doc_category + + +__all__ = ( + "register_deprecated", + "is_deprecated", +) + + +@overload +@doc_category("Deprecations", manual=True) +def register_deprecated(cls: type): + """ + Mark ``cls`` deprecated globally. This function cannot be + used on built-in Python classes. + """ + +@overload +@doc_category("Deprecations", manual=True) +def register_deprecated(cls: type, parameter: str): + """ + Marks a ``parameter`` to be deprecated under specific ``cls``. + """ + +@overload +@doc_category("Deprecations", manual=True) +def register_deprecated(cls: type, parameter: str, *types: type): + """ + Marks multiple ``types`` to be deprecated for a certain ``parameter`` under ``cls``. + """ + +def register_deprecated(cls, parameter: str = None, *types: type): + if parameter is None: + cls.__deprecated__ = True + + elif not types: + cls.__wiz_deprecated_params__ = getattr(cls, "__wiz_deprecated_params__", set()) + cls.__wiz_deprecated_params__.add(parameter) + else: + cls.__wiz_deprecated_param_types__ = getattr(cls, "__wiz_deprecated_param_types__", dict()) + cls.__wiz_deprecated_param_types__[parameter] = cls.__wiz_deprecated_param_types__.get(parameter, set()) + cls.__wiz_deprecated_param_types__[parameter].update(types) + +@overload +@doc_category("Deprecations", manual=True) +def is_deprecated(cls: type): + """ + Checks if ``cls`` is deprecated globally. + """ + +@overload +@doc_category("Deprecations", manual=True) +def is_deprecated(cls: type, parameter: str): + """ + Checks if ``parameter`` is deprecated under ``cls``. + """ + +@overload +@doc_category("Deprecations", manual=True) +def is_deprecated(cls: type, parameter: str, type_: type): + """ + Checks if ``type_`` is deprecated for a certain ``parameter`` under ``cls``. + """ + +def is_deprecated(cls: type, parameter: str = None, type_: type = None): + if parameter is None: + return hasattr(cls, "__deprecated__") + + if type_ is None: + params = getattr(cls, "__wiz_deprecated_params__", set()) + return parameter in params + + return type_ in getattr(cls, "__wiz_deprecated_param_types__", dict()).get(parameter, set()) diff --git a/tkclasswiz/object_frame/frame_iterable.py b/tkclasswiz/object_frame/frame_iterable.py index 2f1bd1d..a20c1b9 100644 --- a/tkclasswiz/object_frame/frame_iterable.py +++ b/tkclasswiz/object_frame/frame_iterable.py @@ -4,6 +4,7 @@ from enum import Enum from ..doc import doc_category +from ..deprecation import * from ..utilities import * from ..convert import * from ..dpi import * @@ -67,7 +68,7 @@ def __init__( allow_save = True ): dpi_5 = dpi_scaled(5) - super().__init__(self.convert_types(class_)[0], return_widget, parent, old_data, check_parameters, allow_save) + super().__init__(class_, return_widget, parent, old_data, check_parameters, allow_save) self.storage_widget = w = ListBoxScrolled(self.frame_main, height=20) ListboxTooltip(self.storage_widget, 0) @@ -78,7 +79,7 @@ def __init__( ttk.Button(frame_cp, text="Copy", command=w.save_to_clipboard).pack(side="left", fill=tk.X, expand=True) ttk.Button(frame_cp, text="Paste", command=w.paste_from_clipboard).pack(side="left", fill=tk.X, expand=True) - menubtn = ttk.Menubutton(frame_edit_remove, text="New") + menubtn = ttk.Menubutton(frame_edit_remove, text="Add") menu = tk.Menu(menubtn) menubtn.configure(menu=menu) menubtn.pack() @@ -91,8 +92,31 @@ def __init__( ttk.Button(frame_up_down, text="Down", command=lambda: w.move_selection(1)).pack(side="left", fill=tk.X, expand=True) self._list_args = get_args(self.class_) - insert_items = [] + args_normal = [] + args_depr = [] for arg in self._list_args: + if is_deprecated(arg): + args_depr.append(arg) + else: + args_normal.append(arg) + + self._create_add_menu(args_normal, menu) + if args_depr: + menu_depr = tk.Menu() + self._create_add_menu(args_depr, menu_depr) + menu.add_cascade(label=">Deprecated", menu=menu_depr) + + w.pack(side="left", fill=tk.BOTH, expand=True) + + if old_data is not None: + self.load(old_data) + + self.remember_gui_data() + + def _create_add_menu(self, args: list, menu: tk.Menu): + insert_items = [] + widget = self.storage_widget + for arg in args: if arg is None: insert_items.append(...) insert_items.append(None) @@ -103,7 +127,10 @@ def __init__( insert_items.append(...) insert_items.extend(get_args(arg)) elif isclass(arg) or isfunction(arg): - menu.add_command(label=self.get_cls_name(arg, True), command=partial(self.new_object_frame, arg, w)) + menu.add_command( + label=f"New {self.get_cls_name(arg, True)}", + command=partial(self.new_object_frame, arg, widget) + ) if insert_items: menu_insert = tk.Menu(menu) @@ -112,17 +139,10 @@ def __init__( menu_insert.add_separator() else: label = f"'{item}'" if isinstance(item, str) else str(item) - menu_insert.add_command(label=label, command=partial(w.insert, tk.END, item)) + menu_insert.add_command(label=label, command=partial(widget.insert, tk.END, item)) menu.add_cascade(label=">Insert", menu=menu_insert) - w.pack(side="left", fill=tk.BOTH, expand=True) - - if old_data is not None: - self.load(old_data) - - self.remember_gui_data() - def load(self, old_data: List[Any]): self.storage_widget.insert(tk.END, *old_data) self.old_gui_data = old_data diff --git a/tkclasswiz/object_frame/frame_struct.py b/tkclasswiz/object_frame/frame_struct.py index 4730129..5629164 100644 --- a/tkclasswiz/object_frame/frame_struct.py +++ b/tkclasswiz/object_frame/frame_struct.py @@ -12,6 +12,7 @@ from ..messagebox import Messagebox from ..extensions import extendable from ..annotations import get_annotations +from ..deprecation import * from ..doc import doc_category from .frame_base import * @@ -132,61 +133,55 @@ def load_template(): state="normal" if self.allow_save else "disabled" ) self.entry_nick.pack(anchor=tk.W, padx=dpi_5_h, pady=dpi_5) + annotations_depr = {k:v for k,v in annotations.items() if is_deprecated(class_, k)} + for k in annotations_depr: + del annotations[k] + + self._create_fields(annotations, additional_values, self.frame_main) + if annotations_depr: + ttk.Separator(self.frame_main).pack(fill=tk.X) + ttk.Label(self.frame_main, text="Deprecated").pack(anchor=tk.W) + self._create_fields(annotations_depr, additional_values, self.frame_main) + + if old_data is not None: # Edit + self.load(old_data) + + self.remember_gui_data() + + def _create_fields(self, annotations: dict[str, type], additional_values: dict, frame: ttk.Frame): + label_width = max(*map(len, annotations), 15) - 2 - def fill_values(k: str, entry_types: list, menu: tk.Menu, combo: ComboBoxObjects): - "Fill ComboBox values based on types in ``entry_types`` and create New buttons" - any_filled = False - for entry_type in entry_types: - if get_origin(entry_type) is Literal: - values = get_args(entry_type) - combo["values"] = values - # tkvalid.add_option_validation(combo, values) - elif entry_type is bool: - combo.insert(tk.END, True) - combo.insert(tk.END, False) - # tkvalid.add_option_validation(combo, ["True", "False", '']) - elif issubclass_noexcept(entry_type, Enum): - combo["values"] = values = [en for en in entry_type] - # tkvalid.add_option_validation(combo, list(map(str, values)) + ['']) - elif entry_type is type(None): - if bool not in entry_types: - combo.insert(tk.END, None) - else: # Type not supported, try other types - any_filled = True - if self.allow_save: - menu.add_command( - label=f"New {self.get_cls_name(entry_type, args=True)}", - command=partial(self.new_object_frame, entry_type, combo) - ) - - # Additional values to be inserted into ComboBox - for value in additional_values.get(k, []): - combo.insert(tk.END, value) - - # The class of last list like type. Needed when "Edit selected" is used - # since we don't know what type it was - return any_filled - - max_attr_name_len = max(*map(len, annotations), 15) - 2 - for (k, v) in annotations.items(): # Init widgets entry_types = self.convert_types(v) - frame_annotated = ttk.Frame(self.frame_main) - frame_annotated.pack(fill=tk.BOTH, expand=True, pady=dpi_5) - ttk.Label(frame_annotated, text=k, width=max_attr_name_len).pack(side="left") - - bnt_new_menu = ttk.Menubutton(frame_annotated, text="New") - menu_new = tk.Menu(bnt_new_menu) - bnt_new_menu.configure(menu=menu_new) + frame_annotated = ttk.Frame(frame) + frame_annotated.pack(fill=tk.BOTH, expand=True, pady=5) + ttk.Label(frame_annotated, text=k, width=label_width).pack(side="left") # Storage widget with the tooltip for displaying # nicknames on ObjectInfo instances w = combo = ComboBoxObjects(frame_annotated) ComboboxTooltip(w) - # Fill values - any_filled = fill_values(k, entry_types, menu_new, combo) + bnt_new_menu = ttk.Menubutton(frame_annotated, text="New") + menu_new = tk.Menu(bnt_new_menu) + bnt_new_menu.configure(menu=menu_new) + + deprecated_param_types = set(t for t in entry_types if is_deprecated(self.class_, k, t)) + normal_types = [t for t in entry_types if t not in deprecated_param_types] + any_filled = self._fill_field_values(k, normal_types, menu_new, combo, additional_values) + + if deprecated_param_types: + menu_depr = tk.Menu(menu_new) + menu_new.add_cascade(label=">Deprecated", menu=menu_depr) + any_filled = self._fill_field_values( + k, + list(deprecated_param_types), + menu_depr, + combo, + additional_values + ) or any_filled + bnt_edit = ttk.Button( frame_annotated, text="🖋️", @@ -203,20 +198,53 @@ def fill_values(k: str, entry_types: list, menu: tk.Menu, combo: ComboBoxObjects if not (any_filled and self.allow_save): bnt_new_menu.configure(state="disabled") - if not any_filled: - bnt_edit.configure(state="disabled") - - bnt_copy_paste.pack(side="right", padx=dpi_5_h) - bnt_edit.pack(side="right", padx=dpi_5_h) - bnt_new_menu.pack(side="right", padx=dpi_5_h) - combo.pack(fill=tk.X, side="right", expand=True, padx=dpi_5_h) + bnt_copy_paste.pack(side="right", padx=2) + bnt_edit.pack(side="right", padx=2) + bnt_new_menu.pack(side="right", padx=2) + combo.pack(fill=tk.X, side="right", expand=True, padx=2) self._map[k] = (w, entry_types) - if old_data is not None: # Edit - self.load(old_data) + def _fill_field_values( + self, + k: str, + entry_types: list, + menu: tk.Menu, + combo: ComboBoxObjects, + additional_values: dict + ): + "Fill ComboBox values based on types in ``entry_types`` and create New buttons" + any_filled = False + for entry_type in entry_types: + if get_origin(entry_type) is Literal: + values = get_args(entry_type) + combo["values"] = values + # tkvalid.add_option_validation(combo, values) + elif entry_type is bool: + combo.insert(tk.END, True) + combo.insert(tk.END, False) + # tkvalid.add_option_validation(combo, ["True", "False", '']) + elif issubclass_noexcept(entry_type, Enum): + combo["values"] = values = [en for en in entry_type] + # tkvalid.add_option_validation(combo, list(map(str, values)) + ['']) + elif entry_type is type(None): + if bool not in entry_types: + combo.insert(tk.END, None) + else: # Type not supported, try other types + any_filled = True + if self.allow_save: + menu.add_command( + label=f"New {self.get_cls_name(entry_type, args=True)}", + command=partial(self.new_object_frame, entry_type, combo) + ) - self.remember_gui_data() + # Additional values to be inserted into ComboBox + for value in additional_values.get(k, []): + combo.insert(tk.END, value) + # The class of last list like type. Needed when "Edit selected" is used + # since we don't know what type it was + return any_filled + @extendable def load(self, old_data: ObjectInfo): data = old_data.data From a4b2f534dc5b04f87bc1bc85efdd1efa3b8d4f19 Mon Sep 17 00:00:00 2001 From: David Hozic Date: Sat, 27 Jan 2024 16:51:55 +0100 Subject: [PATCH 5/5] Flag definition (#36) --- docs/source/changelog.rst | 1 + docs/source/guide/defining.rst | 42 +++++++ docs/source/guide/deprecations.rst | 8 +- .../guide/images/new_define_frame_flag.png | Bin 0 -> 20768 bytes .../images/new_define_frame_flag_values.png | Bin 0 -> 24930 bytes tkclasswiz/object_frame/frame_base.py | 6 +- tkclasswiz/object_frame/frame_flag.py | 111 ++++++++++++++++++ tkclasswiz/object_frame/frame_struct.py | 4 +- tkclasswiz/object_frame/window.py | 43 ++++--- tkclasswiz/storage.py | 29 +++++ 10 files changed, 215 insertions(+), 29 deletions(-) create mode 100644 docs/source/guide/images/new_define_frame_flag.png create mode 100644 docs/source/guide/images/new_define_frame_flag_values.png create mode 100644 tkclasswiz/object_frame/frame_flag.py diff --git a/docs/source/changelog.rst b/docs/source/changelog.rst index c4aa6b8..43188a2 100644 --- a/docs/source/changelog.rst +++ b/docs/source/changelog.rst @@ -28,6 +28,7 @@ v1.4.0 ================ - Definition of enums and literal values inside iterable types. - Ability to register deprecated parameters. +- Ability to define :class:`enum.Flag` like flags. v1.3.1 diff --git a/docs/source/guide/defining.rst b/docs/source/guide/defining.rst index 74c7297..690c5a4 100644 --- a/docs/source/guide/defining.rst +++ b/docs/source/guide/defining.rst @@ -172,6 +172,48 @@ This is how our frame looks after defining 4 ``Wheel`` objects: :width: 15cm +Defining (enum) flags +======================= +Let's say that our car also needs a type designation. The ``Car`` class may contain an enum flag parameter, +indicating this designation. + +.. code-block:: python + + from enum import Flag, auto + + class Designation(Flag): + FAMILIY = auto() + SINGLE_PERSON = auto() + HEAVY_TRANSPORT = auto() + + class Car: + def __init__( + self, + ... + designation: Designation, + ): + ... + +When we try to define the ``designation`` parameter from the above example, +the following window will be displayed. + +.. image:: ./images/new_define_frame_flag.png + :width: 15cm + +The flag definition frame has 4 main elements: + +- a placeholder for the current value, +- a Combobox (dropdown menu) for selecting a flag +- an add button for adding the selected (in Combobox) flag to the current flag value. +- a remove button for removing the selected (in Combobox) flag from the current flag value. + +The following image shows show two added flags look inside the placeholder. + +.. image:: ./images/new_define_frame_flag_values.png + :width: 15cm + +The flag value can be saved like any other type. It is done by clicking on the "Save" button. + Final definition ================= diff --git a/docs/source/guide/deprecations.rst b/docs/source/guide/deprecations.rst index f6a11db..29a6995 100644 --- a/docs/source/guide/deprecations.rst +++ b/docs/source/guide/deprecations.rst @@ -2,14 +2,14 @@ Deprecations ======================== -TkClassWizard allows to users to deprecate different classes, classes' parameters and types under classes' parameter. +TkClassWizard allows users to deprecate different classes, class's parameters and types under a class's parameter. -All the deprecations can be made with :func:`tkclasswiz.deprecation.register_deprecated` function. +All the deprecations can be made with the :func:`tkclasswiz.deprecation.register_deprecated` function. The function has 3 modes: - Deprecate class globally (only ``cls`` parameter given) -- Deprecate a class parameter (``cls`` and ``parameter`` both given) -- Deprecate a type under class parameter (``cls``, ``parameter`` and ``types`` are all given) +- Deprecate a class's parameter (``cls`` and ``parameter`` both given) +- Deprecate a type under class's parameter (``cls``, ``parameter`` and ``types`` are all given). Please note that ``types`` is a variadic parameter, which means multiple types can be passed by just separating them with a comma. diff --git a/docs/source/guide/images/new_define_frame_flag.png b/docs/source/guide/images/new_define_frame_flag.png new file mode 100644 index 0000000000000000000000000000000000000000..1c04237b41e1ef61df8b42c1bf8d20c89506b2d7 GIT binary patch literal 20768 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:cvf;QIKGIEuAAS!0Ty!L zmAjDw`ahJ9c8N?F{@l6xHN)Qv-3&ulqhk!M8vM0kmG-x&-LO3%JK;wb!hEK3&izU~ z4t7)4FhuMTrKYq=2Cbf2s(1eSfL6}koVNsS<%`kwxYaA@mVQh!HrgkS?q~Pd{7nyZ zeQEw-To+CFbBP{5!ED-eGd%AZSXC z17I3~U~jqeRfX>XW}+saVpAL3-SEXrn7a#|L#yKixpH zeg;;hX9B^e?$S3`W(c$UbiK4LbXlGM0erF=aO{%%71m0VFTbedh%U7P7(qCF&n(;g z%B1{v65Lau#%oJeITZI*-j7ed{<$kx6cQ3*)c3E@dQ5NhTEzp&MKL<|?n#q;9=WUu z`~n!y!NvYorYFJ75(za0W(}Xj(eD(mGox2C#I-A_K!#KbOOpcK@Vv?Yi~Su17}@w| zoRSg}FMqFh-aTgK)2{|=q_MW^g0W&J%Kl#!=N{Gck;QRZlxiyiS_BOxAgA(D0;r%o z42XzZ11$m~k3?u7Eg^(839mpB1q3RJZlMMW;iZD05Ggd-ghxP@YC<3=V1U4;l}8K+ zks#1u3_Cx2cF)=Wf9KB3o!`to^S$?emQER~7r?#rzbJUx)Ig+fG}p7OF=tmBR;Sh2 zr@kKh$Hxb-061g6$0@oLGTx9-ANXHyB(JXJ1_#G}}g!AxRQ$*J@LZ=96ns5+$2N zkZ}&Ngfvi;77qGrOq7AeE&mrKi{kxWNTp15t$*P;Oh@`95fItW0x{*XFmo2L6%aJ# z*@gkr#fPC=%!~e69CIek6cy4dGR-kY6eywO#)pIT+Biu=GO;Smxb&EQ*8rqFLv5(W z5DTQQA0dc7h~~Fl;1D9H(4iZ8@0R@UXpm#McTD}%rcm0slyZlGFFF#c_xLpS=P3b2?Q0UMEFFaSlRJ+WGLy=U1{cJ{3bq%}jA2 z;C|fAV-y)*%~p4K2^64)&;q~Wj@NJcRDV{nrjlQTm_^WLRan9q+U33|`NoSneMp_x zW3_xIsiY{o$r5koKFy`5$CEN-XrZ17&&-$S-UGDu89TjP+;M?Z9d>K&gOim=hE5x5 z0T4-W;iW)m$N&C41aTTMkR&ZDsu*2k0HtsSi{$o^*_XdL^zn>>lzRkyXvcLk-WlUz zYbX1HpQy`yW+7i^->T|YXBzd@bP0Y6I>-%K@Yp-`Xy>BnNhta_1}9zqRFCx|qgql@ zrqd1?E(~p7q_fB0zE{jIKW|VSEt};~lQQU)MUzq~pjC_nH`qC25LeE-s5kO0sjqx^ zJr|3BeQ$k{WY!CuXb00^tAcNRKi|H;kzLj3N87CVS*(z}<)?mC-xY3q+p~PFC)yix z0P8C2UIWxAA(Ah1DU%L{9qu#)bT0C0W=b|~Nc(FBX2v|!dH*#%(0PQ}2)1}CQA|Fp z_pw7VFIG`aPQ_Z$2~3+Kj(bW9w?wS+)C)H1jz-){WSKb8_RjA9#XQw$^LU#PZa9^7 zUClU477`{dwkp$=BmBX-B)vAo=I`Z1RwU6@JetA^lqb59u<>;ieEVRX>ge#37t!7Z z2U;hWIlc$K>v($3PvD9~xFgL(k{k9ra@Esbh<(ljbNTb+49xwmR0ogIbdQl$eBJK; z9l&BcSOjZ~+nN9uzoi?JSI6+uxDx`{S~wU=6_!>!&er6AL@s>qp)-0-JUsmF0j!7_ z^sqPX_IN|m&=Lt09NJ>tnik?vb1O#ci7Q%d+969O>|KNyfn>tNv0^fmtpf0&+0&6J z7;X3WZU}|C!@_z_p=|INS?4}qB6+TYo^`GYRi>ml?=6m)L#brMv1Z7pw$39v+hk*NoiKesCJX~K+@A&EECp*XUz0}1!@4I+7;i?$2@?jz5z?S6HgW8Hirj#;p&f5)!closZ#pA8 zIbnVR55(O(dIGbF!pDZC|z|K_-_866o>KL%et^ch?ENL zRMW~M literal 0 HcmV?d00001 diff --git a/docs/source/guide/images/new_define_frame_flag_values.png b/docs/source/guide/images/new_define_frame_flag_values.png new file mode 100644 index 0000000000000000000000000000000000000000..ef67e55f6481ac8c71d5b3982a02be7c55f6284a GIT binary patch literal 24930 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 G1OEr34Ay=C literal 0 HcmV?d00001 diff --git a/tkclasswiz/object_frame/frame_base.py b/tkclasswiz/object_frame/frame_base.py index 6e4f977..e35098f 100644 --- a/tkclasswiz/object_frame/frame_base.py +++ b/tkclasswiz/object_frame/frame_base.py @@ -1,4 +1,5 @@ from typing import get_args, get_origin, Iterable, Union, Literal, Any, TYPE_CHECKING, TypeVar, Generic +from abc import ABC, abstractmethod from inspect import isabstract from contextlib import suppress from itertools import chain @@ -32,7 +33,7 @@ @extendable @doc_category("Object frames") -class NewObjectFrameBase(ttk.Frame): +class NewObjectFrameBase(ttk.Frame, ABC): """ Base Frame for inside the :class:`ObjectEditWindow` that allows object definition. @@ -287,12 +288,14 @@ def new_object_frame( class_, widget, allow_save=allow_save, *args, **kwargs ) + @abstractmethod def to_object(self): """ Creates an object from the GUI data. """ raise NotImplementedError + @abstractmethod def load(self, old_data: Any): """ Loads the old object info data into the GUI. @@ -328,6 +331,7 @@ def remember_gui_data(self): """ self._original_gui_data = self.get_gui_data() + @abstractmethod def get_gui_data(self) -> Any: """ Returns all GUI values. diff --git a/tkclasswiz/object_frame/frame_flag.py b/tkclasswiz/object_frame/frame_flag.py new file mode 100644 index 0000000..e6840da --- /dev/null +++ b/tkclasswiz/object_frame/frame_flag.py @@ -0,0 +1,111 @@ +from typing import TypeVar +from enum import Flag + +from ..doc import doc_category +from ..deprecation import * +from ..utilities import * +from ..convert import * +from ..dpi import * + +from ..storage import * + +from .frame_base import * + +import tkinter as tk +import tkinter.ttk as ttk + + +__all__ = ("NewObjectFrameFlag",) + +T = TypeVar("T", ComboBoxObjects, ListBoxScrolled, ListBoxObjects) + + +@doc_category("Object frames") +class NewObjectFrameFlag(NewObjectFrameBase): + """ + Frame for use inside the ObjectEditWindow that allows definition of enum flags. + + Parameters + ------------ + class_: Flag + The class we are defining for. + return_widget: T + The widget to insert the ObjectInfo into after saving. + parent: TopLevel + The parent window. + old_data: Flag + The old_data gui data. + check_parameters: bool + Check parameters (by creating the real object) upon saving. + This is ignored if editing a function instead of a class. + allow_save: bool + If False, will open in read-only mode. + """ + def __init__( + self, + class_: Flag, + return_widget: T, + parent: tk.Toplevel = None, + old_data: Flag = None, + check_parameters: bool = True, + allow_save=True + ): + super().__init__(class_, return_widget, parent, old_data, check_parameters, allow_save) + + ttk.Label(self.frame_main, text="Current value").pack(anchor=tk.W) + w = PyObjectScalar(self.frame_main) + w.pack(fill=tk.X, pady=dpi_scaled(5)) + + ttk.Separator(self.frame_main).pack(fill=tk.X, pady=dpi_scaled(5)) + + ttk.Label(self.frame_main, text="Modify").pack(anchor=tk.W) + combo_select = ComboBoxObjects(self.frame_main, width=max(map(len, map(str, list(class_))))) + combo_select["values"] = list(class_) + combo_select.pack(anchor=tk.W, pady=dpi_scaled(5)) + bnt_add_flag = ttk.Button(self.frame_main, text="Add flag", command=lambda: self._update_flag(combo_select.get(), True)) + bnt_add_flag.pack(anchor=tk.W) + bnt_remove_flag = ttk.Button(self.frame_main, text="Remove flag", command=lambda: self._update_flag(combo_select.get(), False)) + bnt_remove_flag.pack(anchor=tk.W) + + self.storage_widget = w + + if old_data is not None: + self.load(old_data) + + self.remember_gui_data() + + @gui_except() + def _update_flag(self, flag: Flag, set: bool): + if isinstance(flag, str): + raise ValueError( + f"No valid flag selected! Current selection: '{flag}'\n" + f"Allowed values: {list(map(str, iter(self.class_)))}" + ) + + old_value = self.storage_widget.get() + if old_value is None: + old_value = flag + + if set: + new_value = old_value | flag + else: + new_value = old_value & (~flag) + + self.storage_widget.set(new_value) + + def load(self, old_data: Flag): + self.storage_widget.set(old_data) + self.old_gui_data = old_data + + def get_gui_data(self): + return self.storage_widget.get() + + def to_object(self) -> Flag: + value = self.storage_widget.get() + if isinstance(value, str): + raise ValueError(f"Cannot directly input string '{value}'") + + if value is None: + value = self.class_(0) + + return value diff --git a/tkclasswiz/object_frame/frame_struct.py b/tkclasswiz/object_frame/frame_struct.py index 5629164..372f3c9 100644 --- a/tkclasswiz/object_frame/frame_struct.py +++ b/tkclasswiz/object_frame/frame_struct.py @@ -2,7 +2,7 @@ from collections.abc import Iterable as ABCIterable from functools import partial -from enum import Enum +from enum import Enum, Flag from ..convert import * @@ -223,7 +223,7 @@ def _fill_field_values( combo.insert(tk.END, True) combo.insert(tk.END, False) # tkvalid.add_option_validation(combo, ["True", "False", '']) - elif issubclass_noexcept(entry_type, Enum): + elif issubclass_noexcept(entry_type, Enum) and not issubclass_noexcept(entry_type, Flag): combo["values"] = values = [en for en in entry_type] # tkvalid.add_option_validation(combo, list(map(str, values)) + ['']) elif entry_type is type(None): diff --git a/tkclasswiz/object_frame/window.py b/tkclasswiz/object_frame/window.py index 39a7ecf..dcb319c 100644 --- a/tkclasswiz/object_frame/window.py +++ b/tkclasswiz/object_frame/window.py @@ -1,20 +1,22 @@ -from typing import get_origin, Iterable, List -from collections.abc import Iterable as ABCIterable +from typing import get_origin +from inspect import isclass, isfunction +from collections.abc import Sequence, Set +from enum import Flag from .frame_number import * from .frame_iterable import * from .frame_string import * from .frame_struct import * from .frame_base import * +from .frame_flag import * - -from ..dpi import dpi_scaled +from ..utilities import gui_except, issubclass_noexcept from ..extensions import extendable -from ..utilities import gui_except from ..doc import doc_category +from ..dpi import dpi_scaled -import tkinter as tk import tkinter.ttk as ttk +import tkinter as tk __all__ = ( @@ -28,19 +30,6 @@ class ObjectEditWindow(tk.Toplevel): """ Top level window for creating and editing new objects. """ - - # Map used to map types to specific class. - # If type is not in this map, structured object will be assumed. - TYPE_INIT_MAP = { - str: NewObjectFrameString, - float: NewObjectFrameNumber, - int: NewObjectFrameNumber, - list: NewObjectFrameIterable, - Iterable: NewObjectFrameIterable, - ABCIterable: NewObjectFrameIterable, - tuple: NewObjectFrameIterable, - } - def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._closed = False @@ -48,7 +37,7 @@ def __init__(self, *args, **kwargs): dpi_5 = dpi_scaled(5) # Elements - self.opened_frames: List[NewObjectFrameBase] = [] + self.opened_frames: list[NewObjectFrameBase] = [] self.frame_main = ttk.Frame(self, padding=(dpi_5, dpi_5)) self.frame_toolbar = ttk.Frame(self, padding=(dpi_5, dpi_5)) ttk.Button(self.frame_toolbar, text="Close", command=self.close_object_edit_frame).pack(side="left") @@ -119,11 +108,21 @@ def _create_and_add_frame( kwargs ): frame: NewObjectFrameBase - class_origin = get_origin(class_) + class_origin = get_origin(class_) # Remove any Generic type subscriptions if class_origin is None: class_origin = class_ - frame_class = self.TYPE_INIT_MAP.get(class_origin, NewObjectFrameStruct) + if issubclass_noexcept(class_origin, Flag): + frame_class = NewObjectFrameFlag + elif issubclass(class_origin, str): + frame_class = NewObjectFrameString + elif issubclass_noexcept(class_origin, (int, float, complex)): + frame_class = NewObjectFrameNumber + elif issubclass_noexcept(class_origin, (Sequence, Set)): + frame_class = NewObjectFrameIterable + else: + frame_class = NewObjectFrameStruct + self.opened_frames.append( frame := frame_class( class_, diff --git a/tkclasswiz/storage.py b/tkclasswiz/storage.py index 3368da2..5c0998c 100644 --- a/tkclasswiz/storage.py +++ b/tkclasswiz/storage.py @@ -19,6 +19,7 @@ "ComboBoxObjects", "ComboEditFrame", "HintedEntry", + "PyObjectScalar", ) @@ -75,6 +76,34 @@ def _focus_out(self, event: tk.Event): self._set_hint() +@doc_category("Storage widgets") +class PyObjectScalar(ttk.Frame): + """ + Represents a single storage widget for a Python object. + """ + def __init__(self, *args, **kwargs) -> None: + super().__init__(*args, **kwargs) + self.element = None + self.display = ttk.Entry(self, state="readonly") + self.display.pack(fill=tk.BOTH, expand=True) + + def get(self) -> object: + """ + Returns the stored Python object (or None if not set). + """ + return self.element + + def set(self, value: object): + """ + Sets the Python object as the widget's value. + """ + self.display.config(state="active") + self.display.delete("0", tk.END) + self.display.insert(tk.END, str(value)) + self.display.config(state="readonly") + self.element = value + + @doc_category("Storage widgets") class Text(tk.Text): """