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