Skip to content

Commit 5365564

Browse files
authored
custom repr (#28)
1 parent f206e53 commit 5365564

File tree

6 files changed

+151
-15
lines changed

6 files changed

+151
-15
lines changed

docs/source/changelog.rst

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,11 @@ Glossary
2424
Releases
2525
---------------------
2626

27-
v1.2.4
27+
v1.3.0
2828
================
29-
- Fixed typing system. (names of types)
29+
- The types will now have their subscripted type displayed alongside them.
30+
- Custom repr display of structured objects via
31+
:py:meth:`tkclasswiz.convert.ObjectInfo.register_repr` method.
3032

3133

3234
v1.2.3

docs/source/guide/customrepr.rst

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
============================
2+
Custom object display (repr)
3+
============================
4+
5+
The default way of displaying objects is as follows: ``ClassName(param1=..., param2=..., param3=..., ...)``.
6+
This printout / repr can b overridden with the use of :py:meth:`tkclasswiz.convert.ObjectInfo.register_repr` method.
7+
8+
9+
.. automethod:: tkclasswiz.convert.ObjectInfo.register_repr
10+
:no-index:
11+
12+
13+
For example, let's say I want to define some logical operators and want them to be displayed differently.
14+
The example shows how a custom repr display can be made. It may look a bit much, but the only thing that matters
15+
is the emphasized :py:meth:`~tkclasswiz.convert.ObjectInfo.register_repr` method call.
16+
Inside the call, a lambda function is provided. The lambda accepts
17+
the :class:`~tkclasswiz.convert.ObjectInfo` object and outputs a string.
18+
That string is made up of the name of a logical operator and the operants in the following format:
19+
``<op1> <operator name> <op2> <operator name> <op3> ...``. If the operator's name is ``or_op``, it will be displayed
20+
as ``<op1> or <op2> or <op3> ...``. If the operator's name is ``and_op``, it will be displayed as
21+
``<op1> and <op2> and <op3> ...``.
22+
23+
24+
.. image:: ./images/object_info_custom_repr.png
25+
:width: 20cm
26+
27+
28+
.. code-block:: python
29+
:linenos:
30+
:emphasize-lines: 38-45
31+
32+
from typing import List
33+
from abc import ABC, abstractmethod
34+
35+
import tkinter as tk
36+
import tkinter.ttk as ttk
37+
import tkclasswiz as wiz
38+
39+
class base_op(ABC):
40+
@abstractmethod
41+
def evaluate(self):
42+
pass
43+
44+
class bool_op(base_op):
45+
def __init__(self, operants: List["base_op"]) -> None:
46+
self.operants = operants
47+
48+
class and_op(bool_op):
49+
def evaluate(self):
50+
return all(op.evaluate() for op in self.operants)
51+
52+
class or_op(bool_op):
53+
def evaluate(self):
54+
return any(op.evaluate() for op in self.operants)
55+
56+
class contains(base_op):
57+
def __init__(self, op: str) -> None:
58+
pass
59+
60+
def evaluate(self):
61+
# For demonstration purposes. Otherwise, we would usually check if op is contained within string
62+
return True
63+
64+
class MyLogicResult:
65+
def __init__(self, expression: base_op) -> None:
66+
self.expression = expression
67+
68+
69+
wiz.ObjectInfo.register_repr(
70+
bool_op,
71+
lambda oi: "(" +
72+
f' {oi.class_.__name__.removesuffix("_op")} '
73+
.join(map(repr, oi.data["operants"])) +
74+
")",
75+
True
76+
)
77+
78+
# Tkinter main window
79+
root = tk.Tk("Test")
80+
81+
# Modified tkinter Combobox that will store actual objects instead of strings
82+
combo = wiz.ComboBoxObjects(root)
83+
combo.pack(fill=tk.X, padx=5)
84+
85+
def define(old = None):
86+
"""
87+
Function for opening a window either in new definition mode (old = None) or
88+
edit mode (old != None)
89+
"""
90+
assert old is None or isinstance(old, wiz.ObjectInfo)
91+
window = wiz.ObjectEditWindow()
92+
window.open_object_edit_frame(MyLogicResult, combo)
93+
94+
# Main GUI structure
95+
ttk.Button(text="Open", command=define).pack()
96+
root.mainloop()
97+

docs/source/guide/defining.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,10 @@ This is how our frame looks after defining 4 ``Wheel`` objects:
171171
.. image:: ./images/new_define_frame_list_defined.png
172172
:width: 15cm
173173

174+
175+
Final definition
176+
=================
177+
174178
If we click "Save", we go back to our ``Car`` definition frame, that has all the parameters defined.
175179
Clicking on "Save" once again will save the ``Car`` object to our original GUI Combobox.
176180

29.3 KB
Loading

docs/source/guide/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,6 @@ Index:
1515
conversion
1616
polymorphism
1717
abstractclasses
18+
customrepr
1819
aliasing
1920
generics

tkclasswiz/convert.py

Lines changed: 45 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
"""
22
Modules contains definitions related to GUI object transformations.
33
"""
4-
from typing import Any, Union, List, Generic, TypeVar, Mapping, Optional
4+
from __future__ import annotations
5+
from typing import Any, Union, List, Generic, TypeVar, Mapping, Optional, Callable
56
from contextlib import suppress
67
from inspect import signature
78
from enum import Enum
@@ -124,6 +125,7 @@ class ObjectInfo(Generic[TClass]):
124125
recognition.
125126
"""
126127
CHARACTER_LIMIT = 150
128+
custom_display_map: dict[type, Callable] = {}
127129

128130
def __init__(
129131
self,
@@ -157,23 +159,31 @@ def __repr__(self) -> str:
157159
if self._repr is not None:
158160
return self._repr
159161

162+
repr_get = ObjectInfo.custom_display_map.get(self.class_, self._repr_default)
163+
_ret = repr_get(self)
164+
165+
self._repr = _ret
166+
return _ret
167+
168+
@staticmethod
169+
def _repr_default(object_info: ObjectInfo) -> str:
160170
_ret: List[str] = []
161-
if self.nickname:
162-
_ret += f"({self.nickname}) "
171+
if object_info.nickname:
172+
_ret += f"({object_info.nickname}) "
163173

164-
name = get_aliased_name(self.class_)
174+
name = get_aliased_name(object_info.class_)
165175
if name is not None:
166-
name += f'({self.class_.__name__})'
176+
name += f'({object_info.class_.__name__})'
167177
else:
168-
name = self.class_.__name__
178+
name = object_info.class_.__name__
169179

170180
_ret += name + "("
171181
private_params = set()
172-
if hasattr(self.class_, "__passwords__"):
173-
private_params = private_params.union(self.class_.__passwords__)
182+
if hasattr(object_info.class_, "__passwords__"):
183+
private_params = private_params.union(object_info.class_.__passwords__)
174184

175-
for k, v in self.data.items():
176-
if len(_ret) > self.CHARACTER_LIMIT:
185+
for k, v in object_info.data.items():
186+
if len(_ret) > object_info.CHARACTER_LIMIT:
177187
break
178188

179189
if isinstance(v, str):
@@ -188,12 +198,34 @@ def __repr__(self) -> str:
188198

189199
_ret: str = ''.join(_ret)
190200
_ret = _ret.rstrip(", ") + ")"
191-
if len(_ret) > self.CHARACTER_LIMIT:
192-
_ret = _ret[:self.CHARACTER_LIMIT] + "...)"
201+
if len(_ret) > object_info.CHARACTER_LIMIT:
202+
_ret = _ret[:object_info.CHARACTER_LIMIT] + "...)"
193203

194-
self._repr = _ret
195204
return _ret
196205

206+
@classmethod
207+
def register_repr(cls, class_: type, repr: Callable[[ObjectInfo], str], inherited: bool = False):
208+
"""
209+
Registers a custom __repr__ (string representation of object) function.
210+
The function must as a single parameter accept the ``ObjectInfo`` instance
211+
being represented as a string.
212+
213+
Parameters
214+
------------
215+
class_: type
216+
The class for which this custom ``repr`` is being register.
217+
repr: Callable[[ObjectInfo], str]
218+
The function that will provide custom ``__repr__``.
219+
As a parameter it accepts the ``ObjectInfo`` object.
220+
It returns a :class:`str` (string).
221+
inherited: bool
222+
Boolean flag. Setting it to True will register repr for inherited members as well.
223+
Defaults to False.
224+
"""
225+
cls.custom_display_map[class_] = repr
226+
if inherited:
227+
for type_ in class_.__subclasses__():
228+
cls.register_repr(type_, repr, True)
197229

198230
@cache_result(max=1024)
199231
@doc_category("Conversion")

0 commit comments

Comments
 (0)