#############################################################################
# PyForm table wrapper classes and tests
# Because PyForm assumes a dictionary-of-dictionary interface, this module 
# converts strings and class instance records to and from dicts.  PyForm 
# contains the table mapping--Table is not a PyForm subclass.  Note that 
# some of the wrapper classes may be useful outside PyForm--DmbOfString can
# wrap a dbm containing arbitrary datatypes.  Run the dbinit scripts to
# start a new database from scratch, and run the dbview script to browse
# a database other than the one tested here.  No longer requires classes to 
# have defaults in constructor args, and auto picks up record class from the 
# first one fetched if not passed in to class-record wrapper.  Caveat: still
# assumes that all instances in a table are instances of the same class.
############################################################################

#############################################################################
# records within tables
#############################################################################

class DictionaryRecord:
    def todict(self, value):
        return value                   # to dictionary: no need to convert 
    def fromdict(self, value):
        return value                   # from dictionary: no need to convert

class StringRecord:
    def todict(self, value):
        return eval(value)             # convert string to dictionary (or any)
    def fromdict(self, value):
        return str(value)              # convert dictionary (or any) to string

class InstanceRecord:
    def __init__(self, Class=None):    # need class object to make instances
        self.Class = Class
    def todict(self, value):           # convert instance to attr dictionary
        if not self.Class:             # get class from obj if not yet known
            self.Class = value.__class__
        return value.__dict__ 
    def fromdict(self, value):         # convert attr dictionary to instance
        try:
            class Dummy: pass                       # try what new pickle does
            instance = Dummy()                      # fails in restricted mode
            instance.__class__ = self.Class
        except:                                     # else call class, no args
            instance = self.Class()                 # init args need defaults
        for attr in value.keys():
            setattr(instance, attr, value[attr])    # set instance attributes
        return instance                             # may run Class.__setattr__

#############################################################################
# table containing records
#############################################################################

class Table:
    def __init__(self, mapping, converter):    # table object, record converter
        self.table  = mapping                  # wrap arbitrary table mapping
        self.record = converter                # wrap arbitrary record types

    def storeItems(self, items):               # initialize from dictionary
        for key in items.keys():               # do __setitem__ to xlate, store 
            self[key] = items[key]

    def printItems(self):                      # print wrapped mapping
        for key in self.keys():                # do self.keys to get table keys
            print key, self[key]               # do __getitem__ to fetch, xlate

    def __getitem__(self, key):                # on tbl[key] index fetch
        rawval = self.table[key]               # fetch from table mapping 
        return self.record.todict(rawval)      # translate to dictionary

    def __setitem__(self, key, value):         # on tbl[key]=val index assign
        rawval = self.record.fromdict(value)   # translate from dictionary
        self.table[key] = rawval               # store in table mapping

    def __delitem__(self, key):                # delete from table mapping
        del self.table[key]    

    def keys(self):                            # get table mapping keys index
        return self.table.keys()

    def close(self):
        if hasattr(self.table, 'close'):       # call table close if has one
            self.table.close()                 # may need for shelves, dbm 

#############################################################################
# table/record combinations
#############################################################################

import shelve, anydbm

def ShelveOfInstance(filename, Class=None):
    return Table(shelve.open(filename), InstanceRecord(Class))
def ShelveOfDictionary(filename):
    return Table(shelve.open(filename), DictionaryRecord())
def ShelveOfString(filename):
    return Table(shelve.open(filename), StringRecord())

def DbmOfString(filename):
    return Table(anydbm.open(filename, 'c'), StringRecord())

def DictOfInstance(dict, Class=None):
    return Table(dict, InstanceRecord(Class))
def DictOfDictionary(dict):
    return Table(dict, DictionaryRecord())
def DictOfString(filename):
    return Table(dict, StringRecord())

ObjectOfInstance   = DictOfInstance           # other mapping objects
ObjectOfDictionary = DictOfDictionary         # classes that look like dicts
ObjectOfString     = DictOfString

#############################################################################
# test common applications
#############################################################################

if __name__ == '__main__':
    from sys import argv
    from formgui import FormGui                    # get dict-based gui
    from PP2E.Dbase.testdata import Actor, cast    # get class, dict-of-dicts

    TestType   = 'shelve'                          # shelve, dbm, dict
    TestInit   = 0                                 # init file on startup?
    TestFile   = '../data/shelve1'                 # external filename
    if len(argv) > 1: TestType = argv[1]
    if len(argv) > 2: TestInit = int(argv[2])
    if len(argv) > 3: TestFile = argv[3]

    if TestType == 'shelve':                       # python formtbl.py shelve?
        print 'shelve-of-instance test'
        table = ShelveOfInstance(TestFile, Actor)  # wrap shelf in Table object
        if TestInit:
            table.storeItems(cast)                 # python formtbl.py shelve 1
        FormGui(table).mainloop()
        table.close()
        ShelveOfInstance(TestFile).printItems()    # class picked up on fetch

    elif TestType == 'dbm':                        # python formtbl.py dbm
        print 'dbm-of-dictstring test'
        table = DbmOfString(TestFile)              # wrap dbm in Table object   
        if TestInit:                               
            table.storeItems(cast)                 # python formtbl.py dbm 1
        FormGui(table).mainloop()
        table.close()
        DbmOfString(TestFile).printItems()         # dump new table contents
