Skip to content

Commit d690b83

Browse files
committed
Fix
1 parent 060042c commit d690b83

File tree

6 files changed

+107
-82
lines changed

6 files changed

+107
-82
lines changed

docs/source/changelog.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,12 @@ Glossary
2424
Releases
2525
---------------------
2626

27+
v1.4.5
28+
================
29+
- Further generic type fixes
30+
- Fixed type deprecations to also consider subclasses
31+
32+
2733
v1.4.4
2834
================
2935
- Fixed generic types under iterable types expansion.

tkclasswiz/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
SOFTWARE.
2828
"""
2929

30-
__version__ = "1.4.4"
30+
__version__ = "1.4.5"
3131

3232

3333
from .object_frame import *

tkclasswiz/annotations.py

Lines changed: 77 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,20 @@
11
"""
22
Module used for managing annotations.
33
"""
4-
from typing import Union, Optional, get_args, Generic, get_origin, get_type_hints
4+
from typing import Union, Optional, Generic, Iterable, get_args, get_origin, get_type_hints
55
from datetime import datetime, timedelta, timezone
6+
from inspect import isclass, isabstract
7+
from itertools import product, chain
68
from contextlib import suppress
7-
from inspect import isclass
8-
from .doc import doc_category
9+
910
from .utilities import issubclass_noexcept
11+
from .doc import doc_category
1012

1113

1214
__all__ = (
1315
"register_annotations",
1416
"get_annotations",
17+
"convert_types",
1518
)
1619

1720

@@ -116,3 +119,74 @@ def get_annotations(class_) -> dict:
116119
del annotations["return"]
117120

118121
return annotations
122+
123+
124+
def convert_types(input_type: type):
125+
"""
126+
Type preprocessing method, that extends the list of types with inherited members (polymorphism)
127+
and removes classes that are wrapped by some other class, if the wrapper class also appears in
128+
the annotations.
129+
"""
130+
def remove_classes(types: list):
131+
r = types.copy()
132+
for type_ in types:
133+
# It's a wrapper of some class -> remove the wrapped class
134+
if hasattr(type_, "__wrapped__"):
135+
if type_.__wrapped__ in r:
136+
r.remove(type_.__wrapped__)
137+
138+
# Abstract classes are classes that don't allow instantiation -> remove the class
139+
if isabstract(type_):
140+
r.remove(type_)
141+
142+
return tuple({a:0 for a in r})
143+
144+
145+
if isinstance(input_type, str):
146+
raise TypeError(
147+
f"Provided type '{input_type}' is not a type - it is a string!\n"
148+
"Potential subscripted type problem?\n"
149+
"Instead of e. g., list['type'], try using typing.List['type']."
150+
)
151+
152+
origin = get_origin(input_type)
153+
if issubclass_noexcept(origin, Generic):
154+
# Patch for Python versions < 3.10
155+
input_type.__name__ = origin.__name__
156+
157+
# Unpack Union items into a tuple
158+
if origin is Union or issubclass_noexcept(origin, (Iterable, Generic)):
159+
new_types = []
160+
for arg_group in get_args(input_type):
161+
new_types.append(remove_classes(list(convert_types(arg_group))))
162+
163+
if origin is Union:
164+
return tuple(chain.from_iterable(new_types)) # Just expand unions
165+
166+
# Process abstract classes and polymorphism
167+
new_origins = []
168+
for origin in convert_types(origin):
169+
if issubclass_noexcept(origin, Generic):
170+
for comb in product(*new_types):
171+
new_origins.append(origin[comb])
172+
elif issubclass_noexcept(origin, Iterable):
173+
new = origin[tuple(chain.from_iterable(new_types))] if len(new_types) else origin
174+
new_origins.append(new)
175+
else:
176+
new_origins.append(origin)
177+
178+
return remove_classes(new_origins)
179+
180+
if input_type.__module__ == "builtins":
181+
# Don't consider built-int types for polymorphism
182+
# No removal of abstract classes is needed either as builtins types aren't abstract
183+
return (input_type,)
184+
185+
# Extend subclasses
186+
subtypes = []
187+
if hasattr(input_type, "__subclasses__"):
188+
for st in input_type.__subclasses__():
189+
subtypes.extend(convert_types(st))
190+
191+
# Remove wrapped classes (eg. wrapped by decorator) + ABC classes
192+
return remove_classes([input_type, *subtypes])

tkclasswiz/deprecation.py

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
"""
22
Module can be used to mark deprecated classes / parameters / parameter types.
33
"""
4-
from typing import overload
4+
from typing import overload, get_origin
5+
from itertools import chain
56

7+
from .utilities import issubclass_noexcept
8+
from .annotations import convert_types
69
from .doc import doc_category
710

811

@@ -42,6 +45,7 @@ def register_deprecated(cls, parameter: str = None, *types: type):
4245
cls.__wiz_deprecated_params__ = getattr(cls, "__wiz_deprecated_params__", set())
4346
cls.__wiz_deprecated_params__.add(parameter)
4447
else:
48+
types = tuple(chain.from_iterable(map(convert_types, types)))
4549
cls.__wiz_deprecated_param_types__ = getattr(cls, "__wiz_deprecated_param_types__", dict())
4650
cls.__wiz_deprecated_param_types__[parameter] = cls.__wiz_deprecated_param_types__.get(parameter, set())
4751
cls.__wiz_deprecated_param_types__[parameter].update(types)
@@ -74,5 +78,18 @@ def is_deprecated(cls: type, parameter: str = None, type_: type = None):
7478
if type_ is None:
7579
params = getattr(cls, "__wiz_deprecated_params__", set())
7680
return parameter in params
81+
82+
depr_types = getattr(cls, "__wiz_deprecated_param_types__", dict()).get(parameter, set())
83+
if is_deprecated(type_) or type_ in depr_types:
84+
return True
7785

78-
return is_deprecated(type_) or type_ in getattr(cls, "__wiz_deprecated_param_types__", dict()).get(parameter, set())
86+
origin = get_origin(type_)
87+
if origin is not None and origin in depr_types:
88+
return True
89+
90+
type_ = origin or type_
91+
for depr_type in depr_types:
92+
if issubclass_noexcept(type_, depr_type):
93+
return True
94+
95+
return False

tkclasswiz/object_frame/frame_base.py

Lines changed: 1 addition & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
1-
from typing import get_args, get_origin, Iterable, Union, Literal, Any, TYPE_CHECKING, TypeVar, Generic
1+
from typing import get_args, get_origin, Iterable, Union, Literal, Any, TYPE_CHECKING, TypeVar
22
from abc import ABC, abstractmethod
3-
from inspect import isabstract
43
from contextlib import suppress
5-
from itertools import chain, product
64
from functools import cache
75

86
from ..convert import *
@@ -181,76 +179,6 @@ def cast_type(cls, value: Any, types: Iterable):
181179

182180
return value
183181

184-
@classmethod
185-
def convert_types(cls, input_type: type):
186-
"""
187-
Type preprocessing method, that extends the list of types with inherited members (polymorphism)
188-
and removes classes that are wrapped by some other class, if the wrapper class also appears in
189-
the annotations.
190-
"""
191-
def remove_classes(types: list):
192-
r = types.copy()
193-
for type_ in types:
194-
# It's a wrapper of some class -> remove the wrapped class
195-
if hasattr(type_, "__wrapped__"):
196-
if type_.__wrapped__ in r:
197-
r.remove(type_.__wrapped__)
198-
199-
# Abstract classes are classes that don't allow instantiation -> remove the class
200-
if isabstract(type_):
201-
r.remove(type_)
202-
203-
return tuple({a:0 for a in r})
204-
205-
206-
if isinstance(input_type, str):
207-
raise TypeError(
208-
f"Provided type '{input_type}' is not a type - it is a string!\n"
209-
"Potential subscripted type problem?\n"
210-
"Instead of e. g., list['type'], try using typing.List['type']."
211-
)
212-
213-
origin = get_origin(input_type)
214-
if issubclass_noexcept(origin, Generic):
215-
# Patch for Python versions < 3.10
216-
input_type.__name__ = origin.__name__
217-
218-
# Unpack Union items into a tuple
219-
if origin is Union or issubclass_noexcept(origin, (Iterable, Generic)):
220-
new_types = []
221-
for arg_group in get_args(input_type):
222-
new_types.append(remove_classes(list(cls.convert_types(arg_group))))
223-
224-
if origin is Union:
225-
return tuple(chain.from_iterable(new_types)) # Just expand unions
226-
227-
# Process abstract classes and polymorphism
228-
new_origins = []
229-
for origin in cls.convert_types(origin):
230-
if issubclass_noexcept(origin, Iterable):
231-
new_origins.append(origin[tuple(chain.from_iterable(new_types))])
232-
elif issubclass_noexcept(origin, Generic):
233-
for comb in product(*new_types):
234-
new_origins.append(origin[comb])
235-
else:
236-
new_origins.append(origin)
237-
238-
return remove_classes(new_origins)
239-
240-
if input_type.__module__ == "builtins":
241-
# Don't consider built-int types for polymorphism
242-
# No removal of abstract classes is needed either as builtins types aren't abstract
243-
return (input_type,)
244-
245-
# Extend subclasses
246-
subtypes = []
247-
if hasattr(input_type, "__subclasses__"):
248-
for st in input_type.__subclasses__():
249-
subtypes.extend(cls.convert_types(st))
250-
251-
# Remove wrapped classes (eg. wrapped by decorator) + ABC classes
252-
return remove_classes([input_type, *subtypes])
253-
254182
def init_main_frame(self):
255183
frame_main = ttk.Frame(self)
256184
frame_main.pack(expand=True, fill=tk.BOTH)

tkclasswiz/object_frame/frame_struct.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from ..storage import *
1212
from ..messagebox import Messagebox
1313
from ..extensions import extendable
14-
from ..annotations import get_annotations
14+
from ..annotations import get_annotations, convert_types
1515
from ..deprecation import *
1616
from ..doc import doc_category
1717

@@ -155,7 +155,7 @@ def _create_fields(self, annotations: dict[str, type], additional_values: dict,
155155

156156
for (k, v) in annotations.items():
157157
# Init widgets
158-
entry_types = self.convert_types(v)
158+
entry_types = convert_types(v)
159159
frame_annotated = ttk.Frame(frame)
160160
frame_annotated.pack(fill=tk.BOTH, expand=True, pady=dpi_5)
161161
ttk.Label(frame_annotated, text=k, width=label_width).pack(side="left")
@@ -339,7 +339,7 @@ def _edit_selected(self, key: str, combo: ComboBoxObjects):
339339
return self.new_object_frame(selection.class_, combo, old_data=selection)
340340
else:
341341
type_sel = type(selection)
342-
for t in self.convert_types(get_annotations(self.class_)[key]):
342+
for t in convert_types(get_annotations(self.class_)[key]):
343343
if (get_origin(t) or t) == type_sel:
344344
type_ = t
345345
break

0 commit comments

Comments
 (0)