1import re
2
3__all__ = ["Style", "MixedCaseUnderscoreStyle", "DefaultStyle",
4           "MixedCaseStyle"]
5
6class Style(object):
7
8    """
9    The base Style class, and also the simplest implementation.  No
10    translation occurs -- column names and attribute names match,
11    as do class names and table names (when using auto class or
12    schema generation).
13    """
14
15    def __init__(self, pythonAttrToDBColumn=None,
16                 dbColumnToPythonAttr=None,
17                 pythonClassToDBTable=None,
18                 dbTableToPythonClass=None,
19                 idForTable=None,
20                 longID=False):
21        if pythonAttrToDBColumn:
22            self.pythonAttrToDBColumn = lambda a, s=self: pythonAttrToDBColumn(s, a)
23        if dbColumnToPythonAttr:
24            self.dbColumnToPythonAttr = lambda a, s=self: dbColumnToPythonAttr(s, a)
25        if pythonClassToDBTable:
26            self.pythonClassToDBTable = lambda a, s=self: pythonClassToDBTable(s, a)
27        if dbTableToPythonClass:
28            self.dbTableToPythonClass = lambda a, s=self: dbTableToPythonClass(s, a)
29        if idForTable:
30            self.idForTable = lambda a, s=self: idForTable(s, a)
31        self.longID = longID
32
33    def pythonAttrToDBColumn(self, attr):
34        return attr
35
36    def dbColumnToPythonAttr(self, col):
37        return col
38
39    def pythonClassToDBTable(self, className):
40        return className
41
42    def dbTableToPythonClass(self, table):
43        return table
44
45    def idForTable(self, table):
46        if self.longID:
47            return self.tableReference(table)
48        else:
49            return 'id'
50
51    def pythonClassToAttr(self, className):
52        return lowerword(className)
53
54    def instanceAttrToIDAttr(self, attr):
55        return attr + "ID"
56
57    def instanceIDAttrToAttr(self, attr):
58        return attr[:-2]
59
60    def tableReference(self, table):
61        return table + "_id"
62
63class MixedCaseUnderscoreStyle(Style):
64
65    """
66    This is the default style.  Python attributes use mixedCase,
67    while database columns use underscore_separated.
68    """
69
70    def pythonAttrToDBColumn(self, attr):
71        return mixedToUnder(attr)
72
73    def dbColumnToPythonAttr(self, col):
74        return underToMixed(col)
75
76    def pythonClassToDBTable(self, className):
77        return className[0].lower() \
78               + mixedToUnder(className[1:])
79
80    def dbTableToPythonClass(self, table):
81        return table[0].upper() \
82               + underToMixed(table[1:])
83
84    def pythonClassToDBTableReference(self, className):
85        return self.tableReference(self.pythonClassToDBTable(className))
86
87    def tableReference(self, table):
88        return table + "_id"
89
90DefaultStyle = MixedCaseUnderscoreStyle
91
92class MixedCaseStyle(Style):
93
94    """
95    This style leaves columns as mixed-case, and uses long
96    ID names (like ProductID instead of simply id).
97    """
98
99    def pythonAttrToDBColumn(self, attr):
100        return capword(attr)
101
102    def dbColumnToPythonAttr(self, col):
103        return lowerword(col)
104
105    def dbTableToPythonClass(self, table):
106        return capword(table)
107
108    def tableReference(self, table):
109        return table + "ID"
110
111defaultStyle = DefaultStyle()
112
113def getStyle(soClass, dbConnection=None):
114    if dbConnection is None:
115        if hasattr(soClass, '_connection'):
116            dbConnection = soClass._connection
117    if hasattr(soClass.sqlmeta, 'style') and soClass.sqlmeta.style:
118        return soClass.sqlmeta.style
119    elif dbConnection and dbConnection.style:
120        return dbConnection.style
121    else:
122        return defaultStyle
123
124############################################################
125## Text utilities
126############################################################
127_mixedToUnderRE = re.compile(r'[A-Z]+')
128def mixedToUnder(s):
129    if s.endswith('ID'):
130        return mixedToUnder(s[:-2] + "_id")
131    trans = _mixedToUnderRE.sub(mixedToUnderSub, s)
132    if trans.startswith('_'):
133        trans = trans[1:]
134    return trans
135
136def mixedToUnderSub(match):
137    m = match.group(0).lower()
138    if len(m) > 1:
139        return '_%s_%s' % (m[:-1], m[-1])
140    else:
141        return '_%s' % m
142
143def capword(s):
144    return s[0].upper() + s[1:]
145
146def lowerword(s):
147    return s[0].lower() + s[1:]
148
149_underToMixedRE = re.compile('_.')
150def underToMixed(name):
151    if name.endswith('_id'):
152        return underToMixed(name[:-3] + "ID")
153    return _underToMixedRE.sub(lambda m: m.group(0)[1].upper(),
154                               name)
155