"""Skeleton script for use as a TG paster command base"""
import os, sys,time
from datetime import datetime
import logging, traceback
from paste.script.command import Command
from paste.deploy import appconfig
from sqlalchemy import engine_from_config
import transaction

log = logging.getLogger( 'commands.basecommand' )
log.setLevel( logging.INFO )

class TGCommand(Command):
    """Provides SQLAlchemy session and TurboGears config setup 
    """
    # Parser configuration
    summary = "" # override this...
    usage = "" # override this if you don't want auto-generated
    group_name = "local" # you can override this if you want to separate your commands out in the paster help listing
    
    # Many "simple" commands can just use this parser, but 
    # sub-classes can create new parsers and call 
    # standard_options( parser ) to get the basic option parsing 
    # code working.
    parser = Command.standard_parser(verbose=False)
    def standard_options( parser, default_config='development.ini', default_pid='' ):
        parser.add_option(
            '--config', 
            action='store', 
            dest='config', 
            help='Specify the config file to use for the command',
            default=default_config,
        )
        parser.add_option(
            '--verbose-log', 
            action='store_true', 
            default = False,
            dest='verbose_log', 
            help='Use verbose logging',
        )
        parser.add_option(
            '--pidfile', 
            action='store', 
            dest='pidfile', 
            help='Specify the pidfile to check/write (intended for use in cron scripts to prevent "cron-bombs")', 
            default=default_pid
        )
    # Staticmethods aren't callable directly...
    standard_options( parser )
    # so we wrap it here...
    standard_options = staticmethod( standard_options )
    
    log_level = logging.WARN
    verbose_log_level = logging.INFO
    with_session = True
    _temp_connection = None
    
    def import_model( self ):
        """Import our project's "model" module
        
        Override this (once) for your entire project, then use the 
        class that overrides it as your base-class for commands.
        
        model module must have:
        
            init_model
            metadata
            DBSession
        
        attributes.
        """
        
        # from project import model
        # return model 
    
    def get_session( self, engine ):
        """Imports project schema and binds to the engine"""
        model = self.import_model()
        # Hack to get package name
        self.config['pylons.package'] = model.__name__.split('.')[0]
        model.init_model( engine )
        model.metadata.bind = engine
        return model.DBSession
    def temp_connection( self, engine=None ):
        """Create a temporary (side) connection (w/ trans) to the database
        
        returns connection,transaction
        """
        if engine is None:
            engine = engine_from_config(self.config, 'sqlalchemy.')
        conn = engine.connect()
        trans = conn.begin()
        return conn, trans
    
    def create_pid( self ):
        """Create PID file for our process"""
        if self.options.pidfile:
            if os.path.exists( self.options.pidfile ):
                log.error( 
                    "Exiting, pidfile %s exists", 
                    self.options.pidfile 
                )
                sys.exit( 1 )
                return 
            # Technically a race condition here...
            log.debug( 'Creating PID: %s', self.options.pidfile )
            pidfile = open( self.options.pidfile, 'w')
            pidfile.write( str(os.getpid()))
            pidfile.flush()
            return pidfile
        return None
    
    def get_config( self ):
        """Retrieve our TurboGears configuration file from opts/args"""
        if self.options.config:
            config = self.options.config
        elif self.args:
            config = self.args[0]
        else:
            config = None
        if not config:
            log.error(
                "Missing config file. Please supply it."
            )
            sys.stderr.write( self.parser.format_help() )
            sys.stderr.write( '\n' )
            sys.exit(1)
        cfgpath = os.path.abspath(config)
        if not os.path.exists(config):
            log.error(
                "Config file %s does not exist. Please supply it.",
                cfgpath
            )
            sys.stderr.write( self.parser.format_help() )
            sys.stderr.write( '\n' )
            sys.exit(1)
        cfg = self.config = appconfig('config:%s' % (cfgpath))
        import tg
        tg.config.update( cfg )
        return cfg
    def command(self):
        """Our core command operation"""
        if self.options.verbose_log:
            logging.basicConfig( level=self.verbose_log_level )
            log.debug( 'Verbose log enabled' )
        else:
            logging.basicConfig( level=self.log_level )
        start = time.time()
        pidfile = self.create_pid()
        try:
            cfg = self.get_config()
            engine = engine_from_config(cfg, 'sqlalchemy.')
            self.setup_il8n( cfg )
            if self.with_session:
                DBSession = self.get_session( engine )
            else:
                DBSession = None
            try:
                self.db_command( engine )
            except Exception, err:
                if DBSession:
                    DBSession.rollback()
                    log.warn( 'Rollback of transaction' )
                raise
            else:
                if self.with_session:
                    transaction.commit()
                    log.info( 'Transaction committed' )
            if DBSession:
                DBSession.close()
        finally:
            if pidfile:
                pidfile.close()
                log.debug( 'Removing PID lock-file' )
                os.remove( self.options.pidfile )
            log.info( 'Total time: %0.1fs', time.time()-start )
        
    def setup_il8n( self, config ):
        """Setup internationalization parameters"""
        if 'lang' in config:
            # Get a translator up to make i18n work
            log.debug( 'Setting up i18n ' )
            kw = {
                'pylons_config': { 
                    'pylons.paths' : {
                        'root': os.path.join(
                            config['here'], 
                            config['pylons.package']
                        ) 
                    }, 
                    'pylons.package': config['pylons.package']
                }
            }
            # This assumes that lang is set in the .ini
            import pylons
            from pylons.i18n.translation import _get_translator
            pylons.translator._push_object(
                _get_translator(config['lang'], **kw)
            )

    def db_command( self, engine ):
        """Override to perform our DB-requiring operation here
        
        Exceptions will rollback otherwise will commit on 
        return.  Note that you should *not* import your project's
        model until you enter this method.  That is, your sub-class
        should not import the model objects at the top of their 
        modules, as that would attempt to create a DBSession.
        """
        raise NotImplemented( """db_command not implemented""" )

