"""IPython variables module.
Caveats:
Imported at the beginning before iPython when other module
Imported after IPython when in nodeps.
Workaround in PyCharm Console:
ip = get_ipython()
import nodeps.ipython_dir.profile_default.ipython_pycharm_startup
file = nodeps.ipython_dir.profile_default.ipython_pycharm_startup.__file__
ip.user_ns["__file__"] = file
ip.user_ns["IPYTHON"] = ip
ip.safe_execfile(file, ip.user_ns, raise_exceptions=True)
"""
__all__ = (
"IPYTHONConfigType",
"CWD_STARTUP",
"CWD_SRC",
"IPYTHONType",
"NODEPS_MODULE_PATH",
"NODEPS_NAME",
"NODEPS_SRC",
"VIRTUAL_ENV",
"VIRTUAL_ENV_CWD_STARTUP",
"VIRTUAL_ENV_SRC",
"IPYTHONDIR",
"IPYTHON_PROFILE_DEFAULT_DIR",
"IPYTHON_CONFIG_FILE",
"IPYTHON_EXTENSIONS_DIR",
"IPYTHON_PYCHARM_STARTUP_FILE",
"IPYTHON_EXTENSIONS_NODEPS",
"IPYTHON_EXTENSIONS",
"IPYTHON_STARTUP_DIR",
"IPYTHON_STARTUP_IPY_FILES",
"IPYTHON_STARTUP_PY_FILES",
"PYTHONSTARTUP",
"PYCHARM_CONSOLE",
"RELOAD_EXTENSION",
"IPYTHONconfig",
"IPYTHON",
"to_sys_path",
)
import contextlib
import os
import pathlib
import platform
import sys
import tomllib
import warnings
from typing import TypeAlias
try:
# nodeps[ipython] extras
import IPython.core.shellapp # type: ignore[attr-defined]
from IPython.core.application import BaseIPythonApplication # type: ignore[attr-defined]
from IPython.core.completer import Completer, IPCompleter # type: ignore[attr-defined]
from IPython.core.events import EventManager, _define_event, available_events # type: ignore[attr-defined]
from IPython.core.formatters import BaseFormatter, PlainTextFormatter # type: ignore[attr-defined]
from IPython.core.history import HistoryAccessor, HistoryManager # type: ignore[attr-defined]
from IPython.core.interactiveshell import InteractiveShell, InteractiveShellABC # type: ignore[attr-defined]
from IPython.core.magic import MagicsManager # type: ignore[attr-defined]
from IPython.core.magics.logging import LoggingMagics # type: ignore[attr-defined]
from IPython.core.magics.script import ScriptMagics # type: ignore[attr-defined]
from IPython.core.profiledir import ProfileDir # type: ignore[attr-defined]
from IPython.core.shellapp import InteractiveShellApp # type: ignore[attr-defined]
from IPython.extensions.autoreload import update_instances # type: ignore[attr-defined]
from IPython.extensions.storemagic import StoreMagics, refresh_variables # type: ignore[attr-defined]
from IPython.terminal.interactiveshell import TerminalInteractiveShell # type: ignore[attr-defined]
from IPython.terminal.ipapp import TerminalIPythonApp, load_default_config # type: ignore[attr-defined]
from IPython.terminal.prompts import Prompts, Token # type: ignore[attr-defined]
from traitlets.config.application import Application, get_config # type: ignore[attr-defined]
except ModuleNotFoundError:
IPython = InteractiveShellABC = None
_define_event = get_config = load_default_config = refresh_variables = update_instances = lambda *args: None
Application = BaseFormatter = BaseIPythonApplication = Completer = HistoryAccessor = HistoryManager = \
InteractiveShell = InteractiveShellApp = IPCompleter = LoggingMagics = MagicsManager = PlainTextFormatter = \
ProfileDir = Prompts = ScriptMagics = StoreMagics = TerminalInteractiveShell = TerminalIPythonApp = Token = \
object
try:
import _pydev_bundle.pydev_ipython_console_011 # type: ignore[attr-defined]
from _pydev_bundle.pydev_ipython_console_011 import ( # type: ignore[attr-defined]
PyDebuggerTerminalInteractiveShell, # type: ignore[attr-defined]
PyDevTerminalInteractiveShell, # type: ignore[attr-defined]
_PyDevIPythonFrontEnd, # type: ignore[attr-defined]
)
except ModuleNotFoundError:
_pydev_bundle = _PyDevIPythonFrontEnd = PyDebuggerTerminalInteractiveShell = PyDevTerminalInteractiveShell = object
# <editor-fold desc="IPython Config Type Class">
class Config:
Application: Application = None
BaseFormatter: BaseFormatter = None
BaseIPythonApplication: BaseIPythonApplication = None
Completer: Completer = None
HistoryAccessor: HistoryAccessor = None
HistoryManager: HistoryManager = None
InteractiveShell: InteractiveShell = None
InteractiveShellApp: InteractiveShellApp = None
IPCompleter: IPCompleter = None
LoggingMagics: LoggingMagics = None
MagicsManager: MagicsManager = None
PlainTextFormatter: PlainTextFormatter = None
ProfileDir: ProfileDir = None
ScriptMagics: ScriptMagics = None
StoreMagics: StoreMagics = None
TerminalInteractiveShell: TerminalInteractiveShell = None
TerminalIPythonApp: TerminalIPythonApp = None
initialized: bool = False
IPYTHONConfigType: TypeAlias = Config
# </editor-fold>
# <editor-fold desc="Variables">
CWD_STARTUP = pathlib.Path.cwd()
"""Startup CWD."""
CWD_SRC = _cwd_src if (_cwd_src := CWD_STARTUP / "src").is_dir() else None
"""CWD/src"""
IPYTHONType: TypeAlias = TerminalInteractiveShell | PyDebuggerTerminalInteractiveShell | PyDevTerminalInteractiveShell
NODEPS_MODULE_PATH = pathlib.Path(__file__).parent.parent
"""NoDeps Source Path: src/nodeps or site-packages/nodeps"""
NODEPS_NAME = NODEPS_MODULE_PATH.name
NODEPS_SRC = NODEPS_MODULE_PATH.parent
"""Nodeps src directory /src or /site-packages for sys.path."""
VIRTUAL_ENV = pathlib.Path(_virtual_env) if (_virtual_env := os.environ.get("VIRTUAL_ENV")) else None
"""Virtual Env path."""
VIRTUAL_ENV_CWD_STARTUP = CWD_STARTUP if VIRTUAL_ENV else None
"""CWD if working in a virtual env."""
# noinspection PyUnboundLocalVariable
VIRTUAL_ENV_SRC = _src if VIRTUAL_ENV and (_src := VIRTUAL_ENV.parent / "src").is_dir() else None
"""src directory in a virtual env."""
IPYTHONDIR = NODEPS_MODULE_PATH / "ipython_dir"
"""IPython Profile: `export IPYTHONDIR="$(ipythondir)"`."""
IPYTHON_PROFILE_DEFAULT_DIR = IPYTHONDIR / "profile_default"
IPYTHON_CONFIG_FILE = IPYTHON_PROFILE_DEFAULT_DIR / "ipython_config.py"
IPYTHON_EXTENSIONS_DIR = IPYTHONDIR / "extensions"
IPYTHON_PYCHARM_STARTUP_FILE = IPYTHON_PROFILE_DEFAULT_DIR / "ipython_pycharm_startup.py"
"""Directory to install extensions, loaded automatically with ipython
if defined in config.InteractiveShellApp.extensions or
IPython.core.shellapp.InteractiveShellApp.extensions = IPYTHON_EXTENSIONS"""
IPYTHON_EXTENSIONS_NODEPS = [
str(_i.relative_to(NODEPS_SRC)).replace("/", ".").replace(".py", "")
for _i in IPYTHON_EXTENSIONS_DIR.iterdir() if _i.suffix == ".py"
]
"""Nodeps extensions module names installed in extensions dir."""
IPYTHON_EXTENSIONS = [
"IPython.extensions.autoreload", "IPython.extensions.storemagic", "rich", *IPYTHON_EXTENSIONS_NODEPS
]
"""All IPython extensions to load, loaded automatically with ipython
if defined in config.InteractiveShellApp.extensions or
IPython.core.shellapp.InteractiveShellApp.extensions = IPYTHON_EXTENSIONS"""
IPYTHON_STARTUP_DIR = IPYTHON_PROFILE_DEFAULT_DIR / "startup"
""".py and .ipy files in this directory will be run *prior* to any code or files specified
via the exec_lines or exec_files configurables whenever you load this profile.
"""
IPYTHON_STARTUP_IPY_FILES = [_i for _i in IPYTHON_STARTUP_DIR.iterdir() if _i.suffix == ".ipy"]
""".ipy run prior to any code or files specified via exec_lines, run automatically by ipython."""
IPYTHON_STARTUP_PY_FILES = [_i for _i in IPYTHON_STARTUP_DIR.iterdir() if _i.suffix == ".py"]
""".py run prior to any code or files specified via exec_lines, run automatically by ipython."""
PYTHONSTARTUP = IPYTHON_PROFILE_DEFAULT_DIR / "python_startup.py"
"""Python Startup :mod:`python_startup.__init__`: `export PYTHONSTARTUP="$(pythonstartup)"`."""
PYCHARM_CONSOLE = None
"""Running in PyCharm console."""
for item in sys.path:
if "PyCharm" in item or "pycharm" in item or "pydev" in item:
PYCHARM_CONSOLE = True
break
RELOAD_EXTENSION = None
"""Reload extension module name."""
for extension in IPYTHON_EXTENSIONS_NODEPS:
if "reload" in extension:
RELOAD_EXTENSION = extension
break
NODEPS_IPYTHON_IMPORT_MODULE = None
"""Module to import all."""
if VIRTUAL_ENV:
top = VIRTUAL_ENV.parent
if (pyproject_toml := (top / "pyproject.toml")).is_file():
with pathlib.Path(pyproject_toml).open("rb") as f:
data = tomllib.load(f)
NODEPS_IPYTHON_IMPORT_MODULE = data["project"]["name"]
elif VIRTUAL_ENV_SRC:
# src but not pyproject.toml
NODEPS_IPYTHON_IMPORT_MODULE = VIRTUAL_ENV_SRC.parent.name
elif (package := (top / top.name)).is_dir() and (package / "__init__.py").is_file():
# no src but package/package/__init__.py
NODEPS_IPYTHON_IMPORT_MODULE = top.name
try:
# noinspection PyUnboundLocalVariable
IPYTHON: IPYTHONType = get_ipython() # type: ignore[attr-defined]
except NameError:
try:
from IPython.core.getipython import get_ipython # type: ignore[attr-defined]
except ModuleNotFoundError:
get_ipython = lambda *args: None # noqa: E731
IPYTHON: IPYTHONType = get_ipython()
IPYTHONconfig: Config = get_config()
# </editor-fold>
# <editor-fold desc="MyPrompt">
class MyPrompt(Prompts):
"""IPython prompt."""
_project = None
@property
def project(self):
"""Project instance."""
if self._project is None:
import nodeps
self._project = nodeps.Project()
return self._project
def in_prompt_tokens(self, cli=None):
"""In prompt tokens."""
branch = latest = []
if self.project.gh:
branch = [
(Token, " "),
(Token.Generic, "↪"),
(Token.Generic, self.project.gh.current()),
]
latest = [
(Token, " "),
(Token.Name.Entity, self.project.gh.latest()),
]
return [
(Token, ""),
(Token.OutPrompt, pathlib.Path().absolute().stem),
*branch,
*((Token, " "), (Token.Prompt, "©") if os.environ.get("VIRTUAL_ENV") else (Token, "")),
(Token, " "),
(Token.Name.Class, "v" + platform.python_version()),
*latest,
(Token, " "),
(Token.Prompt, "["),
(Token.PromptNum, str(self.shell.execution_count)),
(Token.Prompt, "]: "),
(
Token.Prompt if self.shell.last_execution_succeeded else Token.Generic.Error,
"❯ ", # noqa: RUF001
),
]
def out_prompt_tokens(self, cli=None):
"""Out Prompt."""
return [
(Token.OutPrompt, "Out<"),
(Token.OutPromptNum, str(self.shell.execution_count)),
(Token.OutPrompt, ">: "),
]
# </editor-fold>
# <editor-fold desc="sys.path">
[docs]
def to_sys_path(path: pathlib.Path | str | None = None) -> list[str]:
"""Prepend path to sys.path if not in sys.path.
Args:
path: path to add, default None
Returns:
new sys.path
"""
if path is not None and (_path := str(path)) not in sys.path:
sys.path.insert(0, _path)
return sys.path
for _i in (IPYTHON_PROFILE_DEFAULT_DIR, NODEPS_SRC, VIRTUAL_ENV_CWD_STARTUP, VIRTUAL_ENV_SRC):
to_sys_path(_i)
# </editor-fold>
# <editor-fold desc="refresh_variables patch">
def _refresh_variables(ip: TerminalInteractiveShell):
"""Patch.
AttributeError: 'PickleShareDB' object has no attribute 'keys'
If not db.keys() already then config.StoreMagics.autorestore will fail
"""
if hasattr(ip.db, "keys"):
refresh_variables(ip)
if "IPython.extensions.storemagic" in sys.modules:
# noinspection PyUnboundLocalVariable,PyUnresolvedReferences
IPython.extensions.storemagic.refresh_variables = _refresh_variables
# </editor-fold>
# <editor-fold desc="update_instances patch">
def _update_instances(old, new):
"""Path autoreload."""
with contextlib.suppress(TypeError, AttributeError):
update_instances(old, new)
if "IPython.extensions.autoreload" in sys.modules:
# noinspection PyUnboundLocalVariable,PyUnresolvedReferences
IPython.extensions.autoreload.update_instances = _update_instances
# </editor-fold>
# <editor-fold desc="PyCharm config patches">
if "_pydev_bundle.pydev_ipython_console_011" in sys.modules:
_pydev_bundle.pydev_ipython_console_011.PyDevTerminalInteractiveShell.automagic = True
_pydev_bundle.pydev_ipython_console_011.PyDevTerminalInteractiveShell.banner1 = ""
_pydev_bundle.pydev_ipython_console_011.PyDevTerminalInteractiveShell.banner2 = ""
_pydev_bundle.pydev_ipython_console_011.PyDevTerminalInteractiveShell.colors = "Linux"
_pydev_bundle.pydev_ipython_console_011.PyDevTerminalInteractiveShell.history_length = 30000
_pydev_bundle.pydev_ipython_console_011.PyDevTerminalInteractiveShell.sphinxify_docstring = True
_pydev_bundle.pydev_ipython_console_011.PyDevTerminalInteractiveShell.auto_match = True
_pydev_bundle.pydev_ipython_console_011.PyDevTerminalInteractiveShell.autoformatter = "black"
_pydev_bundle.pydev_ipython_console_011.PyDevTerminalInteractiveShell.highlighting_style = "monokai"
if IPYTHON:
_pydev_bundle.pydev_ipython_console_011.PyDevTerminalInteractiveShell.prompts = MyPrompt(IPYTHON)
# Breaks PyCharm
# _pydev_bundle.pydev_ipython_console_011.PyDevTerminalInteractiveShell.prompts_class = MyPrompt
_pydev_bundle.pydev_ipython_console_011.PyDevTerminalInteractiveShell.simple_prompt = True
_pydev_bundle.pydev_ipython_console_011.PyDevTerminalInteractiveShell.true_color = True
_pydev_bundle.pydev_ipython_console_011.PyDevTerminalInteractiveShell.warn_venv = False
# </editor-fold>
# <editor-fold desc="PyCharm Patch">
@_define_event
def shell_initialized(ip: IPYTHONType):
"""Fires after initialisation of :class:`~IPython.core.interactiveshell.InteractiveShell`.
This is before extensions and startup scripts are loaded, so it can only be
set by subclassing.
Parameters
----------
ip : :class:`~IPython.core.interactiveshell.InteractiveShell`
The newly initialised shell.
"""
# TODO: meter aquí la config de PyCharm, prompt y quitar IPYTHON - probar como he conseguido ipy e pycharm
print(f"{shell_initialized}, {ip=}")
ip.trio_runner = None
if PYCHARM_CONSOLE:
ip.safe_execfile(str(IPYTHON_PYCHARM_STARTUP_FILE), ip.user_ns, raise_exceptions=True)
def _init_events(self: IPYTHONType):
"""init_events patch."""
self.events = EventManager(self, available_events)
self.events.register("pre_execute", self._clear_warning_registry)
self.events.register("shell_initialized", shell_initialized)
if "IPython.core.interactiveshell" in sys.modules:
IPython.core.interactiveshell.InteractiveShell.init_events = _init_events
# </editor-fold>
if "IPython.core.shellapp" in sys.modules:
# Only works with ipython
IPython.core.shellapp.InteractiveShellApp.extensions = IPYTHON_EXTENSIONS
warnings.filterwarnings("ignore", ".*To exit:.*", UserWarning)
warnings.filterwarnings("ignore", ".*requires you to install the `pickleshare` library.*", UserWarning)