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__:  # pragma: no branch 
 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 re as _re 
 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(filter(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, types=None):
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 `types` : callable 149 Extra type loader. If the type reflection fails, because 150 SQLAlchemy cannot resolve it, the type loader will be called with 151 the type name, (bound) metadata and the symbol table. It is 152 responsible for modifying the symbols and imports *and* the 153 dialect's ``ischema_names``. If omitted or ``None``, the reflector 154 will always fail on unknown types. 155 156 :Return: New Table instance 157 :Rtype: `Table` 158 """ 159 kwargs = {} 160 if '.' in name: 161 schema, name = name.split('.') 162 kwargs['schema'] = schema 163 else: 164 schema = None 165 166 tmatch = _re.compile(u"^Did not recognize type (.+) of column").match 167 168 with _warnings.catch_warnings(): 169 _warnings.filterwarnings('error', category=_sa.exc.SAWarning, 170 message=r'^Did not recognize type ') 171 _warnings.filterwarnings('error', category=_sa.exc.SAWarning, 172 message=r'^Unknown column definition ') 173 _warnings.filterwarnings('error', category=_sa.exc.SAWarning, 174 message=r'^Incomplete reflection of ' 175 r'column definition') 176 _warnings.filterwarnings('ignore', category=_sa.exc.SAWarning, 177 message=r'^Could not instantiate type ') 178 _warnings.filterwarnings('ignore', category=_sa.exc.SAWarning, 179 message=r'^Skipped unsupported ' 180 r'reflection of expression-based' 181 r' index ') 182 _warnings.filterwarnings('ignore', category=_sa.exc.SAWarning, 183 message=r'^Predicate of partial index ') 184 185 seen = set() 186 while True: 187 try: 188 table = _sa.Table(name, metadata, autoload=True, **kwargs) 189 except _sa.exc.SAWarning as e: 190 if types is not None: 191 match = tmatch(e.args[0]) 192 if match: 193 type_name = match.group(1).strip() 194 if type_name.startswith(('"', "'")): 195 type_name = type_name[1:-1] 196 if type_name and type_name not in seen: 197 types(type_name, metadata, symbols) 198 seen.add(type_name) 199 continue 200 raise 201 else: 202 break 203 204 return cls(varname, table, schemas, symbols)
205
206 - def __repr__(self):
207 """ 208 Make string representation 209 210 :Return: The string representation 211 :Rtype: ``str`` 212 """ 213 args = [ 214 repr(_column.Column.from_sa(col, self._symbols)) 215 for col in self.sa_table.columns 216 ] 217 if self.sa_table.schema is not None: 218 args.append('schema=%r' % (_util.unicode(self.sa_table.schema),)) 219 220 args = ',\n '.join(args) 221 if args: 222 args = ',\n %s,\n' % args 223 result = "%s(%r, %s%s)" % ( 224 self._symbols['table'], 225 _util.unicode(self.sa_table.name), 226 self._symbols['meta'], 227 args, 228 ) 229 if self.constraints: 230 result = "\n".join(( 231 result, '\n'.join(map(repr, sorted(self.constraints))) 232 )) 233 return result
234
235 236 -class TableReference(object):
237 """ Referenced table """ 238 is_reference = True 239
240 - def __init__(self, varname, table, schema, symbols):
241 """ 242 Initialization 243 244 :Parameters: 245 `varname` : ``str`` 246 Variable name 247 248 `table` : ``sqlalchemy.Table`` 249 Table 250 251 `symbols` : `Symbols` 252 Symbol table 253 """ 254 self.varname = varname 255 self.sa_table = table 256 self.constraints = [] 257 pkg, mod = schema.rsplit('.', 1) 258 if not mod.startswith('_'): 259 modas = '_' + mod 260 symbols.imports[schema] = 'from %s import %s as %s' % ( 261 pkg, mod, modas 262 ) 263 mod = modas 264 else: 265 symbols.imports[schema] = 'from %s import %s' % (pkg, mod) 266 symbols[u'table_%s' % table.name] = "%s.%s" % (mod, varname)
267
268 269 -class TableCollection(tuple):
270 """ Table collection """ 271 272 @classmethod
273 - def by_names(cls, metadata, names, schemas, symbols, types=None):
274 """ 275 Construct by table names 276 277 :Parameters: 278 `metadata` : ``sqlalchemy.MetaData`` 279 Metadata 280 281 `names` : iterable 282 Name list (list of tuples (varname, name)) 283 284 `symbols` : `Symbols` 285 Symbol table 286 287 `types` : callable 288 Extra type loader. If the type reflection fails, because 289 SQLAlchemy cannot resolve it, the type loader will be called with 290 the type name, (bound) metadata and the symbol table. It is 291 responsible for modifying the symbols and imports *and* the 292 dialect's ``ischema_names``. If omitted or ``None``, the reflector 293 will always fail on unknown types. 294 295 :Return: New table collection instance 296 :Rtype: `TableCollection` 297 """ 298 objects = dict((table.sa_table.key, table) for table in [ 299 Table.by_name(name, varname, metadata, schemas, symbols, 300 types=types) 301 for varname, name in names 302 ]) 303 304 def map_table(sa_table): 305 """ Map SA table to table object """ 306 if sa_table.key not in objects: 307 varname = sa_table.name 308 if _util.py2 and \ 309 isinstance(varname, 310 _util.unicode): # pragma: no cover 311 varname = varname.encode('ascii') 312 objects[sa_table.key] = Table( 313 varname, sa_table, schemas, symbols 314 ) 315 return objects[sa_table.key]
316 317 tables = list(map(map_table, metadata.tables.values())) 318 tables.sort(key=lambda x: (not(x.is_reference), x.varname)) 319 320 _break_cycles(metadata) 321 seen = set() 322 323 for table in tables: 324 seen.add(table.sa_table.key) 325 for con in table.constraints: 326 # pylint: disable = unidiomatic-typecheck 327 if type(con) == _constraint.ForeignKeyConstraint: 328 if con.options == 'seen': 329 continue 330 331 remote_key = con.constraint.elements[0].column.table.key 332 if remote_key not in seen: 333 con.options = 'unseen: %s' % ( 334 objects[remote_key].varname, 335 ) 336 remote_con = con.copy() 337 remote_con.options = 'seen: %s' % (table.varname,) 338 objects[remote_key].constraints.append(remote_con) 339 340 return cls(tables)
341
342 343 -def _break_cycles(metadata):
344 """ 345 Find foreign key cycles and break them apart 346 347 :Parameters: 348 `metadata` : ``sqlalchemy.MetaData`` 349 Metadata 350 """ 351 def break_cycle(e): 352 """ Break foreign key cycle """ 353 cycle_keys = set(map(_op.attrgetter('key'), e.cycles)) 354 cycle_path = [ 355 (parent, child) 356 for parent, child in e.edges 357 if parent.key in cycle_keys and child.key in cycle_keys 358 ] 359 deps = [cycle_path.pop()] 360 while cycle_path: 361 tmp = [] 362 for parent, child in cycle_path: 363 if parent == deps[-1][1]: 364 deps.append((parent, child)) 365 else: 366 tmp.append((parent, child)) 367 if len(tmp) == len(cycle_path): 368 raise AssertionError("Could not construct sorted cycle path") 369 cycle_path = tmp 370 if deps[0][0].key != deps[-1][1].key: 371 raise AssertionError("Could not construct sorted cycle path") 372 373 deps = list(map(_op.itemgetter(0), deps)) 374 first_dep = list(sorted(deps, key=_op.attrgetter('name')))[0] 375 while first_dep != deps[-1]: 376 deps = [deps[-1]] + deps[:-1] 377 deps.reverse() 378 logger.debug("Found foreign key cycle: %s", " -> ".join([ 379 repr(table.name) for table in deps + [deps[0]] 380 ])) 381 382 def visit_foreign_key(fkey): 383 """ Visit foreign key """ 384 if fkey.column.table == deps[1]: 385 fkey.use_alter = True 386 fkey.constraint.use_alter = True
387 388 _sa.sql.visitors.traverse(deps[0], dict(schema_visitor=True), dict( 389 foreign_key=visit_foreign_key, 390 )) 391 392 while True: 393 try: 394 metadata.sorted_tables 395 except _sa.exc.CircularDependencyError as e: 396 break_cycle(e) 397 else: 398 break 399