Skip to content

Commit 24012f7

Browse files
committed
Include contrib folder and pytorch stringifier
This PR introduces a new contrib folder under pudb for community customization. A stringifier for pytorch is included. Detailed changelog: * A module was added under pudb/contrib/stringifiers as place to store custom stringifiers. * A stringifier for pytorch tensors and modules is included. * An option was added in the settings menu for the user to enable or disable the cotrib content for users that want to stick to the core pudb installation. * Changes were made in var_view.py and settings.py to allow for inclusion of the contrib/stringifiers in the configuration menu. Signed-off-by: Giorgos Paraskevopoulos <[email protected]>
1 parent f0de9c6 commit 24012f7

8 files changed

+158
-4
lines changed

README.rst

+8
Original file line numberDiff line numberDiff line change
@@ -84,3 +84,11 @@ version control tool.::
8484
git clone https://github.com/inducer/pudb.git
8585

8686
You may also `browse the code <https://github.com/inducer/pudb>`_ online.
87+
88+
89+
Customize and Extend PuDB
90+
-------------------------
91+
92+
You can contribute your custom stringifiers, themes and shells under
93+
`pudb/contrib` folder. Currently the process is streamlined for stringifiers,
94+
while shells and themes will require some refactoring of the core PuDB code.

pudb/contrib/README.md

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Community contributed extensions for pudb
2+
3+
Here the community can extend pudb with custom stringifiers, themes and shells.
4+
5+
6+
## How to contribute your stringifiers
7+
8+
Simply add a new python module inside `contrib/stringifiers` that contains your custom stringifier.
9+
10+
Then add your stringifier to the `CONTRIB_STRINGIFIERS` dict inside
11+
`contrib/stringifiers/__init__.py`.
12+
13+
The new options should appear in the pudb settings pane after setting the `Enable community contributed content` option.

pudb/contrib/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from pudb.contrib.stringifiers import CONTRIB_STRINGIFIERS

pudb/contrib/stringifiers/__init__.py

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
from pudb.contrib.stringifiers.torch_stringifier import torch_stringifier_fn
2+
3+
CONTRIB_STRINGIFIERS = {
4+
# User contributed stringifiers
5+
# Use the contrib prefix for all keys to avoid clashes with the core stringifiers
6+
# and make known to the user that this is community contributed code
7+
"contrib.pytorch": torch_stringifier_fn,
8+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
from typing import Any
2+
3+
try:
4+
import torch
5+
6+
HAVE_TORCH = 1
7+
except:
8+
HAVE_TORCH = 0
9+
10+
import pudb.var_view as vv
11+
12+
13+
def torch_stringifier_fn(value: Any) -> str:
14+
if not HAVE_TORCH:
15+
# Fall back to default stringifier
16+
17+
return vv.default_stringifier(value)
18+
19+
if isinstance(value, torch.nn.Module):
20+
device: str = str(next(value.parameters()).device)
21+
params: int = sum([p.numel() for p in value.parameters() if p.requires_grad])
22+
rep: str = value.__repr__() if len(value.__repr__()) < 55 else type(
23+
value
24+
).__name__
25+
26+
return "{}[{}] Params: {}".format(rep, device, params)
27+
elif isinstance(value, torch.Tensor):
28+
return "{}[{}][{}] {}".format(
29+
type(value).__name__,
30+
str(value.dtype).replace("torch.", ""),
31+
str(value.device),
32+
str(list(value.shape)),
33+
)
34+
else:
35+
# Fall back to default stringifier
36+
37+
return vv.default_stringifier(value)

pudb/settings.py

+35-3
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@
2525

2626
import os
2727
import sys
28-
2928
from configparser import ConfigParser
3029
from pudb.lowlevel import (lookup_module, get_breakpoint_invalid_reason,
3130
settings_log)
@@ -123,6 +122,8 @@ def load_config():
123122

124123
conf_dict.setdefault("hide_cmdline_win", "False")
125124

125+
conf_dict.setdefault("enable_community_contributed_content", "False")
126+
126127
def normalize_bool_inplace(name):
127128
try:
128129
if conf_dict[name].lower() in ["0", "false", "off"]:
@@ -136,6 +137,7 @@ def normalize_bool_inplace(name):
136137
normalize_bool_inplace("wrap_variables")
137138
normalize_bool_inplace("prompt_on_quit")
138139
normalize_bool_inplace("hide_cmdline_win")
140+
normalize_bool_inplace("enable_community_contributed_content")
139141

140142
_config_[0] = conf_dict
141143
return conf_dict
@@ -223,6 +225,11 @@ def _update_config(check_box, new_state, option_newvalue):
223225
conf_dict.update(new_conf_dict)
224226
_update_hide_cmdline_win()
225227

228+
elif option == "enable_community_contributed_content":
229+
new_conf_dict["enable_community_contributed_content"] = (
230+
not check_box.get_state())
231+
conf_dict.update(new_conf_dict)
232+
226233
elif option == "current_stack_frame":
227234
# only activate if the new state of the radio button is 'on'
228235
if new_state:
@@ -270,6 +277,14 @@ def _update_config(check_box, new_state, option_newvalue):
270277
bool(conf_dict["hide_cmdline_win"]), on_state_change=_update_config,
271278
user_data=("hide_cmdline_win", None))
272279

280+
enable_community_contributed_content = urwid.CheckBox(
281+
"Enable community contributed content. This will give you access to more "
282+
"stringifiers, shells and themes. \n"
283+
"Changing this setting requires a restart of PuDB.",
284+
bool(conf_dict["enable_community_contributed_content"]),
285+
on_state_change=_update_config,
286+
user_data=("enable_community_contributed_content", None))
287+
273288
# {{{ shells
274289

275290
shell_info = urwid.Text("This is the shell that will be "
@@ -345,8 +360,18 @@ def _update_config(check_box, new_state, option_newvalue):
345360
# {{{ stringifier
346361

347362
from pudb.var_view import STRINGIFIERS
363+
from pudb.contrib.stringifiers import CONTRIB_STRINGIFIERS
348364
stringifier_opts = list(STRINGIFIERS.keys())
365+
if conf_dict["enable_community_contributed_content"]:
366+
stringifier_opts = (
367+
list(STRINGIFIERS.keys()) + list(CONTRIB_STRINGIFIERS.keys()))
349368
known_stringifier = conf_dict["stringifier"] in stringifier_opts
369+
contrib_stringifier = conf_dict["stringifier"] in CONTRIB_STRINGIFIERS
370+
fallback_to_default_stringifier = (contrib_stringifier and not
371+
conf_dict["enable_community_contributed_content"])
372+
use_default_stringifier = ((conf_dict["stringifier"] == "default") or
373+
fallback_to_default_stringifier)
374+
custom_stringifier = not (known_stringifier or contrib_stringifier)
350375
stringifier_rb_group = []
351376
stringifier_edit = urwid.Edit(edit_text=conf_dict["custom_stringifier"])
352377
stringifier_info = urwid.Text(
@@ -357,15 +382,21 @@ def _update_config(check_box, new_state, option_newvalue):
357382
"be slower than the default, type, or id stringifiers.\n")
358383
stringifier_edit_list_item = urwid.AttrMap(stringifier_edit,
359384
"input", "focused input")
385+
360386
stringifier_rbs = [
387+
urwid.RadioButton(stringifier_rb_group, "default",
388+
use_default_stringifier,
389+
on_state_change=_update_config,
390+
user_data=("stringifier", "default"))
391+
]+[
361392
urwid.RadioButton(stringifier_rb_group, name,
362393
conf_dict["stringifier"] == name,
363394
on_state_change=_update_config,
364395
user_data=("stringifier", name))
365-
for name in stringifier_opts
396+
for name in stringifier_opts if name != "default"
366397
]+[
367398
urwid.RadioButton(stringifier_rb_group, "Custom:",
368-
not known_stringifier, on_state_change=_update_config,
399+
custom_stringifier, on_state_change=_update_config,
369400
user_data=("stringifier", None)),
370401
stringifier_edit_list_item,
371402
urwid.Text("\nTo use a custom stringifier, see "
@@ -441,6 +472,7 @@ def _update_config(check_box, new_state, option_newvalue):
441472
+ [cb_line_numbers]
442473
+ [cb_prompt_on_quit]
443474
+ [hide_cmdline_win]
475+
+ [enable_community_contributed_content]
444476

445477
+ [urwid.AttrMap(urwid.Text("\nShell:\n"), "group head")]
446478
+ [shell_info]

pudb/var_view.py

+10-1
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,8 @@ def __init__(self):
203203
self.access_level = CONFIG["default_variables_access_level"]
204204
self.show_methods = False
205205
self.wrap = CONFIG["wrap_variables"]
206-
206+
self.enable_contrib_stringifiers = \
207+
CONFIG["enable_community_contributed_content"]
207208

208209
class WatchExpression:
209210
def __init__(self, expression):
@@ -456,11 +457,19 @@ def error_stringifier(_):
456457
}
457458

458459

460+
import pudb.contrib.stringifiers as contrib
461+
462+
459463
def get_stringifier(iinfo: InspectInfo) -> Callable:
460464
"""
461465
:return: a function that turns an object into a Unicode text object.
462466
"""
463467
try:
468+
if iinfo.display_type in contrib.CONTRIB_STRINGIFIERS:
469+
if iinfo.enable_contrib_stringifiers:
470+
return contrib.CONTRIB_STRINGIFIERS[iinfo.display_type]
471+
else:
472+
return STRINGIFIERS["default"]
464473
return STRINGIFIERS[iinfo.display_type]
465474
except KeyError:
466475
try:
+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
try:
2+
import torch
3+
HAVE_TORCH = True
4+
except ImportError:
5+
HAVE_TORCH = False
6+
7+
from pudb.var_view import default_stringifier
8+
from pudb.contrib.stringifiers.torch_stringifier import torch_stringifier_fn
9+
10+
def test_tensor():
11+
if HAVE_TORCH:
12+
x = torch.randn(10, 5, 4)
13+
assert torch_stringifier_fn(x) == "Tensor[float32][cpu] [10, 5, 4]"
14+
15+
16+
def test_conv_module():
17+
if HAVE_TORCH:
18+
x = torch.nn.Conv2d(20, 10, 3)
19+
assert torch_stringifier_fn(x) == "Conv2d(20, 10, kernel_size=(3, 3), stride=(1, 1))[cpu] Params: 1810"
20+
21+
22+
def test_linear_module():
23+
if HAVE_TORCH:
24+
x = torch.nn.Linear(5, 2, bias=False)
25+
assert torch_stringifier_fn(x) == "Linear(in_features=5, out_features=2, bias=False)[cpu] Params: 10"
26+
27+
28+
def test_long_module_repr_should_revert_to_type():
29+
if HAVE_TORCH:
30+
x = torch.nn.Transformer()
31+
assert torch_stringifier_fn(x) == "Transformer[cpu] Params: 44140544"
32+
33+
34+
def test_reverts_to_default_for_str():
35+
x = "Everyone has his day, and some days last longer than others."
36+
assert torch_stringifier_fn(x) == default_stringifier(x)
37+
38+
39+
def test_reverts_to_default_for_dict():
40+
x = {"a": 1, "b": 2, "c": 3}
41+
assert torch_stringifier_fn(x) == default_stringifier(x)
42+
43+
44+
def test_reverts_to_default_for_list():
45+
x = list(range(1000))
46+
assert torch_stringifier_fn(x) == default_stringifier(x)

0 commit comments

Comments
 (0)