Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
bdb620b
Require at least Spyder 6.1
jitseniesen Aug 11, 2025
8df71c6
Declare that plugin uses Qt Web Widgets
jitseniesen Aug 12, 2025
5073968
Use new plugin API for creating new notebooks
jitseniesen Oct 26, 2024
d0cfa01
Use new plugin API for opening files in plugin
jitseniesen Sep 24, 2024
d6737f4
Use new plugin API to hook into "Open last closed" action
jitseniesen Nov 10, 2024
c7e5c53
Use new plugin API to hook into `File > Open recent`
jitseniesen Nov 14, 2024
4482fed
Use new plugin API to hook into "Save file" action
jitseniesen Nov 26, 2024
e45fb1c
Use new plugin API to hook into "Save all" action
jitseniesen Dec 14, 2024
f9e8d66
Use new plugin API to hook into "Save as" action
jitseniesen Dec 14, 2024
f5c5582
Use new plugin API to hook into "Save copy as" action
jitseniesen Dec 19, 2024
fd3d050
Use new plugin API to disable "Revert file"
jitseniesen Dec 23, 2024
47bb382
Use new plugin API to hook into "Close file" action
jitseniesen Dec 26, 2024
02dcf3f
Fix test and remove unused config
jitseniesen Aug 16, 2025
8af94f3
Use new plugin API to hook into "Close all" action
jitseniesen Aug 19, 2025
d3a4bac
Remove New / Open / Save As from pane options menu
jitseniesen Sep 23, 2025
a488f25
Tests: Patch get_action when setting up main widget
jitseniesen Sep 27, 2025
1277cac
Move import of ApplicationsActions inside function
jitseniesen Sep 27, 2025
138d5cd
CI tests: Kill Python tasks on Windows
jitseniesen Sep 28, 2025
0421137
CI tests: Explicitly remove conda defaults channel
jitseniesen Sep 28, 2025
41e35a9
CI tests: Don't fail if no Python process
jitseniesen Sep 29, 2025
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
10 changes: 9 additions & 1 deletion .github/workflows/run-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
matrix:
OS: ['ubuntu', 'macos', 'windows']
PYTHON_VERSION: ['3.9', '3.11', '3.12']
SPYDER_SOURCE: ['conda', 'git']
SPYDER_SOURCE: ['git']
name: ${{ matrix.OS }} py${{ matrix.PYTHON_VERSION }} spyder-from-${{ matrix.SPYDER_SOURCE }}
runs-on: ${{ matrix.OS }}-latest
env:
Expand All @@ -36,6 +36,7 @@ jobs:
with:
miniforge-version: latest
auto-update-conda: true
conda-remove-defaults: "true"
python-version: ${{ matrix.PYTHON_VERSION }}
- name: Checkout Spyder from git
if: matrix.SPYDER_SOURCE == 'git'
Expand Down Expand Up @@ -122,3 +123,10 @@ jobs:
command: |
cd spyder-notebook
pytest spyder_notebook -vv
- name: Kill Python (Windows)
if: matrix.OS == 'windows'
shell: bash -l {0}
run: |
# This prevents hangs in Post Install Conda
tasklist | grep python || true
taskkill //im python.exe //f || true
2 changes: 1 addition & 1 deletion requirements/conda.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
spyder >=6,<7
spyder >=6.1,<6.2
jupyter_core
jupyter_server
nbformat
Expand Down
4 changes: 2 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ def run(self):


REQUIREMENTS = [
'spyder>=6,<7',
'spyder>=6.1,<6.2',
'nbformat',
'notebook>=7.3.3,<7.4',
'psutil',
Expand All @@ -94,7 +94,7 @@ def run(self):
version=get_version(),
cmdclass={'sdist': my_sdist},
keywords='spyder jupyter notebook',
python_requires='>=3.8',
python_requires='>=3.9',
url='https://github.com/spyder-ide/spyder-notebook',
license='MIT',
author='Spyder Development Team',
Expand Down
3 changes: 1 addition & 2 deletions spyder_notebook/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
(
CONF_SECTION,
{
'recent_notebooks': [], # Items in "Open recent" menu
'opened_notebooks': [], # Notebooks to open at start
'theme': 'same as spyder' # Notebook theme (light/dark)
}
Expand All @@ -26,4 +25,4 @@
# or if you want to *rename* options, then you need to do a MAJOR update in
# version, e.g. from 1.0.0 to 2.0.0
# 3. You don't need to touch this value if you're just adding a new option
CONF_VERSION = '0.1.0'
CONF_VERSION = '1.0.0'
106 changes: 102 additions & 4 deletions spyder_notebook/notebookplugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
# Standard library imports
import logging
import os.path as osp
from typing import Optional

# Spyder imports
from spyder.api.plugins import Plugins, SpyderDockablePlugin
Expand All @@ -28,7 +29,7 @@ class NotebookPlugin(SpyderDockablePlugin):
"""Spyder Notebook plugin."""

NAME = 'notebook'
REQUIRES = [Plugins.Preferences]
REQUIRES = [Plugins.Application, Plugins.Preferences]
OPTIONAL = [Plugins.IPythonConsole, Plugins.Switcher]
TABIFY = [Plugins.Editor]
CONF_SECTION = NAME
Expand All @@ -37,9 +38,18 @@ class NotebookPlugin(SpyderDockablePlugin):
WIDGET_CLASS = NotebookMainWidget
CONF_WIDGET_CLASS = NotebookConfigPage

# This plugin hooks into File menu actions
CAN_HANDLE_FILE_ACTIONS = True

# This plugin opens files with the .ipynb exetension
FILE_EXTENSIONS = ['.ipynb']

# Action "Switch to notebook" gives focus to the plugin
RAISE_AND_FOCUS = True

# This plugin requires Qt Web Widgets to function
REQUIRE_WEB_WIDGETS = True

# ---- SpyderDockablePlugin API
# ------------------------------------------------------------------------
@staticmethod
Expand All @@ -62,6 +72,19 @@ def on_initialize(self):
"""Set up the plugin; does nothing."""
pass

@on_plugin_available(plugin=Plugins.Application)
def on_application_available(self) -> None:
# Moving this import to the top of the file somehow interferes with
# the tests in test_main_window.py
from spyder.plugins.application.api import ApplicationActions

application = self.get_plugin(Plugins.Application)
application.enable_file_action(
ApplicationActions.RevertFile, False, self
)
widget = self.get_widget()
widget.sig_new_recent_file.connect(application.add_recent_file)

@on_plugin_available(plugin=Plugins.Preferences)
def on_preferences_available(self):
preferences = self.get_plugin(Plugins.Preferences)
Expand All @@ -78,6 +101,12 @@ def on_switcher_available(self):
switcher.sig_mode_selected.connect(self._handle_switcher_modes)
switcher.sig_item_selected.connect(self._handle_switcher_selection)

@on_plugin_teardown(plugin=Plugins.Application)
def on_application_teardown(self) -> None:
application = self.get_plugin(Plugins.Application)
widget = self.get_widget()
widget.sig_new_recent_file.disconnect(application.add_recent_file)

@on_plugin_teardown(plugin=Plugins.Preferences)
def on_preferences_teardown(self):
preferences = self.get_plugin(Plugins.Preferences)
Expand All @@ -97,9 +126,78 @@ def on_switcher_teardown(self):
def on_mainwindow_visible(self):
self.get_widget().open_previous_session()

# ------ Public API -------------------------------------------------------
def open_notebook(self, filenames=None):
self.get_widget().open_notebook(filenames)
def create_new_file(self) -> None:
"""
Create a new notebook.
"""
self.get_widget().create_new_client()

def open_file(self, filename: str) -> None:
"""
Open file inside plugin.

This method will be called if the user wants to open a notebook.

Parameters
----------
filename: str
The name of the file to be opened.
"""
self.get_widget().open_notebook([filename])

def open_last_closed_file(self) -> None:
"""
Reopens the notebook in the last closed tab.
"""
self.get_widget().open_last_closed_notebook()

def save_file(self) -> None:
"""
Save the current notebook.
"""
self.get_widget().save_notebook()

def save_file_as(self) -> None:
"""
Save the current notebook under a different name.
"""
self.get_widget().save_as()

def save_copy_as(self) -> None:
"""
Save a copy of the current notebook under a different name.
"""
self.get_widget().save_as(close_after_save=False)

def save_all(self) -> None:
"""
Save all opened notebooks.
"""
self.get_widget().save_all()

def close_file(self) -> None:
"""
Close the current notebook.
"""
self.get_widget().close_notebook()

def close_all(self) -> None:
"""
Close all notebook.
"""
self.get_widget().close_all()

def get_current_filename(self) -> Optional[str]:
"""
Return file name of the notebook that is currently displayed.
"""
return self.get_widget().get_current_filename()

def current_file_is_temporary(self) -> bool:
"""
Return whether currently displayed file is a temporary file.
"""
return self.get_widget().current_file_is_temporary()

# ------ Private API ------------------------------------------------------
def _open_console(self, connection_file, tab_name):
Expand Down
4 changes: 3 additions & 1 deletion spyder_notebook/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,9 @@ def get_plugin(self, plugin_name, error=True):
class ConfigDialogTester(QWidget):
def __init__(self, parent, main_class,
general_config_plugins, plugins):
# This import assumes that an QApplication is already running,
# These imports assumes that an QApplication is already running,
# so we can not put it at the top of the file
from spyder.plugins.application.plugin import Application
from spyder.plugins.preferences.plugin import Preferences

super().__init__(parent)
Expand All @@ -107,6 +108,7 @@ def get_plugin(self, plugin_name, error=True):

PLUGIN_REGISTRY.reset()
PLUGIN_REGISTRY.sig_plugin_ready.connect(self._main.register_plugin)
PLUGIN_REGISTRY.register_plugin(self._main, Application)
PLUGIN_REGISTRY.register_plugin(self._main, Preferences)

if plugins:
Expand Down
10 changes: 3 additions & 7 deletions spyder_notebook/tests/test_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,12 @@
from unittest.mock import Mock

# Third-party library imports
from flaky import flaky
import pytest
from qtpy.QtWebEngineWidgets import WEBENGINE
from qtpy.QtWidgets import QMainWindow
import requests

# Spyder imports
from spyder.api.plugins import Plugins
from spyder.config.manager import CONF

# Local imports
Expand Down Expand Up @@ -156,20 +154,18 @@ def test_on_mainwindow_visible_with_opened_notebooks_empty(plugin_no_server):

def test_closing_main_widget(mocker, plugin_no_server):
"""Close the main widget with a welcome tab, a new notebooks and a notebook
opened from a file. Check that config variables `recent_notebooks` and
`opened_notebook` are correctly set."""
opened from a file. Check that config variable `opened_notebook` is
correctly set."""
plugin = plugin_no_server
main_widget = plugin.get_widget()
main_widget.clear_recent_notebooks()
mock_set_option = mocker.patch.object(main_widget, 'set_conf')
main_widget.tabwidget.maybe_create_welcome_client()
main_widget.create_new_client()
main_widget.open_notebook(['ham.ipynb'])

plugin.get_widget().close()

expected = [mocker.call('recent_notebooks', ['ham.ipynb']),
mocker.call('opened_notebooks', ['ham.ipynb'])]
expected = [mocker.call('opened_notebooks', ['ham.ipynb'])]
assert mock_set_option.call_args_list == expected


Expand Down
6 changes: 4 additions & 2 deletions spyder_notebook/utils/templates/welcome-dark.html
Original file line number Diff line number Diff line change
Expand Up @@ -628,8 +628,10 @@
<h1 id="section"></h1>
<h1 id="welcome-to-the-notebook-plugin">Welcome to Spyder-Notebook</h1>
<p>Here you can open, edit and create Jupyter notebooks.</p>
<p>To create new notebooks, you can right-click and then click on <em>New notebook</em>. You can also click the <span title="Open new notebook"><b>&#xFF0B;</b></span> button on the top right of this pane.</p>
<p>To open and save notebooks, click the <span title="Options">&#9776;</span> button on the top right too.</p>
<p>
The items in the <em>File</em> menu act on notebooks if this pane is active.
For example, <em>File</em> > <em>New file...</em> will create a new notebook.
</p>
<p>To resize the text and other elements in a notebook, you can right-click and choose <em>Zoom in</em> or <em>Zoom out</em>.</p>

<HR/>
Expand Down
6 changes: 4 additions & 2 deletions spyder_notebook/utils/templates/welcome.html
Original file line number Diff line number Diff line change
Expand Up @@ -616,8 +616,10 @@
<h1 id="section"></h1>
<h1 id="welcome-to-the-notebook-plugin">Welcome to Spyder-Notebook</h1>
<p>Here you can open, edit and create Jupyter notebooks.</p>
<p>To create new notebooks, you can right-click and then click on <em>New notebook</em>. You can also click the <span title="Open new notebook"><b>&#xFF0B;</b></span> button on the top right of this pane.</p>
<p>To open and save notebooks, click the <span title="Options">&#9776;</span> button on the top right too.</p>
<p>
The items in the <em>File</em> menu act on notebooks if this pane is active.
For example, <em>File</em> > <em>New file...</em> will create a new notebook.
</p>
<p>To resize the text and other elements in a notebook, you can right-click and choose <em>Zoom in</em> or <em>Zoom out</em>.</p>

<HR/>
Expand Down
Loading
Loading