Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1# sqlalchemy/naming.py 

2# Copyright (C) 2005-2020 the SQLAlchemy authors and contributors 

3# <see AUTHORS file> 

4# 

5# This module is part of SQLAlchemy and is released under 

6# the MIT License: http://www.opensource.org/licenses/mit-license.php 

7 

8"""Establish constraint and index naming conventions. 

9 

10 

11""" 

12 

13import re 

14 

15from .elements import _defer_name 

16from .elements import _defer_none_name 

17from .elements import conv 

18from .schema import CheckConstraint 

19from .schema import Column 

20from .schema import Constraint 

21from .schema import ForeignKeyConstraint 

22from .schema import Index 

23from .schema import PrimaryKeyConstraint 

24from .schema import Table 

25from .schema import UniqueConstraint 

26from .. import event 

27from .. import events # noqa 

28from .. import exc 

29 

30 

31class ConventionDict(object): 

32 def __init__(self, const, table, convention): 

33 self.const = const 

34 self._is_fk = isinstance(const, ForeignKeyConstraint) 

35 self.table = table 

36 self.convention = convention 

37 self._const_name = const.name 

38 

39 def _key_table_name(self): 

40 return self.table.name 

41 

42 def _column_X(self, idx): 

43 if self._is_fk: 

44 fk = self.const.elements[idx] 

45 return fk.parent 

46 else: 

47 return list(self.const.columns)[idx] 

48 

49 def _key_constraint_name(self): 

50 if isinstance(self._const_name, (type(None), _defer_none_name)): 

51 raise exc.InvalidRequestError( 

52 "Naming convention including " 

53 "%(constraint_name)s token requires that " 

54 "constraint is explicitly named." 

55 ) 

56 if not isinstance(self._const_name, conv): 

57 self.const.name = None 

58 return self._const_name 

59 

60 def _key_column_X_key(self, idx): 

61 # note this method was missing before 

62 # [ticket:3989], meaning tokens like ``%(column_0_key)s`` weren't 

63 # working even though documented. 

64 return self._column_X(idx).key 

65 

66 def _key_column_X_name(self, idx): 

67 return self._column_X(idx).name 

68 

69 def _key_column_X_label(self, idx): 

70 return self._column_X(idx)._ddl_label 

71 

72 def _key_referred_table_name(self): 

73 fk = self.const.elements[0] 

74 refs = fk.target_fullname.split(".") 

75 if len(refs) == 3: 

76 refschema, reftable, refcol = refs 

77 else: 

78 reftable, refcol = refs 

79 return reftable 

80 

81 def _key_referred_column_X_name(self, idx): 

82 fk = self.const.elements[idx] 

83 # note that before [ticket:3989], this method was returning 

84 # the specification for the :class:`.ForeignKey` itself, which normally 

85 # would be using the ``.key`` of the column, not the name. 

86 return fk.column.name 

87 

88 def __getitem__(self, key): 

89 if key in self.convention: 

90 return self.convention[key](self.const, self.table) 

91 elif hasattr(self, "_key_%s" % key): 

92 return getattr(self, "_key_%s" % key)() 

93 else: 

94 col_template = re.match(r".*_?column_(\d+)(_?N)?_.+", key) 

95 if col_template: 

96 idx = col_template.group(1) 

97 multiples = col_template.group(2) 

98 

99 if multiples: 

100 if self._is_fk: 

101 elems = self.const.elements 

102 else: 

103 elems = list(self.const.columns) 

104 tokens = [] 

105 for idx, elem in enumerate(elems): 

106 attr = "_key_" + key.replace("0" + multiples, "X") 

107 try: 

108 tokens.append(getattr(self, attr)(idx)) 

109 except AttributeError: 

110 raise KeyError(key) 

111 sep = "_" if multiples.startswith("_") else "" 

112 return sep.join(tokens) 

113 else: 

114 attr = "_key_" + key.replace(idx, "X") 

115 idx = int(idx) 

116 if hasattr(self, attr): 

117 return getattr(self, attr)(idx) 

118 raise KeyError(key) 

119 

120 

121_prefix_dict = { 

122 Index: "ix", 

123 PrimaryKeyConstraint: "pk", 

124 CheckConstraint: "ck", 

125 UniqueConstraint: "uq", 

126 ForeignKeyConstraint: "fk", 

127} 

128 

129 

130def _get_convention(dict_, key): 

131 

132 for super_ in key.__mro__: 

133 if super_ in _prefix_dict and _prefix_dict[super_] in dict_: 

134 return dict_[_prefix_dict[super_]] 

135 elif super_ in dict_: 

136 return dict_[super_] 

137 else: 

138 return None 

139 

140 

141def _constraint_name_for_table(const, table): 

142 metadata = table.metadata 

143 convention = _get_convention(metadata.naming_convention, type(const)) 

144 

145 if isinstance(const.name, conv): 

146 return const.name 

147 elif ( 

148 convention is not None 

149 and not isinstance(const.name, conv) 

150 and ( 

151 const.name is None 

152 or "constraint_name" in convention 

153 or isinstance(const.name, _defer_name) 

154 ) 

155 ): 

156 return conv( 

157 convention 

158 % ConventionDict(const, table, metadata.naming_convention) 

159 ) 

160 elif isinstance(convention, _defer_none_name): 

161 return None 

162 

163 

164@event.listens_for(Constraint, "after_parent_attach") 

165@event.listens_for(Index, "after_parent_attach") 

166def _constraint_name(const, table): 

167 if isinstance(table, Column): 

168 # for column-attached constraint, set another event 

169 # to link the column attached to the table as this constraint 

170 # associated with the table. 

171 event.listen( 

172 table, 

173 "after_parent_attach", 

174 lambda col, table: _constraint_name(const, table), 

175 ) 

176 elif isinstance(table, Table): 

177 if isinstance(const.name, (conv, _defer_name)): 

178 return 

179 

180 newname = _constraint_name_for_table(const, table) 

181 if newname is not None: 

182 const.name = newname