Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[IPython] Source inspection dispatcher for better IDLE compatibility #1222

Merged
merged 34 commits into from
Jun 24, 2020
Merged
Show file tree
Hide file tree
Changes from 28 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
80fd302
[Misc] Use 'dill.source' of 'inspect' to run codes in interactive shell
archibate Jun 11, 2020
6ee33d0
merge master
archibate Jun 11, 2020
4da77a6
[skip ci] Add dill to CI
archibate Jun 11, 2020
d64c2c2
_ShellInspectorWrapper
archibate Jun 12, 2020
3d629bf
Hack IDLE to make it happy
archibate Jun 12, 2020
c495d70
Fix IDLE
archibate Jun 12, 2020
742ca63
[skip ci] cache: we do care user experience!!
archibate Jun 12, 2020
d36c58b
improve stability
archibate Jun 12, 2020
17785f8
[skip ci] fix example exit
archibate Jun 12, 2020
26011ee
[skip ci] Merge branch 'master' into dill
archibate Jun 12, 2020
d01c0ba
[skip ci] fix exit in IPython
archibate Jun 12, 2020
67e5f0a
[skip ci] improve comment
archibate Jun 12, 2020
2f18409
fix exec risk in ti debug (@rexwangcc)
archibate Jun 12, 2020
d28a6b3
[skip ci] Fix ti debug too verbose
archibate Jun 12, 2020
f43b482
Better line
archibate Jun 12, 2020
a74160c
fix test_cli
archibate Jun 12, 2020
69e4eac
[skip ci] idle_hacker.py
archibate Jun 12, 2020
d57797f
fix typo and improve hacker
archibate Jun 13, 2020
5fa29bf
[skip ci] reimprov
archibate Jun 13, 2020
950ca17
[skip ci] Merge branch 'master' into dill
archibate Jun 13, 2020
1726291
[skip ci] Merge branch 'master' into dill
archibate Jun 22, 2020
b62f1c0
improve idle inspector
archibate Jun 22, 2020
280cd7b
[skip ci] add tag [IPython]
archibate Jun 22, 2020
411af8b
[skip ci] Merge branch 'master' into dill
archibate Jun 23, 2020
794bf05
revert idle
archibate Jun 23, 2020
44a194d
[skip ci] revert off-topic cuda debug
archibate Jun 23, 2020
4a5c37a
[skip ci] clean
archibate Jun 23, 2020
bfaeb84
[skip ci] enforce code format
taichi-gardener Jun 23, 2020
343feca
[skip ci] no atexit
archibate Jun 23, 2020
7b759f0
fix
archibate Jun 23, 2020
f12b8d9
[skip ci] Update python/taichi/lang/shell.py
archibate Jun 23, 2020
e4f1900
add dill to jenkins
yuanming-hu Jun 23, 2020
fcbba02
Merge branch 'master' into dill
yuanming-hu Jun 23, 2020
0e685ad
Merge branch 'dill' of github.com:archibate/taichi into dill
archibate Jun 24, 2020
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
11 changes: 0 additions & 11 deletions .tmp_idle_source

This file was deleted.

2 changes: 1 addition & 1 deletion docs/dev_install.rst
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ Installing Dependencies

.. code-block:: bash
python3 -m pip install --user setuptools astpretty astor pybind11 Pillow
python3 -m pip install --user setuptools astpretty astor pybind11 Pillow dill
python3 -m pip install --user pytest pytest-rerunfailures pytest-xdist yapf
python3 -m pip install --user numpy GitPython coverage colorama autograd
Expand Down
4 changes: 2 additions & 2 deletions examples/mpm128.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,9 +105,9 @@ def reset():
gravity[None] = [0, -1]

for frame in range(20000):
while gui.get_event(ti.GUI.PRESS):
if gui.get_event(ti.GUI.PRESS):
if gui.event.key == 'r': reset()
elif gui.event.key in [ti.GUI.ESCAPE, ti.GUI.EXIT]: exit(0)
elif gui.event.key in [ti.GUI.ESCAPE, ti.GUI.EXIT]: break
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IPython seems doesn't like exit... Either use sys.exit or break instead.

if gui.event is not None: gravity[None] = [0, 0] # if had any event
if gui.is_pressed(ti.GUI.LEFT, 'a'): gravity[None][0] = -1
if gui.is_pressed(ti.GUI.RIGHT, 'd'): gravity[None][0] = 1
Expand Down
4 changes: 2 additions & 2 deletions examples/stable_fluid.py
Original file line number Diff line number Diff line change
Expand Up @@ -244,10 +244,10 @@ def main():
md_gen = MouseDataGen()
paused = False
while True:
while gui.get_event(ti.GUI.PRESS):
if gui.get_event(ti.GUI.PRESS):
e = gui.event
if e.key == ti.GUI.ESCAPE:
exit(0)
break
elif e.key == 'r':
paused = False
reset()
Expand Down
1 change: 1 addition & 0 deletions misc/ci_setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,7 @@ def run(self):
"distro",
"autograd",
"astor",
"dill",
"pytest",
"pytest-xdist",
"pytest-rerunfailures",
Expand Down
1 change: 1 addition & 0 deletions misc/prtags.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,6 @@
"mac" : "Mac OS X",
"windows" : "Windows",
"perf" : "Performance improvements",
"ipython" : "IPython and other shells",
"release" : "Release"
}
5 changes: 3 additions & 2 deletions python/taichi/lang/ast_checker.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import ast
from .shell import oinspect


class KernelSimplicityASTChecker(ast.NodeVisitor):
Expand Down Expand Up @@ -32,8 +33,8 @@ def __exit__(self, exc_type, exc_val, exc_tb):
def __init__(self, func):
super().__init__()
import inspect
self._func_file = inspect.getsourcefile(func)
self._func_lineno = inspect.getsourcelines(func)[1]
self._func_file = oinspect.getsourcefile(func)
self._func_lineno = oinspect.getsourcelines(func)[1]
self._func_name = func.__name__
self._scope_guards = []

Expand Down
13 changes: 7 additions & 6 deletions python/taichi/lang/kernel.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import ast
from .kernel_arguments import *
from .util import *
from .shell import oinspect
import functools


Expand Down Expand Up @@ -55,7 +56,7 @@ def __call__(self, *args):

def do_compile(self):
from .impl import get_runtime
src = remove_indent(inspect.getsource(self.func))
src = remove_indent(oinspect.getsource(self.func))
tree = ast.parse(src)

func_body = tree.body[0]
Expand All @@ -75,7 +76,7 @@ def do_compile(self):
print('After preprocessing:')
print(astor.to_source(tree.body[0], indent_with=' '))

ast.increment_lineno(tree, inspect.getsourcelines(self.func)[1] - 1)
ast.increment_lineno(tree, oinspect.getsourcelines(self.func)[1] - 1)

local_vars = {}
#frame = inspect.currentframe().f_back
Expand All @@ -84,7 +85,7 @@ def do_compile(self):
global_vars = copy.copy(self.func.__globals__)
exec(
compile(tree,
filename=inspect.getsourcefile(self.func),
filename=oinspect.getsourcefile(self.func),
mode='exec'), global_vars, local_vars)
self.compiled = local_vars[self.func.__name__]

Expand Down Expand Up @@ -260,7 +261,7 @@ def materialize(self, key=None, args=None, arg_features=None):
import taichi as ti
ti.trace("Compiling kernel {}...".format(kernel_name))

src = remove_indent(inspect.getsource(self.func))
src = remove_indent(oinspect.getsource(self.func))
tree = ast.parse(src)
if self.runtime.print_preprocessed:
import astor
Expand Down Expand Up @@ -300,7 +301,7 @@ def materialize(self, key=None, args=None, arg_features=None):
print('After preprocessing:')
print(astor.to_source(tree.body[0], indent_with=' '))

ast.increment_lineno(tree, inspect.getsourcelines(self.func)[1] - 1)
ast.increment_lineno(tree, oinspect.getsourcelines(self.func)[1] - 1)

freevar_names = self.func.__code__.co_freevars
closure = self.func.__closure__
Expand All @@ -316,7 +317,7 @@ def materialize(self, key=None, args=None, arg_features=None):

exec(
compile(tree,
filename=inspect.getsourcefile(self.func),
filename=oinspect.getsourcefile(self.func),
mode='exec'), global_vars, local_vars)
compiled = local_vars[self.func.__name__]

Expand Down
98 changes: 98 additions & 0 deletions python/taichi/lang/shell.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import sys, os, atexit


class ShellType:
NATIVE = 'Python shell'
IPYTHON = 'IPython TerminalInteractiveShell'
JUPYTER = 'IPython ZMQInteractiveShell'
IPYBASED = 'IPython Based Shell'
SCRIPT = None


def get_shell_name():
"""
Detect which type of shell is using.
Can be IPython, IDLE, Python native, or none.
"""
shell = os.environ.get('TI_SHELL_TYPE')
if shell is not None:
return getattr(ShellType, shell.upper())
Comment on lines +17 to +19
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If they encounter any problem detecting shell type, they can still use a env var to force which to wrap.


try:
import __main__ as main
if hasattr(main, '__file__'): # Called from a script?
return ShellType.SCRIPT
except:
pass

# Let's detect which type of interactive shell is being used.
# As you can see, huge engineering efforts are done here just to
# make IDLE and IPython happy, wish our user really love them :)
archibate marked this conversation as resolved.
Show resolved Hide resolved

try: # IPython / Jupyter?
return 'IPython ' + get_ipython().__class__.__name__
except:
# Note that we can't simply do `'IPython' in sys.modules`,
# since it seems `torch` will import IPython on it's own too..
if hasattr(__builtins__, '__IPYTHON__'):
return ShellType.IPYBASED

try:
if getattr(sys, 'ps1', sys.flags.interactive):
return ShellType.NATIVE
except:
pass

return ShellType.SCRIPT


class ShellInspectorWrapper:
"""
Wrapper of the `inspect` module. When interactive shell detected,
we will redirect getsource() calls to the corresponding inspector
provided by / suitable for each type of shell.
"""
def __init__(self):
self.name = get_shell_name()

if self.name is not None:
print('[Taichi] Interactive shell detected:', self.name)

if self.name is None:
# `inspect` for "Python script"
import inspect
self.getsource = inspect.getsource
self.getsourcelines = inspect.getsourcelines
self.getsourcefile = inspect.getsourcefile

elif self.name == ShellType.NATIVE:
# `dill.source` for "Python native shell"
import dill
self.getsource = dill.source.getsource
self.getsourcelines = dill.source.getsourcelines
self.getsourcefile = dill.source.getsourcefile

elif self.name.startswith('IPython'):
# `IPython.core.oinspect` for "IPython advanced shell"
def getsource(o):
import IPython
return IPython.core.oinspect.getsource(o)

def getsourcelines(o):
import IPython
lineno = IPython.core.oinspect.find_source_lines(o)
lines = IPython.core.oinspect.getsource(o).split('\n')
return lines, lineno

def getsourcefile(o):
return '<IPython>'

self.getsource = getsource
self.getsourcelines = getsourcelines
self.getsourcefile = getsourcefile

else:
raise RuntimeError(f'Shell type "{self.name}" not supported')


oinspect = None
14 changes: 7 additions & 7 deletions python/taichi/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,18 +69,18 @@ def __init__(self, debug: bool = False, test_mode: bool = False):
parser.add_argument('command',
help="command from the above list to run")

# Print help if no command provided
if len(sys.argv[1:2]) == 0:
parser.print_help()
exit(1)

# Flag for unit testing
self.test_mode = test_mode

self.main_parser = parser

@timer
def __call__(self):
# Print help if no command provided
if len(sys.argv[1:2]) == 0:
self.main_parser.print_help()
return 1

# Parse the command
args = self.main_parser.parse_args(sys.argv[1:2])

Expand All @@ -90,7 +90,7 @@ def __call__(self):
TaichiMain._exec_python_file(args.command)
print(f"{args.command} is not a valid command!")
self.main_parser.print_help()
exit(1)
return 1

return getattr(self, args.command)(sys.argv[2:])

Expand Down Expand Up @@ -954,4 +954,4 @@ def main_debug():


if __name__ == "__main__":
exit(main())
sys.exit(main())
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
'colorama',
'setuptools',
'astor',
'dill',
# For testing:
'pytest',
'pytest-xdist',
Expand Down
14 changes: 4 additions & 10 deletions tests/python/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,20 +22,14 @@ def patch_sys_argv_helper(custom_argv: list):

def test_cli_exit_one_with_no_command_provided():
with patch_sys_argv_helper(["ti"]):
with pytest.raises(SystemExit) as pytest_wrapped_err:
cli = TaichiMain(test_mode=True)
cli()
assert pytest_wrapped_err.type == SystemExit
assert pytest_wrapped_err.value.code == 1
cli = TaichiMain(test_mode=True)
assert cli() == 1


def test_cli_exit_one_with_bogus_command_provided():
with patch_sys_argv_helper(["ti", "bogus-command-not-registered-yet"]):
with pytest.raises(SystemExit) as pytest_wrapped_err:
cli = TaichiMain(test_mode=True)
cli()
assert pytest_wrapped_err.type == SystemExit
assert pytest_wrapped_err.value.code == 1
cli = TaichiMain(test_mode=True)
assert cli() == 1


def test_cli_can_dispatch_commands_to_methods_correctly():
Expand Down