db.py

'''\
Python DataBase Module.

Classes that use the Python Shelf to implement simple data storage.

classes:
    MyDB

functions:
    db_simple_check()
    db_simple_print(fp)
    db_simple_update()
    sandbox()

Developer@Sonnack.com
July 2012
'''
####################################################################################################
from sys import stdoutstderrargv
from os import path
from datetime import datetimedatetimedelta
import shelve
####################################################################################################


##~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~##
## DATABASE CLASS
##~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~##
class MyDB (object):
    '''\
DataBase Class (based on Python Shelf).

properties:
    .name           - Database name
    .user           - Database user
    .db             - DB object
    .mode           - Open mode
    .db_created     - Date DB created
    .db_updated     - Last update date
    .last_login     - Last update user
    .last_count     - Number of rcds (when opened)

methods:
    .exists         - Test for database file
    .open           - Open (and possibly create) database
    .close          - Close database
    .userkeys       - List of keys, excluding sysparams
    .keys           - List of keys (includes sysparams)
    .values         - List of values
    .create         - Create a new blank database
    .put            - Write a single record to the database
    .get            - Read a single record from the database
    .write          - Write a (key, flds) list of records to the database
    .read           - Read a list of records from the database given a list of keys
    .update         - Write a {key, flds} dict to the database
    .search         - Get a list of records from the database given a query
    .print_stats    - Print database info and sysparams
    .print_recds    - Output a list of database records (uses `repr(rcd)`)
    ._sysparams     - Load sysparams from database
    ._updated       - Time stamp $updated sysparam

    obj[key]        - Get/Set database item 'key'
    del obj[key]    - Remove database item 'key'
    iter(obj)       - iterator
    if obj          - if {database is open}
    key in obj      - contains key
    len(obj)        - DB length
    str(obj)        - string representation
    repr(obj)       - JSON-like string

database sysparams:
    $created        - date database created
    $updated        - date database last updated
    $login          - username of last write/create access

'''
    def __init__ (selfnameuser):
        ''' Create new MyDB instance. '''
        self.name = name
        self.user = user
        self.db   = None
        self.mode = None
        self.db_created = (Noneuser)
        self.db_updated = (Noneuser)
        self.last_login = (Noneuser)
        self.last_count = 0
        if self.exists():
            self.open()
            self.close()
        else:
            print 'DB does not exist; creating...'
            self.create()

    def __del__ (self):
        ''' Close the DB if it's still open when Object is destroyed. '''
        if self.db:
            self.db.close()

    def userkeys (selfkey_filter=None):
        ''' Return list of DataBase Keys excluding system keys (filterable). '''
        if not self.db:
            raise ExceptionE_DB_NOT_OPEN
        ks = filter(lambda k: not k.startswith('$'), self.db.keys())
        if key_filter:
            ks = filter(key_filterks)
        return ks

    def keys (selfkey_filter=None):
        ''' Return list of DataBase Keys (filterable). '''
        if not self.db:
            raise ExceptionE_DB_NOT_OPEN
        ks = self.db.keys()
        if key_filter:
            ks = filter(key_filterks)
        return ks

    def values (selfvalue_filter=None):
        ''' Return list of DataBase Values (filterable). '''
        if not self.db:
            raise ExceptionE_DB_NOT_OPEN
        vs = self.db.values()
        if value_filter:
            vs = filter(value_filtervs)
        return vs

    def __str__ (self):
        ''' Return Pretty Print string. '''
        db_name = path.basename(self.name)
        status = '' if self.db == None else ' (open)'
        dt_cre = self.db_created[0].strftime('%m-%d-%Y %H:%M')
        t = (db_nameself.userstatusdt_cre)
        s = '%s [by %s]%s (%s)'
        return s % t

    def __repr__ (self):
        ''' Return JSON-like string. '''
        status = 'open' if self.db else 'closed'
        t = (self.nameself.last_countself.userstatusself.last_loginself.db_created)
        s = '{MyDB:{name:"%s", rcds:%d, user:"%s", status:"%s", dla:"%s", cre:"%s"}}'
        return s % t

    def __nonzero__ (self):
        return (self.db != None)

    def __len__ (self):
        ''' Return length of DataBase (if open, else zero). '''
        if self.db:
            return len(self.db)
        return 0

    def __iter__ (self):
        ''' Return DataBase iterator. '''
        if self.db:
            return iter(self.db)
        raise ExceptionE_DB_NOT_OPEN

    def __contains__ (selfkey):
        ''' Test for existence of a Key. '''
        if self.db:
            return (True if key in self.db else False)
        raise ExceptionE_DB_NOT_OPEN

    def __getitem__ (selfkey):
        ''' Return a DataBase Entry. '''
        if self.db:
            return (self.db[keyif key in self.db else None)
        raise ExceptionE_DB_NOT_OPEN

    def __setitem__ (selfkeyobj):
        ''' Set a DataBase Entry. '''
        if not self.db:
            raise ExceptionE_DB_NOT_OPEN
        self.db[key] = obj
        self._updated()

    def __delitem__ (selfkey):
        ''' Delete a DataBase Entry. '''
        if not self.db:
            raise ExceptionE_DB_NOT_OPEN
        if key not in self.db:
            raise ExceptionE_DB_NO_ENTRY
        del self.db[key]
        self._updated()

    # ======================
    # Open/Close Protocol...
    # ----------------------

    def exists (self):
        ''' Test if DataBase exists already. '''
        if path.exists(self.nameand path.isfile(self.name):
            return True
        return False

    def open (selfmode='r'):
        ''' Open the DataBase for access. (Modes: r=read existing, w=r/w existing, c=r/w create, n=r/w new)'''
        if self.db:
            raise ExceptionE_DB_IS_OPEN
        self.mode = mode
        try:
            self.db = shelve.open(self.nameflag=self.mode)
            self._sysparams()
            if self.mode != 'r':
                self.db['$login'] = (datetime.today(), self.user)
            # Load system parameters...
        except Exception as e:
            print >> stderre
            raise e

    def close (self):
        ''' Close the DataBase. '''
        try:
            if self.db:
                if self.mode != 'r':
                    self.db['$logout'] = (datetime.today(), self.user)
                self.db.close()
                self.db = None
        except Exception as e:
            print >> stderre

    def _sysparams (self):
        self.db_created = self['$created']
        self.db_updated = self['$updated']
        self.last_login = self['$login']
        self.last_count = len(self.db)

    def _updated (self):
        self.db_updated = (datetime.today(), self.user)
        self.db['$updated'] = self.db_updated

    # =====================
    # One-Call functions...
    # ---------------------

    def create (self):
        ''' Create a new blank DataBase.
        '''
        self.close()
        self.db_created = (datetime.today(), self.user)
        self.db = shelve.open(self.nameflag='n')
        try:
            self.db['$login']   = self.db_created
            self.db['$created'] = self.db_created
            self._updated()
        except Exception as e:
            print >> stderre
            raise e
        finally:
            self.close()

    def put (selfkeyobj):
        ''' Write an entry given a Key and Object.
        '''
        self.close()
        self.open(mode='w')
        try:
            self.db[key] = obj
            self._updated()
        except Exception as e:
            print >> stderre
            raise e
        finally:
            self.close()

    def write (selfkey_obj_list):
        ''' Write a list of Objects given a list of Key+Object pairs.
            ('key_obj_list' is a list of (key, flds) tuples.)
        '''
        self.close()
        self.open(mode='w')
        try:
            for key_obj in key_obj_list:
                self.db[key_obj[0]] = key_obj[1]
            self._updated()
        except Exception as e:
            print >> stderre
            raise e
        finally:
            self.close()

    def update (selfkey_listfields_list):
        ''' Update a list of Objects given lists of Keys and Fields.
            ('key_list' is a list of DataBase keys.)
            (fields_list' is a dict of fields to update.)
        '''
        self.close()
        self.open(mode='w')
        try:
            for key,flds in zip(key_listfields_list):
                if key in self.db:
                    obj = self.db[key]
                    for fld in flds:
                        obj[fld] = flds[fld]
                    self.db[key] = obj
                else:
                    self.db[key] = flds
                    print >> stderr'INSERT: "%s"' % key
            self._updated()
        except Exception as e:
            print >> stderre
            raise e
        finally:
            self.close()

    def get (selfkey):
        ''' Return an Object given its Key.
            (Returns None if key not in DataBase.)
        '''
        self.close()
        self.open(mode='r')
        try:
            if key in self.db:
                return self.db[key]
        except Exception as e:
            print >> stderre
            raise e
        finally:
            self.close()
        return None

    def read (selfkey_list):
        ''' Return a list of Objects given a list of Keys.
            (Returns list of values.)
            ((Returns None in list slot if key not in DataBase.))
        '''
        self.close()
        self.open(mode='r')
        try:
            a = []
            for key in key_list:
                obj = self.db[keyif key in self.db else None
                a.append(obj)
            return a
        except Exception as e:
            print >> stderre
            raise e
        finally:
            self.close()
        return []

    def search (selfkey_filter=Nonevalue_filter=None):
        ''' Return all entries.
            (optional: 'key_filter' is a filter function on the key set.)
            (optional: 'value_filter' is a filter function on the value set.)
            (Returns list of (key, flds) tuples.)
        '''
        self.close()
        self.open(mode='r')
        try:
            a = []
            # Get keys...
            keys = sorted(self.db)
            if key_filter:
                keys = filter(key_filterkeys)
            # Build result set...
            for key in keys:
                obj = self.db[key]
                if value_filter and not value_filter(obj):
                    continue
                t = (keyobj)
                a.append(t)
            return a
        except Exception as e:
            print >> stderre
            raise e
        finally:
            self.close()
        return []

    def print_stats (selfostream):
        ''' Return Status string. '''
        self.close()
        self.open(mode='r')
        try:
            print >> ostream'Name: %s' % path.basename(self.name)
            print >> ostream'Created: %s [by %s]' % (self.db_created[0], self.db_created[1])
            print >> ostream'Updated: %s [by %s]' % (self.db_updated[0], self.db_updated[1])
            print >> ostream'LastUse: %s [by %s]' % (self.last_login[0], self.last_login[1])
        except Exception as e:
            print >> stderre
            raise e
        finally:
            self.close()

    def print_recds (selfostream):
        ''' Output a list of DB contents.
        '''
        self.close()
        self.open(mode='r')
        try:
            for ix,key in enumerate(sorted(self.db.keys())):
                obj = self.db[key]
                t = (ixtype(obj), keyrepr(obj))
                print >> ostream'[%d] (%s) "%s" "%s"' % t
        except Exception as e:
            print >> stderre
            raise e
        finally:
            self.close()

E_DB_NOT_OPEN = 'DB not open!'
E_DB_IS_OPEN  = 'DB already open!'
E_DB_NO_ENTRY = 'Key not in DB!'

####################################################################################################
## MY-DB DEMO
####################################################################################################
ChrisDBName = 'chris.db'

def db_simple_check ():
    ''' Check DataBase. '''
    db = MyDB(ChrisDBName'chris')
    db.open()
    try:
        if '$created' in db:
            print 'DB-Created: %s by %s' % db['$created']
            print 'DB-Updated: %s by %s' % db['$updated']
            print 'Last-Login: %s by %s' % db['$login']
            print 'Last-Logout: %s by %s' % db['$logout']
        else:
            print 'No system fields!'
    except:
        raise
    finally:
        db.close()
    return db

def db_simple_print (fp):
    ''' Print contents of DataBase. '''
    db = MyDB(ChrisDBName'chris')
    db.open(mode='w')
    try:
        dict_type = type(dict())
        keys = db.keys(key_filter=lambda k: type(db[k]) == dict_type)
        for key in sorted(keys):
            print >> fp'%s %s' % (db[key]['fname'], db[key]['lname'])
        # Mark it...
        db['$accessed'] = datetime.today()
    except:
        raise
    finally:
        db.close()
    return db

def db_simple_update ():
    ''' Update Datebase. '''
    db = MyDB(ChrisDBName'chris')
    keys = map(lambda x: x[1], simple_data)
    vals = map(lambda x: {'fname':x[0], 'lname':x[1], 'place':x[2], 'type':x[3]}, simple_data)
    items = zip(keysvals)
    db.write(items)
    return db

simple_data = [
    ('Cindy'    , 'Egeness'  , 'Wife'       , 'WM')
,   ('Kathleen' , 'Matrass'  , 'Minnesota'  , 'GM')
,   ('Ellen'    , 'Buecker'  , 'Minnesota'  , 'LM')
,   ('Barb'     , 'Anderson' , 'Minnesota'  , 'LM')
,   ('Christine''Straining''Minnesota'  , 'GM')
,   ('Cindy'    , 'Coe'      , 'Las Vegas'  , 'l1')
,   ('Carol'    , 'Hyne'     , 'Los Angeles''FM')
,   ('Ingrid'   , 'Pashalak' , 'Los Angeles''GF')
,   ('Shantih'  , 'Hasst'    , 'Los Angeles''FM')
,   ('Hope'     , 'Morrow'   , 'Los Angeles''GM')
,   ('Jayme'    , 'Povalitus''College'    , 'FR')
,   ('Nancy'    , 'Buckley'  , 'College'    , 'BF')
,   ('Anglea'   , 'Paris'    , 'College'    , 'GM')
,   ('Debbie'   , 'Fasciano' , 'College'    , 'FM')
,   ('Valerie'  , 'Klayman'  , 'College'    , 'FM')
,   ('Jeanne'   , 'Schulte'  , 'College'    , 'BF')
,   ('Gloria'   , 'Wright'   , 'College'    , 'GM')
,   ('Diana'    , 'Yoon'     , 'College'    , 'F1')
,   ('Debbie'   , 'Vessels'  , 'College'    , 'GT')
,   ('Jan'      , 'Reeder'   , 'High School''LT')
,   ('Susan'    , 'Sweeny'   , 'High School''LT')
,   ('Julie'    , 'Nelson'   , 'Neighbor'   , 'FL')
]



####################################################################################################
## sandbox
####################################################################################################
def sandbox ():
    ''' Sandbox: a place to experiment. '''
    db = PlayersDB('chris')
    db.open(mode='r')
    try:
        print db['$login']
    except:
        raise
    finally:
        db.close()
    return db


####################################################################################################
def dispatch (command, *args):
    print command
    print
    # Check...
    if 'check' in command:
        return db_simple_check()
    # Update...
    if 'update' in command:
        return db_simple_update()
    # Print...
    if 'print' in command:
        return db_simple_print(stdout)
    # Sandbox...
    if 'sandbox' in command:
        return sandbox()
    # Main...
    if 'main' in command:
        return db_simple_print(stdout)
    return None

####################################################################################################
if __name__ == '__main__':
    print 'autorun:',argv[0]
    command = argv[1if 1 < len(argvelse 'main'
    arguments = argv[2:]
    obj = dispatch(commandarguments)
    print type(obj)

####################################################################################################
###eof###