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 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('ignore', 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 \ 274 isinstance(varname, 275 _util.unicode): # pragma: no cover 276 varname = varname.encode('ascii') 277 objects[sa_table.key] = Table( 278 varname, sa_table, schemas, symbols 279 ) 280 return objects[sa_table.key]
281 282 tables = list(map(map_table, metadata.tables.values())) 283 tables.sort(key=lambda x: (not(x.is_reference), x.varname)) 284 285 _break_cycles(metadata) 286 seen = set() 287 288 for table in tables: 289 seen.add(table.sa_table.key) 290 for con in table.constraints: 291 # pylint: disable = unidiomatic-typecheck 292 if type(con) == _constraint.ForeignKeyConstraint: 293 if con.options == 'seen': 294 continue 295 296 remote_key = con.constraint.elements[0].column.table.key 297 if remote_key not in seen: 298 con.options = 'unseen: %s' % ( 299 objects[remote_key].varname, 300 ) 301 remote_con = con.copy() 302 remote_con.options = 'seen: %s' % (table.varname,) 303 objects[remote_key].constraints.append(remote_con) 304 305 return cls(tables)
306
307 308 -def _break_cycles(metadata):
309 """ 310 Find foreign key cycles and break them apart 311 312 :Parameters: 313 `metadata` : ``sqlalchemy.MetaData`` 314 Metadata 315 """ 316 def break_cycle(e): 317 """ Break foreign key cycle """ 318 cycle_keys = set(map(_op.attrgetter('key'), e.cycles)) 319 cycle_path = [ 320 (parent, child) 321 for parent, child in e.edges 322 if parent.key in cycle_keys and child.key in cycle_keys 323 ] 324 deps = [cycle_path.pop()] 325 while cycle_path: 326 tmp = [] 327 for parent, child in cycle_path: 328 if parent == deps[-1][1]: 329 deps.append((parent, child)) 330 else: 331 tmp.append((parent, child)) 332 if len(tmp) == len(cycle_path): 333 raise AssertionError("Could not construct sorted cycle path") 334 cycle_path = tmp 335 if deps[0][0].key != deps[-1][1].key: 336 raise AssertionError("Could not construct sorted cycle path") 337 338 deps = list(map(_op.itemgetter(0), deps)) 339 first_dep = list(sorted(deps, key=_op.attrgetter('name')))[0] 340 while first_dep != deps[-1]: 341 deps = [deps[-1]] + deps[:-1] 342 deps.reverse() 343 logger.debug("Found foreign key cycle: %s", " -> ".join([ 344 repr(table.name) for table in deps + [deps[0]] 345 ])) 346 347 def visit_foreign_key(fkey): 348 """ Visit foreign key """ 349 if fkey.column.table == deps[1]: 350 fkey.use_alter = True 351 fkey.constraint.use_alter = True
352 353 _sa.sql.visitors.traverse(deps[0], dict(schema_visitor=True), dict( 354 foreign_key=visit_foreign_key, 355 )) 356 357 while True: 358 try: 359 metadata.sorted_tables 360 except _sa.exc.CircularDependencyError as e: 361 break_cycle(e) 362 else: 363 break 364