Skip to content

Commit 25a9099

Browse files
committed
Add descriptive str and repr implementations
When debugging and logging objects it is very useful to get a descriptive and clear string representation. The new representation uses the class name and the user defined name (if any) for `str` and a `repr` that tries to show how the class was created but also important internal state. Signed-off-by: Leandro Lucarella <[email protected]> # ------------------------ >8 ------------------------ # Do not modify or remove the line above. # Everything below it will be ignored. # # Conflicts: # src/frequenz/channels/_base_classes.py # src/frequenz/channels/_bidirectional.py
1 parent 9205e87 commit 25a9099

File tree

7 files changed

+150
-1
lines changed

7 files changed

+150
-1
lines changed

src/frequenz/channels/_anycast.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,17 @@ def new_receiver(self) -> Receiver[T]:
171171
"""
172172
return Receiver(self)
173173

174+
def __str__(self) -> str:
175+
"""Return a string representation of this channel."""
176+
return f"{type(self).__name__}:{self._name}"
177+
178+
def __repr__(self) -> str:
179+
"""Return a string representation of this channel."""
180+
return (
181+
f"{type(self).__name__}(name={self._name!r}, limit={self.limit!r}):<"
182+
f"current={len(self._deque)!r}, closed={self._closed!r}>"
183+
)
184+
174185

175186
class Sender(BaseSender[T]):
176187
"""A sender to send messages to an Anycast channel.
@@ -217,6 +228,14 @@ async def send(self, msg: T) -> None:
217228
self._chan._recv_cv.notify(1)
218229
# pylint: enable=protected-access
219230

231+
def __str__(self) -> str:
232+
"""Return a string representation of this sender."""
233+
return f"{self._chan}:{type(self).__name__}"
234+
235+
def __repr__(self) -> str:
236+
"""Return a string representation of this sender."""
237+
return f"{type(self).__name__}({self._chan!r})"
238+
220239

221240
class _Empty:
222241
"""A sentinel value to indicate that a value has not been set."""
@@ -291,3 +310,11 @@ def consume(self) -> T:
291310
self._next = _Empty
292311

293312
return next_val
313+
314+
def __str__(self) -> str:
315+
"""Return a string representation of this receiver."""
316+
return f"{self._chan}:{type(self).__name__}"
317+
318+
def __repr__(self) -> str:
319+
"""Return a string representation of this receiver."""
320+
return f"{type(self).__name__}({self._chan!r})"

src/frequenz/channels/_base_classes.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,3 +206,11 @@ def consume(self) -> U: # noqa: DOC502
206206
return self._transform(
207207
self._receiver.consume()
208208
) # pylint: disable=protected-access
209+
210+
def __str__(self) -> str:
211+
"""Return a string representation of the timer."""
212+
return f"{type(self).__name__}:{self._receiver}:{self._transform}"
213+
214+
def __repr__(self) -> str:
215+
"""Return a string representation of the timer."""
216+
return f"{type(self).__name__}({self._receiver!r}, {self._transform!r})"

src/frequenz/channels/_bidirectional.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,17 @@ def consume(self) -> W:
112112
err.__cause__ = this_chan_error
113113
raise err
114114

115+
def __str__(self) -> str:
116+
"""Return a string representation of this handle."""
117+
return f"{type(self).__name__}:{self._chan}"
118+
119+
def __repr__(self) -> str:
120+
"""Return a string representation of this handle."""
121+
return (
122+
f"{type(self).__name__}(channel={self._chan!r}, "
123+
f"sender={self._sender!r}, receiver={self._receiver!r})"
124+
)
125+
115126
def __init__(self, *, name: str | None = None) -> None:
116127
"""Create a `Bidirectional` instance.
117128
@@ -180,3 +191,15 @@ def service_handle(self) -> Bidirectional.Handle[U, T]:
180191
Object to send/receive messages with.
181192
"""
182193
return self._service_handle
194+
195+
def __str__(self) -> str:
196+
"""Return a string representation of this channel."""
197+
return f"{type(self).__name__}:{self._name}"
198+
199+
def __repr__(self) -> str:
200+
"""Return a string representation of this channel."""
201+
return (
202+
f"{type(self).__name__}(name={self._name!r}):<"
203+
f"request_channel={self._request_channel!r}, "
204+
f"response_channel={self._response_channel!r}>"
205+
)

src/frequenz/channels/_broadcast.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,20 @@ def new_peekable(self) -> Peekable[T]:
200200
"""
201201
return Peekable(self)
202202

203+
def __str__(self) -> str:
204+
"""Return a string representation of this receiver."""
205+
return f"{type(self).__name__}:{self._name}"
206+
207+
def __repr__(self) -> str:
208+
"""Return a string representation of this channel."""
209+
return (
210+
f"{type(self).__name__}(name={self._name!r}, "
211+
f"resend_latest={self.resend_latest!r}):<"
212+
f"latest={self._latest!r}, "
213+
f"receivers={len(self._receivers)!r}, "
214+
f"closed={self._closed!r}>"
215+
)
216+
203217

204218
class Sender(BaseSender[T]):
205219
"""A sender to send messages to the broadcast channel.
@@ -248,6 +262,14 @@ async def send(self, msg: T) -> None:
248262
self._chan._recv_cv.notify_all()
249263
# pylint: enable=protected-access
250264

265+
def __str__(self) -> str:
266+
"""Return a string representation of this sender."""
267+
return f"{self._chan}:{type(self).__name__}"
268+
269+
def __repr__(self) -> str:
270+
"""Return a string representation of this sender."""
271+
return f"{type(self).__name__}({self._chan!r})"
272+
251273

252274
class Receiver(BaseReceiver[T]):
253275
"""A receiver to receive messages from the broadcast channel.
@@ -396,6 +418,20 @@ def into_peekable(self) -> Peekable[T]:
396418
self._deactivate()
397419
return Peekable(self._chan)
398420

421+
def __str__(self) -> str:
422+
"""Return a string representation of this receiver."""
423+
return f"{self._chan}:{type(self).__name__}"
424+
425+
def __repr__(self) -> str:
426+
"""Return a string representation of this receiver."""
427+
limit = self._q.maxlen
428+
assert limit is not None
429+
return (
430+
f"{type(self).__name__}(name={self._name!r}, limit={limit!r}, "
431+
f"{self._chan!r}):<id={id(self)!r}, used={len(self._q)!r}, "
432+
f"active={self._active!r}>"
433+
)
434+
399435

400436
class Peekable(BasePeekable[T]):
401437
"""A Peekable to peek into broadcast channels.
@@ -422,3 +458,11 @@ def peek(self) -> T | None:
422458
has been sent to the channel yet, or if the channel is closed.
423459
"""
424460
return self._chan._latest # pylint: disable=protected-access
461+
462+
def __str__(self) -> str:
463+
"""Return a string representation of this receiver."""
464+
return f"{self._chan}:{type(self).__name__}"
465+
466+
def __repr__(self) -> str:
467+
"""Return a string representation of this receiver."""
468+
return f"{type(self).__name__}({self._chan!r}):<latest={self.peek()!r}>"

src/frequenz/channels/util/_file_watcher.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,3 +137,17 @@ def consume(self) -> Event:
137137
return FileWatcher.Event(
138138
type=FileWatcher.EventType(change), path=pathlib.Path(path_str)
139139
)
140+
141+
def __str__(self) -> str:
142+
"""Return a string representation of this receiver."""
143+
if len(self._paths) > 3:
144+
paths = [str(p) for p in self._paths[:3]]
145+
paths.append("…")
146+
else:
147+
paths = [str(p) for p in self._paths]
148+
event_types = [event_type.name for event_type in self.event_types]
149+
return f"{type(self).__name__}:{','.join(event_types)}:{','.join(paths)}"
150+
151+
def __repr__(self) -> str:
152+
"""Return a string representation of this receiver."""
153+
return f"{type(self).__name__}({self._paths!r}, {self.event_types!r})"

src/frequenz/channels/util/_merge.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
"""Merge messages coming from channels into a single stream."""
55

66
import asyncio
7+
import itertools
78
from collections import deque
89
from typing import Any
910

@@ -117,3 +118,19 @@ def consume(self) -> T:
117118
assert self._results, "`consume()` must be preceded by a call to `ready()`"
118119

119120
return self._results.popleft()
121+
122+
def __str__(self) -> str:
123+
"""Return a string representation of this receiver."""
124+
if len(self._receivers) > 3:
125+
receivers = [str(p) for p in itertools.islice(self._receivers.values(), 3)]
126+
receivers.append("…")
127+
else:
128+
receivers = [str(p) for p in self._receivers.values()]
129+
return f"{type(self).__name__}:{','.join(receivers)}"
130+
131+
def __repr__(self) -> str:
132+
"""Return a string representation of this receiver."""
133+
return (
134+
f"{type(self).__name__}("
135+
f"{', '.join(f'{k}={v!r}' for k, v in self._receivers.items())})"
136+
)

src/frequenz/channels/util/_merge_named.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22
# Copyright © 2022 Frequenz Energy-as-a-Service GmbH
33

44
"""Merge messages coming from channels into a single stream containing name of message."""
5-
65
import asyncio
6+
import itertools
77
from collections import deque
88
from typing import Any
99

@@ -102,3 +102,19 @@ def consume(self) -> tuple[str, T]:
102102
assert self._results, "`consume()` must be preceded by a call to `ready()`"
103103

104104
return self._results.popleft()
105+
106+
def __str__(self) -> str:
107+
"""Return a string representation of this receiver."""
108+
if len(self._receivers) > 3:
109+
receivers = [str(p) for p in itertools.islice(self._receivers, 3)]
110+
receivers.append("…")
111+
else:
112+
receivers = [str(p) for p in self._receivers]
113+
return f"{type(self).__name__}:{','.join(receivers)}"
114+
115+
def __repr__(self) -> str:
116+
"""Return a string representation of this receiver."""
117+
return (
118+
f"{type(self).__name__}("
119+
f"{', '.join(f'{k}={v!r}' for k, v in self._receivers.items())})"
120+
)

0 commit comments

Comments
 (0)