1"""
2A context manager for managing things injected into :mod:`builtins`.
3"""
4# Copyright (c) IPython Development Team.
5# Distributed under the terms of the Modified BSD License.
6from __future__ import annotations
7
8import builtins as builtin_mod
9from typing import Any, Literal, TYPE_CHECKING
10
11from traitlets.config.configurable import Configurable
12
13from traitlets import Instance
14
15if TYPE_CHECKING:
16 from types import TracebackType
17
18
19class __BuiltinUndefined:
20 pass
21
22
23BuiltinUndefined = __BuiltinUndefined()
24
25
26class __HideBuiltin:
27 pass
28
29
30HideBuiltin = __HideBuiltin()
31
32
33class BuiltinTrap(Configurable):
34
35 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC',
36 allow_none=True)
37
38 def __init__(self, shell: Any = None) -> None:
39 super().__init__(shell=shell, config=None)
40 self._orig_builtins: dict[str, Any] = {}
41 # We define this to track if a single BuiltinTrap is nested.
42 # Only turn off the trap when the outermost call to __exit__ is made.
43 self._nested_level = 0
44 self.shell = shell
45 # builtins we always add - if set to HideBuiltin, they will just
46 # be removed instead of being replaced by something else
47 self.auto_builtins: dict[str, Any] = {
48 'exit': HideBuiltin,
49 'quit': HideBuiltin,
50 'get_ipython': self.shell.get_ipython,
51 }
52
53 def __enter__(self) -> BuiltinTrap:
54 if self._nested_level == 0:
55 self.activate()
56 self._nested_level += 1
57 # I return self, so callers can use add_builtin in a with clause.
58 return self
59
60 def __exit__(self, type: type[BaseException] | None, value: BaseException | None, traceback: TracebackType | None) -> Literal[False]:
61 if self._nested_level == 1:
62 self.deactivate()
63 self._nested_level -= 1
64 # Returning False will cause exceptions to propagate
65 return False
66
67 def add_builtin(self, key: str, value: Any) -> None:
68 """Add a builtin and save the original."""
69 bdict = builtin_mod.__dict__
70 orig = bdict.get(key, BuiltinUndefined)
71 if value is HideBuiltin:
72 if orig is not BuiltinUndefined: #same as 'key in bdict'
73 self._orig_builtins[key] = orig
74 del bdict[key]
75 else:
76 self._orig_builtins[key] = orig
77 bdict[key] = value
78
79 def remove_builtin(self, key: str, orig: Any) -> None:
80 """Remove an added builtin and re-set the original."""
81 if orig is BuiltinUndefined:
82 del builtin_mod.__dict__[key]
83 else:
84 builtin_mod.__dict__[key] = orig
85
86 def activate(self) -> None:
87 """Store ipython references in the __builtin__ namespace."""
88
89 add_builtin = self.add_builtin
90 for name, func in self.auto_builtins.items():
91 add_builtin(name, func)
92
93 def deactivate(self) -> None:
94 """Remove any builtins which might have been added by add_builtins, or
95 restore overwritten ones to their previous values."""
96 remove_builtin = self.remove_builtin
97 for key, val in self._orig_builtins.items():
98 remove_builtin(key, val)
99 self._orig_builtins.clear()