Latest Version: 0.9.6.2
/Users/bbangert/Programming/Python/pylons/pylons/commands.py
0001"""Paster Commands, for use with paster in your project
0002
0003The command(s) listed here are for use with Paste to enable easy creation of
0004various core Pylons templates.
0005
0006Currently available commands are::
0007
0008    controller, restcontroller, shell
0009"""
0010import os
0011import sys
0012
0013import paste.fixture
0014import paste.registry
0015import paste.deploy.config
0016from paste.deploy import loadapp, appconfig
0017from paste.script.command import Command, BadCommand
0018from paste.script.filemaker import FileOp
0019from paste.script.pluginlib import find_egg_info_dir
0020
0021import pylons.util as util
0022
0023__all__ = ['ControllerCommand', 'RestControllerCommand', 'ShellCommand']
0024
0025def can_import(name):
0026    """Attempt to __import__ the specified package/module, returning True when
0027    succeeding, otherwise False"""
0028    try:
0029        __import__(name)
0030        return True
0031    except ImportError:
0032        return False
0033
0034
0035def is_minimal_template(package):
0036    """Determine if the specified Pylons project (package) uses the Pylons
0037    Minimal Tempalte"""
0038    minimal_template = False
0039    try:
0040        # Check if PACKAGE.lib.base exists
0041        __import__(package + '.lib.base')
0042    except ImportError, ie:
0043        if 'No module named lib.base' in str(ie):
0044            minimal_template = True
0045    except:
0046        # PACKAGE.lib.base exists but throws an error
0047        pass
0048    return minimal_template
0049
0050
0051def validate_name(name):
0052    """Validate that the name for the controller isn't present on the
0053    path already"""
0054    if not name:
0055        # This happens when the name is an existing directory
0056        raise BadCommand('Please give the name of a controller.')
0057    # 'setup' is a valid controller name, but when paster controller is ran
0058    # from the root directory of a project, importing setup will import the
0059    # project's setup.py causing a sys.exit(). Blame relative imports
0060    if name != 'setup' and can_import(name):
0061        raise BadCommand(
0062            "\n\nA module named '%s' is already present in your "
0063            "PYTHON_PATH.\nChoosing a conflicting name will likely cause "
0064            "import problems in\nyour controller at some point. It's "
0065            "suggested that you choose an\nalternate name, and if you'd "
0066            "like that name to be accessible as\n'%s', add a route "
0067            "to your projects config/routing.py file similar\nto:\n"
0068            "    map.connect('%s', controller='my_%s')"               % (name, name, name, name))
0070    return True
0071
0072
0073class ControllerCommand(Command):
0074    """Create a Controller and accompanying functional test
0075
0076    The Controller command will create the standard controller template
0077    file and associated functional test to speed creation of controllers.
0078
0079    Example usage::
0080
0081        yourproj% paster controller comments
0082        Creating yourproj/yourproj/controllers/comments.py
0083        Creating yourproj/yourproj/tests/functional/test_comments.py
0084
0085    If you'd like to have controllers underneath a directory, just include
0086    the path as the controller name and the necessary directories will be
0087    created for you::
0088
0089        yourproj% paster controller admin/trackback
0090        Creating yourproj/controllers/admin
0091        Creating yourproj/yourproj/controllers/admin/trackback.py
0092        Creating yourproj/yourproj/tests/functional/test_admin_trackback.py
0093    """
0094    summary = __doc__.splitlines()[0]
0095    usage = '\n' + __doc__
0096
0097    min_args = 1
0098    max_args = 1
0099    group_name = 'pylons'
0100
0101    default_verbosity = 3
0102
0103    parser = Command.standard_parser(simulate=True)
0104    parser.add_option('--no-test',
0105                      action='store_true',
0106                      dest='no_test',
0107                      help="Don't create the test; just the controller")
0108
0109    def command(self):
0110        """Main command to create controller"""
0111        try:
0112            file_op = FileOp(source_dir=os.path.join(
0113                os.path.dirname(__file__), 'templates'))
0114            try:
0115                name, directory = file_op.parse_path_name_args(self.args[0])
0116            except:
0117                raise BadCommand('No egg_info directory was found')
0118
0119            # Check the name isn't the same as the package
0120            base_package = file_op.find_dir('controllers', True)[0]
0121            if base_package.lower() == name.lower():
0122                raise BadCommand(
0123                    'Your controller name should not be the same as '
0124                    'the package name %r.' % base_package)
0125            # Validate the name
0126            name = name.replace('-', '_')
0127            validate_name(name)
0128
0129            # Determine the module's import statement
0130            if is_minimal_template(base_package):
0131                importstatement = "from %s.controllers import *" % base_package
0132            else:
0133                importstatement = "from %s.lib.base import *" % base_package
0134
0135            # Setup the controller
0136            fullname = os.path.join(directory, name)
0137            controller_name = util.class_name_from_module_name(
0138                name.split('/')[-1])
0139            if not fullname.startswith(os.sep):
0140                fullname = os.sep + fullname
0141            testname = fullname.replace(os.sep, '_')[1:]
0142            file_op.template_vars.update(
0143                {'name': controller_name,
0144                 'fname': os.path.join(directory, name),
0145                 'importstatement': importstatement})
0146            file_op.copy_file(template='controller.py_tmpl',
0147                         dest=os.path.join('controllers', directory),
0148                         filename=name)
0149            if not self.options.no_test:
0150                file_op.copy_file(template='test_controller.py_tmpl',
0151                             dest=os.path.join('tests', 'functional'),
0152                             filename='test_'+testname)
0153        except BadCommand, e:
0154            raise BadCommand('An error occurred. %s' % e)
0155        except:
0156            msg = str(sys.exc_info()[1])
0157            raise BadCommand('An unknown error occurred. %s' % msg)
0158
0159
0160class RestControllerCommand(Command):
0161    """Create a REST Controller and accompanying functional test
0162
0163    The RestController command will create a REST-based Controller file for use
0164    with the map.resource REST-based dispatching. This template includes the
0165    methods that map.resource dispatches to in addition to doc strings for
0166    clarification on when the methods will be called.
0167
0168    The first argument should be the singular form of the REST resource. The
0169    second argument is the plural form of the word. If its a nested controller,
0170    put the directory information in front as shown in the second example
0171    below.
0172
0173    Example usage::
0174
0175        yourproj% paster restcontroller comment comments
0176        Creating yourproj/yourproj/controllers/comments.py
0177        Creating yourproj/yourproj/tests/functional/test_comments.py
0178
0179    If you'd like to have controllers underneath a directory, just include
0180    the path as the controller name and the necessary directories will be
0181    created for you::
0182
0183        yourproj% paster restcontroller admin/tracback admin/trackbacks
0184        Creating yourproj/controllers/admin
0185        Creating yourproj/yourproj/controllers/admin/trackbacks.py
0186        Creating yourproj/yourproj/tests/functional/test_admin_trackbacks.py
0187    """
0188    summary = __doc__.splitlines()[0]
0189    usage = '\n' + __doc__
0190
0191    min_args = 2
0192    max_args = 2
0193    group_name = 'pylons'
0194
0195    default_verbosity = 3
0196
0197    parser = Command.standard_parser(simulate=True)
0198    parser.add_option('--no-test',
0199                      action='store_true',
0200                      dest='no_test',
0201                      help="Don't create the test; just the controller")
0202
0203    def command(self):
0204        """Main command to create controller"""
0205        try:
0206            file_op = FileOp(source_dir=os.path.join(
0207                os.path.dirname(__file__), 'templates'))
0208            try:
0209                singularname, singulardirectory =                       file_op.parse_path_name_args(self.args[0])
0211                pluralname, pluraldirectory =                       file_op.parse_path_name_args(self.args[1])
0213            except:
0214                raise BadCommand('No egg_info directory was found')
0215
0216            # Check the name isn't the same as the package
0217            base_package = file_op.find_dir('controllers', True)[0]
0218            if base_package.lower() == pluralname.lower():
0219                raise BadCommand(
0220                    'Your controller name should not be the same as '
0221                    'the package name %r.'% base_package)
0222            # Validate the name
0223            for name in [singularname, pluralname]:
0224                name = name.replace('-', '_')
0225                validate_name(name)
0226
0227            # Determine the module's import statement
0228            if is_minimal_template(base_package):
0229                importstatement = "from %s.controllers import *" % base_package
0230            else:
0231                importstatement = "from %s.lib.base import *" % base_package
0232
0233            # Setup the controller
0234            fullname = os.path.join(pluraldirectory, pluralname)
0235            controller_name = util.class_name_from_module_name(
0236                pluralname.split('/')[-1])
0237            if not fullname.startswith(os.sep):
0238                fullname = os.sep + fullname
0239            testname = fullname.replace(os.sep, '_')[1:]
0240
0241            nameprefix = ''
0242            if pluraldirectory:
0243                nameprefix = pluraldirectory.replace(os.path.sep, '_') + '_'
0244
0245            controller_c = ''
0246            if nameprefix:
0247                controller_c = ", controller='%s', \n\t" %                       '/'.join([pluraldirectory, pluralname])
0249                controller_c += "path_prefix='/%s', name_prefix='%s_'" %                       (pluraldirectory, pluraldirectory)
0251            command = "map.resource('%s', '%s'%s)\n" %                   (singularname, pluralname, controller_c)
0253
0254            file_op.template_vars.update(
0255                {'classname': controller_name,
0256                 'pluralname': pluralname,
0257                 'singularname': singularname,
0258                 'name': controller_name,
0259                 'nameprefix': nameprefix,
0260                 'resource_command': command.replace('\n\t', '\n%s#%s' %                                                            (' '*4, ' '*9)),
0262                 'fname': os.path.join(pluraldirectory, pluralname),
0263                 'importstatement': importstatement})
0264
0265            resource_command = ("\nTo create the appropriate RESTful mapping, "
0266                                "add a map statement to your\n")
0267            resource_command += ("config/routing.py file near the top like "
0268                                 "this:\n\n")
0269            resource_command += command
0270            file_op.copy_file(template='restcontroller.py_tmpl',
0271                         dest=os.path.join('controllers', pluraldirectory),
0272                         filename=pluralname)
0273            if not self.options.no_test:
0274                file_op.copy_file(template='test_controller.py_tmpl',
0275                             dest=os.path.join('tests', 'functional'),
0276                             filename='test_'+testname)
0277            print resource_command
0278        except BadCommand, e:
0279            raise BadCommand('An error occurred. %s' % e)
0280        except:
0281            msg = str(sys.exc_info()[1])
0282            raise BadCommand('An unknown error occurred. %s' % msg)
0283
0284
0285class ShellCommand(Command):
0286    """Open an interactive shell with the Pylons app loaded
0287
0288    The optional CONFIG_FILE argument specifies the config file to use for
0289    the interactive shell. CONFIG_FILE defaults to 'development.ini'.
0290
0291    This allows you to test your mapper, models, and simulate web requests
0292    using ``paste.fixture``.
0293
0294    Example::
0295
0296        $ paster shell my-development.ini
0297    """
0298    summary = __doc__.splitlines()[0]
0299    usage = '\n' + __doc__
0300
0301    min_args = 0
0302    max_args = 1
0303    group_name = 'pylons'
0304
0305    parser = Command.standard_parser(simulate=True)
0306    parser.add_option('-d', '--disable-ipython',
0307                      action='store_true',
0308                      dest='disable_ipython',
0309                      help="Don't use IPython if it is available")
0310
0311    parser.add_option('-q',
0312                      action='count',
0313                      dest='quiet',
0314                      default=0,
0315                      help="Do not load logging configuration from the config file")
0316
0317    def command(self):
0318        """Main command to create a new shell"""
0319        self.verbose = 3
0320        if len(self.args) == 0:
0321            # Assume the .ini file is ./development.ini
0322            config_file = 'development.ini'
0323            if not os.path.isfile(config_file):
0324                raise BadCommand('%sError: CONFIG_FILE not found at: .%s%s\n'
0325                                 'Please specify a CONFIG_FILE' %                                    (self.parser.get_usage(), os.path.sep,
0327                                  config_file))
0328        else:
0329            config_file = self.args[0]
0330
0331        config_name = 'config:%s' % config_file
0332        here_dir = os.getcwd()
0333        locs = dict(__name__="pylons-admin")
0334
0335        # XXX: Note, initializing CONFIG here is Legacy support. pylons.config
0336        # will automatically be initialized and restored via the registry
0337        # restorer along with the other StackedObjectProxys
0338        # Load app config into paste.deploy to simulate request config
0339        # Setup the Paste CONFIG object, adding app_conf/global_conf for legacy
0340        # code
0341        conf = appconfig(config_name, relative_to=here_dir)
0342        conf.update(dict(app_conf=conf.local_conf,
0343                         global_conf=conf.global_conf))
0344        paste.deploy.config.CONFIG.push_thread_config(conf)
0345
0346        # Load locals and populate with objects for use in shell
0347        sys.path.insert(0, here_dir)
0348
0349        # Load the wsgi app first so that everything is initialized right
0350        wsgiapp = loadapp(config_name, relative_to=here_dir)
0351        test_app = paste.fixture.TestApp(wsgiapp)
0352
0353        # Query the test app to setup the environment
0354        tresponse = test_app.get('/_test_vars')
0355        request_id = int(tresponse.body)
0356
0357        # Disable restoration during test_app requests
0358        test_app.pre_request_hook = lambda self:               paste.registry.restorer.restoration_end()
0360        test_app.post_request_hook = lambda self:               paste.registry.restorer.restoration_begin(request_id)
0362
0363        # Restore the state of the Pylons special objects
0364        # (StackedObjectProxies)
0365        paste.registry.restorer.restoration_begin(request_id)
0366
0367        # Determine the package name from the .egg-info top_level.txt.
0368        egg_info = find_egg_info_dir(here_dir)
0369        f = open(os.path.join(egg_info, 'top_level.txt'))
0370        packages = [l.strip() for l in f.readlines()
0371                    if l.strip() and not l.strip().startswith('#')]
0372        f.close()
0373
0374        # Start the rest of our imports now that the app is loaded
0375        found_base = False
0376        for pkg_name in packages:
0377            # Import all objects from the base module
0378            base_module = pkg_name + '.lib.base'
0379            found_base = can_import(base_module)
0380            if not found_base:
0381                # Minimal template
0382                base_module = pkg_name + '.controllers'
0383                found_base = can_import(base_module)
0384
0385            if found_base:
0386                break
0387
0388        if not found_base:
0389            raise ImportError("Could not import base module. Are you sure "
0390                              "this is a Pylons app?")
0391
0392        base = sys.modules[base_module]
0393        base_public = [__name for __name in dir(base) if not                          __name.startswith('_') or __name == '_']
0395        for name in base_public:
0396            locs[name] = getattr(base, name)
0397        locs.update(dict(wsgiapp=wsgiapp, app=test_app))
0398
0399        mapper = tresponse.config.get('routes.map')
0400        if mapper:
0401            locs['mapper'] = mapper
0402
0403        banner = "  All objects from %s are available\n" % base_module
0404        banner += "  Additional Objects:\n"
0405        if mapper:
0406            banner += "  %-10s -  %s\n" % ('mapper', 'Routes mapper object')
0407        banner += "  %-10s -  %s\n" % ('wsgiapp',
0408            "This project's WSGI App instance")
0409        banner += "  %-10s -  %s\n" % ('app',
0410            'paste.fixture wrapped around wsgiapp')
0411
0412        if not self.options.quiet:
0413            # Configure logging from the config file
0414            self.logging_file_config(config_file)
0415
0416        try:
0417            if self.options.disable_ipython:
0418                raise ImportError()
0419
0420            # try to use IPython if possible
0421            from IPython.Shell import IPShellEmbed
0422
0423            shell = IPShellEmbed(argv=self.args)
0424            shell.set_banner(shell.IP.BANNER + '\n\n' + banner)
0425            try:
0426                shell(local_ns=locs, global_ns={})
0427            finally:
0428                paste.registry.restorer.restoration_end()
0429        except ImportError:
0430            import code
0431            newbanner = "Pylons Interactive Shell\nPython %s\n\n" % sys.version
0432            banner = newbanner + banner
0433            shell = code.InteractiveConsole(locals=locs)
0434            try:
0435                import readline
0436            except ImportError:
0437                pass
0438            try:
0439                shell.interact(banner)
0440            finally:
0441                paste.registry.restorer.restoration_end()

Top