urwid 2.6.0.post0__py3-none-any.whl
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.
Potentially problematic release.
This version of urwid might be problematic. Click here for more details.
- urwid/__init__.py +333 -0
- urwid/canvas.py +1413 -0
- urwid/command_map.py +137 -0
- urwid/container.py +59 -0
- urwid/decoration.py +65 -0
- urwid/display/__init__.py +97 -0
- urwid/display/_posix_raw_display.py +413 -0
- urwid/display/_raw_display_base.py +914 -0
- urwid/display/_web.css +12 -0
- urwid/display/_web.js +462 -0
- urwid/display/_win32.py +171 -0
- urwid/display/_win32_raw_display.py +269 -0
- urwid/display/common.py +1219 -0
- urwid/display/curses.py +690 -0
- urwid/display/escape.py +624 -0
- urwid/display/html_fragment.py +251 -0
- urwid/display/lcd.py +518 -0
- urwid/display/raw.py +37 -0
- urwid/display/web.py +636 -0
- urwid/event_loop/__init__.py +55 -0
- urwid/event_loop/abstract_loop.py +175 -0
- urwid/event_loop/asyncio_loop.py +231 -0
- urwid/event_loop/glib_loop.py +294 -0
- urwid/event_loop/main_loop.py +721 -0
- urwid/event_loop/select_loop.py +230 -0
- urwid/event_loop/tornado_loop.py +206 -0
- urwid/event_loop/trio_loop.py +302 -0
- urwid/event_loop/twisted_loop.py +269 -0
- urwid/event_loop/zmq_loop.py +275 -0
- urwid/font.py +695 -0
- urwid/graphics.py +96 -0
- urwid/highlight.css +19 -0
- urwid/listbox.py +1899 -0
- urwid/monitored_list.py +522 -0
- urwid/numedit.py +376 -0
- urwid/signals.py +330 -0
- urwid/split_repr.py +130 -0
- urwid/str_util.py +358 -0
- urwid/text_layout.py +632 -0
- urwid/treetools.py +515 -0
- urwid/util.py +557 -0
- urwid/version.py +16 -0
- urwid/vterm.py +1806 -0
- urwid/widget/__init__.py +181 -0
- urwid/widget/attr_map.py +161 -0
- urwid/widget/attr_wrap.py +140 -0
- urwid/widget/bar_graph.py +649 -0
- urwid/widget/big_text.py +77 -0
- urwid/widget/box_adapter.py +126 -0
- urwid/widget/columns.py +1145 -0
- urwid/widget/constants.py +574 -0
- urwid/widget/container.py +227 -0
- urwid/widget/divider.py +110 -0
- urwid/widget/edit.py +718 -0
- urwid/widget/filler.py +403 -0
- urwid/widget/frame.py +539 -0
- urwid/widget/grid_flow.py +539 -0
- urwid/widget/line_box.py +194 -0
- urwid/widget/overlay.py +829 -0
- urwid/widget/padding.py +597 -0
- urwid/widget/pile.py +971 -0
- urwid/widget/popup.py +170 -0
- urwid/widget/progress_bar.py +141 -0
- urwid/widget/scrollable.py +597 -0
- urwid/widget/solid_fill.py +44 -0
- urwid/widget/text.py +354 -0
- urwid/widget/widget.py +852 -0
- urwid/widget/widget_decoration.py +166 -0
- urwid/widget/wimp.py +792 -0
- urwid/wimp.py +23 -0
- urwid-2.6.0.post0.dist-info/COPYING +504 -0
- urwid-2.6.0.post0.dist-info/METADATA +332 -0
- urwid-2.6.0.post0.dist-info/RECORD +75 -0
- urwid-2.6.0.post0.dist-info/WHEEL +5 -0
- urwid-2.6.0.post0.dist-info/top_level.txt +1 -0
urwid/signals.py
ADDED
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
# Urwid signal dispatching
|
|
2
|
+
# Copyright (C) 2004-2012 Ian Ward
|
|
3
|
+
#
|
|
4
|
+
# This library is free software; you can redistribute it and/or
|
|
5
|
+
# modify it under the terms of the GNU Lesser General Public
|
|
6
|
+
# License as published by the Free Software Foundation; either
|
|
7
|
+
# version 2.1 of the License, or (at your option) any later version.
|
|
8
|
+
#
|
|
9
|
+
# This library is distributed in the hope that it will be useful,
|
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
12
|
+
# Lesser General Public License for more details.
|
|
13
|
+
#
|
|
14
|
+
# You should have received a copy of the GNU Lesser General Public
|
|
15
|
+
# License along with this library; if not, write to the Free Software
|
|
16
|
+
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
17
|
+
#
|
|
18
|
+
# Urwid web site: https://urwid.org/
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
import itertools
|
|
24
|
+
import typing
|
|
25
|
+
import warnings
|
|
26
|
+
import weakref
|
|
27
|
+
|
|
28
|
+
if typing.TYPE_CHECKING:
|
|
29
|
+
from collections.abc import Callable, Collection, Container, Hashable, Iterable
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class MetaSignals(type):
|
|
33
|
+
"""
|
|
34
|
+
register the list of signals in the class variable signals,
|
|
35
|
+
including signals in superclasses.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
def __init__(cls, name: str, bases: tuple[type, ...], d: dict[str, typing.Any]) -> None:
|
|
39
|
+
signals = d.get("signals", [])
|
|
40
|
+
for superclass in cls.__bases__:
|
|
41
|
+
signals.extend(getattr(superclass, "signals", []))
|
|
42
|
+
signals = list(dict.fromkeys(signals).keys())
|
|
43
|
+
d["signals"] = signals
|
|
44
|
+
register_signal(cls, signals)
|
|
45
|
+
super().__init__(name, bases, d)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def setdefaultattr(obj, name, value):
|
|
49
|
+
# like dict.setdefault() for object attributes
|
|
50
|
+
if hasattr(obj, name):
|
|
51
|
+
return getattr(obj, name)
|
|
52
|
+
setattr(obj, name, value)
|
|
53
|
+
return value
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class Key:
|
|
57
|
+
"""
|
|
58
|
+
Minimal class, whose only purpose is to produce objects with a
|
|
59
|
+
unique hash
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
__slots__ = ()
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class Signals:
|
|
66
|
+
_signal_attr = "_urwid_signals" # attribute to attach to signal senders
|
|
67
|
+
|
|
68
|
+
def __init__(self):
|
|
69
|
+
self._supported = {}
|
|
70
|
+
|
|
71
|
+
def register(self, sig_cls, signals: Container[Hashable]) -> None:
|
|
72
|
+
"""
|
|
73
|
+
:param sig_cls: the class of an object that will be sending signals
|
|
74
|
+
:type sig_cls: class
|
|
75
|
+
:param signals: a list of signals that may be sent, typically each
|
|
76
|
+
signal is represented by a string
|
|
77
|
+
:type signals: signal names
|
|
78
|
+
|
|
79
|
+
This function must be called for a class before connecting any
|
|
80
|
+
signal callbacks or emitting any signals from that class' objects
|
|
81
|
+
"""
|
|
82
|
+
self._supported[sig_cls] = signals
|
|
83
|
+
|
|
84
|
+
def connect(
|
|
85
|
+
self,
|
|
86
|
+
obj,
|
|
87
|
+
name: Hashable,
|
|
88
|
+
callback: Callable[..., typing.Any],
|
|
89
|
+
user_arg: typing.Any = None,
|
|
90
|
+
*,
|
|
91
|
+
weak_args: Iterable[typing.Any] = (),
|
|
92
|
+
user_args: Iterable[typing.Any] = (),
|
|
93
|
+
) -> Key:
|
|
94
|
+
"""
|
|
95
|
+
:param obj: the object sending a signal
|
|
96
|
+
:type obj: object
|
|
97
|
+
:param name: the signal to listen for, typically a string
|
|
98
|
+
:type name: signal name
|
|
99
|
+
:param callback: the function to call when that signal is sent
|
|
100
|
+
:type callback: function
|
|
101
|
+
:param user_arg: deprecated additional argument to callback (appended
|
|
102
|
+
after the arguments passed when the signal is
|
|
103
|
+
emitted). If None no arguments will be added.
|
|
104
|
+
Don't use this argument, use user_args instead.
|
|
105
|
+
:param weak_args: additional arguments passed to the callback
|
|
106
|
+
(before any arguments passed when the signal
|
|
107
|
+
is emitted and before any user_args).
|
|
108
|
+
|
|
109
|
+
These arguments are stored as weak references
|
|
110
|
+
(but converted back into their original value
|
|
111
|
+
before passing them to callback) to prevent
|
|
112
|
+
any objects referenced (indirectly) from
|
|
113
|
+
weak_args from being kept alive just because
|
|
114
|
+
they are referenced by this signal handler.
|
|
115
|
+
|
|
116
|
+
Use this argument only as a keyword argument,
|
|
117
|
+
since user_arg might be removed in the future.
|
|
118
|
+
:type weak_args: iterable
|
|
119
|
+
:param user_args: additional arguments to pass to the callback,
|
|
120
|
+
(before any arguments passed when the signal
|
|
121
|
+
is emitted but after any weak_args).
|
|
122
|
+
|
|
123
|
+
Use this argument only as a keyword argument,
|
|
124
|
+
since user_arg might be removed in the future.
|
|
125
|
+
:type user_args: iterable
|
|
126
|
+
|
|
127
|
+
When a matching signal is sent, callback will be called. The
|
|
128
|
+
arguments it receives will be the user_args passed at connect
|
|
129
|
+
time (as individual arguments) followed by all the positional
|
|
130
|
+
parameters sent with the signal.
|
|
131
|
+
|
|
132
|
+
As an example of using weak_args, consider the following snippet:
|
|
133
|
+
|
|
134
|
+
>>> import urwid
|
|
135
|
+
>>> debug = urwid.Text('')
|
|
136
|
+
>>> def handler(widget, newtext):
|
|
137
|
+
... debug.set_text("Edit widget changed to %s" % newtext)
|
|
138
|
+
>>> edit = urwid.Edit('')
|
|
139
|
+
>>> key = urwid.connect_signal(edit, 'change', handler)
|
|
140
|
+
|
|
141
|
+
If you now build some interface using "edit" and "debug", the
|
|
142
|
+
"debug" widget will show whatever you type in the "edit" widget.
|
|
143
|
+
However, if you remove all references to the "debug" widget, it
|
|
144
|
+
will still be kept alive by the signal handler. This because the
|
|
145
|
+
signal handler is a closure that (implicitly) references the
|
|
146
|
+
"edit" widget. If you want to allow the "debug" widget to be
|
|
147
|
+
garbage collected, you can create a "fake" or "weak" closure
|
|
148
|
+
(it's not really a closure, since it doesn't reference any
|
|
149
|
+
outside variables, so it's just a dynamic function):
|
|
150
|
+
|
|
151
|
+
>>> debug = urwid.Text('')
|
|
152
|
+
>>> def handler(weak_debug, widget, newtext):
|
|
153
|
+
... weak_debug.set_text("Edit widget changed to %s" % newtext)
|
|
154
|
+
>>> edit = urwid.Edit('')
|
|
155
|
+
>>> key = urwid.connect_signal(edit, 'change', handler, weak_args=[debug])
|
|
156
|
+
|
|
157
|
+
Here the weak_debug parameter in print_debug is the value passed
|
|
158
|
+
in the weak_args list to connect_signal. Note that the
|
|
159
|
+
weak_debug value passed is not a weak reference anymore, the
|
|
160
|
+
signals code transparently dereferences the weakref parameter
|
|
161
|
+
before passing it to print_debug.
|
|
162
|
+
|
|
163
|
+
Returns a key associated by this signal handler, which can be
|
|
164
|
+
used to disconnect the signal later on using
|
|
165
|
+
urwid.disconnect_signal_by_key. Alternatively, the signal
|
|
166
|
+
handler can also be disconnected by calling
|
|
167
|
+
urwid.disconnect_signal, which doesn't need this key.
|
|
168
|
+
"""
|
|
169
|
+
if user_arg is not None:
|
|
170
|
+
warnings.warn(
|
|
171
|
+
"Don't use user_arg argument, use user_args instead.",
|
|
172
|
+
DeprecationWarning,
|
|
173
|
+
stacklevel=2,
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
sig_cls = obj.__class__
|
|
177
|
+
if name not in self._supported.get(sig_cls, ()):
|
|
178
|
+
raise NameError(f"No such signal {name!r} for object {obj!r}")
|
|
179
|
+
|
|
180
|
+
# Just generate an arbitrary (but unique) key
|
|
181
|
+
key = Key()
|
|
182
|
+
|
|
183
|
+
handlers = setdefaultattr(obj, self._signal_attr, {}).setdefault(name, [])
|
|
184
|
+
|
|
185
|
+
# Remove the signal handler when any of the weakref'd arguments
|
|
186
|
+
# are garbage collected. Note that this means that the handlers
|
|
187
|
+
# dictionary can be modified _at any time_, so it should never
|
|
188
|
+
# be iterated directly (e.g. iterate only over .keys() and
|
|
189
|
+
# .items(), never over .iterkeys(), .iteritems() or the object
|
|
190
|
+
# itself).
|
|
191
|
+
# We let the callback keep a weakref to the object as well, to
|
|
192
|
+
# prevent a circular reference between the handler and the
|
|
193
|
+
# object (via the weakrefs, which keep strong references to
|
|
194
|
+
# their callbacks) from existing.
|
|
195
|
+
obj_weak = weakref.ref(obj)
|
|
196
|
+
|
|
197
|
+
def weakref_callback(weakref): # pylint: disable=redefined-outer-name # bad, but not changing API
|
|
198
|
+
o = obj_weak()
|
|
199
|
+
if o:
|
|
200
|
+
self.disconnect_by_key(o, name, key)
|
|
201
|
+
|
|
202
|
+
user_args = self._prepare_user_args(weak_args, user_args, weakref_callback)
|
|
203
|
+
handlers.append((key, callback, user_arg, user_args))
|
|
204
|
+
|
|
205
|
+
return key
|
|
206
|
+
|
|
207
|
+
def _prepare_user_args(
|
|
208
|
+
self,
|
|
209
|
+
weak_args: Iterable[typing.Any] = (),
|
|
210
|
+
user_args: Iterable[typing.Any] = (),
|
|
211
|
+
callback: Callable[..., typing.Any] | None = None,
|
|
212
|
+
) -> tuple[Collection[weakref.ReferenceType], Collection[typing.Any]]:
|
|
213
|
+
# Turn weak_args into weakrefs and prepend them to user_args
|
|
214
|
+
w_args = tuple(weakref.ref(w_arg, callback) for w_arg in weak_args)
|
|
215
|
+
args = tuple(user_args) or ()
|
|
216
|
+
return (w_args, args)
|
|
217
|
+
|
|
218
|
+
def disconnect(
|
|
219
|
+
self,
|
|
220
|
+
obj,
|
|
221
|
+
name: Hashable,
|
|
222
|
+
callback: Callable[..., typing.Any],
|
|
223
|
+
user_arg: typing.Any = None,
|
|
224
|
+
*,
|
|
225
|
+
weak_args: Iterable[typing.Any] = (),
|
|
226
|
+
user_args: Iterable[typing.Any] = (),
|
|
227
|
+
) -> None:
|
|
228
|
+
"""
|
|
229
|
+
:param obj: the object to disconnect the signal from
|
|
230
|
+
:type obj: object
|
|
231
|
+
:param name: the signal to disconnect, typically a string
|
|
232
|
+
:type name: signal name
|
|
233
|
+
:param callback: the callback function passed to connect_signal
|
|
234
|
+
:type callback: function
|
|
235
|
+
:param user_arg: the user_arg parameter passed to connect_signal
|
|
236
|
+
:param weak_args: the weak_args parameter passed to connect_signal
|
|
237
|
+
:param user_args: the weak_args parameter passed to connect_signal
|
|
238
|
+
|
|
239
|
+
This function will remove a callback from the list connected
|
|
240
|
+
to a signal with connect_signal(). The arguments passed should
|
|
241
|
+
be exactly the same as those passed to connect_signal().
|
|
242
|
+
|
|
243
|
+
If the callback is not connected or already disconnected, this
|
|
244
|
+
function will simply do nothing.
|
|
245
|
+
"""
|
|
246
|
+
signals = setdefaultattr(obj, self._signal_attr, {})
|
|
247
|
+
if name not in signals:
|
|
248
|
+
return None
|
|
249
|
+
|
|
250
|
+
handlers = signals[name]
|
|
251
|
+
|
|
252
|
+
# Do the same processing as in connect, so we can compare the
|
|
253
|
+
# resulting tuple.
|
|
254
|
+
user_args = self._prepare_user_args(weak_args, user_args)
|
|
255
|
+
|
|
256
|
+
# Remove the given handler
|
|
257
|
+
for h in handlers:
|
|
258
|
+
if h[1:] == (callback, user_arg, user_args):
|
|
259
|
+
return self.disconnect_by_key(obj, name, h[0])
|
|
260
|
+
return None
|
|
261
|
+
|
|
262
|
+
def disconnect_by_key(self, obj, name: Hashable, key: Key) -> None:
|
|
263
|
+
"""
|
|
264
|
+
:param obj: the object to disconnect the signal from
|
|
265
|
+
:type obj: object
|
|
266
|
+
:param name: the signal to disconnect, typically a string
|
|
267
|
+
:type name: signal name
|
|
268
|
+
:param key: the key for this signal handler, as returned by
|
|
269
|
+
connect_signal().
|
|
270
|
+
:type key: Key
|
|
271
|
+
|
|
272
|
+
This function will remove a callback from the list connected
|
|
273
|
+
to a signal with connect_signal(). The key passed should be the
|
|
274
|
+
value returned by connect_signal().
|
|
275
|
+
|
|
276
|
+
If the callback is not connected or already disconnected, this
|
|
277
|
+
function will simply do nothing.
|
|
278
|
+
"""
|
|
279
|
+
handlers = setdefaultattr(obj, self._signal_attr, {}).get(name, [])
|
|
280
|
+
handlers[:] = [h for h in handlers if h[0] is not key]
|
|
281
|
+
|
|
282
|
+
def emit(self, obj, name: Hashable, *args) -> bool:
|
|
283
|
+
"""
|
|
284
|
+
:param obj: the object sending a signal
|
|
285
|
+
:type obj: object
|
|
286
|
+
:param name: the signal to send, typically a string
|
|
287
|
+
:type name: signal name
|
|
288
|
+
:param args: zero or more positional arguments to pass to the signal callback functions
|
|
289
|
+
|
|
290
|
+
This function calls each of the callbacks connected to this signal
|
|
291
|
+
with the args arguments as positional parameters.
|
|
292
|
+
|
|
293
|
+
This function returns True if any of the callbacks returned True.
|
|
294
|
+
"""
|
|
295
|
+
result = False
|
|
296
|
+
handlers = getattr(obj, self._signal_attr, {}).get(name, [])
|
|
297
|
+
for _key, callback, user_arg, (weak_args, user_args) in handlers:
|
|
298
|
+
result |= self._call_callback(callback, user_arg, weak_args, user_args, args)
|
|
299
|
+
return result
|
|
300
|
+
|
|
301
|
+
def _call_callback(
|
|
302
|
+
self,
|
|
303
|
+
callback,
|
|
304
|
+
user_arg: typing.Any,
|
|
305
|
+
weak_args: Collection[weakref.ReferenceType],
|
|
306
|
+
user_args: Collection[typing.Any],
|
|
307
|
+
emit_args: Iterable[typing.Any],
|
|
308
|
+
) -> bool:
|
|
309
|
+
args_to_pass = []
|
|
310
|
+
for w_arg in weak_args:
|
|
311
|
+
real_arg = w_arg()
|
|
312
|
+
if real_arg is not None:
|
|
313
|
+
args_to_pass.append(real_arg)
|
|
314
|
+
else:
|
|
315
|
+
# de-referenced
|
|
316
|
+
return False
|
|
317
|
+
|
|
318
|
+
# The deprecated user_arg argument was added to the end
|
|
319
|
+
# instead of the beginning.
|
|
320
|
+
args = itertools.chain(args_to_pass, user_args, emit_args, (user_arg,) if user_arg is not None else ())
|
|
321
|
+
|
|
322
|
+
return bool(callback(*args))
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
_signals = Signals()
|
|
326
|
+
emit_signal = _signals.emit
|
|
327
|
+
register_signal = _signals.register
|
|
328
|
+
connect_signal = _signals.connect
|
|
329
|
+
disconnect_signal = _signals.disconnect
|
|
330
|
+
disconnect_signal_by_key = _signals.disconnect_by_key
|
urwid/split_repr.py
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# Urwid split_repr helper functions
|
|
2
|
+
# Copyright (C) 2004-2011 Ian Ward
|
|
3
|
+
#
|
|
4
|
+
# This library is free software; you can redistribute it and/or
|
|
5
|
+
# modify it under the terms of the GNU Lesser General Public
|
|
6
|
+
# License as published by the Free Software Foundation; either
|
|
7
|
+
# version 2.1 of the License, or (at your option) any later version.
|
|
8
|
+
#
|
|
9
|
+
# This library is distributed in the hope that it will be useful,
|
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
12
|
+
# Lesser General Public License for more details.
|
|
13
|
+
#
|
|
14
|
+
# You should have received a copy of the GNU Lesser General Public
|
|
15
|
+
# License along with this library; if not, write to the Free Software
|
|
16
|
+
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
17
|
+
#
|
|
18
|
+
# Urwid web site: https://urwid.org/
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
from inspect import getfullargspec
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def split_repr(self):
|
|
27
|
+
"""
|
|
28
|
+
Return a helpful description of the object using
|
|
29
|
+
self._repr_words() and self._repr_attrs() to add
|
|
30
|
+
to the description. This function may be used by
|
|
31
|
+
adding code to your class like this:
|
|
32
|
+
|
|
33
|
+
>>> class Foo(object):
|
|
34
|
+
... __repr__ = split_repr
|
|
35
|
+
... def _repr_words(self):
|
|
36
|
+
... return ["words", "here"]
|
|
37
|
+
... def _repr_attrs(self):
|
|
38
|
+
... return {'attrs': "appear too"}
|
|
39
|
+
>>> Foo()
|
|
40
|
+
<Foo words here attrs='appear too'>
|
|
41
|
+
>>> class Bar(Foo):
|
|
42
|
+
... def _repr_words(self):
|
|
43
|
+
... return Foo._repr_words(self) + ["too"]
|
|
44
|
+
... def _repr_attrs(self):
|
|
45
|
+
... return dict(Foo._repr_attrs(self), barttr=42)
|
|
46
|
+
>>> Bar()
|
|
47
|
+
<Bar words here too attrs='appear too' barttr=42>
|
|
48
|
+
"""
|
|
49
|
+
alist = sorted((str(k), normalize_repr(v)) for k, v in self._repr_attrs().items())
|
|
50
|
+
|
|
51
|
+
words = self._repr_words()
|
|
52
|
+
if not words and not alist:
|
|
53
|
+
# if we're just going to print the classname fall back
|
|
54
|
+
# to the previous __repr__ implementation instead
|
|
55
|
+
return super(self.__class__, self).__repr__()
|
|
56
|
+
if words and alist:
|
|
57
|
+
words.append("")
|
|
58
|
+
return f"<{self.__class__.__name__} {' '.join(words) + ' '.join([f'{k}={v}' for k, v in alist])}>"
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def normalize_repr(v):
|
|
62
|
+
"""
|
|
63
|
+
Return dictionary repr sorted by keys, leave others unchanged
|
|
64
|
+
|
|
65
|
+
>>> normalize_repr({1:2,3:4,5:6,7:8})
|
|
66
|
+
'{1: 2, 3: 4, 5: 6, 7: 8}'
|
|
67
|
+
>>> normalize_repr('foo')
|
|
68
|
+
"'foo'"
|
|
69
|
+
"""
|
|
70
|
+
if isinstance(v, dict):
|
|
71
|
+
items = sorted((repr(k), repr(v)) for k, v in v.items())
|
|
72
|
+
|
|
73
|
+
return f"{{{', '.join(f'{k}: {v}' for k, v in items)}}}"
|
|
74
|
+
|
|
75
|
+
return repr(v)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def remove_defaults(d, fn):
|
|
79
|
+
"""
|
|
80
|
+
Remove keys in d that are set to the default values from
|
|
81
|
+
fn. This method is used to unclutter the _repr_attrs()
|
|
82
|
+
return value.
|
|
83
|
+
|
|
84
|
+
d will be modified by this function.
|
|
85
|
+
|
|
86
|
+
Returns d.
|
|
87
|
+
|
|
88
|
+
>>> class Foo(object):
|
|
89
|
+
... def __init__(self, a=1, b=2):
|
|
90
|
+
... self.values = a, b
|
|
91
|
+
... __repr__ = split_repr
|
|
92
|
+
... def _repr_words(self):
|
|
93
|
+
... return ["object"]
|
|
94
|
+
... def _repr_attrs(self):
|
|
95
|
+
... d = dict(a=self.values[0], b=self.values[1])
|
|
96
|
+
... return remove_defaults(d, Foo.__init__)
|
|
97
|
+
>>> Foo(42, 100)
|
|
98
|
+
<Foo object a=42 b=100>
|
|
99
|
+
>>> Foo(10, 2)
|
|
100
|
+
<Foo object a=10>
|
|
101
|
+
>>> Foo()
|
|
102
|
+
<Foo object>
|
|
103
|
+
"""
|
|
104
|
+
args, varargs, varkw, defaults, _, _, _ = getfullargspec(fn)
|
|
105
|
+
|
|
106
|
+
# ignore *varargs and **kwargs
|
|
107
|
+
if varkw:
|
|
108
|
+
del args[-1]
|
|
109
|
+
if varargs:
|
|
110
|
+
del args[-1]
|
|
111
|
+
|
|
112
|
+
# create a dictionary of args with default values
|
|
113
|
+
ddict = dict(zip(args[len(args) - len(defaults) :], defaults))
|
|
114
|
+
|
|
115
|
+
for k in list(d.keys()):
|
|
116
|
+
if k in ddict and ddict[k] == d[k]:
|
|
117
|
+
# remove values that match their defaults
|
|
118
|
+
del d[k]
|
|
119
|
+
|
|
120
|
+
return d
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def _test():
|
|
124
|
+
import doctest
|
|
125
|
+
|
|
126
|
+
doctest.testmod()
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
if __name__ == "__main__":
|
|
130
|
+
_test()
|