import os
import logging
+import sqlalchemy
+import warnings
import wizard
from wizard import scripts, shell, util
+def dsn_callback(options):
+ if not isinstance(options.dsn, sqlalchemy.engine.url.URL):
+ options.dsn = sqlalchemy.engine.url.make_url(options.dsn)
+ # perform some sanity checks on the database
+ database = options.dsn.database
+ options.dsn.database = None
+ engine = sqlalchemy.create_engine(options.dsn)
+ # generates warnings http://groups.google.com/group/sqlalchemy/browse_thread/thread/b7123fefb7dd83d5
+ with warnings.catch_warnings():
+ warnings.simplefilter("ignore")
+ engine.execute("CREATE DATABASE IF NOT EXISTS `%s`" % database)
+ options.dsn.database = database
+ # XXX: another good thing to check might be that the database is empty
+
# XXX: This is in the wrong place
def fetch(options, path, post=None):
"""
"""
return {
'web': WebArgSet(),
- 'mysql': MysqlArgSet(),
+ 'db': DbArgSet(),
'admin': AdminArgSet(),
'email': EmailArgSet(),
'title': TitleArgSet(),
self.dir = dir
def prepare(self):
"""Uses :func:`wizard.scripts.get_web_host_and_path`."""
- self._tuple = scripts.get_web_host_and_path(self.dir)
- if not self._tuple:
+ self._url = scripts.fill_url(self.dir, None)
+ if not self._url:
raise StrategyFailed
def execute(self, options):
"""No-op."""
- options.web_host, options.web_path = self._tuple
+ options.web_host = self._url.netloc # pylint: disable-msg=E1101
+ options.web_path = self._url.path # pylint: disable-msg=E1101
+ options.web_inferred = True # hacky: needed to see if we need a .scripts/url file
class ScriptsMysqlStrategy(Strategy):
"""
may create an appropriate database for the user.
"""
side_effects = True
- provides = frozenset(["mysql_host", "mysql_user", "mysql_password", "mysql_db"])
- def __init__(self, dir):
+ provides = frozenset(["dsn"])
+ def __init__(self, application, dir):
+ self.application = application
self.dir = dir
def prepare(self):
"""Uses :func:`wizard.scripts.get_sql_credentials`"""
- self._triplet = scripts.get_sql_credentials()
- if not self._triplet:
+ if self.application.database != "mysql":
+ raise StrategyFailed
+ try:
+ self._triplet = shell.eval("/mit/scripts/sql/bin/get-password").split()
+ except shell.CallError:
raise StrategyFailed
self._username = os.getenv('USER')
if self._username is None:
raise StrategyFailed
def execute(self, options):
"""Creates a new database for the user using ``get-next-database`` and ``create-database``."""
- sh = shell.Shell()
- options.mysql_host, options.mysql_user, options.mysql_password = self._triplet
+ host, username, password = self._triplet
# race condition
- options.mysql_db = self._username + '+' + sh.eval("/mit/scripts/sql/bin/get-next-database", os.path.basename(self.dir))
- sh.call("/mit/scripts/sql/bin/create-database", options.mysql_db)
+ database = self._username + '+' + shell.eval("/mit/scripts/sql/bin/get-next-database", os.path.basename(self.dir))
+ shell.call("/mit/scripts/sql/bin/create-database", database)
+ options.dsn = sqlalchemy.engine.url.URL("mysql", username=username, password=password, host=host, database=database)
class ScriptsEmailStrategy(Strategy):
"""Performs script specific guess for email."""
type = None
#: If true, is a password
password = False
- @property
- def option(self):
- """Full string of the option."""
- return attr_to_option(self.name)
+ #: Callback that this argument wants to get run on options after finished
+ callback = None
@property
def envname(self):
"""Name of the environment variable containing this arg."""
"""
#: The :class:`Arg` objects that compose this argument set.
args = None
+ # XXX: probably could also use a callback attribute
def __init__(self):
self.args = []
Arg("web_path", type="PATH", help="Relative path to your application root"),
]
-class MysqlArgSet(ArgSet):
- """Common arguments for applications that use a MySQL database."""
+class DbArgSet(ArgSet):
+ """Common arguments for applications that use a database."""
def __init__(self):
self.args = [
- Arg("mysql_host", type="HOST", help="Host that your MySQL server lives on"),
- Arg("mysql_db", type="DB", help="Name of the database to populate"),
- Arg("mysql_user", type="USER", help="Name of user to access database with"),
- Arg("mysql_password", type="PWD", password=True, help="Password of the database user"),
+ Arg("dsn", type="DSN", help="Database Source Name, i.e. mysql://user:pass@host/dbname", callback=dsn_callback),
]
class AdminArgSet(ArgSet):
* ``admin``, which populates the options ``admin_name`` and
``admin_password``.
* ``email``, which populates the option ``email``.
+ * ``title``, which populates the option ``title``.
The options ``web_path`` and ``web_host`` are automatically required.
def add(self, arg):
"""Adds an argument to our schema."""
self.args[arg.name] = arg
- def commit(self, dir):
+ def commit(self, application, dir):
"""Populates :attr:`strategies` and :attr:`provides`"""
self.strategies = []
self.provides = set()
raw_strategies = [
EnvironmentStrategy(self),
ScriptsWebStrategy(dir),
- ScriptsMysqlStrategy(dir),
+ ScriptsMysqlStrategy(application, dir),
ScriptsEmailStrategy(),
]
for arg in self.args.values():
Load values from strategy. Must be called after :meth:`commit`. We
omit strategies whose provided variables are completely specified
already. Will raise :exc:`MissingRequiredParam` if strategies aren't
- sufficient to fill all options.
+ sufficient to fill all options. It will then run any callbacks on
+ arguments.
"""
unfilled = set(name for name in self.args if getattr(options, name) is None)
missing = unfilled - self.provides
if getattr(options, name) is not None:
logging.warning("Overriding pre-specified value for %s", name)
strategy.execute(options)
+ for arg in self.args.values():
+ if arg.callback is None:
+ continue
+ arg.callback(options)
class Error(wizard.Error):
"""Base error class for this module."""
pass
-# XXX: This is in the wrong place
-class Failure(Error):
- """Installation failed."""
- pass
-
class StrategyFailed(Error):
"""Strategy couldn't figure out values."""
pass