Peter Justin 8 лет назад
Родитель
Сommit
136984409e

+ 18 - 9
flaskbb/cli/plugins.py

@@ -130,19 +130,24 @@ def list_plugins():
             )
 
 
-@plugins.command("migrations")
+@plugins.command("migrate")
 @click.argument("plugin_identifier")
-def migrate_plugin(plugin_identifier):
+@click.option("--message", "-m", help="The name of the migration.")
+def migrate_plugin(plugin_identifier, message=None):
     """Generates migration files for a plugin.
-    Migration version files are stored in flaskbb/plugins/<plugin_dir>/migration_versions"""
+    Migration version files are stored in
+    ``flaskbb/plugins/<plugin_dir>/migration_versions``.
+    """
     validate_plugin(plugin_identifier)
     plugin = get_plugin_from_all(plugin_identifier)
-    click.secho("[+] Updating plugin migrations{}...".format(plugin.name), fg="cyan")
+    click.secho("[+] Updating plugin migrations {}...".format(plugin.name),
+                fg="cyan")
     try:
-        plugin.migrate()
+        plugin.migrate(message=message)
     except Exception as e:
-        click.secho("[-] Couldn't generate migrations for plugin because of following "
-                    "exception: \n{}".format(e), fg="red")
+        click.secho("[-] Couldn't generate migrations for plugin because of "
+                    "following exception: \n{}".format(e), fg="red")
+
 
 @plugins.command("upgrade")
 @click.argument("plugin_identifier")
@@ -156,14 +161,18 @@ def upgrade_plugin(plugin_identifier):
     except AttributeError:
         pass
 
+
 @plugins.command("downgrade")
 @click.argument("plugin_identifier")
 def downgrade_plugin(plugin_identifier):
     """Downgrades database to remove a plugin's models"""
     validate_plugin(plugin_identifier)
     plugin = get_plugin_from_all(plugin_identifier)
-    if click.confirm("Please confirm you want to remove this plugins data from the database"):
-        click.secho("[+] Downgrading plugin {}...".format(plugin.name), fg="cyan")
+
+    if click.confirm("Please confirm if you want to remove this plugins data "
+                     "from the database."):
+        click.secho("[+] Downgrading plugin {}...".format(plugin.name),
+                    fg="cyan")
         try:
             plugin.downgrade_database()
         except AttributeError:

+ 59 - 40
flaskbb/plugins/__init__.py

@@ -9,40 +9,42 @@
     :license: BSD, see LICENSE for more details.
 """
 import contextlib
-
 import copy
 import os
+
 from flask import current_app
 from flask import g
 from flask_migrate import upgrade, downgrade, migrate
 from flask_plugins import Plugin
-from flaskbb.extensions import db, migrate as mig
-from flaskbb.management.models import SettingsGroup,Setting
+from flaskbb.extensions import db, migrate as migrate_ext
+from flaskbb.management.models import SettingsGroup, Setting
+
 
 @contextlib.contextmanager  # TODO: Add tests
 def plugin_name_migrate(name):
-    """Migrations in this with block will only apply to models from the named plugin"""
+    """Migrations in this with block will only apply to models
+    from the named plugin"""
     g.plugin_name = name
     yield
     del g.plugin_name
 
 
-def db_for_plugin(plugin_name,sqla_instance=None):
+def db_for_plugin(plugin_name, sqla_instance=None):
     """Labels models as belonging to this plugin.
-    sqla_instance is a valid Flask-SQLAlchemy instance, if None, then the default db is used
+    sqla_instance is a valid Flask-SQLAlchemy instance, if None,
+    then the default db is used
 
     Usage:
         from flaskbb.plugins import db_for_plugin
 
-        db=db_for_plugin(__name__)
+        db = db_for_plugin(__name__)
 
         mytable = db.Table(...)
 
         class MyModel(db.Model):
             ...
-
-        """
-    sqla_instance=sqla_instance or db
+    """
+    sqla_instance = sqla_instance or db
     new_db = copy.copy(sqla_instance)
 
     def Table(*args, **kwargs):
@@ -54,14 +56,14 @@ def db_for_plugin(plugin_name,sqla_instance=None):
     return new_db
 
 
-@mig.configure
+@migrate_ext.configure
 def config_migrate(config):
-    """Configuration callback for plugins environment"""
+    """Configuration callback for plugins environment."""
     plugins = current_app.extensions['plugin_manager'].all_plugins.values()
     migration_dirs = [p.get_migration_version_dir() for p in plugins]
     if config.get_main_option('version_table') == 'plugins':
         config.set_main_option('version_locations', ' '.join(migration_dirs))
-        print config.get_main_option('version_locations')
+        # current_app.logger.debug(config.get_main_option('version_locations'))
     return config
 
 
@@ -70,45 +72,60 @@ class FlaskBBPlugin(Plugin):
     #: install additional things you must set it, else it won't install
     #: anything.
     settings_key = None
-    requires_install=None
+    requires_install = None
 
     def resource_filename(self, *names):
-        "Returns an absolute filename for a plugin resource."
-        if len(names)==1 and '/' in names[0]:
-            names=names[0].split('/')
-        fname= os.path.join(self.path, *names)
-        if ' ' in fname and not '"' in fname and not "'" in fname:
-            fname='"%s"'%fname
+        """Returns an absolute filename for a plugin resource."""
+        if len(names) == 1 and '/' in names[0]:
+            names = names[0].split('/')
+        fname = os.path.join(self.path, *names)
+        if ' ' in fname and '"' not in fname and "'" not in fname:
+            fname = '"%s"' % fname
         return fname
 
     def get_migration_version_dir(self):
-        """Returns path to directory containing the migration version files"""
-        return self.__module__+':migration_versions'
+        """Returns the path to the directory which is containing the
+        migration version files
+        """
+        return self.__module__ + ':migration_versions'
 
     def upgrade_database(self, target='head'):
-        """Updates database to a later version of plugin models.
-        Default behaviour is to upgrade to latest version"""
+        """Updates the database to a later version of the plugin models.
+        Default behaviour is to upgrade to the latest version.
+        """
         plugin_dir = current_app.extensions['plugin_manager'].plugin_folder
-        upgrade(directory=os.path.join(plugin_dir, '_migration_environment'), revision=self.settings_key + '@' + target)
+        plugin_env = os.path.join(plugin_dir, '_migration_environment')
+        plugin_rev = "{}@{}".format(self.settings_key, target)
+        upgrade(directory=plugin_env, revision=plugin_rev)
 
     def downgrade_database(self, target='base'):
-        """Rolls back database to a previous version of plugin models.
-        Default behaviour is to remove models completely"""
+        """Rolls back the database to a previous version of plugin models.
+        Default behaviour is to remove the models completely.
+        """
         plugin_dir = current_app.extensions['plugin_manager'].plugin_folder
-        downgrade(directory=os.path.join(plugin_dir, '_migration_environment'), revision=self.settings_key + '@' + target)
+        plugin_env = os.path.join(plugin_dir, '_migration_environment')
+        plugin_rev = "{}@{}".format(self.settings_key, target)
+        downgrade(directory=plugin_env, revision=plugin_rev)
 
-    def migrate(self):
+    def migrate(self, message=None):
         """Generates new migration files for a plugin and stores them in
-        flaskbb/plugins/<plugin_folder>/migration_versions"""
+        flaskbb/plugins/<plugin_folder>/migration_versions
+
+        :param message: The message of the revision.
+        """
         with plugin_name_migrate(self.__module__):
             plugin_dir = current_app.extensions['plugin_manager'].plugin_folder
+            plugin_env = os.path.join(plugin_dir, '_migration_environment')
+            plugin_ver = os.path.join(self.path, 'migration_versions')
             try:
-                migrate(directory=os.path.join(plugin_dir, '_migration_environment'),
-                        head=self.settings_key)
+                migrate(directory=plugin_env, head=self.settings_key)
             except Exception as e:  # presumably this is the initial migration?
-                migrate(directory=os.path.join(plugin_dir, '_migration_environment'),
-                        version_path=os.path.join(self.path,'migration_versions'),
-                        branch_label=self.settings_key)
+                migrate(
+                    message=message,
+                    directory=plugin_env,
+                    version_path=plugin_ver,
+                    branch_label=self.settings_key
+                )
 
     @property
     def installable(self):
@@ -130,15 +147,17 @@ class FlaskBBPlugin(Plugin):
         return False
 
     def this_version_installed(self):
-        if self.uninstallable():
-            if Setting.as_dict(self.settings_key).get('version',None)==self.version:
+        installed_migration = Setting.\
+            as_dict(self.settings_key).\
+            get('version', None)
+
+        if self.uninstallable:
+            if installed_migration == self.version:
                 return True
             return False
         return None
 
-
-        # Some helpers
-
+    # Some helpers
     def register_blueprint(self, blueprint, **kwargs):
         """Registers a blueprint.
 

+ 9 - 10
flaskbb/plugins/_migration_environment/env.py

@@ -1,8 +1,7 @@
 from __future__ import with_statement
 from alembic import context
-from sqlalchemy import engine_from_config, pool, ext as sa_ext
+from sqlalchemy import engine_from_config, pool
 from logging.config import fileConfig
-from flaskbb.plugins import config_migrate
 import logging
 
 # this is the Alembic Config object, which provides
@@ -30,16 +29,18 @@ target_metadata = db.metadata
 # my_important_option = config.get_main_option("my_important_option")
 # ... etc.
 
-moduletables = {k.__table__.name: k.__module__ for k in db.Model._decl_class_registry.values() if
-                hasattr(k, '__table__')}
+moduletables = {k.__table__.name: k.__module__
+                for k in db.Model._decl_class_registry.values()
+                if hasattr(k, '__table__')}
+
 
 def include_object(obj, name, type_, reflected, compare_to):
-  try:
-    modname = moduletables.get(name) or getattr(obj,'_plugin',None)
+    modname = moduletables.get(name) or getattr(obj, '_plugin', None)
     if hasattr(g, 'plugin_tables'):
         return name in g.plugin_tables
     elif hasattr(g, 'plugin_name'):
-        if name == 'alembic_version': return False
+        if name == 'alembic_version':
+            return False
         if not modname:
             return False
         if modname and modname.startswith(g.plugin_name):
@@ -47,8 +48,7 @@ def include_object(obj, name, type_, reflected, compare_to):
         return False
     else:
         return False
-  except Exception as e:
-    print e
+
 
 def run_migrations_offline():
     """Run migrations in 'offline' mode.
@@ -74,7 +74,6 @@ def run_migrations_online():
 
     In this scenario we need to create an Engine
     and associate a connection with the context.
-
     """
 
     # this callback is used to prevent an auto-migration from being generated

+ 97 - 61
tests/cli/test_plugins.py

@@ -1,68 +1,91 @@
+import zipfile
+import urllib
+import os
+import shutil
+import json
 
-import click,pytest
-import zipfile,urllib,os,shutil
+import pytest
 from click.testing import CliRunner
 from flaskbb.cli import main as cli_main
 from flaskbb import plugins
-from flaskbb.extensions import db
 from importlib import import_module
-def test_new_plugin(tmpdir,application,monkeypatch):
-    runner=CliRunner()
-    #download the cookiecutter file to use locally (bypasses prompt about re-cloning)
-    zipfilename=str(tmpdir.join('cookiecutter.zip'))
-    urllib.urlretrieve('https://github.com/sh4nks/cookiecutter-flaskbb-plugin/archive/master.zip', zipfilename)
+
+
+def test_new_plugin(tmpdir, application, monkeypatch):
+    runner = CliRunner()
+    # download the cookiecutter file to use locally
+    # (bypasses prompt about re-cloning)
+    zipfilename = str(tmpdir.join('cookiecutter.zip'))
+    urllib.urlretrieve('https://github.com/sh4nks/cookiecutter-flaskbb-plugin/archive/master.zip', zipfilename)  # noqa
     with zipfile.ZipFile(zipfilename) as zf:
         zf.extractall(str(tmpdir))
     cookiecutterpath = tmpdir.join('cookiecutter-flaskbb-plugin-master')
 
-    tmp_plugin_folder=str(tmpdir.join('plugin_folder'))
+    tmp_plugin_folder = str(tmpdir.join('plugin_folder'))
     os.mkdir(tmp_plugin_folder)
-    monkeypatch.setattr(cli_main,'create_app',lambda s: application)
-    monkeypatch.setattr(application.extensions['plugin_manager'],'plugin_folder',tmp_plugin_folder)
-    input='\n'.join([
-    'Test Name',
-    'someone@nowhere.com',
-    'Testing Plugin',
-    '',
-    'TestingPlugin',
-    'Straightforward Test Plugin',
-    'www.example.com',
-    '1.0.0'])
-
-
-    result=runner.invoke(cli_main.flaskbb,['plugins','new','testplugin','--template',str(cookiecutterpath)],input=input)
+    monkeypatch.setattr(cli_main, 'create_app', lambda s: application)
+    monkeypatch.setattr(application.extensions['plugin_manager'],
+                        'plugin_folder', tmp_plugin_folder)
+    stdin = '\n'.join([
+        'Test Name',
+        'someone@nowhere.com',
+        'Testing Plugin',
+        '',
+        'TestingPlugin',
+        'Straightforward Test Plugin',
+        'www.example.com',
+        '1.0.0'
+    ])
+
+    result = runner.invoke(
+        cli_main.flaskbb,
+        ['plugins', 'new', 'testplugin', '--template', str(cookiecutterpath)],
+        input=stdin
+    )
+
     assert result.exit_code == 0
-    plugin_dir = os.path.join(application.extensions['plugin_manager'].plugin_folder, 'testing_plugin')
+    plugin_dir = os.path.join(
+        application.extensions['plugin_manager'].plugin_folder,
+        'testing_plugin'
+    )
     assert os.path.exists(plugin_dir)
     assert os.path.isdir(plugin_dir)
-    #add the temporary folder to the plugins path so import flaskbb.plugins.test_plugin works as expected
-    monkeypatch.setattr(plugins,'__path__',plugins.__path__+[tmp_plugin_folder])
-    assert import_module('flaskbb.plugins.testing_plugin').__plugin__=='TestingPlugin'
+    # add the temporary folder to the plugins path
+    # so import flaskbb.plugins.test_plugin works as expected
+    monkeypatch.setattr(
+        plugins, '__path__', plugins.__path__ + [tmp_plugin_folder]
+    )
+    assert import_module('flaskbb.plugins.testing_plugin').__plugin__ == 'TestingPlugin'  # noqa
 
-def test_migrate_plugin(tmpdir,monkeypatch,application):
 
+def test_migrate_plugin(tmpdir, monkeypatch, application):
     pluginmanager = application.extensions['plugin_manager']
-    orig_plugin_folder=pluginmanager.plugin_folder
+    orig_plugin_folder = pluginmanager.plugin_folder
     tmp_plugin_folder = str(tmpdir.join('plugin_folder'))
     os.mkdir(tmp_plugin_folder)
-    shutil.copytree(os.path.join(orig_plugin_folder,'_migration_environment'),os.path.join(tmp_plugin_folder,'_migration_environment'))
-    os.mkdir(os.path.join(tmp_plugin_folder,'testplugin'))
-    with open(os.path.join(tmp_plugin_folder,'testplugin','__init__.py'),'w') as pyfile:
+    shutil.copytree(
+        os.path.join(orig_plugin_folder, '_migration_environment'),
+        os.path.join(tmp_plugin_folder, '_migration_environment')
+    )
+    os.mkdir(os.path.join(tmp_plugin_folder, 'testplugin'))
+
+    pyfile = os.path.join(tmp_plugin_folder, 'testplugin', '__init__.py')
+    with open(pyfile, 'w') as pyfile:
         pyfile.write('\r\n'.join([
-        "from flaskbb.plugins import FlaskBBPlugin",
-        "from flaskbb.extensions import db",
-        "class TestPlugin(FlaskBBPlugin):",
-        "    settings_key='testplugin'",
-        "    def somequery(self):",
-        "        TestModel.query.all()",
-        "class TestModel(db.Model):",
-        "    __tablename__='testtable'",
-        "    testkey=db.Column(db.Integer,primary_key=True)",
-        "",
-        "__plugin__='TestPlugin'",
+            "from flaskbb.plugins import FlaskBBPlugin",
+            "from flaskbb.extensions import db",
+            "class TestPlugin(FlaskBBPlugin):",
+            "    settings_key='testplugin'",
+            "    def somequery(self):",
+            "        TestModel.query.all()",
+            "class TestModel(db.Model):",
+            "    __tablename__='testtable'",
+            "    testkey=db.Column(db.Integer,primary_key=True)",
+            "",
+            "__plugin__='TestPlugin'",
         ]))
-    import json
-    jsoninfo={
+
+    jsoninfo = {
         "identifier": "testplugin",
         "name": "TestPlugin",
         "author": "sh4nks",
@@ -71,38 +94,51 @@ def test_migrate_plugin(tmpdir,monkeypatch,application):
         "description": "A Test Plugin for FlaskBB",
         "version": "0.1"
     }
-    with open(os.path.join(tmp_plugin_folder,'testplugin','info.json'),'w') as jsonfile:
-        json.dump(jsoninfo,jsonfile)
-
+    jsonfile = os.path.join(tmp_plugin_folder, 'testplugin', 'info.json')
+    with open(jsonfile, 'w') as jsonfile:
+        json.dump(jsoninfo, jsonfile)
 
     monkeypatch.setattr(cli_main, 'create_app', lambda s: application)
 
     monkeypatch.setattr(pluginmanager, 'plugin_folder', tmp_plugin_folder)
-    # add the temporary folder to the plugins path so import flaskbb.plugins.test_plugin works as expected
-    monkeypatch.setattr(plugins, '__path__', plugins.__path__ + [tmp_plugin_folder])
-    pluginmanager._plugins=None
+    # add the temporary folder to the plugins path
+    # so import flaskbb.plugins.test_plugin works as expected
+    monkeypatch.setattr(
+        plugins, '__path__', plugins.__path__ + [tmp_plugin_folder]
+    )
+    pluginmanager._plugins = None
     pluginmanager._all_plugins = None
     pluginmanager._available_plugins = dict()
     pluginmanager._found_plugins = dict()
     pluginmanager.setup_plugins()
     assert 'testplugin' in pluginmanager.plugins
-    versionsdir=os.path.join(tmp_plugin_folder,'testplugin','migration_versions')
+    versionsdir = os.path.join(
+        tmp_plugin_folder, 'testplugin', 'migration_versions'
+    )
     assert not os.path.exists(versionsdir)
-    testplugin=pluginmanager.plugins['testplugin']
+    testplugin = pluginmanager.plugins['testplugin']
+
     with application.app_context():
         testplugin.migrate()
         assert os.path.exists(versionsdir)
+
         dirlist = os.listdir(versionsdir)
         assert dirlist
-        dirlist=[os.path.join(versionsdir,d) for d in dirlist if d.endswith('.py')]
+
+        dirlist = [os.path.join(versionsdir, d)
+                   for d in dirlist if d.endswith('.py')]
+
         for d in dirlist:
-            with open(d,'r') as f:
-                output='\n'.join([l for l in f])
+            with open(d, 'r') as f:
+                output = '\n'.join([l for l in f])
 
         assert 'testtable' in output
-        with pytest.raises(Exception,message='Should not be able to run migrations twice'):
-             testplugin.migrate()
-        with pytest.raises(Exception,message='Operations should fail as model not yet registered'):
-             testplugin.somequery()
-        testplugin.upgrade_database()
+        exception_msg = 'Should not be able to run migrations twice'
+        with pytest.raises(Exception, message=exception_msg):
+            testplugin.migrate()
 
+        exception_msg = "Operations should fail as model not yet registered"
+        with pytest.raises(Exception, message=exception_msg):
+            testplugin.somequery()
+
+        testplugin.upgrade_database()