Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 44 additions & 7 deletions IPython/core/interactiveshell.py
Original file line number Diff line number Diff line change
Expand Up @@ -200,11 +200,49 @@ def validate(self, obj, value):
return super(SeparateUnicode, self).validate(obj, value)


@undoc
class DummyMod:
"""A dummy module used for IPython's interactive module when
a namespace must be assigned to the module's __dict__."""
__spec__ = None
class _IPythonMainModuleBase(types.ModuleType):
def __init__(self) -> None:
super().__init__(
"__main__",
doc="Automatically created module for the IPython interactive environment",
)


def make_main_module_type(user_ns: dict[str, Any]) -> type[_IPythonMainModuleBase]:
@undoc
class IPythonMainModule(_IPythonMainModuleBase):
"""
ModuleType that supports passing in a custom user namespace dictionary,
to be used for the module's __dict__. This is enabled by shadowing the
underlying __dict__ attribute of the module, and overriding getters and
setters to point to the custom user namespace dictionary.
The reason to do this is to allow the __main__ module to be an instance
of ModuleType, while still allowing the user namespace to be custom.
"""

@property
def __dict__(self) -> dict[str, Any]: # type: ignore[override]
return user_ns

def __setattr__(self, item: str, value: Any) -> None:
if item == "__dict__":
# Ignore this when IPython tries to set it, since we already provide it
return
user_ns[item] = value

def __getattr__(self, item: str) -> Any:
try:
return user_ns[item]
except KeyError:
raise AttributeError(f"module {self.__name__} has no attribute {item}")

def __delattr__(self, item: str) -> None:
try:
del user_ns[item]
except KeyError:
raise AttributeError(f"module {self.__name__} has no attribute {item}")

return IPythonMainModule


class ExecutionInfo:
Expand Down Expand Up @@ -1265,8 +1303,7 @@ def prepare_user_module(self, user_module=None, user_ns=None):
"""
if user_module is None and user_ns is not None:
user_ns.setdefault("__name__", "__main__")
user_module = DummyMod()
user_module.__dict__ = user_ns
user_module = make_main_module_type(user_ns)()

if user_module is None:
user_module = types.ModuleType("__main__",
Expand Down
5 changes: 2 additions & 3 deletions IPython/terminal/embed.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from IPython.core import ultratb, compilerop
from IPython.core import magic_arguments
from IPython.core.magic import Magics, magics_class, line_magic
from IPython.core.interactiveshell import DummyMod, InteractiveShell
from IPython.core.interactiveshell import InteractiveShell, make_main_module_type
from IPython.terminal.interactiveshell import TerminalInteractiveShell
from IPython.terminal.ipapp import load_default_config

Expand Down Expand Up @@ -306,8 +306,7 @@ def mainloop(
warnings.warn("Failed to get module %s" % \
global_ns.get('__name__', 'unknown module')
)
module = DummyMod()
module.__dict__ = global_ns
module = make_main_module_type(global_ns)()
if compile_flags is None:
compile_flags = (call_frame.f_code.co_flags &
compilerop.PyCF_MASK)
Expand Down