]> scripts.mit.edu Git - wizard.git/blobdiff - wizard/app/__init__.py
Remove string exception from remaster.
[wizard.git] / wizard / app / __init__.py
index 6e4b561410b62cce2caa389d2efef6ea54c4e43b..6a5ee6a05ac00588e33df8af2961cf9e5020dfa6 100644 (file)
@@ -46,39 +46,17 @@ import string
 import urlparse
 import tempfile
 import pkg_resources
 import urlparse
 import tempfile
 import pkg_resources
+import traceback
 
 import wizard
 
 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:
 
 _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:
         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)
                     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
 
 def getApplication(appname):
     """Retrieves application instance given a name"""
-    return applications()[appname]
+    try:
+        return applications()[appname]
+    except KeyError:
+        raise NoSuchApplication(appname)
 
 class Application(object):
     """
 
 class Application(object):
     """
@@ -186,7 +176,7 @@ class Application(object):
     def dsn(self, deployment):
         """
         Returns the deployment specific database URL.  Uses the override file
     def dsn(self, deployment):
         """
         Returns the deployment specific database URL.  Uses the override file
-        in :file:`.scripts` if it exists, and otherwise attempt to extract the
+        in :file:`.wizard` if it exists, and otherwise attempt to extract the
         variables from the source files.
 
         Under some cases, the database URL will contain only the database
         variables from the source files.
 
         Under some cases, the database URL will contain only the database
@@ -232,7 +222,7 @@ class Application(object):
     def url(self, deployment):
         """
         Returns the deployment specific web URL.  Uses the override file
     def url(self, deployment):
         """
         Returns the deployment specific web URL.  Uses the override file
-        in :file:`.scripts` if it exists, and otherwise attempt to extract
+        in :file:`.wizard` if it exists, and otherwise attempt to extract
         the variables from the source files.
 
         This function might return ``None``, which indicates we couldn't figure
         the variables from the source files.
 
         This function might return ``None``, which indicates we couldn't figure
@@ -349,7 +339,7 @@ class Application(object):
         """
         for key, subst in self.substitutions.items():
             subs = subst(deployment)
         """
         for key, subst in self.substitutions.items():
             subs = subst(deployment)
-            if not subs and key not in self.deprecated_keys:
+            if not subs and key not in self.deprecated_keys and key not in self.random_keys:
                 logging.warning("No substitutions for %s" % key)
     def install(self, version, options):
         """
                 logging.warning("No substitutions for %s" % key)
     def install(self, version, options):
         """
@@ -480,8 +470,8 @@ class Application(object):
         Subclasses should provide an implementation.
         """
         # XXX: Unfortunately, this doesn't quite work because we package
         Subclasses should provide an implementation.
         """
         # XXX: Unfortunately, this doesn't quite work because we package
-        # bogus config files in the -scripts versions of installs.  Maybe
-        # we should check a hash or something?
+        # bogus config files.  Maybe we should check a hash or
+        # something?
         raise NotImplementedError
     def researchFilter(self, filename, added, deleted):
         """
         raise NotImplementedError
     def researchFilter(self, filename, added, deleted):
         """
@@ -520,10 +510,11 @@ class ApplicationVersion(object):
         """
         return "%s-%s" % (self.application, self.version)
     @property
         """
         return "%s-%s" % (self.application, self.version)
     @property
-    def scripts_tag(self):
+    def wizard_tag(self):
         """
         Returns the name of the Git tag for this version.
         """
         """
         Returns the name of the Git tag for this version.
         """
+        # XXX: Scripts specific
         end = str(self.version).partition('-scripts')[2].partition('-')[0]
         return "%s-scripts%s" % (self.pristine_tag, end)
     @property
         end = str(self.version).partition('-scripts')[2].partition('-')[0]
         return "%s-scripts%s" % (self.pristine_tag, end)
     @property
@@ -573,12 +564,9 @@ class ApplicationVersion(object):
         Makes/retrieves a singleton :class:`ApplicationVersion` from
         a``app`` and ``version`` string.
         """
         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):
     """
 
 def expand_re(val):
     """
@@ -708,77 +696,24 @@ def filename_regex_substitution(key, files, regex):
         return subs
     return h
 
         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:
     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()
-
-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."""
 
 class Error(wizard.Error):
     """Generic error class for this module."""
@@ -807,6 +742,8 @@ class DeploymentParseError(Error):
     location = None
     def __init__(self, value):
         self.value = value
     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):
     """
 
 class NoSuchApplication(Error):
     """
@@ -820,6 +757,8 @@ class NoSuchApplication(Error):
     location = None
     def __init__(self, app):
         self.app = app
     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):
     """
 
 class Failure(Error):
     """