Skip to content

Commit

Permalink
Merge from 5.x: PR #16409
Browse files Browse the repository at this point in the history
Fixes #17085
Fixes #17083
Fixes #17060
Fixes #12829
  • Loading branch information
ccordoba12 committed Mar 17, 2022
2 parents e948501 + 5eec5ab commit 6cd9cca
Show file tree
Hide file tree
Showing 73 changed files with 818 additions and 411 deletions.
13 changes: 5 additions & 8 deletions .github/scripts/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ if [ "$OS" = "macos" ]; then
PATH=/Users/runner/miniconda3/envs/test/bin:/Users/runner/miniconda3/condabin:$PATH
fi

# Install gdb
if [ "$USE_GDB" = "true" ]; then
mamba install gdb -c conda-forge -q -y
fi

# Install dependencies
if [ "$USE_CONDA" = "true" ]; then

Expand All @@ -14,11 +19,6 @@ if [ "$USE_CONDA" = "true" ]; then
# Install test ones
mamba install python=$PYTHON_VERSION --file requirements/tests.txt -c conda-forge -q -y

# Install Pyzmq 19 because our tests are failing with version 20
if [ "$OS" = "win" ]; then
mamba install pyzmq=19
fi

# To check our manifest and coverage
mamba install check-manifest codecov -c conda-forge -q -y

Expand All @@ -33,9 +33,6 @@ else
# Install Spyder and its dependencies from our setup.py
pip install -e .[test]

# Remove pytest-xvfb because it causes hangs
pip uninstall -q -y pytest-xvfb

# Install qtpy from Github
pip install git+https://github.com/spyder-ide/qtpy.git

Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/test-files.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ jobs:
strategy:
fail-fast: false
matrix:
PYTHON_VERSION: ['3.7']
PYTHON_VERSION: ['3.7', '3.9']
timeout-minutes: 30
steps:
- name: Checkout Pull Requests
Expand Down
15 changes: 12 additions & 3 deletions .github/workflows/test-linux.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,12 @@ jobs:
PYTHON_VERSION: ${{ matrix.PYTHON_VERSION }}
RUN_SLOW: ${{ matrix.TEST_TYPE == 'slow' }}
USE_CONDA: ${{ matrix.INSTALL_TYPE == 'conda' }}
USE_GDB: 'false'
strategy:
fail-fast: false
matrix:
INSTALL_TYPE: ['conda', 'pip']
PYTHON_VERSION: ['3.7']
INSTALL_TYPE: ['pip', 'conda']
PYTHON_VERSION: ['3.7', '3.9']
TEST_TYPE: ['fast', 'slow']
timeout-minutes: 120
steps:
Expand All @@ -71,7 +72,7 @@ jobs:
shell: bash
run: |
sudo apt-get update --fix-missing
sudo apt-get install libxcb-randr0-dev libxcb-xtest0-dev libxcb-xinerama0-dev libxcb-shape0-dev libxcb-xkb-dev libegl1-mesa libxkbcommon-x11-0 xterm --fix-missing
sudo apt-get install -qq pyqt5-dev-tools libxcb-xinerama0 xterm --fix-missing
- name: Cache conda
uses: actions/cache@v2
env:
Expand Down Expand Up @@ -108,10 +109,18 @@ jobs:
run: |
conda info
conda list
#- name: Setup tmate session
# if: env.RUN_BUILD == 'true'
# uses: mxschmitt/action-tmate@v3
# timeout-minutes: 30
- name: Run manifest checks
if: env.RUN_BUILD == 'true'
shell: bash -l {0}
run: check-manifest
- name: Run tests with gdb
if: env.USE_GDB == 'true'
shell: bash -l {0}
run: xvfb-run --auto-servernum gdb -return-child-result -batch -ex r -ex py-bt --args python runtests.py -s
- name: Run tests
if: env.RUN_BUILD == 'true'
shell: bash -l {0}
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/test-mac.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ jobs:
strategy:
fail-fast: false
matrix:
INSTALL_TYPE: ['conda']
INSTALL_TYPE: ['pip']
PYTHON_VERSION: ['3.7']
TEST_TYPE: ['fast', 'slow']
timeout-minutes: 120
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/test-win.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,8 @@ jobs:
strategy:
fail-fast: false
matrix:
INSTALL_TYPE: ['conda']
PYTHON_VERSION: ['3.7']
INSTALL_TYPE: ['pip', 'conda']
PYTHON_VERSION: ['3.7', '3.9']
TEST_TYPE: ['fast', 'slow']
timeout-minutes: 120
steps:
Expand Down
3 changes: 1 addition & 2 deletions binder/environment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ dependencies:
- pygments >=2.0
- pylint >=2.5.0
- pyls-spyder >=0.4.0
- pyqt <5.13
- pyqt <5.16
- python-lsp-black >=1.0.0
- python-lsp-server >=1.3.2,<1.4.0
- pyxdg >=0.26
Expand Down Expand Up @@ -63,7 +63,6 @@ dependencies:
- pytest-mock
- pytest-order
- pytest-qt
- pytest-xvfb
- pyyaml
- scipy
- sympy
Expand Down
2 changes: 1 addition & 1 deletion installers/macOS/test_app.sh
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,6 @@ fi
kill -s SIGKILL $$
) 2> /dev/null &

export SPYDER_DEBUG=2
export SPYDER_DEBUG=3
export INSTALLER_TEST=1
exec $@/Spyder.app/Contents/MacOS/Spyder
1 change: 1 addition & 0 deletions pytest.ini
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,4 @@ markers =
test_environment_interpreter
tk_backend: Test the Tkinter Matplotlib backend
known_leak: Known thread or open file leaks
no_new_console: Prevent creating a new IPython console when reusing a mainwindow instance
2 changes: 1 addition & 1 deletion requirements/conda.txt
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ psutil >=5.3
pygments >=2.0
pylint >=2.5.0
pyls-spyder >=0.4.0
pyqt <5.13
pyqt <5.16
python-lsp-black >=1.0.0
python-lsp-server >=1.3.2,<1.4.0
pyxdg >=0.26
Expand Down
5 changes: 2 additions & 3 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -226,8 +226,8 @@ def run(self):
'pylint>=2.5.0',
'python-lsp-black>=1.0.0',
'pyls-spyder>=0.4.0',
'pyqt5<5.13',
'pyqtwebengine<5.13',
'pyqt5<5.16',
'pyqtwebengine<5.16',
'python-lsp-server[all]>=1.3.2,<1.4.0',
'pyxdg>=0.26;platform_system=="Linux"',
'pyzmq>=17',
Expand All @@ -252,7 +252,6 @@ def run(self):
install_requires.append('spyder-kernels>=2.2.1,<=2.3.0.dev0')

extras_require = {
'test:platform_system == "Linux"': ['pytest-xvfb'],
'test:platform_system == "Windows"': ['pywin32'],
'test': [
'coverage',
Expand Down
2 changes: 1 addition & 1 deletion spyder/api/_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@

"""Spyder API Version."""

VERSION_INFO = (0, 5, 0)
VERSION_INFO = (0, 6, 0)
__version__ = '.'.join(map(str, VERSION_INFO))
91 changes: 79 additions & 12 deletions spyder/api/plugin_registration/registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -389,23 +389,31 @@ def notify_plugin_availability(self, plugin_name: str,
plugin_instance = self.plugin_registry[plugin_name]
plugin_instance.register_plugin_preferences(self)

def delete_plugin(self, plugin_name: str) -> bool:
def delete_plugin(self, plugin_name: str, teardown: bool = True) -> bool:
"""
Remove and delete a plugin from the registry by its name.
Paremeters
----------
plugin_name: str
Name of the plugin to delete.
teardown: bool
True if the teardown notification to other plugins should be sent
when deleting the plugin, False otherwise.
Returns
-------
plugin_deleted: bool
True if the registry was able to teardown and remove the plugin.
False otherwise.
"""
logger.debug(f'Deleting plugin {plugin_name}')
plugin_instance = self.plugin_registry[plugin_name]

# Remove config
if plugin_instance.CONF_FILE:
CONF.unregister_plugin(plugin_instance)

# Determine if plugin can be closed
can_delete = True
if isinstance(plugin_instance, SpyderPluginV2):
Expand All @@ -417,11 +425,12 @@ def delete_plugin(self, plugin_name: str) -> bool:
return False

if isinstance(plugin_instance, SpyderPluginV2):
# Disconnect plugin from other plugins
self._teardown_plugin(plugin_name)
if teardown:
# Disconnect plugin from other plugins
self._teardown_plugin(plugin_name)

# Disconnect depending plugins from the plugin to delete
self._notify_plugin_teardown(plugin_name)
# Disconnect depending plugins from the plugin to delete
self._notify_plugin_teardown(plugin_name)

# Remove the plugin from the main window (if graphical)
if isinstance(plugin_instance, SpyderDockablePlugin):
Expand All @@ -435,10 +444,14 @@ def delete_plugin(self, plugin_name: str) -> bool:
plugin_instance.close_window()

# Perform plugin closure tasks
plugin_instance.on_close(True)
try:
plugin_instance.on_close(True)
except RuntimeError:
pass
elif isinstance(plugin_instance, SpyderPlugin):
# Disconnect depending plugins from the plugin to delete
self._notify_plugin_teardown(plugin_name)
if teardown:
# Disconnect depending plugins from the plugin to delete
self._notify_plugin_teardown(plugin_name)
if isinstance(plugin_instance, SpyderPluginWidget):
# Save if plugin was undocked to restore it the next time.
if plugin_instance._undocked_window:
Expand Down Expand Up @@ -477,6 +490,7 @@ def delete_plugin(self, plugin_name: str) -> bool:

# Remove the plugin from the registry
self.plugin_registry.pop(plugin_name)

return True

def delete_all_plugins(self, excluding: Optional[Set[str]] = None,
Expand Down Expand Up @@ -508,7 +522,8 @@ def delete_all_plugins(self, excluding: Optional[Set[str]] = None,
if plugin_name not in excluding:
plugin_instance = self.plugin_registry[plugin_name]
if isinstance(plugin_instance, SpyderPlugin):
can_close &= self.delete_plugin(plugin_name)
can_close &= self.delete_plugin(
plugin_name, teardown=False)
if not can_close and not close_immediately:
break

Expand All @@ -520,7 +535,30 @@ def delete_all_plugins(self, excluding: Optional[Set[str]] = None,
if plugin_name not in excluding:
plugin_instance = self.plugin_registry[plugin_name]
if isinstance(plugin_instance, SpyderPluginV2):
can_close &= self.delete_plugin(plugin_name)
# Cleanly delete plugin widgets. This avoids segfautls with
# PyQt 5.15
if isinstance(plugin_instance, SpyderDockablePlugin):
try:
plugin_instance.get_widget().close()
plugin_instance.get_widget().deleteLater()
except RuntimeError:
pass
else:
container = plugin_instance.get_container()
if container:
try:
container.close()
container.deleteLater()
except RuntimeError:
pass

# Delete plugin
try:
plugin_instance.deleteLater()
except RuntimeError:
pass
can_close &= self.delete_plugin(
plugin_name, teardown=False)
if not can_close and not close_immediately:
break

Expand All @@ -532,7 +570,13 @@ def delete_all_plugins(self, excluding: Optional[Set[str]] = None,
if plugin_name not in excluding:
plugin_instance = self.plugin_registry[plugin_name]
if isinstance(plugin_instance, SpyderPlugin):
can_close &= self.delete_plugin(plugin_name)
try:
plugin_instance.close()
plugin_instance.deleteLater()
except RuntimeError:
pass
can_close &= self.delete_plugin(
plugin_name, teardown=False)
if not can_close and not close_immediately:
break

Expand All @@ -543,7 +587,30 @@ def delete_all_plugins(self, excluding: Optional[Set[str]] = None,
if plugin_name not in excluding:
plugin_instance = self.plugin_registry[plugin_name]
if isinstance(plugin_instance, SpyderPluginV2):
can_close &= self.delete_plugin(plugin_name)
# Cleanly delete plugin widgets. This avoids segfautls with
# PyQt 5.15
if isinstance(plugin_instance, SpyderDockablePlugin):
try:
plugin_instance.get_widget().close()
plugin_instance.get_widget().deleteLater()
except RuntimeError:
pass
else:
container = plugin_instance.get_container()
if container:
try:
container.close()
container.deleteLater()
except RuntimeError:
pass

# Delete plugin
try:
plugin_instance.deleteLater()
except RuntimeError:
pass
can_close &= self.delete_plugin(
plugin_name, teardown=False)
if not can_close and not close_immediately:
break

Expand Down
11 changes: 11 additions & 0 deletions spyder/api/widgets/main_widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -432,6 +432,10 @@ def setLayout(self, layout):
layout.setContentsMargins(self._margin_left, 0, self._margin_right, 0)
layout.setSpacing(0)

def closeEvent(self, event):
self.on_close()
super().closeEvent(event)

# --- Public methods to use
# ------------------------------------------------------------------------
def get_plugin(self):
Expand Down Expand Up @@ -894,6 +898,13 @@ def update_actions(self):
'A PluginMainWidget subclass must define an `update_actions` '
f'method! Hint: {type(self)} should implement `update_actions`')

def on_close(self):
"""
Perform actions before the widget is closed.
This method **must** only operate on local attributes.
"""
pass

def run_test():
# Third party imports
Expand Down
4 changes: 4 additions & 0 deletions spyder/api/widgets/status.py
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,10 @@ def __init__(self, parent=None):
self.timer.timeout.connect(self.update_status)
self.timer.start(self._interval)

def closeEvent(self, event):
self.timer.stop()
super().closeEvent(event)

# ---- Qt API
def setVisible(self, value):
"""Override Qt method to stops timers if widget is not visible."""
Expand Down
2 changes: 2 additions & 0 deletions spyder/api/widgets/toolbars.py
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,7 @@ def __init__(self, parent, title):

self._style = ToolbarStyle(None)
self._style.TYPE = 'Application'
self._style.setParent(self)
self.setStyle(self._style)

self.setStyleSheet(str(APP_TOOLBAR_STYLESHEET))
Expand Down Expand Up @@ -308,6 +309,7 @@ def __init__(self, parent=None, title=None):

self._style = ToolbarStyle(None)
self._style.TYPE = 'MainWidget'
self._style.setParent(self)
self.setStyle(self._style)

self.setStyleSheet(str(PANES_TOOLBAR_STYLESHEET))
Expand Down
Loading

0 comments on commit 6cd9cca

Please sign in to comment.