0001import re
0002
0003__all__ = ["Style", "MixedCaseUnderscoreStyle", "DefaultStyle",
0004           "MixedCaseStyle"]
0005
0006class Style(object):
0007
0008    """
0009    The base Style class, and also the simplest implementation.  No
0010    translation occurs -- column names and attribute names match,
0011    as do class names and table names (when using auto class or
0012    schema generation).
0013    """
0014
0015    def __init__(self, pythonAttrToDBColumn=None,
0016                 dbColumnToPythonAttr=None,
0017                 pythonClassToDBTable=None,
0018                 dbTableToPythonClass=None,
0019                 idForTable=None,
0020                 longID=False):
0021        if pythonAttrToDBColumn:
0022            self.pythonAttrToDBColumn = lambda a, s=self: pythonAttrToDBColumn(s, a)
0023        if dbColumnToPythonAttr:
0024            self.dbColumnToPythonAttr = lambda a, s=self: dbColumnToPythonAttr(s, a)
0025        if pythonClassToDBTable:
0026            self.pythonClassToDBTable = lambda a, s=self: pythonClassToDBTable(s, a)
0027        if dbTableToPythonClass:
0028            self.dbTableToPythonClass = lambda a, s=self: dbTableToPythonClass(s, a)
0029        if idForTable:
0030            self.idForTable = lambda a, s=self: idForTable(s, a)
0031        self.longID = longID
0032
0033    def pythonAttrToDBColumn(self, attr):
0034        return attr
0035
0036    def dbColumnToPythonAttr(self, col):
0037        return col
0038
0039    def pythonClassToDBTable(self, className):
0040        return className
0041
0042    def dbTableToPythonClass(self, table):
0043        return table
0044
0045    def idForTable(self, table):
0046        if self.longID:
0047            return self.tableReference(table)
0048        else:
0049            return 'id'
0050
0051    def pythonClassToAttr(self, className):
0052        return lowerword(className)
0053
0054    def instanceAttrToIDAttr(self, attr):
0055        return attr + "ID"
0056
0057    def instanceIDAttrToAttr(self, attr):
0058        return attr[:-2]
0059
0060    def tableReference(self, table):
0061        return table + "_id"
0062
0063class MixedCaseUnderscoreStyle(Style):
0064
0065    """
0066    This is the default style.  Python attributes use mixedCase,
0067    while database columns use underscore_separated.
0068    """
0069
0070    def pythonAttrToDBColumn(self, attr):
0071        return mixedToUnder(attr)
0072
0073    def dbColumnToPythonAttr(self, col):
0074        return underToMixed(col)
0075
0076    def pythonClassToDBTable(self, className):
0077        return className[0].lower()                  + mixedToUnder(className[1:])
0079
0080    def dbTableToPythonClass(self, table):
0081        return table[0].upper()                  + underToMixed(table[1:])
0083
0084    def pythonClassToDBTableReference(self, className):
0085        return self.tableReference(self.pythonClassToDBTable(className))
0086
0087    def tableReference(self, table):
0088        return table + "_id"
0089
0090DefaultStyle = MixedCaseUnderscoreStyle
0091
0092class MixedCaseStyle(Style):
0093
0094    """
0095    This style leaves columns as mixed-case, and uses long
0096    ID names (like ProductID instead of simply id).
0097    """
0098
0099    def pythonAttrToDBColumn(self, attr):
0100        return capword(attr)
0101
0102    def dbColumnToPythonAttr(self, col):
0103        return lowerword(col)
0104
0105    def dbTableToPythonClass(self, table):
0106        return capword(table)
0107
0108    def tableReference(self, table):
0109        return table + "ID"
0110
0111defaultStyle = DefaultStyle()
0112
0113def getStyle(soClass, dbConnection=None):
0114    if dbConnection is None:
0115        if hasattr(soClass, '_connection'):
0116            dbConnection = soClass._connection
0117    if hasattr(soClass.sqlmeta, 'style') and soClass.sqlmeta.style:
0118        return soClass.sqlmeta.style
0119    elif dbConnection and dbConnection.style:
0120        return dbConnection.style
0121    else:
0122        return defaultStyle
0123
0124############################################################
0125## Text utilities
0126############################################################
0127_mixedToUnderRE = re.compile(r'[A-Z]+')
0128def mixedToUnder(s):
0129    if s.endswith('ID'):
0130        return mixedToUnder(s[:-2] + "_id")
0131    trans = _mixedToUnderRE.sub(mixedToUnderSub, s)
0132    if trans.startswith('_'):
0133        trans = trans[1:]
0134    return trans
0135
0136def mixedToUnderSub(match):
0137    m = match.group(0).lower()
0138    if len(m) > 1:
0139        return '_%s_%s' % (m[:-1], m[-1])
0140    else:
0141        return '_%s' % m
0142
0143def capword(s):
0144    return s[0].upper() + s[1:]
0145
0146def lowerword(s):
0147    return s[0].lower() + s[1:]
0148
0149_underToMixedRE = re.compile('_.')
0150def underToMixed(name):
0151    if name.endswith('_id'):
0152        return underToMixed(name[:-3] + "ID")
0153    return _underToMixedRE.sub(lambda m: m.group(0)[1].upper(),
0154                               name)