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# engine/reflection.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"""Provides an abstraction for obtaining database schema information. 

9 

10Usage Notes: 

11 

12Here are some general conventions when accessing the low level inspector 

13methods such as get_table_names, get_columns, etc. 

14 

151. Inspector methods return lists of dicts in most cases for the following 

16 reasons: 

17 

18 * They're both standard types that can be serialized. 

19 * Using a dict instead of a tuple allows easy expansion of attributes. 

20 * Using a list for the outer structure maintains order and is easy to work 

21 with (e.g. list comprehension [d['name'] for d in cols]). 

22 

232. Records that contain a name, such as the column name in a column record 

24 use the key 'name'. So for most return values, each record will have a 

25 'name' attribute.. 

26""" 

27 

28from .base import Connectable 

29from .. import exc 

30from .. import inspection 

31from .. import sql 

32from .. import util 

33from ..sql import operators 

34from ..sql import schema as sa_schema 

35from ..sql.type_api import TypeEngine 

36from ..util import deprecated 

37from ..util import topological 

38 

39 

40@util.decorator 

41def cache(fn, self, con, *args, **kw): 

42 info_cache = kw.get("info_cache", None) 

43 if info_cache is None: 

44 return fn(self, con, *args, **kw) 

45 key = ( 

46 fn.__name__, 

47 tuple(a for a in args if isinstance(a, util.string_types)), 

48 tuple((k, v) for k, v in kw.items() if k != "info_cache"), 

49 ) 

50 ret = info_cache.get(key) 

51 if ret is None: 

52 ret = fn(self, con, *args, **kw) 

53 info_cache[key] = ret 

54 return ret 

55 

56 

57class Inspector(object): 

58 """Performs database schema inspection. 

59 

60 The Inspector acts as a proxy to the reflection methods of the 

61 :class:`~sqlalchemy.engine.interfaces.Dialect`, providing a 

62 consistent interface as well as caching support for previously 

63 fetched metadata. 

64 

65 A :class:`_reflection.Inspector` object is usually created via the 

66 :func:`_sa.inspect` function:: 

67 

68 from sqlalchemy import inspect, create_engine 

69 engine = create_engine('...') 

70 insp = inspect(engine) 

71 

72 The inspection method above is equivalent to using the 

73 :meth:`_reflection.Inspector.from_engine` method, i.e.:: 

74 

75 engine = create_engine('...') 

76 insp = Inspector.from_engine(engine) 

77 

78 Where above, the :class:`~sqlalchemy.engine.interfaces.Dialect` may opt 

79 to return an :class:`_reflection.Inspector` 

80 subclass that provides additional 

81 methods specific to the dialect's target database. 

82 

83 """ 

84 

85 def __init__(self, bind): 

86 """Initialize a new :class:`_reflection.Inspector`. 

87 

88 :param bind: a :class:`~sqlalchemy.engine.Connectable`, 

89 which is typically an instance of 

90 :class:`~sqlalchemy.engine.Engine` or 

91 :class:`~sqlalchemy.engine.Connection`. 

92 

93 For a dialect-specific instance of :class:`_reflection.Inspector`, see 

94 :meth:`_reflection.Inspector.from_engine` 

95 

96 """ 

97 # this might not be a connection, it could be an engine. 

98 self.bind = bind 

99 

100 # set the engine 

101 if hasattr(bind, "engine"): 

102 self.engine = bind.engine 

103 else: 

104 self.engine = bind 

105 

106 if self.engine is bind: 

107 # if engine, ensure initialized 

108 bind.connect().close() 

109 

110 self.dialect = self.engine.dialect 

111 self.info_cache = {} 

112 

113 @classmethod 

114 def from_engine(cls, bind): 

115 """Construct a new dialect-specific Inspector object from the given 

116 engine or connection. 

117 

118 :param bind: a :class:`~sqlalchemy.engine.Connectable`, 

119 which is typically an instance of 

120 :class:`~sqlalchemy.engine.Engine` or 

121 :class:`~sqlalchemy.engine.Connection`. 

122 

123 This method differs from direct a direct constructor call of 

124 :class:`_reflection.Inspector` in that the 

125 :class:`~sqlalchemy.engine.interfaces.Dialect` is given a chance to 

126 provide a dialect-specific :class:`_reflection.Inspector` instance, 

127 which may 

128 provide additional methods. 

129 

130 See the example at :class:`_reflection.Inspector`. 

131 

132 """ 

133 if hasattr(bind.dialect, "inspector"): 

134 return bind.dialect.inspector(bind) 

135 return Inspector(bind) 

136 

137 @inspection._inspects(Connectable) 

138 def _insp(bind): 

139 return Inspector.from_engine(bind) 

140 

141 @property 

142 def default_schema_name(self): 

143 """Return the default schema name presented by the dialect 

144 for the current engine's database user. 

145 

146 E.g. this is typically ``public`` for PostgreSQL and ``dbo`` 

147 for SQL Server. 

148 

149 """ 

150 return self.dialect.default_schema_name 

151 

152 def get_schema_names(self): 

153 """Return all schema names. 

154 """ 

155 

156 if hasattr(self.dialect, "get_schema_names"): 

157 return self.dialect.get_schema_names( 

158 self.bind, info_cache=self.info_cache 

159 ) 

160 return [] 

161 

162 @util.deprecated_params( 

163 order_by=( 

164 "1.0", 

165 "The :paramref:`get_table_names.order_by` parameter is deprecated " 

166 "and will be removed in a future release. Please refer to " 

167 ":meth:`_reflection.Inspector.get_sorted_table_and_fkc_names` " 

168 "for a " 

169 "more comprehensive solution to resolving foreign key cycles " 

170 "between tables.", 

171 ) 

172 ) 

173 def get_table_names(self, schema=None, order_by=None): 

174 """Return all table names in referred to within a particular schema. 

175 

176 The names are expected to be real tables only, not views. 

177 Views are instead returned using the 

178 :meth:`_reflection.Inspector.get_view_names` 

179 method. 

180 

181 

182 :param schema: Schema name. If ``schema`` is left at ``None``, the 

183 database's default schema is 

184 used, else the named schema is searched. If the database does not 

185 support named schemas, behavior is undefined if ``schema`` is not 

186 passed as ``None``. For special quoting, use :class:`.quoted_name`. 

187 

188 :param order_by: Optional, may be the string "foreign_key" to sort 

189 the result on foreign key dependencies. Does not automatically 

190 resolve cycles, and will raise :class:`.CircularDependencyError` 

191 if cycles exist. 

192 

193 .. seealso:: 

194 

195 :meth:`_reflection.Inspector.get_sorted_table_and_fkc_names` 

196 

197 :attr:`_schema.MetaData.sorted_tables` 

198 

199 """ 

200 

201 if hasattr(self.dialect, "get_table_names"): 

202 tnames = self.dialect.get_table_names( 

203 self.bind, schema, info_cache=self.info_cache 

204 ) 

205 else: 

206 tnames = self.engine.table_names(schema) 

207 if order_by == "foreign_key": 

208 tuples = [] 

209 for tname in tnames: 

210 for fkey in self.get_foreign_keys(tname, schema): 

211 if tname != fkey["referred_table"]: 

212 tuples.append((fkey["referred_table"], tname)) 

213 tnames = list(topological.sort(tuples, tnames)) 

214 return tnames 

215 

216 def get_sorted_table_and_fkc_names(self, schema=None): 

217 """Return dependency-sorted table and foreign key constraint names in 

218 referred to within a particular schema. 

219 

220 This will yield 2-tuples of 

221 ``(tablename, [(tname, fkname), (tname, fkname), ...])`` 

222 consisting of table names in CREATE order grouped with the foreign key 

223 constraint names that are not detected as belonging to a cycle. 

224 The final element 

225 will be ``(None, [(tname, fkname), (tname, fkname), ..])`` 

226 which will consist of remaining 

227 foreign key constraint names that would require a separate CREATE 

228 step after-the-fact, based on dependencies between tables. 

229 

230 .. versionadded:: 1.0.- 

231 

232 .. seealso:: 

233 

234 :meth:`_reflection.Inspector.get_table_names` 

235 

236 :func:`.sort_tables_and_constraints` - similar method which works 

237 with an already-given :class:`_schema.MetaData`. 

238 

239 """ 

240 if hasattr(self.dialect, "get_table_names"): 

241 tnames = self.dialect.get_table_names( 

242 self.bind, schema, info_cache=self.info_cache 

243 ) 

244 else: 

245 tnames = self.engine.table_names(schema) 

246 

247 tuples = set() 

248 remaining_fkcs = set() 

249 

250 fknames_for_table = {} 

251 for tname in tnames: 

252 fkeys = self.get_foreign_keys(tname, schema) 

253 fknames_for_table[tname] = set([fk["name"] for fk in fkeys]) 

254 for fkey in fkeys: 

255 if tname != fkey["referred_table"]: 

256 tuples.add((fkey["referred_table"], tname)) 

257 try: 

258 candidate_sort = list(topological.sort(tuples, tnames)) 

259 except exc.CircularDependencyError as err: 

260 for edge in err.edges: 

261 tuples.remove(edge) 

262 remaining_fkcs.update( 

263 (edge[1], fkc) for fkc in fknames_for_table[edge[1]] 

264 ) 

265 

266 candidate_sort = list(topological.sort(tuples, tnames)) 

267 return [ 

268 (tname, fknames_for_table[tname].difference(remaining_fkcs)) 

269 for tname in candidate_sort 

270 ] + [(None, list(remaining_fkcs))] 

271 

272 def get_temp_table_names(self): 

273 """return a list of temporary table names for the current bind. 

274 

275 This method is unsupported by most dialects; currently 

276 only SQLite implements it. 

277 

278 .. versionadded:: 1.0.0 

279 

280 """ 

281 return self.dialect.get_temp_table_names( 

282 self.bind, info_cache=self.info_cache 

283 ) 

284 

285 def get_temp_view_names(self): 

286 """return a list of temporary view names for the current bind. 

287 

288 This method is unsupported by most dialects; currently 

289 only SQLite implements it. 

290 

291 .. versionadded:: 1.0.0 

292 

293 """ 

294 return self.dialect.get_temp_view_names( 

295 self.bind, info_cache=self.info_cache 

296 ) 

297 

298 def get_table_options(self, table_name, schema=None, **kw): 

299 """Return a dictionary of options specified when the table of the 

300 given name was created. 

301 

302 This currently includes some options that apply to MySQL tables. 

303 

304 :param table_name: string name of the table. For special quoting, 

305 use :class:`.quoted_name`. 

306 

307 :param schema: string schema name; if omitted, uses the default schema 

308 of the database connection. For special quoting, 

309 use :class:`.quoted_name`. 

310 

311 """ 

312 if hasattr(self.dialect, "get_table_options"): 

313 return self.dialect.get_table_options( 

314 self.bind, table_name, schema, info_cache=self.info_cache, **kw 

315 ) 

316 return {} 

317 

318 def get_view_names(self, schema=None): 

319 """Return all view names in `schema`. 

320 

321 :param schema: Optional, retrieve names from a non-default schema. 

322 For special quoting, use :class:`.quoted_name`. 

323 

324 """ 

325 

326 return self.dialect.get_view_names( 

327 self.bind, schema, info_cache=self.info_cache 

328 ) 

329 

330 def get_view_definition(self, view_name, schema=None): 

331 """Return definition for `view_name`. 

332 

333 :param schema: Optional, retrieve names from a non-default schema. 

334 For special quoting, use :class:`.quoted_name`. 

335 

336 """ 

337 

338 return self.dialect.get_view_definition( 

339 self.bind, view_name, schema, info_cache=self.info_cache 

340 ) 

341 

342 def get_columns(self, table_name, schema=None, **kw): 

343 """Return information about columns in `table_name`. 

344 

345 Given a string `table_name` and an optional string `schema`, return 

346 column information as a list of dicts with these keys: 

347 

348 * ``name`` - the column's name 

349 

350 * ``type`` - the type of this column; an instance of 

351 :class:`~sqlalchemy.types.TypeEngine` 

352 

353 * ``nullable`` - boolean flag if the column is NULL or NOT NULL 

354 

355 * ``default`` - the column's server default value - this is returned 

356 as a string SQL expression. 

357 

358 * ``autoincrement`` - indicates that the column is auto incremented - 

359 this is returned as a boolean or 'auto' 

360 

361 * ``comment`` - (optional) the commnet on the column. Only some 

362 dialects return this key 

363 

364 * ``computed`` - (optional) when present it indicates that this column 

365 is computed by the database. Only some dialects return this key. 

366 Returned as a dict with the keys: 

367 

368 * ``sqltext`` - the expression used to generate this column returned 

369 as a string SQL expression 

370 

371 * ``persisted`` - (optional) boolean that indicates if the column is 

372 stored in the table 

373 

374 .. versionadded:: 1.3.16 - added support for computed reflection. 

375 

376 * ``dialect_options`` - (optional) a dict with dialect specific options 

377 

378 

379 :param table_name: string name of the table. For special quoting, 

380 use :class:`.quoted_name`. 

381 

382 :param schema: string schema name; if omitted, uses the default schema 

383 of the database connection. For special quoting, 

384 use :class:`.quoted_name`. 

385 

386 :return: list of dictionaries, each representing the definition of 

387 a database column. 

388 

389 """ 

390 

391 col_defs = self.dialect.get_columns( 

392 self.bind, table_name, schema, info_cache=self.info_cache, **kw 

393 ) 

394 for col_def in col_defs: 

395 # make this easy and only return instances for coltype 

396 coltype = col_def["type"] 

397 if not isinstance(coltype, TypeEngine): 

398 col_def["type"] = coltype() 

399 return col_defs 

400 

401 @deprecated( 

402 "0.7", 

403 "The :meth:`_reflection.Inspector.get_primary_keys` " 

404 "method is deprecated and " 

405 "will be removed in a future release. Please refer to the " 

406 ":meth:`_reflection.Inspector.get_pk_constraint` method.", 

407 ) 

408 def get_primary_keys(self, table_name, schema=None, **kw): 

409 """Return information about primary keys in `table_name`. 

410 

411 Given a string `table_name`, and an optional string `schema`, return 

412 primary key information as a list of column names. 

413 """ 

414 

415 return self.dialect.get_pk_constraint( 

416 self.bind, table_name, schema, info_cache=self.info_cache, **kw 

417 )["constrained_columns"] 

418 

419 def get_pk_constraint(self, table_name, schema=None, **kw): 

420 """Return information about primary key constraint on `table_name`. 

421 

422 Given a string `table_name`, and an optional string `schema`, return 

423 primary key information as a dictionary with these keys: 

424 

425 constrained_columns 

426 a list of column names that make up the primary key 

427 

428 name 

429 optional name of the primary key constraint. 

430 

431 :param table_name: string name of the table. For special quoting, 

432 use :class:`.quoted_name`. 

433 

434 :param schema: string schema name; if omitted, uses the default schema 

435 of the database connection. For special quoting, 

436 use :class:`.quoted_name`. 

437 

438 """ 

439 return self.dialect.get_pk_constraint( 

440 self.bind, table_name, schema, info_cache=self.info_cache, **kw 

441 ) 

442 

443 def get_foreign_keys(self, table_name, schema=None, **kw): 

444 """Return information about foreign_keys in `table_name`. 

445 

446 Given a string `table_name`, and an optional string `schema`, return 

447 foreign key information as a list of dicts with these keys: 

448 

449 constrained_columns 

450 a list of column names that make up the foreign key 

451 

452 referred_schema 

453 the name of the referred schema 

454 

455 referred_table 

456 the name of the referred table 

457 

458 referred_columns 

459 a list of column names in the referred table that correspond to 

460 constrained_columns 

461 

462 name 

463 optional name of the foreign key constraint. 

464 

465 :param table_name: string name of the table. For special quoting, 

466 use :class:`.quoted_name`. 

467 

468 :param schema: string schema name; if omitted, uses the default schema 

469 of the database connection. For special quoting, 

470 use :class:`.quoted_name`. 

471 

472 """ 

473 

474 return self.dialect.get_foreign_keys( 

475 self.bind, table_name, schema, info_cache=self.info_cache, **kw 

476 ) 

477 

478 def get_indexes(self, table_name, schema=None, **kw): 

479 """Return information about indexes in `table_name`. 

480 

481 Given a string `table_name` and an optional string `schema`, return 

482 index information as a list of dicts with these keys: 

483 

484 name 

485 the index's name 

486 

487 column_names 

488 list of column names in order 

489 

490 unique 

491 boolean 

492 

493 column_sorting 

494 optional dict mapping column names to tuple of sort keywords, 

495 which may include ``asc``, ``desc``, ``nullsfirst``, ``nullslast``. 

496 

497 .. versionadded:: 1.3.5 

498 

499 dialect_options 

500 dict of dialect-specific index options. May not be present 

501 for all dialects. 

502 

503 .. versionadded:: 1.0.0 

504 

505 :param table_name: string name of the table. For special quoting, 

506 use :class:`.quoted_name`. 

507 

508 :param schema: string schema name; if omitted, uses the default schema 

509 of the database connection. For special quoting, 

510 use :class:`.quoted_name`. 

511 

512 """ 

513 

514 return self.dialect.get_indexes( 

515 self.bind, table_name, schema, info_cache=self.info_cache, **kw 

516 ) 

517 

518 def get_unique_constraints(self, table_name, schema=None, **kw): 

519 """Return information about unique constraints in `table_name`. 

520 

521 Given a string `table_name` and an optional string `schema`, return 

522 unique constraint information as a list of dicts with these keys: 

523 

524 name 

525 the unique constraint's name 

526 

527 column_names 

528 list of column names in order 

529 

530 :param table_name: string name of the table. For special quoting, 

531 use :class:`.quoted_name`. 

532 

533 :param schema: string schema name; if omitted, uses the default schema 

534 of the database connection. For special quoting, 

535 use :class:`.quoted_name`. 

536 

537 """ 

538 

539 return self.dialect.get_unique_constraints( 

540 self.bind, table_name, schema, info_cache=self.info_cache, **kw 

541 ) 

542 

543 def get_table_comment(self, table_name, schema=None, **kw): 

544 """Return information about the table comment for ``table_name``. 

545 

546 Given a string ``table_name`` and an optional string ``schema``, 

547 return table comment information as a dictionary with these keys: 

548 

549 text 

550 text of the comment. 

551 

552 Raises ``NotImplementedError`` for a dialect that does not support 

553 comments. 

554 

555 .. versionadded:: 1.2 

556 

557 """ 

558 

559 return self.dialect.get_table_comment( 

560 self.bind, table_name, schema, info_cache=self.info_cache, **kw 

561 ) 

562 

563 def get_check_constraints(self, table_name, schema=None, **kw): 

564 """Return information about check constraints in `table_name`. 

565 

566 Given a string `table_name` and an optional string `schema`, return 

567 check constraint information as a list of dicts with these keys: 

568 

569 name 

570 the check constraint's name 

571 

572 sqltext 

573 the check constraint's SQL expression 

574 

575 dialect_options 

576 may or may not be present; a dictionary with additional 

577 dialect-specific options for this CHECK constraint 

578 

579 .. versionadded:: 1.3.8 

580 

581 :param table_name: string name of the table. For special quoting, 

582 use :class:`.quoted_name`. 

583 

584 :param schema: string schema name; if omitted, uses the default schema 

585 of the database connection. For special quoting, 

586 use :class:`.quoted_name`. 

587 

588 .. versionadded:: 1.1.0 

589 

590 """ 

591 

592 return self.dialect.get_check_constraints( 

593 self.bind, table_name, schema, info_cache=self.info_cache, **kw 

594 ) 

595 

596 def reflecttable( 

597 self, 

598 table, 

599 include_columns, 

600 exclude_columns=(), 

601 resolve_fks=True, 

602 _extend_on=None, 

603 ): 

604 """Given a Table object, load its internal constructs based on 

605 introspection. 

606 

607 This is the underlying method used by most dialects to produce 

608 table reflection. Direct usage is like:: 

609 

610 from sqlalchemy import create_engine, MetaData, Table 

611 from sqlalchemy.engine.reflection import Inspector 

612 

613 engine = create_engine('...') 

614 meta = MetaData() 

615 user_table = Table('user', meta) 

616 insp = Inspector.from_engine(engine) 

617 insp.reflecttable(user_table, None) 

618 

619 :param table: a :class:`~sqlalchemy.schema.Table` instance. 

620 :param include_columns: a list of string column names to include 

621 in the reflection process. If ``None``, all columns are reflected. 

622 

623 """ 

624 

625 if _extend_on is not None: 

626 if table in _extend_on: 

627 return 

628 else: 

629 _extend_on.add(table) 

630 

631 dialect = self.bind.dialect 

632 

633 schema = self.bind.schema_for_object(table) 

634 

635 table_name = table.name 

636 

637 # get table-level arguments that are specifically 

638 # intended for reflection, e.g. oracle_resolve_synonyms. 

639 # these are unconditionally passed to related Table 

640 # objects 

641 reflection_options = dict( 

642 (k, table.dialect_kwargs.get(k)) 

643 for k in dialect.reflection_options 

644 if k in table.dialect_kwargs 

645 ) 

646 

647 # reflect table options, like mysql_engine 

648 tbl_opts = self.get_table_options( 

649 table_name, schema, **table.dialect_kwargs 

650 ) 

651 if tbl_opts: 

652 # add additional kwargs to the Table if the dialect 

653 # returned them 

654 table._validate_dialect_kwargs(tbl_opts) 

655 

656 if util.py2k: 

657 if isinstance(schema, str): 

658 schema = schema.decode(dialect.encoding) 

659 if isinstance(table_name, str): 

660 table_name = table_name.decode(dialect.encoding) 

661 

662 found_table = False 

663 cols_by_orig_name = {} 

664 

665 for col_d in self.get_columns( 

666 table_name, schema, **table.dialect_kwargs 

667 ): 

668 found_table = True 

669 

670 self._reflect_column( 

671 table, 

672 col_d, 

673 include_columns, 

674 exclude_columns, 

675 cols_by_orig_name, 

676 ) 

677 

678 if not found_table: 

679 raise exc.NoSuchTableError(table.name) 

680 

681 self._reflect_pk( 

682 table_name, schema, table, cols_by_orig_name, exclude_columns 

683 ) 

684 

685 self._reflect_fk( 

686 table_name, 

687 schema, 

688 table, 

689 cols_by_orig_name, 

690 exclude_columns, 

691 resolve_fks, 

692 _extend_on, 

693 reflection_options, 

694 ) 

695 

696 self._reflect_indexes( 

697 table_name, 

698 schema, 

699 table, 

700 cols_by_orig_name, 

701 include_columns, 

702 exclude_columns, 

703 reflection_options, 

704 ) 

705 

706 self._reflect_unique_constraints( 

707 table_name, 

708 schema, 

709 table, 

710 cols_by_orig_name, 

711 include_columns, 

712 exclude_columns, 

713 reflection_options, 

714 ) 

715 

716 self._reflect_check_constraints( 

717 table_name, 

718 schema, 

719 table, 

720 cols_by_orig_name, 

721 include_columns, 

722 exclude_columns, 

723 reflection_options, 

724 ) 

725 

726 self._reflect_table_comment( 

727 table_name, schema, table, reflection_options 

728 ) 

729 

730 def _reflect_column( 

731 self, table, col_d, include_columns, exclude_columns, cols_by_orig_name 

732 ): 

733 

734 orig_name = col_d["name"] 

735 

736 table.dispatch.column_reflect(self, table, col_d) 

737 

738 # fetch name again as column_reflect is allowed to 

739 # change it 

740 name = col_d["name"] 

741 if (include_columns and name not in include_columns) or ( 

742 exclude_columns and name in exclude_columns 

743 ): 

744 return 

745 

746 coltype = col_d["type"] 

747 

748 col_kw = dict( 

749 (k, col_d[k]) 

750 for k in [ 

751 "nullable", 

752 "autoincrement", 

753 "quote", 

754 "info", 

755 "key", 

756 "comment", 

757 ] 

758 if k in col_d 

759 ) 

760 

761 if "dialect_options" in col_d: 

762 col_kw.update(col_d["dialect_options"]) 

763 

764 colargs = [] 

765 if col_d.get("default") is not None: 

766 default = col_d["default"] 

767 if isinstance(default, sql.elements.TextClause): 

768 default = sa_schema.DefaultClause(default, _reflected=True) 

769 elif not isinstance(default, sa_schema.FetchedValue): 

770 default = sa_schema.DefaultClause( 

771 sql.text(col_d["default"]), _reflected=True 

772 ) 

773 

774 colargs.append(default) 

775 

776 if "computed" in col_d: 

777 computed = sa_schema.Computed(**col_d["computed"]) 

778 colargs.append(computed) 

779 

780 if "sequence" in col_d: 

781 self._reflect_col_sequence(col_d, colargs) 

782 

783 cols_by_orig_name[orig_name] = col = sa_schema.Column( 

784 name, coltype, *colargs, **col_kw 

785 ) 

786 

787 if col.key in table.primary_key: 

788 col.primary_key = True 

789 table.append_column(col) 

790 

791 def _reflect_col_sequence(self, col_d, colargs): 

792 if "sequence" in col_d: 

793 # TODO: mssql and sybase are using this. 

794 seq = col_d["sequence"] 

795 sequence = sa_schema.Sequence(seq["name"], 1, 1) 

796 if "start" in seq: 

797 sequence.start = seq["start"] 

798 if "increment" in seq: 

799 sequence.increment = seq["increment"] 

800 colargs.append(sequence) 

801 

802 def _reflect_pk( 

803 self, table_name, schema, table, cols_by_orig_name, exclude_columns 

804 ): 

805 pk_cons = self.get_pk_constraint( 

806 table_name, schema, **table.dialect_kwargs 

807 ) 

808 if pk_cons: 

809 pk_cols = [ 

810 cols_by_orig_name[pk] 

811 for pk in pk_cons["constrained_columns"] 

812 if pk in cols_by_orig_name and pk not in exclude_columns 

813 ] 

814 

815 # update pk constraint name 

816 table.primary_key.name = pk_cons.get("name") 

817 

818 # tell the PKConstraint to re-initialize 

819 # its column collection 

820 table.primary_key._reload(pk_cols) 

821 

822 def _reflect_fk( 

823 self, 

824 table_name, 

825 schema, 

826 table, 

827 cols_by_orig_name, 

828 exclude_columns, 

829 resolve_fks, 

830 _extend_on, 

831 reflection_options, 

832 ): 

833 fkeys = self.get_foreign_keys( 

834 table_name, schema, **table.dialect_kwargs 

835 ) 

836 for fkey_d in fkeys: 

837 conname = fkey_d["name"] 

838 # look for columns by orig name in cols_by_orig_name, 

839 # but support columns that are in-Python only as fallback 

840 constrained_columns = [ 

841 cols_by_orig_name[c].key if c in cols_by_orig_name else c 

842 for c in fkey_d["constrained_columns"] 

843 ] 

844 if exclude_columns and set(constrained_columns).intersection( 

845 exclude_columns 

846 ): 

847 continue 

848 referred_schema = fkey_d["referred_schema"] 

849 referred_table = fkey_d["referred_table"] 

850 referred_columns = fkey_d["referred_columns"] 

851 refspec = [] 

852 if referred_schema is not None: 

853 if resolve_fks: 

854 sa_schema.Table( 

855 referred_table, 

856 table.metadata, 

857 autoload=True, 

858 schema=referred_schema, 

859 autoload_with=self.bind, 

860 _extend_on=_extend_on, 

861 **reflection_options 

862 ) 

863 for column in referred_columns: 

864 refspec.append( 

865 ".".join([referred_schema, referred_table, column]) 

866 ) 

867 else: 

868 if resolve_fks: 

869 sa_schema.Table( 

870 referred_table, 

871 table.metadata, 

872 autoload=True, 

873 autoload_with=self.bind, 

874 schema=sa_schema.BLANK_SCHEMA, 

875 _extend_on=_extend_on, 

876 **reflection_options 

877 ) 

878 for column in referred_columns: 

879 refspec.append(".".join([referred_table, column])) 

880 if "options" in fkey_d: 

881 options = fkey_d["options"] 

882 else: 

883 options = {} 

884 table.append_constraint( 

885 sa_schema.ForeignKeyConstraint( 

886 constrained_columns, 

887 refspec, 

888 conname, 

889 link_to_name=True, 

890 **options 

891 ) 

892 ) 

893 

894 _index_sort_exprs = [ 

895 ("asc", operators.asc_op), 

896 ("desc", operators.desc_op), 

897 ("nullsfirst", operators.nullsfirst_op), 

898 ("nullslast", operators.nullslast_op), 

899 ] 

900 

901 def _reflect_indexes( 

902 self, 

903 table_name, 

904 schema, 

905 table, 

906 cols_by_orig_name, 

907 include_columns, 

908 exclude_columns, 

909 reflection_options, 

910 ): 

911 # Indexes 

912 indexes = self.get_indexes(table_name, schema) 

913 for index_d in indexes: 

914 name = index_d["name"] 

915 columns = index_d["column_names"] 

916 column_sorting = index_d.get("column_sorting", {}) 

917 unique = index_d["unique"] 

918 flavor = index_d.get("type", "index") 

919 dialect_options = index_d.get("dialect_options", {}) 

920 

921 duplicates = index_d.get("duplicates_constraint") 

922 if include_columns and not set(columns).issubset(include_columns): 

923 util.warn( 

924 "Omitting %s key for (%s), key covers omitted columns." 

925 % (flavor, ", ".join(columns)) 

926 ) 

927 continue 

928 if duplicates: 

929 continue 

930 # look for columns by orig name in cols_by_orig_name, 

931 # but support columns that are in-Python only as fallback 

932 idx_cols = [] 

933 for c in columns: 

934 try: 

935 idx_col = ( 

936 cols_by_orig_name[c] 

937 if c in cols_by_orig_name 

938 else table.c[c] 

939 ) 

940 except KeyError: 

941 util.warn( 

942 "%s key '%s' was not located in " 

943 "columns for table '%s'" % (flavor, c, table_name) 

944 ) 

945 continue 

946 c_sorting = column_sorting.get(c, ()) 

947 for k, op in self._index_sort_exprs: 

948 if k in c_sorting: 

949 idx_col = op(idx_col) 

950 idx_cols.append(idx_col) 

951 

952 sa_schema.Index( 

953 name, 

954 *idx_cols, 

955 _table=table, 

956 **dict(list(dialect_options.items()) + [("unique", unique)]) 

957 ) 

958 

959 def _reflect_unique_constraints( 

960 self, 

961 table_name, 

962 schema, 

963 table, 

964 cols_by_orig_name, 

965 include_columns, 

966 exclude_columns, 

967 reflection_options, 

968 ): 

969 

970 # Unique Constraints 

971 try: 

972 constraints = self.get_unique_constraints(table_name, schema) 

973 except NotImplementedError: 

974 # optional dialect feature 

975 return 

976 

977 for const_d in constraints: 

978 conname = const_d["name"] 

979 columns = const_d["column_names"] 

980 duplicates = const_d.get("duplicates_index") 

981 if include_columns and not set(columns).issubset(include_columns): 

982 util.warn( 

983 "Omitting unique constraint key for (%s), " 

984 "key covers omitted columns." % ", ".join(columns) 

985 ) 

986 continue 

987 if duplicates: 

988 continue 

989 # look for columns by orig name in cols_by_orig_name, 

990 # but support columns that are in-Python only as fallback 

991 constrained_cols = [] 

992 for c in columns: 

993 try: 

994 constrained_col = ( 

995 cols_by_orig_name[c] 

996 if c in cols_by_orig_name 

997 else table.c[c] 

998 ) 

999 except KeyError: 

1000 util.warn( 

1001 "unique constraint key '%s' was not located in " 

1002 "columns for table '%s'" % (c, table_name) 

1003 ) 

1004 else: 

1005 constrained_cols.append(constrained_col) 

1006 table.append_constraint( 

1007 sa_schema.UniqueConstraint(*constrained_cols, name=conname) 

1008 ) 

1009 

1010 def _reflect_check_constraints( 

1011 self, 

1012 table_name, 

1013 schema, 

1014 table, 

1015 cols_by_orig_name, 

1016 include_columns, 

1017 exclude_columns, 

1018 reflection_options, 

1019 ): 

1020 try: 

1021 constraints = self.get_check_constraints(table_name, schema) 

1022 except NotImplementedError: 

1023 # optional dialect feature 

1024 return 

1025 

1026 for const_d in constraints: 

1027 table.append_constraint(sa_schema.CheckConstraint(**const_d)) 

1028 

1029 def _reflect_table_comment( 

1030 self, table_name, schema, table, reflection_options 

1031 ): 

1032 try: 

1033 comment_dict = self.get_table_comment(table_name, schema) 

1034 except NotImplementedError: 

1035 return 

1036 else: 

1037 table.comment = comment_dict.get("text", None)