From 1c6828488c9c760c4aa831ca222e015faa8fae31 Mon Sep 17 00:00:00 2001 From: steff456 Date: Wed, 24 Jul 2019 22:30:41 -0500 Subject: [PATCH 1/5] add option for a graphical or non-graphical plugin --- cookiecutter.json | 3 +- ...gin_name.lower().replace(' ', '') }}gui.py | 5 +- ..._name.lower().replace(' ', '') }}plugin.py | 84 ++++++++++++++----- 3 files changed, 71 insertions(+), 21 deletions(-) diff --git a/cookiecutter.json b/cookiecutter.json index 8b875da..8875aa6 100644 --- a/cookiecutter.json +++ b/cookiecutter.json @@ -10,5 +10,6 @@ "create_config_page": "n", "use_ciocheck": "y", "support_python_2": "n", - "spyder3_compatibility": "y" + "spyder3_compatibility": "n", + "graphical_plugin": "y" } diff --git a/{{ cookiecutter.repo_name }}/{{ cookiecutter.project_name }}/widgets/{{ cookiecutter.plugin_name.lower().replace(' ', '') }}gui.py b/{{ cookiecutter.repo_name }}/{{ cookiecutter.project_name }}/widgets/{{ cookiecutter.plugin_name.lower().replace(' ', '') }}gui.py index 273bbbb..d79fd89 100644 --- a/{{ cookiecutter.repo_name }}/{{ cookiecutter.project_name }}/widgets/{{ cookiecutter.plugin_name.lower().replace(' ', '') }}gui.py +++ b/{{ cookiecutter.repo_name }}/{{ cookiecutter.project_name }}/widgets/{{ cookiecutter.plugin_name.lower().replace(' ', '') }}gui.py @@ -6,12 +6,15 @@ # (see LICENSE.txt for details) # ----------------------------------------------------------------------------- """{{ cookiecutter.plugin_name }} Widget.""" - +{%- if cookiecutter.graphical_plugin != 'n' -%} +# Third party imports from qtpy.QtWidgets import QWidget + class {{ cookiecutter.plugin_name.replace(' ', '') }}Widget(QWidget): """{{ cookiecutter.plugin_name }} widget.""" def __init__(self, parent): QWidget.__init__(self, parent) self.setWindowTitle("{{ cookiecutter.plugin_name }}") +{%- endif -%} diff --git a/{{ cookiecutter.repo_name }}/{{ cookiecutter.project_name }}/{{ cookiecutter.plugin_name.lower().replace(' ', '') }}plugin.py b/{{ cookiecutter.repo_name }}/{{ cookiecutter.project_name }}/{{ cookiecutter.plugin_name.lower().replace(' ', '') }}plugin.py index 8583fe1..671292d 100644 --- a/{{ cookiecutter.repo_name }}/{{ cookiecutter.project_name }}/{{ cookiecutter.plugin_name.lower().replace(' ', '') }}plugin.py +++ b/{{ cookiecutter.repo_name }}/{{ cookiecutter.project_name }}/{{ cookiecutter.plugin_name.lower().replace(' ', '') }}plugin.py @@ -10,56 +10,99 @@ {%- set widget_name = cookiecutter.plugin_name.replace(' ', '') + 'Widget' -%} {%- set config_page = cookiecutter.plugin_name.replace(' ', '') + 'ConfigPage' %} +{%- if cookiecutter.graphical_plugin == 'n' %} from qtpy.QtWidgets import QVBoxLayout - -{% if cookiecutter.spyder3_compatibility == 'y' -%} +{%- endif %} +{% if cookiecutter.spyder3_compatibility == 'y' %} try: from spyder.api.plugins import SpyderPluginWidget except ImportError: from spyder.plugins import SpyderPluginWidget # Spyder3 {%- if cookiecutter.create_config_page == 'y' %} try: - from spyder.api.preferences import + from spyder.api.preferences import PluginConfigPage except ImportError: from spyder.plugins.configdialog import PluginConfigPage # Spyder3 {%- endif %} -{% else -%} +{% else %} + {%- if cookiecutter.graphical_plugin == 'y' %} from spyder.api.plugins import SpyderPluginWidget + {%- else %} +from spyder.api.plugins import SpyderPlugin + {%- endif %} {%- if cookiecutter.create_config_page == 'y' %} from spyder.api.preferences import PluginConfigPage - {%- endif %} -{%- endif -%} - + {% endif %} +{%- endif %} from .widgets.{{ cookiecutter.plugin_name.lower().replace(' ', '') }}gui import {{ widget_name }} -{% if cookiecutter.create_config_page == 'y' %} +{% if cookiecutter.create_config_page == 'y' %} class {{ config_page }}(PluginConfigPage): """{{ cookiecutter.plugin_name }} plugin preferences.""" pass {% endif %} +{%- if cookiecutter.graphical_plugin == 'n' %} +class {{ cookiecutter.plugin_name.replace(' ', '') }}Plugin(SpyderPlugin): + """{{ cookiecutter.plugin_name }} plugin.""" + CONF_SECTION = '{{ cookiecutter.plugin_name }}' + {% if cookiecutter.create_config_page == 'y' %} + CONFIGWIDGET_CLASS = {{ config_page }} + {% else %} + CONFIGWIDGET_CLASS = None + {% endif %} + def __init__(self, parent=None): + SpyderPlugin.__init__(self, parent) + + # Create widget and add to dockwindow + self.widget = {{ widget_name }}(self.main) + + # Initialize plugin + self.initialize_plugin() + + # --- SpyderPlugin API ---------------------------------------------- + def get_plugin_title(self): + """Return widget title.""" + return "{{ cookiecutter.plugin_name }}" + def refresh_plugin(self): + """Refresh {{ widget_name }} widget.""" + pass + + def get_plugin_actions(self): + """Return a list of actions related to plugin.""" + return [] + + def register_plugin(self): + """Register plugin in Spyder's main window.""" + pass + + def on_first_registration(self): + """Action to be performed on first plugin registration.""" + pass + + def apply_plugin_settings(self, options): + """Apply configuration file's plugin settings.""" + pass +{%- else %} class {{ cookiecutter.plugin_name.replace(' ', '') }}Plugin(SpyderPluginWidget): """{{ cookiecutter.plugin_name }} plugin.""" CONF_SECTION = '{{ cookiecutter.plugin_name }}' -{% if cookiecutter.create_config_page == 'y' %} + {% if cookiecutter.create_config_page == 'y' %} CONFIGWIDGET_CLASS = {{ config_page }} -{% else %} + {% else %} CONFIGWIDGET_CLASS = None -{% endif %} - + {% endif %} def __init__(self, parent=None): SpyderPluginWidget.__init__(self, parent) - self.main = parent # Spyder3 - # Create widget and add to dockwindow - self.widget = {{ widget_name }}(self.main) + # Initialize plugin + self.initialize_plugin() + + # Graphical view layout = QVBoxLayout() layout.addWidget(self.widget) self.setLayout(layout) - # Initialize plugin - self.initialize_plugin() - # --- SpyderPluginWidget API ---------------------------------------------- def get_plugin_title(self): """Return widget title.""" @@ -83,8 +126,11 @@ def register_plugin(self): def on_first_registration(self): """Action to be performed on first plugin registration.""" - self.main.tabify_plugins(self.main.help, self) + # Define where the plugin is going to be tabified next to + # As default, it will be tabbed next to the ipython console + self.tabify(self.main.ipyconsole) def apply_plugin_settings(self, options): """Apply configuration file's plugin settings.""" pass +{%- endif -%} From eda78108a7da170728eb46c41781049e16ac92c2 Mon Sep 17 00:00:00 2001 From: steff456 Date: Fri, 26 Jul 2019 11:53:24 -0500 Subject: [PATCH 2/5] revision and test for non graphical plugin --- hooks/post_gen_project.py | 18 ++++++-- tests/test_cookiecutter_generation.py | 45 +++++++++++++++++++ ...gin_name.lower().replace(' ', '') }}gui.py | 2 - ..._name.lower().replace(' ', '') }}plugin.py | 39 +++------------- 4 files changed, 66 insertions(+), 38 deletions(-) diff --git a/hooks/post_gen_project.py b/hooks/post_gen_project.py index 0580b4f..47d361f 100644 --- a/hooks/post_gen_project.py +++ b/hooks/post_gen_project.py @@ -5,15 +5,27 @@ from __future__ import print_function import os +import shutil from subprocess import call # Get the root project directory PROJECT_DIRECTORY = os.path.realpath(os.path.curdir) -def remove_file(file_name): - if os.path.exists(file_name): - os.remove(file_name) +def remove(filepath): + """Remove files or directory given a path.""" + if os.path.isfile(filepath): + os.remove(filepath) + elif os.path.isdir(filepath): + shutil.rmtree(filepath) + + +create_widget = '{{cookiecutter.graphical_plugin}}' == 'y' + +if not create_widget: + # Remove the widgets folder if the plugin is not graphical + remove(os.path.join(PROJECT_DIRECTORY, + '{{cookiecutter.project_name}}', 'widgets')) def init_git(): diff --git a/tests/test_cookiecutter_generation.py b/tests/test_cookiecutter_generation.py index 6d82dee..00aa00c 100644 --- a/tests/test_cookiecutter_generation.py +++ b/tests/test_cookiecutter_generation.py @@ -17,6 +17,21 @@ def context(): } +@pytest.fixture +def non_graphical(): + return { + 'author': 'Spyder Project Contributors', + 'email': 'admin@spyder-ide.org', + 'github_username': 'spyder-ide', + 'plugin_name': 'Demo-nongraphic', + 'repo_name': 'spyder-demo-nongraphic', + 'project_name': 'spyder_demo_nongraphic', + 'description': 'Plugin for Spyder IDE.', + 'version': '0.1.0', + 'graphical_plugin': 'n' + } + + def test_default_configuration(cookies, context): result = cookies.bake(extra_context=context) assert result.exit_code == 0 @@ -44,3 +59,33 @@ def test_default_configuration(cookies, context): for path in project_files: assert path in found_project_files + + +def test_non_graphical_configuration(cookies, non_graphical): + """Test generation of non-graphical plugin.""" + result = cookies.bake(extra_context=non_graphical) + assert result.exit_code == 0 + assert result.exception is None + assert result.project.basename == non_graphical['repo_name'] + assert result.project.isdir() + + # Test creation of project files + + toplevel_files = ['MANIFEST.in', 'README.rst', 'setup.py', + 'requirements.txt', '.gitattributes'] + + found_toplevel_files = [f.basename for f in result.project.listdir()] + + for path in toplevel_files: + assert path in found_toplevel_files + + # Test creation of plugin files + + project_files = ['assets', 'demo-nongraphicplugin.py', '_version.py', + '__init__.py', 'tests'] + + project_dir = result.project.join(non_graphical['project_name']) + found_project_files = [f.basename for f in project_dir.listdir()] + + for path in project_files: + assert path in found_project_files diff --git a/{{ cookiecutter.repo_name }}/{{ cookiecutter.project_name }}/widgets/{{ cookiecutter.plugin_name.lower().replace(' ', '') }}gui.py b/{{ cookiecutter.repo_name }}/{{ cookiecutter.project_name }}/widgets/{{ cookiecutter.plugin_name.lower().replace(' ', '') }}gui.py index d79fd89..d7f8090 100644 --- a/{{ cookiecutter.repo_name }}/{{ cookiecutter.project_name }}/widgets/{{ cookiecutter.plugin_name.lower().replace(' ', '') }}gui.py +++ b/{{ cookiecutter.repo_name }}/{{ cookiecutter.project_name }}/widgets/{{ cookiecutter.plugin_name.lower().replace(' ', '') }}gui.py @@ -6,7 +6,6 @@ # (see LICENSE.txt for details) # ----------------------------------------------------------------------------- """{{ cookiecutter.plugin_name }} Widget.""" -{%- if cookiecutter.graphical_plugin != 'n' -%} # Third party imports from qtpy.QtWidgets import QWidget @@ -17,4 +16,3 @@ def __init__(self, parent): QWidget.__init__(self, parent) self.setWindowTitle("{{ cookiecutter.plugin_name }}") -{%- endif -%} diff --git a/{{ cookiecutter.repo_name }}/{{ cookiecutter.project_name }}/{{ cookiecutter.plugin_name.lower().replace(' ', '') }}plugin.py b/{{ cookiecutter.repo_name }}/{{ cookiecutter.project_name }}/{{ cookiecutter.plugin_name.lower().replace(' ', '') }}plugin.py index 671292d..901082d 100644 --- a/{{ cookiecutter.repo_name }}/{{ cookiecutter.project_name }}/{{ cookiecutter.plugin_name.lower().replace(' ', '') }}plugin.py +++ b/{{ cookiecutter.repo_name }}/{{ cookiecutter.project_name }}/{{ cookiecutter.plugin_name.lower().replace(' ', '') }}plugin.py @@ -10,7 +10,7 @@ {%- set widget_name = cookiecutter.plugin_name.replace(' ', '') + 'Widget' -%} {%- set config_page = cookiecutter.plugin_name.replace(' ', '') + 'ConfigPage' %} -{%- if cookiecutter.graphical_plugin == 'n' %} +{%- if cookiecutter.graphical_plugin != 'n' %} from qtpy.QtWidgets import QVBoxLayout {%- endif %} {% if cookiecutter.spyder3_compatibility == 'y' %} @@ -51,38 +51,8 @@ class {{ cookiecutter.plugin_name.replace(' ', '') }}Plugin(SpyderPlugin): CONFIGWIDGET_CLASS = None {% endif %} def __init__(self, parent=None): + QObject.__init__(self, parent) SpyderPlugin.__init__(self, parent) - - # Create widget and add to dockwindow - self.widget = {{ widget_name }}(self.main) - - # Initialize plugin - self.initialize_plugin() - - # --- SpyderPlugin API ---------------------------------------------- - def get_plugin_title(self): - """Return widget title.""" - return "{{ cookiecutter.plugin_name }}" - - def refresh_plugin(self): - """Refresh {{ widget_name }} widget.""" - pass - - def get_plugin_actions(self): - """Return a list of actions related to plugin.""" - return [] - - def register_plugin(self): - """Register plugin in Spyder's main window.""" - pass - - def on_first_registration(self): - """Action to be performed on first plugin registration.""" - pass - - def apply_plugin_settings(self, options): - """Apply configuration file's plugin settings.""" - pass {%- else %} class {{ cookiecutter.plugin_name.replace(' ', '') }}Plugin(SpyderPluginWidget): """{{ cookiecutter.plugin_name }} plugin.""" @@ -93,10 +63,13 @@ class {{ cookiecutter.plugin_name.replace(' ', '') }}Plugin(SpyderPluginWidget): CONFIGWIDGET_CLASS = None {% endif %} def __init__(self, parent=None): + QObject.__init__(self, parent) SpyderPluginWidget.__init__(self, parent) + {% if cookiecutter.spyder3_compatibility == 'y' %} # Initialize plugin self.initialize_plugin() + {% endif %} # Graphical view layout = QVBoxLayout() @@ -128,7 +101,7 @@ def on_first_registration(self): """Action to be performed on first plugin registration.""" # Define where the plugin is going to be tabified next to # As default, it will be tabbed next to the ipython console - self.tabify(self.main.ipyconsole) + self.tabify(self.main.help) def apply_plugin_settings(self, options): """Apply configuration file's plugin settings.""" From 20d4900d9c92f02f83f34873ecf12e832e9f372e Mon Sep 17 00:00:00 2001 From: steff456 Date: Wed, 31 Jul 2019 17:56:19 -0500 Subject: [PATCH 3/5] final review --- .../tests/test_plugin.py | 16 +++++++++++++- ..._name.lower().replace(' ', '') }}plugin.py | 21 +++++++++++++------ 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/{{ cookiecutter.repo_name }}/{{ cookiecutter.project_name }}/tests/test_plugin.py b/{{ cookiecutter.repo_name }}/{{ cookiecutter.project_name }}/tests/test_plugin.py index 64c6a8b..fb5d20d 100644 --- a/{{ cookiecutter.repo_name }}/{{ cookiecutter.project_name }}/tests/test_plugin.py +++ b/{{ cookiecutter.repo_name }}/{{ cookiecutter.project_name }}/tests/test_plugin.py @@ -15,7 +15,7 @@ # Local imports from {{ cookiecutter.project_name }}.{{ module_name }} import {{ plugin_name }} - +{%- if cookiecutter.graphical_plugin == 'y' %} @pytest.fixture def setup_{{ object_name }}(qtbot): """Set up the {{ plugin_name }} plugin.""" @@ -31,7 +31,21 @@ def test_basic_initialization(qtbot): # Assert that plugin object exist assert {{ object_name }} is not None +{% else %} +@pytest.fixture +def setup_{{ object_name }}(): + """Set up the {{ plugin_name }} plugin.""" + {{ object_name }} = {{ plugin_name }}(None) + return {{ object_name }} +def test_basic_initialization(): + """Test {{ plugin_name }} initialization.""" + {{ object_name }} = setup_{{ object_name }}() + + # Assert that plugin object exist + assert {{ object_name }} is not None +{% endif %} + if __name__ == "__main__": pytest.main() diff --git a/{{ cookiecutter.repo_name }}/{{ cookiecutter.project_name }}/{{ cookiecutter.plugin_name.lower().replace(' ', '') }}plugin.py b/{{ cookiecutter.repo_name }}/{{ cookiecutter.project_name }}/{{ cookiecutter.plugin_name.lower().replace(' ', '') }}plugin.py index 901082d..55ddf36 100644 --- a/{{ cookiecutter.repo_name }}/{{ cookiecutter.project_name }}/{{ cookiecutter.plugin_name.lower().replace(' ', '') }}plugin.py +++ b/{{ cookiecutter.repo_name }}/{{ cookiecutter.project_name }}/{{ cookiecutter.plugin_name.lower().replace(' ', '') }}plugin.py @@ -34,7 +34,9 @@ from spyder.api.preferences import PluginConfigPage {% endif %} {%- endif %} +{%- if cookiecutter.graphical_plugin != 'n' %} from .widgets.{{ cookiecutter.plugin_name.lower().replace(' ', '') }}gui import {{ widget_name }} +{%- endif %} {% if cookiecutter.create_config_page == 'y' %} class {{ config_page }}(PluginConfigPage): @@ -44,7 +46,7 @@ class {{ config_page }}(PluginConfigPage): {%- if cookiecutter.graphical_plugin == 'n' %} class {{ cookiecutter.plugin_name.replace(' ', '') }}Plugin(SpyderPlugin): """{{ cookiecutter.plugin_name }} plugin.""" - CONF_SECTION = '{{ cookiecutter.plugin_name }}' + CONF_SECTION = '{{ cookiecutter.plugin_name.lower() }}' {% if cookiecutter.create_config_page == 'y' %} CONFIGWIDGET_CLASS = {{ config_page }} {% else %} @@ -85,6 +87,17 @@ def get_focus_widget(self): """Return the widget to give focus to.""" return self.widget + def on_first_registration(self): + """Action to be performed on first plugin registration.""" + # Define where the plugin is going to be tabified next to + # As default, it will be tabbed next to the ipython console + {% if cookiecutter.spyder3_compatibility == 'y' %} + self.main.tabify_plugins(self.main.help, self) + {% else %} + self.tabify(self.main.help) + {% endif %} + + {% if cookiecutter.spyder3_compatibility == 'y' %} def refresh_plugin(self): """Refresh {{ widget_name }} widget.""" pass @@ -97,13 +110,9 @@ def register_plugin(self): """Register plugin in Spyder's main window.""" self.main.add_dockwidget(self) - def on_first_registration(self): - """Action to be performed on first plugin registration.""" - # Define where the plugin is going to be tabified next to - # As default, it will be tabbed next to the ipython console - self.tabify(self.main.help) def apply_plugin_settings(self, options): """Apply configuration file's plugin settings.""" pass + {% endif %} {%- endif -%} From 29665cb234f155ef47367437ccc349c43e084c48 Mon Sep 17 00:00:00 2001 From: steff456 Date: Wed, 31 Jul 2019 18:07:34 -0500 Subject: [PATCH 4/5] only install pytest-cookies --- requirements/tests.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/requirements/tests.txt b/requirements/tests.txt index 21bcc78..5624bea 100644 --- a/requirements/tests.txt +++ b/requirements/tests.txt @@ -1,3 +1,2 @@ # Testing -pytest pytest-cookies \ No newline at end of file From ee4a922e67b279bfb74723fe65a45cae396f7249 Mon Sep 17 00:00:00 2001 From: steff456 Date: Fri, 2 Aug 2019 12:44:11 -0500 Subject: [PATCH 5/5] add blank line --- .../{{ cookiecutter.project_name }}/tests/test_plugin.py | 1 + 1 file changed, 1 insertion(+) diff --git a/{{ cookiecutter.repo_name }}/{{ cookiecutter.project_name }}/tests/test_plugin.py b/{{ cookiecutter.repo_name }}/{{ cookiecutter.project_name }}/tests/test_plugin.py index fb5d20d..ec435c0 100644 --- a/{{ cookiecutter.repo_name }}/{{ cookiecutter.project_name }}/tests/test_plugin.py +++ b/{{ cookiecutter.repo_name }}/{{ cookiecutter.project_name }}/tests/test_plugin.py @@ -15,6 +15,7 @@ # Local imports from {{ cookiecutter.project_name }}.{{ module_name }} import {{ plugin_name }} + {%- if cookiecutter.graphical_plugin == 'y' %} @pytest.fixture def setup_{{ object_name }}(qtbot):