Package gensaschema :: Module _table
[frames] | no frames]

Source Code for Module gensaschema._table

  1  # -*- coding: ascii -*- 
  2  r""" 
  3  ===================================== 
  4   Table inspection and representation 
  5  ===================================== 
  6   
  7  Table inspection and representation 
  8   
  9  :Copyright: 
 10   
 11   Copyright 2010 - 2016 
 12   Andr\xe9 Malo or his licensors, as applicable 
 13   
 14  :License: 
 15   
 16   Licensed under the Apache License, Version 2.0 (the "License"); 
 17   you may not use this file except in compliance with the License. 
 18   You may obtain a copy of the License at 
 19   
 20       http://www.apache.org/licenses/LICENSE-2.0 
 21   
 22   Unless required by applicable law or agreed to in writing, software 
 23   distributed under the License is distributed on an "AS IS" BASIS, 
 24   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
 25   See the License for the specific language governing permissions and 
 26   limitations under the License. 
 27   
 28  """ 
 29  if __doc__: 
 30      # pylint: disable = redefined-builtin 
 31      __doc__ = __doc__.encode('ascii').decode('unicode_escape') 
 32  __author__ = r"Andr\xe9 Malo".encode('ascii').decode('unicode_escape') 
 33  __docformat__ = "restructuredtext en" 
 34   
 35  import logging as _logging 
 36  import operator as _op 
 37  import warnings as _warnings 
 38   
 39  import sqlalchemy as _sa 
 40   
 41  from . import _column 
 42  from . import _constraint 
 43  from . import _util 
 44   
 45  logger = _logging.getLogger(__name__) 
46 47 48 -class Table(object):
49 """ 50 Reflected table 51 52 :CVariables: 53 `is_reference` : ``bool`` 54 Is it a table reference or a table? 55 56 :IVariables: 57 `varname` : ``str`` 58 Variable name 59 60 `sa_table` : ``sqlalchemy.Table`` 61 Table 62 63 `constraints` : ``list`` 64 Constraint list 65 66 `_symbols` : `Symbols` 67 Symbol table 68 """ 69 is_reference = False 70
71 - def __new__(cls, varname, table, schemas, symbols):
72 """ 73 Construct 74 75 This might actually return a table reference 76 77 :Parameters: 78 `varname` : ``str`` 79 Variable name 80 81 `table` : ``sqlalchemy.Table`` 82 Table 83 84 `schemas` : ``dict`` 85 Schema -> module mapping 86 87 `symbols` : `Symbols` 88 Symbol table 89 90 :Return: `Table` or `TableReference` instance 91 :Rtype: ``Table`` or ``TableReference`` 92 """ 93 if table.schema in schemas: 94 return TableReference( 95 varname, table, schemas[table.schema], symbols 96 ) 97 return super(Table, cls).__new__(cls)
98
99 - def __init__(self, varname, table, schemas, symbols):
100 """ 101 Initialization 102 103 :Parameters: 104 `varname` : ``str`` 105 Variable name 106 107 `table` : ``sqlalchemy.Table`` 108 Table 109 110 `schemas` : ``dict`` 111 Schema -> module mapping 112 113 `symbols` : `Symbols` 114 Symbol table 115 """ 116 # pylint: disable = unused-argument 117 118 symbols[u'table_%s' % table.name] = varname 119 self._symbols = symbols 120 self.varname = varname 121 self.sa_table = table 122 self.constraints = list(filter(None, [_constraint.Constraint( 123 con, self.varname, self._symbols, 124 ) for con in table.constraints]))
125 126 @classmethod
127 - def by_name(cls, name, varname, metadata, schemas, symbols):
128 """ 129 Construct by name 130 131 :Parameters: 132 `name` : ``str`` 133 Table name (possibly qualified) 134 135 `varname` : ``str`` 136 Variable name of the table 137 138 `metadata` : SA (bound) metadata 139 Metadata container 140 141 `schemas` : ``dict`` 142 Schema -> module mapping 143 144 `symbols` : `Symbols` 145 Symbol table 146 147 :Return: New Table instance 148 :Rtype: `Table` 149 """ 150 kwargs = {} 151 if '.' in name: 152 schema, name = name.split('.') 153 kwargs['schema'] = schema 154 else: 155 schema = None 156 157 with _warnings.catch_warnings(): 158 _warnings.filterwarnings('error', category=_sa.exc.SAWarning, 159 message=r'^Did not recognize type ') 160 _warnings.filterwarnings('error', category=_sa.exc.SAWarning, 161 message=r'^Unknown column definition ') 162 _warnings.filterwarnings('error', category=_sa.exc.SAWarning, 163 message=r'^Incomplete reflection of ' 164 r'column definition') 165 _warnings.filterwarnings('error', category=_sa.exc.SAWarning, 166 message=r'^Could not instantiate type ') 167 168 table = _sa.Table(name, metadata, autoload=True, **kwargs) 169 # while 1: 170 # try: 171 # table = _sa.Table(name, metadata, autoload=True, 172 # **kwargs) 173 # except _sa.exc.SATypeReflectionWarning, e: 174 # _ext.load_extension(e, metadata, symbols) 175 # else: 176 # break 177 178 return cls(varname, table, schemas, symbols)
179
180 - def __repr__(self):
181 """ 182 Make string representation 183 184 :Return: The string representation 185 :Rtype: ``str`` 186 """ 187 args = [ 188 repr(_column.Column.from_sa(col, self._symbols)) 189 for col in self.sa_table.columns 190 ] 191 if self.sa_table.schema is not None: 192 args.append('schema=%r' % (_util.unicode(self.sa_table.schema),)) 193 194 args = ',\n '.join(args) 195 if args: 196 args = ',\n %s,\n' % args 197 result = "%s(%r, %s%s)" % ( 198 self._symbols['table'], 199 _util.unicode(self.sa_table.name), 200 self._symbols['meta'], 201 args, 202 ) 203 if self.constraints: 204 result = "\n".join(( 205 result, '\n'.join(map(repr, sorted(self.constraints))) 206 )) 207 return result
208
209 210 -class TableReference(object):
211 """ Referenced table """ 212 is_reference = True 213
214 - def __init__(self, varname, table, schema, symbols):
215 """ 216 Initialization 217 218 :Parameters: 219 `varname` : ``str`` 220 Variable name 221 222 `table` : ``sqlalchemy.Table`` 223 Table 224 225 `symbols` : `Symbols` 226 Symbol table 227 """ 228 self.varname = varname 229 self.sa_table = table 230 self.constraints = [] 231 pkg, mod = schema.rsplit('.', 1) 232 if not mod.startswith('_'): 233 modas = '_' + mod 234 symbols.imports[schema] = 'from %s import %s as %s' % ( 235 pkg, mod, modas 236 ) 237 mod = modas 238 else: 239 symbols.imports[schema] = 'from %s import %s' % (pkg, mod) 240 symbols[u'table_%s' % table.name] = "%s.%s" % (mod, varname)
241
242 243 -class TableCollection(tuple):
244 """ Table collection """ 245 246 @classmethod
247 - def by_names(cls, metadata, names, schemas, symbols):
248 """ 249 Construct by table names 250 251 :Parameters: 252 `metadata` : ``sqlalchemy.MetaData`` 253 Metadata 254 255 `names` : iterable 256 Name list (list of tuples (varname, name)) 257 258 `symbols` : `Symbols` 259 Symbol table 260 261 :Return: New table collection instance 262 :Rtype: `TableCollection` 263 """ 264 objects = dict((table.sa_table.key, table) for table in [ 265 Table.by_name(name, varname, metadata, schemas, symbols) 266 for varname, name in names 267 ]) 268 269 def map_table(sa_table): 270 """ Map SA table to table object """ 271 if sa_table.key not in objects: 272 varname = sa_table.name 273 if _util.py2 and isinstance(varname, _util.unicode): 274 varname = varname.encode('ascii') 275 objects[sa_table.key] = Table( 276 varname, sa_table, schemas, symbols 277 ) 278 return objects[sa_table.key]
279 280 tables = list(map(map_table, metadata.tables.itervalues())) 281 tables.sort(key=lambda x: (not(x.is_reference), x.varname)) 282 283 _break_cycles(metadata) 284 seen = set() 285 286 for table in tables: 287 seen.add(table.sa_table.key) 288 for con in table.constraints: 289 # pylint: disable = unidiomatic-typecheck 290 if type(con) == _constraint.ForeignKeyConstraint: 291 if con.options == 'seen': 292 continue 293 294 remote_key = con.constraint.elements[0].column.table.key 295 if remote_key not in seen: 296 con.options = 'unseen: %s' % ( 297 objects[remote_key].varname, 298 ) 299 remote_con = con.copy() 300 remote_con.options = 'seen: %s' % (table.varname,) 301 objects[remote_key].constraints.append(remote_con) 302 303 return cls(tables)
304
305 306 -def _break_cycles(metadata):
307 """ 308 Find foreign key cycles and break them apart 309 310 :Parameters: 311 `metadata` : ``sqlalchemy.MetaData`` 312 Metadata 313 """ 314 def break_cycle(e): 315 """ Break foreign key cycle """ 316 cycle_keys = set(map(_op.attrgetter('key'), e.cycles)) 317 cycle_path = [ 318 (parent, child) 319 for parent, child in e.edges 320 if parent.key in cycle_keys and child.key in cycle_keys 321 ] 322 deps = [cycle_path.pop()] 323 while cycle_path: 324 tmp = [] 325 for parent, child in cycle_path: 326 if parent == deps[-1][1]: 327 deps.append((parent, child)) 328 else: 329 tmp.append((parent, child)) 330 if len(tmp) == len(cycle_path): 331 raise AssertionError("Could not construct sorted cycle path") 332 cycle_path = tmp 333 if deps[0][0].key != deps[-1][1].key: 334 raise AssertionError("Could not construct sorted cycle path") 335 336 deps = list(map(_op.itemgetter(0), deps)) 337 first_dep = list(sorted(deps, key=_op.attrgetter('name')))[0] 338 while first_dep != deps[-1]: 339 deps = [deps[-1]] + deps[:-1] 340 deps.reverse() 341 logger.debug("Found foreign key cycle: %s", " -> ".join([ 342 repr(table.name) for table in deps + [deps[0]] 343 ])) 344 345 def visit_foreign_key(fkey): 346 """ Visit foreign key """ 347 if fkey.column.table == deps[1]: 348 fkey.use_alter = True 349 fkey.constraint.use_alter = True
350 351 _sa.sql.visitors.traverse(deps[0], dict(schema_visitor=True), dict( 352 foreign_key=visit_foreign_key, 353 )) 354 355 while True: 356 try: 357 metadata.sorted_tables 358 except _sa.exc.CircularDependencyError as e: 359 break_cycle(e) 360 else: 361 break 362