Skip to content

Commit e33cf94

Browse files
committed
Add support for tab-completion in VSCode and IPython/Jupyter. #502
1 parent 344df59 commit e33cf94

File tree

2 files changed

+50
-0
lines changed

2 files changed

+50
-0
lines changed

benedict/dicts/keyattr/keyattr_dict.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,22 @@ def __setattr__(self, attr: _K | str, value: Any) -> None:
6868
raise AttributeError(attr_message)
6969
self.__setitem__(attr_k, value)
7070

71+
def __dir__(self) -> list[str]:
72+
# Extend tab-completion (VSCode, IPython, etc.) with the dict keys when
73+
# keyattr is enabled, since keys are accessible as attributes via __getattr__.
74+
# Non-string keys are excluded: __dir__ must return strings (Python protocol)
75+
# and non-string keys can never be valid attribute names anyway.
76+
keys: list[str] = (
77+
[key for key in self.keys() if isinstance(key, str)]
78+
if self._keyattr_enabled
79+
else []
80+
)
81+
return sorted(set(list(super().__dir__()) + keys))
82+
83+
def _ipython_key_completions_(self) -> list[Any]:
84+
# Used by IPython/Jupyter for subscript tab-completion: obj["<TAB>"]
85+
return list(self.keys())
86+
7187
def __setstate__(self, state: Mapping[str, Any]) -> None:
7288
super().__setstate__(state)
7389
self._keyattr_enabled = state["_keyattr_enabled"]

tests/dicts/test_benedict_keyattr.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,3 +216,37 @@ def test_keyattr_dynamic_inheritance_on_casting(self) -> None:
216216
d = benedict(keyattr_dynamic=False)
217217
c = benedict(d)
218218
self.assertFalse(c.keyattr_dynamic)
219+
220+
def test_dir_with_keyattr_enabled(self) -> None:
221+
d = benedict({"a": 1, "b": 2}, keyattr_enabled=True)
222+
result = dir(d)
223+
self.assertIn("a", result)
224+
self.assertIn("b", result)
225+
226+
def test_dir_with_keyattr_disabled(self) -> None:
227+
d = benedict({"a": 1, "b": 2}, keyattr_enabled=False)
228+
result = dir(d)
229+
self.assertNotIn("a", result)
230+
self.assertNotIn("b", result)
231+
232+
def test_dir_with_nested_keys(self) -> None:
233+
d = benedict({"a": {"b": {"c": 1}}}, keyattr_enabled=True)
234+
self.assertIn("a", dir(d))
235+
self.assertIn("b", dir(d.a))
236+
self.assertIn("c", dir(d.a.b))
237+
238+
def test_ipython_key_completions(self) -> None:
239+
d = benedict({"a": 1, "b": 2})
240+
self.assertEqual(sorted(d._ipython_key_completions_()), ["a", "b"])
241+
242+
def test_ipython_key_completions_with_nested_keys(self) -> None:
243+
d = benedict({"a": {"b": {"c": 1}}})
244+
self.assertEqual(d._ipython_key_completions_(), ["a"])
245+
self.assertEqual(d.a._ipython_key_completions_(), ["b"])
246+
self.assertEqual(d.a.b._ipython_key_completions_(), ["c"])
247+
248+
def test_dir_skips_non_string_keys(self) -> None:
249+
d = benedict({1: "a", "b": 2}, keyattr_enabled=True)
250+
result = dir(d)
251+
self.assertIn("b", result)
252+
self.assertNotIn(1, result)

0 commit comments

Comments
 (0)