コード例 #1
0
def save_and_run(filename, cell):
    cell = cell.strip()
    with open(filename, 'w') as f:
        f.write(cell)

    shell = InteractiveShell()
    shell.run_cell(cell)
コード例 #2
0
ファイル: evaluate.py プロジェクト: bwarne/pychart
def process(conn, stdout):
    shell = InteractiveShell()

    orig, sys.stdout = sys.stdout, stdout
    execRes = shell.run_cell(conn.recv())
    sys.stdout = orig

    res = (execRes.result, execRes.error_before_exec or execRes.error_in_exec)
    conn.send(res)
コード例 #3
0
ファイル: uwsgiapp.py プロジェクト: Abramovuch/sagecell
def hello_world():
    from IPython.core.interactiveshell import InteractiveShell

    # I replace \r\n with \n...this might cause problems for code that has legitimate \r characters in it
    # (like in a string)
    code = request.values.get('c','').replace('\r\n','\n')
    if len(code)>0:
        s="Code<br/><pre>%r</pre><hr/>"%code
        try:
            a=InteractiveShell()
            with capture() as out:
                a.run_cell(code)
#            c=compile(code,'<string>','exec')
#            with capture() as out:
#                exec c
            s+="Standard out<br/><pre>%s</pre><hr/>Standard Error<br/><pre>%s</pre>"%tuple(out)
        except Exception as e:
            s+="Error: %s"%e
        return s
    return "<form><textarea name='c' cols='100' rows='20'></textarea><br/><input type='submit'></form>"
コード例 #4
0
ファイル: widget.py プロジェクト: tonyfast/literacy
def _run_cell(self,
              raw_cell,
              store_history=False,
              silent=False,
              shell_futures=True):
    from ipywidgets import Widget
    yield InteractiveShell.run_cell(self,
                                    raw_cell,
                                    store_history=False,
                                    silent=False,
                                    shell_futures=True)
    for value in Widget.widgets.values():
        if isinstance(value, Template): value._change_render()
コード例 #5
0
ファイル: uwsgiapp.py プロジェクト: vbraun/sagecell
def hello_world():
    from IPython.core.interactiveshell import InteractiveShell

    # I replace \r\n with \n...this might cause problems for code that has legitimate \r characters in it
    # (like in a string)
    code = request.values.get('c', '').replace('\r\n', '\n')
    if len(code) > 0:
        s = "Code<br/><pre>%r</pre><hr/>" % code
        try:
            a = InteractiveShell()
            with capture() as out:
                a.run_cell(code)


#            c=compile(code,'<string>','exec')
#            with capture() as out:
#                exec c
            s += "Standard out<br/><pre>%s</pre><hr/>Standard Error<br/><pre>%s</pre>" % tuple(
                out)
        except Exception as e:
            s += "Error: %s" % e
        return s
    return "<form><textarea name='c' cols='100' rows='20'></textarea><br/><input type='submit'></form>"
コード例 #6
0
def execute_cell(raw_cell, current_ns):
    """
    Perform the execution of the async cell
    """
    # Create a new InteractiveShell
    shell = InteractiveShell()
    # Disable Debugger
    shell.call_pdb = False
    shell.pdb = False

    # Process and Inject in the Namespace imported modules
    module_names = current_ns.pop('import_modules')
    modules = {}
    if module_names:
        for alias, mname in module_names:
            module = import_module(mname)
            modules[alias] = module
    shell.user_ns.update(current_ns)
    if modules:
        shell.user_ns.update(modules)

    output = ''
    with capture_output() as io:
        _ = shell.run_cell(raw_cell, silent=True, shell_futures=False)

    # Update Namespace
    updated_namespace = dict()
    updated_namespace.setdefault('import_modules', list())
    for k, v in shell.user_ns.items():
        try:
            if inspect_ismodule(v):
                updated_namespace['import_modules'].append((k, v.__name__))
            else:
                _ = pickle.dumps({k: v})
                updated_namespace[k] = v
        except TypeError:
            continue
        except pickle.PicklingError:
            continue
        except AttributeError:
            continue

    # if not output:
    output += io.stdout
    return output, updated_namespace
コード例 #7
0
    def update(self):
        """
        Evaluate the script to update the chart model with latest data sources.
        Changes to the chart model will trigger an update to the chart editor.
        """
        self.updateStarted.emit()
        shell = InteractiveShell()
        script = self.document.scriptEditorModel.getScript()
        execRes = shell.run_cell(script)

        # error with syntax or execution
        if execRes.error_before_exec or execRes.error_in_exec:
            self.updateErrored.emit()
            return

        # update document which triggers a chart update
        self.document.chartEditorModel.setChartDataSources(execRes.result)
        self.updateFinished.emit()
コード例 #8
0
ファイル: interpreter.py プロジェクト: BrenBarn/pyzo
class PyzoInterpreter:
    """ PyzoInterpreter
    
    The pyzo interpreter is the part that makes the pyzo kernel interactive.
    It executes code, integrates the GUI toolkit, parses magic commands, etc.
    The pyzo interpreter has been designed to emulate the standard interactive
    Python console as much as possible, but with a lot of extra goodies.
    
    There is one instance of this class, stored at sys._pyzoInterpreter and
    at the __pyzo__ variable in the global namespace.
    
    The global instance has a couple of interesting attributes:
      * context: the yoton Context instance at the kernel (has all channels)
      * introspector: the introspector instance (a subclassed yoton.RepChannel)
      * magician: the object that handles the magic commands
      * guiApp: a wrapper for the integrated GUI application
      * sleeptime: the amount of time (in seconds) to sleep at each iteration
    
    """
    
    # Simular working as code.InteractiveConsole. Some code was copied, but
    # the following things are changed:
    # - prompts are printed in the err stream, like the default interpreter does
    # - uses an asynchronous read using the yoton interface
    # - support for hijacking GUI toolkits
    # - can run large pieces of code
    # - support post mortem debugging
    # - support for magic commands
    
    def __init__(self, locals, filename="<console>"):
        
        # Init variables for locals and globals (globals only for debugging)
        self.locals = locals
        self.globals = None
        
        # Store filename
        self._filename = filename
        
        # Store ref of locals that is our main
        self._main_locals = locals
        
        # Flag to ignore sys exit, to allow running some scripts
        # interactively, even if they call sys.exit.
        self.ignore_sys_exit = False
        
        # Information for debugging. If self._dbFrames, we're in debug mode
        # _dbFrameIndex starts from 1 
        self._dbFrames = []
        self._dbFrameIndex = 0
        self._dbFrameName = ''
        
        # Init datase to store source code that we execute
        self._codeCollection = ExecutedSourceCollection()
        
        # Init buffer to deal with multi-line command in the shell
        self._buffer = []
        
        # Init sleep time. 0.001 result in 0% CPU usage at my laptop (Windows),
        # but 8% CPU usage at my older laptop (on Linux).
        self.sleeptime = 0.01 # 100 Hz
        
        # Create compiler
        if sys.platform.startswith('java'):
            import compiler
            self._compile = compiler.compile  # or 'exec' does not work
        else:
            self._compile = CommandCompiler()
        
        # Instantiate magician and tracer
        self.magician = Magician()
        self.debugger = Debugger()
        
        # To keep track of whether to send a new prompt, and whether more
        # code is expected.
        self.more = 0
        self.newPrompt = True
        
        # Code and script to run on first iteration
        self._codeToRunOnStartup = None
        self._scriptToRunOnStartup = None
        
        # Remove "THIS" directory from the PYTHONPATH
        # to prevent unwanted imports. Same for pyzokernel dir
        thisPath = os.getcwd()
        for p in [thisPath, os.path.join(thisPath,'pyzokernel')]:
            while p in sys.path:
                sys.path.remove(p)
    
    
    def run(self):    
        """ Run (start the mainloop)
        
        Here we enter the main loop, which is provided by the guiApp. 
        This event loop calls process_commands on a regular basis. 
        
        We may also enter the debug intereaction loop, either from a
        request for post-mortem debugging, or *during* execution by
        means of a breakpoint. When in this debug-loop, the guiApp event
        loop lays still, but the debug-loop does call process-commands
        for user interaction. 
        
        When the user wants to quit, SystemExit is raised (one way or
        another). This is detected in process_commands and the exception
        instance is stored in self._exitException. Then the debug-loop
        is stopped if necessary, and the guiApp is told to stop its event
        loop.
        
        And that brings us back here, where we exit using in order of
        preference: self._exitException, the exception with which the
        event loop was exited (if any), or a new exception.
        
        """
        
        # Prepare
        self._prepare()
        self._exitException = None
        
        # Enter main
        try:
            self.guiApp.run(self.process_commands, self.sleeptime) 
        except SystemExit:
            # Set self._exitException if it is not set yet
            type, value, tb = sys.exc_info();  del tb
            if self._exitException is None:
                self._exitException = value
        
        # Exit
        if self._exitException is None:
            self._exitException = SystemExit()
        raise self._exitException
    
    
    def _prepare(self):
        """ Prepare for running the main loop.
        Here we do some initialization like obtaining the startup info,
        creating the GUI application wrapper, etc.
        """
        
        # Reset debug status
        self.debugger.writestatus()
        
        # Get startup info (get a copy, or setting the new version wont trigger!)
        while self.context._stat_startup.recv() is None:
            time.sleep(0.02)
        self.startup_info = startup_info = self.context._stat_startup.recv().copy()
        
        # Set startup info (with additional info)
        if sys.platform.startswith('java'):
            import __builtin__ as builtins  # Jython
        else:
            builtins = __builtins__
        if not isinstance(builtins, dict):
            builtins = builtins.__dict__
        startup_info['builtins'] = [builtin for builtin in builtins.keys()]
        startup_info['version'] = tuple(sys.version_info)
        startup_info['keywords'] = keyword.kwlist
        self.context._stat_startup.send(startup_info)
        
        # Prepare the Python environment
        self._prepare_environment(startup_info)
        
        # Run startup code (before loading GUI toolkit or IPython
        self._run_startup_code(startup_info)
        
        # Write Python banner (to stdout)
        thename = 'Python'
        if sys.version_info[0] == 2:
            thename = 'Legacy Python'
        if '__pypy__' in sys.builtin_module_names:
            thename = 'Pypy'
        if sys.platform.startswith('java'):
            thename = 'Jython'
            # Jython cannot do struct.calcsize("P")
            import java.lang
            real_plat = java.lang.System.getProperty("os.name").lower()
            plat = '%s/%s' % (sys.platform, real_plat)
        elif sys.platform.startswith('win'):
            NBITS = 8 * struct.calcsize("P")
            plat = 'Windows (%i bits)' % NBITS
        else:
            NBITS = 8 * struct.calcsize("P")
            plat = '%s (%i bits)' % (sys.platform, NBITS) 
        printDirect("%s %s on %s.\n" %
                    (thename, sys.version.split('[')[0].rstrip(), plat))
        
        # Integrate GUI
        guiName, guiError = self._integrate_gui(startup_info)
        
        # Write pyzo part of banner (including what GUI loop is integrated)
        if True:
            pyzoBanner = 'This is the Pyzo interpreter'
        if guiError:
            pyzoBanner += '. ' + guiError + '\n'
        elif guiName:
            pyzoBanner += ' with integrated event loop for ' 
            pyzoBanner += guiName + '.\n'
        else:
            pyzoBanner += '.\n'
        printDirect(pyzoBanner)
        
        # Try loading IPython
        if startup_info.get('ipython', '').lower() in ('', 'no', 'false'):
            self._ipython = None
        else:
            try:
                self._load_ipyhon()
            except Exception:
                type, value, tb = sys.exc_info();
                del tb
                printDirect('IPython could not be loaded: %s\n' % str(value))
                self._ipython = None
        
        # Set prompts
        sys.ps1 = PS1(self)
        sys.ps2 = PS2(self)
        
        # Notify about project path
        projectPath = startup_info['projectPath']
        if projectPath:
            printDirect('Prepending the project path %r to sys.path\n' % 
                projectPath)
        
        # Write tips message.
        if self._ipython:
            import IPython
            printDirect("\nUsing IPython %s -- An enhanced Interactive Python.\n"
                        %  IPython.__version__)
            printDirect(
                "?         -> Introduction and overview of IPython's features.\n"
                "%quickref -> Quick reference.\n"
                "help      -> Python's own help system.\n"
                "object?   -> Details about 'object', "
                "use 'object??' for extra details.\n")
        else:
            printDirect("Type 'help' for help, " + 
                        "type '?' for a list of *magic* commands.\n")
        
        # Notify the running of the script
        if self._scriptToRunOnStartup:
            printDirect('\x1b[0;33mRunning script: "'+self._scriptToRunOnStartup+'"\x1b[0m\n')
        
        # Prevent app nap on OSX 9.2 and up
        # The _nope module is taken from MINRK's appnope package
        if sys.platform == "darwin" and LV(platform.mac_ver()[0]) >= LV("10.9"):
            from pyzokernel import _nope
            _nope.nope()
        
        # Setup post-mortem debugging via appropriately logged exceptions
        class PMHandler(logging.Handler):
            def emit(self, record):
                if record.exc_info:
                    sys.last_type, sys.last_value, sys.last_traceback = record.exc_info
                return record
        #
        root_logger = logging.getLogger()
        if not root_logger.handlers:
            root_logger.addHandler(logging.StreamHandler())
        root_logger.addHandler(PMHandler())
    
    
    def _prepare_environment(self, startup_info):
        """ Prepare the Python environment. There are two possibilities:
        either we run a script or we run interactively.
        """
        
        # Get whether we should (and can) run as script
        scriptFilename = startup_info['scriptFile']
        if scriptFilename:
            if not os.path.isfile(scriptFilename):
                printDirect('Invalid script file: "'+scriptFilename+'"\n')
                scriptFilename = None
        
        # Get project path
        projectPath = startup_info['projectPath']
        
        if scriptFilename.endswith('.ipynb'):
            # Run Jupyter notebook
            import notebook.notebookapp
            sys.argv = ['jupyter_notebook', scriptFilename]
            sys.exit(notebook.notebookapp.main())
        
        elif scriptFilename:
            # RUN AS SCRIPT
            # Set __file__  (note that __name__ is already '__main__')
            self.locals['__file__'] = scriptFilename
            # Set command line arguments
            sys.argv[:] = []
            sys.argv.append(scriptFilename)
            sys.argv.extend(shlex.split(startup_info.get('argv', '')))
            # Insert script directory to path
            theDir = os.path.abspath( os.path.dirname(scriptFilename) )
            if theDir not in sys.path:
                sys.path.insert(0, theDir)
            if projectPath is not None:
                sys.path.insert(0,projectPath)
            # Go to script dir
            os.chdir( os.path.dirname(scriptFilename) )
            # Run script later
            self._scriptToRunOnStartup = scriptFilename
        else:
            # RUN INTERACTIVELY
            # No __file__ (note that __name__ is already '__main__')
            self.locals.pop('__file__','')
            # Remove all command line arguments, set first to empty string
            sys.argv[:] = []
            sys.argv.append('')
            sys.argv.extend(shlex.split(startup_info.get('argv', '')))
            # Insert current directory to path
            sys.path.insert(0, '')
            if projectPath:
                sys.path.insert(0,projectPath)
            # Go to start dir
            startDir = startup_info['startDir']
            if startDir and os.path.isdir(startDir):
                os.chdir(startDir)
            else:
                os.chdir(os.path.expanduser('~')) # home dir 
    
    
    def _run_startup_code(self, startup_info):
        """ Execute the startup code or script.
        """
        
        # Run startup script (if set)
        script = startup_info['startupScript']
        # Should we use the default startupScript?
        if script == '$PYTHONSTARTUP':
            script = os.environ.get('PYTHONSTARTUP','')
        
        if '\n' in script:
            # Run code later or now
            firstline = script.split('\n')[0].replace(' ', '')
            if firstline.startswith('#AFTER_GUI'):
                self._codeToRunOnStartup = script
            else:
                self.context._stat_interpreter.send('Busy') 
                msg = {'source': script, 'fname': '<startup>', 'lineno': 0}
                self.runlargecode(msg, True)
        elif script and os.path.isfile(script):
            # Run script
            self.context._stat_interpreter.send('Busy') 
            self.runfile(script)
        else:
            # Nothing to run
            pass
    
    
    def _integrate_gui(self, startup_info):
        """ Integrate event loop of GUI toolkit (or use pure Python
        event loop).
        """
        
        self.guiApp = guiintegration.App_base()
        self.guiName = guiName = startup_info['gui'].upper()
        guiError = ''
        try:
            if guiName in ['', 'NONE']:
                guiName = ''
            elif guiName == 'AUTO':
                for tryName, tryApp in [('PYQT4', guiintegration.App_pyqt4),
                                        ('PYSIDE', guiintegration.App_pyside),
                                        #('WX', guiintegration.App_wx),
                                        ('TK', guiintegration.App_tk),
                                        ]:
                    try:
                        self.guiApp = tryApp()
                    except Exception:
                        continue
                    guiName = tryName
                    break
                else:
                    guiName = ''
            elif guiName == 'TK':
                self.guiApp = guiintegration.App_tk()
            elif guiName == 'WX':
                self.guiApp = guiintegration.App_wx()
            elif guiName == 'TORNADO':
                self.guiApp = guiintegration.App_tornado()
            elif guiName == 'PYSIDE':
                self.guiApp = guiintegration.App_pyside()
            elif guiName in ['PYQT4', 'QT4']:
                self.guiApp = guiintegration.App_pyqt4()
            elif guiName == 'FLTK':
                self.guiApp = guiintegration.App_fltk()
            elif guiName == 'GTK':
                self.guiApp = guiintegration.App_gtk()
            else:
                guiError = 'Unkown gui: %s' % guiName
                guiName = ''
        except Exception: # Catch any error
            # Get exception info (we do it using sys.exc_info() because
            # we cannot catch the exception in a version independent way.
            type, value, tb = sys.exc_info();  del tb
            guiError = 'Failed to integrate event loop for %s: %s' % (
                guiName, str(value))
        
        return guiName, guiError
    
    
    def _load_ipyhon(self):
        """ Try loading IPython shell. The result is set in self._ipython
        (can be None if IPython not available).
        """
        
        # Init
        self._ipython = None
        import __main__
        
        # Try importing IPython
        try:
            import IPython
        except ImportError:
            return
        
        # Version ok?
        if IPython.version_info < (1,):
            return
        
        # Create an IPython shell
        from IPython.core.interactiveshell import InteractiveShell 
        self._ipython = InteractiveShell(user_module=__main__)
        
        # Set some hooks / event callbacks
        # Run hook (pre_run_code_hook is depreacted in 2.0)
        pre_run_cell_hook  = self.ipython_pre_run_cell_hook
        if IPython.version_info < (2,):
            self._ipython.set_hook('pre_run_code_hook', pre_run_cell_hook)
        else:
            self._ipython.events.register('pre_run_cell', pre_run_cell_hook)
        # Other hooks
        self._ipython.set_hook('editor', self.ipython_editor_hook)
        self._ipython.set_custom_exc((bdb.BdbQuit,), self.dbstop_handler)
        
        # Some patching
        self._ipython.ask_exit = self.ipython_ask_exit
        
        # Make output be shown on Windows
        if sys.platform.startswith('win'):
            # IPython wraps std streams just like we do below, but
            # pyreadline adds *another* wrapper, which is where it
            # goes wrong. Here we set it back to bypass pyreadline.
            from IPython.utils import io
            io.stdin = io.IOStream(sys.stdin)
            io.stdout = io.IOStream(sys.stdout)
            io.stderr = io.IOStream(sys.stderr)
            
            # Ipython uses msvcrt e.g. for pausing between pages
            # but this does not work in pyzo
            import msvcrt
            msvcrt.getwch = msvcrt.getch = input  # input is deffed above
    
    
    def process_commands(self):
        """ Do one iteration of processing commands (the REPL).
        """
        try:
            
            self._process_commands()
            
        except SystemExit:
            # It may be that we should ignore sys exit now...
            if self.ignore_sys_exit:
                self.ignore_sys_exit = False  # Never allow more than once
                return
            # Get and store the exception so we can raise it later
            type, value, tb = sys.exc_info();  del tb
            self._exitException = value
            # Stop debugger if it is running
            self.debugger.stopinteraction()
            # Exit from interpreter. Exit in the appropriate way
            self.guiApp.quit()  # Is sys.exit() by default
    
    
    def _process_commands(self):
        
        # Run startup code/script inside the loop (only the first time)
        # so that keyboard interrupt will work
        if self._codeToRunOnStartup:
            self.context._stat_interpreter.send('Busy')
            self._codeToRunOnStartup, tmp = None, self._codeToRunOnStartup
            self.pushline(tmp)
        if self._scriptToRunOnStartup:
            self.context._stat_interpreter.send('Busy') 
            self._scriptToRunOnStartup, tmp = None, self._scriptToRunOnStartup
            self.runfile(tmp)
        
        # Flush real stdout / stderr
        sys.__stdout__.flush()
        sys.__stderr__.flush()
        
        # Set status and prompt?
        # Prompt is allowed to be an object with __str__ method
        if self.newPrompt:
            self.newPrompt = False
            ps = [sys.ps1, sys.ps2][bool(self.more)]
            self.context._strm_prompt.send(str(ps))
        
        if True:
            # Determine state. The message is really only send
            # when the state is different. Note that the kernelbroker
            # can also set the state ("Very busy", "Busy", "Dead")
            if self._dbFrames:
                self.context._stat_interpreter.send('Debug')
            elif self.more:
                self.context._stat_interpreter.send('More')
            else:
                self.context._stat_interpreter.send('Ready')
        
        
        # Are we still connected?
        if sys.stdin.closed or not self.context.connection_count:
            # Exit from main loop.
            # This will raise SystemExit and will shut us down in the 
            # most appropriate way
            sys.exit()
        
        # Get channel to take a message from
        ch = yoton.select_sub_channel(self.context._ctrl_command, self.context._ctrl_code)
        
        if ch is None:
            pass # No messages waiting
        
        elif ch is self.context._ctrl_command:
            # Read command 
            line1 = self.context._ctrl_command.recv(False) # Command
            if line1:
                # Notify what we're doing
                self.context._strm_echo.send(line1)
                self.context._stat_interpreter.send('Busy')
                self.newPrompt = True
                # Convert command 
                # (only a few magics are supported if IPython is active)
                line2 = self.magician.convert_command(line1.rstrip('\n'))
                # Execute actual code
                if line2 is not None:
                    for line3 in line2.split('\n'):  # not splitlines!
                        self.more = self.pushline(line3)
                else:
                    self.more = False
                    self._resetbuffer()
        
        elif ch is self.context._ctrl_code:
            # Read larger block of code (dict)
            msg = self.context._ctrl_code.recv(False)
            if msg:
                # Notify what we're doing
                # (runlargecode() sends on stdin-echo)
                self.context._stat_interpreter.send('Busy')
                self.newPrompt = True
                # Execute code
                self.runlargecode(msg)
                # Reset more stuff
                self._resetbuffer()
                self.more = False
        
        else:
            # This should not happen, but if it does, just flush!
            ch.recv(False)


    
    ## Running code in various ways
    # In all cases there is a call for compilecode and a call to execcode
    
    def _resetbuffer(self):
        """Reset the input buffer."""
        self._buffer = []
    
    
    def pushline(self, line):
        """Push a line to the interpreter.
        
        The line should not have a trailing newline; it may have
        internal newlines.  The line is appended to a buffer and the
        interpreter's _runlines() method is called with the
        concatenated contents of the buffer as source.  If this
        indicates that the command was executed or invalid, the buffer
        is reset; otherwise, the command is incomplete, and the buffer
        is left as it was after the line was appended.  The return
        value is 1 if more input is required, 0 if the line was dealt
        with in some way (this is the same as _runlines()).
        
        """
        # Get buffer, join to get source
        buffer = self._buffer
        buffer.append(line)
        source = "\n".join(buffer)
        # Clear buffer and run source
        self._resetbuffer()
        more = self._runlines(source, self._filename)
        # Create buffer if needed
        if more:
            self._buffer = buffer 
        return more
    

    def _runlines(self, source, filename="<input>", symbol="single"):
        """Compile and run some source in the interpreter.
        
        Arguments are as for compile_command().
        
        One several things can happen:
        
        1) The input is incorrect; compile_command() raised an
        exception (SyntaxError or OverflowError).  A syntax traceback
        will be printed by calling the showsyntaxerror() method.
        
        2) The input is incomplete, and more input is required;
        compile_command() returned None.  Nothing happens.
        
        3) The input is complete; compile_command() returned a code
        object.  The code is executed by calling self.execcode() (which
        also handles run-time exceptions, except for SystemExit).
        
        The return value is True in case 2, False in the other cases (unless
        an exception is raised).  The return value can be used to
        decide whether to use sys.ps1 or sys.ps2 to prompt the next
        line.
        
        """
        
        use_ipython = self._ipython and not self._dbFrames
        
        # Try compiling.
        # The IPython kernel does not handle incomple lines, so we check
        # that ourselves ...
        error = None
        try:
            code = self.compilecode(source, filename, symbol)
        except (OverflowError, SyntaxError, ValueError):
            error = sys.exc_info()[1]
            code = False
        
        if use_ipython:
            if code is None:
                # Case 2
                #self._ipython.run_cell('', True)
                return True
            else:
                # Case 1 and 3 handled by IPython
                self._ipython.run_cell(source, True, False)
                return False
                
        else:
            if code is None:
                # Case 2
                return True
            elif not code:
                # Case 1, a bit awkward way to show the error, but we need
                # to call showsyntaxerror in an exception handler.
                try:
                    raise error
                except Exception:
                    self.showsyntaxerror(filename)
                return False
            else:
                # Case 3
                self.execcode(code)
                return False
    
    
    def runlargecode(self, msg, silent=False):
        """ To execute larger pieces of code. """
        
        # Get information
        source, fname, lineno = msg['source'], msg['fname'], msg['lineno']
        cellName = msg.get('cellName', '')
        source += '\n'
        
        # Construct notification message
        lineno1 = lineno + 1
        lineno2 = lineno + source.count('\n')
        fname_show = fname
        if not fname.startswith('<'):
            fname_show = os.path.split(fname)[1]
        if cellName:
            runtext = '(executing cell "%s" (line %i of "%s"))\n' % (cellName, lineno1, fname_show)
        elif lineno1 == lineno2:
            runtext = '(executing line %i of "%s")\n' % (lineno1, fname_show)
        else:
            runtext = '(executing lines %i to %i of "%s")\n' % (
                                                lineno1, lineno2, fname_show)
        # Notify IDE
        if not silent:
            self.context._strm_echo.send('\x1b[0;33m%s\x1b[0m' % runtext)
            # Increase counter
            if self._ipython:
                self._ipython.execution_count += 1
        
        # Put the line number in the filename (if necessary)
        # Note that we could store the line offset in the _codeCollection,
        # but then we cannot retrieve it for syntax errors.
        if lineno:
            fname = "%s+%i" % (fname, lineno)
        
        # Try compiling the source
        code = None
        try:            
            # Compile
            code = self.compilecode(source, fname, "exec")          
            
        except (OverflowError, SyntaxError, ValueError):
            self.showsyntaxerror(fname)
            return
        
        if code:
            # Store the source using the (id of the) code object as a key
            self._codeCollection.store_source(code, source)
            # Execute the code
            self.execcode(code)
        else:
            # Incomplete code
            self.write('Could not run code because it is incomplete.\n')
    
    
    def runfile(self, fname):
        """  To execute the startup script. """ 
        
        # Get text (make sure it ends with a newline)
        try:
            source = open(fname, 'rb').read().decode('UTF-8')
        except Exception:
            printDirect('Could not read script (decoding using UTF-8): "' + fname + '"\n')
            return
        try:
            source = source.replace('\r\n', '\n').replace('\r','\n')
            if source[-1] != '\n':
                source += '\n'
        except Exception:        
            printDirect('Could not execute script: "' + fname + '"\n')
            return
        
        # Try compiling the source
        code = None
        try:            
            # Compile
            code = self.compilecode(source, fname, "exec")
        except (OverflowError, SyntaxError, ValueError):
            time.sleep(0.2) # Give stdout time to be send
            self.showsyntaxerror(fname)
            return
        
        if code:
            # Store the source using the (id of the) code object as a key
            self._codeCollection.store_source(code, source)
            # Execute the code
            self.execcode(code)
        else:
            # Incomplete code
            self.write('Could not run code because it is incomplete.\n')
    
    
    def compilecode(self, source, filename, mode, *args, **kwargs):
        """ Compile source code.
        Will mangle coding definitions on first two lines. 
        
        * This method should be called with Unicode sources.
        * Source newlines should consist only of LF characters.
        """
        
        # This method solves pyzo issue 22

        # Split in first two lines and the rest
        parts = source.split('\n', 2)
        
        # Replace any coding definitions
        ci = 'coding is'
        contained_coding = False
        for i in range(len(parts)-1):
            tmp = parts[i]
            if tmp and tmp[0] == '#' and 'coding' in tmp:
                contained_coding = True
                parts[i] = tmp.replace('coding=', ci).replace('coding:', ci)
        
        # Combine parts again (if necessary)
        if contained_coding:
            source = '\n'.join(parts)
        
        # Convert filename to UTF-8 if Python version < 3
        if PYTHON_VERSION < 3:
            filename = filename.encode('utf-8')
        
        # Compile
        return self._compile(source, filename, mode, *args, **kwargs)
    
    
    def execcode(self, code):
        """Execute a code object.
        
        When an exception occurs, self.showtraceback() is called to
        display a traceback.  All exceptions are caught except
        SystemExit, which is reraised.
        
        A note about KeyboardInterrupt: this exception may occur
        elsewhere in this code, and may not always be caught.  The
        caller should be prepared to deal with it.
        
        The globals variable is used when in debug mode.
        """
        
        try:
            if self._dbFrames:
                self.apply_breakpoints()
                exec(code, self.globals, self.locals)
            else:
                # Turn debugger on at this point. If there are no breakpoints,
                # the tracing is disabled for better performance.
                self.apply_breakpoints()
                self.debugger.set_on() 
                exec(code, self.locals)
        except bdb.BdbQuit:
            self.dbstop_handler()
        except Exception:
            time.sleep(0.2) # Give stdout some time to send data
            self.showtraceback()
        except KeyboardInterrupt: # is a BaseException, not an Exception
            time.sleep(0.2)
            self.showtraceback()
    
    
    def apply_breakpoints(self):
        """ Breakpoints are updated at each time a command is given,
        including commands like "db continue".
        """
        try:
            breaks = self.context._stat_breakpoints.recv()
            if self.debugger.breaks:
                self.debugger.clear_all_breaks()
            if breaks:  # Can be None
                for fname in breaks:
                    for linenr in breaks[fname]:
                        self.debugger.set_break(fname, linenr)
        except Exception:
            type, value, tb = sys.exc_info(); del tb
            print('Error while setting breakpoints: %s' % str(value))
    
    
    ## Handlers and hooks
    
    def ipython_pre_run_cell_hook(self, ipython=None):
        """ Hook that IPython calls right before executing code.
        """
        self.apply_breakpoints()
        self.debugger.set_on() 
    
    
    def ipython_editor_hook(self, ipython, filename, linenum=None, wait=True):
        # Correct line number for cell offset
        filename, linenum = self.correctfilenameandlineno(filename, linenum or 0)
        # Get action string
        if linenum:
            action = 'open %i %s' % (linenum, os.path.abspath(filename))
        else:
            action = 'open %s' % os.path.abspath(filename)
        # Send
        self.context._strm_action.send(action)
    
    
    def ipython_ask_exit(self):
        # Ask the user
        a = input("Do you really want to exit ([y]/n)? ")
        a = a or 'y'
        # Close stdin if necessary
        if a.lower() == 'y':
            sys.stdin._channel.close()
    
    
    def dbstop_handler(self, *args, **kwargs):
        print("Program execution stopped from debugger.")
    
    
    
    
    
    
    ## Writing and error handling
    
    
    def write(self, text):
        """ Write errors. """
        sys.stderr.write( text )
    
    
    def showsyntaxerror(self, filename=None):
        """Display the syntax error that just occurred.
        This doesn't display a stack trace because there isn't one.        
        If a filename is given, it is stuffed in the exception instead
        of what was there before (because Python's parser always uses
        "<string>" when reading from a string).
        
        Pyzo version: support to display the right line number,
        see doc of showtraceback for details.        
        """
        
        # Get info (do not store)
        type, value, tb = sys.exc_info();  del tb
        
        # Work hard to stuff the correct filename in the exception
        if filename and type is SyntaxError:
            try:
                # unpack information
                msg, (dummy_filename, lineno, offset, line) = value
                # correct line-number
                fname, lineno = self.correctfilenameandlineno(filename, lineno)
            except:
                # Not the format we expect; leave it alone
                pass
            else:
                # Stuff in the right filename
                value = SyntaxError(msg, (fname, lineno, offset, line))
                sys.last_value = value
        
        # Show syntax error 
        strList = traceback.format_exception_only(type, value)
        for s in strList:
            self.write(s)
    
    
    def showtraceback(self, useLastTraceback=False):
        """Display the exception that just occurred.
        We remove the first stack item because it is our own code.
        The output is written by self.write(), below.
        
        In the pyzo version, before executing a block of code,
        the filename is modified by appending " [x]". Where x is
        the index in a list that we keep, of tuples 
        (sourcecode, filename, lineno). 
        
        Here, showing the traceback, we check if we see such [x], 
        and if so, we extract the line of code where it went wrong,
        and correct the lineno, so it will point at the right line
        in the editor if part of a file was executed. When the file
        was modified since the part in question was executed, the
        fileno might deviate, but the line of code shown shall 
        always be correct...
        """
        # Traceback info:
        # tb_next -> go down the trace
        # tb_frame -> get the stack frame
        # tb_lineno -> where it went wrong
        #
        # Frame info:
        # f_back -> go up (towards caller)
        # f_code -> code object
        # f_locals -> we can execute code here when PM debugging
        # f_globals
        # f_trace -> (can be None) function for debugging? (
        #
        # The traceback module is used to obtain prints from the
        # traceback.
        
        try:
            if useLastTraceback:
                # Get traceback info from buffered
                type = sys.last_type
                value = sys.last_value
                tb = sys.last_traceback
            else:
                # Get exception information and remove first, since that's us
                type, value, tb = sys.exc_info()
                tb = tb.tb_next
                
                # Store for debugging, but only store if not in debug mode
                if not self._dbFrames:
                    sys.last_type = type
                    sys.last_value = value
                    sys.last_traceback = tb
            
            # Get traceback to correct all the line numbers
            # tblist = list  of (filename, line-number, function-name, text)
            tblist = traceback.extract_tb(tb)
            
            # Get frames
            frames = []
            while tb:
                frames.append(tb.tb_frame)
                tb = tb.tb_next
            
            # Walk through the list
            for i in range(len(tblist)):
                tbInfo = tblist[i]                
                # Get filename and line number, init example
                fname, lineno = self.correctfilenameandlineno(tbInfo[0], tbInfo[1])
                if not isinstance(fname, ustr):
                    fname = fname.decode('utf-8')
                example = tbInfo[3]
                # Reset info
                tblist[i] = (fname, lineno, tbInfo[2], example)
            
            # Format list
            strList = traceback.format_list(tblist)
            if strList:
                strList.insert(0, "Traceback (most recent call last):\n")
            strList.extend( traceback.format_exception_only(type, value) )
            
            # Write traceback
            for s in strList:
                self.write(s)
            
            # Clean up (we cannot combine except and finally in Python <2.5
            tb = None
            frames = None
        
        except Exception:
            self.write('An error occured, but could not write traceback.\n')
            tb = None
            frames = None
    
    
    def correctfilenameandlineno(self, fname, lineno):
        """ Given a filename and lineno, this function returns
        a modified (if necessary) version of the two. 
        As example:
        "foo.py+7", 22  -> "foo.py", 29
        """
        j = fname.rfind('+')
        if j>0:
            try:
                lineno += int(fname[j+1:])
                fname = fname[:j]
            except ValueError:
                pass
        return fname, lineno
コード例 #9
0
class PyzoInterpreter:
    """ PyzoInterpreter
    
    The pyzo interpreter is the part that makes the pyzo kernel interactive.
    It executes code, integrates the GUI toolkit, parses magic commands, etc.
    The pyzo interpreter has been designed to emulate the standard interactive
    Python console as much as possible, but with a lot of extra goodies.
    
    There is one instance of this class, stored at sys._pyzoInterpreter and
    at the __pyzo__ variable in the global namespace.
    
    The global instance has a couple of interesting attributes:
      * context: the yoton Context instance at the kernel (has all channels)
      * introspector: the introspector instance (a subclassed yoton.RepChannel)
      * magician: the object that handles the magic commands
      * guiApp: a wrapper for the integrated GUI application
      * sleeptime: the amount of time (in seconds) to sleep at each iteration
    
    """
    
    # Simular working as code.InteractiveConsole. Some code was copied, but
    # the following things are changed:
    # - prompts are printed in the err stream, like the default interpreter does
    # - uses an asynchronous read using the yoton interface
    # - support for hijacking GUI toolkits
    # - can run large pieces of code
    # - support post mortem debugging
    # - support for magic commands
    
    def __init__(self, locals, filename="<console>"):
        
        # Init variables for locals and globals (globals only for debugging)
        self.locals = locals
        self.globals = None
        
        # Store filename
        self._filename = filename
        
        # Store ref of locals that is our main
        self._main_locals = locals
        
        # Flag to ignore sys exit, to allow running some scripts
        # interactively, even if they call sys.exit.
        self.ignore_sys_exit = False
        
        # Information for debugging. If self._dbFrames, we're in debug mode
        # _dbFrameIndex starts from 1 
        self._dbFrames = []
        self._dbFrameIndex = 0
        self._dbFrameName = ''
        
        # Init datase to store source code that we execute
        self._codeCollection = ExecutedSourceCollection()
        
        # Init buffer to deal with multi-line command in the shell
        self._buffer = []
        
        # Init sleep time. 0.001 result in 0% CPU usage at my laptop (Windows),
        # but 8% CPU usage at my older laptop (on Linux).
        self.sleeptime = 0.01 # 100 Hz
        
        # Create compiler
        if sys.platform.startswith('java'):
            import compiler
            self._compile = compiler.compile  # or 'exec' does not work
        else:
            self._compile = CommandCompiler()
        
        # Instantiate magician and tracer
        self.magician = Magician()
        self.debugger = Debugger()
        
        # To keep track of whether to send a new prompt, and whether more
        # code is expected.
        self.more = 0
        self.newPrompt = True
        
        # Code and script to run on first iteration
        self._codeToRunOnStartup = None
        self._scriptToRunOnStartup = None
        
        # Remove "THIS" directory from the PYTHONPATH
        # to prevent unwanted imports. Same for pyzokernel dir
        thisPath = os.getcwd()
        for p in [thisPath, os.path.join(thisPath,'pyzokernel')]:
            while p in sys.path:
                sys.path.remove(p)
    
    
    def run(self):    
        """ Run (start the mainloop)
        
        Here we enter the main loop, which is provided by the guiApp. 
        This event loop calls process_commands on a regular basis. 
        
        We may also enter the debug intereaction loop, either from a
        request for post-mortem debugging, or *during* execution by
        means of a breakpoint. When in this debug-loop, the guiApp event
        loop lays still, but the debug-loop does call process-commands
        for user interaction. 
        
        When the user wants to quit, SystemExit is raised (one way or
        another). This is detected in process_commands and the exception
        instance is stored in self._exitException. Then the debug-loop
        is stopped if necessary, and the guiApp is told to stop its event
        loop.
        
        And that brings us back here, where we exit using in order of
        preference: self._exitException, the exception with which the
        event loop was exited (if any), or a new exception.
        
        """
        
        # Prepare
        self._prepare()
        self._exitException = None
        
        # Enter main
        try:
            self.guiApp.run(self.process_commands, self.sleeptime) 
        except SystemExit:
            # Set self._exitException if it is not set yet
            type, value, tb = sys.exc_info();  del tb
            if self._exitException is None:
                self._exitException = value
        
        # Exit
        if self._exitException is None:
            self._exitException = SystemExit()
        raise self._exitException
    
    
    def _prepare(self):
        """ Prepare for running the main loop.
        Here we do some initialization like obtaining the startup info,
        creating the GUI application wrapper, etc.
        """
        
        # Reset debug status
        self.debugger.writestatus()
        
        # Get startup info (get a copy, or setting the new version wont trigger!)
        while self.context._stat_startup.recv() is None:
            time.sleep(0.02)
        self.startup_info = startup_info = self.context._stat_startup.recv().copy()
        
        # Set startup info (with additional info)
        if sys.platform.startswith('java'):
            import __builtin__ as builtins  # Jython
        else:
            builtins = __builtins__
        if not isinstance(builtins, dict):
            builtins = builtins.__dict__
        startup_info['builtins'] = [builtin for builtin in builtins.keys()]
        startup_info['version'] = tuple(sys.version_info)
        startup_info['keywords'] = keyword.kwlist
        self.context._stat_startup.send(startup_info)
        
        # Prepare the Python environment
        self._prepare_environment(startup_info)
        
        # Run startup code (before loading GUI toolkit or IPython
        self._run_startup_code(startup_info)
        
        # Write Python banner (to stdout)
        thename = 'Python'
        if sys.version_info[0] == 2:
            thename = 'Legacy Python'
        if '__pypy__' in sys.builtin_module_names:
            thename = 'Pypy'
        if sys.platform.startswith('java'):
            thename = 'Jython'
            # Jython cannot do struct.calcsize("P")
            import java.lang
            real_plat = java.lang.System.getProperty("os.name").lower()
            plat = '%s/%s' % (sys.platform, real_plat)
        elif sys.platform.startswith('win'):
            NBITS = 8 * struct.calcsize("P")
            plat = 'Windows (%i bits)' % NBITS
        else:
            NBITS = 8 * struct.calcsize("P")
            plat = '%s (%i bits)' % (sys.platform, NBITS) 
        printDirect("%s %s on %s.\n" %
                    (thename, sys.version.split('[')[0].rstrip(), plat))
        
        # Integrate GUI
        guiName, guiError = self._integrate_gui(startup_info)
        
        # Write pyzo part of banner (including what GUI loop is integrated)
        if True:
            pyzoBanner = 'This is the Pyzo interpreter'
        if guiError:
            pyzoBanner += '. ' + guiError + '\n'
        elif guiName:
            pyzoBanner += ' with integrated event loop for ' 
            pyzoBanner += guiName + '.\n'
        else:
            pyzoBanner += '.\n'
        printDirect(pyzoBanner)
        
        # Try loading IPython
        if startup_info.get('ipython', '').lower() in ('', 'no', 'false'):
            self._ipython = None
        else:
            try:
                self._load_ipyhon()
            except Exception:
                type, value, tb = sys.exc_info();
                del tb
                printDirect('IPython could not be loaded: %s\n' % str(value))
                self._ipython = None
        
        # Set prompts
        sys.ps1 = PS1(self)
        sys.ps2 = PS2(self)
        
        # Notify about project path
        projectPath = startup_info['projectPath']
        if projectPath:
            printDirect('Prepending the project path %r to sys.path\n' % 
                projectPath)
        
        # Write tips message.
        if self._ipython:
            import IPython
            printDirect("\nUsing IPython %s -- An enhanced Interactive Python.\n"
                        %  IPython.__version__)
            printDirect(
                "?         -> Introduction and overview of IPython's features.\n"
                "%quickref -> Quick reference.\n"
                "help      -> Python's own help system.\n"
                "object?   -> Details about 'object', "
                "use 'object??' for extra details.\n")
        else:
            printDirect("Type 'help' for help, " + 
                        "type '?' for a list of *magic* commands.\n")
        
        # Notify the running of the script
        if self._scriptToRunOnStartup:
            printDirect('\x1b[0;33mRunning script: "'+self._scriptToRunOnStartup+'"\x1b[0m\n')
        
        # Prevent app nap on OSX 9.2 and up
        # The _nope module is taken from MINRK's appnope package
        if sys.platform == "darwin" and LV(platform.mac_ver()[0]) >= LV("10.9"):
            from pyzokernel import _nope
            _nope.nope()
        
        # Setup post-mortem debugging via appropriately logged exceptions
        class PMHandler(logging.Handler):
            def emit(self, record):
                if record.exc_info:
                    sys.last_type, sys.last_value, sys.last_traceback = record.exc_info
                return record
        #
        root_logger = logging.getLogger()
        if not root_logger.handlers:
            root_logger.addHandler(logging.StreamHandler())
        root_logger.addHandler(PMHandler())
    
    
    def _prepare_environment(self, startup_info):
        """ Prepare the Python environment. There are two possibilities:
        either we run a script or we run interactively.
        """
        
        # Get whether we should (and can) run as script
        scriptFilename = startup_info['scriptFile']
        if scriptFilename:
            if not os.path.isfile(scriptFilename):
                printDirect('Invalid script file: "'+scriptFilename+'"\n')
                scriptFilename = None
        
        # Get project path
        projectPath = startup_info['projectPath']
        
        if scriptFilename.endswith('.ipynb'):
            # Run Jupyter notebook
            import notebook.notebookapp
            sys.argv = ['jupyter_notebook', scriptFilename]
            sys.exit(notebook.notebookapp.main())
        
        elif scriptFilename:
            # RUN AS SCRIPT
            # Set __file__  (note that __name__ is already '__main__')
            self.locals['__file__'] = scriptFilename
            # Set command line arguments
            sys.argv[:] = []
            sys.argv.append(scriptFilename)
            sys.argv.extend(shlex.split(startup_info.get('argv', '')))
            # Insert script directory to path
            theDir = os.path.abspath( os.path.dirname(scriptFilename) )
            if theDir not in sys.path:
                sys.path.insert(0, theDir)
            if projectPath is not None:
                sys.path.insert(0,projectPath)
            # Go to script dir
            os.chdir( os.path.dirname(scriptFilename) )
            # Run script later
            self._scriptToRunOnStartup = scriptFilename
        else:
            # RUN INTERACTIVELY
            # No __file__ (note that __name__ is already '__main__')
            self.locals.pop('__file__','')
            # Remove all command line arguments, set first to empty string
            sys.argv[:] = []
            sys.argv.append('')
            sys.argv.extend(shlex.split(startup_info.get('argv', '')))
            # Insert current directory to path
            sys.path.insert(0, '')
            if projectPath:
                sys.path.insert(0,projectPath)
            # Go to start dir
            startDir = startup_info['startDir']
            if startDir and os.path.isdir(startDir):
                os.chdir(startDir)
            else:
                os.chdir(os.path.expanduser('~')) # home dir 
    
    
    def _run_startup_code(self, startup_info):
        """ Execute the startup code or script.
        """
        
        # Run startup script (if set)
        script = startup_info['startupScript']
        # Should we use the default startupScript?
        if script == '$PYTHONSTARTUP':
            script = os.environ.get('PYTHONSTARTUP','')
        
        if '\n' in script:
            # Run code later or now
            firstline = script.split('\n')[0].replace(' ', '')
            if firstline.startswith('#AFTER_GUI'):
                self._codeToRunOnStartup = script
            else:
                self.context._stat_interpreter.send('Busy') 
                msg = {'source': script, 'fname': '<startup>', 'lineno': 0}
                self.runlargecode(msg, True)
        elif script and os.path.isfile(script):
            # Run script
            self.context._stat_interpreter.send('Busy') 
            self.runfile(script)
        else:
            # Nothing to run
            pass
    
    
    def _integrate_gui(self, startup_info):
        """ Integrate event loop of GUI toolkit (or use pure Python
        event loop).
        """
        
        self.guiApp = guiintegration.App_base()
        self.guiName = guiName = startup_info['gui'].upper()
        guiError = ''
        try:
            if guiName in ['', 'NONE']:
                guiName = ''
            elif guiName == 'AUTO':
                for tryName, tryApp in [('PYQT5', guiintegration.App_pyqt5),
                                        ('PYQT4', guiintegration.App_pyqt4),
                                        ('PYSIDE2', guiintegration.App_pyside2),
                                        ('PYSIDE', guiintegration.App_pyside),
                                        #('WX', guiintegration.App_wx),
                                        ('TK', guiintegration.App_tk),
                                        ]:
                    try:
                        self.guiApp = tryApp()
                    except Exception:
                        continue
                    guiName = tryName
                    break
                else:
                    guiName = ''
            elif guiName == 'TK':
                self.guiApp = guiintegration.App_tk()
            elif guiName == 'WX':
                self.guiApp = guiintegration.App_wx()
            elif guiName == 'TORNADO':
                self.guiApp = guiintegration.App_tornado()
            elif guiName == 'PYSIDE':
                self.guiApp = guiintegration.App_pyside()
            elif guiName == 'PYSIDE2':
                self.guiApp = guiintegration.App_pyside2()
            elif guiName in ['PYQT5', 'QT5']:
                self.guiApp = guiintegration.App_pyqt5()
            elif guiName in ['PYQT4', 'QT4']:
                self.guiApp = guiintegration.App_pyqt4()
            elif guiName == 'FLTK':
                self.guiApp = guiintegration.App_fltk()
            elif guiName == 'GTK':
                self.guiApp = guiintegration.App_gtk()
            else:
                guiError = 'Unkown gui: %s' % guiName
                guiName = ''
        except Exception: # Catch any error
            # Get exception info (we do it using sys.exc_info() because
            # we cannot catch the exception in a version independent way.
            type, value, tb = sys.exc_info();  del tb
            guiError = 'Failed to integrate event loop for %s: %s' % (
                guiName, str(value))
        
        return guiName, guiError
    
    
    def _load_ipyhon(self):
        """ Try loading IPython shell. The result is set in self._ipython
        (can be None if IPython not available).
        """
        
        # Init
        self._ipython = None
        import __main__
        
        # Try importing IPython
        try:
            import IPython
        except ImportError:
            return
        
        # Version ok?
        if IPython.version_info < (1,):
            return
        
        # Create an IPython shell
        from IPython.core.interactiveshell import InteractiveShell 
        self._ipython = InteractiveShell(user_module=__main__)
        
        # Set some hooks / event callbacks
        # Run hook (pre_run_code_hook is depreacted in 2.0)
        pre_run_cell_hook  = self.ipython_pre_run_cell_hook
        if IPython.version_info < (2,):
            self._ipython.set_hook('pre_run_code_hook', pre_run_cell_hook)
        else:
            self._ipython.events.register('pre_run_cell', pre_run_cell_hook)
        # Other hooks
        self._ipython.set_hook('editor', self.ipython_editor_hook)
        self._ipython.set_custom_exc((bdb.BdbQuit,), self.dbstop_handler)
        
        # Some patching
        self._ipython.ask_exit = self.ipython_ask_exit
        
        # Make output be shown on Windows
        if sys.platform.startswith('win'):
            # IPython wraps std streams just like we do below, but
            # pyreadline adds *another* wrapper, which is where it
            # goes wrong. Here we set it back to bypass pyreadline.
            from IPython.utils import io
            io.stdin = io.IOStream(sys.stdin)
            io.stdout = io.IOStream(sys.stdout)
            io.stderr = io.IOStream(sys.stderr)
            
            # Ipython uses msvcrt e.g. for pausing between pages
            # but this does not work in pyzo
            import msvcrt
            msvcrt.getwch = msvcrt.getch = input  # input is deffed above
    
    
    def process_commands(self):
        """ Do one iteration of processing commands (the REPL).
        """
        try:
            
            self._process_commands()
            
        except SystemExit:
            # It may be that we should ignore sys exit now...
            if self.ignore_sys_exit:
                self.ignore_sys_exit = False  # Never allow more than once
                return
            # Get and store the exception so we can raise it later
            type, value, tb = sys.exc_info();  del tb
            self._exitException = value
            # Stop debugger if it is running
            self.debugger.stopinteraction()
            # Exit from interpreter. Exit in the appropriate way
            self.guiApp.quit()  # Is sys.exit() by default
    
    
    def _process_commands(self):
        
        # Run startup code/script inside the loop (only the first time)
        # so that keyboard interrupt will work
        if self._codeToRunOnStartup:
            self.context._stat_interpreter.send('Busy')
            self._codeToRunOnStartup, tmp = None, self._codeToRunOnStartup
            self.pushline(tmp)
        if self._scriptToRunOnStartup:
            self.context._stat_interpreter.send('Busy') 
            self._scriptToRunOnStartup, tmp = None, self._scriptToRunOnStartup
            self.runfile(tmp)
        
        # Flush real stdout / stderr
        sys.__stdout__.flush()
        sys.__stderr__.flush()
        
        # Set status and prompt?
        # Prompt is allowed to be an object with __str__ method
        if self.newPrompt:
            self.newPrompt = False
            ps = [sys.ps1, sys.ps2][bool(self.more)]
            self.context._strm_prompt.send(str(ps))
        
        if True:
            # Determine state. The message is really only send
            # when the state is different. Note that the kernelbroker
            # can also set the state ("Very busy", "Busy", "Dead")
            if self._dbFrames:
                self.context._stat_interpreter.send('Debug')
            elif self.more:
                self.context._stat_interpreter.send('More')
            else:
                self.context._stat_interpreter.send('Ready')
        
        
        # Are we still connected?
        if sys.stdin.closed or not self.context.connection_count:
            # Exit from main loop.
            # This will raise SystemExit and will shut us down in the 
            # most appropriate way
            sys.exit()
        
        # Get channel to take a message from
        ch = yoton.select_sub_channel(self.context._ctrl_command, self.context._ctrl_code)
        
        if ch is None:
            pass # No messages waiting
        
        elif ch is self.context._ctrl_command:
            # Read command 
            line1 = self.context._ctrl_command.recv(False) # Command
            if line1:
                # Notify what we're doing
                self.context._strm_echo.send(line1)
                self.context._stat_interpreter.send('Busy')
                self.newPrompt = True
                # Convert command 
                # (only a few magics are supported if IPython is active)
                line2 = self.magician.convert_command(line1.rstrip('\n'))
                # Execute actual code
                if line2 is not None:
                    for line3 in line2.split('\n'):  # not splitlines!
                        self.more = self.pushline(line3)
                else:
                    self.more = False
                    self._resetbuffer()
        
        elif ch is self.context._ctrl_code:
            # Read larger block of code (dict)
            msg = self.context._ctrl_code.recv(False)
            if msg:
                # Notify what we're doing
                # (runlargecode() sends on stdin-echo)
                self.context._stat_interpreter.send('Busy')
                self.newPrompt = True
                # Execute code
                self.runlargecode(msg)
                # Reset more stuff
                self._resetbuffer()
                self.more = False
        
        else:
            # This should not happen, but if it does, just flush!
            ch.recv(False)


    
    ## Running code in various ways
    # In all cases there is a call for compilecode and a call to execcode
    
    def _resetbuffer(self):
        """Reset the input buffer."""
        self._buffer = []
    
    
    def pushline(self, line):
        """Push a line to the interpreter.
        
        The line should not have a trailing newline; it may have
        internal newlines.  The line is appended to a buffer and the
        interpreter's _runlines() method is called with the
        concatenated contents of the buffer as source.  If this
        indicates that the command was executed or invalid, the buffer
        is reset; otherwise, the command is incomplete, and the buffer
        is left as it was after the line was appended.  The return
        value is 1 if more input is required, 0 if the line was dealt
        with in some way (this is the same as _runlines()).
        
        """
        # Get buffer, join to get source
        buffer = self._buffer
        buffer.append(line)
        source = "\n".join(buffer)
        # Clear buffer and run source
        self._resetbuffer()
        more = self._runlines(source, self._filename)
        # Create buffer if needed
        if more:
            self._buffer = buffer 
        return more
    

    def _runlines(self, source, filename="<input>", symbol="single"):
        """Compile and run some source in the interpreter.
        
        Arguments are as for compile_command().
        
        One several things can happen:
        
        1) The input is incorrect; compile_command() raised an
        exception (SyntaxError or OverflowError).  A syntax traceback
        will be printed by calling the showsyntaxerror() method.
        
        2) The input is incomplete, and more input is required;
        compile_command() returned None.  Nothing happens.
        
        3) The input is complete; compile_command() returned a code
        object.  The code is executed by calling self.execcode() (which
        also handles run-time exceptions, except for SystemExit).
        
        The return value is True in case 2, False in the other cases (unless
        an exception is raised).  The return value can be used to
        decide whether to use sys.ps1 or sys.ps2 to prompt the next
        line.
        
        """
        
        use_ipython = self._ipython and not self._dbFrames
        
        # Try compiling.
        # The IPython kernel does not handle incomple lines, so we check
        # that ourselves ...
        error = None
        try:
            code = self.compilecode(source, filename, symbol)
        except (OverflowError, SyntaxError, ValueError):
            error = sys.exc_info()[1]
            code = False
        
        if use_ipython:
            if code is None:
                # Case 2
                #self._ipython.run_cell('', True)
                return True
            else:
                # Case 1 and 3 handled by IPython
                self._ipython.run_cell(source, True, False)
                return False
                
        else:
            if code is None:
                # Case 2
                return True
            elif not code:
                # Case 1, a bit awkward way to show the error, but we need
                # to call showsyntaxerror in an exception handler.
                try:
                    raise error
                except Exception:
                    self.showsyntaxerror(filename)
                return False
            else:
                # Case 3
                self.execcode(code)
                return False
    
    
    def runlargecode(self, msg, silent=False):
        """ To execute larger pieces of code. """
        
        # Get information
        source, fname, lineno = msg['source'], msg['fname'], msg['lineno']
        cellName = msg.get('cellName', '')
        source += '\n'
        
        # Construct notification message
        lineno1 = lineno + 1
        lineno2 = lineno + source.count('\n')
        fname_show = fname
        if not fname.startswith('<'):
            fname_show = os.path.split(fname)[1]
        if cellName == fname:
            runtext = '(executing file "%s")\n' % fname_show
            if os.path.isfile(fname):
                d = os.path.normpath(os.path.normcase(os.path.dirname(fname)))
                if d != os.getcwd():
                    # print('Changing directory to', d)
                    os.chdir(d)
        elif cellName:
            runtext = '(executing cell "%s" (line %i of "%s"))\n' % (cellName, lineno1, fname_show)
        elif lineno1 == lineno2:
            runtext = '(executing line %i of "%s")\n' % (lineno1, fname_show)
        else:
            runtext = '(executing lines %i to %i of "%s")\n' % (
                                                lineno1, lineno2, fname_show)
        # Notify IDE
        if not silent:
            self.context._strm_echo.send('\x1b[0;33m%s\x1b[0m' % runtext)
            # Increase counter
            if self._ipython:
                self._ipython.execution_count += 1
        
        # Put the line number in the filename (if necessary)
        # Note that we could store the line offset in the _codeCollection,
        # but then we cannot retrieve it for syntax errors.
        if lineno:
            fname = "%s+%i" % (fname, lineno)
        
        # Try compiling the source
        code = None
        try:            
            # Compile
            code = self.compilecode(source, fname, "exec")          
            
        except (OverflowError, SyntaxError, ValueError):
            self.showsyntaxerror(fname)
            return
        
        if code:
            # Store the source using the (id of the) code object as a key
            self._codeCollection.store_source(code, source)
            # Execute the code
            self.execcode(code)
        else:
            # Incomplete code
            self.write('Could not run code because it is incomplete.\n')
    
    
    def runfile(self, fname):
        """  To execute the startup script. """ 
        
        # Get text (make sure it ends with a newline)
        try:
            source = open(fname, 'rb').read().decode('UTF-8')
        except Exception:
            printDirect('Could not read script (decoding using UTF-8): "' + fname + '"\n')
            return
        try:
            source = source.replace('\r\n', '\n').replace('\r','\n')
            if source[-1] != '\n':
                source += '\n'
        except Exception:        
            printDirect('Could not execute script: "' + fname + '"\n')
            return
        
        # Try compiling the source
        code = None
        try:            
            # Compile
            code = self.compilecode(source, fname, "exec")
        except (OverflowError, SyntaxError, ValueError):
            time.sleep(0.2) # Give stdout time to be send
            self.showsyntaxerror(fname)
            return
        
        if code:
            # Store the source using the (id of the) code object as a key
            self._codeCollection.store_source(code, source)
            # Execute the code
            self.execcode(code)
        else:
            # Incomplete code
            self.write('Could not run code because it is incomplete.\n')
    
    
    def compilecode(self, source, filename, mode, *args, **kwargs):
        """ Compile source code.
        Will mangle coding definitions on first two lines. 
        
        * This method should be called with Unicode sources.
        * Source newlines should consist only of LF characters.
        """
        
        # This method solves pyzo issue 22

        # Split in first two lines and the rest
        parts = source.split('\n', 2)
        
        # Replace any coding definitions
        ci = 'coding is'
        contained_coding = False
        for i in range(len(parts)-1):
            tmp = parts[i]
            if tmp and tmp[0] == '#' and 'coding' in tmp:
                contained_coding = True
                parts[i] = tmp.replace('coding=', ci).replace('coding:', ci)
        
        # Combine parts again (if necessary)
        if contained_coding:
            source = '\n'.join(parts)
        
        # Convert filename to UTF-8 if Python version < 3
        if PYTHON_VERSION < 3:
            filename = filename.encode('utf-8')
        
        # Compile
        return self._compile(source, filename, mode, *args, **kwargs)
    
    
    def execcode(self, code):
        """Execute a code object.
        
        When an exception occurs, self.showtraceback() is called to
        display a traceback.  All exceptions are caught except
        SystemExit, which is reraised.
        
        A note about KeyboardInterrupt: this exception may occur
        elsewhere in this code, and may not always be caught.  The
        caller should be prepared to deal with it.
        
        The globals variable is used when in debug mode.
        """
        
        try:
            if self._dbFrames:
                self.apply_breakpoints()
                exec(code, self.globals, self.locals)
            else:
                # Turn debugger on at this point. If there are no breakpoints,
                # the tracing is disabled for better performance.
                self.apply_breakpoints()
                self.debugger.set_on() 
                exec(code, self.locals)
        except bdb.BdbQuit:
            self.dbstop_handler()
        except Exception:
            time.sleep(0.2) # Give stdout some time to send data
            self.showtraceback()
        except KeyboardInterrupt: # is a BaseException, not an Exception
            time.sleep(0.2)
            self.showtraceback()
    
    
    def apply_breakpoints(self):
        """ Breakpoints are updated at each time a command is given,
        including commands like "db continue".
        """
        try:
            breaks = self.context._stat_breakpoints.recv()
            if self.debugger.breaks:
                self.debugger.clear_all_breaks()
            if breaks:  # Can be None
                for fname in breaks:
                    for linenr in breaks[fname]:
                        self.debugger.set_break(fname, linenr)
        except Exception:
            type, value, tb = sys.exc_info(); del tb
            print('Error while setting breakpoints: %s' % str(value))
    
    
    ## Handlers and hooks
    
    def ipython_pre_run_cell_hook(self, ipython=None):
        """ Hook that IPython calls right before executing code.
        """
        self.apply_breakpoints()
        self.debugger.set_on() 
    
    
    def ipython_editor_hook(self, ipython, filename, linenum=None, wait=True):
        # Correct line number for cell offset
        filename, linenum = self.correctfilenameandlineno(filename, linenum or 0)
        # Get action string
        if linenum:
            action = 'open %i %s' % (linenum, os.path.abspath(filename))
        else:
            action = 'open %s' % os.path.abspath(filename)
        # Send
        self.context._strm_action.send(action)
    
    
    def ipython_ask_exit(self):
        # Ask the user
        a = input("Do you really want to exit ([y]/n)? ")
        a = a or 'y'
        # Close stdin if necessary
        if a.lower() == 'y':
            sys.stdin._channel.close()
    
    
    def dbstop_handler(self, *args, **kwargs):
        print("Program execution stopped from debugger.")
    
    
    
    
    
    
    ## Writing and error handling
    
    
    def write(self, text):
        """ Write errors. """
        sys.stderr.write( text )
    
    
    def showsyntaxerror(self, filename=None):
        """Display the syntax error that just occurred.
        This doesn't display a stack trace because there isn't one.        
        If a filename is given, it is stuffed in the exception instead
        of what was there before (because Python's parser always uses
        "<string>" when reading from a string).
        
        Pyzo version: support to display the right line number,
        see doc of showtraceback for details.        
        """
        
        # Get info (do not store)
        type, value, tb = sys.exc_info();  del tb
        
        # Work hard to stuff the correct filename in the exception
        if filename and type is SyntaxError:
            try:
                # unpack information
                msg, (dummy_filename, lineno, offset, line) = value
                # correct line-number
                fname, lineno = self.correctfilenameandlineno(filename, lineno)
            except:
                # Not the format we expect; leave it alone
                pass
            else:
                # Stuff in the right filename
                value = SyntaxError(msg, (fname, lineno, offset, line))
                sys.last_value = value
        
        # Show syntax error 
        strList = traceback.format_exception_only(type, value)
        for s in strList:
            self.write(s)
    
    
    def showtraceback(self, useLastTraceback=False):
        """Display the exception that just occurred.
        We remove the first stack item because it is our own code.
        The output is written by self.write(), below.
        
        In the pyzo version, before executing a block of code,
        the filename is modified by appending " [x]". Where x is
        the index in a list that we keep, of tuples 
        (sourcecode, filename, lineno). 
        
        Here, showing the traceback, we check if we see such [x], 
        and if so, we extract the line of code where it went wrong,
        and correct the lineno, so it will point at the right line
        in the editor if part of a file was executed. When the file
        was modified since the part in question was executed, the
        fileno might deviate, but the line of code shown shall 
        always be correct...
        """
        # Traceback info:
        # tb_next -> go down the trace
        # tb_frame -> get the stack frame
        # tb_lineno -> where it went wrong
        #
        # Frame info:
        # f_back -> go up (towards caller)
        # f_code -> code object
        # f_locals -> we can execute code here when PM debugging
        # f_globals
        # f_trace -> (can be None) function for debugging? (
        #
        # The traceback module is used to obtain prints from the
        # traceback.
        
        try:
            if useLastTraceback:
                # Get traceback info from buffered
                type = sys.last_type
                value = sys.last_value
                tb = sys.last_traceback
            else:
                # Get exception information and remove first, since that's us
                type, value, tb = sys.exc_info()
                tb = tb.tb_next
                
                # Store for debugging, but only store if not in debug mode
                if not self._dbFrames:
                    sys.last_type = type
                    sys.last_value = value
                    sys.last_traceback = tb
            
            # Get traceback to correct all the line numbers
            # tblist = list  of (filename, line-number, function-name, text)
            tblist = traceback.extract_tb(tb)
            
            # Get frames
            frames = []
            while tb:
                frames.append(tb.tb_frame)
                tb = tb.tb_next
            
            # Walk through the list
            for i in range(len(tblist)):
                tbInfo = tblist[i]                
                # Get filename and line number, init example
                fname, lineno = self.correctfilenameandlineno(tbInfo[0], tbInfo[1])
                if not isinstance(fname, ustr):
                    fname = fname.decode('utf-8')
                example = tbInfo[3]
                # Reset info
                tblist[i] = (fname, lineno, tbInfo[2], example)
            
            # Format list
            strList = traceback.format_list(tblist)
            if strList:
                strList.insert(0, "Traceback (most recent call last):\n")
            strList.extend( traceback.format_exception_only(type, value) )
            
            # Write traceback
            for s in strList:
                self.write(s)
            
            # Clean up (we cannot combine except and finally in Python <2.5
            tb = None
            frames = None
        
        except Exception:
            self.write('An error occured, but could not write traceback.\n')
            tb = None
            frames = None
    
    
    def correctfilenameandlineno(self, fname, lineno):
        """ Given a filename and lineno, this function returns
        a modified (if necessary) version of the two. 
        As example:
        "foo.py+7", 22  -> "foo.py", 29
        """
        j = fname.rfind('+')
        if j>0:
            try:
                lineno += int(fname[j+1:])
                fname = fname[:j]
            except ValueError:
                pass
        return fname, lineno
コード例 #10
0
class BashKernel(Kernel):
    implementation = 'bash_kernel'
    implementation_version = __version__

    @property
    def language_version(self):
        m = version_pat.search(self.banner)
        return m.group(1)

    _banner = None

    @property
    def banner(self):
        if self._banner is None:
            self._banner = check_output(['bash', '--version']).decode('utf-8')
        return self._banner

    language_info = {
        'name': 'bash',
        'codemirror_mode': 'shell',
        'mimetype': 'text/x-sh',
        'file_extension': '.sh'
    }

    def __init__(self, **kwargs):
        Kernel.__init__(self, **kwargs)
        self.ipy_shell = InteractiveShell()
        self.ipy_shell.extension_manager.load_extension('ipython_nose')
        InteractiveShell._instance = self.ipy_shell
        self._start_bash()

    def _start_bash(self):
        # Signal handlers are inherited by forked processes, and we can't easily
        # reset it from the subprocess. Since kernelapp ignores SIGINT except in
        # message handlers, we need to temporarily reset the SIGINT handler here
        # so that bash and its children are interruptible.
        sig = signal.signal(signal.SIGINT, signal.SIG_DFL)
        try:
            # Note: the next few lines mirror functionality in the
            # bash() function of pexpect/replwrap.py.  Look at the
            # source code there for comments and context for
            # understanding the code here.
            bashrc = os.path.join(os.path.dirname(pexpect.__file__),
                                  'bashrc.sh')
            child = pexpect.spawn("bash", ['--rcfile', bashrc],
                                  echo=False,
                                  encoding='utf-8',
                                  codec_errors='replace')
            ps1 = replwrap.PEXPECT_PROMPT[:
                                          5] + u'\[\]' + replwrap.PEXPECT_PROMPT[
                                              5:]
            ps2 = replwrap.PEXPECT_CONTINUATION_PROMPT[:
                                                       5] + u'\[\]' + replwrap.PEXPECT_CONTINUATION_PROMPT[
                                                           5:]
            prompt_change = u"PS1='{0}' PS2='{1}' PROMPT_COMMAND=''".format(
                ps1, ps2)

            # Using IREPLWrapper to get incremental output
            self.bashwrapper = IREPLWrapper(
                child,
                u'\$',
                prompt_change,
                extra_init_cmd="export PAGER=cat",
                line_output_callback=self.process_output)
        finally:
            signal.signal(signal.SIGINT, sig)

        # Register Bash function to write image data to temporary file
        self.bashwrapper.run_command(image_setup_cmd)
        self.ipy_shell.user_ns['bash'] = self.bashwrapper
        self.ipy_shell.user_ns['outputs'] = "\n"

    def process_output(self, output):
        if not self.silent:
            image_filenames, output = extract_image_filenames(output)

            # Send standard output
            stream_content = {'name': 'stdout', 'text': output}
            self.send_response(self.iopub_socket, 'stream', stream_content)

            # Save output of the last cell
            self.ipy_shell.user_ns['outputs'] += output

            # Send images, if any
            for filename in image_filenames:
                try:
                    data = display_data_for_image(filename)
                except ValueError as e:
                    message = {'name': 'stdout', 'text': str(e)}
                    self.send_response(self.iopub_socket, 'stream', message)
                else:
                    self.send_response(self.iopub_socket, 'display_data', data)

    def do_execute(self,
                   code,
                   silent,
                   store_history=True,
                   user_expressions=None,
                   allow_stdin=False):

        if code.startswith('#DC_GRADE'):
            return self._execute_python(code.lstrip('#DC_GRADE'))
        else:
            return self._execute_bash(code, silent, store_history,
                                      user_expressions, allow_stdin)

    def do_complete(self, code, cursor_pos):
        code = code[:cursor_pos]
        default = {
            'matches': [],
            'cursor_start': 0,
            'cursor_end': cursor_pos,
            'metadata': dict(),
            'status': 'ok'
        }

        if not code or code[-1] == ' ':
            return default

        tokens = code.replace(';', ' ').split()
        if not tokens:
            return default

        matches = []
        token = tokens[-1]
        start = cursor_pos - len(token)

        if token[0] == '$':
            # complete variables
            cmd = 'compgen -A arrayvar -A export -A variable %s' % token[
                1:]  # strip leading $
            output = self.bashwrapper.run_command(cmd).rstrip()
            completions = set(output.split())
            # append matches including leading $
            matches.extend(['$' + c for c in completions])
        else:
            # complete functions and builtins
            cmd = 'compgen -cdfa %s' % token
            output = self.bashwrapper.run_command(cmd).rstrip()
            matches.extend(output.split())

        if not matches:
            return default
        matches = [m for m in matches if m.startswith(token)]

        return {
            'matches': sorted(matches),
            'cursor_start': start,
            'cursor_end': cursor_pos,
            'metadata': dict(),
            'status': 'ok'
        }

    def _execute_bash(self,
                      code,
                      silent,
                      store_history=True,
                      user_expressions=None,
                      allow_stdin=False):
        self.silent = silent
        if not code.strip():
            return {
                'status': 'ok',
                'execution_count': self.execution_count,
                'payload': [],
                'user_expressions': {}
            }

        interrupted = False
        try:
            # Note: timeout=None tells IREPLWrapper to do incremental
            # output.  Also note that the return value from
            # run_command is not needed, because the output was
            # already sent by IREPLWrapper.
            self.bashwrapper.run_command(code.rstrip(), timeout=None)
        except KeyboardInterrupt:
            self.bashwrapper.child.sendintr()
            interrupted = True
            self.bashwrapper._expect_prompt()
            output = self.bashwrapper.child.before
            self.process_output(output)
        except EOF:
            output = self.bashwrapper.child.before + 'Restarting Bash'
            self._start_bash()
            self.process_output(output)

        if interrupted:
            return {'status': 'abort', 'execution_count': self.execution_count}

        try:
            exitcode = int(self.bashwrapper.run_command('echo $?').rstrip())
        except Exception:
            exitcode = 1

        if exitcode:
            error_content = {
                'execution_count': self.execution_count,
                'ename': '',
                'evalue': str(exitcode),
                'traceback': []
            }

            self.send_response(self.iopub_socket, 'error', error_content)
            error_content['status'] = 'error'
            return error_content
        else:
            return {
                'status': 'ok',
                'execution_count': self.execution_count,
                'payload': [],
                'user_expressions': {}
            }

    def _execute_python(self, code):
        # run cell
        try:
            res = self.ipy_shell.run_cell(code)
            res.raise_error()
            message = {'name': 'stdout', 'text': str(res.result)}
        except Exception as e:
            message = {'name': 'stdout', 'text': repr(e)}

        self.send_response(self.iopub_socket, 'stream', message)

        # if ran nose test, send summary of results
        if hasattr(res.result, '_summarize'):
            _msg = {'name': 'stdout', 'text': '\n\n' + res.result._summarize()}
            self.send_response(self.iopub_socket, 'stream', _msg)

            self.send_response(self.iopub_socket, 'display_data', {
                'data': res.result.json_output,
                'metadata': {}
            })

        return {
            'status': 'ok',
            'execution_count': self.execution_count,
            'payload': [],
            'user_expressions': {}
        }
コード例 #11
0
class LabNotebook():
    def __init__(self, path):
        self.path = path
        self.nb = nbformat.read(path, 4)
        self._code_answers = None

        self.mod = types.ModuleType(self.path)
        self.mod.__file__ = self.path
        self.mod.__loader__ = self
        self.mod.__dict__['get_ipython'] = get_ipython

        self.shell = InteractiveShell(user_module=self.mod).instance()
        self.shell.prepare_user_module(self.mod)

        # For saving the last line outputof iPython cells
        self.cell_outs = dict()

    def code_cells(self):
        for cell in self.nb.cells:
            if cell.cell_type != 'markdown':
                yield cell

    def code_answers(self):
        if self._code_answers is None:
            code_answers = {}
            for cell in self.code_cells():
                matches = re.findall('Answer-(Q[\d\w]+)',
                                     cell.source,
                                     flags=re.IGNORECASE)
                if len(matches) > 0:
                    q = matches[0].lower()
                    try:
                        assert len(matches) == 1
                        assert q not in code_answers
                    except:
                        logging.warn(
                            "{} cell shows up more that once".format(q))
                    code_answers[q] = self.strip_comments(cell.source)
            self._code_answers = code_answers
        return self._code_answers

    def strip_comments(self, source):
        return re.sub("^ *#.*$", '', source, flags=re.MULTILINE)

    def run_as_module(self):

        #sys.modules[fullname] = mod

        # extra work to ensure that magics that would affect the user_ns
        # actually affect the notebook module's ns
        ns = self.save_ns()
        try:
            for cell in self.code_cells():
                self.run_string_cell_in_module(cell.source, save_ns=False)
        finally:
            # If anything crashes, need to fix the namespace
            self.restore_ns(ns)
        return self.mod

    def run_string_cell_in_module(self,
                                  source,
                                  name=None,
                                  save_ns=True,
                                  silent=False):
        if name is None:
            matches = re.findall('Answer-(Q[\d\w]+)',
                                 source,
                                 flags=re.IGNORECASE)
            if len(matches) > 0:
                name = matches[0].lower() + '_resolved'

        if save_ns:
            ns = self.save_ns()
        try:
            if 'grade check' in source.lower():
                return
            comments_stripped = self.strip_comments(source)
            #transformed = self.shell.input_transformer_manager.transform_cell(comments_stripped)
            out = self.shell.run_cell(comments_stripped, silent=silent)

            #exec(transformed, self.mod.__dict__)
            if name:
                self.mod.__dict__[name] = out.result

        finally:
            pass
            if save_ns:
                self.restore_ns(ns)
        #return out

    def get_var_answer(self, q):
        ans, err = None, None
        qname = q + '_answer'
        if not hasattr(self.mod, qname):
            err = "Can't find the answer"

        try:
            ans = getattr(self.mod, qname)
        except:
            return None, "Can't find answer"
        if type(ans) is str:
            ans = ans.strip()
            if ans == "":
                err = "Answer is empty."

        return ans, err

    def get_cell_answer(self, q):
        ans, err = None, None
        if q not in self.code_answers():
            err = "Can't find the cell with the answer. Did you include # Answer-{} at the top?".format(
                q.upper())
        else:
            ans = self.code_answers()[q]
        return ans, err

    def run_method(self, method, **kwargs):
        return method(**kwargs)

    def save_ns(self):
        save_user_ns = self.shell.user_ns
        self.shell.user_ns = self.mod.__dict__
        return save_user_ns

    def restore_ns(self, save_user_ns):
        self.shell.user_ns = save_user_ns

    def _handgrade(self,
                   q,
                   ans,
                   max_pts,
                   last_pts=None,
                   last_comment=None,
                   debug=False,
                   question_text=None):
        if debug:
            return max_pts, "DEBUGGING"
        clear_output()
        print(q.center(100, '-'))
        if question_text:
            print("Q:", question_text, "\n--")
        print(ans)

        while True:
            time.sleep(.5)
            msg = "How many points, out of {}? (default is {}, can subtract from max with -x)".format(
                max_pts, "max" if last_pts is None else last_pts)
            pts = input(msg)
            print(pts)
            if pts.strip() == "":
                pts = "max" if last_pts is None else last_pts
            if pts in ["max"] or str(pts).strip('-').replace('.',
                                                             '').isdigit():
                break
            else:
                time.sleep(1)
                last_comment = pts  # Assume an accidental comment
                print('Looks like a bad input for pts, try again.')

        msg = "Any comments?"
        if last_comment:
            msg += "PREVIOUS COMMENT (will stay if empty, use single hyphen to wipe):" + last_comment
        time.sleep(.5)
        comments = input(msg)
        time.sleep(.5)
        comments = comments.strip()
        if comments == "" and last_comment:
            comments = last_comment
        elif comments == "-":
            comments = ""
        return pts, comments

    def autograde_var_answer(self, q, ans, correct_answers, filters=[]):
        pts, comments = None, ""
        filtered_ans = self.run_filters(ans, filters)
        comment_for_answer = ""

        for correct in correct_answers:
            if type(correct) is tuple:
                if len(correct) == 2:
                    correct, pts_for_answer = correct
                elif len(correct) == 3:
                    correct, pts_for_answer, comment_for_answer = correct
                else:
                    raise Exception("unexpected length for tuple answer")
            else:
                pts_for_answer = "max"

            correct = self.run_filters(correct, filters)
            if filtered_ans == correct:
                pts = pts_for_answer
                comments = comment_for_answer
                break
            pts = 0

        if pts == 0:
            comments += "Your answer: {}".format(ans)
            comments += "\nExample of correct answer is:\n{}".format(
                correct_answers[0])

        return pts, comments

    def run_filters(self, ans, filters):
        for filter in filters:
            if callable(filter):
                ans = filter(ans)
            elif hasattr(Filters, filter):
                ans = getattr(Filters, filter)(ans)
            else:
                print(filter)
                raise Exception("unknown filter")
        return ans

    def grade_answer(self, q, params, prev_grade=None, debug=False):
        if prev_grade is None:
            grade = dict(pts=None,
                         max_pts=params['pts'],
                         comments="",
                         question=None)
        else:
            # Regrading
            grade = prev_grade
        if 'question' in params:
            grade['question'] = params['question']

        if params['entrytype'] == 'cell':
            ans, err = self.get_cell_answer(q)
        elif params['entrytype'] == 'var':
            ans, err = self.get_var_answer(q)
        if err:
            grade['comments'] += err
            grade['pts'] = 0
            return grade

        if prev_grade:
            # Forces auto-grading
            pts, comments = self._handgrade(q,
                                            ans,
                                            grade['max_pts'],
                                            last_pts=grade['pts'],
                                            last_comment=grade['comments'],
                                            debug=debug,
                                            question_text=grade['question'])

        elif params['auto'] & (params['entrytype'] == 'var'):
            try:
                filters = params['filters'] if 'filters' in params else []
                pts, comments = self.autograde_var_answer(q,
                                                          ans,
                                                          params['answers'],
                                                          filters=filters)
            except:
                #if debug:
                #    raise
                pts = 0
                comments = ""
        elif params['auto'] & (params['entrytype'] == 'cell'):
            try:
                pts, comments = self.autograde_cell_answer(
                    q, ans, params['answers'])
            except:
                #if debug:
                #    raise
                pts = 0
                comments = ""
        elif not params['auto']:
            pts, comments = self._handgrade(q,
                                            ans,
                                            grade['max_pts'],
                                            debug=debug,
                                            question_text=grade['question'])
        if pts == 'max':
            pts = grade['max_pts']
        else:
            pts = float(pts)
            if pts < 0:
                pts = grade['max_pts'] + pts

        grade['pts'] = pts
        grade['comments'] = comments

        return grade

    def autograde_cell_answer(self, q, ans, answer_func):
        pts, comments = self._grade_cell_answer_class(ans, answer_func, q=q)
        if pts is None:
            pts = 'max'
        if comments is None:
            comments = ""
        return pts, comments

    def _grade_cell_answer_class(self, answer, answer_class, q=None):
        checker = answer_class(q)
        self.run_method(checker.prep)
        self.run_string_cell_in_module(answer)
        pts, errs = self.run_method(checker.check, ns=self.mod)
        return pts, errs

    def grade_all(self,
                  answerkey,
                  with_hand_doublecheck=True,
                  hand_regrade=False,
                  debug=False):
        grades = dict()
        try:
            for q, params in answerkey.items():
                grade = self.grade_answer(q, params, debug=debug)
                grades[q] = grade

            if hand_regrade and not debug:
                ''' Regrade wrong answers manually'''
                new_grades = dict()
                for q, params in answerkey.items():
                    grade = grades[q]
                    if ((grade['pts'] == 0) or
                        (grade['pts'] is None)) and (params['auto']):
                        grade = self.grade_answer(q, params, prev_grade=grade)
                    new_grades[q] = grade
                return new_grades
            else:
                return grades
        except:
            raise
            # return incomplete grades
            return grades