]> scripts.mit.edu Git - wizard.git/blobdiff - wizard/app/__init__.py
Add human readable names to applications.
[wizard.git] / wizard / app / __init__.py
index e43a237fd529671c9f5714b2d3f21c5dea1f5117..b653f524074eb5a3038041f6baa8e9f1473be1b0 100644 (file)
@@ -46,39 +46,17 @@ import string
 import urlparse
 import tempfile
 import pkg_resources
+import traceback
 
 import wizard
-from wizard import resolve, shell, sql, util
-
-# SCRIPTS SPECIFIC
-_scripts_application_list = [
-    "mediawiki", "wordpress", "joomla", "e107", "gallery2",
-    "phpBB", "advancedbook", "phpical", "trac", "turbogears", "django",
-    "rails",
-    # these are technically deprecated
-    "advancedpoll", "gallery",
-]
-def _scripts_make(name):
-    """Makes an application, but uses the correct subtype if available."""
-    try:
-        __import__("wizard.app." + name)
-        return getattr(wizard.app, name).Application(name)
-    except ImportError as error:
-        # XXX ugly hack to check if the import error is from the top level
-        # module we care about or a submodule. should be an archetectural change.
-        if error.args[0].split()[-1]==name:
-            return Application(name)
-        else:
-            raise
+from wizard import plugin, resolve, shell, sql, util
 
 _applications = None
 def applications():
     """Hash table for looking up string application name to instance"""
     global _applications
     if not _applications:
-        # SCRIPTS SPECIFIC
-        _applications = dict([(n,_scripts_make(n)) for n in _scripts_application_list ])
-        # setup plugins
+        _applications = dict()
         for dist in pkg_resources.working_set:
             for appname, entry in dist.get_entry_map("wizard.app").items():
                 if appname in _applications:
@@ -89,11 +67,23 @@ def applications():
                     appname = newname
                 appclass = entry.load()
                 _applications[appname] = appclass(appname)
+        # setup dummy apps
+        for entry in pkg_resources.iter_entry_points("wizard.dummy_apps"):
+            appfun = entry.load()
+            dummy_apps = appfun()
+            for appname in dummy_apps:
+                # a dummy app that already exists is not a fatal error
+                if appname in _applications:
+                    continue
+                _applications[appname] = Application(appname)
     return _applications
 
 def getApplication(appname):
     """Retrieves application instance given a name"""
-    return applications()[appname]
+    try:
+        return applications()[appname]
+    except KeyError:
+        raise NoSuchApplication(appname)
 
 class Application(object):
     """
@@ -106,6 +96,8 @@ class Application(object):
     """
     #: String name of the application
     name = None
+    #: Human-readable name of the application
+    fullname = None
     #: Dictionary of version strings to :class:`ApplicationVersion`.
     #: See also :meth:`makeVersion`.
     versions = None
@@ -574,12 +566,9 @@ class ApplicationVersion(object):
         Makes/retrieves a singleton :class:`ApplicationVersion` from
         a``app`` and ``version`` string.
         """
-        try:
-            # defer to the application for version creation to enforce
-            # singletons
-            return applications()[app].makeVersion(version)
-        except KeyError:
-            raise NoSuchApplication(app)
+        # defer to the application for version creation to enforce
+        # singletons
+        return getApplication(app).makeVersion(version)
 
 def expand_re(val):
     """
@@ -709,78 +698,24 @@ def filename_regex_substitution(key, files, regex):
         return subs
     return h
 
-def backup_database(outdir, deployment):
+@decorator.decorator
+def throws_database_errors(f, self, *args, **kwargs):
     """
-    Generic database backup function for MySQL.
+    Decorator that takes database errors from :mod:`wizard.sql` and
+    converts them into application script failures from
+    :mod:`wizard.app`.  We can't throw application errors directly from
+    :mod:`wizard.sql` because that would result in a cyclic import;
+    also, it's cleaner to distinguish between a database error and an
+    application script failure.
     """
-    # XXX: Change this once deployments support multiple dbs
-    if deployment.application.database == "mysql":
-        return backup_mysql_database(outdir, deployment)
-    else:
-        raise NotImplementedError
-
-def backup_mysql_database(outdir, deployment):
-    """
-    Database backups for MySQL using the :command:`mysqldump` utility.
-    """
-    outfile = os.path.join(outdir, "db.sql")
     try:
-        shell.call("mysqldump", "--compress", "-r", outfile, *get_mysql_args(deployment.dsn))
-        shell.call("gzip", "--best", outfile)
-    except shell.CallError as e:
-        raise BackupFailure(e.stderr)
-
-def restore_database(backup_dir, deployment):
-    """
-    Generic database restoration function for MySQL.
-    """
-    # XXX: see backup_database
-    if deployment.application.database == "mysql":
-        return restore_mysql_database(backup_dir, deployment)
-    else:
-        raise NotImplementedError
-
-def restore_mysql_database(backup_dir, deployment):
-    """
-    Database restoration for MySQL by piping SQL commands into :command:`mysql`.
-    """
-    if not os.path.exists(backup_dir):
-        raise RestoreFailure("Backup %s doesn't exist", backup_dir.rpartition("/")[2])
-    sql = open(os.path.join(backup_dir, "db.sql"), 'w+')
-    shell.call("gunzip", "-c", os.path.join(backup_dir, "db.sql.gz"), stdout=sql)
-    sql.seek(0)
-    shell.call("mysql", *get_mysql_args(deployment.dsn), stdin=sql)
-    sql.close()
-
-# XXX: SCRIPTS
-def remove_database(deployment):
-    """
-    Generic database removal function.  Actually, not so generic because we
-    go and check if we're on scripts and if we are run a different command.
-    """
-    if deployment.dsn.host == "sql.mit.edu":
-        try:
-            shell.call("/mit/scripts/sql/bin/drop-database", deployment.dsn.database)
-            return
-        except shell.CallError:
-            pass
-    engine = sqlalchemy.create_engine(deployment.dsn)
-    engine.execute("DROP DATABASE `%s`" % deployment.dsn.database)
-
-def get_mysql_args(dsn):
-    """
-    Extracts arguments that would be passed to the command line mysql utility
-    from a deployment.
-    """
-    args = []
-    if dsn.host:
-        args += ["-h", dsn.host]
-    if dsn.username:
-        args += ["-u", dsn.username]
-    if dsn.password:
-        args += ["-p" + dsn.password]
-    args += [dsn.database]
-    return args
+        return f(self, *args, **kwargs)
+    except sql.BackupDatabaseError:
+        raise BackupFailure(traceback.format_exc())
+    except sql.RestoreDatabaseError:
+        raise RestoreFailure(traceback.format_exc())
+    except sql.RemoveDatabaseError:
+        raise RemoveFailure(traceback.format_exc())
 
 class Error(wizard.Error):
     """Generic error class for this module."""
@@ -809,6 +744,8 @@ class DeploymentParseError(Error):
     location = None
     def __init__(self, value):
         self.value = value
+    def __str__(self):
+        return "Could not parse '%s' from versions store in '%s'" % (self.value, self.location)
 
 class NoSuchApplication(Error):
     """
@@ -822,6 +759,8 @@ class NoSuchApplication(Error):
     location = None
     def __init__(self, app):
         self.app = app
+    def __str__(self):
+        return "Wizard doesn't know about an application named '%s'." % self.app
 
 class Failure(Error):
     """