-
-
Notifications
You must be signed in to change notification settings - Fork 124
/
_cli.py
134 lines (100 loc) · 3.98 KB
/
_cli.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
import faulthandler
import inspect
import logging
import sys
from importlib.machinery import SourceFileLoader
from importlib.util import module_from_spec, spec_from_loader
from os import environ, getcwd
from types import ModuleType
from typing import Any, Dict
import click
import IPython # type: ignore
from ape import config
from ape import project as default_project
from ape.cli import NetworkBoundCommand, ape_cli_context, network_option
from ape.utils.misc import _python_version
from ape.version import version as ape_version # type: ignore
CONSOLE_EXTRAS_FILENAME = "ape_console_extras.py"
@click.command(
cls=NetworkBoundCommand,
short_help="Load the console",
context_settings=dict(ignore_unknown_options=True),
)
@network_option()
@ape_cli_context()
def cli(cli_ctx, network):
"""Opens a console for the local project."""
verbose = cli_ctx.logger.level == logging.DEBUG
return console(verbose=verbose)
def import_extras_file(file_path) -> ModuleType:
"""Import a module"""
loader = SourceFileLoader(file_path.name[:-3], str(file_path))
spec = spec_from_loader(loader.name, loader)
assert spec is not None
module = module_from_spec(spec)
loader.exec_module(module)
return module
def load_console_extras(namespace: Dict[str, Any]) -> Dict[str, Any]:
"""load and return namespace updates from ape_console_extras.py files if
they exist"""
global_extras = config.DATA_FOLDER.joinpath(CONSOLE_EXTRAS_FILENAME)
project_extras = config.PROJECT_FOLDER.joinpath(CONSOLE_EXTRAS_FILENAME)
for extras_file in [global_extras, project_extras]:
if not extras_file.is_file():
continue
module = import_extras_file(extras_file)
ape_init_extras = getattr(module, "ape_init_extras", None)
# If found, execute ape_init_extras() function.
if ape_init_extras is not None:
# Figure out the kwargs the func is looking for and assemble
# from the original namespace
func_spec = inspect.getfullargspec(ape_init_extras)
init_kwargs: Dict[str, Any] = {k: namespace.get(k) for k in func_spec.args}
# Execute functionality with existing console namespace as
# kwargs.
extras = ape_init_extras(**init_kwargs)
# If ape_init_extras returned a dict expect it to be new symbols
if isinstance(extras, dict):
namespace.update(extras)
# Add any public symbols from the module into the console namespace
for k in dir(module):
if k != "ape_init_extras" and not k.startswith("_"):
# Prevent override of existing namespace symbols
if k in namespace:
continue
namespace[k] = getattr(module, k)
return namespace
def console(project=None, verbose=None, extra_locals=None):
import ape
if not project:
# Use default project
project = default_project
banner = ""
if verbose:
banner = """
Python: {python_version}
IPython: {ipython_version}
Ape: {ape_version}
Project: {project_path}
Are you ready to Ape, anon?
""".format(
python_version=_python_version,
ipython_version=IPython.__version__,
ape_version=ape_version,
project_path=project.path,
)
if not environ.get("APE_TESTING"):
faulthandler.enable() # NOTE: In case we segfault
namespace = {component: getattr(ape, component) for component in ape.__all__}
namespace["ape"] = ape
if extra_locals:
namespace.update(extra_locals)
sys.path.insert(0, getcwd())
console_extras = load_console_extras(namespace)
if console_extras:
namespace.update(console_extras)
from traitlets.config.loader import Config
config = Config()
if environ.get("APE_TESTING"):
config.HistoryManager.enabled = False
IPython.embed(colors="Neutral", banner1=banner, user_ns=namespace, config=config)