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