]> scripts.mit.edu Git - wizard.git/commitdiff
Refactor documentation in wizard.install
authorEdward Z. Yang <ezyang@mit.edu>
Thu, 29 Oct 2009 17:09:53 +0000 (13:09 -0400)
committerEdward Z. Yang <ezyang@mit.edu>
Thu, 29 Oct 2009 17:09:53 +0000 (13:09 -0400)
Signed-off-by: Edward Z. Yang <ezyang@mit.edu>
doc/module/wizard.install.rst
wizard/install/__init__.py

index 24d5dc8349ff6d920fe7f87c5e9eea667416ba4a..3d02c6161cf72a1cb552a664bcda5c55dd81c4f7 100644 (file)
@@ -45,6 +45,8 @@ Functions
 Exceptions
 ----------
 .. autoexception:: Error
+.. autoexception:: Failure
+    :show-inheritance:
 .. autoexception:: StrategyFailed
     :show-inheritance:
 .. autoexception:: UnrecognizedPreloads
index 16ecf88b3ff3976e66603b024c8f43643df7a667..36e90858c82e252fac1b8977821a5c4584b45439 100644 (file)
@@ -1,58 +1,19 @@
 """
-This module contains an object model for specifying "required options",
-also known as "Args".  While the format of this schema is inspired
-by :mod:`optparse`, this is not a controller (that is the job
-of :mod:`wizard.install` submodules); it merely is a schema
-that controllers can consume in order to determine their desired
-behavior.
-
-An :class:`Arg` is the simplest unit of this
-model, and merely represents some named argument that an installer
-script needs in order to finish the installation (i.e., the password
-to the database, or the name of the new application).  Instances
-of :class:`Arg` can be registered to the :class:`ArgHandler`, which
-manages marshalling these objects to whatever object
-is actually managing user input.  An argument is any valid Python
-variable name, usually categorized using underscores (i.e.
-``admin_user``); the argument capitalized and with ``WIZARD_`` prepended
-to it indicates a corresponding environment variable, i.e.
-``WIZARD_ADMIN_USER``.  Arguments must be unique; applications
-that define custom arguments are expected to namespace them.
-
-Because autoinstallers will often have a number of themed
-arguments (i.e. MySQL credentials) that are applicable across
-autoinstallers, :class:`ArgSet` can be use to group :class:`Arg`
-instances together, as well as promote reuse of these arguments.
-There are a number of precanned :class:`ArgSet` subclasses
-that serve this purpose, such as :class:`MysqlArgSet`.
-:class:`ArgHandler` also contains some convenience syntax in its
-constructor for loading predefined instances of :class:`ArgSet`.
-
-Certain arguments will vary from install to install, but
-can be automatically calculated if certain assumptions about the
-server environment are made.  For example, an application might
-request an email; if we are on an Athena machine, one would
-reasonably expect the currently logged in user + @mit.edu to be
-a valid email address.  :class:`Strategy` objects are responsible
-for this sort of calculation, and may be attached to any
-:class:`ArgSet` instance. (If you would like to attach a strategy
-to a single arg, you should put the arg in a :class:`ArgSet` and
-then set the strategy).
-
-Finally, certain :class:`Strategy` objects may perform operations
-with side effects (as marked by :attr:`Strategy.side_effects`).
-The primary use case for this is automatic creation of databases
-during an autoinstall.  Marking a :class:`Strategy` as having
-side effects is important, so as to delay executing it until
-absolutely necessary (at the end of options parsing, but before
-the actual installation begins).
-
-.. note:
-
-    Because Wizard is eventually intended for public use,
-    some hook mechanism for overloading the default strategies will
-    need to be created.  Setting up environment variables may act
-    as a vaguely reasonable workaround in the interim.
+This module deals with marshalling information from the user to the install
+process for an application.  We divide this process into two parts: this module
+addresses the specification of what fields the application is requesting, and
+submodules implement controller logic for actually getting this information
+from the user.  Common logic between controllers is stored in this module.
+
+:class:`ArgSchema` is composed of two orthogonal components: a dictionary of
+:class:`Arg` objects (which may be organized using :class:`ArgSet`) and a list
+of :class:`Strategy` objects.  An :class:`Arg` contains information about a
+given argument, and specified at compile-time by an application, while a
+:class:`Strategy` contains a possible procedure for automatically determining
+what the contents of some argument are, and is specified at run-time by the
+user (this is not quite true yet, but will be soon).  Some arguments are most
+commonly used together, so we group them together as an :class:`ArgSet` and
+allow applications to refer to them as a single name.
 
 .. testsetup:: *
 
@@ -65,6 +26,7 @@ import logging
 import wizard
 from wizard import scripts, shell, util
 
+# XXX: This is in the wrong place
 def fetch(options, path, post=None):
     """
     Fetches a web page from the autoinstall, usually to perform database
@@ -87,7 +49,20 @@ def preloads():
             }
 
 class Strategy(object):
-    """Represents a strategy for calculating arg values without user input."""
+    """
+    Represents a strategy for calculating arg values without user input.
+
+    Under many circumstances, making some assumptions about the server
+    environment means that we don't actually have to ask the user for values
+    such as the host or the path: these tend to be side effect free strategies.
+    Furthermore, we may have utility scripts present that can automatically
+    configure a new database for a user when one is necessary: these are side
+    effectful computations.
+
+    Note for an implementor: it is perfectly acceptable to calculate preliminary
+    results in :meth:`prepare`, store them as underscore prefixed variables,
+    and refer to them from :meth:`execute`.
+    """
     #: Arguments that this strategy provides
     provides = frozenset()
     #: Whether or not this strategy has side effects.
@@ -103,12 +78,16 @@ class Strategy(object):
         """
         Performs effectful computations associated with this strategy,
         and mutates ``options`` with the new values.  Behavior is
-        undefined if :meth:`prepare` was not called first.
+        undefined if :meth:`prepare` was not called first.  If this
+        method throws an exception, it should be treated as fatal.
         """
         raise NotImplemented
 
 class EnvironmentStrategy(Strategy):
-    """Fills in values from environment variables."""
+    """
+    Fills in values from environment variables, based off of
+    :attr:`Arg.envname` from ``schema``.
+    """
     def __init__(self, schema):
         self.provides = set()
         self.envlookup = {}
@@ -178,10 +157,7 @@ class ScriptsEmailStrategy(Strategy):
 
 class Arg(object):
     """
-    Represent a required, named argument for installation.  These
-    cannot have strategies associated with them, so if you'd like
-    to have a strategy associated with a single argument, create
-    an :class:`ArgSet` with one item in it.
+    Represent a required, named argument for installation.
     """
     #: Attribute name of the argument
     name = None
@@ -190,7 +166,7 @@ class Arg(object):
     #: String "type" of the argument, used for metavar
     type = None
     #: If true, is a password
-    password = None
+    password = False
     @property
     def option(self):
         """Full string of the option."""
@@ -199,16 +175,12 @@ class Arg(object):
     def envname(self):
         """Name of the environment variable containing this arg."""
         return 'WIZARD_' + self.name.upper()
-    def prompt(self, options):
-        """Interactively prompts for a value and sets it to options."""
-        # XXX: put a sane default implementation; we'll probably need
-        # "big" descriptions for this, since 'help' is too sparse.
-        pass
-    def __init__(self, name, password=False, type=None, help=None):
+    def __init__(self, name, **kwargs):
         self.name = name
-        self.password = password
-        self.help = help or "UNDOCUMENTED"
-        self.type = type
+        for k,v in kwargs.items(): # cuz I'm lazy
+            if not hasattr(self, k):
+                raise TypeError("Arg() got unexpected keyword argument '%s'" % k)
+            setattr(self, k, v)
 
 class ArgSet(object):
     """
@@ -216,7 +188,7 @@ class ArgSet(object):
     for an installation to complete successfully.  Arguments in a set
     should share a common prefix and be related in functionality (the
     litmus test is if you need one of these arguments, you should need
-    all of them).
+    all of them).  Register them in :func:`preloads`.
     """
     #: The :class:`Arg` objects that compose this argument set.
     args = None
@@ -272,15 +244,14 @@ class ArgSchema(object):
 
     Example::
 
-        parser = ArgHandler("sql", "admin", "email")
+        parser = ArgHandler("mysql", "admin", "email")
         parser.add(Arg("title", help="Title of the new application"))
     """
     #: Dictionary of argument names to :class:`Arg` objects in schema.
     args = None
-    #: List of :class:`ArgStrategy` objects in schema.
+    #: List of :class:`Strategy` objects in schema.
     strategies = None
-    #: Set of arguments that are already provided.  (This doesn't
-    #: say how to get them: probably running strategies or environment variables.)
+    #: Set of arguments that are already provided by :attr:`strategies`.
     provides = None
     def __init__(self, *args):
         self.args = {}
@@ -342,6 +313,7 @@ class Error(wizard.Error):
     """Base error class for this module."""
     pass
 
+# XXX: This is in the wrong place
 class Failure(Error):
     """Installation failed."""
     pass