Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Fixes #2306: Add a restart method #2312

Merged
merged 5 commits into from
Apr 21, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions bootstrap.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@

options, args = parser.parse_args()

# Store variable to be used in self.restart (restart spyder instance)
os.environ['SPYDER_BOOTSTRAP_ARGS'] = str(sys.argv[1:])

assert options.gui in (None, 'pyqt5', 'pyqt', 'pyside'), \
"Invalid GUI toolkit option '%s'" % options.gui

Expand Down
1 change: 1 addition & 0 deletions spyderlib/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -477,6 +477,7 @@ def is_ubuntu():
'_/save current layout': "Shift+Alt+S",
'_/toggle default layout': "Shift+Alt+Home",
'_/layout preferences': "Shift+Alt+P",
'_/restart': "Shift+Alt+R",
'_/quit': "Ctrl+Q",
# -- In plugins/editor
'_/debug step over': "Ctrl+F10",
Expand Down
145 changes: 145 additions & 0 deletions spyderlib/restart_app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# Copyright © 2015 The Spyder Development Team
# Licensed under the terms of the MIT License
# (see spyderlib/__init__.py for details)

"""
Restart Spyder

A helper script that allows Spyder to restart from within the application.
"""

import ast
import os
import os.path as osp
import subprocess
import sys
import time


PY2 = sys.version[0] == '2'


def _is_pid_running_on_windows(pid):
"""Check if a process is running on windows systems based on the pid."""
pid = str(pid)

# Hide flashing command prompt
startupinfo = subprocess.STARTUPINFO()
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW

process = subprocess.Popen(r'tasklist /fi "PID eq {0}"'.format(pid),
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
startupinfo=startupinfo)
stdoutdata, stderrdata = process.communicate()
stdoutdata = to_text_string(stdoutdata)
process.kill()
check = pid in stdoutdata

return check


def _is_pid_running_on_unix(pid):
"""Check if a process is running on unix systems based on the pid."""
try:
# On unix systems os.kill with a 0 as second argument only pokes the
# process (if it exists) and does not kill it
os.kill(pid, 0)
except OSError:
return False
else:
return True


def is_pid_running(pid):
"""Check if a process is running based on the pid."""
# Select the correct function depending on the OS
if os.name == 'nt':
return _is_pid_running_on_windows(pid)
else:
return _is_pid_running_on_unix(pid)


def to_text_string(obj, encoding=None):
"""Convert `obj` to (unicode) text string"""
if PY2:
# Python 2
if encoding is None:
return unicode(obj)
else:
return unicode(obj, encoding)
else:
# Python 3
if encoding is None:
return str(obj)
elif isinstance(obj, str):
# In case this function is not used properly, this could happen
return obj
else:
return str(obj, encoding)


def main():
# Note: Variables defined in spyderlib\spyder.py 'restart()' method
spyder_args = os.environ.pop('SPYDER_ARGS', None)
pid = os.environ.pop('SPYDER_PID', None)
is_bootstrap = os.environ.pop('SPYDER_IS_BOOTSTRAP', None)

# Get the spyder base folder based on this file
spyder_folder = osp.split(osp.dirname(osp.abspath(__file__)))[0]

if any([not spyder_args, not pid, not is_bootstrap]):
error = "This script can only be called from within a Spyder instance"
raise RuntimeError(error)

# Variables were stored as string literals in the environment, so to use
# them we need to parse them in a safe manner.
is_bootstrap = ast.literal_eval(is_bootstrap)
pid = int(pid)
args = ast.literal_eval(spyder_args)

# Enforce the --new-instance flag when running spyder
if '--new-instance' not in args:
if is_bootstrap and not '--' in args:
args = args + ['--', '--new-instance']
else:
args.append('--new-instance')

# Arrange arguments to be passed to the restarter subprocess
args = ' '.join(args)

# Get python excutable running this script
python = sys.executable

# Build the command
if is_bootstrap:
spyder = osp.join(spyder_folder, 'bootstrap.py')
else:
spyderlib = osp.join(spyder_folder, 'spyderlib')
spyder = osp.join(spyderlib, 'start_app.py')

command = '"{0}" "{1}" {2}'.format(python, spyder, args)

# Adjust the command and/or arguments to subprocess depending on the OS
shell = os.name != 'nt'

# Wait for original process to end before launching the new instance
while True:
if not is_pid_running(pid):
break
time.sleep(0.2) # Throttling control

env = os.environ.copy()
try:
subprocess.Popen(command, shell=shell, env=env)
except Exception as error:
print(command)
print(error)
time.sleep(15)


if __name__ == '__main__':
main()
65 changes: 63 additions & 2 deletions spyderlib/spyder.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
#
# Copyright © 2009-2013 Pierre Raybaut
# Copyright © 2013-2015 The Spyder Development Team
# Licensed under the terms of the MIT License
# (see spyderlib/__init__.py for details)

Expand Down Expand Up @@ -30,6 +31,7 @@
import re
import socket
import shutil
import subprocess
import sys
import threading

Expand Down Expand Up @@ -118,7 +120,7 @@
from spyderlib.baseconfig import (get_conf_path, get_module_data_path,
get_module_source_path, STDERR, DEBUG, DEV,
debug_print, TEST, SUBFOLDER, MAC_APP_NAME,
running_in_mac_app)
running_in_mac_app, get_module_path)
from spyderlib.config import CONF, EDIT_EXT, IMPORT_EXT, OPEN_FILES_PORT
from spyderlib.cli_options import get_options
from spyderlib import dependencies
Expand Down Expand Up @@ -853,10 +855,16 @@ def create_edit_action(text, tr_text, icon_name):
icon='exit.png', tip=_("Quit"),
triggered=self.console.quit)
self.register_shortcut(quit_action, "_", "Quit")
restart_action = create_action(self, _("&Restart"),
icon='restart.png',
tip=_("Restart"),
triggered=self.restart)
self.register_shortcut(restart_action, "_", "Restart")

self.file_menu_actions += [self.load_temp_session_action,
self.load_session_action,
self.save_session_action,
None, quit_action]
None, restart_action, quit_action]
self.set_splash("")

self.debug_print(" ..widgets")
Expand Down Expand Up @@ -2653,6 +2661,59 @@ def start_open_files_server(self):
self.sig_open_external_file.emit(fname)
req.sendall(b' ')

# ---- Quit and restart
def restart(self):
"""Quit and Restart Spyder application"""
# Get start path to use in restart script
spyder_start_directory = get_module_path('spyderlib')
restart_script = osp.join(spyder_start_directory, 'restart_app.py')

# Get any initial argument passed when spyder was started
# Note: Variables defined in bootstrap.py and spyderlib\start_app.py
env = os.environ.copy()
bootstrap_args = env.pop('SPYDER_BOOTSTRAP_ARGS', None)
Copy link
Member

Choose a reason for hiding this comment

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

Why using pop here instead of get with a default value?

Copy link
Contributor

Choose a reason for hiding this comment

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

To clean the environment ?

Copy link
Member Author

Choose a reason for hiding this comment

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

Thanks mind reader++,

@ccordoba12 since I am passing the env to the new instance to be open, I want to clean it ...

Copy link
Member

Choose a reason for hiding this comment

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

Ok

spyder_args = env.pop('SPYDER_ARGS')

# Get current process and python running spyder
pid = os.getpid()
python = sys.executable

# Check if started with bootstrap.py
if bootstrap_args is not None:
spyder_args = bootstrap_args
is_bootstrap = True
else:
is_bootstrap = False

# Pass variables as environment variables (str) to restarter subprocess
env['SPYDER_ARGS'] = spyder_args
env['SPYDER_PID'] = str(pid)
env['SPYDER_IS_BOOTSTRAP'] = str(is_bootstrap)

# Build the command and popen arguments depending on the OS
if os.name == 'nt':
# Hide flashing command prompt
startupinfo = subprocess.STARTUPINFO()
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
Copy link
Member

Choose a reason for hiding this comment

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

What does this line do?

Copy link
Member Author

Choose a reason for hiding this comment

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

In windows it should hide the cmd prompt from flashing

Copy link
Member

Choose a reason for hiding this comment

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

Ok

Copy link
Member Author

Choose a reason for hiding this comment

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

I will add a comment

Copy link
Member

Choose a reason for hiding this comment

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

Please add a comment about this subprocess flag here :-)

Copy link
Member Author

Choose a reason for hiding this comment

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

Will do

shell = False
else:
startupinfo = None
shell = True

command = '"{0}" "{1}"'
command = command.format(python, restart_script)

try:
if self.closing(True):
Copy link
Member

Choose a reason for hiding this comment

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

I thought that self.closing closed the app, but I was mistaken. However, thanks for doing this change :-)

subprocess.Popen(command, shell=shell, env=env,
startupinfo=startupinfo)
self.console.quit()
except Exception as error:
# If there is an error with subprocess, Spyder should not quit and
# the error can be inspected in the internal console
print(error)
print(command)
Copy link
Member

Choose a reason for hiding this comment

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

Where do these print's are going to print?

Copy link
Member Author

Choose a reason for hiding this comment

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

hmmm good point :p, do we have an error logger for Spyder? where these things ca be written in case it shuts down?

Copy link
Member Author

Choose a reason for hiding this comment

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

Hmm if there is an error, then I guess it would be in the subprocess... in which case it would not restart nor quit... so internal console is my bet?


# ---- Interactive Tours
def show_tour(self, index):
""" """
Expand Down
4 changes: 4 additions & 0 deletions spyderlib/start_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import os.path as osp
import random
import socket
import sys
import time

# Local imports
Expand Down Expand Up @@ -63,6 +64,9 @@ def main():
# Parse command line options
options, args = get_options()

# Store variable to be used in self.restart (restart spyder instance)
os.environ['SPYDER_ARGS'] = str(sys.argv[1:])

if CONF.get('main', 'single_instance') and not options.new_instance \
and not running_in_mac_app():
# Minimal delay (0.1-0.2 secs) to avoid that several
Expand Down